diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..765207f06 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +# Customise GitHub Linguist + +# Highlight Inno Setup's .ps files as Pascal +*.ps linguist-language=Pascal +# Include Markdown files in stats +*.md linguist-detectable diff --git a/.gitignore b/.gitignore index ad252c726..64f314858 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -# .gitignore file for CodeSnip project +# .gitignore file for CodeSnip project # Delphi generated temporary files and directories *.local @@ -10,8 +10,6 @@ __history/ # Project specific directories & files -Bin -Exe -Release +_build Src/CodeSnip.cfg Src/AutoGen/IntfExternalObj.pas diff --git a/Build.html b/Build.html index 0f319dd3b..5dde59363 100644 --- a/Build.html +++ b/Build.html @@ -1,968 +1,1019 @@ - - - - - - - - - Building CodeSnip - - - - - - -

- CodeSnip Build Instructions -

- -

- Introduction -

- -

- CodeSnip is written in Object Pascal and is targeted at Delphi - XE. Compilation with earlier compilers is not guaranteed. The code will - require some changes to compile on Delphi XE2 or later. -

- -

- The are currently two editions of CodeSnip: the standard edition and - the portable edition. They both share the same code base and the different - editions are created using conditional compilation. These instructions show - how to build either edition. -

- -

- The Delphi XE IDE can be used to modify the source and to perform test builds. - Final builds should be created using the provided makefile. -

- -

- Dependencies -

- -

- Several DelphiDabbler and other 3rd party libraries and components are - required to compile CodeSnip, most of which are included in the code - repository in the Src/3rdParty directory. Code not included in - the repository is noted below. -

- -

- Indy libraries v10 -

- -

- The Indy 10 Internet components ship with Delphi XE. - If you prefer to work with the latest release you can download it from http://www.indyproject.org/. If you download a copy of Indy 10 you - should compile the source code separately with the same version of Delphi - that is being used to compile CodeSnip. -

- -

- Regardless of whether you are using the version of Indy 10 supplied with - Delphi or if you have downloaded and compiled your own version, you must set - the INDY10 environment variable to the directory where you - placed the compiled code. -

- -

- Changes between different Indy 10 releases -

- -

- Changes were made to the parameter lists of the TWorkBeginEvent - and TWorkEvent events between early and later releases of Indy - 10. Specifically, earlier versions use type Integer for the - AWorkCount parameter of TWorkEvent and the - AWorkCountMax parameter of TWorkBeginEvent, while - later versions use Int64. -

- -

- CodeSnip's source code uses conditional compilation to provide the - correct event handler signatures – and it makes an intelligent guess - at which signature to use depending on the version number provided by the - Indy library code. Should the program fail to compile with an error in the - Web.UDownloadMonitor unit, you should check the event signatures in - your Indy IdComponent unit and then define the - INDY_WORKEVENT_INT64 environment variable if Int64 - parameters are required or INDY_WORKEVENT_INT32 if - Integer parameters are used. -

- -

- Delphi RTL & VCL -

- -

- Goes without saying really, but you need the RTL and VCL that ships with - Delphi. -

- -

- Build Tools -

- -

- The following tools are required to build CodeSnip. -

- -

- Delphi -

- -

- A copy of the Delphi XE command line compiler is required to build the - object Pascal code from the provided makefile. -

- -

- You can use the Delphi IDE to edit the code and test compile it, but final - builds should be created using the makefile, which requires the following - tools that are supplied with Delphi: -

- -
-
- DCC32 -
-
- The Delphi command line compiler. -
-
- BRCC32 -
-
- The Borland resource compiler. Used to compile various resource source - (.rc) files. -
-
- GenTLB -
-
- Type library generator. Used to create the ExternalObj.tlb type - library from source code in ExternalObj.ridl. -
-
- TLibImpl -
-
- Type library importer tool. Used to create a Pascal unit that describes - code contained in ExternalObj.idl. -
-
- -

- The following environment variables are associated with these tools: -

- -
-
- DELPHIROOT - required unless DELPHIXE is set. -
-
- Should be set to the install directory of the version of Delphi being - used. DCC32, BRCC32 and TLibImpl - are expected to be in the Bin sub-directory of - DELPHIROOT. -
-
- DELPHIXE - optional -
-
- This environment variable can be set to the Delphi XE install - directory. When DELPHIXE is defined - DELPHIROOT will be set to the value of - DELPHIXE. -
-
- INDY10 - required -
-
- Must be set to the directory where the Indy 10 components are installed. - The code must have been built with the same version of Delphi used to - compile CodeSnip. -
-
- INDY_WORKEVENT_INT64 or - INDY_WORKEVENT_INT32 - optional -
-
- See above for details. If used, only one of - the environment variables may be defined. Defining both causes - compilation to fail. -
-
- -

- MAKE -

- -

- This is the make tool that ships with Delphi. You can use any version that - works. -

- -

- DelphiDabbler Version Information Editor (VIEd) -

- -

- This tool is used to compile version information (.vi) files - into intermediate resource source (.rc) files. Version 2.11.2 - or later is required. - Version Information Editor can be obtained from - http://www.delphidabbler.com/software/vied. -

- -

- The program is expected to be on the path unless its install directory is - specified by the VIEDROOT environment variable. -

- -

- DelphiDabbler HTML Resource Compiler (HTMLRes) -

- -

- HTMLRes is used to compile HTML.hrc which stores various - HTML, JavaScript, CSS and images into HTML resources. Version 1.1 or later - is required. - The HTML Resource Compiler can be obtained from - http://www.delphidabbler.com/software/htmlres. -

- -

- The program is expected to be on the path unless its install directory is - specified by the HTMLRESROOT environment variable. -

- -

- Inno Setup -

- -

- The Unicode version on the Inno setup command line compiler is needed to - create CodeSnip's install program. v5.5.2 (u) or later is required. - Earlier (Unicode) versions may work, but this is not guaranteed. -

- -

- You can get Inno Setup at http://www.jrsoftware.org/isinfo.php. Choose the Unicode version and - ensure that the ISPP pre-processor is installed. If you already have the ANSI - version the Unicode version can be installed alongside it - just use a - different install directory and program group name. -

- -

- The path to Unicode Inno Setup's install directory will be looked for in the - INNOSETUP_U environment variable, or, if that is not set, in the - INNOSETUP environment variable. If neither of these is set then - the correct version of Inno Setup is expected to be on the path. -

- -

- Note: Inno Setup is not required if you are creating only the - portable edition of CodeSnip since that edition does not have an - install program. -

- -

- Microsoft HTML Help Compiler (HHC) -

- -

- This command line compiler is supplied with Microsoft HTML Help Workshop. It - is used to compile the CodeSnip help file. -

- -

- The program is expected to be on the path unless its install directory is - specified by the HHCROOT environment variable. -

- -

- Zip -

- -

- This program is used to create CodeSnip's release file. - You can get a Windows command line version at - http://stahlforce.com/dev/index.php?tool=zipunzip. -

- -

- The program is expected to be on the path unless its install directory is - specified by the ZIPROOT environment variable. -

- -

- Note: You do not need Zip if you do not intend to create - release files. -

- -

- Preparation -

- -

- Configure the environment. -

- -

- You can configure environment variables either by modifying your system - environment variables or by creating a batch file that you run before - performing the build. -

- -

- Step 1 -

- -

- Configure the required environment variables. Compilation will fail if these - environment variables are not set: -

- - - -

- Step 2 -

- -

- Update the PATH environment variable to include - %DELPHIROOT%\Bin as its first path, i.e. do: -

- -
> set PATH=%DELPHIROOT%\Bin;%PATH%
- -

- You do not have to do this but it means you can run the preferred version of - Make from the command line without having to specify its path - every time. -

- -

- Step 3 -

- -

- Set any of the following environment variables that are needed to specify - the path to any tools that cannot be found on the path: -

- - - -

- Step 4 -

- -

- Set INDY_WORKEVENT_INT64 or INDY_WORKEVENT_INT32 - if necessary (explained above). -

- -

- Get the Source Code -

- -

- If you don't already have it, download or checkout the CodeSnip - source code. There are several options: -

- -
    -
  1. -
    - If you are a Subversion user you can: -
    - -
    - These commands get code from the repository trunk. To get the code of a - stable release replace trunk with tags/XXXX - where XXX specifies the version. -
    -
  2. -
  3. - Download source files for the current many older releases from the CodeSnip SourceForge Files Page. -
  4. -
  5. - Grab the source code of the latest release from DelphiDabbler.com. -
  6. -
- -

- Configure the Source Tree -

- -

- After checking out or downloading and extracting the source code you should - have the following directory structure: -

- -
./
-  |
-  +-- Docs                  - documentation
-  |   |
-  |   +-- ChangeLogs        - program change log files
-  |   |
-  |   +-- Design            - documents concerning program design
-  |      |
-  |      +-- FileFormats    - documentation of CodeSnip's file formats
-  |
-  +-- Src                   - main CodeSnip source code
-  |   |
-  |   +-- 3rdParty          - third party & DelphiDabbler library source code
-  |   |
-  |   +-- AutoGen           - receives automatically generated code
-  |   |
-  |   +-- Help              - help source files
-  |   |   |
-  |   |   +-- CSS           - CSS code for help files
-  |   |   |
-  |   |   +-- HTML          - HTML files included in help file
-  |   |   |
-  |   |   +-- Images        - images included in help file
-  |   |
-  |   +-- Install           - setup script and support code
-  |   |   |
-  |   |   +-- Assets        - files required for inclusion in install program
-  |   |
-  |   +-- Res               - container for files that are embedded in resources
-  |       |
-  |       +-- CSS           - CSS files
-  |       |
-  |       +-- HTML          - HTML files
-  |       |
-  |       +-- Img           - image files
-  |       |   |
-  |       |   +-- Branding  - image files used for CodeSnip branding
-  |       |   |
-  |       |   +-- Egg       - image files for 'Easter Egg'
-  |       |
-  |       +-- Misc          - other resources
-  |       |
-  |       +-- Scripts       - scripting files
-  |           |
-  |           +-- 3rdParty  - 3rd party scripting files
-  |
-  +-- Tests                 - contains test code
-      |
-      +-- Src               - test source code
-          |
-          +-- DUnit         - test source code that uses the DUnit framework
-

- If, by chance you also have Bin, Exe and / or - Release directories don't worry - all will become clear. - Subversion users may also see the usual .svn hidden - directories. If you have done some editing in the Delphi IDE you may also see - occasional hidden __history folders. -

- -

- Before you can get hacking, you need to prepare the code tree. Open a command - console and navigate into the Src sub-folder. Run any script you - have created to set environment variables then do: -

- -
> Make config
- -

- You may need to replace Make with the full path to - Make if it isn't on the path, or if the Make that - runs isn't the CodeGear / Embarcadero version. If this is the case try: -

- -
> %DELPHIROOT%\Bin\Make config
- -

- or -

- -
> %DELPHIXE%\Bin\Make config
- -

- depending on which environment variable you have set. -

- -

- Once Make config has completed your folder structure should - have acquired the following new folders, if they weren't present already: -

- -
./
-  |
-  +-- Bin                   - receives object files for CodeSnip
-  |
-  ...
-  |
-  +-- Exe                   - receives executable code and compiled help file
-  |
-  +-- Release               - receives release files
-  |
-  ...
- -

- If the Bin folder already existed, it will have been emptied. - In addition, Make will have created a .cfg file from - template in the Src folder. This .cfg file is needed - for DCC32 to run correctly. The file will be ignored by Subversion. -

- -

- Using the Delphi IDE -

- -

- If you are intending to use the Delphi IDE to compile code, you should also - do: -

- -
> Make resources
-> Make typelib
-> Make autogen
- -

- This compiles the resource files that the IDE needs to link into compiled - executables, compiles the type library from IDL code and generates the - Pascal file that provides an interface to the type library. -

- -

- If you wish to build the portable edition of CodeSnip you also need - to do: -

- -
> Make -DPORTABLE resources
- -

- and define the PORTABLE conditional define in Project - Options. The standard name for the portable exe file is - CodeSnip-p.exe, but the IDE will generate - CodeSnip.exe. You can rename the file manually. -

- -

- Note that building with the make file insted of the IDE performs all the above - steps automatically. -

- -

- Building CodeSnip -

- -

- This section guides you through building CodeSnip from the command - line, not from the IDE. -

- -

- You have several options: -

- - - -

- Each of these options is described below. All except the last assume that - Make config has been run. -

- -

- Note: This information applies only to building - CodeSnip itself, not to building and using the code in the - Test directory. -

- -

- Build the CodeSnip Executable -

- -

- This is the most common build and has a simple command: -

- -
> Make codesnip
- -

- This is the same as doing this sequence of commands: -

- -
> Make typelib
-> Make resources
-> Make autogen
-> Make pascal
- -

- The CodeSnip executable, named CodeSnip.exe will be - placed in the Exe folder. -

- -

- Portable edition -

- -

- To build the portable edition of CodeSnip you must either define the - PORTABLE environment variable or do: -

- -
> Make -DPORTABLE codesnip
- -

- Again the executable is placed in the Exe folder, but this time - it is named CodeSnip-p.exe -

- -

- Build the Help File -

- -

- To build the help file just do -

- -
> Make help
- -

- Build the Setup Program -

- -

- The setup program requires that the CodeSnip excutable and the - compiled help file are already present in the Exe directory. -

- -

- As an aside, you can make all the required files by doing: -

- -
> Make exes
- -

- Once you have built all the required files you build the setup file by - doing: -

- -
> Make setup
- -

- The setup program is named CodeSnip-Setup-x.x.x.exe, where - x.x.x is the version number extracted from CodeSnip's version - information. It is placed in the Exe directory. -

- -

- If the SpecialBuild string is defined in CodeSnip's - version information the string will be appended to the setup file name like - this CodeSnip-Setup-x.x.x-SPECIALBUILD. -

- -

- Portable edition -

- -

- CodeSnip's portable edition does not use a setup file so Make - setup does nothing except print a message if it is run when the - PORTABLE symbol is defined. -

- -

- Build the Release Zip File -

- -

- Make can create zip files containing the files that are included in a release. -

- -

- Standard edition -

- -

- The release file for the standard edition of CodeSnip includes the - setup file along with ReadMe.txt from the Docs - directory. Both must be present. -

- -

- Build the release by doing: -

- -
> Make release
- -

- By default the release file is named dd-codesnip.zip. You can - change this name by defining the RELEASEFILENAME macro or - enviroment variable. For example, you can name the file - MyRelease.zip by doing: -

- -
> Make -DRELEASEFILENAME=MyRelease.zip release
- -

- Portable edition -

- -

- The release file for the portable edition includes the portable executable - file, CodeSnip-p.exe, the help file CodeSnip.chm and - several files from the Docs directory. All must be present. -

- -

- Build the portable release by doing: -

- -
> Make -DPORTABLE release
- -

- By default the release file is named dd-codesnip-portable.zip. - You can change this name by defining the RELEASEFILENAME macro or - enviroment variable. For example, you can name the file - MyPortableRelease.zip by doing: -

- -
> Make -DPORTABLE -DRELEASEFILENAME=MyPortableRelease.zip release
- -

- Warning: If you are building both the standard and portable - releases with custom file names, make sure you supply a different value of - the RELEASEFILENAME macro for each release, otherwise the last - built release will overwrite the first. -

- - -

- Build and Release Everything -

- -

- You can do a complete build of everything, and generate the release zip file - simply by doing: -

- -
> Make
- -

- without specifying a target. This is the equivalent of: -

- -
> Make config
-> Make exes
-> Make setup
-> Make release
- -

- Portable edition -

- -

- To perform a complete build of the portable edition of CodeSnip do -

- -
> Make -DPORTABLE
- -

- Clean Up -

- -

- Various temporary files and directories are created by the IDE. These can be - deleted by running. -

- -
> Make clean
- -

- Warning: This command removes the __history - folders that Delphi uses to maintain earlier versions of files. -

- -

- Running the Tests -

- -

- At present all tests use the DUnit unit testing framework and are - combined into a single test application. -

- -

- To compile the tests, open the .\Src\CodeSnip.groupproj group - project file in the Delphi XE IDE. Now select the CodeSnipTests.exe - target in the project manager and compile. -

- -

- If they were not already present Bin and Exe - sub-directories will have been created in the .\Tests directory. - The Exe directory contains the DUnit test program while - Bin contains intermediate binaries. -

- -

- You can compile the tests as either a GUI application (default) or as a - console application. For details please see the comments in - .\Tests\Src\DUnit\CodeSnipTests.dpr. -

- -

- License -

- -

- The majority of CodeSnip's original source code is licensed under the - Mozilla Public License v2.0. The are a few exceptions, mainly relating to - third party source code and image files. For full details of all applicable - licenses please read License.html in the Docs - directory. -

- - - - + + + + + + + + + Building CodeSnip + + + + + + +

+ CodeSnip Build Instructions +

+ +

+ Introduction +

+ +

+ CodeSnip is written in Object Pascal and is targeted at Delphi + XE. Compilation with other compilers is not guaranteed. +

+ +

+ For an explanation of why CodeSnip still uses Delphi XE see + FAQ 11 of the CodeSnip Compiling & Source Code FAQs. +

+ +

+ The are currently two editions of CodeSnip: the standard edition and + the portable edition. They both share the same code base: the different + editions are created using conditional compilation. These instructions show + how to build either edition. +

+ +

+ Dependencies +

+ +

+ Several DelphiDabbler and other 3rd party libraries and components are + required to compile CodeSnip. They are all included in the code + repository in the Src/3rdParty directory. +

+ +

+ It goes without saying that you will also need the RTL and VCL that ships with + Delphi. +

+ +

+ Build Tools +

+ +

+ The following tools are required to build CodeSnip. +

+ +

+ Delphi +

+ +

+ All the following tools that ship with Delphi XE are required: +

+ +
+
+ MAKE +
+
+ The make tool – do not use the Microsoft make tool. +
+
+ DCC32 +
+
+ The Delphi command line compiler. +
+
+ BRCC32 +
+
+ The Borland resource compiler. Used to compile various resource source + (.rc) files. +
+
+ GenTLB +
+
+ Type library generator. Used to create the ExternalObj.tlb type + library from source code in ExternalObj.ridl. +
+
+ TLibImpl +
+
+ Type library importer tool. Used to create a Pascal unit that describes + code contained in ExternalObj.ridl. +
+
+ +

+ The following environment variables are associated with these tools: +

+ +
+
+ DELPHIROOT - required unless DELPHIXE is set. +
+
+ Should be set to the install directory of the version of Delphi being + used. DCC32, BRCC32 and TLibImpl + are expected to be in the Bin sub-directory of + DELPHIROOT. +
+
+ DELPHIXE - optional +
+
+ This environment variable can be set to the Delphi XE install + directory. When DELPHIXE is defined + DELPHIROOT will be set to the value of + DELPHIXE. +
+
+ +

+ DelphiDabbler Version Information Editor (VIEd) +

+ +

+ This tool is used to compile version information (.vi) files and + any associated macro file(s) into intermediate resource source + (.rc) files. Version 2.15.0 or later is required. Version + Information Editor can be obtained from + https://github.com/delphidabbler/vied/releases. +

+ +

+ The program is expected to be on the path unless its install directory is + specified by the VIEDROOT environment variable. +

+ +

+ DelphiDabbler HTML Resource Compiler (HTMLRes) +

+ +

+ HTMLRes is used to compile HTML.hrc which adds various + HTML, JavaScript, CSS and images to HTML resources. Version 1.1 or later + is required. + The HTML Resource Compiler can be obtained from + https://github.com/delphidabbler/htmlres/releases. +

+ +

+ The program is expected to be on the path unless its install directory is + specified by the HTMLRESROOT environment variable. +

+ +

+ Inno Setup +

+ +

+ The Unicode version of the Inno Setup v5 command line + compiler is needed to create CodeSnip's install program. v5.5.2(u) or + later is required. +

+ +

+ Warning: Do not use Inno Setup v6. This will fail to compile + the setup script. Inno Setup 6 does not support Windows 2000 or XE, while + CodeSnip still does. +

+ +

+ You can get Inno Setup at https://www.jrsoftware.org/isinfo.php. Choose the Unicode version and + ensure that the ISPP pre-processor is installed. If you already have the ANSI + version the Unicode version can be installed alongside it - just use a + different install directory and program group name. +

+ +

+ The path to Unicode Inno Setup's install directory will be looked for in the + INNOSETUP_U environment variable, or, if that is not set, in the + INNOSETUP environment variable. If neither of these is set then + the correct version of Inno Setup is expected to be on the path. +

+ +

+ Note: Inno Setup is not required if you are creating only the + portable edition of CodeSnip since that edition does not have an + install program. +

+ +

+ Microsoft HTML Help Compiler (HHC) +

+ +

+ This command line compiler is supplied with Microsoft HTML Help Workshop. It + is used to compile the CodeSnip help file. +

+ +

+ The program is expected to be on the path unless its install directory is + specified by the HHCROOT environment variable. +

+ +

+ Zip +

+ +

+ This program is used to create CodeSnip's release file. The InfoZip + version of zip is required. You can get a Windows command line version at + http://stahlforce.com/dev/index.php?tool=zipunzip. +

+ +

+ Warning: The above link is http only. If you or + your browser object to the insecure link you can download an identical version + from delphidabbler.com, using the https protocol. See https://delphidabbler.com/extras/info-zip. +

+ +

+ The program is expected to be on the path unless its install directory is + specified by the ZIPROOT environment variable. +

+ +

+ You do not need Zip if you do not intend to create release files. +

+ + +

+ Preparation +

+ +

+ Configure the environment. +

+ +

+ You can configure environment variables either by modifying your system + environment variables or by creating a batch file that you run before + performing the build. +

+ +

+ Step 1 +

+ +

+ Configure the required environment variables. Compilation will fail if these + environment variables are not set: +

+ + + +

+ Step 2 +

+ +

+ Update the PATH environment variable to include + %DELPHIROOT%\Bin as its first path, i.e. do: +

+ +
> set PATH=%DELPHIROOT%\Bin;%PATH%
+ +

+ You do not have to do this but it means you can run + Make from the command line without having to specify its path + every time. +

+ +

+ Step 3 +

+ +

+ Set any of the following environment variables that are needed to specify + the path to any tools that cannot be found on the path: +

+ + + +

+ Get the Source Code +

+ +

+ The source code is maintained in the delphidabbler/codesnip Git respository on GitHub. Source code can be obtained in three ways: +

+ +
    +
  1. +

    + Fork the project from GitHub and then clone your forked repository. +

    +
  2. +
  3. +

    + Clone the existing repository using: +

    +
    > git clone https://github.com/delphidabbler/codesnip.git
    +
  4. +
  5. +

    + Download the source of a specific release from the project's Releases section on GitHub – just choose the version you want. +

    +
  6. +
+ +

+ If you are intending to contribute code to the project please read the most up to date version of the project's read-me file before doing so. +

+ +

+ Important: If you are planning to fork CodeSnip and to develop and release your own application derived from the CodeSnip code base then some changes to the code are required under the terms of the CodeSnip license. See the Conditions For Release of Modified Code section below for details. +

+ +

+ Configure the Source Tree +

+ +

+ After forking the repository or downloading and extracting the source code you should have the following directory structure: +

+ +
./
+  |
+  +-- Docs                    - documentation
+  |   |
+  |   +-- Design              - documents concerning program design
+  |      |
+  |      +-- FileFormats      - documentation of CodeSnip's file formats
+  |
+  +-- Src                     - main CodeSnip source code
+  |   |
+  |   +-- 3rdParty            - third party & DelphiDabbler library source code
+  |   |
+  |   +-- AutoGen             - receives automatically generated code
+  |   |
+  |   +-- Help                - help source files
+  |   |   |
+  |   |   +-- CSS             - CSS code for help files
+  |   |   |
+  |   |   +-- HTML            - HTML files included in help file
+  |   |   |
+  |   |   +-- Images          - images included in help file
+  |   |
+  |   +-- Install             - setup script and support code
+  |   |   |
+  |   |   +-- Assets          - files required for inclusion in install program
+  |   |
+  |   +-- Res                 - container for files that are embedded in resources
+  |       |
+  |       +-- CSS             - CSS files
+  |       |
+  |       +-- HTML            - HTML files
+  |       |
+  |       +-- Img             - image files
+  |       |   |
+  |       |   +-- AltBranding - image files used for 3rd party branding
+  |       |   |
+  |       |   +-- Branding    - image files used for CodeSnip branding only
+  |       |   |
+  |       |   +-- Egg         - image files for 'Easter Egg'
+  |       |
+  |       +-- Misc            - other resources
+  |       |
+  |       +-- Scripts         - scripting files
+  |           |
+  |           +-- 3rdParty    - 3rd party scripting files
+  |
+  +-- Tests                   - contains test code
+      |
+      +-- Src                 - test source code
+          |
+          +-- DUnit           - test source code that uses the DUnit framework
+

+ If, by chance you also have a _build directory don't worry - all will become clear. + Git users may also see the usual .git hidden + directory. If you have done some editing in the Delphi IDE you may also see + occasional hidden __history folders. +

+ +

+ Before you can get hacking, you need to prepare the code tree. Open a command + console then run any script you may have created to set the required environment variables. Now navigate into the Src sub-folder and do: +

+ +
> Make config
+ +

+ You may need to replace Make with the full path to + Make if it isn't on the path. If this is the case try: +

+ +
> %DELPHIROOT%\Bin\Make config
+ +

+ or +

+ +
> %DELPHIXE%\Bin\Make config
+ +

+ depending on which environment variable you have set. +

+ +

+ Once Make config has run your folder structure should + have acquired the following new folders, if they weren't present already: +

+ +
./
+  |
+  +-- _build                  - contains all the build files
+  |   |
+  |   +-- bin                 - receives object files for CodeSnip
+  |   |
+  |   +-- exe                 - receives executable code and compiled help file
+  |   |
+  |   +-- release             - receives release files
+  |       |
+  |       +-- ~tmp~           - store for temp files ceated in release process
+  |
+  ...
+ +

+ If the _build/bin folder already existed, it will have been emptied. + In addition, Make will have created a .cfg file from a + template in the Src folder. This .cfg file is needed + for DCC32 to run correctly. The file will be ignored by Git. +

+ +

+ Using the Delphi IDE +

+ +

+ If you are intending to use the Delphi IDE to compile code, you should also + do: +

+ +
> Make resources
+> Make typelib
+> Make autogen
+ +

+ This compiles the resource files that the IDE needs to link into compiled + executables, compiles the type library from IDL code and generates the + Pascal file that provides an interface to the type library. +

+ +

+ If you wish to build the portable edition of CodeSnip you also need + to do: +

+ +
> Make -DPORTABLE resources
+ +

+ and define the PORTABLE conditional define in Project + Options. The standard name for the portable exe file is + CodeSnip-p.exe, but the IDE will generate + CodeSnip.exe. You can rename the file manually. +

+ +

+ After you have gone through these steps you can edit Pascal code and test + compile from the Delphi IDE. However if you change any files compiled into resources, or the type library, or run a clean up, then you must repeat the + above steps and do a complete build from the IDE. +

+ +

+ Note that building with the make file insted of the IDE performs all the above + steps automatically. +

+ +

+ Building CodeSnip +

+ +

+ This section guides you through building CodeSnip from the command + line, not from the IDE. +

+ +

+ You have several options: +

+ +
    +
  1. + Build the CodeSnip Executable +
  2. +
  3. + Build the Help File. +
  4. +
  5. + Build the Setup Program. +
  6. +
  7. + Build the Release Zip File. +
  8. +
  9. + Build and Release Everything. +
  10. +
  11. + Clean Up. +
  12. +
+ +

+ Each of these options is described below. All except options 5 and 6 assume that + Make config has been run. +

+ +

+ Note: This information applies only to building + CodeSnip itself, not to building and using the code in the + Test directory. +

+ +

+ Build the CodeSnip Executable +

+ +

+ This is the most common build and has a simple command: +

+ +
> Make codesnip
+ +

+ This is the same as doing this sequence of commands: +

+ +
> Make typelib
+> Make resources
+> Make autogen
+> Make pascal
+ +

+ The CodeSnip executable, named CodeSnip.exe will be + placed in the _build\exe folder. +

+ +

+ Portable edition +

+ +

+ To build the portable edition of CodeSnip you must either define the + PORTABLE environment variable or do: +

+ +
> Make -DPORTABLE codesnip
+ +

+ Again the executable is placed in the _build\exe folder, but this time + it is named CodeSnip-p.exe +

+ +

+ Build the Help File +

+ +

+ To build the help file just do +

+ +
> Make help
+ +

+ The compiled help file will be written to the _build\exe folder. +

+ +

+ The same help file is used for the standard and portable editions. +

+ +

+ Build the Setup Program +

+ +

+ The setup program requires that the CodeSnip executable and the + compiled help file are already present in the _build\exe directory. +

+ +

+ As an aside, you can make all the required files by doing: +

+ +
> Make exes
+ +

+ Once you have built all the required files you build the setup file by + doing: +

+ +
> Make setup
+ +

+ The setup program is named CodeSnip-Setup-x.x.x.exe, where + x.x.x is the version number extracted from CodeSnip's version + information. It is placed in the _build\exe directory. +

+ +

+ If the SpecialBuild string is defined in CodeSnip's + version information the string will be appended to the setup file name like + this CodeSnip-Setup-x.x.x-SPECIALBUILD. +

+ +

+ Portable edition +

+ +

+ CodeSnip's portable edition does not use a setup file so Make + setup does nothing except print a message if it is run when the + PORTABLE symbol is defined. +

+ +

+ Build the Release Zip File +

+ +

+ Make can create zip files containing all the files that are included in a release. + Zip files are written to the _build\release directory. +

+ +

+ Standard edition +

+ +

+ The release zip file for the standard edition requires that the setup files is already + present in the _build\exe directory. +

+ +

+ The release file includes the setup file along with ReadMe.txt + that is automatically generated from Docs\ReadMe-standard.txt. +

+ +

+ Build the release by doing: +

+ +
> Make release
+ +

+ By default the release file is named codesnip-exe.zip. You can + change this name by defining the RELEASEFILENAME macro or + enviroment variable. For example, you can name the file + MyRelease.zip by doing: +

+ +
> Make -DRELEASEFILENAME=MyRelease release
+ +

+ Note that the .zip extension should not be included in the file name. +

+ +

+ Portable edition +

+ +

+ The release zip file for the portable edition cannot be created until the + CodeSnip excutable and the compiled help file are already present in the + _build\exe directory. +

+ +

+ The release file includes the portable executable file, CodeSnip-p.exe, + the help file CodeSnip.chm, Docs\License.html and + ReadMe.txt that is automatically generated from + Docs\ReadMe-portable.txt. +

+ +

+ Build the portable release by doing: +

+ +
> Make -DPORTABLE release
+ +

+ By default the release file is named dd-codesnip-portable.zip. + You can change this name by defining the RELEASEFILENAME macro or + enviroment variable. For example, you can name the file + MyPortableRelease.zip by doing: +

+ +
> Make -DPORTABLE -DRELEASEFILENAME=MyPortableRelease release
+ +

+ Once again note that the .zip extension should not be included in the file name. +

+ +

+ Warning: If you are building both the standard and portable + releases with custom file names, make sure you supply a different value of + the RELEASEFILENAME macro for each release, otherwise the last + built release will overwrite the first. +

+ +

+ Including version numbers in zip file names +

+ +

+ A version number can be suffixed to the release zip file name by defining the VERSION macro. + This macro works with both the PORTABLE and RELEASEFILENAME macros. +

+ +

+ For example to appended version number 4.22.0 to the zip file name on a standard edition build, with the default + file name do: +

+ +
> Make -DVERSION=4.22.0 release
+ +

+ This will create a zip file named codesnip-exe-4.22.0.zip. +

+ +

+ A more complex example would be to append the same version number to a portable edition build named MyPortableRelease. Do: +

+ +
> Make -DPORTABLE -DRELEASEFILENAME=MyPortableRelease -DVERSION=4.22.0 release
+ +

+ This time the resulting zip file will be named MyPortableRelease-4.22.0.zip. +

+ +

+ Build and Release Everything +

+ +

+ You can do a complete build of everything, and generate the release zip file + simply by doing: +

+ +
> Make
+ +

+ without specifying a target. This is the equivalent of: +

+ +
> Make config
+> Make exes
+> Make setup
+> Make release
+ +

+ To perform a complete build of the portable edition of CodeSnip do +

+ +
> Make -DPORTABLE
+ +

+ Note that the RELEASEFILENAME and VERSION macros that can be used for customising + zip file names can be used here too. +

+ +

+ There is also a quicker way to build a release, but you must provide a version number to use it. First navigate up + to the repository root. Then run +

+ +
> Deploy 9.9.9
+ +

+ where 9.9.9 is the release version number. +

+ +

+ This command will build both the standard and portable executables, the help file, the standard edition setup file + and finally create the release zip files for both editions, with the release version number incorporated in the file names. +

+ +

+ Using Deploy 9.9.9 is the equivalent of doing: +

+ +
> cd Src
+> Make -DVERSION=9.9.9
+> Make -DPORTABLE -DVERSION=9.9.9
+> cd ..
+ +

+ Clean Up +

+ +

+ Various temporary files and directories are created by the IDE. These can be + deleted by running. +

+ +
> Make clean
+ +

+ Warning: This command removes the __history + folders that Delphi uses to roll back to earlier versions of files. +

+ +

+ Running the Tests +

+ +

+ At present all tests use the DUnit unit testing framework and are + combined into a single test application. +

+ +

+ To compile the tests, open the Src\CodeSnip.groupproj group + project file in the Delphi XE IDE. Now select the CodeSnipTests.exe + target in the project manager and compile. +

+ +

+ If they were not already present Bin and Exe + sub-directories will have been created in the Tests directory. + The Exe directory contains the DUnit test program while + Bin contains intermediate binaries. +

+ +

+ You can compile the tests as either a GUI application (default) or as a + console application. For details please see the comments in + Tests\Src\DUnit\CodeSnipTests.dpr. +

+ +

+ License +

+ +

+ The majority of CodeSnip's original source code is licensed under the + Mozilla Public License v2.0. The are a few exceptions, mainly relating to + third party source code and image files. For full details of all applicable + licenses please read License.html in the Docs + directory. +

+ +

+ Conditions For Release of Modified Code +

+ +

+ If you are intending to release your own application based on the CodeSnip source code you must either change the source code as described below or seek written permission to use the DelphiDabbler CodeSnip branding. To seek such permission please use the CodeSnip Issue Tracker on GitHub. +

+ +

+ Required Changes +

+ +

+ The changes are required to remove DelphiDabbler CodeSnip copyrighted branding from the program, to prevent interference with existing CodeSnip installations and to remove any implied endorsement of the modified release. You must: +

+ +
    +
  1. +

    + Replace the files in the Src\Res\Img\Branding directory with copies of the identically named placeholder files in the Src\Res\Img\AltBranding directory. The placeholder files are Public Domain, so you may use them as-is, edit them or replace them. If you delete the files in Src\Res\Img\Branding without copying the placeholder files across then CodeSnip will fail to build. +

    +
  2. +
  3. +

    + Replace all relevant references, in source code and documentation, to the names "CodeSnip" and "DelphiDabbler" with your own company and program name. Relevant occurences are: +

    + +
  4. +
  5. +

    + Provide your own license file with content compatible with the requirements of the CodeSnip license as it relates to the code reused from the CodeSnip source tree. Do not edit or re-use Docs/License.html. +

    +
  6. +
  7. +

    + Modify source code and documentation where necessary to acknowledge the origins of the program's source code, documentation and images, in accordance with the CodeSnip license. +

    +
  8. +
+ +

+ Note that the CodeSnip license can be found in Docs\License.html. +

+ +

+ If you are unsure about whether your changes meet the license requirements then you can seek clarification by creating an issue on the aforementioned Issue Tracker. +

+ + + + diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..d3fbdcf23 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,2331 @@ +# Changelog + +This is the change log for _DelphiDabbler CodeSnip_. It begins with the first ever pre-release version of _CodeSnip_. + +Releases are listed in reverse version number order. + +> Note that _CodeSnip_ v4 was developed in parallel with v3 for a while. As a consequence some v3 releases have later release dates than early v4 releases. + +## Release v4.26.0 of 02 May 2025 + +* Updated the dialogue box displayed when saving units and annotated source code [issue #166]: + * The _File Encoding_ drop down list control is disabled if there is only one encoding option. + * Updated and clarified the naming of encodings in the _File Encoding_ drop down list. + * The sole encoding option displayed for the _Rich text file_ file type was changed from the erroneous ANSI to the correct ASCII. +* Fixed bug where, when ANSI encoding was selected in the _Save Unit_ and _Save Annotated Source_ dialogue boxes, snippets containing characters not supported in the default locale's code page were being rendered diffently in the Preview dialogue box to when saved to file [issue #164]. The previewed code is now the same as that of the saved source code. +* Updated file formats available when the _File | Save Snippet Information_ menu option is selected: + * Syntax highlighting of the existing RTF format output is now optional. + * Added the option to save snippet information in the following new formats: + * Plain text, in UTF-8, UTF-16LE, UTF-16BE and the system locale's default ANSI code page. [issue #162] + * HMTL 5 with optional syntax highlighting, in UTF-8 format [issue #153]. + * XHTML with optional syntax highlighting, in UTF-8 format [issue #153]. + * Markdown, in UTF-8, UTF-16LE, UTF-16BE and the system locale's default ANSI code page [issue #155]. + * Changed the _Save Snippet Information_ dialogue box: + * It is now based on that used for saving unit and annotated source code in that file encoding and snippet highlighting can be customised where relevant, although the _Comment style_ controls are disabled since they are not relevant. + * The suggested file name was changed from "SnippetInfo" to the display name of the selected snippet. + * The dialogue box caption now contains the display name of the selected snippet. +* Changed the title of the _Save Annotated Source_ dialogue box when displaying snippets. +* Added option to prevent descriptive comments from appearing in the implementation section of generated units. A check box for this option has been added to the _Code Formatting_ tab of the _Preferences_ dialogue box [issue #85]. +* The _Help | CodeSnip News Blog_ menu item was changed to link to the [DelphiDabbler Blog](https://delphidabbler.blogspot.com/) instead of the CodeSnip Blog, because the latter is to be closed down. The menu item was renamed to _Help | CodeSnip News On DelphiDabbler Blog_ [issue #161]. +* Improved how the CSS used in generated HTML 5 and XHTML files is generated: + * The ordering of CSS selectors can now be pre-determined. + * CSS lengths and sizes can now be specified in units, such as `em`, instead of just pixels. +* Refactored the `USourceGen` unit to remove an unnecessary dependency on user preferences [issue #167]. +* Updated the help file: + * Re changes when saving snippet information [issue #163]. + * Re changes to the _Save Unit_ and _Save Annotated Source_ dialogue boxes. + * Re changes to the blog linked from the _Help_ menu. + * Re the new option to inhibit comments in the implementation sections of generated units. +* Updated documentation: + * File format documentation was changed re the addition of the Markdown file format and the changes to the encodings used in saved files. + * Read-me files were updated re the change of news blog. + +## Release v4.25.0 of 19 April 2025 + +* Added new feature to save snippet information to file in RTF format using the new _File | Save Snippet Information_ menu option [issue #140]. +* Added the option to save optionally highlighted annotated source code and units in HTML 5 format [issue #87]. +* Fixed malformed bullet character(s) in the list of imported snippets on the last page of the Snippets Import Wizard dialogue box [issue #147]. +* Improved the solution to the crash after hibernation bug, initially fixed in v4.24.1 and v4.24.2, with much improved and more stable code [issue #158]. Implemented by [@SirRufo](https://github.com/SirRufo). +* Overhauled rich text format processing: + * Fixed bug where Unicode characters that don't exist in the system code page were not being displayed correctly [issue #157]. + * Fixed potential bug where some reserved ASCII characters may not be escaped properly [issue #159]. + * Refactored and improved the rich text handling code [issue #100]. +* Corrected the copyright date displayed in the About Box to include 2025 [issue #149]. +* Documentation changes: + * Fixed error in the export file formation documentation and related help topic [issue #151]. + * Corrected erroneous comments for the _TREMLEntities.MapToEntity_ method [issue #84]. + * Updated file format documentation with details the changes introduced when implementing issues #87 and #140. + * Updated the help file with details of the new features added in this release. + +## Release v4.24.2 of 14 April 2025 + +Hotfix release. + +* Updated bug fix implemented in v4.24.1 to avoid relying on a potentially problematic windows event [issue #70 (2nd attempt)]. +* Corrected release date error for v4.24.1 in `CHANGELOG.md`. + +## Release v4.24.1 of 13 April 2025 + +* Fixed bug where CodeSnip occasionally crashes after a computer resumes from hibernation [issue #70]. +* Updated license copyright dates for 2025. + +## Release v4.24.0 of 23 October 2024 + +* Compilers with which a snippet has not been tested are now omitted from snippet information that is copied to the clipboard and included in print outs [issue #143]. +* Reversed order of compilers in the snippets editor's _Compile Results_ tab so that later compilers are display first. This change was accidentally left out of release v4.22.0 when similar changes were made in other parts of the UI [issue #135]. +* Release version number is now displayed in the program title bar [issue #122]. +* Fixed incorrect copyright date displayed in About Box [issue #129]. +* Fixed bug when checking for correct preamble bytes (BOMs) in UTF-8 and UTF-16 format text files [issue #139]. +* Portable and Standard edition now use the same program names. Portable edition was previously declaring itself as _DelphiDabbler CodeSnip-p_ instead of _DelphiDabbler CodeSnip_ [issue #130]. +* Updated operating system detection code [issues #126 and #144]. +* Added `Deploy.bat` script to create and package both the CodeSnip standard and portable releases [issue #128]. +* Documentation changes: + * CodeSnip standard and portable releases now each have their own release read-me files instead of both releases being shipped with the same read-me [issue #127]. Updated `Build.html` and `README.md` re this change. + * Updated and corrected REML documentation and REML help topic. Those documents and others that discuss REML were also changed to link to authoritative REML definitions in the `delphidabbler/reml` repository. [issues #131, #133 & #134]. + * Updated `Build.html` with alternative, more secure, download link for `zip.exe` program that is required to package releases [issue #137]. + +## Release v4.23.0 of 02 April 2024 + +* Removed marketing names (e.g. "Athens" or "Rio") from Delphi compiler names to save space when the compiler names are displayed in the UI [issue #125]. +* Added new `'` entity to REML markup language and boosted REML version to v6 as a consequence [issue #99]. +* Refactored class helper code by splitting a single monolithic unit into three more specialised units [issue #90]. +* Updated documentation and related help topic re change to REML v6. + +## Release v4.22.0 of 08 November 2023 + +* Added support for test compiling snippets with Delphi 12 Athens [issue #121]. +* Documentation changes re addition of support for Delphi 12: + * File format additions for config, export, user database and main database. + * `Docs/ReadMe.txt`. + * Relevant help topics. +* Reversed order in which compilers are listed in the Configure Compilers and Find Compilers dialogue boxes so that the most recent version of Delphi is listed first [issue #51]. +* Refactored out all `with` statements from Pascal source code [issue #118]. +* Fixed error in `CHANGELOG.md` entry for release v4.21.2 [issue #120]. + +## Release v4.21.2 of 14 July 2023 + +* Removed broken links and fixed unsafe links in the About box [issue #105]. +* Fixed bug in version information files that resulted in an error in the Comments section of the version information of both editions of _CodeSnip_ [issue #106]. +* Fixed potential XSS vulnerability in JQuery code used in Easter egg [issue #107]. +* Documentation changes: + * Rationalised, corrected, updated and clarified licensing information. These changes affected many documentation files. [issue #108]. + * Overhauled `README.md` and `Docs/ReadMe.txt` and created a new `CONTRIBUTING.md` file that explains how to contribute in detail [issue #104]. + +## Release v4.21.1 of 09 April 2023 + +* Completed implementation of support for [REML version 5](https://htmlpreview.github.io/?https://github.com/delphidabbler/codesnip/blob/version-4.21.0/Docs/Design/reml.html) (ommitted from v4.20.0 in error) and fixed some bugs in the original implementation [issues #81 and #82], including: + * Heavily revised "active text" handling code and document model to fix support for lists introduced in v4.21.0. + * Added support for rendering lists in plain text reports and generated source code header comments. + * Added support for rendering lists in Rich Text Format for use in printed information and in reports copied to the clipboard. + * Overhauled HTML rendering code that generates HTML for display in the UI. + * Heavily revised parsing and generation of REML code. + * Updated "active text" validation code. +* Prevented snippets editor from stripping REML `

` tags [issue #103]. +* Fixed garbled copyright symbols in generated source code [issue #80]. +* Fixed bug in code that compresses multiple white space into a single space [issue #95]. +* Fixed out of range error in code that handles text encodings [issue #97]. +* Fixed broken formatting of compiler result tables in text and rich text snippet reports & print outs [issue #101]. +* Updated copyright date displayed in about box [issue #98]. +* Updated operating system detection code to detect Windows 10/11 builds released in December 2022 and Q1 2023. +* Some refactoring [including issue #83] +* Changed build process to create all files in `_build` directory and to use different zip file names [issue #78]. +* Documentation changes: + * Updated `Build.html` to document changes in build process. + * Updated `CHANGELOG.md` to fix broken link [issue #76] and to remove information about semantic versioning. + * Removed broken links in `Docs/License.html`. + * Updated copyright date in various license files [including issue #96]. + * Fixed errors and oversights in REML documentation. +* Removed some redundant tests that were failing due to passing invalid parameters to the revised _StrWrap_ routine [issue #79]. + +## Release v4.21.0 of 16 December 2022 + +* Updated to support [REML version 5](https://htmlpreview.github.io/?https://github.com/delphidabbler/codesnip/blob/version-4.21.0/Docs/Design/reml.html) in snippet description & extra information [issue #71]: + * Numerous new character entities supported. + * New list tags: `

    `, ` - Other IDs could be used for other backup types.
    FileCount @@ -374,7 +370,6 @@

    'DBAC' for user database backup. - Other IDs could be used for other backup types.
    FileCount @@ -423,13 +418,13 @@

    Code that writes backup files should only ever write the latest available file - format at the time the code was written. It should never attempt to write + format at the time the code was written. It musr never attempt to write earlier versions.

    - Code that restores backup files be able to interpret all earlier formats in - addition to the current one because the program may to restore data from an + Code that restores backup files must be able to interpret all earlier formats in + addition to the current one, because the program may need to restore data from an old backup file. If the code detects a later version than it was designed for it should display an error message and terminate.

    @@ -440,9 +435,9 @@

    Some earlier versions of the backup restoration code have faults in that they - will attempt to read later backup file versions than thosethey were designed - for. By good fortune factors in the design of later file formats mean that the - reading code always abandons the reading, albeit with no error message. These + will attempt to read later backup file versions than those they were designed + for. By good fortune, factors in the design of later file formats mean that the + reading code always abandons reading later, albeit with no error message. These problems are:

    @@ -458,11 +453,10 @@

    The reading code expects backup files to begin with a file count as a text - hex representation of a SmallInt. In file version 2 and later the first + representation of a SmallInt in hex. In file version 2 and later the first four ANSI chars of the file are 'FFFF', which decodes as -1. The reader interprets this value as meaning there are no - files in the backup and does nothing, leaving the existing database - intact. + files in the backup and does nothing.
    @@ -476,7 +470,7 @@

    In v3 files the file ID is recorded in the same location and same format as the v2 file format expects to find the file count. Luckily the only two values in use as File IDs are 'CBAC' and 'DBAC', - both of which decode as negative integers. The reader software interprets + both of which decode as negative integers expressed in hex. The reader software interprets this as meaning there are no files in the backup.
    @@ -498,11 +492,11 @@

    work with file format v3 will also try to read later file versions. It looks for its file count in a hex encoded SmallInt value at position 12 in the backup file. File format v4 was designed to have a padding value of - '0000' in this location. Agains this fools the reader software + '0000' in this location. This fools the v3 reader software into thinking there are no files in the backup.

    - From CodeSnip v3.8.6 the file reader works correctly. + From CodeSnip v3.8.6 the file reader works correctly and exits with an error when an unsupported backup file version is encountered.
    @@ -536,7 +530,7 @@

    File format version 1

    - Watermark: '' + Watermark: '' (empty)
    File format version 2 @@ -561,8 +555,8 @@

    It can be seen that the version number at byte offset 4 is not necessary for identification if the watermark is made longer. However, it should always be - used because earlier versions of the file readers use it to determine which - format is to be read. + used because earlier versions of the file readers may use it to determine which + format version is to be read.

    diff --git a/Docs/Design/FileFormats/config.html b/Docs/Design/FileFormats/config.html index 8d3a9f12a..915d7098f 100644 --- a/Docs/Design/FileFormats/config.html +++ b/Docs/Design/FileFormats/config.html @@ -1,18 +1,15 @@ + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> - + @@ -49,15 +46,11 @@

    - CodeSnip uses two config files – one system wide application file named - Common.config and another per-user file named - User.config. There will be a different one of the latter for each - user account on the system where CodeSnip is used. + CodeSnip uses two config files – one system wide application file named Common.config and another user file named User.config. The standard version of CodeSnip maintains separate user config file for each user account that uses CodeSnip, while the portable version has just one such file.

    - Config files use the standard .ini file format and are written and read using - Windows API calls. + Config files use the standard INI file format and are written and read using Windows API calls.

    @@ -66,27 +59,19 @@

    • - String – stored as a sequence of characters, optionally - enclosed in double quotes. + String – stored as a sequence of characters, optionally enclosed in double quotes.
    • - Boolean – stored as the text False and - True (although 0, No and - N are all read as false. + Boolean – stored as the text False and True (although 0, No and N are all read as false).
    • - Integer – stored as positive or negative whole numbers or - zero. + Integer – stored as positive or negative whole numbers or zero.
    • - Float – stored as positive or negative floating point numbers - or zero. Floating point representations of whole numbers are stored as - integers. + Float – stored as positive or negative floating point numbers or zero. Floating point representations of whole numbers are stored as integers.
    • - Date-Time – stored in the YYYY-MM-DD hh:mm:ss - format in all locales (i.e. the date and time separators are always '-' and - ':' respectively. + Date-Time – stored in the YYYY-MM-DD hh:mm:ss format in all locales (i.e. the date and time separators are always - and : respectively.
    @@ -95,16 +80,11 @@

    - From CodeSnip v4 config files are little endian Unicode text files with a byte - order mark. This encoding is used because the international support is - required and because the Windows API, which is used to read and write the - files, understands this file format. + From CodeSnip v4 config files are little endian Unicode text files with a byte order mark. This encoding is used because the international support is required and because the Windows API, which is used to read and write the files, understands this file format.

    - On earlier versions of CodeSnip the config files were ANSI text files using - the system's default encoding. The installer takes care of converting the - older files to Unicode. + On earlier versions of CodeSnip the config files were ANSI text files using the system's default encoding. CodeSnip v4 and the standard edition's installer convert the older ANSI files to Unicode.

    @@ -115,20 +95,20 @@

    Common Config File

    +

    + Note: From CodeSnip v4.16 this file is used only by the installer of the standard edition. The portable edition does not use the file and does not create it. +

    +

    - This file stores information about CodeSnip that is common to all users. There - is just one file named Common.config that is stored in the - %ProgramData%\DelphiDabbler\CodeSnip.4 directory. + The file stores information about the current version of the program. It is named Common.config. The file is stored in the standard edition's %ProgramData%\DelphiDabbler\CodeSnip.4 directory.

    - There have been several versions of this file. The current one is version 6. - The change to version 6 came with CodeSnip v4 and the change to Unicode - encoding. + There have been several versions of this file. The current one is version 7. The change to version 7 came with CodeSnip v4.16.0 and the removal of DelphiDabbler web service support.

    - The file is in INI file format. It has the following sections. + The file has the following sections.

    @@ -145,30 +125,10 @@

    - Key (String) -
    -
    - Unique application ID. String of 32 hex digits. -
    -
    - RegCode (String) + Version (String)
    - Registration code. String of 32 hex digits. May be empty string or missing - if application not registered. -
    -
    - RegName (String) -
    -
    - Name of person who registered program. May be empty string or missing if - application not registered. -
    -
    - Version (String) -
    -
    - Version of installed program as dotted quad string. + Internal version number of the installed version of CodeSnip as a dotted quad string.
    @@ -186,39 +146,32 @@

    - Version (Integer) + Version (Integer)
    - Version number of file. Incremented whenever the config file format - changes. If this section or this value is missing then the default value - is 1. The installer takes care of converting earlier file versions and - stamping config file with the correct version number. + Version number of the config file. Incremented whenever the file format changes. If this section or this value is missing then the default value is 1.
    - Current value is 6. + The current value is 7.

    - Per User Config File + User Config File

    - This file records configuration information that is unique to each user of the - application. Some sections correspond to user preferences while others record - details of previous operations or the layout of the GUI. The file is named - User.config and is stored in the logged on user's - %AppData%\DelphiDabbler\CodeSnip.4 directory. + This file records configuration information that is unique users of the application. Some sections correspond to user preferences while others record details of previous operations or the layout of the GUI. The file is named User.config. The standard edition of CodeSnip stores the file in the logged on user's %AppData%\DelphiDabbler\CodeSnip.4 directory, while the portable version stores it in the AppDir sub-directory of the folder where CodeSnip is installed.

    - There have been several versions of this file. The current one is version 15. -

    + There have been several versions of this file. The current one is version 20. The change to version 20 came with CodeSnip v4.26.0 and the addition of the UseCommentsInUnitImpl key in the [Prefs:SourceCode] section. +

    - The file is in INI file format. It has the following sections. + The file has the following sections.

    @@ -226,14 +179,11 @@

    - There is one of these section for each compiler known to CodeSnip. Each - section describes how CodeSnip should use the compiler, or indicates that the - compiler is not available. + There is one of these sections for each compiler known to CodeSnip. Each section describes how CodeSnip should use the compiler, or indicates that the compiler is not available.

    - The actual name of a section is found by replacing XXX with one of - the following values: + The actual name of a section is found by replacing XXX with one of the following values:

      @@ -243,7 +193,7 @@

    • D3 – Delphi 3
    • -
    • +
    • D4 – Delphi 4
    • @@ -294,6 +244,24 @@

    • D10S – Delphi 10 Seattle
    • +
    • + D101B – Delphi 10.1 Berlin +
    • +
    • + D102T – Delphi 10.2 Tokyo +
    • +
    • + D103R – Delphi 10.3 Rio +
    • +
    • + D104S – Delphi 10.4 Sydney +
    • +
    • + D11A – Delphi 11.x Alexandria +
    • +
    • + D12Y – Delphi 12 Athens +
    • FPC – Free Pascal
    • @@ -305,80 +273,112 @@

      - ExePath (String) + ExePath (String)
      - Path to compiler. Empty string if compiler not configured. + Path to the compiler's executable file. Empty string if the compiler not configured.
      - Displayable (Boolean) + Displayable (Boolean)
      - Indicates whether compiler's compilation results for a snippet can be - displayed in detail pane. + Indicates whether the compiler's compilation results for a snippet should be displayed in the UI.
      - Namespaces (String) + Namespaces (String)
      - Space separated list of namespaces containing Delphi RTL units. Missing or - empty string value ⇒ use default RTL namespaces for compiler. + Space separated list of the namespaces containing Delphi's RTL units. A missing or empty string value ⇒ use the default RTL namespaces for the compiler.
      - Relates to the Delphi XE2 and later compilers. If the value present, it is - ignored for earlier compilers and Free Pascal. + Relates to the Delphi XE2 and later compilers. If the value is present, it is ignored for earlier compilers and Free Pascal.
      - Prefix0 (String) + Prefix0 (String)
      - Double quoted prefix to text output by the compiler to indicate fatal - compiler errors. If not present the default is "Fatal: ". + Double quoted prefix to the text output by the compiler to indicate fatal compiler errors. If not present the default is Fatal: .
      - Prefix1 (String) + Prefix1 (String)
      - Double quoted prefix to text output by the compiler to indicate standard - compiler errors. If not present the default is "Error: ". + Double quoted prefix to the text output by the compiler to indicate standard compiler errors. If not present the default is Error: .
      - Prefix2 (String) + Prefix2 (String)
      - Double quoted prefix to text output by the compiler to indicate warnings. If - not present the default is "Warning: ". + Double quoted prefix to the text output by the compiler to indicate warnings. If not present the default is Warning: .
      - Switches (String) + Switches (String)
      - Comma separated list of compiler switches. Empty string value ⇒ use - default switches for compiler. A missing value is the same as an empty - string value. + Comma separated list of compiler switches. A missing or empty string value ⇒ use default switches for the compiler.
      - SearchDirCount (Integer) + SearchDirCount (Integer)
      - Count of number of search directories configured for compiler. + Count of the number of search directories configured for the compiler.
      - SearchDirXXX (String) + SearchDirXXX (String)
      - Once entry for each search directory configured for compiler where - XXX ranges from 0 to SearchDirCount - 1. + One entry for each search directory configured for the compiler where XXX ranges from 0 to SearchDirCount - 1.
      Each entry contains a fully specified directory path.
      +
      + CanAutoInstall (Boolean) +
      +
      +
      + Determines whether the compiler can be automatically detected and registered by CodeSnip. +
      +
      + Applies to Delphi compilers only, not to Free Pascal. +
      +
      +

      + [Compilers] section +

      + +

      + This section records configuration information that applies to all, or + multiple, compilers. +

      + +

      + Name / Value pairs: +

      + +
      +
      + PermitStartupDetection (Boolean) +
      +
      +
      + Determines whether CodeSnip should detect, and potentially register, any + Delphi compilers that are installed on the user's system but not + registered with the program. +
      +
      + Does not apply to the Free Pascal compiler. +
      +
      +
      + +

      [Database] section

      @@ -393,17 +393,14 @@

      - UserDataDir (String) + UserDataDir (String)
      - Specifies the directory that contains the user database. This value is - only present if the user has moved the database from its default - location. + Specifies the directory that contains the user database. This value is only present if the user has moved the database from its default location.
      - Note: The value is ignored in the portable version of - CodeSnip. + Note: The value is ignored in the portable version of CodeSnip.
      @@ -413,7 +410,7 @@

      - Persists state of controls in Duplicate Snippet dialogue box. + Persists the state of controls in the Duplicate Snippet dialogue box.

      @@ -422,10 +419,10 @@

      - EditSnippetOnClose (Boolean) + EditSnippetOnClose (Boolean)
      - Stores state of Edit in Snippets Editor check box. + Stores the state of the Edit in Snippets Editor check box.
      @@ -434,7 +431,7 @@

      - Persists state of controls in Favourites dialogue box. + Persists the state of controls in Favourites dialogue box.

      @@ -443,17 +440,16 @@

      - DisplayInNewTabs (Boolean) + DisplayInNewTabs (Boolean)
      - Stores state of Open favourites in new tabs check box. + Stores the state of the Open favourites in new tabs check box.
      - InactiveAlphaBlendValue (Integer) + InactiveAlphaBlendValue (Integer)
      - Stores alpha blend value (i.e. degree of transparency) of Favourites - dialogue box when it is not active. Values should be in range 128 to 255. + Stores the alpha blend value (i.e. the degree of transparency) of the Favourites dialogue box when it is not active. Values should be in the range 128 to 255.
      @@ -471,151 +467,179 @@

      - D2 (Boolean) + D2 (Boolean) +
      +
      + Indicates whether Delphi 2 was included in the search. +
      +
      + D3 (Boolean) +
      +
      + Indicates whether Delphi 3 was included in the search. +
      +
      + D4 (Boolean) +
      +
      + Indicates whether Delphi 4 was included in the search. +
      +
      + D5 (Boolean) +
      +
      + Indicates whether Delphi 5 was included in the search. +
      +
      + D6 (Boolean) +
      +
      + Indicates whether Delphi 6 was included in the search. +
      +
      + D7 (Boolean) +
      +
      + Indicates whether Delphi 7 was included in the search. +
      +
      + D2005w32 (Boolean)
      - Indicates whether Delphi 2 was included in search. + Indicates whether the Win32 personality of Delphi 2005 was included in the search.
      - D3 (Boolean) + D2006w32 (Boolean)
      - Indicates whether Delphi 3 was included in search. + Indicates whether the Win32 personality of Delphi 2006 was included in the search.
      - D4 (Boolean) + D2007 (Boolean)
      - Indicates whether Delphi 4 was included in search. + Indicates whether the Win32 personality of Delphi 2007 was included in the search.
      - D5 (Boolean) + D2009w32 (Boolean)
      - Indicates whether Delphi 5 was included in search. + Indicates whether the Win32 personality of Delphi 2009 was included in the search.
      - D6 (Boolean) + D2010 (Boolean)
      - Indicates whether Delphi 6 was included in search. + Indicates whether Delphi 2010 was included in the search.
      - D7 (Boolean) + DXE (Boolean)
      - Indicates whether Delphi 7 was included in search. + Indicates whether Delphi XE was included in the search.
      - D2005w32 (Boolean) + DXE2 (Boolean)
      - Indicates whether the Win32 personality of Delphi 2005 was included in - search. + Indicates whether Delphi XE2 was included in the search.
      - D2006w32 (Boolean) + DXE3 (Boolean)
      - Indicates whether the Win32 personality of Delphi 2006 was included in - search. + Indicates whether Delphi XE3 was included in the search.
      - D2007 (Boolean) + DXE4 (Boolean)
      - Indicates whether the Win32 personality of Delphi 2007 was included in - search. + Indicates whether Delphi XE4 was included in the search.
      - D2009w32 (Boolean) + DXE5 (Boolean)
      - Indicates whether the Win32 personality of Delphi 2009 was included in - search. + Indicates whether Delphi XE5 was included in the search.
      - D2010 (Boolean) + DXE6 (Boolean)
      - Indicates whether Delphi 2010 was included in search. + Indicates whether Delphi XE6 was included in the search.
      - DXE (Boolean) + DXE7 (Boolean)
      - Indicates whether Delphi XE was included in search. + Indicates whether Delphi XE7 was included in the search.
      - DXE2 (Boolean) + DXE8 (Boolean)
      - Indicates whether Delphi XE2 was included in search. + Indicates whether Delphi XE8 was included in the search.
      - DXE3 (Boolean) + D10S (Boolean)
      - Indicates whether Delphi XE3 was included in search. + Indicates whether Delphi 10 Seattle was included in the search.
      - DXE4 (Boolean) + D101B (Boolean)
      - Indicates whether Delphi XE4 was included in search. + Indicates whether Delphi 10.1 Berlin was included in the search.
      - DXE5 (Boolean) + D102T (Boolean)
      - Indicates whether Delphi XE5 was included in search. + Indicates whether Delphi 10.2 Tokyo was included in the search.
      - DXE6 (Boolean) + D103R (Boolean)
      - Indicates whether Delphi XE6 was included in search. + Indicates whether Delphi 10.3 Rio was included in the search.
      - DXE7 (Boolean) + D104S (Boolean)
      - Indicates whether Delphi XE7 was included in search. + Indicates whether Delphi 10.4 Sydney was included in the search.
      - DXE8 (Boolean) + D11A (Boolean)
      - Indicates whether Delphi XE8 was included in search. + Indicates whether Delphi 11.x Alexandria was included in the search.
      - D10S (Boolean) + D12Y (Boolean)
      - Indicates whether Delphi 10 Seattle was included in search. + Indicates whether Delphi 12 Athens was included in the search.
      - FPC (Boolean) + FPC (Boolean)
      - Indicates whether Free Pascal was included in search. + Indicates whether Free Pascal was included in the search.
      - Option (Integer) + Option (Integer)
      - Code that describes the kind of compilation result to be searched for: 0 - ⇒ snippet compiles OK; 1 ⇒ snippet compiles without warnings; 2 - ⇒ snippet compiles with warning(s); 3 ⇒ snippet fails to compile; - 4 ⇒ snippet not tested. + Code that describes the kind of compilation result to be searched for: 0 ⇒ snippet compiles OK; 1 ⇒ snippet compiles without warnings; 2 ⇒ snippet compiles with warning(s); 3 ⇒ snippet fails to compile; 4 ⇒ snippet not tested.
      - Logic (Integer) + Logic (Integer)
      - Code that describes search logic to use: 0 ⇒ "and"; 1 ⇒ - "or". + Code that describes the search logic to use: 0 ⇒ "and"; 1 ⇒ "or".
      @@ -624,8 +648,7 @@

      - Provides information about last the last text search that was run and records - a history of search terms. + Provides information about last the last text search that was run and records a history of search terms.

      @@ -634,42 +657,39 @@

      - HistoryCount (Integer) + HistoryCount (Integer)
      - Count of items in search term history. + Count of items in the search term history.
      - HistoryXXX (String) + HistoryXXX (String)
      - One entry for each search term in history where XXX ranges from 0 - to HistoryCount - 1. + One entry for each search term in the history where XXX ranges from 0 to HistoryCount - 1.
      Each entry contains one or more spaced separated search words.
      - MatchCase (Boolean) + MatchCase (Boolean)
      One of the search options. Indicates whether searches are case sensitive.
      - WholeWord (Boolean) + WholeWord (Boolean)
      - One of the search options. Indicates whether searches match whole words - only. + One of the search options. Indicates whether searches match whole words only.
      - Logic (Integer) + Logic (Integer)
      - Code that describes search logic to use: 0 ⇒ "and"; 1 ⇒ - "or". + Code that describes the search logic to use: 0 ⇒ "and"; 1 ⇒ "or".
      @@ -678,8 +698,7 @@

      - Provides information about the last snippet cross reference search that was - run. + Provides information about the last snippet cross reference search that was run.

      @@ -688,54 +707,46 @@

      - IncludeSnippet (Boolean) + IncludeSnippet (Boolean)
      - Indicates whether to include the snippet for which the search is being - performed in the search result. + Indicates whether to include the snippet for which the search is being performed in the search result.
      - Required (Boolean) + Required (Boolean)
      - Indicates whether to include snippets that are required to compile the - selected snippet are included in search result. + Indicates whether snippets that are required to compile the selected snippet are included in the search results.
      - RequiredRecurse (Boolean) + RequiredRecurse (Boolean)
      - Indicates whether to recurse required snippets to search for required - snippets of each required snippet and so on. Ignored if - Required = False. + Indicates whether the search for required snippets is recursive. Ignored if Required = False.
      - RequiredReverse (Boolean) + RequiredReverse (Boolean)
      - Indicates whether to include snippets that depend on the searched for - snippet. + Indicates whether snippets that depend on the searched-for snippet are included in the search results.
      - SeeAlso (Boolean) + SeeAlso (Boolean)
      - Indicates whether to include snippets that are cross referenced by the - selected snippet in search results. + Indicates whether snippets that are cross referenced by the selected snippet are included in the search results.
      - SeeAlsoRecurse (Boolean) + SeeAlsoRecurse (Boolean)
      - Indicates whether to recurse cross-referenced snippets to search for their - own cross references and so on. Ignored if SeeAlso = False. + Indicates whether the search for cross-referenced snippets is recursive. Ignored if SeeAlso = False.
      - SeeAlsoReverse (Boolean) + SeeAlsoReverse (Boolean)
      - Indicates whether to include snippets that cross-reference searched for - snippet. + Indicates whether snippets that cross-reference the searched-for snippet are included in the search results.
      @@ -744,7 +755,7 @@

      - Provides information about the config file itself. + Provides information about the config file and installed version of CodeSnip.

      @@ -753,24 +764,42 @@

      - Version (Integer) + Version (Integer)
      - Version number of file. Incremented whenever the config file format - changes. If this section or this value is missing then the default value - is 1. The installer takes care of converting earlier file versions and - stamping config file with the correct version number. + The version number of the config file. Incremented whenever the file format changes. If this section or this value is missing then the default value is 1.
      - Current value is 15. + The current value is 20.
      - ProgramVersion (String) + ProgramVersion (String) +
      +
      + Internal version number of the currently installed version of CodeSnip as a dotted quad. +
      +
      + +

      + [Prefs] section +

      + +

      + Stores information about the Preferences dialogue box itself, rather than actual preferences data. Actual preference data is stored in sections with names like [Prefs:XXX] where XXX is a preferences sub-section. +

      + +

      + Name / Value pairs: +

      + +
      +
      + LastTab (string)
      - Internal version number of currently installed program as a dotted quad. + Name of the tab that was open when the dialogue box was last closed. May be the empty string if the dialogue box has not yet been opened.
      @@ -779,8 +808,7 @@

      - Stores source code generation preferences. Used in test compilations or when - generating user defined units. + Stores source code generation preferences. Used in test compilations or when generating user defined units.

      @@ -789,57 +817,48 @@

      - EmitWarnings (Boolean) + EmitWarnings (Boolean)
      - Flag indicating whether directives are emitted to switch specified warnings - on or off. True ⇒ emit directives; False ⇒ do emit directives. + Flag indicating whether directives are emitted to switch specified warnings on or off. True ⇒ emit directives; False ⇒ do not emit directives.
      - WarningCount (Integer) + WarningCount (Integer)
      Number of warning directives supported.
      - WarningXXX.Symbol (String) + WarningXXX.Symbol (String)
      - One entry for each supported warning where XXX ranges from 0 to - WarningCount - 1. + One entry for each supported warning directive, where XXX ranges from 0 to WarningCount - 1.
      - Each entry contains the symbol representing a warning as used in Delphi's - $WARN directive. + Each entry contains the symbol representing a warning as used in Delphi's $WARN directive.
      - WarningXXX.MinCompiler (Float) + WarningXXX.MinCompiler (Float)
      - One entry for each supported warning where XXX ranges from 0 to - WarningCount - 1. + One entry for each supported warning directive, where XXX ranges from 0 to WarningCount - 1.
      - Records the earliest version of Delphi that supports the warning. This - version number is a decimal value containing compiler version number as - specified by Delphi's CompilerVersion constant. Must be 14.0 - (Delphi 6) or higher. + Records the earliest version of Delphi that supports the warning. This version number is a decimal value containing the compiler version number as specified by Delphi's CompilerVersion constant. Must be 14.0 (Delphi 6) or higher.
      - WarningXXX.State (Boolean) + WarningXXX.State (Boolean)
      - One entry for each supported warning where XXX ranges from 0 to - WarningCount - 1. + One entry for each supported warning directive, where XXX ranges from 0 to WarningCount - 1.
      - Indicates whether the warning should be switched on or off. True ⇒ - switch on; False ⇒ switch off. + Indicates whether the warning should be switched on or off. True ⇒ switch on; False ⇒ switch off.
      @@ -849,7 +868,7 @@

      - Stores main display preferences. + Stores preferences for the main display.

      @@ -858,116 +877,104 @@

      - OverviewStartState (Integer) + OverviewStartState (Integer)
      - Code that indicates the desired start state of the overview pane treeview: - 0 ⇒ fully expanded; 1 ⇒ fully collapsed. + Code that indicates the desired start state of the overview pane treeview: 0 ⇒ fully expanded; 1 ⇒ fully collapsed.
      - ShowEmptySections (Boolean) + ShowEmptySections (Boolean)
      - Flag that specifies whether empty sections should be displayed in overview - pane. True ⇒ display empty sections; False ⇒ hide empty - sections. + Flag that specifies whether empty sections should be displayed in the overview pane. True ⇒ display empty sections; False ⇒ hide empty sections.
      - ShowNewSnippetsInNewTabs (Boolean) + ShowNewSnippetsInNewTabs (Boolean)
      - Flag that specifies how newly created snippets and categories are displayed - in detail pane. True ⇒ new tab created for each new item; False ⇒ - new items overwrite item in selected tab. + Flag that specifies how newly created snippets and categories are displayed in the detail pane. True ⇒ a new tab is created for each new item; False ⇒ new items overwrite the current item in the selected tab.
      - MainDBHeadingColour (Integer) + MainDBHeadingColour (Integer)
      - Colour to be used to display headings of items from main database. + Colour to be used to display headings of items from the main database.
      - UserDBHeadingColour (Integer) + UserDBHeadingColour (Integer)
      - Colour to be used to display headings of items from user database. + Colour to be used to display headings of items from the user database.
      - SourceCodeBGColour (Integer) + SourceCodeBGColour (Integer)
      - Colour to be used for background of source code in the main display. + Colour to be used for the background of source code in the main display.
      - MainDBHeadingCustomColourCount (Integer) + MainDBHeadingCustomColourCount (Integer)
      Number of recorded custom colours for main database item headings.
      - MainDBHeadingCustomColourXXX (String) + MainDBHeadingCustomColourXXX (String)
      - One entry for each recorded custom colours for main database item - headings, where XXX ranges from 0 to - MainDBHeadingCustomColourCount - 1. + One entry for each recorded custom colour for main database item headings, where XXX ranges from 0 to MainDBHeadingCustomColourCount - 1.
      - The value is a definition of a custom colour in the format used by the - TColorDialog dialogue box. This format is - ColourID=ColourNum where ColourID is a value - from "ColorA" to "ColorP" and ColourNum is - the hex representation of the colour. + The value is a definition of a custom colour in the format used by the TColorDialog dialogue box. This format is ColourID=ColourNum where ColourID is a value from ColorA to ColorP and ColourNum is the hex representation of the colour.
      - UserDBHeadingCustomColourCount (Integer) + UserDBHeadingCustomColourCount (Integer)
      Number of recorded custom colours for user database item headings.
      - UserDBHeadingCustomColourXXX (String) + UserDBHeadingCustomColourXXX (String)
      - One entry for each recorded custom colours for user database item - headings, where XXX ranges from 0 to - UserDBHeadingCustomColourCount - 1. + One entry for each recorded custom colour for user database item headings, where XXX ranges from 0 to UserDBHeadingCustomColourCount - 1.
      - The value is a definition of a custom colour in the format used by the - TColorDialog dialogue box. This format is - ColourID=ColourNum where ColourID is a value - from "ColorA" to "ColorP" and ColourNum is - the hex representation of the colour. + The value is a definition of a custom colour in the format used by the TColorDialog dialogue box. This format is ColourID=ColourNum where ColourID is a value from ColorA to ColorP and ColourNum is the hex representation of the colour.
      - SourceCodeBGCustomColourCount (Integer) + SourceCodeBGCustomColourCount (Integer)
      - Number of recorded custom colours for source code background in the main - display. + Number of recorded custom colours for the source code background in the main display.
      - SourceCodeBGCustomColourXXX (String) + SourceCodeBGCustomColourXXX (String)
      - One entry for each recorded custom colours for source code background in - the main display, where XXX ranges from 0 to - SourceCodeBGCustomColourCount - 1. + One entry for each recorded custom colour for the source code background in the main display, where XXX ranges from 0 to SourceCodeBGCustomColourCount - 1.
      - The value is a definition of a custom colour in the format used by the - TColorDialog dialogue box. This format is - ColourID=ColourNum where ColourID is a value - from "ColorA" to "ColorP" and ColourNum is - the hex representation of the colour. + The value is a definition of a custom colour in the format used by the TColorDialog dialogue box. This format is ColourID=ColourNum where ColourID is a value from ColorA to ColorP and ColourNum is the hex representation of the colour.
      +
      + OverviewFontSize (Integer) +
      +
      + Size of font to be used in overview pane tree view. If missing or empty the default value is the default font size of the operationg system. +
      +
      + DetailFontSize (Integer) +
      +
      + Size of font to be used in detail pane for all text except for source code. If missing or empty the default value is the default content font size of the operating system. +

      @@ -975,7 +982,7 @@

      - Stores general (miscellaneous) application preferences. + Stores miscellaneous application preferences.

      @@ -984,11 +991,10 @@

      - Units (Integer) + Units (Integer)
      - A code that records the units of measurement used by the application: 0 - ⇒ inches; 1 ⇒ millimeters. + A code that records the units of measurement used by the application: 0 ⇒ inches; 1 ⇒ millimeters.
      @@ -997,9 +1003,7 @@

      - Stores preferences that define the properties of one or more syntax - highlighters. Also records information about the custom colours available when - configuring the highlighters. + Stores preferences that define the properties of one or more syntax highlighters. Also records information about the custom colours available when configuring the highlighters.

      @@ -1008,176 +1012,125 @@

      - FontSize (Integer) + FontSize (Integer)
      - Size of current highlighter font in points. + Size of the current highlighter font in points.
      - FontName (String) + FontName (String)
      - Name of current highlighter font. + Name of the current highlighter font.
      - ElemXXX.Color (Integer) + ElemXXX.Color (Integer)
      - One entry for each of the 12 source code elements recognised by the - current syntax highlighter. XXX ranges from 0 to 11. + One entry for each of the 12 source code elements recognised by the current syntax highlighter. XXX ranges from 0 to 11.
      - Colour of source code element. Integer representation of a Delphi - TColor value. If the colour is not specified the value is the - integer representation of clNone. + Each value is a colour of a source code element as an integer representation of a Delphi TColor value. If the colour is not specified the value is the integer representation of clNone.
      - ElemXXX.Style (Integer) + ElemXXX.Style (Integer)
      - One entry for each of the 12 source code elements recognised by the - current syntax highlighter. XXX ranges from 0 to 11. + One entry for each of the 12 source code elements recognised by the current syntax highlighter. XXX ranges from 0 to 11.
      - Bitmask representing the font styles used for the source code element. + Each value is a bitmask representing the font styles used for the source code element.
      - NamedHiliterCount (Integer) + NamedHiliterCount (Integer)
      - Number of custom (or "Named") syntax highlighters defined by user. + Number of custom (or "Named") syntax highlighters defined by the user.
      - HilterNameXXX (String). + HilterNameXXX (String)
      - One entry for each custom syntax highlighter, where XXX ranges - from 0 to NamedHiliterCount - 1. + One entry for each custom syntax highlighter, where XXX ranges from 0 to NamedHiliterCount - 1.
      - Each value is the name used to identify the syntax highlighter with the - same index. (See the various NamedHiliterXXX entries below.) + Each value is the name used to identify the syntax highlighter with the same index. (See the various NamedHiliterXXX entries below.)
      - NamedHiliterXXX.FontSize (Integer) + NamedHiliterXXX.FontSize (Integer)
      - One entry for each custom syntax highlighter, where XXX ranges - from 0 to NamedHiliterCount - 1. + One entry for each custom syntax highlighter, where XXX ranges from 0 to NamedHiliterCount - 1.
      - Each value is the size of the corresponding syntax highlighter's font, in - points. + Each value is the size of the corresponding syntax highlighter's font, in points.
      - NamedHiliterXXX.FontName + NamedHiliterXXX.FontName (String)
      - One entry for each custom syntax highlighter, where XXX ranges - from 0 to NamedHiliterCount - 1. + One entry for each custom syntax highlighter, where XXX ranges from 0 to NamedHiliterCount - 1.
      Each value is the name of the corresponding syntax highlighter's font.
      - NamedHiliterXXX.ElemYYY.Color (Integer) + NamedHiliterXXX.ElemYYY.Color (Integer)
      - There is an entry for each source code element of each custom synax - highliter. XXX represents the index of the corresponding syntax - highlighter and ranges from 0 to NamedHiliterCount - 1. - For each value of XXX there are 12 - .ElemYYY values, with YYY ranging from - 0 to 11. + There is an entry for each source code element of each custom synax highliter. XXX represents the index of the corresponding syntax highlighter and ranges from 0 to NamedHiliterCount - 1. For each value of XXX there are 12 .ElemYYY values, with YYY ranging from 0 to 11.
      - Each value specifies the colour of a source code element. The values are - integer representations of a Delphi TColor value. If the colour - is not specified the value is the integer representation of - clNone. + Each value specifies the colour of a source code element as an integer representations of a Delphi TColor value. If the colour is not specified the value is the integer representation of clNone.
      - NamedHiliterXXX.ElemYYY.Style (Integer) + NamedHiliterXXX.ElemYYY.Style (Integer)
      - There is an entry for each source code element of each custom synax - highliter. XXX represents the index of the corresponding syntax - highlighter and ranges from 0 to NamedHiliterCount - 1. - For each value of XXX there are 12 - .ElemYYY values, with YYY ranging from - 0 to 11. + There is an entry for each source code element of each custom synax highliter. XXX represents the index of the corresponding syntax highlighter and ranges from 0 to NamedHiliterCount - 1. For each value of XXX there are 12 .ElemYYY values, with YYY ranging from 0 to 11.
      - Each value contains a bitmask representing the font style of a source code - element. + Each value contains a bitmask representing the font style of a source code element.
      - CustomColourCount (Integer) + CustomColourCount (Integer)
      Number of recorded custom colours available for use by highlighter elements.
      - CustomColourXXX (String) + CustomColourXXX (String)
      - One entry for each recorded custom colour, where XXX ranges from - 0 to CustomColourCount - 1. + One entry for each recorded custom colour, where XXX ranges from 0 to CustomColourCount - 1.
      - The value is a definition of a custom colour in the format used by the - TColorDialog dialogue box. This format is - ColourID=ColourNum where ColourID is a value - from "ColorA" to "ColorP" and ColourNum is - the hex representation of the colour. + The value is a definition of a custom colour in the format used by the TColorDialog dialogue box. This format is ColourID=ColourNum where ColourID is a value from ColorA to ColorP and ColourNum is the hex representation of the colour.
      -

      - [Prefs:News] section -

      - -

      - Stores preferences that determine how items from the CodeSnip news feed are - displayed. -

      - -

      - Name / Value pairs: -

      - -
      -
      - MaxAge (Integer) -
      -
      - Maximum age of a news item that can appear in CodeSnip News dialogue box in - days. Default is 92 days. -
      -
      -

      [Prefs:Printing] section

      - Stores preferences that determine printing defaults. + Stores default printing preferences.

      @@ -1186,41 +1139,40 @@

      - UseColor (Boolean) + UseColor (Boolean)
      - Flag that indicates whether printing is to be in colour. + Flag that indicates whether printing is to be in colour: True ⇒ print in colour; False ⇒ print in black and white.
      - SyntaxPrint (Boolean) + SyntaxPrint (Boolean)
      - Flag that indicates whether source code in printed output is to be syntax - highlighted. + Flag that indicates whether source code in printed output is to be syntax highlighted: True ⇒ use syntax highlighting; False ⇒ do not syntax highlight.
      - LeftMargin (Float) + LeftMargin (Float)
      - Size of printed page's left margin in mm. + Size of a printed page's left margin in mm.
      - TopMargin (Float) + TopMargin (Float)
      - Size of printed page's top margin in mm. + Size of a printed page's top margin in mm.
      - RightMargin (Float) + RightMargin (Float)
      - Size of printed page's right margin in mm. + Size of a printed page's right margin in mm.
      - BottomMargin (Float) + BottomMargin (Float)
      - Size of printed page's bottom margin in mm. + Size of a printed page's bottom margin in mm.
      @@ -1229,8 +1181,7 @@

      - Stores preferences that describes customisations of snippet pages displayed in - details pane. + Stores preferences that describes the customisation of pages displayed in the details pane for different snippet kinds.

      @@ -1239,47 +1190,42 @@

      - PageKindXXX (String) + PageKindXXX (String)
      - One entry for each snippet kind where XXX is the ordinal value of - the snippet kind. + One entry for each snippet kind where XXX is the ordinal value of the snippet kind.
      - Each entry's value describes the structure of the details pane page that - snippets of the kind given by the key name. The value is a comma separated - list of zero or more names, each of which denotes a page component. - Components are displayed in the order they are listed. Valid component - names are: + Each entry's value describes the structure of the details pane page that displays snippets of the related kind. The value is a comma separated list of zero or more names, each of which denotes a page component. Components are displayed in the order they are listed. Valid component names are:
      • - Description + Description
      • - SourceCode + SourceCode
      • - Kind + Kind
      • - Category + Category
      • - Units + Units
      • - Depends + Depends
      • - XRefs + XRefs
      • - CompileResults + CompileResults
      • - ExtraInfo + ExtraInfo
      @@ -1290,8 +1236,7 @@

      - Stores preferences that govern the way source code is stored in exported - documents by default. + Stores preferences that specify how source code is exported by default.

      @@ -1300,74 +1245,43 @@

      - FileType (Integer) + FileType (Integer)
      - Code that determines type of exported files: 0 ⇒ plain text; 1 ⇒ - Pascal; 2 ⇒ HTML; 3 ⇒ rich text (RTF). + A code that determines type of exported files: 0 ⇒ plain text; 1 ⇒ Pascal; 2 ⇒ HTML; 3 ⇒ rich text (RTF).
      - CommentStyle (Integer) + CommentStyle (Integer)
      - Code that determines style of commenting used for documentation of snippets - in output files: 0 ⇒ no documentation comment; 1 ⇒ description of - snippet between prototype and body; 2 ⇒ description of snippet - immediately precedes snippet. + A code that determines the style of documentation comments used for snippets in output files: 0 ⇒ no documentation comment; 1 ⇒ a description of the snippet apperars between the snippet's prototype and the body of the code; 2 ⇒ the description of the snippet immediately precedes it.
      - TruncateComments (Boolean) + TruncateComments (Boolean)
      - Flag indicating whether multi-paragraph snippet descriptions are to be - truncated to first paragraph only in snippet documentation comments. + Flag indicating whether multi-paragraph snippet descriptions are to be truncated to the first paragraph only in documentation comments. True ⇒ truncate the description; False ⇒ use the full description.
      - UseSyntaxHiliting (Boolean) + UseCommentsInUnitImpl (Boolean)
      - Flag indicating whether source code is to be syntax highlighted. + Flag indicating whether source code comments are repeated in a generated unit's implementation section. True ⇒ emit comments in both the interface and implementation sections; False ⇒ emit comments in the interface section only.
      -
      - -

      - [Prefs:Updating] section -

      - -

      - Stores preferences relating to updating CodeSnip and the local copy of the - online database. -

      - -

      - Name / Value pairs: -

      - -
      - AutoCheckProgramFrequency (Integer) + UseSyntaxHiliting (Boolean)
      - Specifies frequency, in days, with which CodeSnip automatically checks - online for program updates. A value of 0 indicates that CodeSnip will not - check for program updates automatically. -
      -
      - AutoCheckDatabaseFrequency (Integer) -
      -
      - Specifies frequency, in days, with which CodeSnip automatically checks - online for database updates. A value of 0 indicates that CodeSnip will not - check for database updates automatically. + Flag indicating whether source code is to be syntax highlighted. True ⇒ use syntax highlighting; False ⇒ do not syntax highlight.

      - [ProxyServer] section + [UnitList] section

      - Stores information about any proxy internet server that is to be used. + Records the names of the units that appear in the Units check box list on the References tab of the Edit Snippet dialoogue box.

      @@ -1376,112 +1290,80 @@

      - UseProxy (Boolean) -
      -
      - Flag that indicates whether to use a proxy server or not. If false the - remaining name / value pairs in this section are ignored. -
      -
      - IPAddress (String) + Count (Integer)
      - IP address of proxy server in dotted quad format, e.g. 127.0.0.1. + The number of units in the list.
      - Port (Integer) + UnitXXX (String)
      - Port on which to access proxy server. -
      -
      - UserName (String) -
      -
      - Username for proxy server access. Empty if server doesn't require a user - name. -
      -
      - Password (String) -
      -
      - Encrypted form of any password required to access proxy server. Empty if no - password is required. This value is a hex representation of the bytes of - the encrypted password. + One entry for each unit, where XXX range from 0 to Count - 1.
      -

      - [UpdateChecks] section -

      -

      - Records date of last (automatic) update checks for different items. These - values are used to determine when to perform the next check. (Checks are made - within a fixed interval of the preceding check). + Note that some "reserved" unit names that always appear in the list box are not recorded in settings. They are:

      -

      - Name / Value pairs: -

      - -
      -
      - LastProgramCheck (Date-Time) -
      -
      - Date (in GMT) that the program last checked to see if a new version of - CodeSnip is available. -
      -
      - LastDatabaseCheck (Date-Time) -
      -
      - Date (in GMT) that the program last checked to see if the online version of - the Code Snippets Database had been updated. -
      -
      - -

      - [UserInfo] section -

      +
        +
      • + SysUtils +
      • +
      • + Classes +
      • +
      • + Windows +
      • +
      • + Graphics +
      • +

      - Stores personal information about the user. + If this section is missing or has no units listed then CodeSnip defaults to using the following units in addition to the "reserved" units:

      +
        +
      • + Controls +
      • +
      • + Messages +
      • +
      • + Types +
      • +
      • + ShlObj +
      • +
      • + ShellAPI +
      • +
      • + ActiveX +
      • +
      • + Math +
      • +
      +

      - Name / Value pairs: + These default unit names will be included in this section the first time it is written.

      -
      -
      - Name (String) -
      -
      - User's name. -
      -
      - Email (String) -
      -
      - User's email address. -
      -
      -

      [WindowState:XXX] sections

      - There is one of these section for each window that stores its state and size - in the config file. XXX is a sub-section placeholder and is replaced - by a unique name representing the window whose state is stored. + There is one of these section for each window that stores its state and size in the config file. XXX is a sub-section placeholder and is replaced by a unique name representing the window whose state is stored.

      - Some windows also store custom information in their sub-section that is not - used in all sub-sections. + Some windows also store custom information in their sub-section that is unique to them. Values common to all sub-sections are described first, followed by additional details of any sub-section storing custom information.

      @@ -1498,50 +1380,48 @@
      - Left (Integer) + Left (Integer)
      - Location of left side of window on screen in pixels. + Location of the left side of the window on screen in pixels.
      - Top (Integer) + Top (Integer)
      - Location of top of window on screen in pixels. + Location of the top of the window on screen in pixels.
      - Width (Integer) + Width (Integer)
      - Width of window in pixels. + Width of the window in pixels.
      Not used for fixed size windows.
      - Height (Integer) + Height (Integer)
      - Height of window in pixels. + Height of the window in pixels.
      Not used for fixed size windows.
      - State (Integer) + State (Integer)
      - Value of that describes state of window: 0 ⇒ normal state; 1 ⇒ - minimized; 2 ⇒ maximized. + Value of that describes the state of window: 0 ⇒ normal state; 1 ⇒ minimized; 2 ⇒ maximized.
      - Not used for dialogue boxes and other windows that are always displayed in - the normal state. + Not used for dialogue boxes and other windows that are always displayed in the normal state.
      @@ -1560,17 +1440,16 @@
      - SplitterPos (Integer) + SplitterPos (Integer)
      - Position of vertical splitter control in main window, in pixels from left - of window client area. + Position of the main window's vertical splitter control, in pixels from the left of the window client area.
      - OverviewTab (Integer) + OverviewTab (Integer)
      - Index of selected tab in overview pane. + Index of the selected tab in the overview pane.
      diff --git a/Docs/Design/FileFormats/export.html b/Docs/Design/FileFormats/export.html index 0559b76a1..7f6e80653 100644 --- a/Docs/Design/FileFormats/export.html +++ b/Docs/Design/FileFormats/export.html @@ -1,18 +1,15 @@ + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> - + @@ -44,14 +41,44 @@

      Export Files

      +
      + +

      + Contents +

      + + + +
      + +
      +

      Introduction

      User defined snippets can be exported from the program for importing into - another program or for submitting to the online database. Exported data is - stored in a single XML file. + another user's user database. Exported data is stored in a single XML file.

      @@ -59,13 +86,20 @@

      these is explained below.

      +

      + +
      +

      Encoding

      - XML export files use UTF-8 encoding without a byte order mark. XML generated - for submission to the online database is also encoded in UTF-8. + XML export files use UTF-8 encoding without a byte order mark. +

      + +

      + In earlier versions of CodeSnip, XML files generated for submission to the online database were also encoded in UTF-8. Support for code submission was removed from CodeSnip v4.16.0.

      @@ -74,13 +108,17 @@

      this attribute.

      +

      + +
      +

      File Format

      - There have been six different versions of the XML file format – v1 to - v6. Tags from all six versions are explained below with notes describing + There have been eight different versions of the XML export file format – v1 to + v8. Tags used by all versions are explained below, with notes describing which versions a tag applies to. Where there is no note the tag is valid in all versions.

      @@ -109,7 +147,7 @@

      • - versions 1..4: Attribute not + versions 1 to 4: Attribute not provided.
      • @@ -140,8 +178,8 @@

        version

    - Identifies version of file. Determines which tags are valid and - establishes rules concerning content. Valid versions are 1..6. + Identifies major version of file. Determines which tags are valid and + establishes rules concerning content. Valid versions are 1 to 8.
    @@ -158,29 +196,56 @@

    codesnip-export/user-info

    - Contains information about user who created the file (omitted for normal - exports – included for submissions to the online database). +
      +
    • + versions 1 to 6: Contains information about user who created the file   used for submissions to the online database, omitted for other exports. +
    • +
    • + version 7 and later: Not supported. Ignored if present. +
    • +
    codesnip-export/user-info/name
    - User's name or nickname. +
      +
    • + versions 1 to 6: User's name or nickname. +
    • +
    • + version 7 and later: Not supported. Ignored if present. +
    • +
    codesnip-export/user-info/email
    - User's email address. +
      +
    • + versions 1 to 6: User's email address. +
    • +
    • + version 7 and later: Not supported. Ignored if present. +
    • +
    codesnip-export/user-info/comments
    - Any comments provided by user. +
      +
    • + versions 1 to 6: Any comments provided by user. +
    • +
    • + version 7 and later: Not supported. Ignored if present. +
    • +
    @@ -208,7 +273,7 @@

    • - versions 1..4: Name must begin with + versions 1 to 4: Name must begin with an English language letter or the underscore.
    • @@ -230,12 +295,20 @@

      • - versions 1..5: Content is a single line + versions 1 to 5: Content is a single line of plain text.
      • - version 6: Content is formatted text - encoded in REML markup. REML v3 is supported. + version 6 to 7.2: Content is formatted text + encoded in REML markup. REML v4 is supported. +
      • +
      • + version 7.3 and 7.4: Content is formatted text + encoded in REML markup. REML v5 is supported. +
      • +
      • + version 7.5 and later: Content is formatted text + encoded in REML markup. REML v6 is supported.
      @@ -253,11 +326,11 @@

      • - versions 1..5: Not supported. + versions 1 to 5: Not supported.
      • - version 6: + version 6 and later:
        @@ -282,18 +355,18 @@

      - codesnip-data/routines/routine/display-name + codesnip-export/routines/routine/display-name
      • - versions 1..5: Not supported. + versions 1 to 5: Not supported.
      • - version 6: Display name of snippet. Can + version 6 and later: Display name of snippet. Can contain any characters and need not be unique. Present only if snippet has a display name that is different to the value of the name - attribute of the codesnip-data/routines/routine tag. + attribute of the codesnip-export/routines/routine tag.
      @@ -369,14 +442,22 @@

      • - version 2: indicates REML v1 + version 2: supports REML v1. +
      • +
      • + version 3: supports REML v2. +
      • +
      • + version 4: supports REML v3. +
      • +
      • + versions 5 to 7.2: supports REML v4.
      • - version 3: indicates REML v2 + version 7.3 & 7.4: supports REML v5.
      • - versions 4 and 5: indicates REML - v3. + version 7.5 & later: supports REML v6.

    • @@ -456,70 +537,90 @@

      • - d2 – Delphi 2 compiler + d2 – Delphi 2 compiler (all versions) +
      • +
      • + d3 – Delphi 3 compiler (all versions) +
      • +
      • + d4 – Delphi 4 compiler (all versions) +
      • +
      • + d5 – Delphi 5 compiler (all versions) +
      • +
      • + d6 – Delphi 6 compiler (all versions) +
      • +
      • + d7 – Delphi 7 compiler (all versions) +
      • +
      • + d2005 – Delphi 2005 compiler (all versions) +
      • +
      • + d2006 – Delphi 2006 compiler (all versions)
      • - d3 – Delphi 3 compiler + d2007 – Delphi 2007 compiler (all versions)
      • - d4 – Delphi 4 compiler + d2009 – Delphi 2009 compiler (all versions)
      • - d5 – Delphi 5 compiler + d2010 – Delphi 2010 compiler (v4.1 & later)
      • - d6 – Delphi 6 compiler + dXE – Delphi XE compiler (v4.2 & later)
      • - d7 – Delphi 7 compiler + dXE2 – Delphi XE2 compiler (v4.3 & later)
      • - d2005 – Delphi 2005 compiler + dXE3 – Delphi XE3 compiler (v4.4 to v4.5 and v6.1 & later)
      • - d2006 – Delphi 2006 compiler + dXE4 – Delphi XE4 compiler (v4.5 only) +
        Note: CodeSnip 3 used correct dXE4 id, but CodeSnip 4 did not (see dDX4 below).
      • - d2007 – Delphi 2007 compiler + dDX4 – Delphi XE4 compiler (v6.2 & later) +
        Note: CodeSnip 4 dDX4 in error instead of dXE4 used by CodeSnip 3 (see above). The erroneous value was retained for backwards compatibility reasons.
      • - d2009 – Delphi 2009 compiler + dXE5 – Delphi XE5 compiler (v6.3 & later)
      • - d2010 – Delphi 2010 compiler + dXE6 – Delphi XE6 compiler (v6.4 & later)
      • - dXE – Delphi XE compiler + dXE7 – Delphi XE7 compiler (v6.5 & later)
      • - dXE2 – Delphi XE2 compiler + dXE8 – Delphi XE8 compiler (v6.6 & later)
      • - dXE3 – Delphi XE3 compiler + d10s – Delphi 10 Seattle compiler (v6.7 & later)
      • - dDX4 – Delphi XE4 compiler. Note: - This value was named dDX4 in error: it should have been - named dXE4 but the erroneous value has been retained for - backwards compatibility reasons. + d101b – Delphi 10.1 Berlin compiler (v6.8 & later)
      • - dXE5 – Delphi XE5 compiler + d102t – Delphi 10.2 Tokyo compiler (v7.1 & later)
      • - dXE6 – Delphi XE6 compiler + d103r – Delphi 10.3 Rio compiler (v7.1 & later)
      • - dXE7 – Delphi XE7 compiler + d104s – Delphi 10.4 Sydney compiler (v7.1 & later)
      • - dXE8 – Delphi XE8 compiler + d11a – Delphi 11.x Alexandria compiler (v7.2 & later)
      • - d10s – Delphi 10 Seattle compiler + d12y – Delphi 12 Athens compiler (v7.4 & later)
      • - fpc – Free Pascal compiler + fpc – Free Pascal compiler (all versions)
      @@ -578,7 +679,7 @@

      • - versions 1..4: Name must begin with an + versions 1 to 4: Name must begin with an English language letter or the underscore.
      • @@ -589,56 +690,74 @@

      -
      - codesnip-export/routines/routine/xref -
      -
      - List of cross-referenced snippets. -
      + -
      - codesnip-export/routines/routine/xref/pascal-name -
      -
      -
      - Name of a snippet within cross-reference list. -
      -
        -
      • - versions 1..4: Name must begin with an - English language letter or the underscore. -
      • -
      • - version 5 and later: Name can begin with - any character that is valid as the first character of a Unicode Pascal - identifier. -
      • -
      -
      - +
      + +

      + Erratum +

      + +

      + The codesnip-export/routines/routine/xref and codesnip-export/routines/routine/xref/pascal-name tags were included in versions 1 to 7 of this specification in error. XRefs were never intended to be written to export files by any version of CodeSnip, as source code comments make clear. +

      + +

      + These tags have been removed from this document entirely of specification version 8. +

      + +
      + +

      - Differences Between File Versions + Change Log

      - The differences between different export file versions is summarised below: + This section describes the changes between versions of the file format. +

      + +

      + There were small changes within versions, that probably should have been given minor version numbers - but weren't. Such minor numbers have been assigned retrospectively below, in order to better explain when in-version changes actually took place. +

      + +

      + File formats v4 and v5/v6 actually overlapped in the dates they were in use. This is because v4 was used by CodeSnip v3 and v5/v6 were used by CodeSnip 4. Those two versions of CodeSnip were maintained in parallel for a while.

      - Version 1 + Version 1 - 15 December 2008
      - First version of database. +

      + Introduced in CodeSnip v2.2 +

      +

      + First version of export file format. +

      +

      + Supported Delphi compilers from Delphi 2 to Delphi 2009 plus Free Pascal. +

      +

      + REML not supported. +

      +

      + The XML file was in UTF-8 format with no BOM and no XML encoding attribute. +

      +
      - Version 2 + Version 2 - 31 December 2008
      -
      - The following tags are no longer supported: -
      +

      + Introduced with CodeSnip v2.2.5. +

      +

      + Removed following tags: +

      • codesnip-export/routines/routine/comments @@ -650,114 +769,282 @@

        codesnip-export/routines/routine/credits-url

      -
      - The following tag was introduced: -
      +

      + Added following tag: +

      • - codesnip-export/routines/routine/extra (uses REML v1 markup). + codesnip-export/routines/routine/extra
      +

      + The version of REML supported by the + codesnip-export/routines/routine/extra tag was v1. +

      +
      - Version 3 + Version 3 - 29 June 2009
      -
      - The following tag is no longer supported: -
      +

      + Introduced with CodeSnip v3.0. +

      +

      + The following tag was removed: +

      • codesnip-export/routines/routine/standard-format
      -
      +

      The following tag was introduced: -

      +

      • codesnip-export/routines/routine/kind
      -
      +

      The version of REML supported by the codesnip-export/routines/routine/extra tag was updated to v2. -

      +

      +
      - Version 4 + Version 4 - 06 July 2009
      - The version of REML supported by the - codesnip-export/routines/routine/extra tag was updated to v3. +

      + Introduced with CodeSnip v3.0.1. +

      +

      + The version of REML supported by the + codesnip-export/routines/routine/extra tag was updated to v3. +

      +
      +
      + Version 4.1 - 24 September 2009 +
      +
      + Updated with CodeSnip v3.4 to add support for Delphi 2010. +
      +
      + Version 4.2 - 23 October 2010 +
      +
      + Updated with CodeSnip v3.8.0 to add support for Delphi XE. +
      +
      + Version 4.3 - 07 September 2011 +
      +
      + Updated with CodeSnip v3.9.0 to add support for Delphi XE2. +
      +
      + Version 4.4 - 17 September 2012 +
      +
      + Updated with CodeSnip v3.11.0 to add support for Delphi XE3. +
      +
      + Version 4.5 - 02 May 2013 +
      +
      + Updated with CodeSnip v3.12.0 to add support for Delphi XE4. +
      +
      +
      - Version 5 + Version 5 - 31 December 2011
      -
      +

      + Introduced with CodeSnip v4.0 alpha 1. +

      +

      The XML file's encoding was explicitly set to "UTF-8" by setting the encoding attribute of the XML processing instruction to this value. -

      -
      +

      +

      Snippet names, wherever they occur in the XML file, can now begin with any character that is a valid first character of a Unicode Pascal identifier. Previously the first character of the attribute had to be one of 'A'..'Z', 'a'..'z' or '_'. -

      -
      +

      +

      New "class" and "unit" snippet kinds supported. -

      +

      +

      + The version of REML supported by the + codesnip-export/routines/routine/extra tag was updated to v4. +

      +
      - Version 6 + Version 6 - 11 August 2012
      -
      - A snippet's description is now stored as formatted text using REML markup. - Previously the description was plain text. -
      -
      +

      + Introduced with CodeSnip v4.0 beta 1. +

      +

      + A snippet's description is now stored as formatted text using REML v4 markup. Previously the description was plain text. +

      +

      The following tags were introduced: -

      +

      • - codesnip-data/routines/routine/display-name + codesnip-export/routines/routine/display-name
      • codesnip-export/routines/routine/highlight-source
      +
      +
      + Version 6.1 - 14 September 2012 +
      +
      + Updated with CodeSnip v4.0 RC 1 to add support for Delphi XE3. +
      +
      + Version 6.2 - 02 May 2013 +
      +
      + Updated with CodeSnip v4.5.0 to add support for Delphi XE4. +
      +
      + Version 6.3 - 12 September 2013 +
      +
      + Updated with CodeSnip v4.8.0 to add support for Delphi XE5. +
      +
      + Version 6.4 - 30 April 2014 +
      +
      + Updated with CodeSnip v4.9.0 to add support for Delphi XE6. +
      +
      + Version 6.5 - 12 September 2014 +
      +
      + Updated with CodeSnip v4.10.0 to add support for Delphi XE7. +
      +
      + Version 6.6 - 06 May 2015 +
      +
      + Updated with CodeSnip v4.12.0 to add support for Delphi XE8. +
      +
      + Version 6.7 - 05 September 2015 +
      +
      + Updated with CodeSnip v4.13.0 to add support for Delphi 10 Seattle. +
      +
      + Version 6.8 - 13 July 2016 +
      +
      + Updated with CodeSnip v4.15.0 to add support for Delphi 10.1 Berlin. +
      +
      +
      + +
      + Version 7 - 31 May 2020 +
      +
      +

      + Introduced with CodeSnip v4.16.0. +

      +

      + The codesnip-export/user-info tag and sub-tags were no longer supported. +

      +
      +
      + Version 7.1 - 31 July 2020 +
      +
      + Updated with CodeSnip v4.17.0 to add support for Delphi 10.2 Tokyo, Delphi 10.3 Rio and Delphi 10.4 Sydney. +
      +
      + Version 7.2 - 13 September 2021 +
      +
      + Updated with CodeSnip v4.18.0 to add support for Delphi 11.x Alexandria. +
      +
      + Version 7.3 - 16 December 2022 +
      +
      + Updated with CodeSnip v4.21.0 to add support for REML v5, which is backward compatible with REML v4. +
      +
      + Version 7.4 - 7 November 2023 +
      +
      + Updated in time for CodeSnip v4.22.0 to add support for Delphi 12 Athens. +
      +
      + Version 7.5 - 2 April 2014 +
      +
      + Added support for REML v6, which is backward compatible with REML v4. +
      +
      +
      + +
      + Version 8 - 15 April 2025 +
      +
      +

      + Introduced with CodeSnip v4.24.3. +

      +

      + The codesnip-export/routines/routine/xref and codesnip-export/routines/routine/xref/pascal-name tags were removed from the specification. See Erratum above for details. +

      -

      - Notes For File Readers Used For Importing -

      +
      + +
      + +

      + Notes For File Readers +

      Readers of v1 files must convert the contents of the - codesnip-data/routines/routine/comments, - codesnip-data/routines/routine/credits and - codesnip-data/routines/routine/credits-url tags into formatted text + codesnip-export/routines/routine/comments, + codesnip-export/routines/routine/credits and + codesnip-export/routines/routine/credits-url tags into formatted text that simulates the parsed content of a - codesnip-data/routines/routine/extra tag. + codesnip-export/routines/routine/extra tag.

      Readers of v1 and v2 files should map a - codesnip-data/routines/routine/standard-format value of "0" - to a codesnip-data/routines/routine/kind value of + codesnip-export/routines/routine/standard-format value of "0" + to a codesnip-export/routines/routine/kind value of "freeform" and a value of "1" to "routine".

      Readers of v1 to v5 files must:

      -
        + +
        • Convert the plain text snippet description read from - codesnip-data/routines/routine/description into the formatted text + codesnip-export/routines/routine/description into the formatted text equivalent of a single paragraph containing the description.
        • @@ -766,6 +1053,20 @@

        +

        + Readers of v1 to v6 files must ignore the codesnip-export/user-info tag and sub-tags, where present. +

        + +

        + Readers of v2 files and later can parse REML as v6, since all versions of REML up to v6 are backwards compatible. +

        + +

        + Readers of v1 to v7 files must ignore any codesnip-export/routines/routine/xref tags and sub tags in the unlikely event that they are found. For an explanation see Erratum above. +

        + +
      + diff --git a/Docs/Design/FileFormats/favourites.html b/Docs/Design/FileFormats/favourites.html index 9d4c0bbb6..8eeaf27d0 100644 --- a/Docs/Design/FileFormats/favourites.html +++ b/Docs/Design/FileFormats/favourites.html @@ -1,18 +1,15 @@  + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> - + diff --git a/Docs/Design/FileFormats/index.html b/Docs/Design/FileFormats/index.html index 42af426d7..eab961529 100644 --- a/Docs/Design/FileFormats/index.html +++ b/Docs/Design/FileFormats/index.html @@ -1,18 +1,15 @@ + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> - + @@ -41,9 +38,7 @@

      - This documentation describes the various file formats used by CodeSnip. It - also includes the format of some streams downloaded from or uploaded to web - services. + This documentation describes the various file formats used by CodeSnip.

        @@ -62,12 +57,6 @@

      • Main Database Files
      • -
      • - Main Database Update Data Stream -
      • -
      • - News RSS Stream -
      • Saved Files
      • diff --git a/Docs/Design/FileFormats/main-db-update.html b/Docs/Design/FileFormats/main-db-update.html deleted file mode 100644 index f962e500f..000000000 --- a/Docs/Design/FileFormats/main-db-update.html +++ /dev/null @@ -1,273 +0,0 @@ - - - - - - - - CodeSnip File Format Documentation - Main Database Update Data Stream - - - - - - - - - - -
        -
        - DelphiDabbler CodeSnip -
        -
        - File Format Documentation -
        -
        - -

        - Main Database Update Data Stream -

        - -

        - Introduction -

        - -

        - The Database Update Data Stream is a stream of data received from the CodeSnip - Database Update web service that is used to update the local copy of the main - database. -

        - -

        - The stream is plain text and consists of a concatenation of text files from - the online database along with some housekeeping information. The text files - are recreated in the main database directory. -

        - -

        - Encoding -

        - -

        - The data stream is received from the web server as single- or multi-byte ANSI - encoded text. The encoding must be such that characters from the ASCII - character set occupy one byte each. Therefore encodings that use two bytes for - such characters, such as UTF-16, cannot be used. -

        - -

        - The actual encoding used is determined by the web server should be specified - in HTTP header. If the HTTP headers do not specify the encoding then - ISO-8859-1 is assumed. -

        - -

        - The encoding used for the files recreated in the main database directory is - UTF-8 with byte order mark. -

        - -

        - Data is converted between several formats on its journey from the web server - to the final database file. See the appendix for - details. -

        - -

        - Stream Format -

        - -

        - The stream contains structured plain text comprising both numeric and string - information. Variable length strings are preceded by numeric values that - indicate the length of the following string in bytes. Numeric values are - encoded as hexadecimal characters. The format is as follows: -

        - -
        -
        - FileCount -
        -
        - Number of files encoded in the data stream. 16 bit integer encoded as four - hex digits. Maximum number of files is 32,767. -
        -
        - -

        - Followed by FileCount file records of: -

        - -
        -
        - Name -
        -
        - Name of file without path information. AnsiString preceded by its size in - bytes as a 16 bit integer encoded as four hex digits. -
        -
        - UnixDate -
        -
        - File's modification date (GMT) in Unix format. Int64 encoded as 16 hex - digits. -
        -
        - Content -
        -
        -
        - File contents. -
        -
        - For web service version 5 this is an AnsiString preceded by its - size in bytes as a 16 bit integer encoded as four hex digits. File size is - limited to 32kB. -
        -
        - For web service version 6 this is an AnsiString preceded by its - size in bytes as a 32 bit integer encoded as eight hex digits. File size - limit is raised to 2 Gb. -
        -
        -
        - -

        - Appendix: Description of Data Encoding Conversions -

        - -

        - The following flowchart show the various encodings used for downloaded data on - its journey from web server to main database file. -

        - -
        - -
        - Text sent from web server using a single or multi-byte ANSI encoding.
        - Encoding used sent in HTTP header. -
        - -
        - ↓ -
        - -
        - ANSI text stream -
        - -
        - ↓ -
        - -
        - CodeSnip's HTTP handling code automatically converts ANSI text stream into - Unicode string using encoding specified in HTTP header. -
        - -
        - ↓ -
        - -
        - Unicode string -
        - -
        - ↓ -
        - -
        - Database download manager code converts Unicode string back into ANSI text - stream with same encoding in which it was sent from web server. -
        - -
        - ↓ -
        - -
        - ANSI text stream -
        - -
        - ↓ -
        - -
        - File updater interprets information stored in formatted ANSI text stream and - get contents of each file, converting them to Unicode. -
        - -
        - ↓ -
        - -
        - Unicode string -
        - -
        - ↓ -
        - -
        - File writer finally writes each file as UTF-8 with a BOM. -
        - -
        - ↓ -
        - -
        - UTF-8 stream -
        - -
        - ↓ -
        - -
        - UTF-8 text files. -
        - -
        - - - - diff --git a/Docs/Design/FileFormats/main-db.html b/Docs/Design/FileFormats/main-db.html index 426c55b28..1b122069c 100644 --- a/Docs/Design/FileFormats/main-db.html +++ b/Docs/Design/FileFormats/main-db.html @@ -1,18 +1,15 @@ - + - + @@ -48,45 +45,48 @@

        Introduction

        +

        + The are two versions of the Code Snippets Database: v1 and v2. Both versions are similar and both are described here. Where one version differs from another the differences will be noted. Where there is no such note there is no difference between the versions. +

        +

        - The Code Snippets database is stored in a set of .ini files with associated - data files located in the main database directory. Two subsidiary text files - containing credits are also included. + The database is organised as a set of categories, each of which contains one or more code snippets.

        - The master data file is named categories.ini. This lists the - database categories. There is another .ini file for each category which stores - information about all the snippets in the category. + Meta data describing the categories and snippets is stored in a set of .ini files. In addition there are numerous .dat files, each of which contains the source code of a snippet.

        - The source code for each snippet is stored in separate data files – one - or more per snippet. This snippet file is referenced in the relevant category - .ini file. + There are additional files containing further information.

        - The credits text files contain lists of names. + All the files are plain text, encoded in UTF-8 format with UTF-8 preamble (BOM).

        Encoding

        +

        + Version 1 +

        +

        - CodeSnip v4 stores all main database files, including the subsidiary text - files, using UTF-8 encoding. Files are saved with the UTF-8 preamble (BOM). + With CodeSnip v4 all the files are plain text, encoded in UTF-8 format with UTF-8 preamble (BOM). Prior to CodeSnip v4 local database files were encoded using the system default ANSI code page.

        - Prior to CodeSnip v4 local database files were encoded using the system - default ANSI code page. + CodeSnip v4 must be able to work with both encodings because it may need to use a database installed by CodeSnip v3 or earlier.

        +

        + Version 2 +

        +

        - CodeSnip v4 must be able to work with both encodings because it may inherit or - have to use a database downloaded by a v3 or earlier version of the program. + All the files are plain text, encoded in UTF-8 format with UTF-8 preamble (BOM).

        @@ -94,33 +94,71 @@

        - Master Data File + Meta Data Files

        - This file has a simple format. Each section is named after a category id, i.e. - the id by which the category is referred to in the database. Each section of - the .ini file has a value containing the description of the category and - another that names the category's .ini file. Here is the format of a - section: + There are two kinds of meta data files: +

        + +
          +
        1. + categories.ini lists the categories contained in the collection. +
        2. +
        3. + A set of further .ini files, one for each category specified in categories.ini. Each of these files contains numerous details of each snippet along with the name of the file containing the source code. +
        4. +
        + +

        + Format Of categories.ini +

        + +

        + Each section in this file defines a category and has the following format:

        -
        [cat-id]
        -Desc=Category description
        -Ini=ini-file-name
        +
        [<cat-id>]
        +Desc=<category-description>
        +Ini=<ini-file-name>

        - and here is an example (this is a copy of the "Date and Time" - category's entry): + The sections in these files are named with a unique identifier that identifies a category within the collection. The section names must be alphanumeric characters. The values have the following purpose: +

        + +
        + +
        + Desc +
        +
        +

        + A brief, human readable, description of the category. Any valid UTF-8 text is permitted. +

        +
        + +
        + Ini +
        +
        +

        + The name of the .ini file that contains information about each snippet in the category. Must be a valid file name, without path. The files must be stored in the same directory as category.ini. +

        +
        + +
        + +

        + Here is an example (this is a copy of the "Date and Time" category's entry):

        [date]
         Desc=Date and Time
         Ini=date.ini
        -

        - Category ini files -

        +

        + Format Of Individual Category .ini Files +

        Each section in these files defines a snippet and has the following content: @@ -157,6 +195,11 @@

        DelphiXE7=<Y|N|Q|W> DelphiXE8=<Y|N|Q|W> Delphi10S=<Y|N|Q|W> +Delphi101B=<Y|N|Q|W> +Delphi102T=<Y|N|Q|W> +Delphi103R=<Y|N|Q|W> +Delphi104S=<Y|N|Q|W> +Delphi11A=<Y|N|Q|W> FPC=<Y|N|Q|W> Extra=<extra-info-REML> StandardFormat=<0|1> @@ -165,502 +208,518 @@

        MinVer=<version-number>

        - The sections in these files are named with a unique name that identifies a - snippet. This must be a valid Unicode Pascal identifier. The values have the - following purpose: + The sections in these files are named with identifiers that uniquely identify a snippet. This must be a valid Unicode Pascal identifier. The keys in a section have the following purpose:

        -
        +
        +
        - DisplayName + DisplayName
        -
        - Display name of snippet. If present this value is used in the program GUI - as the snippet's name. The value is optional. If not present the name of - the section is used as the display name. There are no restriction on the - characters used. -
        -
        - Supported from CodeSnip v4.0 beta 1 and ignored by earlier versions which - always use the section name as display name. -
        +

        + Human readable name of snippet. The value is optional. If not present then the snippet identifier that names the section is used as the human readable name. +

        +
          +
        • + v1: There are no restriction on the characters used. +
        • +
        • + v2: May be any valid UTF-8 string of up to 64 characters. +
        • +
        +
        - Desc + Desc
        - Description of the snippet as plain text. Ignored by versions of CodeSnip - that recognise DescEx if that field is present. +

        + Description of the snippet as plain text. Ignored if a non-empty DescEx key is present. +

        +
        - DescEx + DescEx
        -
        - Formatted description of the snippet using REML code. May be empty, in which case the Desc field - is used. The Desc field is ignored if DescEx has a - value. -
        -
        - Supported from CodeSnip v4.0 beta 1. -
        +

        + Formatted description of the snippet using REML1 code. May be empty, in which case the Desc key is used. Desc is ignored if DescEx has a non-empty value. +

        +
        - Snip + Snip
        - Name of file containing source code. Contains no file path information. +

        + Name of the file containing this snippet's source code. The file name must: +

        +
          +
        • + be a valid file name; +
        • +
        • + contain no path information; +
        • +
        • + exist in the same directory as the .ini file; +
        • +
        • + be unique within the directory. +
        • +
        +
        - Units + Units
        - Comma separated list of units required by the snippet. May be empty if no - units are required. The System unit is assumed and does not need to be - specified. +

        + Comma separated list of units required to compile the snippet. May be omitted or empty if no units are required. The System unit is assumed and does not need to be specified. Unit names must be valid Pascal identifiers. +

        +
        - Depends + Depends
        - Comma separated list of snippets that are required to compile this snippet. - May be empty if there are no dependencies. Must not cause a circular - reference back to this snippet. +

        + Comma separated list of the identifiers of snippets that are required to compile this snippet. May be omitted or empty if there are no such dependencies. Snippet identifiers must exist within the collection and must not cause a circular reference back to this snippet. +

        +
        - SeeAlso + SeeAlso
        - Comma separated list of cross-referenced snippets. May be empty. +

        + Comma separated list of the identifiers of cross-referenced snippets. May be omitted or empty. Snippet identifiers should exist in the collection. +

        +
        - Credits + Credits2
        -
        - Text string that notes any credits / acknowledgements. May be empty. -
        -
        - Credits may optionally contain one (and only one) section of text - delimited by "[" and "]" characters that indicate the - presence of a hyperlink. The text enclosed by "[" and - "]" is used for the text of the hyperlink. If present the - Credits_URL field must contain the URL of the hyperlink. -
        -
        - Credits is ignored by versions of CodeSnip that recognise the - Extra field unless the Extra field is empty. -
        +

        + Text string that notes any credits / acknowledgements. May be omitted or empty. Credits may optionally contain one (and only one) section of text delimited by [ and ] characters that indicate the presence of a hyperlink. The enclosed text is used as the text of a hyperlink whose URL must be specified in the Credits_URL key. +

        +

        + Credits is ignored if a non-empty Extra key is present. +

        +
        - Credits_URL + Credits_URL2
        - The URL used in any hyperlink present in the Credits field. Should - be empty if Credits contains no hyperlink section. +

        + The URL used in any hyperlink present in the Credits key. Must be present if Credits specifies a hyperlink. +

        +

        + Ignored if Credits contains no hyperlink section or if a non-empty Extra key is present. +

        +

          +
        • + v1: Should be empty if Credits contains no hyperlink. +
        • +
        +
        - Comments + Comments
        -
        - Text string containing any additional comments about the snippet. May be - empty. -
        -
        - Ignored by versions of CodeSnip that recognise the Extra field - unless the Extra field is empty. -
        +

        + Text string containing any additional comments about the snippet. Optional. Ignored if a non-empty Extra key is present. +

        +
        - DelphiXXX & FPC + DelphiXXX & FPC
        -
        - This related group of values describe compilation results for the snippets - on various compilers. Valid value names are: -
        +

        + This related group of keys describe compilation results for the snippets on various compilers. Valid key names are: +

        • - Delphi2 – Delphi 2 compiler + Delphi2 – Delphi 2 compiler +
        • +
        • + Delphi3 – Delphi 3 compiler +
        • +
        • + Delphi4 – Delphi 4 compiler
        • - Delphi3 – Delphi 3 compiler + Delphi5 – Delphi 5 compiler
        • - Delphi4 – Delphi 4 compiler + Delphi6 – Delphi 6 compiler
        • - Delphi5 – Delphi 5 compiler + Delphi7 – Delphi 7 compiler
        • - Delphi6 – Delphi 6 compiler + Delphi2005Win32 – Win32 personality of Delphi 2005 compiler
        • - Delphi7 – Delphi 7 compiler + Delphi2006Win32 – Win32 personality of Delphi 2006 compiler
        • - Delphi2005Win32 – Win32 personality of Delphi 2005 - compiler + Delphi2007 – Delphi 2007 compiler
        • - Delphi2006Win32 – Win32 personality of Delphi 2006 - compiler + Delphi2009Win32 – Win32 personality of Delphi 2009 compiler
        • - Delphi2007 – Delphi 2007 compiler + Delphi2010 – Delphi 2010 compiler
        • - Delphi2009Win32 – Win32 personality of Delphi 2009 - compiler + DelphiXE – Delphi XE compiler
        • - Delphi2010 – Delphi 2010 compiler + DelphiXE2 – Delphi XE2 compiler
        • - DelphiXE – Delphi XE compiler + DelphiXE3 – Delphi XE3 compiler
        • - DelphiXE2 – Delphi XE2 compiler + DelphiXE4 – Delphi XE4 compiler
        • - DelphiXE3 – Delphi XE3 compiler + DelphiXE5 – Delphi XE5 compiler
        • - DelphiXE4 – Delphi XE4 compiler + DelphiXE6 – Delphi XE6 compiler
        • - DelphiXE5 – Delphi XE5 compiler + DelphiXE7 – Delphi XE7 compiler
        • - DelphiXE6 – Delphi XE6 compiler + DelphiXE8 – Delphi XE8 compiler
        • - DelphiXE7 – Delphi XE7 compiler + Delphi10S – Delphi 10 Seattle compiler
        • - DelphiXE8 – Delphi XE8 compiler + Delphi101B – Delphi 10.1 Berlin compiler
        • - Delphi10S – Delphi 10 Seattle compiler + Delphi102T – Delphi 10.2 Tokyo compiler *
        • - FPC – Free Pascal compiler + Delphi103R – Delphi 10.3 Rio compiler * +
        • +
        • + Delphi104S – Delphi 10.4 Sydney compiler * +
        • +
        • + Delphi11A – Delphi 11.x Alexandria compiler * +
        • +
        • + Delphi12A – Delphi 12 Athens compiler * +
        • +
        • + FPC – Free Pascal compiler
        -
        - Valid values for these fields are: -
        +

        + Valid values for these keys are: +

        • - "Y" – compiles on the compiler + Y – compiles on the compiler
        • - "N" – does not compile on the compiler + N – does not compile on the compiler
        • - "W" – compiles with warnings on the compiler + W – compiles with warnings on the compiler
        • - "Q" – compilation result unknown + Q – compilation result unknown
        -
        - If not present, the compile result is assumed to be "Q" -
        -
        - From CodeSnip v3.8.9 a compile result of "W" is treated as if it - were "Y". -
        -
        +

        + If any of the above compilers is not present, the compile result is assumed to be Q. A compile result of W is obsolete and treated as if it were Y. +

        +

        Any compiler not recognised by a given version of CodeSnip is ignored. -

        +

        +

        + * These compilers are not yet supported by the online DelphiDabbler Code Snippets Database. +

        +
        - Extra + Extra
        -
        - Provides extra information about the snippet. Can be empty or be a string - of REML code. -
        -
        - If empty the extra information is generated from any Comments and - Credits / Credits_URL values. -
        -
        - This value was supported from CodeSnip v2.2.5. -
        +

        + Optional. Provides extra information about the snippet. When present the value must be a valid string of REML1 code. +

        +

        + If omitted the extra information is generated from the values of any Comments, Credits and Credits_URL keys. +

        +

        + When Extra has a non-empty value the Comments, Credits and Credits_URL keys are ignored. +

        +
        - StandardFormat + StandardFormat
        -
        - Flag indicating if a snippet is in "standard format". Valid - values are: -
        +
          +
        • +

          + v1: Flag indicating if a snippet is in "standard format". Valid values are: +

          +
            +
          • + 0 – no. +
          • +
          • + 1 – yes. +
          • +
          +

          + Ignored if the Kind key is present. +

          +
        • +
        • + v2: not supported. +
        • +
        +
        + +
        + Kind +
        +
        +

        + The kind (or type) of this code snippet. Valid values are: +

        • - "0" – no + freeform – a freeform snippet that doesn't conform to any other other supported type.
        • - "1" – yes. + routine – a Pascal routine (function or procedure).
        • -
        -
        - If not present then StandardFormat = "1" is assumed. -
        -
        - This value was supported from CodeSnip v2.0. -
        -
        - From CodeSnip v3.0 StandardFormat is ignored if the Kind - value is present. If Kind is not present then - StandardFormat = "0" is read as Kind = - "freeform" and StandardFormat = "1" is read - as Kind = "routine". -
        -
        -
        - Kind -
        -
        -
        - Kind of code snippet. Valid values are: -
        -
        • - "freeform" – a freeform snippet that doesn't conform to - any type other supported type + type – a simple Pascal type definition.
        • - "routine" – a Pascal routine (function or procedure) + const – a Pascal constant definition.
        • - "type" – a simple Pascal type definition + class – a Pascal class or advanced record definition and implementation.
        • - "const" – a Pascal constant definition + unit – a complete Pascal unit.
        • +
        +

        + If Kind is omitted or has no value then: +

        +
        • - "class" – a Pascal class or advanced record definition - and implementation. + v1: If the StandardFormat key has value 0 then Kind defaults to freeform. If StandardFormat has value 1 or is not present then Kind defaults to routine.
        • - "unit" – a complete Pascal unit + v2: If Kind is not present then its value defaults to freeform.
        -
        - If Kind is not present and StandardFormat is then - StandardFormat = "1" implies Kind = - "routine" and StandardFormat = "0" implies - Kind = "freeform". If neither Kind nor - StandardFormat are present then Kind defaults to - "routine". -
        -
        - This value was supported from CodeSnip v3.0. -
        +
        - TestInfo + TestInfo
        -
        - Testing information for snippets. Valid values are: -
        +

        + Testing information for the snippet. Valid values are: +

        • - "none" – the snippet has not been tested + none – the snippet has not been tested.
        • - "basic" – the snippet has passed some simple testing + basic – the snippet has passed some simple testing.
        • - "advanced" – the snippet has passed more advanced - testing, usually unit tests + advanced – the snippet has passed more advanced testing, usually unit tests.
        -
        - If TestInfo is not present then its value defaults to - "basic". This is because, until the release of CodeSnip v4.0, - all snippets had undergone at least basic testing. -
        -
        - This value was supported from CodeSnip v4.0. -
        +

        + If TestInfo is not present then its value defaults to basic. +

        +
        - MinVer + MinVer
        -
        - Ignored. -
        -
        - This value was used by a now defunct version of the online Code Snippets - Database, but is no longer used. -
        +

        + Redundant key left over from an earlier, and now unsupported version of CodeSnip. +

        +
          +
        • + v1: Ignored. +
        • +
        • + v2: Not supported. +
        • +
        +

        - Each value should occur only once. If more than one value of the same type is - present the result is not defined and may change between program versions. + Each key should occur only once. If more than one value of the same type is present the result is not defined and may change between program versions. +

        + +

        + This file format is quite messy, with several keys having similar or overlapping purposes. This has happened because new features have been added over time while preserving backward compatibility.

        Pre-processor directives

        +

        + Pre-processor directives are supported only in v1 of the database. +

        +

        - A simple pre-processor was added to CodeSnip v3 and later that can process - special pre-processor instructions contained in .ini files. The pre-processor - symbols are ignored by CodeSnip versions earlier than v3. These symbols allow - the program's version number to be tested and different parts of the file to - be read or ignored accordingly. + A simple pre-processor was added to CodeSnip v3 and later that can process special pre-processor instructions contained in .ini files. The pre-processor symbols ignored by CodeSnip versions earlier than v3. These symbols allow the program's version number to be tested and different parts of the file to be read or ignored accordingly.

        - Pre-processor instructions all begin with ";", the .ini file comment - character, followed by a "#" and then the instruction name. Valid - instructions are: + Pre-processor instructions all begin with ;, the .ini file comment character, followed by a # and then the instruction name. Valid instructions are:

        -
        +
        +
        - if-ver-eq <version> + if-ver-eq <version>
        Checks if application version is equal to <version>.
        +
        - if-ver-neq <version> + if-ver-neq <version>
        - Checks if application version is not equal to <version>. + Checks if the application version is not equal to <version>.
        +
        - if-ver-lt <version> + if-ver-lt <version>
        - Checks if application version is less than <version>. + Checks if the application version is less than <version>.
        +
        - if-ver-lte <version> + if-ver-lte <version>
        - Checks if application version is less than or equal to - <version>. † + Checks if the application version is less than or equal to + <version>. 3
        +
        - if-ver-gt <version> + if-ver-gt <version>
        - Checks if application version is greater than <version>. + Checks if the application version is greater than <version>.
        +
        - if-ver-gte <version> + if-ver-gte <version>
        - Checks if application version is greater than or equal to - <version>. † + Checks if the application version is greater than or equal to + <version>. 3
        +
        - if-ver-inrange <version-lo> <version-hi> + if-ver-inrange <version-lo> <version-hi>
        - Checks if application version is in the range of versions specified from + Checks if the application version is in the range of versions specified from <version-lo> to <version-hi>, inclusive.
        +
        - end-if + end-if
        - Ends a block started by any of the if-xxx instructions above. The block of - text is only evaluated if the if-xxx instruction evaluates true. + Ends a block started by any of the if-xxx instructions above. The block of text is only evaluated if the if-xxx instruction evaluates true.
        +

        - † Due to a bug going back to CodeSnip v3.0 if-ver-gte - and if-ver-lte have never worked correctly: they have always been - interpreted as if-ver-gt and if-ver-lt respectively. A fix - to this could now cause more problems than it solves in older version of - CodeSnip, so the implementation of the problematic directives has - been removed from later versions of the program and all occurrences of the - directives in the database have been replaced by suitable - if-ver-inrange directives. + In all cases version numbers are dotted quads of the form X.X.X.X where X is a sequence of digits. All except the first digit can be left out: omitted digits are assumed to be zero, so that 1.0.0.0, 1.0.0, 1.0 and 1 are all equivalent.

        - In all cases version numbers are dotted quads of the form X.X.X.X where X is - a sequence of digits. All except the first digit can be left out: omitted - digits are assumed to be zero, so that 1.0.0.0, 1.0.0, 1.0 and 1 are all - equivalent. + if-ver-lt 3 can be used to ignore a whole snippet definition in a normal .ini file, leaving the possibility of having a duplicate definition for CodeSnip v3 or later stored in a matching .3.ini file. E.g.:

        -

        - if-ver-lt 3 can be used to ignore a whole snippet definition in a - normal .ini file, leaving the possibility of having a duplicate definition for - CodeSnip v3 or later stored in a matching .3.ini file. E.g. -

        +
        -

        - date.ini: -

        +

        + date.ini: +

        -
        -;#if-ver-lt 3
        -[Snippet1]
        -Desc="My description"
        -Snip=044.dat
        -...
        -;#end-if
        +
        +  ;#if-ver-lt 3
        +  [Snippet1]
        +  Desc="My description"
        +  Snip=044.dat
        +  ...
        +  ;#end-if
        -

        - date.3.ini: -

        +

        + date.3.ini: +

        + +
        [Snippet1]
        +  Desc="My second description"
        +  Snip=044.3.dat
        +  ...
        -
        [Snippet1]
        -Desc="My second description"
        -Snip=044.3.dat
        -...
        +

        - Additional .ini Files For CodeSnip v3 and v4 + Additional .ini Files For CodeSnip v3 and v4

        +

        + Additional .ini files are supported only in v1 of the database. +

        +

        - From CodeSnip v3 additional .ini files with .3 interposed between the file - name and extension are supported (e.g. category.3.ini and - date.3.ini are valid names). + From CodeSnip v3 additional .ini files with .3 interposed between the file name and extension are supported (e.g. category.3.ini and date.3.ini are valid names).

        - From v4 similar files using the interposed .4 extension are supported in - addition to the .3 files (e.g winsys.4.ini). + From CodeSnip v4 similar files using the interposed .4 extension are supported in addition to the .3 files (e.g winsys.4.ini).

        - CodeSnip concatenates all .ini files with the same base name before analysing - the file. For example if date.ini, date.3.ini and - date.4.ini all exist then date.3.ini and - date.4.ini are appended to date.ini before the files - are analysed. + CodeSnip concatenates all .ini files with the same base name before analysing the file. For example if date.ini, date.3.ini and date.4.ini all exist then date.3.ini and date.4.ini are appended to date.ini before the files are analysed.

        - The .3 "inner" extension was provided for category files so that - constant and type definition snippets could be hidden from CodeSnip versions - below v3 but be visible to CodeSnip v3 and later. The new style constant and - type definition snippets are only recorded in .3 style files. + The .3 "inner" extension was provided for category files so that constant and type definition snippets could be hidden from CodeSnip versions below v3 but be visible to CodeSnip v3 and later. The new style constant and type definition snippets are only recorded in .3 style files.

        @@ -670,57 +729,237 @@

        - .4 "inner" extensions are there to support any CodeSnip v4 specific - features that can't be hidden from earlier versions. + .4 "inner" extensions are there to support any CodeSnip v4 specific features that can't be hidden from earlier versions.

        - Finally, it is possible that a name.3.ini or - name.4.ini file will exist with no matching - name.ini. In this case categories.ini (or - categories.3.ini) must still refer to name.ini, not - name.3.ini or name.4.ini. The program works out - which files to load. + Finally, it is possible that a name.3.ini or name.4.ini file will exist with no matching name.ini. In this case categories.ini (or categories.3.ini) must still refer to name.ini, not name.3.ini or name.4.ini. The program works out which files to load.

        +

        Source Code Files

        - Source code is stored separately from the category .ini files. Each snippet - has its own uniquely named file. Files can have any name providing it doesn't - clash with the master file name or the names of the category files. By - convention, source code files are named numerically in sequence and have a - .dat file extension. So 102.dat is a typical source code file - name. Sometimes there is a separate file for later versions of CodeSnip, so - an occasional file named, for example, 102.3.dat may be found. + There is a separate source code file for each snippet. These file names must be named exactly as specified in the related category .ini file's Snip key. They are usually numbered from 001 and have a .dat extension, but this is not a requirement. +

        + +

        + For example 102.dat is a typical source code file name.

        - Source code files are referenced by the Snip field in category .ini - files. + v1 only: Sometimes there is a separate file for later versions of CodeSnip, so an occasional file named, for example, 102.3.dat may be found.

        - Subsidiary "Credits" Text Files + Credits Files

        - There are two credits files, contrib.txt and - testers.txt that list the people who have contributed code to the - main database or helped to test the code, respectively. + There are two credits files that list the people who have contributed code to the database or helped to test the code, respectively. +

        + +

        + The files are named, respectively: +

        + +
          +
        • + v1: contrib.txt and testers.txt. +
        • +
        • + v2: CONTRIBUTORS and TESTERS. +
        • +
        + +

        + Each file is simply a list of names, in plain text format, with each name on a separate line. No blank lines are permitted.

        - Each file is simply a list of names, each name on a separate line. + The files may be empty if there are no contributors and/or testers, but they must be present.

        - The credits files stand alone from the other files in the database in that - they are not referenced by, and do not reference, any of the other files. + The credits files are not referenced by, and do not reference, any of the other files in the database. +

        + +

        + License Files +

        + +

        + License files are supported only in v2 of the database.

        +

        + There are two files relating to license (and copyright) information: the full text of the license in human readable format and a file providing machine readable meta data about the license and copyright. +

        + +

        + Full License Text +

        + +

        + This is a plain UTF-8 text file named LICENSE that contains the license that applies to the source code in the collection. +

        +

        + This file must be present and non-empty. +

        +

        + The LICENSE file is not referenced by, and does not reference, any of the other files in the collection. +

        + +

        + License Meta Information +

        + +

        + A file named LICENSE-INFO is provided that contains information about the license in machine readable form. Information is in Key=Value format as follows: +

        + +
        LicenseName=<human-readable-name>
        +LicenseSPDX=<code>
        +LicenseURL=<url>
        +CopyrightDate=<date-range>
        +CopyrightHolder=<name>
        +CopyrightHolderURL=<url>
        + +

        + The keys have the following meaning: +

        + +
        + +
        + LicenseName +
        +
        +

        + The name of the license as plain text. E.g. MIT License or Mozilla Public License 2.0. +

        +
        + +
        + LicenseSPDX +
        +
        +

        + The Open Source Initiative (OSI) SPDX short identifier of the license, if any. E.g. MIT or MPL-2.0. If the license does not have a SPDX identifier this key must either be omitted or be empty. +

        +

        + For a list of OSI licenses with their SPDXs see https://opensource.org/licenses/alphabetical. +

        +
        + +
        + LicenseURL +
        +
        +

        + The URL of an online copy of the license. E.g. https://opensource.org/licenses/MIT. If the license has no URL then this key must be omitted or be empty. +

        +
        + +
        + CopyrightDate +
        +
        +

        + The date of the copyright or range of copyright dates as plain text. E.g. 2020 or 2005-2020. +

        +
        + +
        + CopyrightHolder +
        +
        +

        + The name of the copyright holder as plain text. Where there are contributors either list them all or append and contributors4 to the primary copyright holder's name. E.g. Joe Bloggs or Annie Smith, Joe Bloggs and Jessie Sharp or Annie Smith and Contributors. +

        +
        + +
        + CopyrightHolderURL +
        +
        +

        + The URL of a web page where details of the copyright holder(s) or primary copyright holder can be found. E.g. https://example.com/joe-blogs-bio. This key is optional. +

        +
        + +
        + +

        + The LICENSE-INFO file is not referenced by, and does not reference, any of the other files in the collection. +

        + +

        + Version Information File +

        + +

        + The version information file is supported only in v2 of the database. +

        + +

        + There is a plain UTF-8 text file named VERSION that contains the version number of the database in the form vX.X.X. where X represents a non-negative integer. The file is required and must be non-empty. +

        + +

        + Version numbers must be incremented according to to the principles of semantic versioning. +

        + +

        + The current major version is v25. +

        + +

        + The VERSION file is not referenced by, and does not reference, any of the other files in the collection. +

        + +

        + Footnotes +

        + +
          +
        1. +

          + REML is a text markup language used to format text. REML version 6 is supported. The REML format is documented here. +

          +
        2. +
        3. +

          + Here is an example of how the Credits and Credits_URL key work together. +

          +

          + If Credits="See [example]" and Credits_URL="https://example.com" and the Extra key is empty or missing then the extra text generated will be See <a href="example.com">example 1</a>. +

          +
        4. +
        5. +

          + Due to a bug going back to CodeSnip v3.0 if-ver-gte and if-ver-lte have never worked correctly: they have always been interpreted as if-ver-gt and if-ver-lt respectively. +

          +

          + A fix to this could now cause more problems than it solves in older version of CodeSnip, so the implementation of the problematic directives has been removed from later versions of the program and all occurrences of the directives in the database have been replaced by suitable if-ver-inrange directives. +

          +
        6. +
        7. +

          + If "contributors" is specified as part of the CopyrightHolder key value in LICENSE-INFO then the CONTRIBUTORS file must contain a list of all the contributors. +

          +
        8. +
        9. +

          + Version tracking was not done before v2.0.0. However it is safe to assume, using semantic versioning, that the current format is the second major release. This is because all previous database versions were backwards compatible and therefore all belonged to the same major version, which, logically, must have been v1. The fact that this version of the database breaks that backward compatibility means the major version must be bumped. +

          +
        10. +
        + + diff --git a/Docs/Design/FileFormats/main.css b/Docs/Design/FileFormats/main.css index 946ec8b04..fcbdefa1b 100644 --- a/Docs/Design/FileFormats/main.css +++ b/Docs/Design/FileFormats/main.css @@ -1,12 +1,9 @@ /* * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2012-2021, Peter Johnson (gravatar.com/delphidabbler). * * CodeSnip File Format Documentation: CSS used by all documentation HTML files. */ @@ -117,6 +114,17 @@ code { font-family: "Courier New", Courier, monospace; } +code.key { + font-weight: bold; + font-style: none; +} + +code.value { + padding: 1px 2px; + background-color: #f5f5f5; + font-style: normal; +} + del { text-decoration: line-through; color: gray; @@ -165,3 +173,13 @@ acronym { font-style: italic; font-weight: bold; } + +.del { + text-decoration: line-through; + color: gray; +} + +.very-strong{ + font-variant: small-caps; + font-weight: bold; +} diff --git a/Docs/Design/FileFormats/news-rss.html b/Docs/Design/FileFormats/news-rss.html deleted file mode 100644 index 2b38cbefe..000000000 --- a/Docs/Design/FileFormats/news-rss.html +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - - CodeSnip File Format Documentation - News RSS Stream - - - - - - - - -
        -
        - DelphiDabbler CodeSnip -
        -
        - File Format Documentation -
        -
        - -

        - News RSS Stream -

        - -

        - CodeSnip downloads news about the program and database as an RSS feed from - DelphiDabbler.com. -

        - -

        - The feed is generated as an RSS XML document using the default encoding used - by the DelphiDabbler web server, ISO-8859-1. The XML document has no encoding - information embedded in it. -

        - -

        - CodeSnip's HTTP handling code gets the RSS document as text and converts it to - a Unicode string using the encoding indicated by the web service (ISO-8859-1 - by default). -

        - -

        - The XML is then parsed as a Unicode string. -

        - -

        - Therefore, although no explicit encoding information is embedded in the XML, - the document is translated to Unicode correctly and information is preserved. -

        - - - - diff --git a/Docs/Design/FileFormats/saved.html b/Docs/Design/FileFormats/saved.html index 9b78e8530..7f9e6b571 100644 --- a/Docs/Design/FileFormats/saved.html +++ b/Docs/Design/FileFormats/saved.html @@ -1,18 +1,15 @@ + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> - + @@ -49,20 +46,45 @@

        - CodeSnip saves external files in two different ways: + CodeSnip saves external files in three different ways:

        1. - By saving snippets to file from the File | Save Snippet menu. + By saving snippet information using the File | Save Snippet Information menu option. +
        2. +
        3. + By saving snippets using the File | Save Snippet menu option.
        4. - By saving units to file from the File | Save Unit menu. + By saving units using the File | Save Unit menu option.

        - In each case the following file types can be chosen by the user: + In the first case the snippet information can be saved as one of the following file types: +

        + +
          +
        • + Plain text. +
        • +
        • + HTML 5 files. +
        • +
        • + XHTML files. +
        • +
        • + Rich text files. +
        • +
        • + Markdown files. +
        • +
        + +

        + In the second two cases the following file types can be chosen by the user:

          @@ -72,6 +94,9 @@

        • Pascal source files (either .inc or .pas files).
        • +
        • + HTML 5 files. +
        • XHTML files.
        • @@ -81,7 +106,7 @@

        - There is no specific file format for these files, other than XHTML and RTF + There is no specific file format for these files, except that HTML 5, XHTML, RTF and Markdown files conform to published specifications.

        @@ -90,7 +115,7 @@

        - The encodings used depend on the file type and user choice. Different file + The available encodings depend on the file type and user choice. Different file types have different encoding choices, as follows:

        @@ -107,10 +132,10 @@

        UTF-8
      • - Unicode little endian (UTF16-LE) + UTF-16LE
      • - Unicode big endian (UTF16-BE) + UTF-16BE
      @@ -127,6 +152,16 @@

    +
    + HTML 5 files +
    +
    +
      +
    • + UTF-8 +
    • +
    +
    XHTML files
    @@ -140,11 +175,30 @@

    Rich text files (RTF)
    +
    +
      +
    • + ASCII +
    • +
    +
    +
    + Markdown +
    • ANSI (system default code page)
    • +
    • + UTF-8 +
    • +
    • + UTF-16LE +
    • +
    • + UTF-16BE +
    diff --git a/Docs/Design/FileFormats/selection.html b/Docs/Design/FileFormats/selection.html index 6d83814cb..6d597fce3 100644 --- a/Docs/Design/FileFormats/selection.html +++ b/Docs/Design/FileFormats/selection.html @@ -1,18 +1,15 @@  + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> - + diff --git a/Docs/Design/FileFormats/test-unit.html b/Docs/Design/FileFormats/test-unit.html index 8ec83e453..4d0722607 100644 --- a/Docs/Design/FileFormats/test-unit.html +++ b/Docs/Design/FileFormats/test-unit.html @@ -1,18 +1,15 @@ + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> - + @@ -49,7 +46,7 @@

    - CodeSnip saves temporary test units that are valid Pascal unit. These units + CodeSnip saves temporary test units that are valid Pascal units. These units are used to perform test compiles.

    @@ -68,8 +65,7 @@

    If the unit's source code contains only characters that are supported by the - system default ANSI code page the file is saved using the default ANSI - encoding. + system default ANSI code page then the file is saved using that encoding.

    @@ -86,7 +82,7 @@

    - Unit filenames are limited to ANSI characters again for reasons of + Unit filenames are limited to ANSI characters also for reasons of compatibility with earlier Delphis because they will report Unicode file names as "not found".

    diff --git a/Docs/Design/FileFormats/user-db.html b/Docs/Design/FileFormats/user-db.html index d4b7cc103..d8d7773f0 100644 --- a/Docs/Design/FileFormats/user-db.html +++ b/Docs/Design/FileFormats/user-db.html @@ -1,18 +1,15 @@ + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> - + @@ -44,6 +41,42 @@

    User Database Files

    +
    + +

    + Contents +

    + + + +
    + +
    +

    Introduction

    @@ -61,16 +94,17 @@

    - The source code for each snippet is stored in separate sequentially numbered - .dat data files – one per snippet. Each source code file is referenced - by the XML file. + The source code for each snippet is stored in separate, sequentially numbered, .dat data files – one per snippet. Each source code file is referenced by the XML file.

    - There have been several different versions of the XML file format. Each of - these is explained below. + There have been several different versions of the XML file format. The differences between versions are explained below. Details of all the changes between versions are listed in the Change Log at the end of this document

    +

    + +
    +

    Encoding

    @@ -83,21 +117,23 @@

    - Prior to CodeSnip v4 source code data files were encoded using the system - default ANSI code page. The XML file was in UTF-8, but its XML processing - instruction had no "encoding" atrribute. + Prior to CodeSnip v4 (and database v5) source code data files were encoded using ANSI code page 1252. The XML file was in UTF-8, but its XML processing instruction had no "encoding" atrribute.

    - CodeSnip v4 must be able to work with all these encoding because it may + CodeSnip v4 must be able to work with all these encodings because it may inherit a copy of a user database in an earlier format.

    +

    + +
    +

    File Format

    -

    +

    XML File

    @@ -163,7 +199,7 @@

    version

    - Identifies version of file. Determines which tags are valid and rules + Identifies major version of file. Determines which tags are valid and rules concerning content. Valid versions are 1..6.
    @@ -285,8 +321,16 @@

    of plain text.
  1. - version 6: Content is formatted text - encoded in REML markup. REML v3 is supported. + version 6.0 to 6.10: Content is formatted text + encoded in REML markup. REML v4 is supported. +
  2. +
  3. + version 6.11 & 6.12: Content is formatted text + encoded in REML markup. REML v5 is supported. +
  4. +
  5. + version 6.13 & later: Content is formatted text + encoded in REML markup. REML v6 is supported.
  6. @@ -299,7 +343,7 @@

    - codesnip-export/routines/routine/highlight-source + codesnip-data/routines/routine/highlight-source
      @@ -416,17 +460,26 @@

      version 2 and later: Additional information about a snippet. Content is formatted text encoded in - REML markup. + REML markup.
      • - version 2: indicates REML v1 + version 2: supports REML v1. +
      • +
      • + version 3: supports REML v2.
      • - version 3: indicates REML v2 + version 4: supports REML v3.
      • - versions 4..6: indicates REML v3. + versions 5 & 6.10: supports REML v4. +
      • +
      • + version 6.11 & 6.12: supports REML v5. +
      • +
      • + version 6.13 & later: supports REML v6.
      @@ -506,70 +559,90 @@

      • - d2 – Delphi 2 compiler + d2 – Delphi 2 compiler (all versions) +
      • +
      • + d3 – Delphi 3 compiler (all versions) +
      • +
      • + d4 – Delphi 4 compiler (all versions) +
      • +
      • + d5 – Delphi 5 compiler (all versions) +
      • +
      • + d6 – Delphi 6 compiler (all versions)
      • - d3 – Delphi 3 compiler + d7 – Delphi 7 compiler (all versions)
      • - d4 – Delphi 4 compiler + d2005 – Delphi 2005 compiler (all versions)
      • - d5 – Delphi 5 compiler + d2006 – Delphi 2006 compiler (all versions)
      • - d6 – Delphi 6 compiler + d2007 – Delphi 2007 compiler (all versions)
      • - d7 – Delphi 7 compiler + d2009 – Delphi 2009 compiler (v1.1 & later)
      • - d2005 – Delphi 2005 compiler + d2010 – Delphi 2010 compiler (v4.1 & later)
      • - d2006 – Delphi 2006 compiler + dXE – Delphi XE compiler (v4.2 & later)
      • - d2007 – Delphi 2007 compiler + dXE2 – Delphi XE2 compiler (v4.3 & later)
      • - d2009 – Delphi 2009 compiler + dXE3 – Delphi XE3 compiler (v4.4..v4.5 and v6.1 & later)
      • - d2010 – Delphi 2010 compiler + dXE4 – Delphi XE4 compiler (v4.5 only) +
        Note: CodeSnip 3 used correct dXE4 id, but CodeSnip 4 did not (see dDX4 below).
      • - dXE – Delphi XE compiler + dDX4 – Delphi XE4 compiler (v6.2 & later) +
        Note: CodeSnip 4 dDX4 in error instead of dXE4 used by CodeSnip 3 (see above). The erroneous value was retained for backwards compatibility reasons.
      • - dXE2 – Delphi XE2 compiler + dXE5 – Delphi XE5 compiler (v6.3 & later)
      • - dXE3 – Delphi XE3 compiler + dXE6 – Delphi XE6 compiler (v6.4 & later)
      • - dDX4 – Delphi XE4 compiler. Note: - This value was named dDX4 in error: it should have been - named dXE4 but the erroneous value has been retained for - backwards compatibility reasons. + dXE7 – Delphi XE7 compiler (v6.5 & later)
      • - dXE5 – Delphi XE5 compiler + dXE8 – Delphi XE8 compiler (v6.6 & later)
      • - dXE6 – Delphi XE6 compiler + d10s – Delphi 10 Seattle compiler (v6.7 & later)
      • - dXE7 – Delphi XE7 compiler + d101b – Delphi 10.1 Berlin compiler (v6.8 & later)
      • - dXE8 – Delphi XE8 compiler + d102t – Delphi 10.2 Tokyo compiler (v6.9 & later)
      • - d10s – Delphi 10 Seattle compiler + d103r – Delphi 10.3 Rio compiler (v6.9 & later)
      • - fpc – Free Pascal compiler + d104s – Delphi 10.4 Sydney compiler (v6.9 & later) +
      • +
      • + d11a – Delphi 11.x Alexandria compiler (v6.10 & later) +
      • +
      • + d12y – Delphi 12 Athens compiler (v6.12 & later) +
      • +
      • + fpc – Free Pascal compiler (all versions)

    @@ -667,14 +740,14 @@

    -

    +

    Source Code Files

    Source code is stored separately from the main XML file. The source code of - each snippet has its file. Files are numbered sequentially and have a .dat - extension, for example 6.dat. + each snippet has its own file. Files are numbered sequentially and have a + .dat extension, for example 6.dat.

    @@ -683,29 +756,63 @@

    XML file.

    + + +
    +

    - Differences Between File Versions + Change Log

    - The differences between different user database file versions is summarised - below: + This section describes the changes between versions of the file format. +

    + +

    + There were small changes within versions, that probably should have been given minor version numbers - but weren't. Such minor numbers have been assigned retrospectively below in order to better explain when in-version changes actually took place. +

    + +

    + File formats v4 and v5/v6 actually overlapped in the dates they were in use. This is because v4 was used by CodeSnip v3 and v5/v6 were used by CodeSnip 4. Those two versions of CodeSnip were maintained in parallel for a while.

    - Version 1 + Version 1 - 15 September 2008
    - First version of database. +

    + Introduced with CodeSnip v2.0. +

    +

    + Supported Delphi compilers from Delphi 2 to Delphi 2007 plus Free Pascal. +

    +

    + REML not supported. +

    +

    + Data files were ANSI text using code page 1252. The XML file was in UTF-8 format with no BOM and no XML encoding attribute. +

    +
    +
    + Version 1.1 - 11 October 2008 +
    +
    + Updated with CodeSnip v2.1 to add support for Delphi 2009. +
    +
    +
    - Version 2 + Version 2 - 31 December 2008
    -
    - The following tags are no longer supported: -
    +

    + Introduced with CodeSnip v2.2.5. +

    +

    + Removed following tags: +

    • codesnip-data/routines/routine/comments @@ -717,102 +824,256 @@

      codesnip-data/routines/routine/credits-url

    -
    - The following tag was introduced: -
    +

    + Added following tag: +

    • - codesnip-data/routines/routine/extra (uses REML v1 markup). + codesnip-data/routines/routine/extra
    +

    + The version of REML supported by the + codesnip-data/routines/routine/extra tag was v1. +

    +
    - Version 3 + Version 3 - 29 June 2009
    -
    +

    + Introduced with CodeSnip v3.0. +

    +

    The following tag is no longer supported: -

    +

    • codesnip-data/routines/routine/standard-format
    -
    +

    The following tag was introduced: -

    +

    • codesnip-data/routines/routine/kind
    -
    - The version of REML supported by the - codesnip-data/routines/routine/extra tag was updated to v2. -
    +

    + The version of REML supported by the + codesnip-data/routines/routine/extra tag was updated to v2. +

    +
    - Version 4 + Version 4 - 06 July 2009
    - The version of REML supported by the - codesnip-data/routines/routine/extra tag was updated to v3. +

    + Introduced with CodeSnip v3.0.1. +

    +

    + The version of REML supported by the + codesnip-data/routines/routine/extra tag was updated to v3. +

    +
    +
    + Version 4.1 - 24 September 2009 +
    +
    + Updated with CodeSnip v3.4 to add support for Delphi 2010. +
    +
    + Version 4.2 - 23 October 2010 +
    +
    + Updated with CodeSnip v3.8.0 to add support for Delphi XE. +
    +
    + Version 4.3 - 07 September 2011 +
    +
    + Updated with CodeSnip v3.9.0 to add support for Delphi XE2. +
    +
    + Version 4.4 - 17 September 2012 +
    +
    + Updated with CodeSnip v3.11.0 to add support for Delphi XE3. +
    +
    + Version 4.5 - 02 May 2013 +
    +
    + Updated with CodeSnip v3.12.0 to add support for Delphi XE4. +
    +
    +
    - Version 5 + Version 5 - 21 April 2012
    -
    +

    + Introduced with CodeSnip v4.0 alpha 2. +

    +

    The XML file's encoding was explicitly set to "UTF-8" by setting the encoding attribute of the XML processing instruction to this value. -

    -
    +

    +

    Snippet names, wherever they occur in the XML file, can now begin with any character that is a valid first character of a Unicode Pascal identifier. Previously the first character of the attribute had to be one of 'A'..'Z', 'a'..'z' or '_'. -

    -
    +

    +

    Data files changed to use UTF-8 encoding with no BOM instead of the system default encoding. -

    -
    +

    +

    New "class" and "unit" snippet kinds supported. -

    +

    +

    + The version of REML supported by the + codesnip-data/routines/routine/extra tag was updated to v4. +

    +
    - Version 6 + Version 6 - 11 August 2012
    -
    - A snippet's description is now stored as formatted text using REML markup. - Previously the description was plain text. -
    -
    - The following tag was introduced: -
    +

    + Introduced with CodeSnip v4.0 beta 1. +

    +

    + A snippet's description is now stored as formatted text using REML v4 markup. Previously the description was plain text. +

    +

    + The following tags were introduced: +

    • codesnip-data/routines/routine/display-name
    • - codesnip-export/routines/routine/highlight-source + codesnip-data/routines/routine/highlight-source
    +
    +
    + Version 6.1 - 14 September 2012 +
    +
    + Updated with CodeSnip v4.0 RC 1 to add support for Delphi XE3. +
    +
    + Version 6.2 - 02 May 2013 +
    +
    + Updated with CodeSnip v4.5.0 to add support for Delphi XE4. +
    +
    + Version 6.3 - 12 September 2013 +
    +
    + Updated with CodeSnip v4.8.0 to add support for Delphi XE5. +
    +
    + Version 6.4 - 30 April 2014 +
    +
    + Updated with CodeSnip v4.9.0 to add support for Delphi XE6. +
    +
    + Version 6.5 - 12 September 2014 +
    +
    + Updated with CodeSnip v4.10.0 to add support for Delphi XE7. +
    +
    + Version 6.6 - 06 May 2015 +
    +
    + Updated with CodeSnip v4.12.0 to add support for Delphi XE8. +
    +
    + Version 6.7 - 05 September 2015 +
    +
    + Updated with CodeSnip v4.13.0 to add support for Delphi 10 Seattle. +
    +
    + Version 6.8 - 13 July 2016 +
    +
    + Updated with CodeSnip v4.15.0 to add support for Delphi 10.1 Berlin. +
    +
    + Version 6.9 - 31 July 2020 +
    +
    + Updated with CodeSnip v4.17.0 to add support for Delphi 10.2 Tokyo, Delphi 10.3 Rio and Delphi 10.4 Sydney. +
    +
    + Version 6.10 - 13 September 2021 +
    +
    + Updated with CodeSnip v4.18.0 to add support for Delphi 11.x Alexandria. +
    +
    + Version 6.11 - 16 December 2022 +
    +
    + Updated with CodeSnip v4.21.0 to add support for REML v5, which is backwards compatible with REML v4. +
    +
    + Version 6.12 - 7 November 2023 +
    +
    + Updated in time for CodeSnip v4.22.0 to add support for Delphi 12 Athens. +
    +
    + Version 6.13 - 2 April 2024 +
    +
    + Updated with CodeSnip v4.23.0 to add support for REML v6, which is backwards compatible with REML v4. +
    +
    -

    +

    + +
    + +

    Notes for File Readers +

    + +

    + To ensure backwards compatibility with all user database versions file reader software that works with the latest version of CodeSnip needs to be able to interpret older formats as follows. +

    + +

    + Handling redundant XML tags

    - Readers of version 1 files must convert the contents of the - codesnip-data/routines/routine/comments, - codesnip-data/routines/routine/credits and - codesnip-data/routines/routine/credits-url tags into formatted text - that simulates the parsed content of the - codesnip-data/routines/routine/extra tag. + Readers of version 1 files must convert the contents of the the following tags: +

    + +
      +
    • codesnip-data/routines/routine/comments
    • +
    • codesnip-data/routines/routine/credits
    • +
    • codesnip-data/routines/routine/credits-url
    • +
    + +

    + into valid REML code that simulates the parsed content of the codesnip-data/routines/routine/extra tag.

    @@ -825,18 +1086,37 @@

    Readers of v1 to v5 files must:

    -
      + +
      • Convert the plain text snippet description read from - codesnip-data/routines/routine/description into the formatted text + codesnip-data/routines/routine/description into the REML equivalent of a single paragraph containing the description.
      • - Proceed as if a codesnip-export/routines/routine/highlight-source + Proceed as if a codesnip-data/routines/routine/highlight-source tag with value "1" had been specified.
      +

      + Readers of v2 and later files may parse REML from any file version as if it were REML v6, since all versions of REML up to v6 are compatible. +

      + +

      + Handling Text File Encodings +

      + +

      + Readers of v1 to v4 files should interpret all source code .dat files as encoded in ANSI code page 1252 - the files were created using the default code page in the UK, which is 1252. The XML file should be assumed to be in UTF-8 format, regardless of the absence of an encoding attribute. +

      + +

      + v5 and later files will always be encoded in UTF-8. +

      + +

    + diff --git a/Docs/Design/Warnings.txt b/Docs/Design/Warnings.txt index c00758683..1e2ddd6a1 100644 --- a/Docs/Design/Warnings.txt +++ b/Docs/Design/Warnings.txt @@ -5,8 +5,3 @@ XE warning options as follows: "Platform Symbol" is switched OFF. "Widening given AnsiChar constant to WideChar lost information" is switched ON. "Widening given AnsiString constant lost information" is switched ON. - --------------------------------------------------------------------------------- -$Rev$ -$Date$ --------------------------------------------------------------------------------- \ No newline at end of file diff --git a/Docs/Design/WebServices.txt b/Docs/Design/WebServices.txt deleted file mode 100644 index 321967fb8..000000000 --- a/Docs/Design/WebServices.txt +++ /dev/null @@ -1,67 +0,0 @@ -================================================================================ - -Documentation of classes that access web-services on behalf of CodeSnip. - -================================================================================ - - -DelphiDabbler Standard Web Service --------------------------------------------------------------------------------- - -A DelphiDabbler Standard Web Service object has the following attributes: - -* It connects to the web service over HTTP. - -* Requests are made using HTTP Post. Information is POSTed to the web service - either in the form of URL encoded query strings or as raw post data. Which - format is used depends on web service. - -* Web services respond with plain text. The text is encoded as the default for - the server which defaults to ISO-8859-1. - -* The response text is split into lines by CRLF pairs. There is always at least - one line in a response. Any trailing CRLFs should be stripped from the - response text. - -* The first line of the response always contains a status code. A zero code - indicates success, while non-zero codes indicate an error. The meaning of the - error code is specific to the web service. Consult the documentation of the - service for details. - -* A successful response *may* include further information, starting from the - second line of the text. The meaning of this text depends on the web service. - Consult the documentation of the service for details. - -* An error response *must* include an error message starting from the second - line of the text. Error messages *may* have more than one line. - -Some web services also require that the correct user agent string for the -service is used. Such web services return HTTP error 403 if the user agent is -not valid. User agent strings *may* also specify the version of the web service -to be used. - -The response format in ABNF is: - - response = status_code [CRLF data] [CRLF] - status_code = success_code / error_code - success_code = "0" - error_code = ["-"] 1*DIGIT - data = data_line [CRLF data] - data_line = 0*[VCHAR | WSP] - -The class TStdWebService, which inherits from TBaseWebService, provides core -functionality for working with standard web services. In particular it: - -* Provides methods for posting data in the form of query strings to the web - service. - -* Handles the response, checking the status code. When an error code is received - an exception is raised using the error message from the response. In the case - of a successful response the status code line is stripped and the remaining - data made available for processing by descendant classes. - - --------------------------------------------------------------------------------- -$Rev$ -$Date$ --------------------------------------------------------------------------------- diff --git a/Docs/Design/reml.html b/Docs/Design/reml.html new file mode 100644 index 000000000..fb8b0ce15 --- /dev/null +++ b/Docs/Design/reml.html @@ -0,0 +1,172 @@ + + + + + + + + + + + + + + + + + REML Documentation + + + + + + +
    + +
    +

    + REML Markup Language +

    +
    + +
    + +
    + +
    + +

    + About REML +

    + +

    + REML is a little markup language that can be used to style text. It is a SGML language similar to HTML, albeit much smaller. A small number of tags and character entities are supported. +

    + +

    + See the REML v6 language definition for full details. +

    + +
    + +
    + +

    + REML in CodeSnip +

    + +

    + Code snippets include REML to format snippets' description and extra fields. CodeSnip interprets and renders the REML when displaying snippets in its UI and when printing them. +

    + +

    + CodeSnip currently supports REML v6. Earlier versions of CodeSnip supported different versions of REML: +

    + +
      +
    • REML v1 was first supported by CodeSnip v2.2.5
    • +
    • REML v2 was first supported by CodeSnip v3.0
    • +
    • REML v3 was first supported by CodeSnip v3.0.1
    • +
    • REML v4 was first supported by CodeSnip v4.0 alpha 1 (preview)
    • +
    • REML v5 was first supported by CodeSnip v4.21.0
    • +
    • REML v6 was first supported by CodeSnip v4.23.0
    • +
    + +

    + All CodeSnip versions are backward compatible with earlier versions of REML. +

    + +
    + +
    + + + + diff --git a/Docs/LICENSE b/Docs/LICENSE deleted file mode 100644 index 72fc4f92e..000000000 --- a/Docs/LICENSE +++ /dev/null @@ -1,8 +0,0 @@ -All the files in the Docs directory, and all its sub-directories are governed by -the following license. - -This Source Code Form is subject to the terms of the Mozilla Public -License, v. 2.0. If a copy of the MPL was not distributed with this -file, You can obtain one at http://mozilla.org/MPL/2.0/. - -All files are copyright (C) 2012-2016, Peter Johnson (www.delphidabbler.com). diff --git a/Docs/License.html b/Docs/License.html index 97e6bda07..c47a44323 100644 --- a/Docs/License.html +++ b/Docs/License.html @@ -1,23 +1,20 @@  - + CodeSnip License @@ -224,14 +221,19 @@ <h1> Overview </h1> + <p> + This license applies to the standard and portable editions <em>CodeSnip</em> in executable form and to <em>CodeSnip</em>'s source code. + </p> + <p> + An unaltered copy of this license <strong>must</strong> be distributed with any executable or source code form of <em>CodeSnip</em>. + </p> <h2> Executable Program </h2> <p> - DelphiDabbler <em>CodeSnip</em> is copyright © 2005-2016 by Peter D - Johnson, <a - href="http://www.delphidabbler.com" - >http://www.delphidabbler.com</a>. + DelphiDabbler <em>CodeSnip</em> is copyright © 2005-2025 by <a + href="https://gravatar.com/delphidabbler" + >Peter D Johnson</a>. </p> <p> The executable version of the program is made available under the terms of @@ -241,194 +243,206 @@ <h2> <em>CodeSnip</em> as you wish. </p> <p> - You may also modify <em>CodeSnip</em> as you wish and you may distribute - copies of your modified version under the terms of the Mozilla Public - License. The only exception is that you may not use the program's branding - (including the names "DelphiDabbler" and "CodeSnip", the program's icon and the splash screen), in any modification you distribute, - unless you have the explicit permission of the copyright holder. + You may also modify <em>CodeSnip</em> and you may distribute copies of your modified version under the terms of the license, with the exception that you may not use the program's branding (including the names "DelphiDabbler" and "CodeSnip", the program's icon and the splash screen), in any modification you distribute, unless you have the explicit permission of the copyright holder. </p> <h2> Source Code </h2> <p> - All of <em>CodeSnip</em>'s original source code, and some third party code, - is available from the <a - href="http://delphidabbler.com/view/repo/codesnip" - ><em>CodeSnip</em> Subversion Repository</a>. The exception is the <em>Indy - Project</em> source code, which must be obtained separately. + For the purposes of this license, the term "source code" refers to all files that are part of the <a href="https://github.com/delphidabbler/codesnip"><em>delphidabbler/codesnip</em></a> repository on GitHub. This includes all program code, documentation and image files. </p> <p> - Details of the license applying to a source code file will usually be - included in a comment within the file itself. If this is not the case any - file named <kbd>LICENSE</kbd> in the same directory, or a parent directory, - should contain the required information. + Unless explicitly mentioned in the <em>Exceptions</em> sub-section below, all source files are licensed under the <a href="#mpl-2.0">Mozilla Public License 2.0</a> (MPL 2.0). </p> + <h3> + Exceptions + </h3> <p> - Most of the source code is available under the <a href="#mpl-2.0">Mozilla - Public License 2.0</a> (MPL 2.0). Other relevant source code licenses are - listed below. + The following licenses apply to the specified files: </p> <ul> <li> <div class="license-name"> - Indy Dual License + <a href="#tlistviewex">Vadim Crit's TListViewEx License</a> </div> - <div> - You may use the licensed code under the terms of either of the following - two licenses. + <div class="applies-to"> + <kbd>Src/3rdParty/LVEx.pas</kbd>. </div> - <ul> - <li> - <div class="license-name"> - <a href="#mpl-1.1">Mozilla Public License v1.1</a> - </div> - </li> - <li> - <div class="license-name"> - <a href="#indy-bsd">Indy Modified BSD License</a> - </div> - </li> - </ul> <div class="applies-to"> - Used by the <em>Indy Project</em>. Components from this project are - <strong>not</strong> included in the <em>CodeSnip</em> repository. + <kbd>Src/3rdParty/LVEx.res</kbd>. </div> </li> <li> <div class="license-name"> - <a href="#md5">MD5 License</a> + <a href="#jquery">jQuery License</a> </div> <div class="applies-to"> - Applies to <kbd>Src/3rdParty/PJMD5.pas</kbd>, in addition to the <a - href="#mpl-2.0" - >Mozilla Public License 2.0</a>. + <kbd>Src/Res/Scripts/3rdParty/jquery-1.12.4.min.js</kbd>. </div> </li> <li> <div class="license-name"> - <a href="#tgifimage">TGIFImage License</a> + <a href="#jquery-cycle">jQuery Cycle Lite Plugin MIT License</a> </div> <div class="applies-to"> - Used by <kbd>Src/3rdParty/GIFImage.pas</kbd>. + <kbd>Src/Res/Scripts/3rdParty/jquery.cycle.lite.js</kbd>. + </div> + <div class="indent"> + Note that jQuery Cycle Lite is dual licensed under the MIT or GPL license. It is used here under the MIT license. </div> </li> <li> <div class="license-name"> - <a href="#tlistviewex">Vadim Crit's TListViewEx License</a> + <a href="#CC-BY-SA-3.0">Creative Commons Attribution Share Alike 3.0 License</a> </div> <div class="applies-to"> - Used by <kbd>Src/3rdParty/LVEx.pas</kbd> and - <kbd>Src/3rdParty/LVEx.res</kbd>. + All files in the <kbd>Src/Help/Images</kbd> directory. + </div> + <div class="applies-to"> + All files in the <kbd>Src/Res/Img</kbd> directory. + </div> + <div class="applies-to"> + All files in the <kbd>Src/Res/Img/Egg</kbd> directory. + </div> + <aside> + <div> + This license requires that the images in the above directories should be attributed. To do this + simply note in your documentation, about box, web page or similar that + the images form part of the image set for DelphiDabbler <em>CodeSnip</em> + and provide a link to <a + href="https://delphidabbler.com/software/codesnip" + >https://delphidabbler.com/software/codesnip</a>. + </div> + </aside> + <div class="indent"> + <div> + Some of the image files above include copies, modifications or remixes of third-party images supplied under the following licenses: + </div> + <ul> + <li> + <div class="license-name"> + <a href="#CC-BY-2.5">Creative Commons Attribution 2.5 License</a> + </div> + <div class="applies-to"> + Silk Icon set v1.3. + </div> + <div class="applies-to"> + Silk Companion 1. + </div> + </li> + <li> + <div class="license-name"> + <a href="#CC-BY-SA-3.0">Creative Commons Attribution Share Alike 3.0 + License</a> + </div> + <div class="applies-to"> + Led Icon Set. + </div> + <div class="applies-to"> + Aha-Soft 16x16 Free Application Icons. + </div> + </li> + <li> + <div class="license-name"> + <a href="#toolbar-icons-mit">Toolbar Icons MIT License</a> + </div> + <div class="applies-to"> + Toolbar Icons. + </div> + </li> + </ul> + </div> + <div class="indent"> + Those images originally supplied under the <a href="#CC-BY-2.5">Creative Commons Attribution 2.5 License</a> and the <a href="#toolbar-icons-mit">Toolbar Icons MIT License</a> have been relicensed under the <a href="#CC-BY-SA-3.0">Creative Commons Attribution Share Alike 3.0 License</a>, as is permitted by the licenses. </div> </li> <li> <div class="license-name"> - <a href="#jquery">jQuery License</a> + <a href="#cc0">CC0 1.0 Universal Public Domain Dedication</a> + </div> + <div class="applies-to"> + All files in the <kbd>Src/Res/Img/AltBranding</kbd> directory. + </div> + <aside> + <div> + These files are provided as placeholder replacements for the identically named files in the <kbd>Src/Res/Img/Branding</kbd> directory that are not permitted to be used in derived programs ("Larger Works"). + </div> + </aside> + <div class="applies-to"> + <kbd>Src/CodeSnip.cfg.tplt</kbd>. + </div> + <div class="applies-to"> + <kbd>Src/CodeSnip.dproj</kbd>. + </div> + <div class="applies-to"> + <kbd>Src/CodeSnip.groupproj</kbd>. + </div> + <div class="applies-to"> + <kbd>Src/CodeSnip.todo</kbd>. </div> <div class="applies-to"> - Used by <kbd>Src/Res/Scripts/3rdParty/jquery-1.8.0.min.js</kbd> + All files in the <kbd>Tests/Src/DUnit</kbd> directory. </div> </li> <li> <div class="license-name"> - <a href="#jquery-cycle">jQuery Cycle Lite Plugin License</a> + <a href="#ddab-exclusive">DelphiDabbler Exclusive Use License</a> </div> <div class="applies-to"> - Used by <kbd>Src/Res/Scripts/3rdParty/jquery.cycle.lite.js</kbd> + <code>Docs/License.html</code> (this file). + </div> + <div class="indent"> + Any derived applications ("Larger Works") <strong>must</strong> include a license that is compatible with the terms of this license as it relates to any of <em>CodeSnip</em>'s source code that is used in the larger work. + </div> + <div class="applies-to"> + All files in the <kbd>Src/Res/Img/Branding</kbd> directory. + </div> + <div class="indent"> + These files comprise the program's icon and splash screen and <strong>must not</strong> be used in, or distributed with, derived programs. + </div> + <aside> + <div> + Identically named images from the <kbd>Src/Res/Img/AltBranding</kbd> directory may be used as replacements in derived programs ("Larger Works"). These images may be freely modified. + </div> + </aside> + </li> + <li> + <div class="license-name"> + <a href="#md5">MD5 License</a> + </div> + <div class="applies-to"> + <kbd>Src/3rdParty/PJMD5.pas</kbd> + </div> + <div class="indent"> + The MD5 License applies to this file <em>in addition</em> to the <a href="#mpl-2.0">Mozilla Public License 2.0</a>. </div> </li> </ul> + + <h3> + Automatically generated files + </h3> <p> - Some 3rd party source code requires attribution. See the - <a href="#required-notices">Required Notices</a> section below. - </p> - <h2> - Images - </h2> - <p> - Numerous images are used in the <em>CodeSnip</em> project. Some are - original while others are copied or modified from third party sources. + Some source files are automatically generated as part of the build process. Such files are not included in the <a href="https://github.com/delphidabbler/codesnip"><em>delphidabbler/codesnip</em></a> repository. </p> <p> - Copies of the images are available in the <a - href="http://delphidabbler.com/view/repo/codesnip" - ><em>CodeSnip</em> Subversion Repository</a> in the - <kbd>Src/Help/Images</kbd> and <kbd>Src/Res/Img</kbd> directories and - sub-directories. These images are licensed as follows: + The license that applies to these files is the same as that of the generating file. The automatically generated files are: </p> <ul> <li> - <div> - The program's icon and splash screen may not be copied or modified and - may not be used in distribution of derived programs without explicit - permission of the copyright holder. - </div> - <div> - This condition applies to all files in the - <kbd>Src/Res/Img/Branding</kbd> directory, all of which are original - work copyright © 2012 by Peter D Johnson, <a - href="http://www.delphidabbler.com/" - >http://www.delphidabbler.com/</a>. - </div> + <kbd>Src/CodeSnip.cfg</kbd>, generated from <kbd>CodeSnip.cfg.tplt</kbd> (<a href="#cc0">CC0</a>). </li> <li> - <div> - Images found in the <kbd>Src/Help/Images</kbd>, <kbd>Src/Res/Img</kbd> - and <kbd>Src/Res/Img/Egg</kbd> directories, are licensed under the - <a href="#CC-BY-SA-3.0">Creative Commons Attribution Share Alike 3.0 - License</a>. - </div> - <div> - The license requires that the images should be attributed. To do this - simply note in your documentation, about box, web page or similar that - the icons form part of the image set for DelphiDabbler <em>CodeSnip</em> - and provide a link to <a - href="http://delphidabbler.com/software/codesnip" - >http://delphidabbler.com/software/codesnip</a>. - </div> - <div> - These images include modifications and remixes of icons supplied under - the following licenses: - </div> - <ul> - <li> - <div class="license-name"> - <a href="#CC-BY-2.5">Creative Commons Attribution 2.5 License</a> - </div> - <div class="applies-to"> - Silk Icon set v1.3 - </div> - <div class="applies-to"> - Silk Companion 1 - </div> - </li> - <li> - <div class="license-name"> - <a href="#CC-BY-SA-3.0">Creative Commons Attribution Share Alike 3.0 - License</a> - </div> - <div class="applies-to"> - Led Icon Set - </div> - <div class="applies-to"> - Aha-Soft 16x16 Free Application Icons - </div> - </li> - <li> - <div class="license-name"> - <a href="#toolbar-icons-mit">Toolbar Icons MIT License</a> - </div> - <div class="applies-to"> - Toolbar Icons - </div> - </li> - </ul> + <kbd>Src/AutoGen/IntfExternalObj.pas</kbd>, generated from <kbd>Src/ExternalObj.ridl</kbd> (<a href="#mpl-2.0">MPL 2.0</a>). </li> </ul> + <h3> + Attribution + </h3> <p> - Some 3rd party image sets require attribution. See the - <a href="#required-notices">Required Notices</a> section below. + Some 3rd party source code and image sets require attribution. Such attributions are provided in the <a href="#required-notices">Required Notices</a> section below. </p> + </section> <section id="open-source-licenses"> @@ -854,7 +868,7 @@ <h3>Exhibit A - Source Code Form License Notice</h3> <blockquote> <p>This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, - You can obtain one at http://mozilla.org/MPL/2.0/.</p> + You can obtain one at https://mozilla.org/MPL/2.0/.</p> </blockquote> <p>If it is not possible or desirable to put the notice in a particular @@ -871,583 +885,7 @@ <h3>Exhibit B - "Incompatible With Secondary Licenses" Notice</h3> Licenses", as defined by the Mozilla Public License, v. 2.0.</p> </blockquote> - <hr /> - - <h2 id="mpl-1.1"> - Mozilla Public License v1.1 - </h2> - - <h3>1. Definitions.</h3> - <dl> - <dt>1.0.1. "Commercial Use"</dt> - - <dd> - <p>means distribution or otherwise making the Covered Code available to - a third party.</p> - </dd> - - <dt>1.1. "Contributor"</dt> - - <dd> - <p>means each entity that creates or contributes to the creation of - Modifications.</p> - </dd> - - <dt>1.2. "Contributor Version"</dt> - - <dd> - <p>means the combination of the Original Code, prior Modifications used - by a Contributor, and the Modifications made by that particular - Contributor.</p> - </dd> - - <dt>1.3. "Covered Code"</dt> - - <dd> - <p>means the Original Code or Modifications or the combination of the - Original Code and Modifications, in each case including portions - thereof.</p> - </dd> - - <dt>1.4. "Electronic Distribution Mechanism"</dt> - - <dd> - <p>means a mechanism generally accepted in the software development - community for the electronic transfer of data.</p> - </dd> - - <dt>1.5. "Executable"</dt> - - <dd> - <p>means Covered Code in any form other than Source Code.</p> - </dd> - - <dt>1.6. "Initial Developer"</dt> - - <dd> - <p>means the individual or entity identified as the Initial Developer in - the Source Code notice required by Exhibit A.</p> - </dd> - - <dt>1.7. "Larger Work"</dt> - - <dd> - <p>means a work which combines Covered Code or portions thereof with - code not governed by the terms of this License.</p> - </dd> - - <dt>1.8. "License"</dt> - - <dd> - <p>means this document.</p> - </dd> - - <dt>1.8.1. "Licensable"</dt> - - <dd> - <p>means having the right to grant, to the maximum extent possible, - whether at the time of the initial grant or subsequently acquired, any and - all of the rights conveyed herein.</p> - </dd> - - <dt>1.9. "Modifications"</dt> - - <dd> - <p>means any addition to or deletion from the substance or structure of - either the Original Code or any previous Modifications. When Covered Code - is released as a series of files, a Modification is:</p> - <ol type="a"> - <li>Any addition to or deletion from the contents of a file containing - Original Code or previous Modifications.</li> - <li>Any new file that contains any part of the Original Code or - previous Modifications.</li> - </ol> - </dd> - - <dt>1.10. "Original Code"</dt> - - <dd> - <p>means Source Code of computer software code which is described in the - Source Code notice required by Exhibit A as Original Code, and which, at - the time of its release under this License is not already Covered Code - governed by this License.</p> - </dd> - - <dt>1.10.1. "Patent Claims"</dt> - - <dd> - <p>means any patent claim(s), now owned or hereafter acquired, including - without limitation, method, process, and apparatus claims, in any patent - Licensable by grantor.</p> - </dd> - - <dt>1.11. "Source Code"</dt> - - <dd> - <p>means the preferred form of the Covered Code for making modifications - to it, including all modules it contains, plus any associated interface - definition files, scripts used to control compilation and installation of - an Executable, or source code differential comparisons against either the - Original Code or another well known, available Covered Code of the - Contributor's choice. The Source Code can be in a compressed or archival - form, provided the appropriate decompression or de-archiving software is - widely available for no charge.</p> - </dd> - - <dt>1.12. "You" (or "Your")</dt> - - <dd> - <p>means an individual or a legal entity exercising rights under, and - complying with all of the terms of, this License or a future version of - this License issued under Section 6.1. For legal entities, "You" - includes any entity which controls, is controlled by, or is under common - control with You. For purposes of this definition, "control" - means (a) the power, direct or indirect, to cause the direction or - management of such entity, whether by contract or otherwise, or (b) - ownership of more than fifty percent (50%) of the outstanding shares or - beneficial ownership of such entity.</p> - </dd> - - </dl> - - <h3>2. Source Code License.</h3> - - <h4>2.1. The Initial Developer Grant.</h4> - - <p>The Initial Developer hereby grants You a world-wide, royalty-free, - non-exclusive license, subject to third party intellectual property - claims:</p> - - <ol type="a"> - - <li>under intellectual property rights (other than patent or trademark) - Licensable by Initial Developer to use, reproduce, modify, display, perform, - sublicense and distribute the Original Code (or portions thereof) with or - without Modifications, and/or as part of a Larger Work; and</li> - - <li>under Patents Claims infringed by the making, using or selling of - Original Code, to make, have made, use, practice, sell, and offer for sale, - and/or otherwise dispose of the Original Code (or portions thereof).</li> - - <li>the licenses granted in this Section 2.1 (a) and (b) are effective on - the date Initial Developer first distributes Original Code under the terms - of this License.</li> - - <li>Notwithstanding Section 2.1 (b) above, no patent license is granted: 1) - for code that You delete from the Original Code; 2) separate from the - Original Code; or 3) for infringements caused by: i) the modification of the - Original Code or ii) the combination of the Original Code with other - software or devices.</li> - - </ol> - - <h4>2.2. Contributor Grant.</h4> - - <p>Subject to third party intellectual property claims, each Contributor - hereby grants You a world-wide, royalty-free, non-exclusive license</p> - - <ol type="a"> - <li>under intellectual property rights (other than patent or trademark) - Licensable by Contributor, to use, reproduce, modify, display, perform, - sublicense and distribute the Modifications created by such Contributor (or - portions thereof) either on an unmodified basis, with other Modifications, - as Covered Code and/or as part of a Larger Work; and</li> - - <li>under Patent Claims infringed by the making, using, or selling of - Modifications made by that Contributor either alone and/or in combination - with its Contributor Version (or portions of such combination), to make, - use, sell, offer for sale, have made, and/or otherwise dispose of: 1) - Modifications made by that Contributor (or portions thereof); and 2) the - combination of Modifications made by that Contributor with its Contributor - Version (or portions of such combination).</li> - - <li>the licenses granted in Sections 2.2 (a) and 2.2 (b) are effective - on the date Contributor first makes Commercial Use of the Covered Code.</li> - - <li>Notwithstanding Section 2.2 (b) above, no patent license is granted: 1) - for any code that Contributor has deleted from the Contributor Version; 2) - separate from the Contributor Version; 3) for infringements caused by: i) - third party modifications of Contributor Version or ii) the combination of - Modifications made by that Contributor with other software (except as part - of the Contributor Version) or other devices; or 4) under Patent Claims - infringed by Covered Code in the absence of Modifications made by that - Contributor.</li> - </ol> - - <h3>3. Distribution Obligations.</h3> - - <h4>3.1. Application of License.</h4> - - <p>The Modifications which You create or to which You contribute are governed - by the terms of this License, including without limitation Section 2.2. The - Source Code version of Covered Code may be distributed only under the terms of - this License or a future version of this License released under Section 6.1, - and You must include a copy of this License with every copy of the Source Code - You distribute. You may not offer or impose any terms on any Source Code - version that alters or restricts the applicable version of this License or the - recipients' rights hereunder. However, You may include an additional document - offering the additional rights described in Section 3.5.</p> - - <h4>3.2. Availability of Source Code.</h4> - - <p>Any Modification which You create or to which You contribute must be made - available in Source Code form under the terms of this License either on the - same media as an Executable version or via an accepted Electronic Distribution - Mechanism to anyone to whom you made an Executable version available; and if - made available via Electronic Distribution Mechanism, must remain available - for at least twelve (12) months after the date it initially became available, - or at least six (6) months after a subsequent version of that particular - Modification has been made available to such recipients. You are responsible - for ensuring that the Source Code version remains available even if the - Electronic Distribution Mechanism is maintained by a third party.</p> - - <h4>3.3. Description of Modifications.</h4> - - <p>You must cause all Covered Code to which You contribute to contain a file - documenting the changes You made to create that Covered Code and the date of - any change. You must include a prominent statement that the Modification is - derived, directly or indirectly, from Original Code provided by the Initial - Developer and including the name of the Initial Developer in (a) the Source - Code, and (b) in any notice in an Executable version or related documentation - in which You describe the origin or ownership of the Covered Code.</p> - - <h4>3.4. Intellectual Property Matters</h4> - - <h5>(a) Third Party Claims</h5> - - <p>If Contributor has knowledge that a license under a third party's - intellectual property rights is required to exercise the rights granted by - such Contributor under Sections 2.1 or 2.2, Contributor must include a text - file with the Source Code distribution titled "LEGAL" which - describes the claim and the party making the claim in sufficient detail that a - recipient will know whom to contact. If Contributor obtains such knowledge - after the Modification is made available as described in Section 3.2, - Contributor shall promptly modify the LEGAL file in all copies Contributor - makes available thereafter and shall take other steps (such as notifying - appropriate mailing lists or newsgroups) reasonably calculated to inform those - who received the Covered Code that new knowledge has been obtained.</p> - - <h5>(b) Contributor APIs</h5> - - <p>If Contributor's Modifications include an application programming - interface and Contributor has knowledge of patent licenses which are - reasonably necessary to implement that <abbr>API</abbr>, Contributor must - also include this information in the <span class="very-strong">Legal</span> - file.</p> - - <h5>(c) Representations.</h5> - - <p>Contributor represents that, except as disclosed pursuant to Section 3.4 - (a) above, Contributor believes that Contributor's Modifications are - Contributor's original creation(s) and/or Contributor has sufficient rights - to grant the rights conveyed by this License.</p> - - <h4>3.5. Required Notices.</h4> - - <p>You must duplicate the notice in Exhibit A in each file of the Source - Code. If it is not possible to put such notice in a particular Source Code - file due to its structure, then You must include such notice in a location - (such as a relevant directory) where a user would be likely to look for such - a notice. If You created one or more Modification(s) You may add your name as - a Contributor to the notice described in Exhibit A<. You must also duplicate - this License in any documentation for the Source Code where You describe - recipients' rights or ownership rights relating to Covered Code. You may - choose to offer, and to charge a fee for, warranty, support, indemnity or - liability obligations to one or more recipients of Covered Code. However, You - may do so only on Your own behalf, and not on behalf of the Initial Developer - or any Contributor. You must make it absolutely clear than any such warranty, - support, indemnity or liability obligation is offered by You alone, and You - hereby agree to indemnify the Initial Developer and every Contributor for any - liability incurred by the Initial Developer or such Contributor as a result - of warranty, support, indemnity or liability terms You offer.</p> - - <h4>3.6. Distribution of Executable Versions.</h4> - - <p>You may distribute Covered Code in Executable form only if the - requirements of Sections 3.1, 3.2, 3.3, 3.4 and 3.5 have been met for that - Covered Code, and if You include a notice stating that the Source Code - version of the Covered Code is available under the terms of this License, - including a description of how and where You have fulfilled the obligations - of Section 3.2. The notice must be conspicuously included in any notice in an - Executable version, related documentation or collateral in which You describe - recipients' rights relating to the Covered Code. You may distribute the - Executable version of Covered Code or ownership rights under a license of - Your choice, which may contain terms different from this License, provided - that You are in compliance with the terms of this License and that the - license for the Executable version does not attempt to limit or alter the - recipient's rights in the Source Code version from the rights set forth in - this License. If You distribute the Executable version under a different - license You must make it absolutely clear that any terms which differ from - this License are offered by You alone, not by the Initial Developer or any - Contributor. You hereby agree to indemnify the Initial Developer and every - Contributor for any liability incurred by the Initial Developer or such - Contributor as a result of any such terms You offer.</p> - - <h4>3.7. Larger Works.</h4> - - <p>You may create a Larger Work by combining Covered Code with other code - not governed by the terms of this License and distribute the Larger Work as a - single product. In such a case, You must make sure the requirements of this - License are fulfilled for the Covered Code.</p> - - <h3>4. Inability to Comply Due to Statute or Regulation.</h3> - - <p>If it is impossible for You to comply with any of the terms of this - License with respect to some or all of the Covered Code due to statute, - judicial order, or regulation then You must: (a) comply with the terms of - this License to the maximum extent possible; and (b) describe the limitations - and the code they affect. Such description must be included in the - <span class="very-strong">Legal</span> file described in Section 3.4 and must - be included with all distributions of the Source Code. Except to the extent - prohibited by statute or regulation, such description must be sufficiently - detailed for a recipient of ordinary skill to be able to understand it.</p> - - <h3>5. Application of this License.</h3> - - <p>This License applies to code to which the Initial Developer has attached - the notice in Exhibit A and to related Covered Code.</p> - - <h3>6. Versions of the License.</h3> - - <h4>6.1. New Versions</h4> - - <p>Netscape Communications Corporation ("Netscape") may publish - revised and/or new versions of the License from time to time. Each version - will be given a distinguishing version number.</p> - - <h4>6.2. Effect of New Versions</h4> - - <p>Once Covered Code has been published under a particular version of the - License, You may always continue to use it under the terms of that version. - You may also choose to use such Covered Code under the terms of any - subsequent version of the License published by Netscape. No one other than - Netscape has the right to modify the terms applicable to Covered Code created - under this License.</p> - - <h4>6.3. Derivative Works</h4> - - <p>If You create or use a modified version of this License (which you may - only do in order to apply it to code which is not already Covered Code - governed by this License), You must (a) rename Your license so that the - phrases "Mozilla", "MOZILLAPL", "MOZPL", - "Netscape", "MPL", "NPL" or any confusingly - similar phrase do not appear in your license (except to note that your license - differs from this License) and (b) otherwise make it clear that Your version - of the license contains terms which differ from the Mozilla Public License and - Netscape Public License. (Filling in the name of the Initial Developer, - Original Code or Contributor in the notice described in Exhibit A shall not of - themselves be deemed to be modifications of this License.)</p> - - <h3>7. <strong class="very-strong">Disclaimer of warranty</strong></h3> - - <p><strong class="very-strong">Covered code is provided under this license - on an "as is" basis, without warranty of any kind, either expressed - or implied, including, without limitation, warranties that the covered code is - free of defects, merchantable, fit for a particular purpose or - non-infringing. The entire risk as to the quality and performance of the - covered code is with you. Should any covered code prove defective in any - respect, you (not the initial developer or any other contributor) assume the - cost of any necessary servicing, repair or correction. This disclaimer of - warranty constitutes an essential part of this license. No use of any covered - code is authorized hereunder except under this disclaimer.</strong></p> - - <h3>8. Termination</h3> - - <p>8.1. This License and the rights granted hereunder will terminate - automatically if You fail to comply with terms herein and fail to cure such - breach within 30 days of becoming aware of the breach. All sublicenses to the - Covered Code which are properly granted shall survive any termination of this - License. Provisions which, by their nature, must remain in effect beyond the - termination of this License shall survive.</p> - - <p>8.2. If You initiate litigation by asserting a patent infringement claim - (excluding declatory judgment actions) against Initial Developer or a - Contributor (the Initial Developer or Contributor against whom You file such - action is referred to as "Participant") alleging that:</p> - - <ol type="a"> - <li>such Participant's Contributor Version directly or indirectly infringes - any patent, then any and all rights granted by such Participant to You under - Sections 2.1 and/or 2.2 of this License shall, upon 60 days notice from - Participant terminate prospectively, unless if within 60 days after receipt - of notice You either: (i) agree in writing to pay Participant a mutually - agreeable reasonable royalty for Your past and future use of Modifications - made by such Participant, or (ii) withdraw Your litigation claim with - respect to the Contributor Version against such Participant. If within 60 - days of notice, a reasonable royalty and payment arrangement are not - mutually agreed upon in writing by the parties or the litigation claim is - not withdrawn, the rights granted by Participant to You under Sections - 2.1 and/or 2.2 automatically terminate at the expiration of the 60 day - notice period specified above.</li> - - <li>any software, hardware, or device, other than such Participant's - Contributor Version, directly or indirectly infringes any patent, then any - rights granted to You by such Participant under Sections 2.1(b) and 2.2(b) - are revoked effective as of the date You first made, used, sold, - distributed, or had made, Modifications made by that Participant.</li> - </ol> - - <p>8.3. If You assert a patent infringement claim against Participant alleging - that such Participant's Contributor Version directly or indirectly infringes - any patent where such claim is resolved (such as by license or settlement) - prior to the initiation of patent infringement litigation, then the reasonable - value of the licenses granted by such Participant under Sections 2.1 or 2.2 - shall be taken into account in determining the amount or value of any payment - or license.</p> - - <p>8.4. In the event of termination under Sections 8.1 or 8.2 above, all end - user license agreements (excluding distributors and resellers) which have been - validly granted by You or any distributor hereunder prior to termination shall - survive termination.</p> - - <h3>9. <strong class="very-strong">Limitation of liability</strong></h3> - - <p><strong class="very-strong">Under no circumstances and under no legal - theory, whether tort (including negligence), contract, or otherwise, shall - you, the initial developer, any other contributor, or any distributor of - covered code, or any supplier of any of such parties, be liable to any person - for any indirect, special, incidental, or consequential damages of any - character including, without limitation, damages for loss of goodwill, work - stoppage, computer failure or malfunction, or any and all other commercial - damages or losses, even if such party shall have been informed of the - possibility of such damages. This limitation of liability shall not apply to - liability for death or personal injury resulting from such party's negligence - to the extent applicable law prohibits such limitation. Some jurisdictions do - not allow the exclusion or limitation of incidental or consequential damages, - so this exclusion and limitation may not apply to you.</strong></p> - - <h3>10. <abbr title="United States">U.S.</abbr> government end users</h3> - - <p>The Covered Code is a "commercial item," as that term is defined - in 48 <abbr>C.F.R.</abbr> 2.101 (<abbr title="October">Oct.</abbr> 1995), - consisting of "commercial computer software" and "commercial - computer software documentation," as such terms are used in 48 - <abbr>C.F.R.</abbr> 12.212 (<abbr title="September">Sept.</abbr> 1995). - Consistent with 48 <abbr>C.F.R.</abbr> 12.212 and 48 <abbr>C.F.R.</abbr> - 227.7202-1 through 227.7202-4 (June 1995), all <abbr>U.S.</abbr> Government - End Users acquire Covered Code with only those rights set forth herein.</p> - - <h3>11. Miscellaneous</h3> - - <p>This License represents the complete agreement concerning subject matter - hereof. If any provision of this License is held to be unenforceable, such - provision shall be reformed only to the extent necessary to make it - enforceable. This License shall be governed by California law provisions - (except to the extent applicable law, if any, provides otherwise), excluding - its conflict-of-law provisions. With respect to disputes in which at least one - party is a citizen of, or an entity chartered or registered to do business in - the United States of America, any litigation relating to this License shall be - subject to the jurisdiction of the Federal Courts of the Northern District of - California, with venue lying in Santa Clara County, California, with the - losing party responsible for costs, including without limitation, court - costs and reasonable attorneys' fees and expenses. The application of the - United Nations Convention on Contracts for the International Sale of Goods is - expressly excluded. Any law or regulation which provides that the language of - a contract shall be construed against the drafter shall not apply to this - License.</p> - - <h3>12. Responsibility for claims</h3> - - <p>As between Initial Developer and the Contributors, each party is - responsible for claims and damages arising, directly or indirectly, out of its - utilization of rights under this License and You agree to work with Initial - Developer and Contributors to distribute such responsibility on an equitable - basis. Nothing herein is intended or shall be deemed to constitute any - admission of liability.</p> - - <h3>13. Multiple-licensed code</h3> - - <p>Initial Developer may designate portions of the Covered Code as - "Multiple-Licensed". "Multiple-Licensed" means that the - Initial Developer permits you to utilize portions of the Covered Code under - Your choice of the <abbr>MPL</abbr> or the alternative licenses, if any, - specified by the Initial Developer in the file described in Exhibit A.</p> - - <h3>Exhibit A - Mozilla Public License.</h3> - -<pre>"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/ - -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 ______________________________________. - -The Initial Developer of the Original Code is ________________________. -Portions created by ______________________ are Copyright (C) ______ -_______________________. All Rights Reserved. - -Contributor(s): ______________________________________. - -Alternatively, the contents of this file may be used under the terms -of the _____ license (the "[___] License"), in which case the -provisions of [______] License are applicable instead of those -above. If you wish to allow use of your version of this file only -under the terms of the [____] License and not to allow others to use -your version of this file under the MPL, indicate your decision by -deleting the provisions above and replace them with the notice and -other provisions required by the [___] License. If you do not delete -the provisions above, a recipient may use your version of this file -under either the MPL or the [___] License."</pre> - - <p>NOTE: The text of this Exhibit A may differ slightly from the text of the - notices in the Source Code files of the Original Code. You should use the text - of this Exhibit A rather than the text found in the Original Code Source Code - for Your Modifications.</p> - - <hr /> - - <h2 id="indy-bsd"> - Indy Modified BSD License - </h2> - - <h3>Copyright</h3> - - <p>Portions of this software are Copyright © 1993 - 2003, Chad Z. Hower - (Kudzu) and the Indy Pit Crew - <a - href="http://www.IndyProject.org/" - >http://www.IndyProject.org/</a></p> - - <h3>License</h3> - - <p>Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are - met:</p> - - <ul> - - <li>Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer.</li> - - <li>Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation, about box and/or other materials provided with the - distribution.</li> - - <li>No personal names or organizations names associated with the Indy - project may be used to endorse or promote products derived from this - software without specific prior written permission of the specific - individual or organization. </li> - - </ul> - - <p>THIS SOFTWARE IS PROVIDED BY Chad Z. Hower (Kudzu) and the Indy Pit Crew - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR - BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER - IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE.</p> - - <hr /> + <hr> <h2 id="md5"> MD5 License @@ -1474,120 +912,7 @@ <h2 id="md5"> <p>These notices must be retained in any copies of any part of this documentation and/or software.</p> - <hr /> - - <h2 id="tgifimage"> - TGIFImage License - </h2> - - <p>TGIFImage is Copyright © 1997-99 Anders Melander. All rights - reserved.</p> - - <p - style="text-decoration: line-through;" - title="No such file was included in the distribution." - >Please see <kbd>copyright.txt</kbd> for additional copyrights. - </p> - - <p>Before proceeding with the installation and/or use of this software, - carefully read the following terms and conditions of this license agreement - and limited warranty (The License).</p> - - <p>By installing or using this software you indicate your acceptance of this - License. If you do not accept or agree with these terms, you may not install - or use this software!</p> - - <h3>License</h3> - - <p>This software, including documentation, source code, object code and/or - additional materials (TGIFImage) is owned by Anders Melander (the Author).</p> - - <p>This License does not provide you with title or ownership of TGIFImage, but - only a right of limited use as outlined in this License agreement. The Author - hereby grants you a non-exclusive, royalty free license to use TGIFImage as - set forth below:</p> - - <ul> - <li>integrate TGIFImage with your Applications, subject to the - redistribution terms below.</li> - - <li>modify or adapt TGIFImage in whole or in part for the development of - Applications based on TGIFImage.</li> - - <li>use portions of the TGIFImage source code or TGIFImage Demo Programs in - your own products.</li> - </ul> - - <h3>Redistribution rights</h3> - - <p>You are granted a non-exclusive, royalty-free right to reproduce and - redistribute executable files created using TGIFImage (the Executable Code) - in conjunction with software products that you develop and/or market (the - Applications).</p> - - <h4>Restrictions</h4> - - <p>Without the expressed, written consent of the Author, you may NOT:</p> - - <ul> - <li>distribute modified versions of TGIFImage, in whole or in part.</li> - - <li>rent or lease TGIFImage.</li> - - <li>sell any portion of TGIFImage on its own, without integrating it into - your Applications as Executable Code.</li> - - <li>bundle TGIFImage with commercial development libraries.</li> - - <li>charge for the value TGIFImage adds to your Applications.</li> - </ul> - - <aside> - <p>TGIFImage v2.2 is now maintained by Finn Tolderlund, <a - href="http://www.tolderlund.eu/delphi/" - >http://www.tolderlund.eu/delphi/</a> with the permission of Anders - Melander.</p> - </aside> - - <h3>Limited warranty</h3> - - <p>There is no warranty or other guarantee of fitness for this software, it is - provided solely "as is". Bug reports or fixes may be sent to the - Author, who may or may not act on them as he desires.</p> - - <h3>LZW license</h3> - - <p>GIF (and thus TGIFImage) uses an adaption of the LZW compression algorithm - for image compression. The LZW algorithm is patented by UNISYS. Unfortunately - UNISYS requires royalty payment for all software that uses the LZW - algorithm.</p> - - <p>To avoid the use of the LZW algorithm for writing GIFs, TGIFImage can write - GIFs using a LZW compatible RLE compression method. See the - <var>TGIFImage.Compression</var> property for more information. There are - conflicting opinions on whether a LZW license is required to read GIFs. Some - patent lawyers are of the opinion that the LZW patent does not cover LZW - decoders, but others disagree. If this matters to you, you should contact your - own lawyer.</p> - - <p>For information regarding UNISYS' view on the use of LZW in commercial - software, please read the License Information on GIF and Other LZW-based - Technologies. The UNISYS patent on the LZW algorithm may or may not apply to - you depending on the laws of your country. Personally I have less than warm - feelings for Unisys and their patent and I don't care if you have a license or - not.</p> - - <p>The LZW patent <span style="text-decoration:line-through">expires</span> - expired in 2004.</p> - - <h3>Credit of work</h3> - - <p>If you redistribute TGIFImage in binary form (i.e. as a library or linked - into an application), the accompanying documentation (e.g. readme file, help - file or about-box) should state that This software is based, in part, on the - work of Anders Melander or words to that effect.</p> - - <hr /> + <hr> <h2 id="tlistviewex"> Vadim Crit's TListViewEx License @@ -1607,15 +932,15 @@ <h2 id="tlistviewex"> the reference to the original author.</li> </ol> - <hr /> + <hr> <h2 id="jquery"> jQuery License </h2> <p>Copyright 2012 jQuery Foundation and other contributors <a - href="http://jquery.com/" - >http://jquery.com/</a></p> + href="https://jquery.com/" + >https://jquery.com/</a></p> <p>Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -1636,15 +961,15 @@ <h2 id="jquery"> ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</p> - <hr /> + <hr> <h2 id="jquery-cycle"> - jQuery Cycle Lite Plugin License + jQuery Cycle Lite Plugin MIT License </h2> <p>Copyright 2008-2012 M. Alsup <a - href="http://malsup.com/jquery/cycle/lite/" - >http://malsup.com/jquery/cycle/lite/</a></p> + href="https://malsup.com/jquery/cycle/lite/" + >https://malsup.com/jquery/cycle/lite/</a></p> <p>Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -1665,7 +990,7 @@ <h2 id="jquery-cycle"> ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</p> - <hr /> + <hr> <h2 id="CC-BY-SA-3.0"> Creative Commons Attribution Share Alike 3.0 License @@ -1721,8 +1046,8 @@ <h3><em>License</em></h3> <li><strong>"Creative Commons Compatible License"</strong> means a license that is listed at <a - href="http://creativecommons.org/compatiblelicenses" - >http://creativecommons.org/compatiblelicenses</a> that has been approved by + href="https://creativecommons.org/compatiblelicenses" + >https://creativecommons.org/compatiblelicenses</a> that has been approved by Creative Commons as being essentially equivalent to this License, including, at a minimum, because that license: (i) contains terms that have the same purpose, meaning and effect as the License Elements of this License; and, @@ -2056,12 +1381,12 @@ <h3>Creative Commons Notice</h3> License.</p> <p>Creative Commons may be contacted at <a - href="http://creativecommons.org/" - >http://creativecommons.org/</a>.</p> + href="https://creativecommons.org/" + >https://creativecommons.org/</a>.</p> </aside> <!-- END CC NOTICE --> - <hr /> + <hr> <h2 id="CC-BY-2.5"> Creative Commons Attribution 2.5 License @@ -2330,11 +1655,11 @@ <h2 id="CC-BY-2.5"> available upon request from time to time.</p> <p>Creative Commons may be contacted at <a - href="http://creativecommons.org" - >http://creativecommons.org/</a>.</p> + href="https://creativecommons.org" + >https://creativecommons.org/</a>.</p> </aside> - <hr /> + <hr> <h2 id="toolbar-icons-mit"> Toolbar Icons MIT License @@ -2342,8 +1667,8 @@ <h2 id="toolbar-icons-mit"> <p>Toolbar Icons is made available under the terms of the MIT License. See <a - href="http://toolbaricons.sourceforge.net/" - >http://toolbaricons.sourceforge.net/</a> for more information.</p> + href="https://toolbaricons.sourceforge.net/" + >https://toolbaricons.sourceforge.net/</a> for more information.</p> <p>Copyright © 2010 Florian Haag</p> @@ -2366,20 +1691,129 @@ <h2 id="toolbar-icons-mit"> ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</p> + <hr> + + <h2 id="cc0"> + CC0 1.0 Universal Public Domain Dedication + </h2> + + <aside> + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER. + </aside> + + <h3> + Statement of Purpose + </h3> + + <p> + The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). + </p> + + <p> + Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. + </p> + + <p> + For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. + </p> + + <p> + <strong>1. Copyright and Related Rights.</strong> A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: + </p> + + <ol type="i"> + <li> + the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; + </li> + <li> + moral rights retained by the original author(s) and/or performer(s); + </li> + <li> + publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; + </li> + <li> + rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; + </li> + <li> + rights protecting the extraction, dissemination, use and reuse of data in a Work; + </li> + <li> + database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and + </li> + <li> + other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. + </li> + </ol> + + <p> + <strong>2. Waiver.</strong> To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. + </p> + + <p> + <strong>3. Public License Fallback.</strong> Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. + </p> + + <p> + <strong>4. Limitations and Disclaimers.</strong> + </p> + + <ol type="a"> + <li> + No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. + </li> + <li> + Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. + </li> + <li> + Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. + </li> + <li> + Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. + </li> + </ol> + </section> <section id="proprietary-source-code"> + <h1> Proprietary Source Code </h1> + + <h2> + Embarcadero + </h2> + <p> <em>CodeSnip</em> is built using <em>Embarcadero Delphi XE</em>. </p> + <p> Original and third party source code make calls to the proprietary Delphi run time library, parts of which are statically linked into the <em>CodeSnip</em> executable. </p> + + <hr> + + <h2 id="ddab-exclusive"> + DelphiDabbler Exclusive Use License + </h2> + + <p> + Files covered by this license are original work, copyright © 2012-2025, <a href="https://gravatar.com/delphidabbler">Peter D Johnson</a>. + </p> + + <p> + Such files <strong>must not</strong> be used, in either original or modified form, in any distribution of a derived program ("Larger Work") without the written permission of the copyright holder. To seek to obtain such permission open an issue on the <em>CodeSnip</em> <a href="https://github.com/delphidabbler/codesnip/issues">Issue Tracker</a>. + </p> + + <aside> + <p> + This restriction does not apply to modifications of <em>CodeSnip</em> that are for personal use only and that are not distributed publicly. + </p> + </aside> + </section> <section id="required-notices"> @@ -2391,15 +1825,6 @@ <h1> The MD5 digest code used in this program is based on the RSA Data Security, Inc. MD5 Message-Digest Algorithm. </li> - <li> - This software is based, in part, on the work of Anders Melander. - </li> - <li> - Portions of this software are Copyright © 1993 - 2003, Chad Z. Hower - (Kudzu) and the Indy Pit Crew - <a - href="http://www.IndyProject.org/" - >http://www.IndyProject.org/</a>. - </li> <li> The TListViewEx component used in this program is copyright © 1999-2009 Vadim Crits. @@ -2411,24 +1836,16 @@ <h1> </div> <ul> <li> - Silk Icon Set 1.3 by Mark James: <a - href="http://www.famfamfam.com/lab/icons/silk/" - >http://www.famfamfam.com/lab/icons/silk/</a>. + Silk Icon Set 1.3 by Mark James: <del>http://www.famfamfam.com/lab/icons/silk/</del> [link broken]. </li> <li> - Silk Companion 1 by Damien Guard: <a - href="http://www.damieng.com/icons/silkcompanion" - >http://www.damieng.com/icons/silkcompanion</a>. + Silk Companion 1 by Damien Guard: <del>https://www.damieng.com/icons/silkcompanion</del> [link broken]. </li> <li> - Led Icon Set v1.0: <a - href="http://led24.de/iconset/" - >http://led24.de/iconset/</a>. + Led Icon Set v1.0: <del>http://led24.de/iconset/</del> [link broken]. </li> <li> - 16x16-free-application-icons by <a - href="http://www.aha-soft.com" - >Aha-Soft</a>. + 16x16-free-application-icons by Aha-Soft: <del>https://www.aha-soft.com</del> [link broken]. </li> </ul> </li> @@ -2444,33 +1861,25 @@ <h1> are willingly made: </p> <ul> - <li> - Thanks to Steve Schafer of TeamB (<a - href="http://www.teamb.com/" - >http://www.teamb.com/</a>) for the code on which the program's encryption - support is based. - </li> <li> <em>CodeSnip</em>'s installer was created using <em>Inno Setup</em>: see <a - href="http://www.jrsoftware.org/isinfo.php" - >http://www.jrsoftware.org/isinfo.php</a>. + href="https://www.jrsoftware.org/isinfo.php" + >https://www.jrsoftware.org/isinfo.php</a>. </li> <li> - Some program icons are based on the public domain PixelBox icon collection - at <a - href="http://www.icojam.com/blog/?p=222" - >http://www.icojam.com/blog/?p=222</a>. + Some images are based on the public domain PixelBox icon collection: + <del>http://www.icojam.com/blog/?p=222</del> [link broken]. </li> <li> - Some program icons are based on Florian Haag's Toolbar Icons set at <a - href="http://toolbaricons.sourceforge.net/" - >http://toolbaricons.sourceforge.net/</a>. + Some images are based on Florian Haag's Toolbar Icons set at <a + href="https://toolbaricons.sourceforge.net/" + >https://toolbaricons.sourceforge.net/</a>. </li> <li> Some images used in the program's Easter Egg are based on public domain images obtained from <a - href="http://www.clker.com/" + href="https://www.clker.com/" >Clker.com</a>. </li> </ul> diff --git a/Docs/MPL-2.0-Boilerplate.txt b/Docs/MPL-2.0-Boilerplate.txt index 8e70675bf..9c2d0901a 100644 --- a/Docs/MPL-2.0-Boilerplate.txt +++ b/Docs/MPL-2.0-Boilerplate.txt @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) COPYRIGHTDATE, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) COPYRIGHTDATE, Peter Johnson (gravatar.com/delphidabbler). * * DESCRIPTION. } @@ -15,12 +12,9 @@ /* * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ - * - * Copyright (C) COPYRIGHTDATE, Peter Johnson (www.delphidabbler.com). + * obtain one at https://mozilla.org/MPL/2.0/ * - * $Rev$ - * $Date$ + * Copyright (C) COPYRIGHTDATE, Peter Johnson (gravatar.com/delphidabbler). * * DESCRIPTION. */ @@ -28,36 +22,27 @@ ; This Source Code Form is subject to the terms of the Mozilla Public License, ; v. 2.0. If a copy of the MPL was not distributed with this file, You can -; obtain one at http://mozilla.org/MPL/2.0/ -; -; Copyright (C) COPYRIGHTDATE, Peter Johnson (www.delphidabbler.com). +; obtain one at https://mozilla.org/MPL/2.0/ ; -; $Rev$ -; $Date$ + * Copyright (C) COPYRIGHTDATE, Peter Johnson (gravatar.com/delphidabbler). ; ; DESCRIPTION. # This Source Code Form is subject to the terms of the Mozilla Public License, # v. 2.0. If a copy of the MPL was not distributed with this file, You can -# obtain one at http://mozilla.org/MPL/2.0/ +# obtain one at https://mozilla.org/MPL/2.0/ # -# Copyright (C) COPYRIGHTDATE, Peter Johnson (www.delphidabbler.com). -# -# $Rev$ -# $Date$ + * Copyright (C) COPYRIGHTDATE, Peter Johnson (gravatar.com/delphidabbler). # # DESCRIPTION. <!-- * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ - * - * Copyright (C) COPYRIGHTDATE, Peter Johnson (www.delphidabbler.com). + * obtain one at https://mozilla.org/MPL/2.0/ * - * $Rev$ - * $Date$ + * Copyright (C) COPYRIGHTDATE, Peter Johnson (gravatar.com/delphidabbler). * * DESCRIPTION. --> diff --git a/Docs/PreSVNHistory.txt b/Docs/PreSVNHistory.txt index 564a444d1..aa9b452c0 100644 --- a/Docs/PreSVNHistory.txt +++ b/Docs/PreSVNHistory.txt @@ -12148,8 +12148,3 @@ Version Information Editor 2.11.2 Inno Setup Compiler v5.2.3 with ISPP preprocessor. Microsoft MIDL compiler v7.0 Borland TLIBIMP v10.0 - --------------------------------------------------------------------------------- -$Rev$ -$Date$ --------------------------------------------------------------------------------- diff --git a/Docs/Privacy.txt b/Docs/Privacy.txt deleted file mode 100644 index 75fc551bc..000000000 --- a/Docs/Privacy.txt +++ /dev/null @@ -1,269 +0,0 @@ -================================================================================ - -PRIVACY STATEMENT FOR DELPHIDABBLER CODESNIP - -================================================================================ - - -Offline Privacy -================================================================================ - -CodeSnip stores some personal information in two configuration files. The -location of the files differs depending on whether you are using the standard or -portable edition. - - -Common.config --------------------------------------------------------------------------------- - -Common.config is located in the %ProgramData%\DelphiDabbler\CodeSnip.4 folder -for the standard edition or in the AppData sub folder of the install directory -for the portable edition. - -The data that is stored in Common.config includes: - - * A unique 32 digit key based on attributes of your computer. - * A 32 digit registration key (registered programs only). - * The registered user name or nickname (registered programs only). - -The data that is used to create the hexidecimal keys cannot be recovered from -those keys. - - -User.config --------------------------------------------------------------------------------- - -User.config is found in the current user's %AppData%\DelphiDabbler\CodeSnip.4 -folder for the standard edition or in the AppData sub folder of the install -directory for the portable edition. - -The file stores several user-specific application data settings such as your -preferences. The only personal information is as follows: - - * Any user name or email address you provided if you ever entered the - information in a dialogue box. This is used simply to automatically enter - the data in dialogue boxes where needed, to save you re-typing it. See - below for details of activities that require either or both a name or - email address to be submitted. - - * Your user name and password if you configured CodeSnip to access the - internet using a proxy server. The information is used only to access the - proxy. Passwords are encrypted. - - -Spyware -================================================================================ - -No spyware or other software that threatens your privacy has been deliberately -or knowingly included with CodeSnip or its installer. If the program is found to -contain spyware it will have been added by third parties without permission. -Please inform the author of any such occurrence or any suspicious behaviour via -http://delphidabbler.com/contact. - -CodeSnip never tries to "phone home" of its own accord. It only goes online in -response to user requests. Should you notice any unauthorised activity please -inform the author since it is likely that your copy of the program has been -hacked by some third party. - - -CodeSnip Online Activity -================================================================================ - -CodeSnip may go online for several reasons. They are: - - 1. Updating the database. - 2. Registering the program. - 3. Submitting code for inclusion in the main database. - 4. Displaying the latest news in the "CodeSnip News" dialogue box. - 5. Checking for program updates. - 6. Importing snippets from the SWAG database. - 7. Accessing various websites via hyperlinks and menu options. - 8. Accessing the internet using a proxy server. - -The first six actions are performed directly by CodeSnip, which communicates -with web services located on delphidabbler.com using HTTP on port 80. The -seventh action simply displays web pages in a browser. The eighth passes user -information to the proxy server. - -Personal data will neither be published on the DelphiDabbler website nor -knowingly passed to third parties. Furthermore your data will only be used by -the author for the purposes described below. - - -Updating the Database --------------------------------------------------------------------------------- - -This activity is started by displaying the "Update From Web" dialogue box and -clicking its "Update from Web" button. - -CodeSnip may also check automatically, behind the scenes, to see if database -updates are available. This usually happens just after the program has started. -You can disable this automatic checking by using the "Updates" tab of the -"Preferences" dialogue box which is displayed via the "Tools | Preferences" menu -option. - -When CodeSnip contacts the update web service it sends the following -information: - - * The unique 32 digit program key (see Common.config above for details). - * Program version number. - * Operating system description. - * Internet Explorer version. - -This information is recorded in two places: - - 1. Each transaction is recorded in a log file on the web server. This log - is used for tracking any database errors. - - 2. A database of the program keys is updated with the following data: - - * the number of times that program has updated the database; - * the first and latest version of CodeSnip having that program key; - * the date of the first and latest updates; - * the current operating system being used; - * the current Internet Explorer version being used. - - This database is used to provide usage information and to track the - operating systems and browser control versions currently in use. This - information informs development of CodeSnip. - -This information may also be used to update the registered program database. - -None of these records store any information that can be tracked back to an -individual. - - -Registering the Program --------------------------------------------------------------------------------- - -This activity is started when the user chooses to register CodeSnip. A wizard -appears that guides the user through the registration process. The wizard -displays the data that is to be sent to the registration web service. The data -is only sent if the user clicks the wizard's "Submit" button. The data is: - - * The program's id, name and version information. - * The unique 32 digit program key (see Common.config above for details). - * The user name or nickname. - * A description of the operating system and version of Internet Explorer - being used. - -The registration data is recorded in a database on the DelphiDabbler web server. -The data is only used to keep track of the number of people using the different -versions of the program and what operating systems are being used. The -registration web service returns a registration code to the program. CodeSnip -records this code, with the user name, in Common.config. - -Registration information may be updated when the user updates the database (see -"Updating the Database" above). - - -Submitting code to the main database --------------------------------------------------------------------------------- - -This activity is started from the "Snippets | Submit Snippets" menu option and -data is collected using the resulting wizard. Clicking the "Submit" button in -the wizard sends the following information to the DelphiDabbler code submission -web service: - - * The CodeSnip program version number. - * Your name, email address any comments entered in the wizard. - * All the snippets you chose to send. - -You can preview the data to be sent (in XML format) from the wizard. The XML -will be stored in a file on the DelphiDabbler server until processed. The data -is also emailed to the database maintainer (currently the author of CodeSnip). - -The snippets may be edited and published on the Code Snippets online database -and distributed to CodeSnip users. Your name may be used to credit the snippets. -Your email address will not be published and is used only to contact you with -any queries about the submitted code or its licensing. - - -Displaying the latest news --------------------------------------------------------------------------------- - -This action is performed by selecting the "Help | CodeSnip News" menu option or -by clicking the "Latest News" button in the "Update from Web" dialogue box. - -CodeSnip simply requests an XML document containing an RSS 2.0 news feed from -the DelphiDabbler website. No personal information is included in the request. - - -Checking for program updates --------------------------------------------------------------------------------- - -This action can be started manually by clicking the "Tools | Check For Program -Updates" menu option. The resulting dialogue box connects to the DelphiDabbler -program update web service. - -CodeSnip may also check for updates automatically, behind the scenes. The same -DelphiDabbler program update web is used in this case. The process usually -happens just after the program has started. You can disable this automatic -checking by using the "Updates" tab of the "Preferences" dialogue box which is -displayed via the "Tools | Preferences" menu option. - -Whichever way the the web service is used the same information is sent, which -is: - - * An API key used permit access to the web service. - * The program's version information. - * The unique 32 digit program key (see Common.config above for details). - * A description of the operating system and version of Internet Explorer - being used. - * An indication of whether the service is being called automatically or - manually. - -This information is recorded in a log file on the DelphiDabbler web server. The -log file is used to gather information about the operating systems and program -versions currently in use and to monitor any web service errors. - -Regardless of the method used to contact the web service, if an update is -available you are given the option to download it. To do this CodeSnip uses your -default web browser to download a zip file containing the update from the -SourceForge website. SourceForge may set cookies on your computer that are -outside CodeSnip's control. - - -Importing snippets from the SWAG database ------------------------------------------ - -Snippets are imported from the SWAG database (http://swag.delphidabbler.com) -using the SWAG Import Wizard that is accessed from the "Snippets | Import -Snippets From SWAG" menu item. - -The wizard gets the required snippets from the REST API at -swag.delphidabbler.com. This API is documented at -http://swag.delphidabbler.com/docs/api/. No personal data is sent. - - -Accessing Websites via Links and Menu Options --------------------------------------------------------------------------------- - -CodeSnip's "Help" menu contains several items that cause various web pages from -DelphiDabbler.com to be displayed in your default web browser. - -In addition, various links in the program's main display, some dialogue boxes -and the help file access remote websites which are displayed in your default -browser. You should refer to the relevant website's privacy policies to learn -how they use any data you supply when visiting their pages. - -DelphiDabbler has no control over the content of linked third party websites and -you follow such links at your own risk. - - -Accessing the internet using a proxy server --------------------------------------------------------------------------------- - -CodeSnip can be configured to use a proxy server to access the internet. In this -case the program sends the appropriate user name and password to the proxy -server if required. - -DelphiDabbler has no control over the proxy server and you should satisfy -yourself that the proxy you use is trustworthy. You use this feature at your own -risk. - - -================================================================================ -$Rev$ -$Date$ -================================================================================ diff --git a/Docs/ReadMe-portable.txt b/Docs/ReadMe-portable.txt new file mode 100644 index 000000000..c22134c23 --- /dev/null +++ b/Docs/ReadMe-portable.txt @@ -0,0 +1,260 @@ +================================================================================ + +DELPHIDABBLER CODESNIP v4 PORTABLE EDITION README + +================================================================================ + + +What is CodeSnip? +================================================================================ + +DelphiDabbler CodeSnip 4 is a code snippets repository targetted at the Pascal / +Delphi programming languages. It can download and display code snippets from the +online DelphiDabbler Code Snippets database as well as maintain a database of +user-defined snippets. + +It displays details of each snippet in the database and can test-compile them +with each installed Win32 version of Delphi from Delphi 2 to Delphi 12.x and +Free Pascal. + +Compilable Pascal units can be created that contain selected snippets. + + +CodeSnip Editions +================================================================================ + +This document relates to the PORTABLE edition of CodeSnip. This edition can be +run from any writeable removable storage medium (e.g. a USB memory stick) or +from any folder on the computer's hard disk. It makes no changes to the host +computer. + +There is also a standard edition of the program. This edition is installed on +the user's computer using an installer. It records its presence in the registry +and stores data in the system's application and user data directories. You can +get the standard edition from the same place you downloaded the this edition. + +You can run both the standard and portable editions together on the same +computer and even run them at the same time. However, each edition maintains its +own settings and keeps its own copies of the snippets databases. To share user +defined snippets you must export them from one edition and import into the +other. CodeSnip provides no mechanism for keeping them synchronised. + + +Installation +================================================================================ + +CodeSnip requires Windows 2000 or later. It also requires MS Internet Explorer 6 +or later, although IE 8, 9 or 10 are strongly recommended. Note that recent +releases have only been tested on Windows 11. + +The portable edition of CodeSnip 4 is distributed in a zip file that contains +the program executable, the help file and various documentation files. + +Install the program using the following steps: + +1) Mount any storage medium on which you want to install CodeSnip. + +2) Create a folder on the storage medium or on your computer's internal disk in + which to copy the required files. + +3) Copy the files CodeSnip-p.exe (the executable program) and CodeSnip.chm + (the help file) into the folder you created. + + CodeSnip does not need the other files included in the zip file in order to + run, but you may find them useful. Copy them if you wish. + +Run the program by double clicking it. When it first runs it will create two +sub-directories within the folder where you installed the program. These will +be named AppData and UserData. Do not remove these directories or alter any of +the contents because CodeSnip uses them to store configuration data along with +your code snippets. + +No files are written outside the folder where you copied the files and the +registry is not modified. + +** WARNING: When updating an existing portable installation with a new version +of CodeSnip it is important that you do not change or delete the AppData and +UserData folders. If you do this you risk loosing your settings and/or database. + + +Uninstallation +================================================================================ + +Simply delete the folder where you installed the portable edition of CodeSnip +along with all its contents. + +Be aware that any snippets you have created will be lost. If you want to keep +them for use in another CodeSnip installation, either export them or back up the +user database before deleting the folder. See the help file for details of how +to do this. + + +Downloading & Updating the Code Snippets Database +================================================================================ + +The online DelphiDabbler Code Snippets database is not installed with the +program. + +CodeSnip's start-up screen shows details of any installed databases. If there is +no copy of the online database then a link is displayed that enables the +database to be installed. This link opens the "Install or Update DelphiDabbler +Snippets Database" wizard dialogue box. The dialogue box explains how to +download and install the database. + +You can download or update the database later by opening the same dialogue box +using the "Database | Install or Update DelphiDabbler Snippets Database" menu +option. + + +Configuring CodeSnip to Work With Your Compilers +================================================================================ + +A feature of CodeSnip is its ability to test compile snippets with any installed +Windows 32 version of Delphi (from Delphi 2 to Delphi.x) and FreePascal, +providing some simple rules are followed. + +When CodeSnip is first installed it knows nothing about the available compilers +and so test compilations cannot be performed. If any supported Delphi compiler +is detected when the program is first run you will be given the option of +registering it. This does not work for Free Pascal. + +You can also tell CodeSnip about the available compilers by using the "Tools | +Configure Compilers" menu option. The resulting dialogue can automatically +detect all installed versions of supported Delphi compilers at the click of a +button. Free Pascal, where installed, must be set up manually. The Welcome page +displays a list of compilers it has been configured to work with. + +Compilers that do not use English as their output language will need further +configuration. See the help file for information (look up "configure compilers +dialogue" in the help file index). + +Each user can configure compilers differently. + +Delphi XE2 and later may need to be configured to search for required units in +the correct namespaces. This is explained in the Add/Edit Snippet Dialogue Box +help topic and in the FAQ at +https://github.com/delphidabbler/codesnip-faq/blob/master/UsingCodeSnip.md#faq-7 + +Any type of snippet other than "freeform" can be test compiled. + + +Updating the Program +================================================================================ + +Updates are published on GitHub. See +https://github.com/delphidabbler/codesnip/releases + +News of new updates is published on the DelphiDabbler Blog: +https://delphidabbler.blogspot.com/. + + +Known Installation and Upgrading Issues +================================================================================ + ++ If you have updated to CodeSnip v4.2.0 or later from any earlier v4 release, + and then run the earlier version of the program again, its saved main window + state, size, position and layout will have been lost and the program will + display in its default size. + ++ If you have updated to CodeSnip v4.3.0 or later from v4.2.x or earlier any -NS + command line options you have specified on the "Switches" (aka "Command Line") + tab of the Configure Compilers dialogue box for Delphi XE2 or later will be + removed and equivalent entries will have been made on the "Namespaces" tab. + ++ CodeSnip v4.16.0 and later cannot be registered. Any previous registration + information may be lost. + + +License & Disclaimer +================================================================================ + +CodeSnip is made available under the terms of the Mozilla Public License v2.0. +The license is explained in full in the file License.html that is installed with +CodeSnip. A summary of the license can be viewed from the "Help | License" menu +option. + +CodeSnip is supplied on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either +express or implied. See License.html for details. + +The source code of any snippet managed by CodeSnip, whether from the +DelphiDabbler Code Snippets Database or the user database, is used WITHOUT +WARRANTY OF ANY KIND, either express or implied. The code is used entirely at +the user's own risk. + +The snippets from the DelphiDabbler Code Snippets Database are open source. See +the "About The Database" tab of the About dialogue box for details of the +applicable license. (You can display the About box from the "Help" menu.) + +The user is responsible to ensure that any code snippets managed by CodeSnip are +used in accordance with any applicable license. + + +Source Code +================================================================================ + +CodeSnip's source code is freely available. For details of how to obtain the +source see the FAQ at +https://github.com/delphidabbler/codesnip-faq/blob/master/SourceCode.md#faq-1 + +The portable edition of CodeSnip shares the same source code base with the +standard edition. + +The original source code of v4 is released under the Mozilla Public license +v2.0 (see https://www.mozilla.org/MPL/) and other open source licenses. See the +file "License.html" in the "Docs" directory of the repository for full licensing +information. + + +Bugs & Feature Requests +================================================================================ + +Please do report any bugs you find. Suggestions for new features are also +welcomed. + +Both bug reports and feature requests are made using the GitHub issue tracker +(GitHub account required). For details about using the issue tracker see +https://github.com/delphidabbler/codesnip/blob/master/CONTRIBUTING.md#issues. + + +FAQs +================================================================================ + +There are Frequently Asked Questions pages for CodeSnip on the web, at +https://github.com/delphidabbler/codesnip-faq/blob/master/README.md + + +Privacy +================================================================================ + +From v4.16.0 CodeSnip neither stores nor transmits any personally identifiable +data. + +Do note though that CodeSnip can display web pages via your default web browser, +but only in response to user input. No guarantee is made about any personal data +collected by such web pages. + + +Thanks +================================================================================ + +Thanks to: + ++ David Mustard and Bill Miller for providing information that enabled me to add + Delphi 2007 and Delphi 2009 support, respectively, to the program. + ++ geoffsmith82 and an anonymous contributor for information about getting + CodeSnip to work with Delphi XE2. + ++ The authors of the third party source code and images used by the program. See + the program's about box or License.html for details. + ++ SirRufo for helping to fix a long standing bug where CodeSnip would crash on + resuming from hibernation. + ++ Various contributors to the DelphiDabbler Code Snippets database. Names of + contributors are listed in the program's About Box (use the "Help | About" + menu option then select the "About the Database" tab). The list will be empty + if the Code Snippets Database has not been installed. + + +================================================================================ diff --git a/Docs/ReadMe-standard.txt b/Docs/ReadMe-standard.txt new file mode 100644 index 000000000..97ac0577b --- /dev/null +++ b/Docs/ReadMe-standard.txt @@ -0,0 +1,308 @@ +================================================================================ + +DELPHIDABBLER CODESNIP v4 STANDARD EDITION README + +================================================================================ + + +What is CodeSnip? +================================================================================ + +DelphiDabbler CodeSnip 4 is a code snippets repository targetted at the Pascal / +Delphi programming languages. It can download and display code snippets from the +online DelphiDabbler Code Snippets database as well as maintain a database of +user-defined snippets. + +It displays details of each snippet in the database and can test-compile them +with each installed Win32 version of Delphi from Delphi 2 to Delphi 12.x and +Free Pascal. + +Compilable Pascal units can be created that contain selected snippets. + + +CodeSnip Editions +================================================================================ + +This document relates to the STANDARD edition of CodeSnip. This edition is +installed on the user's computer using a standard Windows installer and which +records its presence in the registry and stores data in the system's application +and user data directories. + +There is also a portable edition of the program. This edition can be run from +any writeable removable storage medium (e.g. a USB memory stick) or from any +folder on the computer's hard disk. It makes no changes to the host computer. +This edition has no installer and is simply copied to the required location. + +You can run both the portable and standard editions together on the same +computer and even run them at the same time. However, each edition maintains its +own settings and keeps its own copies of the snippets databases. To share user +defined snippets you must export them from one edition and import into the +other. CodeSnip provides no mechanism for keeping them synchronised. + + +Installation +================================================================================ + +CodeSnip requires Windows 2000 or later. It also requires MS Internet Explorer 6 +or later, although IE 8, 9 or 10 are strongly recommended. Note that recent +releases have only been tested on Windows 11. + +You will need administrator privileges to run the setup program. If you are +using a non-admin user account on Windows 2000 or XP you should run setup as +administrator. By default Windows Vista to Windows 11 will require admin +privileges and setup will attempt to elevate the process if required. If UAC +prompts are disabled you must run setup as administrator. + +CodeSnip v4 will install alongside any v3 or earlier release that may already be +installed. If you want to replace the earlier version simply uninstall it in the +usual way. Uninstalling v3 or earlier after installing v4 will have no adverse +affect on v4. + +CodeSnip's installation program is named codesnip-setup-4.x.x.exe, where x.x +is the program's minor version number. The install program is distributed in a +zip file. + +Close any running instance of CodeSnip, run the install program then follow the +on-screen instructions. + +The installer makes the following changes to your system: + ++ The main program's executable file and documentation are installed into the + chosen install folder (%ProgramFiles%\DelphiDabbler\CodeSnip-4 by default). + ++ Files required by the uninstaller are stored in the main installation's Uninst + sub-folder. + ++ The program's uninstall information is registered with the "Installed App" + (a.k.a. "Apps and Features", a.k.a. "Programs and Features", a.k.a. "Add / + Remove Programs") control panel app. + ++ A program group may be created in the start menu (optional). + ++ A %ProgramData%\DelphiDabbler\CodeSnip.4 folder is created. A configuration + file is stored in the folder. If the online database is installed, it will be + copied to the "Database" sub-folder. + ++ An %AppData%\DelphiDabbler\CodeSnip.4 folder is also created. This is used to + hold a file that stores per-user configuration data and, sometimes, another + file that records any favourite snippets. A "UserDatabase" sub-folder is used + to store any user defined snippets. These folders are created when CodeSnip is + first run. + + Users can move the user defined snippets data from the "UserDatabase" + sub-folder to another location, in which case "UserDatabase" will not be + present. You might want to do this to place the snippets data in a folder that + will be backed up, e.g. a Dropbox or GoogleDrive sub-directory. + +If you are updating to CodeSnip 4 from version 3 or earlier, CodeSnip will give +you the option of bringing forward your old settings and / or user defined +database. This happens the first time v4 is run for each user. + + +Uninstallation +================================================================================ + +CodeSnip can be uninstalled using your version of Windows' application +uninstaller, run from Control Panel. Alternatively you can choose "Uninstall +DelphiDabbler CodeSnip" from the program's start menu group. + +Administrator privileges will be required to uninstall CodeSnip. + +The uninstall program will delete any local copy of the online Code Snippets +database but will leave any user defined database, configuration data and +favourites intact. To remove user defined databases and configuration data, +delete the %AppData%\DelphiDabbler\CodeSnip.4 directory and all its contents for +each user who ran CodeSnip. If any user has moved the user database directory +those directories also need to be deleted. + + +Downloading & Updating the Code Snippets Database +================================================================================ + +The online DelphiDabbler Code Snippets database is not installed with the +program. + +CodeSnip's start-up screen shows details of any installed databases. If there is +no copy of the online database then a link is displayed that enables the +database to be installed. This link opens the "Install or Update DelphiDabbler +Snippets Database" wizard dialogue box. The dialogue box explains how to +download and install the database. + +You can download or update the database later by opening the same dialogue box +using the "Database | Install or Update DelphiDabbler Snippets Database" menu +option. + +During installation the setup program will detect if an older database version +is present and will give the option to carry it forward. When setup completes it +checks for the presence of the database and puts up a message if it is not +present. + +Database updates will apply to all users of the computer the next time they +start CodeSnip. + + +Configuring CodeSnip to Work With Your Compilers +================================================================================ + +A feature of CodeSnip is its ability to test compile snippets with any installed +Windows 32 version of Delphi (from Delphi 2 to Delphi.x) and FreePascal, +providing some simple rules are followed. + +When CodeSnip is first installed it knows nothing about the available compilers +and so test compilations cannot be performed. If any supported Delphi compiler +is detected when the program is first run you will be given the option of +registering it. This does not work for Free Pascal. + +You can also tell CodeSnip about the available compilers by using the "Tools | +Configure Compilers" menu option. The resulting dialogue can automatically +detect all installed versions of supported Delphi compilers at the click of a +button. Free Pascal, where installed, must be set up manually. The Welcome page +displays a list of compilers it has been configured to work with. + +Compilers that do not use English as their output language will need further +configuration. See the help file for information (look up "configure compilers +dialogue" in the help file index). + +Each user can configure compilers differently. + +Delphi XE2 and later may need to be configured to search for required units in +the correct namespaces. This is explained in the Add/Edit Snippet Dialogue Box +help topic and in the FAQ at +https://github.com/delphidabbler/codesnip-faq/blob/master/UsingCodeSnip.md#faq-7 + +Any type of snippet other than "freeform" can be test compiled. + + +Updating the Program +================================================================================ + +Updates are published on GitHub. See +https://github.com/delphidabbler/codesnip/releases + +News of new updates is published on the DelphiDabbler Blog: +https://delphidabbler.blogspot.com/. + + +Known Installation and Upgrading Issues +================================================================================ + ++ Any syntax highlighter customisation you have made will be lost if you are + updating from any v2 or earlier. + + You will need to redo any customisation using the "Syntax Highlighter" page of + the Preferences dialogue box displayed from the "Tools | Preferences" menu + option. + ++ Your source code formatting preferences will have been lost if you are + updating from v1.7.4 or earlier. + + You will need to reconfigure them using the "Code Formatting" page of the + Preferences dialogue box displayed from the "Tools | Preferences" menu option. + ++ If you have updated to CodeSnip v4.2.0 or later from any earlier v4 release, + and then run the earlier version of the program again, its saved main window + state, size, position and layout will have been lost and the program will + display in its default size. + ++ If you have updated to CodeSnip v4.3.0 or later from v4.2.x or earlier any -NS + command line options you have specified on the "Switches" (aka "Command Line") + tab of the Configure Compilers dialogue box for Delphi XE2 or later will be + removed and equivalent entries will have been made on the "Namespaces" tab. + ++ CodeSnip v4.16.0 and later cannot be registered. Any previous registration + information may be lost. + + +License & Disclaimer +================================================================================ + +CodeSnip is made available under the terms of the Mozilla Public License v2.0. +The license is explained in full in the file License.html that is installed with +CodeSnip. A summary of the license can be viewed from the "Help | License" menu +option. + +CodeSnip is supplied on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either +express or implied. See License.html for details. + +The source code of any snippet managed by CodeSnip, whether from the +DelphiDabbler Code Snippets Database or the user database, is used WITHOUT +WARRANTY OF ANY KIND, either express or implied. The code is used entirely at +the user's own risk. + +The snippets from the DelphiDabbler Code Snippets Database are open source. See +the "About The Database" tab of the About dialogue box for details of the +applicable license. (You can display the About box from the "Help" menu.) + +The user is responsible to ensure that any code snippets managed by CodeSnip are +used in accordance with any applicable license. + + +Source Code +================================================================================ + +CodeSnip's source code is freely available. For details of how to obtain the +source see the FAQ at +https://github.com/delphidabbler/codesnip-faq/blob/master/SourceCode.md#faq-1 + +The standard edition of CodeSnip shares the same source code base with the +portable edition. + +The original source code of v4 is released under the Mozilla Public license +v2.0 (see https://www.mozilla.org/MPL/) and other open source licenses. See the +file "License.html" in the "Docs" directory of the repository for full licensing +information. + + +Bugs & Feature Requests +================================================================================ + +Please do report any bugs you find. Suggestions for new features are also +welcomed. + +Both bug reports and feature requests are made using the GitHub issue tracker +(GitHub account required). For details about using the issue tracker see +https://github.com/delphidabbler/codesnip/blob/master/CONTRIBUTING.md#issues. + + +FAQs +================================================================================ + +There are Frequently Asked Questions pages for CodeSnip on the web, at +https://github.com/delphidabbler/codesnip-faq/blob/master/README.md + + +Privacy +================================================================================ + +From v4.16.0 CodeSnip neither stores nor transmits any personally identifiable +data. + +Do note though that CodeSnip can display web pages via your default web browser, +but only in response to user input. No guarantee is made about any personal data +collected by such web pages. + + +Thanks +================================================================================ + +Thanks to: + ++ David Mustard and Bill Miller for providing information that enabled me to add + Delphi 2007 and Delphi 2009 support, respectively, to the program. + ++ geoffsmith82 and an anonymous contributor for information about getting + CodeSnip to work with Delphi XE2. + ++ SirRufo for helping to fix a long standing bug where CodeSnip would crash on + resuming from hibernation. + ++ The authors of the third party source code and images used by the program. See + the program's about box or License.html for details. + ++ Various contributors to the DelphiDabbler Code Snippets database. Names of + contributors are listed in the program's About Box (use the "Help | About" + menu option then select the "About the Database" tab). The list will be empty + if the Code Snippets Database has not been installed. + + +================================================================================ diff --git a/Docs/ReadMe.txt b/Docs/ReadMe.txt deleted file mode 100644 index 5d2e7948c..000000000 --- a/Docs/ReadMe.txt +++ /dev/null @@ -1,449 +0,0 @@ -================================================================================ - -DELPHIDABBLER CODESNIP v4 README - -================================================================================ - - -What is CodeSnip? -================================================================================ - -DelphiDabbler CodeSnip 4 is a code snippets repository targetted at the Pascal / -Delphi programming languages. It can download and display code snippets from the -online DelphiDabbler Code Snippets database as well as maintain a database of -user-defined snippets. - -It displays details of each snippet in the database and can test-compile them -with each installed Win32 version of Delphi from Delphi 2 to Delphi 10 Seattle -along with Free Pascal. - -Compilable Pascal units can be created that contain selected snippets. - -Features new to CodeSnip 4 are listed in the "What's New In CodeSnip 4" topic -in the program's help file. - - -CodeSnip Editions -================================================================================ - -There are two different editions of CodeSnip 4 available: - -+ The standard edition, which is installed on the user's computer using an - installer and which records its presence in the registry and stores data in - the system's application and user data directories. - -+ The portable edition that can be run from any writeable removable storage - medium (e.g. a USB memory stick) that makes no changes to the host computer. - This edition has no installer and is simply copied onto the required medium. - -You can run both the standard and portable editions together on the same -computer and even run them at the same time. However, each edition maintains its -own settings and keeps its own copies of the snippets databases. To share user -defined snippets you must export them from one edition and import into the -other. CodeSnip provides no mechanism for keeping them synchronised. - - -Installation -================================================================================ - -CodeSnip requires Windows 2000 or later. It also requires MS Internet Explorer -V6 or later, but IE 8, 9 or 10 are strongly recommended. - -Installing the Standard Edition -------------------------------- - -You will need administrator privileges to run the setup program for the standard -edition. If you are using a non-admin user account on Windows 2000 or XP you -should run setup as administrator. By default Windows Vista and Windows 7 will -require an admin password if running as a standard user and setup will attempt -to elevate the process. If UAC prompts are disabled you must run setup as -administrator. - -CodeSnip v4 will install alongside any v3 or earlier release that may already be -installed. If you want to replace the earlier version simply uninstall it in the -usual way. Uninstalling v3 or earlier after installing v4 will have no adverse -affect on v4. - -CodeSnip's installation program is named codesnip-setup-4.x.x.exe, where x.x -is the program's minor version number. The install program may be distributed in -a zip file. - -Close any running instance of CodeSnip, run the install program then follow the -on-screen instructions. - -The installer makes the following changes to your system: - -+ The main program's executable file and documentation are installed into the - chosen install folder (%ProgramFiles%\DelphiDabbler\CodeSnip-4 by default). - -+ Files required by the uninstaller are stored in the main installation's Uninst - sub-folder. - -+ The program's uninstall information is registered with the "Programs and - Features" (a.k.a. "Add / Remove Programs") control panel applet. - -+ A program group may be created in the start menu (optional). - -+ A %ProgramData%\DelphiDabbler\CodeSnip.4 folder is created. A configuration - file is stored in the folder. If the online database is downloaded, it will be - stored in a "Database" sub-folder, unless you have changed the database - location (see below). - -+ An %AppData%\DelphiDabbler\CodeSnip.4 folder is also created. This is used to - hold a file that stores per-user configuration data and, sometimes, another - file that records any favourite snippets. A "UserDatabase" sub-folder is used - to store any user defined snippets. These folders are created when CodeSnip is - first run. - - Users can move the user defined snippets data from the "UserDatabase" - sub-folder to another location, in which case "UserDatabase" will not be - present. You might want to do this to place the snippets data in a folder that - will be backed up, e.g. a Dropbox or GoogleDrive sub-directory. - -If you are updating to CodeSnip 4 from version 3 or earlier, CodeSnip will give -you the option of bringing forward your old settings and / or user defined -database. This happens the first time v4 is run for each user. - -Installing the Portable Edition -------------------------------- - -The portable edition of CodeSnip 4 is distributed in a zip file that contains -the program executable, the help file and various documentation files. - -Install the program using the following steps: - -1) Mount the storage medium on which you want to install CodeSnip. - -2) Create a folder on the storage medium in which to "install" CodeSnip. - -3) Copy the files CodeSnip-p.exe (the executable program) and CodeSnip.chm - (the help file) into the folder you created. - - CodeSnip does not need the other files included in the zip file in order to - run, but you may find them useful. Copy them if you wish. - -Run the program by double clicking it. When it first runs it will created two -sub-directories within the folder where you installed the program. These will -be named AppData and UserData. Do not remove these directories or alter any of -the contents. CodeSnip uses them to store configuration data along with your -code snippets. - -No files are written to the host computer and the registry is not modified. - - -Uninstallation -================================================================================ - -Uninstalling the Standard Edition ---------------------------------- - -CodeSnip can be uninstalled via "Programs and Features" (a.k.a. "Add/Remove -Programs") from the Windows Control Panel or by choosing "Uninstall -DelphiDabbler CodeSnip" from the program's start menu group. - -Administrator privileges will be required to uninstall CodeSnip. Windows Vista -and Windows 7 with UAC prompts enabled will prompt for an admin password if -necessary. - -The uninstall program will delete any local copy of the online Code Snippets -database but will leave any user defined database, configuration data and -favourites intact. To remove user defined databases and configuration data, -delete the %AppData%\DelphiDabbler\CodeSnip.4 directory and all its contents for -each user who ran CodeSnip. If any user has moved the user database directory -those directories also need to be deleted. - -Uninstalling the Portable Edition ---------------------------------- - -Simply delete the folder where you installed CodeSnip and all its contents. - -Be aware that any snippets you have created will be lost. If you want to keep -them for use in another CodeSnip installation either export them or back up the -user database before deleting the folder. See the help file for details of how -to do this. - - -Downloading & Updating the Code Snippets Database -================================================================================ - -The online DelphiDabbler Code Snippets database is not installed with the -program. - -CodeSnip's start-up screen shows details of any installed databases. If there is -no copy of the online database a link is displayed that enables the database to -be installed. - -By default CodeSnip checks online periodically to find out whether the database -has been updated. The checking process runs behind the scenes unless an update -is available when CodeSnip will display a notification window at the bottom -right of the main window. From the window you can display the "Update From Web" -dialogue which you can use to download the updated database. - -You can also check for updates manually by using the program's "Database | -Update From Web" menu option. This displays the same "Update From Web" dialogue -box mentioned above. You can get to know about what updates are available by -subscribing to the CodeSnip RSS feed (see below). - -If you don't want CodeSnip to check for database updates automatically, or if -you want to change the frequency with with it checks, you can do that from the -"Updates" tab of the "Preferences" dialogue box. This dialogue box is accessed -via the "Tools | Preferences" menu option. - -NOTE: You may need to configure your firewall to permit CodeSnip to access the -internet otherwise the automatic updating may not work. - -Standard Edition Only ---------------------- - -When installing the standard edition, the setup program will detect if an older -database installation is present and will give the option to carry it forward. -When setup completes it checks for the presence of the database and puts up a -message if it is not present. - -Database updates will apply to all users of the computer the next time they -start CodeSnip. - - -Configuring CodeSnip to Work With Your Compilers -================================================================================ - -A feature of CodeSnip is its ability to test compile snippets with any installed -Windows 32 version of Delphi and FreePascal. User defined snippets can also be -test compiled providing some simple rules are followed. - -When CodeSnip is first installed it knows nothing about the available compilers -and so test compilations cannot be performed. You must tell CodeSnip about the -available compilers by using the "Tools | Configure Compilers" menu option. The -resulting dialogue can automatically detect all installed versions of supported -Delphi compilers at the click of a button. Free Pascal, where installed, must be -set up manually. The Welcome page displays a list of compilers it has been -configured to work with. - -Compilers that do not use English as their output language will need further -configuration. See the help file for information (look up "configure compilers -dialogue" in the help file index). - -Each user can configure compilers differently. - -Delphi XE2 to XE8 and Delphi 10 Seattle may need to be configured to search for -required units in the correct namespaces. This is explained in the Add/Edit -Snippet Dialogue Box help topic and in the FAQ at -http://delphidabbler.com/url/codesnip-and-xe2. - - -Registration -================================================================================ - -Registration of CodeSnip is not required, but the author would be grateful if -you do register the program, just so he knows it is being used. - -To register click the "Tools | Register CodeSnip" menu item and follow the -wizard. If this menu option is not displayed then the program has already been -registered. - -On systems with multiple users using the standard edition, only one user needs -to register. Once this is done the program will show as registered regardless of -which user is logged on. - -When using the portable edition, uninstalling the program looses registration -data. If the portable edition is installed on more than one device then each -copy must be registered separately. - - -Updating the Program -================================================================================ - -CodeSnip automatically checks online for available program updates. If it finds -that an update is available it displays a notification window at the bottom -right of the main window. That window has a button you can click that opens your -default browser at a web page that downloads the new version of the program. - -Once the updated version is downloaded you must close CodeSnip install the -program in the usual way. - -You can change the frequency with which CodeSnip checks for updates, or turn off -the feature, from the "Updates" tab of the "Preferences" dialogue box. You can -display this dialogue box from the "Tools | Preferences" menu option. - -You can also check for program updates manually at any time from the "Tools | -Check For Program Updates" menu option. Once again you will be directed to a web -page from where the updated program can be downloaded. - -Neither of the above methods will detect beta and preview releases of the -program. To find out about those you should subscribe to the CodeSnip news feed -(see below). Alternatively you can view the latest news from the feed from the -"Help | CodeSnip News" menu option. - -NOTE: You may need to configure your firewall to permit CodeSnip to access the -internet otherwise the automatic updating may not work. - - -Known Installation and Upgrading Issues -================================================================================ - -+ Any syntax highlighter customisation you have made will be lost if you are - updating from any v2 or earlier release. - - You will need to redo any customisation using the Syntax Highlighter tab of - the Preferences dialogue box displayed from the "Tools | Preferences" menu - option. - -+ If you are updating from v3.6.0 or earlier and have set up a password - protected proxy server for internet access your password will have been lost. - This is because the format for storing passwords changed at v3.6.1. - - To re-enter your proxy password use the Proxy Server Configuration dialogue - box displayed from the "Tools | Proxy Server" menu option. - -+ Your source code formatting preferences will have been lost if you are - updating from v1.7.4 or earlier. - - You will need to reconfigure them using the Code Formatting tab of the - Preferences dialogue box displayed from the "Tools | Preferences" menu option. - -+ If you are updating from v1.8.11 or earlier and have registered CodeSnip your - registration information will have been lost. - - You can check this by displaying the About dialogue box. If it displays a - Register CodeSnip button the program is not registered. You can (re)register - if you wish by clicking the button. - -+ If you have updated to CodeSnip v4.2.0 or later from any earlier v4 release, - and then run the earlier version of the program again, its saved main window - state, size, position and layout will have been lost and the program will - display in its default size. - -+ If you have updated to CodeSnip v4.3.0 or later from v4.2.x or earlier any -NS - command line options you have specified on the "Switches" (aka "Command Line") - tab of the Configure Compilers dialogue box for Delphi XE2 or later will be - removed and equivalent entries will have been made on the "Namespaces" tab. - - -RSS News Feed -================================================================================ - -You can get notified of all updates to the CodeSnip program and to the database -by subscribing to the Code Snippets RSS Feed at -http://delphidabbler.com/feeds/site-news-feed?id=codesnip. - -The latest news from this feed can also be displayed from the program's "Help | -CodeSnip News" menu option. - - -License & Disclaimer -================================================================================ - -CodeSnip is made available under the terms of the Mozilla Public License v2.0. -The license is explained in full in the file License.html that is installed with -CodeSnip. A summary of the license can be viewed from the "Help | License" menu -option. - -CodeSnip is supplied on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either -express or implied. See License.html for details. - -The source code contained in the database, or in any units or snippets generated -by this program, is made available on an "AS IS" basis, WITHOUT WARRANTY OF ANY -KIND, either express or implied. The code is used entirely at you own risk. - - -Source Code -================================================================================ - -CodeSnip's source code is freely available. For details of how to obtain the -source see the FAQ at -http://wiki.delphidabbler.com/index.php/FAQs/CodeSnipAppSource#FAQ1 - -The standard and portable editions of CodeSnip share the same source code. - -The original source code of v4 is released under the Mozilla Public license -v2.0 (see http://www.mozilla.org/MPL/) and other open source licenses. See the -file "License.html" in the "Docs" directory of the repository for full licensing -information. - - -Bugs -================================================================================ - -Please do report any bugs you find. - -Bugs are recorded in tracker software. View the reported and fixed bugs via -http://delphidabbler.com/url/codesnip-bugs (this redirects to the tracker page). - -You can also access the bug tracker from CodeSnip by using the "Tools | Report -Bug Online" menu option then following the link that appears in the resulting -dialogue box. - -If you wish to report a bug, please check the current reports on the bug -tracker. If your bug hasn't already been reported or fixed please add a report -using the "Add new" link on Tracker. - -Please note that versions 1 and 2 of CodeSnip are no longer supported, so don't -report bugs for those versions. You should update the program first and only -report the bug if it is still present. - - -Make a Donation -================================================================================ - -CodeSnip is free to use and there is no requirement to pay anything for it. You -get a fully working version of the program whether you make a donation or not. - -Having said that, it takes time and money to maintain CodeSnip and the online -database. So if you wish to make a contribution it will be most welcome. - -Payment in pounds sterling can be made via this address - -http://delphidabbler.com/url/donate-cs - which redirects to a secure PayPal -page. - - -Feedback -================================================================================ - -If you want to suggest new features please use the feature request tracker -accessed from http://delphidabbler.com/url/codesnip-featurereq. - -Any other comments can be sent using the contact page at -http://delphidabbler.com/contact. - - -FAQs -================================================================================ - -There are Frequently Asked Questions pages for CodeSnip on the web, at -http://delphidabbler.com/url/codesnip-faq. - - -Contribute to the Database -================================================================================ - -Please do contribute Pascal snippets to the on-line Code Snippets database. - -You can submit routines from your user-defined snippets database using the -"Snippets | Submit Routines" menu option. Otherwise please send your code via -the DelphiDabbler contact page at http://delphidabbler.com/contact. - - -Thanks -================================================================================ - -Thanks to: - -+ David Mustard and Bill Miller for providing information that enabled me to add - Delphi 2007 and Delphi 2009 support respectively to the program. - -+ geoffsmith82 and an anonymous contributor for information about getting - CodeSnip to work with Delphi XE2. - -+ The authors of the third party source code and images used by the program. See - the program's about box or License.html for details. - -+ Various contributors to the Code Snippets database. Names of contributors are - listed in the program's About Box (use the "Help | About" menu option then - select the "About the Database" tab). If the list is empty then updating the - Code Snippets Database will download the details. - - -================================================================================ -$Rev$ -$Date$ -================================================================================ diff --git a/LICENSE b/LICENSE deleted file mode 100644 index b46349156..000000000 --- a/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -Licensing of CodeSnip's source and image files is on a per file basis. - -There are two ways that license information can be found: - -1) By examining comments within source files. License information will be at or - near the beginning of the file. - -2) By reading any LICENSE file that exists in the same directory as the files - you are interested in, or if no such file exists, in a parent directory. - The "nearest" LICENSE file takes precedence. - - A LICENSE file is used to provide license information for source files that - have no (or unclear) embedded information and for images and other files that - do not have human-readable content. - -If any information is missing or incorrect please inform the author by filling -in a bug report at http://delphidabbler.com/url/codesnip-bugs - -If you are planning on re-using any of the CodeSnip source, detailed licensing -information will be found in Docs/License.html. \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 000000000..94996ab7c --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,11 @@ +# CodeSnip License + +Executable releases of CodeSnip are released under the terms of the [Mozilla Public License 2.0](https://htmlpreview.github.io/?https://github.com/delphidabbler/codesnip/blob/master/Docs/License.html#mpl-2.0). + +Much of CodeSnip's source code is released under the same license, although other open source licenses are also used. + +There are restrictions on using CodeSnip's branding in any independent forks of the program. + +For definitive details see the [full license text](https://htmlpreview.github.io/?https://github.com/delphidabbler/codesnip/blob/master/Docs/License.html). + +A copy of the full license text is included with each CodeSnip executable. The original document can be found in the file `Docs/License.html` in the [_delphidabbler/codesnip_](https://github.com/delphidabbler/codesnip) repository on GitHub. diff --git a/README.md b/README.md index 5639ca79f..4110004a2 100644 --- a/README.md +++ b/README.md @@ -4,101 +4,86 @@ A code bank designed with Pascal in mind. * [Overview](#overview) * [Installation](#installation) -* [Bugs & Feature Requests](#bugs--feature-requests) * [Support](#support) * [Source Code](#source-code) +* [Compiling](#compiling) +* [Contributing](#contributing) * [Change Log](#change-log) -* [CodeSnip 5 Development](#codesnip-5-development) -* [Donate](#donate) +* [License](#license) +* [Bug Reports and Feature Requests](#bug-reports-and-feature-requests) ## Overview -CodeSnip is an open source code bank for storing and viewing your code snippets. While it can manage snippets in any source language, it is focussed mainly on Pascal and Delphi code for which additional features are available. - -CodeSnip also provides offline access to the DelphiDabbler [Code Snippets Database](http://snippets.delphidabbler.com/). +CodeSnip is an open source code bank for storing and viewing your code snippets. While it can manage snippets in any source language, it is focused mainly on Pascal and Delphi code for which additional features are available. The program is available in both standard and portable editions. -For more information see [http://codesnip.delphidabbler.com/features](http://codesnip.delphidabbler.com/features). - -CodeSnip requires Windows 2000 or later and Internet Explorer 6 or later, although XP and IE 8 and later are preferred. +CodeSnip can import code from the DelphiDabbler [Code Snippets Database](https://github.com/delphidabbler/code-snippets) and the [SWAG Pascal Code Collection](https://github.com/delphidabbler/swag). ## Installation -CodeSnip is installed and removed using a standard Windows installer. Administrator privileges are required for installation. - -## Bugs & Feature Requests +The standard edition of CodeSnip is installed and removed using a Windows installer. Administrator privileges are required for installation. -Bugs can be reported using the [Bug Tracker](https://sourceforge.net/p/codesnip/bugs/) on SourceForge. Info on how best to report bugs is provided on the tracker. +The portable edition has no installer. Simply follow the instructions in the [read me file](https://raw.githubusercontent.com/delphidabbler/codesnip/master/Docs/ReadMe-portable.txt) that is included in the download. -Feature requests can be made on the [Feature Request Tracker](https://sourceforge.net/p/codesnip/feature-requests/) on SourceForge. Once again info on how to proceed is provided. - -> In time the bug and feature request trackers will be moved to GitHub. +The program _should_ run on Windows 2000, with Internet Explorer 6 or later, although XP and IE 8 and later are recommended. _But_ note that recent releases of CodeSnip have only been tested on Windows 10 & 11. ## Support -There's quite a lot of support available for CodeSnip: +The following support is available to CodeSnip users: * A comprehensive help file. -* A [read-me file](https://raw.githubusercontent.com/delphidabbler/codesnip/master/Docs/ReadMe.txt)<sup>*</sup> that discusses installation, configuration, updating and known issues. -* A dedicated set of [web pages](http://delphidabbler.com/url/codesnip-home). -* An [FAQ](http://wiki.delphidabbler.com/index.php/FAQs/CodeSnipAppUsing). -* A [discussion group](https://sourceforge.net/p/codesnip/discussion) on SourceForge (albeit barely used!). -* An [RSS news feed](http://delphidabbler.com/feeds/site-news-feed?id=codesnip). This can also be accessed via the program. -* A [Google+ page](https://plus.google.com/u/0/b/108251259814638768561/108251259814638768561/posts). -* A [Blog](http://codesnip-app.blogspot.co.uk/). -* A [privacy statement](https://raw.githubusercontent.com/delphidabbler/codesnip/master/Docs/Privacy.txt)<sup>*</sup> that provides information about any personal information stored by the program and what info is transmitted over the net. +* A read-me file that discusses installation, configuration, updating and known issues. There are different versions of this file for each edition of CodeSnip: one for the [standard edition](https://raw.githubusercontent.com/delphidabbler/codesnip/master/Docs/ReadMe-standard.txt) and another for the [portable edition](https://raw.githubusercontent.com/delphidabbler/codesnip/master/Docs/ReadMe-portable.txt). [^1] +* The [Using CodeSnip FAQ](https://github.com/delphidabbler/codesnip-faq/blob/master/UsingCodeSnip.md). +* The [DelphiDabbler Blog](https://delphidabbler.blogspot.co.uk/) that provides CodeSnip news. +* CodeSnip's own [Web Page](https://delphidabbler.com/software/codesnip). There's also plenty of info available on how to compile CodeSnip from source - see below. -<sup>*</sup> These links take you to the most recent version of the documents -- they can change from release to release. +> [^1]: The linked read-me file is the most recent version. It can change from release to release. ## Source Code -Up to and including release 4.13.1 the project's source code was maintained in a Subversion repository on [SourceForge](https://sourceforge.net/p/codesnip/code/). The Subversion repo was converted to Git on 21 5 October 2015 and imported to GitHub. All releases from v3.0.0 are marked by tags in the form `version-x.x.x` where `x.x.x` is the version number. - -> Note that any branches that were created and deleted in the Subversion repo are missing from the Git history. So, for full details of the the project's history from v3.0.0 to v4.13.1 please refer to the old Subversion repo on SourceForge. +CodeSnip's source code is maintained in the [`delphidabbler/codesnip`](https://github.com/delphidabbler/codesnip) Git repository on GitHub. [^2] -Following tag `version-4.13.1` the [Git Flow](http://nvie.com/posts/a-successful-git-branching-model/) methodology was adopted, with the exception that development work on CodeSnip v5 exists outside Git Flow in the `pagoda` branch (See below for details). +The [Git Flow](https://nvie.com/posts/a-successful-git-branching-model/) methodology has been adopted for CodeSnip 4 development. The following branches are used: -Up to and including release 4.13.1, `master` is simply a copy of the old Subversion `trunk` and, as such, contains various development commits along with numerous commits related to management of Subversion. After release 4.13.1, and the addition of this read-me file, `master` contains only commits relating to an actual release. +* [`master`](https://github.com/delphidabbler/codesnip/tree/master): Always reflects the state of the source code as of the latest release. [^3] +* [`develop`](https://github.com/delphidabbler/codesnip/tree/develop): The head of this branch contains the latest v4 development code. Normal development of CodeSnip 4 takes place in feature branches that are then merged into `develop`. +* Feature branches, with names of the form `feature/<feature-name>`. Normally such branches are only used locally, but occasionally some feature branches may be pushed to the main repository. -### Contributions +You will find other branches in the repository. These are either experimental or abandoned. To find out more about them switch to the required branch and read its `README.md` file. -Contributions are welcome. Just fork the repo and create a feature branch off the `develop` branch. Commit your changes to your feature branch then submit a pull request when ready. +> [^2]: Up to and including v4.13.1 the source code was kept in a Subversion repository on SourceForge. It was converted to Git in October 2015 and imported into GitHub. All releases from v3.0.0 are marked by tags in the form `version-x.x.x` where `x.x.x` is the version number. None of the Subversion branches made it through the conversion to Git, so to see a full history look at the old [SourceForge repository](https://sourceforge.net/p/codesnip/code/). -If you are contributing to CodeSnip 5 development please create your feature branch off the `pagoda` branch instead. +> [^3]: All the converted Subversion code was committed to `master`, making it a copy of the old Subversion `trunk`. As such `master` contains various development commits along with numerous commits related to management of Subversion. After release 4.13.1, and the the first commit of this read-me file, `master` contains only commits relating to actual releases. -### Compiling +## Compiling -`master` and each branch will have a file in the root directory named `Build.html` that gives detailed information about how to compile CodeSnip. +If you want to compile CodeSnip 4 from source code you will need the rather long-in-the-tooth Delphi XE. See [this FAQ](https://github.com/delphidabbler/codesnip-faq/blob/master/SourceCode.md#faq-11) to find out why. -There is also a [FAQ](http://wiki.delphidabbler.com/index.php/FAQs/CodeSnipAppSource). +Full instructions on setting up the build environment are provided in [`Build.html`](https://htmlpreview.github.io/?https://github.com/delphidabbler/codesnip/blob/develop/Build.html). -## Change Log - -The program's current change log can be found in the file `Docs/ChangeLogs/ChangeLog-v4.txt` on the `master` branch. +## Contributing -## License +Please see [`CONTRIBUTING.md`](https://github.com/delphidabbler/codesnip/blob/develop/CONTRIBUTING.md) for details of how to contribute to the CodeSnip project. -The program's EULA which gives full details of the license applying to the latest release can be found in the file `Docs\License.html` in the `master` branch. The license has changed between releases, so if you need to see an older one, select the appropriate `version-x.x.x` tag and read the older version of the file. +⛔ Contributions to experimental and abandoned branches are not accepted. -Most of the original code is made available under the [Mozilla Public License v2](https://www.mozilla.org/MPL/2.0/). +## Change Log -The [CodeSnip Compiling & Source Code FAQ](http://wiki.delphidabbler.com/index.php/FAQs/CodeSnipAppSource) may be useful if you have any queries about re-using CodeSnip source in other projects. +The change log can be found in the file [`CHANGELOG.md`](https://github.com/delphidabbler/codesnip/blob/master/CHANGELOG.md). [^4] -## CodeSnip 5 Development +> [^4]: CodeSnip v4.15.1 and earlier did not have `CHANGELOG.md`. Instead, some versions maintained a separate change log for each major version in the `Docs/ChangeLogs` directory. -Following a false start back in 2013/4 in the `parsnip` branch, development of CodeSnip 5 has restarted. The code can be found in the `pagoda` branch, which picks up where `parsnip` left off. (Don't ask where those names came from!) +## License -At present the direction CodeSnip is being taken is to: +A summary of CodeSnip's license can be found in [`LICENSE.md`](https://github.com/delphidabbler/codesnip/blob/master/LICENSE.md) and the complete license text is in [`Docs\License.html`](https://htmlpreview.github.io/?https://github.com/delphidabbler/codesnip/blob/master/Docs/License.html). [^5] -1. Generalise it to be a code bank for several different languages instead of just Pascal, while still providing some additional support for test-compiling Pascal code. -2. Increase the focus on the user's own code while downplaying the importance of code downloaded from the DelphiDabbler [Code Snippets database](http://snippets.delphidabbler.com/). This will still be available but will be very much an add on, like [SWAG](http://swag.delphidabbler.com/) is at present. -3. Remove the program's dependency on web services provided by [DelphiDabbler.com](http://delphidabbler.com) given the current uncertainty over that websites future. +> [^5]: The linked license files relate to the latest release. However, the license file names and content can change between releases, so if you need to see an older version, select the relevant `version-x.x.x` tag to find the appropriate file. -Comments on these ideas are welcome - just create a [feature request](https://sourceforge.net/p/codesnip/feature-requests/) and select the `v5 (pagoda)` milestone. +The [CodeSnip Compiling & Source Code FAQ](https://github.com/delphidabbler/codesnip-faq/blob/master/SourceCode.md) may be useful if you have any queries about re-using the CodeSnip source code in other projects. -## Donate +## Bug Reports and Feature Requests -If you've found the program useful please consider making a donation to help cover costs. Donations are accepted by [PayPal](http://delphidabbler.com/url/donate-cs) in British Pounds (GBP) -- [see common exchange rates](http://www.xe.com/currency/gbp-british-pound). +Report bugs and requests for new features are welcome. Please see the [Issues section of `CONTRIBUTING.md`](https://github.com/delphidabbler/codesnip/blob/develop/CONTRIBUTING.md#issues) for information about how to proceed. diff --git a/Src/3rdParty/GIFImage.pas b/Src/3rdParty/GIFImage.pas deleted file mode 100644 index 1fbe8a80c..000000000 --- a/Src/3rdParty/GIFImage.pas +++ /dev/null @@ -1,12957 +0,0 @@ -unit GIFImage; -//////////////////////////////////////////////////////////////////////////////// -// // -// Project: GIF Graphics Object // -// Module: gifimage // -// Description: TGraphic implementation of the GIF89a graphics format // -// Version: 2.2 // -// Release: 5 // -// Date: 23-MAY-1999 // -// Target: Win32, Delphi 2, 3, 4 & 5, C++ Builder 3 & 4 // -// Author(s): anme: Anders Melander, anders@melander.dk // -// fila: Filip Larsen // -// rps: Reinier Sterkenburg // -// Copyright: (c) 1997-99 Anders Melander. // -// All rights reserved. // -// Formatting: 2 space indent, 8 space tabs, 80 columns. // -// // -//////////////////////////////////////////////////////////////////////////////// -// Changed 2001.07.23 by Finn Tolderlund: // -// Changed according to e-mail from "Rolf Frei" <rolf@eicom.ch> // -// on 2001.07.23 so that it works in Delphi 6. // -// // -// Changed 2002.07.07 by Finn Tolderlund: // -// Incorporated additional modifications by Alexey Barkovoy (clootie@reactor.ru) -// found in his Delphi 6 GifImage.pas (from 22-Dec-2001). // -// Alexey Barkovoy's Delphi 6 gifimage.pas can be downloaded from // -// http://clootie.narod.ru/delphi/download_vcl.html // -// These changes made showing of animated gif files more stable. The code // -// from 2001.07.23 could crash sometimes with an Execption EAccessViolation. // -// // -// Changed 2002.10.06 by Finn Tolderlund: // -// Delphi 7 compatible. // -// // -// Changed 2003-03-06 by Finn Tolderlund: // -// Changes made as a result of postings in borland.public.delphi.graphics // -// from 2003-02-28 to 2003-03-05 where white (255,255,255) in a bitmap // -// was converted to (254,254,254) in the gif. // -// The doCreateOptimizedPaletteFromSingleBitmap function and // -// the CreateOptimizedPaletteFromManyBitmaps function is changed so that // -// the correct offset 246 is used instead of 245. // -// The ReduceColors function is changed according to Anders Melander's post // -// so that a colour get converted to the precise colour if that colour is // -// present in the palette when using ColorReduction rmQuantize. // -// // -// Changed 2003-03-09 by Finn Tolderlund: // -// Delphi 7 version is now assumed if unknown compiler version is unknown // -// for better compatibility with future Delphi versions. // -// Hopefully this code is now compatible with future Delphi versions, // -// unless Borland makes some changes that breaks existing code. // -// // -// Changed 2003-08-04 by Finn Tolderlund: // -// Changed procedure AddMaskOnly so that it doesn't leak a GDI HBitmap-object // -// and it doesn't release the handle of the source bitmap which // -// is used to assign to the GIF object as in gif.assign(bm); // -// These changes were made as a result of a news post made by Renate Schaaf // -// with the subject "TGifImage HBitmap leak on assign?" // -// in borland.public.delphi.graphics on Mon 28 Jul 2003 and Sun 03 Aug 2003. // -// // -// Changed 2004.03.09 by Finn Tolderlund: // -// Added a ForceFrame property to the TGIFImage class. // -// The ForceFrame property can be used to make TGIFImage display a apecific // -// sub frame from an animated gif. // -// How to use: Set the Animate property to False and set the ForceFrame // -// property to a desired frame number (0-N) // -// Normal display: Set the ForceFrame property to -1 and set Animate to True. // -// If ForceFrame is negative TGIFImage behaves just as before this change. // -// Note that if the sub frame in the gif only contains part of the image // -// (i.e. only the changes from previous frames) the result is unpredictable. // -// The result is best if each sub frame contains a whole image. // -// If the sub frame is transparent the background is not automatically // -// restored, you must do so yourself if you want that. // -// If you are using a TImage to display the gif you can use // -// Image.Parent.Invalidate or Image.Parent.Refresh to restore the background. // -// This change was made as a result of a email correspondance with // -// Tineke Kosmis (http://www.classe.nl/) which requested such a property. // -// // -// Changed 2006.07.09 by Finn Tolderlund: // -// Added conditional switch as default: FIXHEADER_WIDTHHEIGHT_SILENT // -// When the switch is defined: // -// When loading a gif all frames are examined. If a frame has a larger // -// Width/Height than the header values then the header values are updated // -// with the larger values from the frame. // -// I had a MANTA.GIF where the header said 120x89 but the frames said 200x148 // -// and the frames got clipped. MSIE didn't clip it. // -// http://www.graphcomp.com/info/specs/ani_gif.html : // -// Do not assume all of your images are the same size. Read through their // -// sizes and set the logical screen to the largest width & height included // -// in the file. // -// By removing the define FIXHEADER_WIDTHHEIGHT_SILENT // -// the header is not altered. This makes the unit work as before. // -// // -// Changed 2006.07.10 by Finn Tolderlund: // -// Added conditional switch as default: DEFAULT_GOCLEARLOOP // -// When the switch is defined: // -// When loading a gif default DrawOptions include goClearLoop // -// Same as adding goClearLoop manually to DrawOptions. // -// This will clear an animated gif before first frame on each loop. // -// Someone sent me a 'conductor.gif' where some of the last frame was retaind // -// when beginning a new loop and that was visually incorrect. // -// Without glClearLoop the first frame may look different on the second loop // -// because some part of the last frame could still be present. // -// With goClearLoop the first frame will always look the same on each loop. // -// I think the last is better. // -// // -// Changed 2006.07.29 by Finn Tolderlund: // -// Added a check in procedure TGIFSubImage.Decompress to make sure that // -// the InitialBitsPerCode variable never exeeds the value 15. // -// Someone sent an animated iup110296.gif (corrupt I think) which caused // -// this unit to crash in function NextLZW because InitialBitsPerCode was 20. // -// This fix prevents the crash and should not cause problems with other gifs. // -// Not sure that the fix is the correct way to handle it. It seems to work. // -// // -// Changed 2006.10.09 by Finn Tolderlund: // -// Received a mail from Michael Thomas Greer with a fix that allows // -// the TGIFSubImage.Pixels[] property to be writeable. The help file states // -// that the Pixels property can be written, but it was read-only. // -// Help file: "Write Pixels to change the color index of individual pixels". // -// // -// Changed 2006.10.16 by Finn Tolderlund: // -// Received a mail from Maurizio Lotauro who was using Delphi 5 and FastMM4. // -// FastMM4 complains about a memory leak when using Delphi 5. // -// I don't have Delphi 5 installed so I can't test if there really is a // -// memory leak or if it's just FastMM4 which can't detect it correctly. // -// The problem and fix only applies to Delphi 5 or older. // -// Added a fix to keep FastMM4 happy. See more at this link: // -// http://sourceforge.net/forum/forum.php?thread_id=1559584&forum_id=443400 // -// // -// Changed 2007.01.18 by Finn Tolderlund: // -// The ReduceColors function is changed so that it's now possible to use // -// the TFastColorLookup class if you use ColorReduction rmQuantize. // -// The TFastColorLookup class was removed 2003-03-06, but is introduced again // -// because Paul Lopez needed speed when adding images to a gif. // -// This changes how rmQuantize works: It's now fast but less precise. // -// This means: // -// Use rmQuantizeWindows to get precision, use rmQuantize if you need speed. // -// // -// Changed 2008.10.19 by Finn Tolderlund: // -// Now compatible with Delphi 2009. // -// Generally changed use of Char/PChar to AnsiChar/PAnsiChar. // -// // -// Changed 2009.10.10 by Finn Tolderlund: // -// Now compatible with Delphi 2010. // -// Changed conditional defines to assume Delphi 2010 for future compilers. // -// Kind thanks to Peter Johnson (www.delphidabbler.com) // -// // -// Changed 2009.10.14 by Finn Tolderlund: // -// Simplified the list of defines and remove a few warnings in Delphi 2006. // -// // -// Changed 2009.10.24 by Peter Johnson (delphidabbler) // -// Switched explicit string cast with loss warning for Delphi 2009 and later. // -// // -// Changed 2010.03.18 by Peter Johnson (delphidabbler) // -// Comment out all TODOs. // -// // -//////////////////////////////////////////////////////////////////////////////// -// // -// Please read the "Conditions of use" in the release notes. // -// // -//////////////////////////////////////////////////////////////////////////////// -// Known problems: -// -// * The combination of buffered, tiled and transparent draw will display the -// background incorrectly (scaled). -// If this is a problem for you, use non-buffered (goDirectDraw) drawing -// instead. -// -// * The combination of non-buffered, transparent and stretched draw is -// sometimes distorted with a pattern effect when the image is displayed -// smaller than the real size (shrinked). -// -// * Buffered display flickers when TGIFImage is used by a transparent TImage -// component. -// This is a problem with TImage caused by the fact that TImage was designed -// with static images in mind. Not much I can do about it. -// -//////////////////////////////////////////////////////////////////////////////// -// To do (in rough order of priority): -// {.TODO -oanme -cFeature : TImage hook for destroy notification. } -// {.TODO -oanme -cFeature : TBitmap pool to limit resource consumption on Win95/98. } -// {.TODO -oanme -cImprovement : Make BitsPerPixel property writable. } -// {.TODO -oanme -cFeature : Visual GIF component. } -// {.TODO -oanme -cImprovement : Easier method to determine DrawPainter status. } -// {.TODO -oanme -cFeature : Import to 256+ color GIF. } -// {.TODO -oanme -cFeature : Make some of TGIFImage's properties persistent (DrawOptions etc). } -// {.TODO -oanme -cFeature : Add TGIFImage.Persistent property. Should save published properties in application extension when this options is set. } -// {.TODO -oanme -cBugFix : Solution for background buffering in scrollbox. } -// -////////////////////////////////////////////////////////////////////////////////// -{$ifdef BCB} -{$ObjExportAll On} -{$endif} - -interface -//////////////////////////////////////////////////////////////////////////////// -// -// Conditional Compiler Symbols -// -//////////////////////////////////////////////////////////////////////////////// -(* - DEBUG Must be defined if any of the DEBUG_xxx - symbols are defined. - If the symbol is defined the source will not be - optimized and overflow- and range checks will be - enabled. - - DEBUG_HASHPERFORMANCE Calculates hash table performance data. - DEBUG_HASHFILLFACTOR Calculates fill factor of hash table - - Interferes with DEBUG_HASHPERFORMANCE. - DEBUG_COMPRESSPERFORMANCE Calculates LZW compressor performance data. - DEBUG_DECOMPRESSPERFORMANCE Calculates LZW decompressor performance data. - DEBUG_DITHERPERFORMANCE Calculates color reduction performance data. - DEBUG_DRAWPERFORMANCE Calculates low level drawing performance data. - The performance data for DEBUG_DRAWPERFORMANCE - will be displayed when you press the Ctrl key. - DEBUG_RENDERPERFORMANCE Calculates performance data for the GIF to - bitmap converter. - The performance data for DEBUG_DRAWPERFORMANCE - will be displayed when you press the Ctrl key. - - GIF_NOSAFETY Define this symbol to disable overflow- and - range checks. - Ignored if the DEBUG symbol is defined. - - STRICT_MOZILLA Define to mimic Mozilla as closely as possible. - If not defined, a slightly more "optimal" - implementation is used (IMHO). - - FAST_AS_HELL Define this symbol to use strictly GIF compliant - (but too fast) animation timing. - Since our paint routines are much faster and - more precise timed than Mozilla's, the standard - GIF and Mozilla values causes animations to loop - faster than they would in Mozilla. - If the symbol is _not_ defined, an alternative - set of tweaked timing values will be used. - The tweaked values are not optimal but are based - on tests performed on my reference system: - - Windows 95 - - 133 MHz Pentium - - 64Mb RAM - - Diamond Stealth64/V3000 - - 1600*1200 in 256 colors - The alternate values can be modified if you are - not satisfied with my defaults (they can be - found a few pages down). - - REGISTER_TGIFIMAGE Define this symbol to register TGIFImage with - the TPicture class and integrate with TImage. - This is required to be able to display GIFs in - the TImage component. - The symbol is defined by default. - Undefine if you use another GIF library to - provide GIF support for TImage. - - PIXELFORMAT_TOO_SLOW When this symbol is defined, the internal - PixelFormat routines are used in some places - instead of TBitmap.PixelFormat. - The current implementation (Delphi4, Builder 3) - of TBitmap.PixelFormat can in some situation - degrade performance. - The symbol is defined by default. - - CREATEDIBSECTION_SLOW If this symbol is defined, TDIBWriter will - use global memory as scanline storage, instead - of a DIB section. - Benchmarks have shown that a DIB section is - twice as slow as global memory. - The symbol is defined by default. - The symbol requires that PIXELFORMAT_TOO_SLOW - is defined. - - SERIALIZE_RENDER Define this symbol to serialize threaded - GIF to bitmap rendering. - When a GIF is displayed with the goAsync option - (the default), the GIF to bitmap rendering is - executed in the context of the draw thread. - If more than one thread is drawing the same GIF - or the GIF is being modified while it is - animating, the GIF to bitmap rendering should be - serialized to guarantee that the bitmap isn't - modified by more than one thread at a time. If - SERIALIZE_RENDER is defined, the draw threads - uses TThread.Synchronize to serialize GIF to - bitmap rendering. - - FIXHEADER_WIDTHHEIGHT_SILENT Define this symbol to adjust Width and Height - in the header if any of the frames has a larger - Width or Height. - - DEFAULT_GOCLEARLOOP Define this symbol to clear animation on each - loop before first frame. - Same as adding goClearLoop to DrawOptions. - STRICT_MOZILLA does the same, - but STRICT_MOZILLA does something more. - -*) - -{$DEFINE REGISTER_TGIFIMAGE} -{$DEFINE PIXELFORMAT_TOO_SLOW} -{$DEFINE CREATEDIBSECTION_SLOW} -{$DEFINE FIXHEADER_WIDTHHEIGHT_SILENT} -{$DEFINE DEFAULT_GOCLEARLOOP} - -//////////////////////////////////////////////////////////////////////////////// -// -// Determine Delphi and C++ Builder version -// -//////////////////////////////////////////////////////////////////////////////// - -// Delphi 1.x -{$IFDEF VER80} - 'Error: TGIFImage does not support Delphi 1.x' -{$ENDIF} - -// Delphi 2.x -{$IFDEF VER90} - {$DEFINE VER9x} -{$ENDIF} - -// C++ Builder 1.x -{$IFDEF VER93} - // Good luck... - {$DEFINE VER9x} -{$ENDIF} - -// Delphi 3.x -{$IFDEF VER100} - {$DEFINE VER10_PLUS} - {$DEFINE D3_BCB3} -{$ENDIF} - -// C++ Builder 3.x -{$IFDEF VER110} - {$DEFINE VER10_PLUS} - {$DEFINE VER11_PLUS} - {$DEFINE D3_BCB3} - {$DEFINE BAD_STACK_ALIGNMENT} -{$ENDIF} - -// Delphi 4.x -{$IFDEF VER120} - {$DEFINE VER10_PLUS} - {$DEFINE VER11_PLUS} - {$DEFINE VER12_PLUS} - {$DEFINE BAD_STACK_ALIGNMENT} -{$ENDIF} - -// C++ Builder 4.x -{$IFDEF VER125} - {$DEFINE VER10_PLUS} - {$DEFINE VER11_PLUS} - {$DEFINE VER12_PLUS} - {$DEFINE VER125_PLUS} - {$DEFINE BAD_STACK_ALIGNMENT} -{$ENDIF} - -// Delphi 5.x -{$IFDEF VER130} - {$DEFINE VER10_PLUS} - {$DEFINE VER11_PLUS} - {$DEFINE VER12_PLUS} - {$DEFINE VER125_PLUS} - {$DEFINE VER13_PLUS} - {$DEFINE BAD_STACK_ALIGNMENT} -{$ENDIF} - -(* -// Delphi 6.x -{$IFDEF VER140} - {$WARN SYMBOL_PLATFORM OFF} - {$DEFINE VER10_PLUS} - {$DEFINE VER11_PLUS} - {$DEFINE VER12_PLUS} - {$DEFINE VER125_PLUS} - {$DEFINE VER13_PLUS} - {$DEFINE VER14_PLUS} - {$DEFINE BAD_STACK_ALIGNMENT} -{$ENDIF} - -// Delphi 7.x -{$IFDEF VER150} - {$WARN SYMBOL_PLATFORM OFF} - {$DEFINE VER10_PLUS} - {$DEFINE VER11_PLUS} - {$DEFINE VER12_PLUS} - {$DEFINE VER125_PLUS} - {$DEFINE VER13_PLUS} - {$DEFINE VER14_PLUS} - {$DEFINE VER15_PLUS} - {$DEFINE BAD_STACK_ALIGNMENT} -{$ENDIF} - -// 2008.10.19 -> -// Delphi 2009 -{$IFDEF VER200} - {$WARN SYMBOL_PLATFORM OFF} - {$DEFINE VER10_PLUS} - {$DEFINE VER11_PLUS} - {$DEFINE VER12_PLUS} - {$DEFINE VER125_PLUS} - {$DEFINE VER13_PLUS} - {$DEFINE VER14_PLUS} - {$DEFINE VER15_PLUS} - {$DEFINE VER20_PLUS} - {$DEFINE BAD_STACK_ALIGNMENT} -{$ENDIF} -// 2008.10.19 <- - -// 2003.03.09 -> -// Unknown compiler version - assume D7 compatible -{$IFNDEF VER9x} -{$IFNDEF VER10_PLUS} - {$WARN SYMBOL_PLATFORM OFF} - {$DEFINE VER10_PLUS} - {$DEFINE VER11_PLUS} - {$DEFINE VER12_PLUS} - {$DEFINE VER125_PLUS} - {$DEFINE VER13_PLUS} - {$DEFINE VER14_PLUS} - {$DEFINE VER15_PLUS} - {$DEFINE BAD_STACK_ALIGNMENT} -{$ENDIF} -{$ENDIF} -// 2003.03.09 <- - -// 2009.10.10 -> -// This ensures that future compilers always have same defines as latest compiler listed here. -{$IFDEF CONDITIONALEXPRESSIONS} - {$IF CompilerVersion >= 21.0} // >= Delphi 2010 - {$WARN SYMBOL_PLATFORM OFF} - {$WARN SYMBOL_DEPRECATED OFF} - {$DEFINE VER10_PLUS} - {$DEFINE VER11_PLUS} - {$DEFINE VER12_PLUS} - {$DEFINE VER125_PLUS} - {$DEFINE VER13_PLUS} - {$DEFINE VER14_PLUS} - {$DEFINE VER15_PLUS} - {$DEFINE VER20_PLUS} - {$DEFINE BAD_STACK_ALIGNMENT} - {$DEFINE VER21_PLUS} - {$IFEND} -{$ENDIF} -// 2009.10.10 <- -*) - -// 2009.10.14 -> -// This ensures that future compilers always have same defines as latest compiler listed here. -{$IFDEF CONDITIONALEXPRESSIONS} - {$IF CompilerVersion >= 14.0} // >= Delphi 6 - {$WARN SYMBOL_PLATFORM OFF} - {$WARN SYMBOL_DEPRECATED OFF} - {$DEFINE VER10_PLUS} - {$DEFINE VER11_PLUS} - {$DEFINE VER12_PLUS} - {$DEFINE VER125_PLUS} - {$DEFINE VER13_PLUS} - {$DEFINE VER14_PLUS} - {$DEFINE BAD_STACK_ALIGNMENT} - {$IFEND} - {$IF CompilerVersion >= 15.0} // >= Delphi 7 - {$DEFINE VER15_PLUS} - {$IFEND} - {$IF CompilerVersion >= 20.0} // >= Delphi 2009 - {$DEFINE VER20_PLUS} - // 2009.10.24 delphidabbler -> - {$WARN EXPLICIT_STRING_CAST_LOSS OFF} - // 2009.10.24 delphidabbler <- - {$IFEND} - {$IF CompilerVersion >= 21.0} // >= Delphi 2010 - {$DEFINE VER21_PLUS} - {$IFEND} -{$ENDIF} -// 2009.10.14 <- - -//////////////////////////////////////////////////////////////////////////////// -// -// Compiler Options required to compile this library -// -//////////////////////////////////////////////////////////////////////////////// -{$A+,B-,H+,J+,K-,M-,T-,X+} - -// Debug control - You can safely change these settings -{$IFDEF DEBUG} - {$C+} // ASSERTIONS - {$O-} // OPTIMIZATION - {$Q+} // OVERFLOWCHECKS - {$R+} // RANGECHECKS -{$ELSE} - {$C-} // ASSERTIONS - {$IFDEF GIF_NOSAFETY} - {$Q-}// OVERFLOWCHECKS - {$R-}// RANGECHECKS - {$ENDIF} -{$ENDIF} - -// Special options for Time2Help parser -{$ifdef TIME2HELP} -{$UNDEF PIXELFORMAT_TOO_SLOW} -{$endif} - -//////////////////////////////////////////////////////////////////////////////// -// -// External dependecies -// -//////////////////////////////////////////////////////////////////////////////// -uses - sysutils, - Windows, - Graphics, - Classes; - -//////////////////////////////////////////////////////////////////////////////// -// -// TGIFImage library version -// -//////////////////////////////////////////////////////////////////////////////// -const - GIFVersion = $0202; - GIFVersionMajor = 2; - GIFVersionMinor = 2; - GIFVersionRelease = 5; - -//////////////////////////////////////////////////////////////////////////////// -// -// Misc constants and support types -// -//////////////////////////////////////////////////////////////////////////////// -const - GIFMaxColors = 256; // Max number of colors supported by GIF - // Don't bother changing this value! - - BitmapAllocationThreshold = 500000; // Bitmap pixel count limit at which - // a newly allocated bitmap will be - // converted to 1 bit format before - // being resized and converted to 8 bit. - -var -{$IFDEF FAST_AS_HELL} - GIFDelayExp: integer = 10; // Delay multiplier in mS. -{$ELSE} - GIFDelayExp: integer = 12; // Delay multiplier in mS. Tweaked. -{$ENDIF} - // * GIFDelayExp: - // The following delay values should all - // be multiplied by this value to - // calculate the effective time (in mS). - // According to the GIF specs, this - // value should be 10. - // Since our paint routines are much - // faster than Mozilla's, you might need - // to increase this value if your - // animations loops too fast. The - // optimal value is impossible to - // determine since it depends on the - // speed of the CPU, the viceo card, - // memory and many other factors. - - GIFDefaultDelay: integer = 10; // * GIFDefaultDelay: - // Default animation delay. - // This value is used if no GCE is - // defined. - // (10 = 100 mS) - -{$IFDEF FAST_AS_HELL} - GIFMinimumDelay: integer = 1; // Minimum delay (from Mozilla source). - // (1 = 10 mS) -{$ELSE} - GIFMinimumDelay: integer = 3; // Minimum delay - Tweaked. -{$ENDIF} - // * GIFMinimumDelay: - // The minumum delay used in the Mozilla - // source is 10mS. This corresponds to a - // value of 1. However, since our paint - // routines are much faster than - // Mozilla's, a value of 3 or 4 gives - // better results. - - GIFMaximumDelay: integer = 1000; // * GIFMaximumDelay: - // Maximum delay when painter is running - // in main thread (goAsync is not set). - // This value guarantees that a very - // long and slow GIF does not hang the - // system. - // (1000 = 10000 mS = 10 Seconds) - -type - TGIFVersion = (gvUnknown, gv87a, gv89a); - TGIFVersionRec = array[0..2] of AnsiChar; - -const - GIFVersions : array[gv87a..gv89a] of TGIFVersionRec = ('87a', '89a'); - -type - // TGIFImage mostly throws exceptions of type GIFException - GIFException = class(EInvalidGraphic); - - // Severity level as indicated in the Warning methods and the OnWarning event - TGIFSeverity = (gsInfo, gsWarning, gsError); - -//////////////////////////////////////////////////////////////////////////////// -// -// Delphi 2.x support -// -//////////////////////////////////////////////////////////////////////////////// -{$IFDEF VER9x} -// Delphi 2 doesn't support TBitmap.PixelFormat -{$DEFINE PIXELFORMAT_TOO_SLOW} -type - // TThreadList from Delphi 3 classes.pas - TThreadList = class - private - FList: TList; - FLock: TRTLCriticalSection; - public - constructor Create; - destructor Destroy; override; - procedure Add(Item: Pointer); - procedure Clear; - function LockList: TList; - procedure Remove(Item: Pointer); - procedure UnlockList; - end; - - // From Delphi 3 sysutils.pas - EOutOfMemory = class(Exception); - - // From Delphi 3 classes.pas - EOutOfResources = class(EOutOfMemory); - - // From Delphi 3 windows.pas - PMaxLogPalette = ^TMaxLogPalette; - TMaxLogPalette = packed record - palVersion: Word; - palNumEntries: Word; - palPalEntry: array [Byte] of TPaletteEntry; - end; { TMaxLogPalette } - - // From Delphi 3 graphics.pas. Used by the D3 TGraphic class. - TProgressStage = (psStarting, psRunning, psEnding); - TProgressEvent = procedure (Sender: TObject; Stage: TProgressStage; - PercentDone: Byte; RedrawNow: Boolean; const R: TRect; const Msg: string) of object; - - // From Delphi 3 windows.pas - PRGBTriple = ^TRGBTriple; -{$ENDIF} - -//////////////////////////////////////////////////////////////////////////////// -// -// Forward declarations -// -//////////////////////////////////////////////////////////////////////////////// -type - TGIFImage = class; - TGIFSubImage = class; - -//////////////////////////////////////////////////////////////////////////////// -// -// TGIFItem -// -//////////////////////////////////////////////////////////////////////////////// - TGIFItem = class(TPersistent) - private - FGIFImage: TGIFImage; - protected - function GetVersion: TGIFVersion; virtual; - procedure Warning(Severity: TGIFSeverity; Message: string); virtual; - public - constructor Create(GIFImage: TGIFImage); virtual; - - procedure SaveToStream(Stream: TStream); virtual; abstract; - procedure LoadFromStream(Stream: TStream); virtual; abstract; - procedure SaveToFile(const Filename: string); virtual; - procedure LoadFromFile(const Filename: string); virtual; - property Version: TGIFVersion read GetVersion; - property Image: TGIFImage read FGIFImage; - end; - -//////////////////////////////////////////////////////////////////////////////// -// -// TGIFList -// -//////////////////////////////////////////////////////////////////////////////// - TGIFList = class(TPersistent) - private - FItems: TList; - FImage: TGIFImage; - protected - function GetItem(Index: Integer): TGIFItem; - procedure SetItem(Index: Integer; Item: TGIFItem); - function GetCount: Integer; - procedure Warning(Severity: TGIFSeverity; Message: string); virtual; - public - constructor Create(Image: TGIFImage); - destructor Destroy; override; - - function Add(Item: TGIFItem): Integer; - procedure Clear; - procedure Delete(Index: Integer); - procedure Exchange(Index1, Index2: Integer); - function First: TGIFItem; - function IndexOf(Item: TGIFItem): Integer; - procedure Insert(Index: Integer; Item: TGIFItem); - function Last: TGIFItem; - procedure Move(CurIndex, NewIndex: Integer); - function Remove(Item: TGIFItem): Integer; - procedure SaveToStream(Stream: TStream); virtual; - procedure LoadFromStream(Stream: TStream; Parent: TObject); virtual; abstract; - - property Items[Index: Integer]: TGIFItem read GetItem write SetItem; default; - property Count: Integer read GetCount; - property List: TList read FItems; - property Image: TGIFImage read FImage; - end; - -//////////////////////////////////////////////////////////////////////////////// -// -// TGIFColorMap -// -//////////////////////////////////////////////////////////////////////////////// - // One way to do it: - // TBaseColor = (bcRed, bcGreen, bcBlue); - // TGIFColor = array[bcRed..bcBlue] of BYTE; - // Another way: - TGIFColor = packed record - Red: byte; - Green: byte; - Blue: byte; - end; - - TColorMap = packed array[0..GIFMaxColors-1] of TGIFColor; - PColorMap = ^TColorMap; - - TUsageCount = record - Count : integer; // # of pixels using color index - Index : integer; // Color index - end; - TColormapHistogram = array[0..255] of TUsageCount; - TColormapReverse = array[0..255] of byte; - - TGIFColorMap = class(TPersistent) - private - FColorMap : PColorMap; - FCount : integer; - FCapacity : integer; - FOptimized : boolean; - protected - function GetColor(Index: integer): TColor; - procedure SetColor(Index: integer; Value: TColor); - function GetBitsPerPixel: integer; - function DoOptimize: boolean; - procedure SetCapacity(Size: integer); - procedure Warning(Severity: TGIFSeverity; Message: string); virtual; abstract; - procedure BuildHistogram(var Histogram: TColormapHistogram); virtual; abstract; - procedure MapImages(var Map: TColormapReverse); virtual; abstract; - - public - constructor Create; - destructor Destroy; override; - class function Color2RGB(Color: TColor): TGIFColor; - class function RGB2Color(Color: TGIFColor): TColor; - procedure SaveToStream(Stream: TStream); - procedure LoadFromStream(Stream: TStream; Count: integer); - procedure Assign(Source: TPersistent); override; - function IndexOf(Color: TColor): integer; - function Add(Color: TColor): integer; - function AddUnique(Color: TColor): integer; - procedure Delete(Index: integer); - procedure Clear; - function Optimize: boolean; virtual; abstract; - procedure Changed; virtual; abstract; - procedure ImportPalette(Palette: HPalette); - procedure ImportColorTable(Pal: pointer; Count: integer); - procedure ImportDIBColors(Handle: HDC); - procedure ImportColorMap(Map: TColorMap; Count: integer); - function ExportPalette: HPalette; - property Colors[Index: integer]: TColor read GetColor write SetColor; default; - property Data: PColorMap read FColorMap; - property Count: integer read FCount; - property Optimized: boolean read FOptimized write FOptimized; - property BitsPerPixel: integer read GetBitsPerPixel; - end; - -//////////////////////////////////////////////////////////////////////////////// -// -// TGIFHeader -// -//////////////////////////////////////////////////////////////////////////////// - TLogicalScreenDescriptor = packed record - ScreenWidth: word; { logical screen width } - ScreenHeight: word; { logical screen height } - PackedFields: byte; { packed fields } - BackgroundColorIndex: byte; { index to global color table } - AspectRatio: byte; { actual ratio = (AspectRatio + 15) / 64 } - end; - - TGIFHeader = class(TGIFItem) - private - FLogicalScreenDescriptor: TLogicalScreenDescriptor; - FColorMap : TGIFColorMap; - procedure Prepare; - protected - function GetVersion: TGIFVersion; override; - function GetBackgroundColor: TColor; - procedure SetBackgroundColor(Color: TColor); - procedure SetBackgroundColorIndex(Index: BYTE); - function GetBitsPerPixel: integer; - function GetColorResolution: integer; - public - constructor Create(GIFImage: TGIFImage); override; - destructor Destroy; override; - procedure Assign(Source: TPersistent); override; - procedure SaveToStream(Stream: TStream); override; - procedure LoadFromStream(Stream: TStream); override; - procedure Clear; - property Version: TGIFVersion read GetVersion; - property Width: WORD read FLogicalScreenDescriptor.ScreenWidth - write FLogicalScreenDescriptor.ScreenWidth; - property Height: WORD read FLogicalScreenDescriptor.ScreenHeight - write FLogicalScreenDescriptor.Screenheight; - property BackgroundColorIndex: BYTE read FLogicalScreenDescriptor.BackgroundColorIndex - write SetBackgroundColorIndex; - property BackgroundColor: TColor read GetBackgroundColor - write SetBackgroundColor; - property AspectRatio: BYTE read FLogicalScreenDescriptor.AspectRatio - write FLogicalScreenDescriptor.AspectRatio; - property ColorMap: TGIFColorMap read FColorMap; - property BitsPerPixel: integer read GetBitsPerPixel; - property ColorResolution: integer read GetColorResolution; - end; - -//////////////////////////////////////////////////////////////////////////////// -// -// TGIFExtension -// -//////////////////////////////////////////////////////////////////////////////// - TGIFExtensionType = BYTE; - TGIFExtension = class; - TGIFExtensionClass = class of TGIFExtension; - - TGIFGraphicControlExtension = class; - - TGIFExtension = class(TGIFItem) - private - FSubImage: TGIFSubImage; - protected - function GetExtensionType: TGIFExtensionType; virtual; abstract; - function GetVersion: TGIFVersion; override; - function DoReadFromStream(Stream: TStream): TGIFExtensionType; - class procedure RegisterExtension(elabel: BYTE; eClass: TGIFExtensionClass); - class function FindExtension(Stream: TStream): TGIFExtensionClass; - class function FindSubExtension(Stream: TStream): TGIFExtensionClass; virtual; - public - // Ignore compiler warning about hiding base class constructor - constructor Create(ASubImage: TGIFSubImage); {$IFDEF VER12_PLUS} reintroduce; {$ENDIF} virtual; - destructor Destroy; override; - procedure SaveToStream(Stream: TStream); override; - procedure LoadFromStream(Stream: TStream); override; - property ExtensionType: TGIFExtensionType read GetExtensionType; - property SubImage: TGIFSubImage read FSubImage; - end; - -//////////////////////////////////////////////////////////////////////////////// -// -// TGIFSubImage -// -//////////////////////////////////////////////////////////////////////////////// - TGIFExtensionList = class(TGIFList) - protected - function GetExtension(Index: Integer): TGIFExtension; - procedure SetExtension(Index: Integer; Extension: TGIFExtension); - public - procedure LoadFromStream(Stream: TStream; Parent: TObject); override; - property Extensions[Index: Integer]: TGIFExtension read GetExtension write SetExtension; default; - end; - - TImageDescriptor = packed record - Separator: byte; { fixed value of ImageSeparator } - Left: word; { Column in pixels in respect to left edge of logical screen } - Top: word; { row in pixels in respect to top of logical screen } - Width: word; { width of image in pixels } - Height: word; { height of image in pixels } - PackedFields: byte; { Bit fields } - end; - - TGIFSubImage = class(TGIFItem) - private - FBitmap : TBitmap; - FMask : HBitmap; - FNeedMask : boolean; - FLocalPalette : HPalette; - FData : PAnsiChar; - FDataSize : integer; - FColorMap : TGIFColorMap; - FImageDescriptor : TImageDescriptor; - FExtensions : TGIFExtensionList; - FTransparent : boolean; - FGCE : TGIFGraphicControlExtension; - procedure Prepare; - procedure Compress(Stream: TStream); - procedure Decompress(Stream: TStream); - protected - function GetVersion: TGIFVersion; override; - function GetInterlaced: boolean; - procedure SetInterlaced(Value: boolean); - function GetColorResolution: integer; - function GetBitsPerPixel: integer; - procedure AssignTo(Dest: TPersistent); override; - function DoGetBitmap: TBitmap; - function DoGetDitherBitmap: TBitmap; - function GetBitmap: TBitmap; - procedure SetBitmap(Value: TBitmap); - procedure FreeMask; - function GetEmpty: Boolean; - function GetPalette: HPALETTE; - procedure SetPalette(Value: HPalette); - function GetActiveColorMap: TGIFColorMap; - function GetBoundsRect: TRect; - procedure SetBoundsRect(const Value: TRect); - procedure DoSetBounds(ALeft, ATop, AWidth, AHeight: integer); - function GetClientRect: TRect; - function GetPixel(x, y: integer): BYTE; -// 2006.10.09 -> - procedure SetPixel(x, y: integer; Value: BYTE); -// 2006.10.09 <- - function GetScanline(y: integer): pointer; - procedure NewBitmap; - procedure FreeBitmap; - procedure NewImage; - procedure FreeImage; - procedure NeedImage; - function ScaleRect(DestRect: TRect): TRect; - function HasMask: boolean; - function GetBounds(Index: integer): WORD; - procedure SetBounds(Index: integer; Value: WORD); - function GetHasBitmap: boolean; - procedure SetHasBitmap(Value: boolean); - public - constructor Create(GIFImage: TGIFImage); override; - destructor Destroy; override; - procedure Clear; - procedure SaveToStream(Stream: TStream); override; - procedure LoadFromStream(Stream: TStream); override; - procedure Assign(Source: TPersistent); override; - procedure Draw(ACanvas: TCanvas; const Rect: TRect; - DoTransparent, DoTile: boolean); - procedure StretchDraw(ACanvas: TCanvas; const Rect: TRect; - DoTransparent, DoTile: boolean); - procedure Crop; - procedure Merge(Previous: TGIFSubImage); - property HasBitmap: boolean read GetHasBitmap write SetHasBitmap; - property Left: WORD index 1 read GetBounds write SetBounds; - property Top: WORD index 2 read GetBounds write SetBounds; - property Width: WORD index 3 read GetBounds write SetBounds; - property Height: WORD index 4 read GetBounds write SetBounds; - property BoundsRect: TRect read GetBoundsRect write SetBoundsRect; - property ClientRect: TRect read GetClientRect; - property Interlaced: boolean read GetInterlaced write SetInterlaced; - property ColorMap: TGIFColorMap read FColorMap; - property ActiveColorMap: TGIFColorMap read GetActiveColorMap; - property Data: PAnsiChar read FData; - property DataSize: integer read FDataSize; - property Extensions: TGIFExtensionList read FExtensions; - property Version: TGIFVersion read GetVersion; - property ColorResolution: integer read GetColorResolution; - property BitsPerPixel: integer read GetBitsPerPixel; - property Bitmap: TBitmap read GetBitmap write SetBitmap; - property Mask: HBitmap read FMask; - property Palette: HPALETTE read GetPalette write SetPalette; - property Empty: boolean read GetEmpty; - property Transparent: boolean read FTransparent; - property GraphicControlExtension: TGIFGraphicControlExtension read FGCE; -// 2006.10.09 -> -// property Pixels[x, y: integer]: BYTE read GetPixel; - property Pixels[x, y: integer]: BYTE read GetPixel write SetPixel; -// 2006.10.09 <- - property Scanline[y: integer]: pointer read GetScanline; - end; - -//////////////////////////////////////////////////////////////////////////////// -// -// TGIFTrailer -// -//////////////////////////////////////////////////////////////////////////////// - TGIFTrailer = class(TGIFItem) - procedure SaveToStream(Stream: TStream); override; - procedure LoadFromStream(Stream: TStream); override; - end; - -//////////////////////////////////////////////////////////////////////////////// -// -// TGIFGraphicControlExtension -// -//////////////////////////////////////////////////////////////////////////////// - // Graphic Control Extension block a.k.a GCE - TGIFGCERec = packed record - BlockSize: byte; { should be 4 } - PackedFields: Byte; - DelayTime: Word; { in centiseconds } - TransparentColorIndex: Byte; - Terminator: Byte; - end; - - TDisposalMethod = (dmNone, dmNoDisposal, dmBackground, dmPrevious); - - TGIFGraphicControlExtension = class(TGIFExtension) - private - FGCExtension: TGIFGCERec; - protected - function GetExtensionType: TGIFExtensionType; override; - function GetTransparent: boolean; - procedure SetTransparent(Value: boolean); - function GetTransparentColor: TColor; - procedure SetTransparentColor(Color: TColor); - function GetTransparentColorIndex: BYTE; - procedure SetTransparentColorIndex(Value: BYTE); - function GetDelay: WORD; - procedure SetDelay(Value: WORD); - function GetUserInput: boolean; - procedure SetUserInput(Value: boolean); - function GetDisposal: TDisposalMethod; - procedure SetDisposal(Value: TDisposalMethod); - - public - constructor Create(ASubImage: TGIFSubImage); override; - destructor Destroy; override; - procedure SaveToStream(Stream: TStream); override; - procedure LoadFromStream(Stream: TStream); override; - property Delay: WORD read GetDelay write SetDelay; - property Transparent: boolean read GetTransparent write SetTransparent; - property TransparentColorIndex: BYTE read GetTransparentColorIndex - write SetTransparentColorIndex; - property TransparentColor: TColor read GetTransparentColor write SetTransparentColor; - property UserInput: boolean read GetUserInput write SetUserInput; - property Disposal: TDisposalMethod read GetDisposal write SetDisposal; - end; - -//////////////////////////////////////////////////////////////////////////////// -// -// TGIFTextExtension -// -//////////////////////////////////////////////////////////////////////////////// - TGIFPlainTextExtensionRec = packed record - BlockSize: byte; { should be 12 } - Left, Top, Width, Height: Word; - CellWidth, CellHeight: Byte; - TextFGColorIndex, - TextBGColorIndex: Byte; - end; - - TGIFTextExtension = class(TGIFExtension) - private - FText : TStrings; - FPlainTextExtension : TGIFPlainTextExtensionRec; - protected - function GetExtensionType: TGIFExtensionType; override; - function GetForegroundColor: TColor; - procedure SetForegroundColor(Color: TColor); - function GetBackgroundColor: TColor; - procedure SetBackgroundColor(Color: TColor); - function GetBounds(Index: integer): WORD; - procedure SetBounds(Index: integer; Value: WORD); - function GetCharWidthHeight(Index: integer): BYTE; - procedure SetCharWidthHeight(Index: integer; Value: BYTE); - function GetColorIndex(Index: integer): BYTE; - procedure SetColorIndex(Index: integer; Value: BYTE); - public - constructor Create(ASubImage: TGIFSubImage); override; - destructor Destroy; override; - procedure SaveToStream(Stream: TStream); override; - procedure LoadFromStream(Stream: TStream); override; - property Left: WORD index 1 read GetBounds write SetBounds; - property Top: WORD index 2 read GetBounds write SetBounds; - property GridWidth: WORD index 3 read GetBounds write SetBounds; - property GridHeight: WORD index 4 read GetBounds write SetBounds; - property CharWidth: BYTE index 1 read GetCharWidthHeight write SetCharWidthHeight; - property CharHeight: BYTE index 2 read GetCharWidthHeight write SetCharWidthHeight; - property ForegroundColorIndex: BYTE index 1 read GetColorIndex write SetColorIndex; - property ForegroundColor: TColor read GetForegroundColor; - property BackgroundColorIndex: BYTE index 2 read GetColorIndex write SetColorIndex; - property BackgroundColor: TColor read GetBackgroundColor; - property Text: TStrings read FText write FText; - end; - -//////////////////////////////////////////////////////////////////////////////// -// -// TGIFCommentExtension -// -//////////////////////////////////////////////////////////////////////////////// - TGIFCommentExtension = class(TGIFExtension) - private - FText : TStrings; - protected - function GetExtensionType: TGIFExtensionType; override; - public - constructor Create(ASubImage: TGIFSubImage); override; - destructor Destroy; override; - procedure SaveToStream(Stream: TStream); override; - procedure LoadFromStream(Stream: TStream); override; - property Text: TStrings read FText; - end; - -//////////////////////////////////////////////////////////////////////////////// -// -// TGIFApplicationExtension -// -//////////////////////////////////////////////////////////////////////////////// - TGIFIdentifierCode = array[0..7] of AnsiChar; - TGIFAuthenticationCode = array[0..2] of AnsiChar; - TGIFApplicationRec = packed record - Identifier : TGIFIdentifierCode; - Authentication : TGIFAuthenticationCode; - end; - - TGIFApplicationExtension = class; - TGIFAppExtensionClass = class of TGIFApplicationExtension; - - TGIFApplicationExtension = class(TGIFExtension) - private - FIdent : TGIFApplicationRec; - function GetAuthentication: AnsiString; - function GetIdentifier: AnsiString; - protected - function GetExtensionType: TGIFExtensionType; override; - procedure SetAuthentication(const Value: AnsiString); - procedure SetIdentifier(const Value: AnsiString); - procedure SaveData(Stream: TStream); virtual; abstract; - procedure LoadData(Stream: TStream); virtual; abstract; - public - constructor Create(ASubImage: TGIFSubImage); override; - destructor Destroy; override; - procedure SaveToStream(Stream: TStream); override; - procedure LoadFromStream(Stream: TStream); override; - class procedure RegisterExtension(eIdent: TGIFApplicationRec; eClass: TGIFAppExtensionClass); - class function FindSubExtension(Stream: TStream): TGIFExtensionClass; override; - property Identifier: AnsiString read GetIdentifier write SetIdentifier; - property Authentication: AnsiString read GetAuthentication write SetAuthentication; - end; - -//////////////////////////////////////////////////////////////////////////////// -// -// TGIFUnknownAppExtension -// -//////////////////////////////////////////////////////////////////////////////// - TGIFBlock = class(TObject) - private - FSize : BYTE; - FData : pointer; - public - constructor Create(ASize: integer); - destructor Destroy; override; - procedure SaveToStream(Stream: TStream); - procedure LoadFromStream(Stream: TStream); - property Size: BYTE read FSize; - property Data: pointer read FData; - end; - - TGIFUnknownAppExtension = class(TGIFApplicationExtension) - private - FBlocks : TList; - protected - procedure SaveData(Stream: TStream); override; - procedure LoadData(Stream: TStream); override; - public - constructor Create(ASubImage: TGIFSubImage); override; - destructor Destroy; override; - property Blocks: TList read FBlocks; - end; - -//////////////////////////////////////////////////////////////////////////////// -// -// TGIFAppExtNSLoop -// -//////////////////////////////////////////////////////////////////////////////// - TGIFAppExtNSLoop = class(TGIFApplicationExtension) - private - FLoops : WORD; - FBufferSize : DWORD; - protected - procedure SaveData(Stream: TStream); override; - procedure LoadData(Stream: TStream); override; - public - constructor Create(ASubImage: TGIFSubImage); override; - property Loops: WORD read FLoops write FLoops; - property BufferSize: DWORD read FBufferSize write FBufferSize; - end; - -//////////////////////////////////////////////////////////////////////////////// -// -// TGIFImage -// -//////////////////////////////////////////////////////////////////////////////// - TGIFImageList = class(TGIFList) - protected - function GetImage(Index: Integer): TGIFSubImage; - procedure SetImage(Index: Integer; SubImage: TGIFSubImage); - public - procedure LoadFromStream(Stream: TStream; Parent: TObject); override; - procedure SaveToStream(Stream: TStream); override; - property SubImages[Index: Integer]: TGIFSubImage read GetImage write SetImage; default; - end; - - // Compression algorithms - TGIFCompression = - (gcLZW, // Normal LZW compression - gcRLE // GIF compatible RLE compression - ); - - // Color reduction methods - TColorReduction = - (rmNone, // Do not perform color reduction - rmWindows20, // Reduce to the Windows 20 color system palette - rmWindows256, // Reduce to the Windows 256 color halftone palette (Only works in 256 color display mode) - rmWindowsGray, // Reduce to the Windows 4 grayscale colors - rmMonochrome, // Reduce to a black/white monochrome palette - rmGrayScale, // Reduce to a uniform 256 shade grayscale palette - rmNetscape, // Reduce to the Netscape 216 color palette - rmQuantize, // Reduce to optimal 2^n color palette - rmQuantizeWindows, // Reduce to optimal 256 color windows palette - rmPalette // Reduce to custom palette - ); - TDitherMode = - (dmNearest, // Nearest color matching w/o error correction - dmFloydSteinberg, // Floyd Steinberg Error Diffusion dithering - dmStucki, // Stucki Error Diffusion dithering - dmSierra, // Sierra Error Diffusion dithering - dmJaJuNI, // Jarvis, Judice & Ninke Error Diffusion dithering - dmSteveArche, // Stevenson & Arche Error Diffusion dithering - dmBurkes // Burkes Error Diffusion dithering - // dmOrdered, // Ordered dither - ); - - // Optimization options - TGIFOptimizeOption = - (ooCrop, // Crop animated GIF frames - ooMerge, // Merge pixels of same color - ooCleanup, // Remove comments and application extensions - ooColorMap, // Sort color map by usage and remove unused entries - ooReduceColors // Reduce color depth ***NOT IMPLEMENTED*** - ); - TGIFOptimizeOptions = set of TGIFOptimizeOption; - - TGIFDrawOption = - (goAsync, // Asyncronous draws (paint in thread) - goTransparent, // Transparent draws - goAnimate, // Animate draws - goLoop, // Loop animations - goLoopContinously, // Ignore loop count and loop forever - goValidateCanvas, // Validate canvas in threaded paint ***NOT IMPLEMENTED*** - goDirectDraw, // Draw() directly on canvas - goClearOnLoop, // Clear animation on loop - goTile, // Tiled display - goDither, // Dither to Netscape palette - goAutoDither // Only dither on 256 color systems - ); - TGIFDrawOptions = set of TGIFDrawOption; - // Note: if goAsync is not set then goDirectDraw should be set. Otherwise - // the image will not be displayed. - - PGIFPainter = ^TGIFPainter; - - TGIFPainter = class(TThread) - private - FImage : TGIFImage; // The TGIFImage that owns this painter - FCanvas : TCanvas; // Destination canvas - FRect : TRect; // Destination rect - FDrawOptions : TGIFDrawOptions;// Paint options - FAnimationSpeed : integer; // Animation speed % - FActiveImage : integer; // Current frame - Disposal , // Used by synchronized paint - OldDisposal : TDisposalMethod;// Used by synchronized paint - BackupBuffer : TBitmap; // Used by synchronized paint - FrameBuffer : TBitmap; // Used by synchronized paint - Background : TBitmap; // Used by synchronized paint - ValidateDC : HDC; - DoRestart : boolean; // Flag used to restart animation - FStarted : boolean; // Flag used to signal start of paint - PainterRef : PGIFPainter; // Pointer to var referencing painter - FEventHandle : THandle; // Animation delay event - ExceptObject : Exception; // Eaten exception - ExceptAddress : pointer; // Eaten exceptions address - FEvent : TNotifyEvent; // Used by synchronized events - FOnStartPaint : TNotifyEvent; - FOnPaint : TNotifyEvent; - FOnAfterPaint : TNotifyEvent; - FOnLoop : TNotifyEvent; - FOnEndPaint : TNotifyEvent; - procedure DoOnTerminate(Sender: TObject);// Sync. shutdown procedure - procedure DoSynchronize(Method: TThreadMethod);// Conditional sync stub -{$ifdef SERIALIZE_RENDER} - procedure PrefetchBitmap; // Sync. bitmap prefetch -{$endif} - procedure DoPaintFrame; // Sync. buffered paint procedure - procedure DoPaint; // Sync. paint procedure - procedure DoEvent; - procedure SetActiveImage(const Value: integer);// Sync. event procedure - protected - procedure Execute; override; - procedure SetAnimationSpeed(Value: integer); - public - constructor Create(AImage: TGIFImage; ACanvas: TCanvas; ARect: TRect; - Options: TGIFDrawOptions); - constructor CreateRef(Painter: PGIFPainter; AImage: TGIFImage; ACanvas: TCanvas; ARect: TRect; - Options: TGIFDrawOptions); - destructor Destroy; override; - procedure Start; - procedure Stop; - procedure Restart; - property Image: TGIFImage read FImage; - property Canvas: TCanvas read FCanvas; - property Rect: TRect read FRect write FRect; - property DrawOptions: TGIFDrawOptions read FDrawOptions write FDrawOptions; - property AnimationSpeed: integer read FAnimationSpeed write SetAnimationSpeed; - property Started: boolean read FStarted; - property ActiveImage: integer read FActiveImage write SetActiveImage; - property OnStartPaint: TNotifyEvent read FOnStartPaint write FOnStartPaint; - property OnPaint: TNotifyEvent read FOnPaint write FOnPaint; - property OnAfterPaint: TNotifyEvent read FOnAfterPaint write FOnAfterPaint; - property OnLoop: TNotifyEvent read FOnLoop write FOnLoop; - property OnEndPaint : TNotifyEvent read FOnEndPaint write FOnEndPaint ; - property EventHandle: THandle read FEventHandle; - end; - - TGIFWarning = procedure(Sender: TObject; Severity: TGIFSeverity; Message: string) of object; - - TGIFImage = class(TGraphic) - private - IsDrawing : Boolean; - IsInsideGetPalette : boolean; - FImages : TGIFImageList; - FHeader : TGIFHeader; - FGlobalPalette : HPalette; - FPainters : TThreadList; - FDrawOptions : TGIFDrawOptions; - FColorReduction : TColorReduction; - FReductionBits : integer; - FDitherMode : TDitherMode; - FCompression : TGIFCompression; - FOnWarning : TGIFWarning; - FBitmap : TBitmap; - FDrawPainter : TGIFPainter; - FThreadPriority : TThreadPriority; - FAnimationSpeed : integer; - FForceFrame: Integer; // 2004.03.09 - FDrawBackgroundColor: TColor; - FOnStartPaint : TNotifyEvent; - FOnPaint : TNotifyEvent; - FOnAfterPaint : TNotifyEvent; - FOnLoop : TNotifyEvent; - FOnEndPaint : TNotifyEvent; -{$IFDEF VER9x} - FPaletteModified : Boolean; - FOnProgress : TProgressEvent; -{$ENDIF} - function GetAnimate: Boolean; // 2002.07.07 - procedure SetAnimate(const Value: Boolean); // 2002.07.07 - procedure SetForceFrame(const Value: Integer); // 2004.03.09 - protected - // Obsolete: procedure Changed(Sender: TObject); {$IFDEF VER9x} virtual; {$ELSE} override; {$ENDIF} - function GetHeight: Integer; override; - procedure SetHeight(Value: Integer); override; - function GetWidth: Integer; override; - procedure SetWidth(Value: Integer); override; - procedure AssignTo(Dest: TPersistent); override; - function InternalPaint(Painter: PGIFPainter; ACanvas: TCanvas; const Rect: TRect; Options: TGIFDrawOptions): TGIFPainter; - procedure Draw(ACanvas: TCanvas; const Rect: TRect); override; - function Equals(Graphic: TGraphic): Boolean; override; - function GetPalette: HPALETTE; {$IFDEF VER9x} virtual; {$ELSE} override; {$ENDIF} - procedure SetPalette(Value: HPalette); {$IFDEF VER9x} virtual; {$ELSE} override; {$ENDIF} - function GetEmpty: Boolean; override; - procedure WriteData(Stream: TStream); override; - function GetIsTransparent: Boolean; - function GetVersion: TGIFVersion; - function GetColorResolution: integer; - function GetBitsPerPixel: integer; - function GetBackgroundColorIndex: BYTE; - procedure SetBackgroundColorIndex(const Value: BYTE); - function GetBackgroundColor: TColor; - procedure SetBackgroundColor(const Value: TColor); - function GetAspectRatio: BYTE; - procedure SetAspectRatio(const Value: BYTE); - procedure SetDrawOptions(Value: TGIFDrawOptions); - procedure SetAnimationSpeed(Value: integer); - procedure SetReductionBits(Value: integer); - procedure NewImage; - function GetBitmap: TBitmap; - function NewBitmap: TBitmap; - procedure FreeBitmap; - function GetColorMap: TGIFColorMap; - function GetDoDither: boolean; - property DrawPainter: TGIFPainter read FDrawPainter; // Extremely volatile - property DoDither: boolean read GetDoDither; -{$IFDEF VER9x} - procedure Progress(Sender: TObject; Stage: TProgressStage; - PercentDone: Byte; RedrawNow: Boolean; const R: TRect; const Msg: string); dynamic; -{$ENDIF} -{$IFDEF FIXHEADER_WIDTHHEIGHT_SILENT} - procedure FixHeaderWidthHeight; // 2006.07.09 -{$ENDIF} - public - constructor Create; override; - destructor Destroy; override; - procedure SaveToStream(Stream: TStream); override; - procedure LoadFromStream(Stream: TStream); override; - procedure LoadFromResourceName(Instance: THandle; const ResName: String); // 2002.07.07 - function Add(Source: TPersistent): integer; - procedure Pack; - procedure OptimizeColorMap; - procedure Optimize(Options: TGIFOptimizeOptions; - ColorReduction: TColorReduction; DitherMode: TDitherMode; - ReductionBits: integer); - procedure Clear; - procedure StopDraw; - function Paint(ACanvas: TCanvas; const Rect: TRect; Options: TGIFDrawOptions): TGIFPainter; - procedure PaintStart; - procedure PaintPause; - procedure PaintStop; - procedure PaintResume; - procedure PaintRestart; - procedure Warning(Sender: TObject; Severity: TGIFSeverity; Message: string); virtual; - procedure Assign(Source: TPersistent); override; - procedure LoadFromClipboardFormat(AFormat: Word; AData: THandle; - APalette: HPALETTE); override; - procedure SaveToClipboardFormat(var AFormat: Word; var AData: THandle; - var APalette: HPALETTE); override; - property GlobalColorMap: TGIFColorMap read GetColorMap; - property Version: TGIFVersion read GetVersion; - property Images: TGIFImageList read FImages; - property ColorResolution: integer read GetColorResolution; - property BitsPerPixel: integer read GetBitsPerPixel; - property BackgroundColorIndex: BYTE read GetBackgroundColorIndex write SetBackgroundColorIndex; - property BackgroundColor: TColor read GetBackgroundColor write SetBackgroundColor; - property AspectRatio: BYTE read GetAspectRatio write SetAspectRatio; - property Header: TGIFHeader read FHeader; // ***OBSOLETE*** - property IsTransparent: boolean read GetIsTransparent; - property DrawOptions: TGIFDrawOptions read FDrawOptions write SetDrawOptions; - property DrawBackgroundColor: TColor read FDrawBackgroundColor write FDrawBackgroundColor; - property ColorReduction: TColorReduction read FColorReduction write FColorReduction; - property ReductionBits: integer read FReductionBits write SetReductionBits; - property DitherMode: TDitherMode read FDitherMode write FDitherMode; - property Compression: TGIFCompression read FCompression write FCompression; - property AnimationSpeed: integer read FAnimationSpeed write SetAnimationSpeed; - property Animate: Boolean read GetAnimate write SetAnimate; // 2002.07.07 - property ForceFrame: Integer read FForceFrame write SetForceFrame; // 2004.03.09 - property Painters: TThreadList read FPainters; - property ThreadPriority: TThreadPriority read FThreadPriority write FThreadPriority; - property Bitmap: TBitmap read GetBitmap; // Volatile - beware! - property OnWarning: TGIFWarning read FOnWarning write FOnWarning; - property OnStartPaint: TNotifyEvent read FOnStartPaint write FOnStartPaint; - property OnPaint: TNotifyEvent read FOnPaint write FOnPaint; - property OnAfterPaint: TNotifyEvent read FOnAfterPaint write FOnAfterPaint; - property OnLoop: TNotifyEvent read FOnLoop write FOnLoop; - property OnEndPaint : TNotifyEvent read FOnEndPaint write FOnEndPaint ; -{$IFDEF VER9x} - property Palette: HPALETTE read GetPalette write SetPalette; - property PaletteModified: Boolean read FPaletteModified write FPaletteModified; - property OnProgress: TProgressEvent read FOnProgress write FOnProgress; -{$ENDIF} - end; - -//////////////////////////////////////////////////////////////////////////////// -// -// Utility routines -// -//////////////////////////////////////////////////////////////////////////////// - // WebPalette creates a 216 color uniform palette a.k.a. the Netscape Palette - function WebPalette: HPalette; - - // ReduceColors - // Map colors in a bitmap to their nearest representation in a palette using - // the methods specified by the ColorReduction and DitherMode parameters. - // The ReductionBits parameter specifies the desired number of colors (bits - // per pixel) when the reduction method is rmQuantize. The CustomPalette - // specifies the palette when the rmPalette reduction method is used. - function ReduceColors(Bitmap: TBitmap; ColorReduction: TColorReduction; - DitherMode: TDitherMode; ReductionBits: integer; CustomPalette: hPalette): TBitmap; - - // CreateOptimizedPaletteFromManyBitmaps - //: Performs Color Quantization on multiple bitmaps. - // The Bitmaps parameter is a list of bitmaps. Returns an optimized palette. - function CreateOptimizedPaletteFromManyBitmaps(Bitmaps: TList; Colors, ColorBits: integer; - Windows: boolean): hPalette; - -{$IFDEF VER9x} - // From Delphi 3 graphics.pas -type - TPixelFormat = (pfDevice, pf1bit, pf4bit, pf8bit, pf15bit, pf16bit, pf24bit, pf32bit, pfCustom); -{$ENDIF} - - procedure InternalGetDIBSizes(Bitmap: HBITMAP; var InfoHeaderSize: Integer; - var ImageSize: longInt; PixelFormat: TPixelFormat); - function InternalGetDIB(Bitmap: HBITMAP; Palette: HPALETTE; - var BitmapInfo; var Bits; PixelFormat: TPixelFormat): Boolean; - -//////////////////////////////////////////////////////////////////////////////// -// -// Global variables -// -//////////////////////////////////////////////////////////////////////////////// -// GIF Clipboard format identifier for use by LoadFromClipboardFormat and -// SaveToClipboardFormat. -// Set in Initialization section. -var - CF_GIF: WORD; - -//////////////////////////////////////////////////////////////////////////////// -// -// Library defaults -// -//////////////////////////////////////////////////////////////////////////////// -var - //: Default options for TGIFImage.DrawOptions. - GIFImageDefaultDrawOptions : TGIFDrawOptions = - [goAsync, goLoop, goTransparent, goAnimate, goDither, goAutoDither -{$IFDEF STRICT_MOZILLA} - ,goClearOnLoop -{$ENDIF} -{$IFDEF DEFAULT_GOCLEARLOOP} // 2006.07.10 - ,goClearOnLoop -{$ENDIF} - ]; - - // WARNING! Do not use goAsync and goDirectDraw unless you have absolute - // control of the destination canvas. - // TGIFPainter will continue to write on the canvas even after the canvas has - // been deleted, unless *you* prevent it. - // The goValidateCanvas option will fix this problem if it is ever implemented. - - //: Default color reduction methods for bitmap import. - // These are the fastest settings, but also the ones that gives the - // worst result (in most cases). - GIFImageDefaultColorReduction: TColorReduction = rmNetscape; - GIFImageDefaultColorReductionBits: integer = 8; // Range 3 - 8 - GIFImageDefaultDitherMode: TDitherMode = dmNearest; - - //: Default encoder compression method. - GIFImageDefaultCompression: TGIFCompression = gcLZW; - - //: Default painter thread priority - GIFImageDefaultThreadPriority: TThreadPriority = tpNormal; - - //: Default animation speed in % of normal speed (range 0 - 1000) - GIFImageDefaultAnimationSpeed: integer = 100; - - // DoAutoDither is set to True in the initializaion section if the desktop DC - // supports 256 colors or less. - // It can be modified in your application to disable/enable Auto Dithering - DoAutoDither: boolean = False; - - // Palette is set to True in the initialization section if the desktop DC - // supports 256 colors or less. - // You should NOT modify it. - PaletteDevice: boolean = False; - - // Set GIFImageRenderOnLoad to True to render (convert to bitmap) the - // GIF frames as they are loaded instead of rendering them on-demand. - // This might increase resource consumption and will increase load time, - // but will cause animated GIFs to display more smoothly. - GIFImageRenderOnLoad: boolean = False; - - // If GIFImageOptimizeOnStream is true, the GIF will be optimized - // before it is streamed to the DFM file. - // This will not affect TGIFImage.SaveToStream or SaveToFile. - GIFImageOptimizeOnStream: boolean = False; - -//////////////////////////////////////////////////////////////////////////////// -// -// Design Time support -// -//////////////////////////////////////////////////////////////////////////////// -// Dummy component registration for design time support of GIFs in TImage -procedure Register; - -//////////////////////////////////////////////////////////////////////////////// -// -// Error messages -// -//////////////////////////////////////////////////////////////////////////////// -{$ifndef VER9x} -resourcestring -{$else} -const -{$endif} - // GIF Error messages - sOutOfData = 'Premature end of data'; - sTooManyColors = 'Color table overflow'; - sBadColorIndex = 'Invalid color index'; - sBadVersion = 'Unsupported GIF version'; - sBadSignature = 'Invalid GIF signature'; - sScreenBadColorSize = 'Invalid number of colors specified in Screen Descriptor'; - sImageBadColorSize = 'Invalid number of colors specified in Image Descriptor'; - sUnknownExtension = 'Unknown extension type'; - sBadExtensionLabel = 'Invalid extension introducer'; - sOutOfMemDIB = 'Failed to allocate memory for GIF DIB'; - sDIBCreate = 'Failed to create DIB from Bitmap'; - sDecodeTooFewBits = 'Decoder bit buffer under-run'; - sDecodeCircular = 'Circular decoder table entry'; - sBadTrailer = 'Invalid Image trailer'; - sBadExtensionInstance = 'Internal error: Extension Instance does not match Extension Label'; - sBadBlockSize = 'Unsupported Application Extension block size'; - sBadBlock = 'Unknown GIF block type'; - sUnsupportedClass = 'Object type not supported for operation'; - sInvalidData = 'Invalid GIF data'; - sBadHeight = 'Image height too small for contained frames'; - sBadWidth = 'Image width too small for contained frames'; -{$IFNDEF REGISTER_TGIFIMAGE} - sGIFToClipboard = 'Clipboard operations not supported for GIF objects'; -{$ELSE} - sFailedPaste = 'Failed to store GIF on clipboard'; -{$IFDEF VER9x} - sUnknownClipboardFormat= 'Unsupported clipboard format'; -{$ENDIF} -{$ENDIF} - sScreenSizeExceeded = 'Image exceeds Logical Screen size'; - sNoColorTable = 'No global or local color table defined'; - sBadPixelCoordinates = 'Invalid pixel coordinates'; - sUnsupportedBitmap = 'Unsupported bitmap format'; - sInvalidPixelFormat = 'Unsupported PixelFormat'; - sBadDimension = 'Invalid image dimensions'; - sNoDIB = 'Image has no DIB'; - sInvalidStream = 'Invalid stream operation'; - sInvalidColor = 'Color not in color table'; - sInvalidBitSize = 'Invalid Bits Per Pixel value'; - sEmptyColorMap = 'Color table is empty'; - sEmptyImage = 'Image is empty'; - sInvalidBitmapList = 'Invalid bitmap list'; - sInvalidReduction = 'Invalid reduction method'; -{$IFDEF VER9x} - // From Delphi 3 consts.pas - SOutOfResources = 'Out of system resources'; - SInvalidBitmap = 'Bitmap image is not valid'; - SScanLine = 'Scan line index out of range'; -{$ENDIF} - -//////////////////////////////////////////////////////////////////////////////// -// -// Misc texts -// -//////////////////////////////////////////////////////////////////////////////// - // File filter name - sGIFImageFile = 'GIF Image'; - - // Progress messages - sProgressLoading = 'Loading...'; - sProgressSaving = 'Saving...'; - sProgressConverting = 'Converting...'; - sProgressRendering = 'Rendering...'; - sProgressCopying = 'Copying...'; - sProgressOptimizing = 'Optimizing...'; - - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -// -// Implementation -// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -implementation - -{ This makes me long for the C preprocessor... } -{$ifdef DEBUG} - {$ifdef DEBUG_COMPRESSPERFORMANCE} - {$define DEBUG_PERFORMANCE} - {$else} - {$ifdef DEBUG_DECOMPRESSPERFORMANCE} - {$define DEBUG_PERFORMANCE} - {$else} - {$ifdef DEBUG_DITHERPERFORMANCE} - {$define DEBUG_PERFORMANCE} - {$else} - {$ifdef DEBUG_DITHERPERFORMANCE} - {$define DEBUG_PERFORMANCE} - {$else} - {$ifdef DEBUG_DRAWPERFORMANCE} - {$define DEBUG_PERFORMANCE} - {$else} - {$ifdef DEBUG_RENDERPERFORMANCE} - {$define DEBUG_PERFORMANCE} - {$endif} - {$endif} - {$endif} - {$endif} - {$endif} - {$endif} -{$endif} - -uses -{$ifdef DEBUG} - dialogs, -{$endif} - mmsystem, // timeGetTime() - messages, - Consts; - -//////////////////////////////////////////////////////////////////////////////// -// -// Misc consts -// -//////////////////////////////////////////////////////////////////////////////// -const - { Extension/block label values } - bsPlainTextExtension = $01; - bsGraphicControlExtension = $F9; - bsCommentExtension = $FE; - bsApplicationExtension = $FF; - - bsImageDescriptor = Ord(','); - bsExtensionIntroducer = Ord('!'); - bsTrailer = ord(';'); - - // Thread messages - Used by TThread.Synchronize() - CM_DESTROYWINDOW = $8FFE; // Defined in classes.pas - CM_EXECPROC = $8FFF; // Defined in classes.pas - - -//////////////////////////////////////////////////////////////////////////////// -// -// Design Time support -// -//////////////////////////////////////////////////////////////////////////////// -//: Dummy component registration to add design-time support of GIFs to TImage. -// Since TGIFImage isn't a component there's nothing to register here, but -// since Register is only called at design time we can set the design time -// GIF paint options here (modify as you please): -procedure Register; -begin - // Don't loop animations at design-time. Animated GIFs will animate once and - // then stop thus not using CPU resources and distracting the developer. - Exclude(GIFImageDefaultDrawOptions, goLoop); -end; - -//////////////////////////////////////////////////////////////////////////////// -// -// Utilities -// -//////////////////////////////////////////////////////////////////////////////// -//: Creates a 216 color uniform non-dithering Netscape palette. -function WebPalette: HPalette; -type - TLogWebPalette = packed record - palVersion : word; - palNumEntries : word; - PalEntries : array[0..5,0..5,0..5] of TPaletteEntry; - end; -var - r, g, b : byte; - LogWebPalette : TLogWebPalette; - LogPalette : TLogpalette absolute LogWebPalette; // Stupid typecast -begin - with LogWebPalette do - begin - palVersion:= $0300; - palNumEntries:= 216; - for r:=0 to 5 do - for g:=0 to 5 do - for b:=0 to 5 do - begin - with PalEntries[r,g,b] do - begin - peRed := 51 * r; - peGreen := 51 * g; - peBlue := 51 * b; - peFlags := 0; - end; - end; - end; - Result := CreatePalette(Logpalette); -end; - -(* -** GDI Error handling -** Adapted from graphics.pas -*) -{$IFOPT R+} - {$DEFINE R_PLUS} - {$RANGECHECKS OFF} -{$ENDIF} -{$ifdef D3_BCB3} -function GDICheck(Value: Integer): Integer; -{$else} -function GDICheck(Value: Cardinal): Cardinal; -{$endif} -var - ErrorCode : integer; -// 2008.10.19 -> -{$IFDEF VER20_PLUS} - Buf : array [byte] of WideChar; -{$ELSE} - Buf : array [byte] of AnsiChar; -{$ENDIF} -// 2008.10.19 <- - - function ReturnAddr: Pointer; - // From classes.pas - asm - MOV EAX,[EBP+4] // sysutils.pas says [EBP-4], but this works ! - end; - -begin - if (Value = 0) then - begin - ErrorCode := GetLastError; - if (ErrorCode <> 0) and (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, nil, - ErrorCode, LOCALE_USER_DEFAULT, Buf, sizeof(Buf), nil) <> 0) then - raise EOutOfResources.Create(Buf) at ReturnAddr - else - raise EOutOfResources.Create(SOutOfResources) at ReturnAddr; - end; - Result := Value; -end; -{$IFDEF R_PLUS} - {$RANGECHECKS ON} - {$UNDEF R_PLUS} -{$ENDIF} - -(* -** Raise error condition -*) -procedure Error(msg: string); - function ReturnAddr: Pointer; - // From classes.pas - asm - MOV EAX,[EBP+4] // sysutils.pas says [EBP-4] ! - end; -begin - raise GIFException.Create(msg) at ReturnAddr; -end; - -(* -** Return number bytes required to -** hold a given number of bits. -*) -function ByteAlignBit(Bits: Cardinal): Cardinal; -begin - Result := (Bits+7) SHR 3; -end; -// Rounded up to nearest 2 -function WordAlignBit(Bits: Cardinal): Cardinal; -begin - Result := ((Bits+15) SHR 4) SHL 1; -end; -// Rounded up to nearest 4 -function DWordAlignBit(Bits: Cardinal): Cardinal; -begin - Result := ((Bits+31) SHR 5) SHL 2; -end; -// Round to arbitrary number of bits -function AlignBit(Bits, BitsPerPixel, Alignment: Cardinal): Cardinal; -begin - Dec(Alignment); - Result := ((Bits * BitsPerPixel) + Alignment) and not Alignment; - Result := Result SHR 3; -end; - -(* -** Compute Bits per Pixel from Number of Colors -** (Return the ceiling log of n) -*) -function Colors2bpp(Colors: integer): integer; -var - MaxColor : integer; -begin - (* - ** This might be faster computed by multiple if then else statements - *) - - if (Colors = 0) then - Result := 0 - else - begin - Result := 1; - MaxColor := 2; - while (Colors > MaxColor) do - begin - inc(Result); - MaxColor := MaxColor SHL 1; - end; - end; -end; - -(* -** Write an ordinal byte value to a stream -*) -procedure WriteByte(Stream: TStream; b: BYTE); -begin - Stream.Write(b, 1); -end; - -(* -** Read an ordinal byte value from a stream -*) -function ReadByte(Stream: TStream): BYTE; -begin - Stream.Read(Result, 1); -end; - -(* -** Read data from stream and raise exception of EOF -*) -procedure ReadCheck(Stream: TStream; var Buffer; Size: LongInt); -var - ReadSize : integer; -begin - ReadSize := Stream.Read(Buffer, Size); - if (ReadSize <> Size) then - Error(sOutOfData); -end; - -(* -** Write a string list to a stream as multiple blocks -** of max 255 characters in each. -*) -procedure WriteStrings(Stream: TStream; Text: TStrings); -var - i : integer; - b : BYTE; - size : integer; - s : AnsiString; -begin - for i := 0 to Text.Count-1 do - begin - s := AnsiString(Text[i]); - size := length(s); - if (size > 255) then - b := 255 - else - b := size; - while (size > 0) do - begin - dec(size, b); - WriteByte(Stream, b); -// 2008.10.19 -> -// Stream.Write(PChar(s)^, b); - Stream.Write(PByte(s)^, b); -// 2008.10.19 <- - delete(s, 1, b); - if (b > size) then - b := size; - end; - end; - // Terminating zero (length = 0) - WriteByte(Stream, 0); -end; - - -(* -** Read a string list from a stream as multiple blocks -** of max 255 characters in each. -*) -{.TODO -oanme -cImprovement : Replace ReadStrings with TGIFReader. } -procedure ReadStrings(Stream: TStream; Text: TStrings); -var - size : BYTE; - buf : array[0..255] of AnsiChar; -begin - Text.Clear; - if (Stream.Read(size, 1) <> 1) then - exit; - while (size > 0) do - begin - ReadCheck(Stream, buf, size); - buf[size] := #0; -// 2008.10.19 -> -// Text.Add(Buf); - Text.Add(string(Buf)); -// 2008.10.19 <- - if (Stream.Read(size, 1) <> 1) then - exit; - end; -end; - - -//////////////////////////////////////////////////////////////////////////////// -// -// Delphi 2.x / C++ Builder 1.x support -// -//////////////////////////////////////////////////////////////////////////////// -{$IFDEF VER9x} -var - // From Delphi 3 graphics.pas - SystemPalette16: HPalette; // 16 color palette that maps to the system palette - -type - TPixelFormats = set of TPixelFormat; - -const - // Only pf1bit, pf4bit and pf8bit is supported since they are the only ones - // with palettes - SupportedPixelformats: TPixelFormats = [pf1bit, pf4bit, pf8bit]; -{$ENDIF} - - -// -------------------------- -// InitializeBitmapInfoHeader -// -------------------------- -// Fills a TBitmapInfoHeader with the values of a bitmap when converted to a -// DIB of a specified PixelFormat. -// -// Parameters: -// Bitmap The handle of the source bitmap. -// Info The TBitmapInfoHeader buffer that will receive the values. -// PixelFormat The pixel format of the destination DIB. -// -{$IFDEF BAD_STACK_ALIGNMENT} - // Disable optimization to circumvent optimizer bug... - {$IFOPT O+} - {$DEFINE O_PLUS} - {$O-} - {$ENDIF} -{$ENDIF} -procedure InitializeBitmapInfoHeader(Bitmap: HBITMAP; var Info: TBitmapInfoHeader; - PixelFormat: TPixelFormat); -// From graphics.pas, "optimized" for our use -var - DIB : TDIBSection; - Bytes : Integer; -begin - DIB.dsbmih.biSize := 0; - Bytes := GetObject(Bitmap, SizeOf(DIB), @DIB); - if (Bytes = 0) then - Error(sInvalidBitmap); - - if (Bytes >= (sizeof(DIB.dsbm) + sizeof(DIB.dsbmih))) and - (DIB.dsbmih.biSize >= sizeof(DIB.dsbmih)) then - Info := DIB.dsbmih - else - begin - FillChar(Info, sizeof(Info), 0); - with Info, DIB.dsbm do - begin - biSize := SizeOf(Info); - biWidth := bmWidth; - biHeight := bmHeight; - end; - end; - case PixelFormat of - pf1bit: Info.biBitCount := 1; - pf4bit: Info.biBitCount := 4; - pf8bit: Info.biBitCount := 8; - pf24bit: Info.biBitCount := 24; - else - Error(sInvalidPixelFormat); - // Info.biBitCount := DIB.dsbm.bmBitsPixel * DIB.dsbm.bmPlanes; - end; - Info.biPlanes := 1; - Info.biCompression := BI_RGB; // Always return data in RGB format - Info.biSizeImage := AlignBit(Info.biWidth, Info.biBitCount, 32) * Cardinal(abs(Info.biHeight)); -end; -{$IFDEF O_PLUS} - {$O+} - {$UNDEF O_PLUS} -{$ENDIF} - -// ------------------- -// InternalGetDIBSizes -// ------------------- -// Calculates the buffer sizes nescessary for convertion of a bitmap to a DIB -// of a specified PixelFormat. -// See the GetDIBSizes API function for more info. -// -// Parameters: -// Bitmap The handle of the source bitmap. -// InfoHeaderSize -// The returned size of a buffer that will receive the DIB's -// TBitmapInfo structure. -// ImageSize The returned size of a buffer that will receive the DIB's -// pixel data. -// PixelFormat The pixel format of the destination DIB. -// -procedure InternalGetDIBSizes(Bitmap: HBITMAP; var InfoHeaderSize: Integer; - var ImageSize: longInt; PixelFormat: TPixelFormat); -// From graphics.pas, "optimized" for our use -var - Info : TBitmapInfoHeader; -begin - InitializeBitmapInfoHeader(Bitmap, Info, PixelFormat); - // Check for palette device format - if (Info.biBitCount > 8) then - begin - // Header but no palette - InfoHeaderSize := SizeOf(TBitmapInfoHeader); - if ((Info.biCompression and BI_BITFIELDS) <> 0) then - Inc(InfoHeaderSize, 12); - end else - // Header and palette - InfoHeaderSize := SizeOf(TBitmapInfoHeader) + SizeOf(TRGBQuad) * (1 shl Info.biBitCount); - ImageSize := Info.biSizeImage; -end; - -// -------------- -// InternalGetDIB -// -------------- -// Converts a bitmap to a DIB of a specified PixelFormat. -// -// Parameters: -// Bitmap The handle of the source bitmap. -// Pal The handle of the source palette. -// BitmapInfo The buffer that will receive the DIB's TBitmapInfo structure. -// A buffer of sufficient size must have been allocated prior to -// calling this function. -// Bits The buffer that will receive the DIB's pixel data. -// A buffer of sufficient size must have been allocated prior to -// calling this function. -// PixelFormat The pixel format of the destination DIB. -// -// Returns: -// True on success, False on failure. -// -// Note: The InternalGetDIBSizes function can be used to calculate the -// nescessary sizes of the BitmapInfo and Bits buffers. -// -function InternalGetDIB(Bitmap: HBITMAP; Palette: HPALETTE; - var BitmapInfo; var Bits; PixelFormat: TPixelFormat): Boolean; -// From graphics.pas, "optimized" for our use -var - OldPal : HPALETTE; - DC : HDC; -begin - InitializeBitmapInfoHeader(Bitmap, TBitmapInfoHeader(BitmapInfo), PixelFormat); - OldPal := 0; - DC := CreateCompatibleDC(0); - try - if (Palette <> 0) then - begin - OldPal := SelectPalette(DC, Palette, False); - RealizePalette(DC); - end; - Result := (GetDIBits(DC, Bitmap, 0, abs(TBitmapInfoHeader(BitmapInfo).biHeight), - @Bits, TBitmapInfo(BitmapInfo), DIB_RGB_COLORS) <> 0); - finally - if (OldPal <> 0) then - SelectPalette(DC, OldPal, False); - DeleteDC(DC); - end; -end; - -// ---------- -// DIBFromBit -// ---------- -// Converts a bitmap to a DIB of a specified PixelFormat. -// The DIB is returned in a TMemoryStream ready for streaming to a BMP file. -// -// Note: As opposed to D2's DIBFromBit function, the returned stream also -// contains a TBitmapFileHeader at offset 0. -// -// Parameters: -// Stream The TMemoryStream used to store the bitmap data. -// The stream must be allocated and freed by the caller prior to -// calling this function. -// Src The handle of the source bitmap. -// Pal The handle of the source palette. -// PixelFormat The pixel format of the destination DIB. -// DIBHeader A pointer to the DIB's TBitmapInfo (or TBitmapInfoHeader) -// structure in the memory stream. -// The size of the structure can either be deduced from the -// pixel format (i.e. number of colors) or calculated by -// subtracting the DIBHeader pointer from the DIBBits pointer. -// DIBBits A pointer to the DIB's pixel data in the memory stream. -// -procedure DIBFromBit(Stream: TMemoryStream; Src: HBITMAP; - Pal: HPALETTE; PixelFormat: TPixelFormat; var DIBHeader, DIBBits: Pointer); -// (From D2 graphics.pas, "optimized" for our use) -var - HeaderSize : integer; - FileSize : longInt; - ImageSize : longInt; - BitmapFileHeader : PBitmapFileHeader; -begin - if (Src = 0) then - Error(sInvalidBitmap); - // Get header- and pixel data size for new pixel format - InternalGetDIBSizes(Src, HeaderSize, ImageSize, PixelFormat); - // Make room in stream for a TBitmapInfo and pixel data - FileSize := sizeof(TBitmapFileHeader) + HeaderSize + ImageSize; - Stream.SetSize(FileSize); - // Get pointer to TBitmapFileHeader - BitmapFileHeader := Stream.Memory; - // Get pointer to TBitmapInfo - DIBHeader := Pointer(Longint(BitmapFileHeader) + sizeof(TBitmapFileHeader)); - // Get pointer to pixel data - DIBBits := Pointer(Longint(DIBHeader) + HeaderSize); - // Initialize file header - FillChar(BitmapFileHeader^, sizeof(TBitmapFileHeader), 0); - with BitmapFileHeader^ do - begin - bfType := $4D42; // 'BM' = Windows BMP signature - bfSize := FileSize; // File size (not needed) - bfOffBits := sizeof(TBitmapFileHeader) + HeaderSize; // Offset of pixel data - end; - // Get pixel data in new pixel format - InternalGetDIB(Src, Pal, DIBHeader^, DIBBits^, PixelFormat); -end; - -// -------------- -// GetPixelFormat -// -------------- -// Returns the current pixel format of a bitmap. -// -// Replacement for delphi 3 TBitmap.PixelFormat getter. -// -// Parameters: -// Bitmap The bitmap which pixel format is returned. -// -// Returns: -// The PixelFormat of the bitmap -// -function GetPixelFormat(Bitmap: TBitmap): TPixelFormat; -{$IFDEF VER9x} -// From graphics.pas, "optimized" for our use -var - DIBSection : TDIBSection; - Bytes : Integer; - Handle : HBitmap; -begin - Result := pfCustom; // This value is never returned - // BAD_STACK_ALIGNMENT - // Note: To work around an optimizer bug, we do not use Bitmap.Handle - // directly. Instead we store the value and use it indirectly. Unless we do - // this, the register containing Bitmap.Handle will be overwritten! - Handle := Bitmap.Handle; - if (Handle <> 0) then - begin - Bytes := GetObject(Handle, SizeOf(DIBSection), @DIBSection); - if (Bytes = 0) then - Error(sInvalidBitmap); - - with (DIBSection) do - begin - // Check for NT bitmap - if (Bytes < (SizeOf(dsbm) + SizeOf(dsbmih))) or (dsbmih.biSize < SizeOf(dsbmih)) then - DIBSection.dsBmih.biBitCount := dsbm.bmBitsPixel * dsbm.bmPlanes; - - case (dsBmih.biBitCount) of - 0: Result := pfDevice; - 1: Result := pf1bit; - 4: Result := pf4bit; - 8: Result := pf8bit; - 16: case (dsBmih.biCompression) of - BI_RGB: - Result := pf15Bit; - BI_BITFIELDS: - if (dsBitFields[1] = $07E0) then - Result := pf16Bit; - end; - 24: Result := pf24Bit; - 32: if (dsBmih.biCompression = BI_RGB) then - Result := pf32Bit; - else - Error(sUnsupportedBitmap); - end; - end; - end else -// Result := pfDevice; - Error(sUnsupportedBitmap); -end; -{$ELSE} -begin - Result := Bitmap.PixelFormat; -end; -{$ENDIF} - -// -------------- -// SetPixelFormat -// -------------- -// Changes the pixel format of a TBitmap. -// -// Replacement for delphi 3 TBitmap.PixelFormat setter. -// The returned TBitmap will always be a DIB. -// -// Note: Under Delphi 3.x this function will leak a palette handle each time it -// converts a TBitmap to pf8bit format! -// If possible, use SafeSetPixelFormat instead to avoid this. -// -// Parameters: -// Bitmap The bitmap to modify. -// PixelFormat The pixel format to convert to. -// -procedure SetPixelFormat(Bitmap: TBitmap; PixelFormat: TPixelFormat); -{$IFDEF VER9x} -var - Stream : TMemoryStream; - Header , - Bits : Pointer; -begin - // Can't change anything without a handle - if (Bitmap.Handle = 0) then - Error(sInvalidBitmap); - - // Only convert to supported formats - if not(PixelFormat in SupportedPixelformats) then - Error(sInvalidPixelFormat); - - // No need to convert to same format - if (GetPixelFormat(Bitmap) = PixelFormat) then - exit; - - Stream := TMemoryStream.Create; - try - // Convert to DIB file in memory stream - DIBFromBit(Stream, Bitmap.Handle, Bitmap.Palette, PixelFormat, Header, Bits); - // Load DIB from stream - Stream.Position := 0; - Bitmap.LoadFromStream(Stream); - finally - Stream.Free; - end; -end; -{$ELSE} -begin - Bitmap.PixelFormat := PixelFormat; -end; -{$ENDIF} - -{$IFDEF VER100} -var - pf8BitBitmap: TBitmap = nil; -{$ENDIF} - -// ------------------ -// SafeSetPixelFormat -// ------------------ -// Changes the pixel format of a TBitmap but doesn't preserve the contents. -// -// Replacement for Delphi 3 TBitmap.PixelFormat setter. -// The returned TBitmap will always be an empty DIB of the same size as the -// original bitmap. -// -// This function is used to avoid the palette handle leak that Delphi 3's -// SetPixelFormat and TBitmap.PixelFormat suffers from. -// -// Parameters: -// Bitmap The bitmap to modify. -// PixelFormat The pixel format to convert to. -// -procedure SafeSetPixelFormat(Bitmap: TBitmap; PixelFormat: TPixelFormat); -{$IFDEF VER9x} -begin - SetPixelFormat(Bitmap, PixelFormat); -end; -{$ELSE} -{$IFNDEF VER100} -var - Palette : hPalette; -begin - Bitmap.PixelFormat := PixelFormat; - - // Work around a bug in TBitmap: - // When converting to pf8bit format, the palette assigned to TBitmap.Palette - // will be a half tone palette (which only contains the 20 system colors). - // Unfortunately this is not the palette used to render the bitmap and it - // is also not the palette saved with the bitmap. - if (PixelFormat = pf8bit) then - begin - // Disassociate the wrong palette from the bitmap (without affecting - // the DIB color table) - Palette := Bitmap.ReleasePalette; - if (Palette <> 0) then - DeleteObject(Palette); - // Recreate the palette from the DIB color table - Bitmap.Palette; - end; -end; -{$ELSE} -var - Width , - Height : integer; -begin - if (PixelFormat = pf8bit) then - begin - // Partial solution to "TBitmap.PixelFormat := pf8bit" leak - // by Greg Chapman <glc@well.com> - if (pf8BitBitmap = nil) then - begin - // Create a "template" bitmap - // The bitmap is deleted in the finalization section of the unit. - pf8BitBitmap:= TBitmap.Create; - // Convert template to pf8bit format - // This will leak 1 palette handle, but only once - pf8BitBitmap.PixelFormat:= pf8Bit; - end; - // Store the size of the original bitmap - Width := Bitmap.Width; - Height := Bitmap.Height; - // Convert to pf8bit format by copying template - Bitmap.Assign(pf8BitBitmap); - // Restore the original size - Bitmap.Width := Width; - Bitmap.Height := Height; - end else - // This is safe since only pf8bit leaks - Bitmap.PixelFormat := PixelFormat; -end; -{$ENDIF} -{$ENDIF} - - -{$IFDEF VER9x} - -// ----------- -// CopyPalette -// ----------- -// Copies a HPALETTE. -// -// Copied from D3 graphics.pas. -// This is declared private in some old versions of Delphi 2 so we have to -// implement it here to support those old versions. -// -// Parameters: -// Palette The palette to copy. -// -// Returns: -// The handle to a new palette. -// -function CopyPalette(Palette: HPALETTE): HPALETTE; -var - PaletteSize: Integer; - LogPal: TMaxLogPalette; -begin - Result := 0; - if Palette = 0 then Exit; - PaletteSize := 0; - if GetObject(Palette, SizeOf(PaletteSize), @PaletteSize) = 0 then Exit; - if PaletteSize = 0 then Exit; - with LogPal do - begin - palVersion := $0300; - palNumEntries := PaletteSize; - GetPaletteEntries(Palette, 0, PaletteSize, palPalEntry); - end; - Result := CreatePalette(PLogPalette(@LogPal)^); -end; - - -// TThreadList implementation from Delphi 3 classes.pas -constructor TThreadList.Create; -begin - inherited Create; - InitializeCriticalSection(FLock); - FList := TList.Create; -end; - -destructor TThreadList.Destroy; -begin - LockList; // Make sure nobody else is inside the list. - try - FList.Free; - inherited Destroy; - finally - UnlockList; - DeleteCriticalSection(FLock); - end; -end; - -procedure TThreadList.Add(Item: Pointer); -begin - LockList; - try - if FList.IndexOf(Item) = -1 then - FList.Add(Item); - finally - UnlockList; - end; -end; - -procedure TThreadList.Clear; -begin - LockList; - try - FList.Clear; - finally - UnlockList; - end; -end; - -function TThreadList.LockList: TList; -begin - EnterCriticalSection(FLock); - Result := FList; -end; - -procedure TThreadList.Remove(Item: Pointer); -begin - LockList; - try - FList.Remove(Item); - finally - UnlockList; - end; -end; - -procedure TThreadList.UnlockList; -begin - LeaveCriticalSection(FLock); -end; -// End of TThreadList implementation - -// From Delphi 3 sysutils.pas -{ CompareMem performs a binary compare of Length bytes of memory referenced - by P1 to that of P2. CompareMem returns True if the memory referenced by - P1 is identical to that of P2. } -function CompareMem(P1, P2: Pointer; Length: Integer): Boolean; assembler; -asm - PUSH ESI - PUSH EDI - MOV ESI,P1 - MOV EDI,P2 - MOV EDX,ECX - XOR EAX,EAX - AND EDX,3 - SHR ECX,1 - SHR ECX,1 - REPE CMPSD - JNE @@2 - MOV ECX,EDX - REPE CMPSB - JNE @@2 -@@1: INC EAX -@@2: POP EDI - POP ESI -end; - -// Dummy ASSERT procedure since ASSERT does not exist in Delphi 2.x -procedure ASSERT(Condition: boolean; Message: string); -begin -end; - -{$ENDIF} // Delphi 2.x stuff - -//////////////////////////////////////////////////////////////////////////////// -// -// TDIB Classes -// -// These classes gives read and write access to TBitmap's pixel data -// independently of the Delphi version used. -// -//////////////////////////////////////////////////////////////////////////////// -type - TDIB = class(TObject) - private - FBitmap : TBitmap; - FPixelFormat : TPixelFormat; - protected - function GetScanline(Row: integer): pointer; virtual; abstract; - constructor Create(ABitmap: TBitmap; APixelFormat: TPixelFormat); - public - property Scanline[Row: integer]: pointer read GetScanline; - property Bitmap: TBitmap read FBitmap; - property PixelFormat: TPixelFormat read FPixelFormat; - end; - - TDIBReader = class(TDIB) - private -{$ifdef VER9x} - FDIB : TDIBSection; - FDC : HDC; - FScanLine : pointer; - FLastRow : integer; - FInfo : PBitmapInfo; - FBytes : integer; -{$endif} - protected - function GetScanline(Row: integer): pointer; override; - public - constructor Create(ABitmap: TBitmap; APixelFormat: TPixelFormat); - destructor Destroy; override; - end; - - TDIBWriter = class(TDIB) - private -{$ifdef PIXELFORMAT_TOO_SLOW} - FDIBInfo : PBitmapInfo; - FDIBBits : pointer; - FDIBInfoSize : integer; - FDIBBitsSize : longInt; -{$ifndef CREATEDIBSECTION_SLOW} - FDIB : HBITMAP; -{$endif} -{$endif} - FPalette : HPalette; - FHeight : integer; - FWidth : integer; - protected - procedure CreateDIB; - procedure FreeDIB; - procedure NeedDIB; - function GetScanline(Row: integer): pointer; override; - public - constructor Create(ABitmap: TBitmap; APixelFormat: TPixelFormat; - AWidth, AHeight: integer; APalette: HPalette); - destructor Destroy; override; - procedure UpdateBitmap; - property Width: integer read FWidth; - property Height: integer read FHeight; - property Palette: HPalette read FPalette; - end; - -//////////////////////////////////////////////////////////////////////////////// -constructor TDIB.Create(ABitmap: TBitmap; APixelFormat: TPixelFormat); -begin - inherited Create; - FBitmap := ABitmap; - FPixelFormat := APixelFormat; -end; - -//////////////////////////////////////////////////////////////////////////////// -constructor TDIBReader.Create(ABitmap: TBitmap; APixelFormat: TPixelFormat); -{$ifdef VER9x} -var - InfoHeaderSize : integer; - ImageSize : longInt; -{$endif} -begin - inherited Create(ABitmap, APixelFormat); -{$ifndef VER9x} - SetPixelFormat(FBitmap, FPixelFormat); -{$else} - FDC := CreateCompatibleDC(0); - SelectPalette(FDC, FBitmap.Palette, False); - - // Allocate DIB info structure - InternalGetDIBSizes(ABitmap.Handle, InfoHeaderSize, ImageSize, APixelFormat); - GetMem(FInfo, InfoHeaderSize); - // Get DIB info - InitializeBitmapInfoHeader(ABitmap.Handle, FInfo^.bmiHeader, APixelFormat); - - // Allocate scan line buffer - GetMem(FScanLine, ImageSize DIV abs(FInfo^.bmiHeader.biHeight)); - - FLastRow := -1; -{$endif} -end; - -destructor TDIBReader.Destroy; -begin -{$ifdef VER9x} - DeleteDC(FDC); - FreeMem(FScanLine); - FreeMem(FInfo); -{$endif} - inherited Destroy; -end; - -function TDIBReader.GetScanline(Row: integer): pointer; -begin -{$ifdef VER9x} - if (Row < 0) or (Row >= FBitmap.Height) then - raise EInvalidGraphicOperation.Create(SScanLine); - GDIFlush; - - Result := FScanLine; - if (Row = FLastRow) then - exit; - FLastRow := Row; - - if (FInfo^.bmiHeader.biHeight > 0) then // bottom-up DIB - Row := FInfo^.bmiHeader.biHeight - Row - 1; - GetDIBits(FDC, FBitmap.Handle, Row, 1, FScanLine, FInfo^, DIB_RGB_COLORS); - -{$else} - Result := FBitmap.ScanLine[Row]; -{$endif} -end; - -//////////////////////////////////////////////////////////////////////////////// -constructor TDIBWriter.Create(ABitmap: TBitmap; APixelFormat: TPixelFormat; - AWidth, AHeight: integer; APalette: HPalette); -begin - inherited Create(ABitmap, APixelFormat); - - // DIB writer only supports 8 or 24 bit bitmaps - if not(APixelFormat in [pf8bit, pf24bit]) then - Error(sInvalidPixelFormat); - if (AWidth = 0) or (AHeight = 0) then - Error(sBadDimension); - - FHeight := AHeight; - FWidth := AWidth; -{$ifndef PIXELFORMAT_TOO_SLOW} - FBitmap.Palette := 0; - FBitmap.Height := FHeight; - FBitmap.Width := FWidth; - SafeSetPixelFormat(FBitmap, FPixelFormat); - FPalette := CopyPalette(APalette); - FBitmap.Palette := FPalette; -{$else} - FPalette := APalette; - FDIBInfo := nil; - FDIBBits := nil; -{$ifndef CREATEDIBSECTION_SLOW} - FDIB := 0; -{$endif} -{$endif} -end; - -destructor TDIBWriter.Destroy; -begin - UpdateBitmap; - FreeDIB; - inherited Destroy; -end; - -function TDIBWriter.GetScanline(Row: integer): pointer; -begin -{$ifdef PIXELFORMAT_TOO_SLOW} - NeedDIB; - - if (FDIBBits = nil) then - Error(sNoDIB); - with FDIBInfo^.bmiHeader do - begin - if (Row < 0) or (Row >= Height) then - raise EInvalidGraphicOperation.Create(SScanLine); - GDIFlush; - - if biHeight > 0 then // bottom-up DIB - Row := biHeight - Row - 1; - Result := PAnsiChar(Cardinal(FDIBBits) + Cardinal(Row) * AlignBit(biWidth, biBitCount, 32)); - end; -{$else} - Result := FBitmap.ScanLine[Row]; -{$endif} -end; - -procedure TDIBWriter.CreateDIB; -{$IFDEF PIXELFORMAT_TOO_SLOW} -var - SrcColors : WORD; -// ScreenDC : HDC; - - // From Delphi 3.02 graphics.pas - // There is a bug in the ByteSwapColors from Delphi 3.0! - procedure ByteSwapColors(var Colors; Count: Integer); - var // convert RGB to BGR and vice-versa. TRGBQuad <-> TPaletteEntry - SysInfo: TSystemInfo; - begin - GetSystemInfo(SysInfo); - asm - MOV EDX, Colors - MOV ECX, Count - DEC ECX - JS @@END - LEA EAX, SysInfo - CMP [EAX].TSystemInfo.wProcessorLevel, 3 - JE @@386 - @@1: MOV EAX, [EDX+ECX*4] - BSWAP EAX - SHR EAX,8 - MOV [EDX+ECX*4],EAX - DEC ECX - JNS @@1 - JMP @@END - @@386: - PUSH EBX - @@2: XOR EBX,EBX - MOV EAX, [EDX+ECX*4] - MOV BH, AL - MOV BL, AH - SHR EAX,16 - SHL EBX,8 - MOV BL, AL - MOV [EDX+ECX*4],EBX - DEC ECX - JNS @@2 - POP EBX - @@END: - end; - end; -{$ENDIF} -begin -{$ifdef PIXELFORMAT_TOO_SLOW} - FreeDIB; - - if (PixelFormat = pf8bit) then - // 8 bit: Header and palette - FDIBInfoSize := SizeOf(TBitmapInfoHeader) + SizeOf(TRGBQuad) * (1 shl 8) - else - // 24 bit: Header but no palette - FDIBInfoSize := SizeOf(TBitmapInfoHeader); - - // Allocate TBitmapInfo structure - GetMem(FDIBInfo, FDIBInfoSize); - try - FDIBInfo^.bmiHeader.biSize := SizeOf(FDIBInfo^.bmiHeader); - FDIBInfo^.bmiHeader.biWidth := Width; - FDIBInfo^.bmiHeader.biHeight := Height; - FDIBInfo^.bmiHeader.biPlanes := 1; - FDIBInfo^.bmiHeader.biSizeImage := 0; - FDIBInfo^.bmiHeader.biCompression := BI_RGB; - - if (PixelFormat = pf8bit) then - begin - FDIBInfo^.bmiHeader.biBitCount := 8; - // Find number of colors defined by palette - if (Palette <> 0) and - (GetObject(Palette, sizeof(SrcColors), @SrcColors) <> 0) and - (SrcColors <> 0) then - begin - // Copy all colors... - GetPaletteEntries(Palette, 0, SrcColors, FDIBInfo^.bmiColors[0]); - // ...and convert BGR to RGB - ByteSwapColors(FDIBInfo^.bmiColors[0], SrcColors); - end else - SrcColors := 0; - - // Finally zero any unused entried - if (SrcColors < 256) then - FillChar(pointer(LongInt(@FDIBInfo^.bmiColors)+SizeOf(TRGBQuad)*SrcColors)^, - 256 - SrcColors, 0); - FDIBInfo^.bmiHeader.biClrUsed := 256; - FDIBInfo^.bmiHeader.biClrImportant := SrcColors; - end else - begin - FDIBInfo^.bmiHeader.biBitCount := 24; - FDIBInfo^.bmiHeader.biClrUsed := 0; - FDIBInfo^.bmiHeader.biClrImportant := 0; - end; - FDIBBitsSize := AlignBit(Width, FDIBInfo^.bmiHeader.biBitCount, 32) * Cardinal(abs(Height)); - -{$ifdef CREATEDIBSECTION_SLOW} - FDIBBits := GlobalAllocPtr(GMEM_MOVEABLE, FDIBBitsSize); - if (FDIBBits = nil) then - raise EOutOfMemory.Create(sOutOfMemDIB); -{$else} -// ScreenDC := GDICheck(GetDC(0)); - try - // Allocate DIB section - // Note: You can ignore warnings about the HDC parameter being 0. The - // parameter is not used for 24 bit bitmaps - FDIB := GDICheck(CreateDIBSection(0 {ScreenDC}, FDIBInfo^, DIB_RGB_COLORS, - FDIBBits, - {$IFDEF VER9x} nil, {$ELSE} 0, {$ENDIF} - 0)); - finally -// ReleaseDC(0, ScreenDC); - end; -{$endif} - - except - FreeDIB; - raise; - end; -{$endif} -end; - -procedure TDIBWriter.FreeDIB; -begin -{$ifdef PIXELFORMAT_TOO_SLOW} - if (FDIBInfo <> nil) then - FreeMem(FDIBInfo); -{$ifdef CREATEDIBSECTION_SLOW} - if (FDIBBits <> nil) then - GlobalFreePtr(FDIBBits); -{$else} - if (FDIB <> 0) then - DeleteObject(FDIB); - FDIB := 0; -{$endif} - FDIBInfo := nil; - FDIBBits := nil; -{$endif} -end; - -procedure TDIBWriter.NeedDIB; -begin -{$ifdef PIXELFORMAT_TOO_SLOW} -{$ifdef CREATEDIBSECTION_SLOW} - if (FDIBBits = nil) then -{$else} - if (FDIB = 0) then -{$endif} - CreateDIB; -{$endif} -end; - -// Convert the DIB created by CreateDIB back to a TBitmap -procedure TDIBWriter.UpdateBitmap; -{$ifdef PIXELFORMAT_TOO_SLOW} -var - Stream : TMemoryStream; - FileSize : longInt; - BitmapFileHeader : TBitmapFileHeader; -{$endif} -begin -{$ifdef PIXELFORMAT_TOO_SLOW} - -{$ifdef CREATEDIBSECTION_SLOW} - if (FDIBBits = nil) then -{$else} - if (FDIB = 0) then -{$endif} - exit; - - // Win95 and NT differs in what solution performs best -{$ifndef CREATEDIBSECTION_SLOW} -{$ifdef VER10_PLUS} - if (Win32Platform = VER_PLATFORM_WIN32_NT) then - begin - // Assign DIB to bitmap - FBitmap.Handle := FDIB; - FDIB := 0; - FBitmap.Palette := CopyPalette(Palette); - end else -{$endif} -{$endif} - begin - // Write DIB to a stream in the BMP file format - Stream := TMemoryStream.Create; - try - // Make room in stream for a TBitmapInfo and pixel data - FileSize := sizeof(TBitmapFileHeader) + FDIBInfoSize + FDIBBitsSize; - Stream.SetSize(FileSize); - // Initialize file header - FillChar(BitmapFileHeader, sizeof(TBitmapFileHeader), 0); - with BitmapFileHeader do - begin - bfType := $4D42; // 'BM' = Windows BMP signature - bfSize := FileSize; // File size (not needed) - bfOffBits := sizeof(TBitmapFileHeader) + FDIBInfoSize; // Offset of pixel data - end; - // Save file header - Stream.Write(BitmapFileHeader, sizeof(TBitmapFileHeader)); - // Save TBitmapInfo structure - Stream.Write(FDIBInfo^, FDIBInfoSize); - // Save pixel data - Stream.Write(FDIBBits^, FDIBBitsSize); - - // Rewind and load bitmap from stream - Stream.Position := 0; - FBitmap.LoadFromStream(Stream); - finally - Stream.Free; - end; - end; -{$endif} -end; - -//////////////////////////////////////////////////////////////////////////////// -// -// Color Mapping -// -//////////////////////////////////////////////////////////////////////////////// -type - TColorLookup = class(TObject) - private - FColors : integer; - public - constructor Create(Palette: hPalette); virtual; - function Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): AnsiChar; virtual; abstract; - property Colors: integer read FColors; - end; - - PRGBQuadArray = ^TRGBQuadArray; // From Delphi 3 graphics.pas - TRGBQuadArray = array[Byte] of TRGBQuad; // From Delphi 3 graphics.pas - - BGRArray = array[0..0] of TRGBTriple; - PBGRArray = ^BGRArray; - - PalArray = array[byte] of TPaletteEntry; - PPalArray = ^PalArray; - - // TFastColorLookup implements a simple but reasonably fast generic color - // mapper. It trades precision for speed by reducing the size of the color - // space. - // Using a class instead of inline code results in a speed penalty of - // approx. 15% but reduces the complexity of the color reduction routines that - // uses it. If bitmap to GIF conversion speed is really important to you, the - // implementation can easily be inlined again. - TInverseLookup = array[0..1 SHL 15-1] of SmallInt; - PInverseLookup = ^TInverseLookup; - - TFastColorLookup = class(TColorLookup) - private - FPaletteEntries : PPalArray; - FInverseLookup : PInverseLookup; - public - constructor Create(Palette: hPalette); override; - destructor Destroy; override; - function Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): AnsiChar; override; - end; - - // TSlowColorLookup implements a precise but very slow generic color mapper. - // It uses the GetNearestPaletteIndex GDI function. - // Note: Tests has shown TFastColorLookup to be more precise than - // TSlowColorLookup in many cases. I can't explain why... - TSlowColorLookup = class(TColorLookup) - private - FPaletteEntries : PPalArray; - FPalette : hPalette; - public - constructor Create(Palette: hPalette); override; - destructor Destroy; override; - function Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): AnsiChar; override; - end; - - // TNetscapeColorLookup maps colors to the netscape 6*6*6 color cube. - TNetscapeColorLookup = class(TColorLookup) - public - constructor Create(Palette: hPalette); override; - function Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): AnsiChar; override; - end; - - // TGrayWindowsLookup maps colors to 4 shade palette. - TGrayWindowsLookup = class(TSlowColorLookup) - public - constructor Create(Palette: hPalette); override; - function Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): AnsiChar; override; - end; - - // TGrayScaleLookup maps colors to a uniform 256 shade palette. - TGrayScaleLookup = class(TColorLookup) - public - constructor Create(Palette: hPalette); override; - function Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): AnsiChar; override; - end; - - // TMonochromeLookup maps colors to a black/white palette. - TMonochromeLookup = class(TColorLookup) - public - constructor Create(Palette: hPalette); override; - function Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): AnsiChar; override; - end; - -constructor TColorLookup.Create(Palette: hPalette); -begin - inherited Create; -end; - -constructor TFastColorLookup.Create(Palette: hPalette); -var - i : integer; - InverseIndex : integer; -begin - inherited Create(Palette); - - GetMem(FPaletteEntries, sizeof(TPaletteEntry) * 256); - FColors := GetPaletteEntries(Palette, 0, 256, FPaletteEntries^); - - New(FInverseLookup); - for i := low(TInverseLookup) to high(TInverseLookup) do - FInverseLookup^[i] := -1; - - // Premap palette colors - if (FColors > 0) then - for i := 0 to FColors-1 do - with FPaletteEntries^[i] do - begin - InverseIndex := (peRed SHR 3) OR ((peGreen AND $F8) SHL 2) OR ((peBlue AND $F8) SHL 7); - if (FInverseLookup^[InverseIndex] = -1) then - FInverseLookup^[InverseIndex] := i; - end; -end; - -destructor TFastColorLookup.Destroy; -begin - if (FPaletteEntries <> nil) then - FreeMem(FPaletteEntries); - if (FInverseLookup <> nil) then - Dispose(FInverseLookup); - - inherited Destroy; -end; - -// Map color to arbitrary palette -function TFastColorLookup.Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): AnsiChar; -var - i : integer; - InverseIndex : integer; - Delta , - MinDelta , - MinColor : integer; -begin - // Reduce color space with 3 bits in each dimension - InverseIndex := (Red SHR 3) OR ((Green AND $F8) SHL 2) OR ((Blue AND $F8) SHL 7); - - if (FInverseLookup^[InverseIndex] <> -1) then - Result := AnsiChar(FInverseLookup^[InverseIndex]) - else - begin - // Sequential scan for nearest color to minimize euclidian distance - MinDelta := 3 * (256 * 256); - MinColor := 0; - for i := 0 to FColors-1 do - with FPaletteEntries[i] do - begin - Delta := ABS(peRed - Red) + ABS(peGreen - Green) + ABS(peBlue - Blue); - if (Delta < MinDelta) then - begin - MinDelta := Delta; - MinColor := i; - end; - end; - Result := AnsiChar(MinColor); - FInverseLookup^[InverseIndex] := MinColor; - end; - - with FPaletteEntries^[ord(Result)] do - begin - R := peRed; - G := peGreen; - B := peBlue; - end; -end; - -constructor TSlowColorLookup.Create(Palette: hPalette); -begin - inherited Create(Palette); - FPalette := Palette; - FColors := GetPaletteEntries(Palette, 0, 256, nil^); - if (FColors > 0) then - begin - GetMem(FPaletteEntries, sizeof(TPaletteEntry) * FColors); - FColors := GetPaletteEntries(Palette, 0, 256, FPaletteEntries^); - end; -end; - -destructor TSlowColorLookup.Destroy; -begin - if (FPaletteEntries <> nil) then - FreeMem(FPaletteEntries); - - inherited Destroy; -end; - -// Map color to arbitrary palette -function TSlowColorLookup.Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): AnsiChar; -begin - Result := AnsiChar(GetNearestPaletteIndex(FPalette, Red OR (Green SHL 8) OR (Blue SHL 16))); - if (FPaletteEntries <> nil) then - with FPaletteEntries^[ord(Result)] do - begin - R := peRed; - G := peGreen; - B := peBlue; - end; -end; - -constructor TNetscapeColorLookup.Create(Palette: hPalette); -begin - inherited Create(Palette); - FColors := 6*6*6; // This better be true or something is wrong -end; - -// Map color to netscape 6*6*6 color cube -function TNetscapeColorLookup.Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): AnsiChar; -begin - R := (Red+3) DIV 51; - G := (Green+3) DIV 51; - B := (Blue+3) DIV 51; - Result := AnsiChar(B + 6*G + 36*R); - R := R * 51; - G := G * 51; - B := B * 51; -end; - -constructor TGrayWindowsLookup.Create(Palette: hPalette); -begin - inherited Create(Palette); - FColors := 4; -end; - -// Convert color to windows grays -function TGrayWindowsLookup.Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): AnsiChar; -begin - Result := inherited Lookup(MulDiv(Red, 77, 256), - MulDiv(Green, 150, 256), MulDiv(Blue, 29, 256), R, G, B); -end; - -constructor TGrayScaleLookup.Create(Palette: hPalette); -begin - inherited Create(Palette); - FColors := 256; -end; - -// Convert color to grayscale -function TGrayScaleLookup.Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): AnsiChar; -begin - Result := AnsiChar((Blue*29 + Green*150 + Red*77) DIV 256); - R := ord(Result); - G := ord(Result); - B := ord(Result); -end; - -constructor TMonochromeLookup.Create(Palette: hPalette); -begin - inherited Create(Palette); - FColors := 2; -end; - -// Convert color to black/white -function TMonochromeLookup.Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): AnsiChar; -begin - if ((Blue*29 + Green*150 + Red*77) > 32512) then - begin - Result := #1; - R := 255; - G := 255; - B := 255; - end else - begin - Result := #0; - R := 0; - G := 0; - B := 0; - end; -end; - -//////////////////////////////////////////////////////////////////////////////// -// -// Dithering engine -// -//////////////////////////////////////////////////////////////////////////////// -type - TDitherEngine = class - private - protected - FDirection : integer; - FColumn : integer; - FLookup : TColorLookup; - Width : integer; - public - constructor Create(AWidth: integer; Lookup: TColorLookup); virtual; - function Dither(Red, Green, Blue: BYTE; var R, G, B: BYTE): AnsiChar; virtual; - procedure NextLine; virtual; - procedure NextColumn; - - property Direction: integer read FDirection; - property Column: integer read FColumn; - end; - - // Note: TErrorTerm does only *need* to be 16 bits wide, but since - // it is *much* faster to use native machine words (32 bit), we sacrifice - // some bytes (a lot actually) to improve performance. - TErrorTerm = Integer; - TErrors = array[0..0] of TErrorTerm; - PErrors = ^TErrors; - - TFloydSteinbergDitherer = class(TDitherEngine) - private - ErrorsR , - ErrorsG , - ErrorsB : PErrors; - ErrorR , - ErrorG , - ErrorB : PErrors; - CurrentErrorR , // Current error or pixel value - CurrentErrorG , - CurrentErrorB , - BelowErrorR , // Error for pixel below current - BelowErrorG , - BelowErrorB , - BelowPrevErrorR , // Error for pixel below previous pixel - BelowPrevErrorG , - BelowPrevErrorB : TErrorTerm; - public - constructor Create(AWidth: integer; Lookup: TColorLookup); override; - destructor Destroy; override; - function Dither(Red, Green, Blue: BYTE; var R, G, B: BYTE): AnsiChar; override; - procedure NextLine; override; - end; - - T5by3Ditherer = class(TDitherEngine) - private - ErrorsR0 , - ErrorsG0 , - ErrorsB0 , - ErrorsR1 , - ErrorsG1 , - ErrorsB1 , - ErrorsR2 , - ErrorsG2 , - ErrorsB2 : PErrors; - ErrorR0 , - ErrorG0 , - ErrorB0 , - ErrorR1 , - ErrorG1 , - ErrorB1 , - ErrorR2 , - ErrorG2 , - ErrorB2 : PErrors; - FDirection2 : integer; - protected - FDivisor : integer; - procedure Propagate(Errors0, Errors1, Errors2: PErrors; Error: integer); virtual; abstract; - public - constructor Create(AWidth: integer; Lookup: TColorLookup); override; - destructor Destroy; override; - function Dither(Red, Green, Blue: BYTE; var R, G, B: BYTE): AnsiChar; override; - procedure NextLine; override; - end; - - TStuckiDitherer = class(T5by3Ditherer) - protected - procedure Propagate(Errors0, Errors1, Errors2: PErrors; Error: integer); override; - public - constructor Create(AWidth: integer; Lookup: TColorLookup); override; - end; - - TSierraDitherer = class(T5by3Ditherer) - protected - procedure Propagate(Errors0, Errors1, Errors2: PErrors; Error: integer); override; - public - constructor Create(AWidth: integer; Lookup: TColorLookup); override; - end; - - TJaJuNiDitherer = class(T5by3Ditherer) - protected - procedure Propagate(Errors0, Errors1, Errors2: PErrors; Error: integer); override; - public - constructor Create(AWidth: integer; Lookup: TColorLookup); override; - end; - - TSteveArcheDitherer = class(TDitherEngine) - private - ErrorsR0 , - ErrorsG0 , - ErrorsB0 , - ErrorsR1 , - ErrorsG1 , - ErrorsB1 , - ErrorsR2 , - ErrorsG2 , - ErrorsB2 , - ErrorsR3 , - ErrorsG3 , - ErrorsB3 : PErrors; - ErrorR0 , - ErrorG0 , - ErrorB0 , - ErrorR1 , - ErrorG1 , - ErrorB1 , - ErrorR2 , - ErrorG2 , - ErrorB2 , - ErrorR3 , - ErrorG3 , - ErrorB3 : PErrors; - FDirection2 , - FDirection3 : integer; - public - constructor Create(AWidth: integer; Lookup: TColorLookup); override; - destructor Destroy; override; - function Dither(Red, Green, Blue: BYTE; var R, G, B: BYTE): AnsiChar; override; - procedure NextLine; override; - end; - - TBurkesDitherer = class(TDitherEngine) - private - ErrorsR0 , - ErrorsG0 , - ErrorsB0 , - ErrorsR1 , - ErrorsG1 , - ErrorsB1 : PErrors; - ErrorR0 , - ErrorG0 , - ErrorB0 , - ErrorR1 , - ErrorG1 , - ErrorB1 : PErrors; - FDirection2 : integer; - public - constructor Create(AWidth: integer; Lookup: TColorLookup); override; - destructor Destroy; override; - function Dither(Red, Green, Blue: BYTE; var R, G, B: BYTE): AnsiChar; override; - procedure NextLine; override; - end; - -//////////////////////////////////////////////////////////////////////////////// -// TDitherEngine -constructor TDitherEngine.Create(AWidth: integer; Lookup: TColorLookup); -begin - inherited Create; - - FLookup := Lookup; - Width := AWidth; - - FDirection := 1; - FColumn := 0; -end; - -function TDitherEngine.Dither(Red, Green, Blue: BYTE; var R, G, B: BYTE): AnsiChar; -begin - // Map color to palette - Result := FLookup.Lookup(Red, Green, Blue, R, G, B); - NextColumn; -end; - -procedure TDitherEngine.NextLine; -begin - FDirection := -FDirection; - if (FDirection = 1) then - FColumn := 0 - else - FColumn := Width-1; -end; - -procedure TDitherEngine.NextColumn; -begin - inc(FColumn, FDirection); -end; - -//////////////////////////////////////////////////////////////////////////////// -// TFloydSteinbergDitherer -constructor TFloydSteinbergDitherer.Create(AWidth: integer; Lookup: TColorLookup); -begin - inherited Create(AWidth, Lookup); - - // The Error arrays has (columns + 2) entries; the extra entry at - // each end saves us from special-casing the first and last pixels. - // We can get away with a single array (holding one row's worth of errors) - // by using it to store the current row's errors at pixel columns not yet - // processed, but the next row's errors at columns already processed. We - // need only a few extra variables to hold the errors immediately around the - // current column. (If we are lucky, those variables are in registers, but - // even if not, they're probably cheaper to access than array elements are.) - GetMem(ErrorsR, sizeof(TErrorTerm)*(Width+2)); - GetMem(ErrorsG, sizeof(TErrorTerm)*(Width+2)); - GetMem(ErrorsB, sizeof(TErrorTerm)*(Width+2)); - FillChar(ErrorsR^, sizeof(TErrorTerm)*(Width+2), 0); - FillChar(ErrorsG^, sizeof(TErrorTerm)*(Width+2), 0); - FillChar(ErrorsB^, sizeof(TErrorTerm)*(Width+2), 0); - ErrorR := ErrorsR; - ErrorG := ErrorsG; - ErrorB := ErrorsB; - CurrentErrorR := 0; - CurrentErrorG := CurrentErrorR; - CurrentErrorB := CurrentErrorR; - BelowErrorR := CurrentErrorR; - BelowErrorG := CurrentErrorR; - BelowErrorB := CurrentErrorR; - BelowPrevErrorR := CurrentErrorR; - BelowPrevErrorG := CurrentErrorR; - BelowPrevErrorB := CurrentErrorR; -end; - -destructor TFloydSteinbergDitherer.Destroy; -begin - FreeMem(ErrorsR); - FreeMem(ErrorsG); - FreeMem(ErrorsB); - inherited Destroy; -end; - -{$IFOPT R+} - {$DEFINE R_PLUS} - {$RANGECHECKS OFF} -{$ENDIF} -function TFloydSteinbergDitherer.Dither(Red, Green, Blue: BYTE; var R, G, B: BYTE): AnsiChar; -var - BelowNextError : TErrorTerm; - Delta : TErrorTerm; -begin - CurrentErrorR := Red + (CurrentErrorR + ErrorR[0] + 8) DIV 16; -// CurrentErrorR := Red + (CurrentErrorR + ErrorR[Direction] + 8) DIV 16; - if (CurrentErrorR < 0) then - CurrentErrorR := 0 - else if (CurrentErrorR > 255) then - CurrentErrorR := 255; - - CurrentErrorG := Green + (CurrentErrorG + ErrorG[0] + 8) DIV 16; -// CurrentErrorG := Green + (CurrentErrorG + ErrorG[Direction] + 8) DIV 16; - if (CurrentErrorG < 0) then - CurrentErrorG := 0 - else if (CurrentErrorG > 255) then - CurrentErrorG := 255; - - CurrentErrorB := Blue + (CurrentErrorB + ErrorB[0] + 8) DIV 16; -// CurrentErrorB := Blue + (CurrentErrorB + ErrorB[Direction] + 8) DIV 16; - if (CurrentErrorB < 0) then - CurrentErrorB := 0 - else if (CurrentErrorB > 255) then - CurrentErrorB := 255; - - // Map color to palette - Result := inherited Dither(CurrentErrorR, CurrentErrorG, CurrentErrorB, R, G, B); - - // Propagate Floyd-Steinberg error terms. - // Errors are accumulated into the error arrays, at a resolution of - // 1/16th of a pixel count. The error at a given pixel is propagated - // to its not-yet-processed neighbors using the standard F-S fractions, - // ... (here) 7/16 - // 3/16 5/16 1/16 - // We work left-to-right on even rows, right-to-left on odd rows. - - // Red component - CurrentErrorR := CurrentErrorR - R; - if (CurrentErrorR <> 0) then - begin - BelowNextError := CurrentErrorR; // Error * 1 - - Delta := CurrentErrorR * 2; - inc(CurrentErrorR, Delta); - ErrorR[0] := BelowPrevErrorR + CurrentErrorR; // Error * 3 - - inc(CurrentErrorR, Delta); - BelowPrevErrorR := BelowErrorR + CurrentErrorR; // Error * 5 - - BelowErrorR := BelowNextError; // Error * 1 - - inc(CurrentErrorR, Delta); // Error * 7 - end; - - // Green component - CurrentErrorG := CurrentErrorG - G; - if (CurrentErrorG <> 0) then - begin - BelowNextError := CurrentErrorG; // Error * 1 - - Delta := CurrentErrorG * 2; - inc(CurrentErrorG, Delta); - ErrorG[0] := BelowPrevErrorG + CurrentErrorG; // Error * 3 - - inc(CurrentErrorG, Delta); - BelowPrevErrorG := BelowErrorG + CurrentErrorG; // Error * 5 - - BelowErrorG := BelowNextError; // Error * 1 - - inc(CurrentErrorG, Delta); // Error * 7 - end; - - // Blue component - CurrentErrorB := CurrentErrorB - B; - if (CurrentErrorB <> 0) then - begin - BelowNextError := CurrentErrorB; // Error * 1 - - Delta := CurrentErrorB * 2; - inc(CurrentErrorB, Delta); - ErrorB[0] := BelowPrevErrorB + CurrentErrorB; // Error * 3 - - inc(CurrentErrorB, Delta); - BelowPrevErrorB := BelowErrorB + CurrentErrorB; // Error * 5 - - BelowErrorB := BelowNextError; // Error * 1 - - inc(CurrentErrorB, Delta); // Error * 7 - end; - - // Move on to next column - if (Direction = 1) then - begin - inc(longInt(ErrorR), sizeof(TErrorTerm)); - inc(longInt(ErrorG), sizeof(TErrorTerm)); - inc(longInt(ErrorB), sizeof(TErrorTerm)); - end else - begin - dec(longInt(ErrorR), sizeof(TErrorTerm)); - dec(longInt(ErrorG), sizeof(TErrorTerm)); - dec(longInt(ErrorB), sizeof(TErrorTerm)); - end; -end; -{$IFDEF R_PLUS} - {$RANGECHECKS ON} - {$UNDEF R_PLUS} -{$ENDIF} - -{$IFOPT R+} - {$DEFINE R_PLUS} - {$RANGECHECKS OFF} -{$ENDIF} -procedure TFloydSteinbergDitherer.NextLine; -begin - ErrorR[0] := BelowPrevErrorR; - ErrorG[0] := BelowPrevErrorG; - ErrorB[0] := BelowPrevErrorB; - - // Note: The optimizer produces better code for this construct: - // a := 0; b := a; c := a; - // compared to this construct: - // a := 0; b := 0; c := 0; - CurrentErrorR := 0; - CurrentErrorG := CurrentErrorR; - CurrentErrorB := CurrentErrorG; - BelowErrorR := CurrentErrorG; - BelowErrorG := CurrentErrorG; - BelowErrorB := CurrentErrorG; - BelowPrevErrorR := CurrentErrorG; - BelowPrevErrorG := CurrentErrorG; - BelowPrevErrorB := CurrentErrorG; - - inherited NextLine; - - if (Direction = 1) then - begin - ErrorR := ErrorsR; - ErrorG := ErrorsG; - ErrorB := ErrorsB; - end else - begin - ErrorR := @ErrorsR[Width+1]; - ErrorG := @ErrorsG[Width+1]; - ErrorB := @ErrorsB[Width+1]; - end; -end; -{$IFDEF R_PLUS} - {$RANGECHECKS ON} - {$UNDEF R_PLUS} -{$ENDIF} - -//////////////////////////////////////////////////////////////////////////////// -// T5by3Ditherer -constructor T5by3Ditherer.Create(AWidth: integer; Lookup: TColorLookup); -begin - inherited Create(AWidth, Lookup); - - GetMem(ErrorsR0, sizeof(TErrorTerm)*(Width+4)); - GetMem(ErrorsG0, sizeof(TErrorTerm)*(Width+4)); - GetMem(ErrorsB0, sizeof(TErrorTerm)*(Width+4)); - GetMem(ErrorsR1, sizeof(TErrorTerm)*(Width+4)); - GetMem(ErrorsG1, sizeof(TErrorTerm)*(Width+4)); - GetMem(ErrorsB1, sizeof(TErrorTerm)*(Width+4)); - GetMem(ErrorsR2, sizeof(TErrorTerm)*(Width+4)); - GetMem(ErrorsG2, sizeof(TErrorTerm)*(Width+4)); - GetMem(ErrorsB2, sizeof(TErrorTerm)*(Width+4)); - FillChar(ErrorsR0^, sizeof(TErrorTerm)*(Width+4), 0); - FillChar(ErrorsG0^, sizeof(TErrorTerm)*(Width+4), 0); - FillChar(ErrorsB0^, sizeof(TErrorTerm)*(Width+4), 0); - FillChar(ErrorsR1^, sizeof(TErrorTerm)*(Width+4), 0); - FillChar(ErrorsG1^, sizeof(TErrorTerm)*(Width+4), 0); - FillChar(ErrorsB1^, sizeof(TErrorTerm)*(Width+4), 0); - FillChar(ErrorsR2^, sizeof(TErrorTerm)*(Width+4), 0); - FillChar(ErrorsG2^, sizeof(TErrorTerm)*(Width+4), 0); - FillChar(ErrorsB2^, sizeof(TErrorTerm)*(Width+4), 0); - - FDivisor := 1; - FDirection2 := 2 * Direction; - ErrorR0 := PErrors(longInt(ErrorsR0)+2*sizeof(TErrorTerm)); - ErrorG0 := PErrors(longInt(ErrorsG0)+2*sizeof(TErrorTerm)); - ErrorB0 := PErrors(longInt(ErrorsB0)+2*sizeof(TErrorTerm)); - ErrorR1 := PErrors(longInt(ErrorsR1)+2*sizeof(TErrorTerm)); - ErrorG1 := PErrors(longInt(ErrorsG1)+2*sizeof(TErrorTerm)); - ErrorB1 := PErrors(longInt(ErrorsB1)+2*sizeof(TErrorTerm)); - ErrorR2 := PErrors(longInt(ErrorsR2)+2*sizeof(TErrorTerm)); - ErrorG2 := PErrors(longInt(ErrorsG2)+2*sizeof(TErrorTerm)); - ErrorB2 := PErrors(longInt(ErrorsB2)+2*sizeof(TErrorTerm)); -end; - -destructor T5by3Ditherer.Destroy; -begin - FreeMem(ErrorsR0); - FreeMem(ErrorsG0); - FreeMem(ErrorsB0); - FreeMem(ErrorsR1); - FreeMem(ErrorsG1); - FreeMem(ErrorsB1); - FreeMem(ErrorsR2); - FreeMem(ErrorsG2); - FreeMem(ErrorsB2); - inherited Destroy; -end; - -{$IFOPT R+} - {$DEFINE R_PLUS} - {$RANGECHECKS OFF} -{$ENDIF} -function T5by3Ditherer.Dither(Red, Green, Blue: BYTE; var R, G, B: BYTE): AnsiChar; -var - ColorR , - ColorG , - ColorB : integer; // Error for current pixel -begin - // Apply red component error correction - ColorR := Red + (ErrorR0[0] + FDivisor DIV 2) DIV FDivisor; - if (ColorR < 0) then - ColorR := 0 - else if (ColorR > 255) then - ColorR := 255; - - // Apply green component error correction - ColorG := Green + (ErrorG0[0] + FDivisor DIV 2) DIV FDivisor; - if (ColorG < 0) then - ColorG := 0 - else if (ColorG > 255) then - ColorG := 255; - - // Apply blue component error correction - ColorB := Blue + (ErrorB0[0] + FDivisor DIV 2) DIV FDivisor; - if (ColorB < 0) then - ColorB := 0 - else if (ColorB > 255) then - ColorB := 255; - - // Map color to palette - Result := inherited Dither(ColorR, ColorG, ColorB, R, G, B); - - // Propagate red component error - Propagate(ErrorR0, ErrorR1, ErrorR2, ColorR - R); - // Propagate green component error - Propagate(ErrorG0, ErrorG1, ErrorG2, ColorG - G); - // Propagate blue component error - Propagate(ErrorB0, ErrorB1, ErrorB2, ColorB - B); - - // Move on to next column - if (Direction = 1) then - begin - inc(longInt(ErrorR0), sizeof(TErrorTerm)); - inc(longInt(ErrorG0), sizeof(TErrorTerm)); - inc(longInt(ErrorB0), sizeof(TErrorTerm)); - inc(longInt(ErrorR1), sizeof(TErrorTerm)); - inc(longInt(ErrorG1), sizeof(TErrorTerm)); - inc(longInt(ErrorB1), sizeof(TErrorTerm)); - inc(longInt(ErrorR2), sizeof(TErrorTerm)); - inc(longInt(ErrorG2), sizeof(TErrorTerm)); - inc(longInt(ErrorB2), sizeof(TErrorTerm)); - end else - begin - dec(longInt(ErrorR0), sizeof(TErrorTerm)); - dec(longInt(ErrorG0), sizeof(TErrorTerm)); - dec(longInt(ErrorB0), sizeof(TErrorTerm)); - dec(longInt(ErrorR1), sizeof(TErrorTerm)); - dec(longInt(ErrorG1), sizeof(TErrorTerm)); - dec(longInt(ErrorB1), sizeof(TErrorTerm)); - dec(longInt(ErrorR2), sizeof(TErrorTerm)); - dec(longInt(ErrorG2), sizeof(TErrorTerm)); - dec(longInt(ErrorB2), sizeof(TErrorTerm)); - end; -end; -{$IFDEF R_PLUS} - {$RANGECHECKS ON} - {$UNDEF R_PLUS} -{$ENDIF} - -{$IFOPT R+} - {$DEFINE R_PLUS} - {$RANGECHECKS OFF} -{$ENDIF} -procedure T5by3Ditherer.NextLine; -var - TempErrors : PErrors; -begin - FillChar(ErrorsR0^, sizeof(TErrorTerm)*(Width+4), 0); - FillChar(ErrorsG0^, sizeof(TErrorTerm)*(Width+4), 0); - FillChar(ErrorsB0^, sizeof(TErrorTerm)*(Width+4), 0); - - // Swap lines - TempErrors := ErrorsR0; - ErrorsR0 := ErrorsR1; - ErrorsR1 := ErrorsR2; - ErrorsR2 := TempErrors; - - TempErrors := ErrorsG0; - ErrorsG0 := ErrorsG1; - ErrorsG1 := ErrorsG2; - ErrorsG2 := TempErrors; - - TempErrors := ErrorsB0; - ErrorsB0 := ErrorsB1; - ErrorsB1 := ErrorsB2; - ErrorsB2 := TempErrors; - - inherited NextLine; - - FDirection2 := 2 * Direction; - if (Direction = 1) then - begin - // ErrorsR0[1] gives compiler error, so we - // use PErrors(longInt(ErrorsR0)+sizeof(TErrorTerm)) instead... - ErrorR0 := PErrors(longInt(ErrorsR0)+2*sizeof(TErrorTerm)); - ErrorG0 := PErrors(longInt(ErrorsG0)+2*sizeof(TErrorTerm)); - ErrorB0 := PErrors(longInt(ErrorsB0)+2*sizeof(TErrorTerm)); - ErrorR1 := PErrors(longInt(ErrorsR1)+2*sizeof(TErrorTerm)); - ErrorG1 := PErrors(longInt(ErrorsG1)+2*sizeof(TErrorTerm)); - ErrorB1 := PErrors(longInt(ErrorsB1)+2*sizeof(TErrorTerm)); - ErrorR2 := PErrors(longInt(ErrorsR2)+2*sizeof(TErrorTerm)); - ErrorG2 := PErrors(longInt(ErrorsG2)+2*sizeof(TErrorTerm)); - ErrorB2 := PErrors(longInt(ErrorsB2)+2*sizeof(TErrorTerm)); - end else - begin - ErrorR0 := @ErrorsR0[Width+1]; - ErrorG0 := @ErrorsG0[Width+1]; - ErrorB0 := @ErrorsB0[Width+1]; - ErrorR1 := @ErrorsR1[Width+1]; - ErrorG1 := @ErrorsG1[Width+1]; - ErrorB1 := @ErrorsB1[Width+1]; - ErrorR2 := @ErrorsR2[Width+1]; - ErrorG2 := @ErrorsG2[Width+1]; - ErrorB2 := @ErrorsB2[Width+1]; - end; -end; -{$IFDEF R_PLUS} - {$RANGECHECKS ON} - {$UNDEF R_PLUS} -{$ENDIF} - -//////////////////////////////////////////////////////////////////////////////// -// TStuckiDitherer -constructor TStuckiDitherer.Create(AWidth: integer; Lookup: TColorLookup); -begin - inherited Create(AWidth, Lookup); - FDivisor := 42; -end; - -{$IFOPT R+} - {$DEFINE R_PLUS} - {$RANGECHECKS OFF} -{$ENDIF} -procedure TStuckiDitherer.Propagate(Errors0, Errors1, Errors2: PErrors; Error: integer); -begin - if (Error = 0) then - exit; - // Propagate Stucki error terms: - // ... ... (here) 8/42 4/42 - // 2/42 4/42 8/42 4/42 2/42 - // 1/42 2/42 4/42 2/42 1/42 - inc(Errors2[FDirection2], Error); // Error * 1 - inc(Errors2[-FDirection2], Error); // Error * 1 - - Error := Error + Error; - inc(Errors1[FDirection2], Error); // Error * 2 - inc(Errors1[-FDirection2], Error); // Error * 2 - inc(Errors2[Direction], Error); // Error * 2 - inc(Errors2[-Direction], Error); // Error * 2 - - Error := Error + Error; - inc(Errors0[FDirection2], Error); // Error * 4 - inc(Errors1[-Direction], Error); // Error * 4 - inc(Errors1[Direction], Error); // Error * 4 - inc(Errors2[0], Error); // Error * 4 - - Error := Error + Error; - inc(Errors0[Direction], Error); // Error * 8 - inc(Errors1[0], Error); // Error * 8 -end; -{$IFDEF R_PLUS} - {$RANGECHECKS ON} - {$UNDEF R_PLUS} -{$ENDIF} - -//////////////////////////////////////////////////////////////////////////////// -// TSierraDitherer -constructor TSierraDitherer.Create(AWidth: integer; Lookup: TColorLookup); -begin - inherited Create(AWidth, Lookup); - FDivisor := 32; -end; - -{$IFOPT R+} - {$DEFINE R_PLUS} - {$RANGECHECKS OFF} -{$ENDIF} -procedure TSierraDitherer.Propagate(Errors0, Errors1, Errors2: PErrors; Error: integer); -var - TempError : integer; -begin - if (Error = 0) then - exit; - // Propagate Sierra error terms: - // ... ... (here) 5/32 3/32 - // 2/32 4/32 5/32 4/32 2/32 - // ... 2/32 3/32 2/32 ... - TempError := Error + Error; - inc(Errors1[FDirection2], TempError); // Error * 2 - inc(Errors1[-FDirection2], TempError);// Error * 2 - inc(Errors2[Direction], TempError); // Error * 2 - inc(Errors2[-Direction], TempError); // Error * 2 - - inc(TempError, Error); - inc(Errors0[FDirection2], TempError); // Error * 3 - inc(Errors2[0], TempError); // Error * 3 - - inc(TempError, Error); - inc(Errors1[-Direction], TempError); // Error * 4 - inc(Errors1[Direction], TempError); // Error * 4 - - inc(TempError, Error); - inc(Errors0[Direction], TempError); // Error * 5 - inc(Errors1[0], TempError); // Error * 5 -end; -{$IFDEF R_PLUS} - {$RANGECHECKS ON} - {$UNDEF R_PLUS} -{$ENDIF} - -//////////////////////////////////////////////////////////////////////////////// -// TJaJuNiDitherer -constructor TJaJuNiDitherer.Create(AWidth: integer; Lookup: TColorLookup); -begin - inherited Create(AWidth, Lookup); - FDivisor := 38; -end; - -{$IFOPT R+} - {$DEFINE R_PLUS} - {$RANGECHECKS OFF} -{$ENDIF} -procedure TJaJuNiDitherer.Propagate(Errors0, Errors1, Errors2: PErrors; Error: integer); -var - TempError : integer; -begin - if (Error = 0) then - exit; - // Propagate Jarvis, Judice and Ninke error terms: - // ... ... (here) 8/38 4/38 - // 2/38 4/38 8/38 4/38 2/38 - // 1/38 2/38 4/38 2/38 1/38 - inc(Errors2[FDirection2], Error); // Error * 1 - inc(Errors2[-FDirection2], Error); // Error * 1 - - TempError := Error + Error; - inc(Error, TempError); - inc(Errors1[FDirection2], Error); // Error * 3 - inc(Errors1[-FDirection2], Error); // Error * 3 - inc(Errors2[Direction], Error); // Error * 3 - inc(Errors2[-Direction], Error); // Error * 3 - - inc(Error, TempError); - inc(Errors0[FDirection2], Error); // Error * 5 - inc(Errors1[-Direction], Error); // Error * 5 - inc(Errors1[Direction], Error); // Error * 5 - inc(Errors2[0], Error); // Error * 5 - - inc(Error, TempError); - inc(Errors0[Direction], Error); // Error * 7 - inc(Errors1[0], Error); // Error * 7 -end; -{$IFDEF R_PLUS} - {$RANGECHECKS ON} - {$UNDEF R_PLUS} -{$ENDIF} - -//////////////////////////////////////////////////////////////////////////////// -// TSteveArcheDitherer -constructor TSteveArcheDitherer.Create(AWidth: integer; Lookup: TColorLookup); -begin - inherited Create(AWidth, Lookup); - - GetMem(ErrorsR0, sizeof(TErrorTerm)*(Width+6)); - GetMem(ErrorsG0, sizeof(TErrorTerm)*(Width+6)); - GetMem(ErrorsB0, sizeof(TErrorTerm)*(Width+6)); - GetMem(ErrorsR1, sizeof(TErrorTerm)*(Width+6)); - GetMem(ErrorsG1, sizeof(TErrorTerm)*(Width+6)); - GetMem(ErrorsB1, sizeof(TErrorTerm)*(Width+6)); - GetMem(ErrorsR2, sizeof(TErrorTerm)*(Width+6)); - GetMem(ErrorsG2, sizeof(TErrorTerm)*(Width+6)); - GetMem(ErrorsB2, sizeof(TErrorTerm)*(Width+6)); - GetMem(ErrorsR3, sizeof(TErrorTerm)*(Width+6)); - GetMem(ErrorsG3, sizeof(TErrorTerm)*(Width+6)); - GetMem(ErrorsB3, sizeof(TErrorTerm)*(Width+6)); - FillChar(ErrorsR0^, sizeof(TErrorTerm)*(Width+6), 0); - FillChar(ErrorsG0^, sizeof(TErrorTerm)*(Width+6), 0); - FillChar(ErrorsB0^, sizeof(TErrorTerm)*(Width+6), 0); - FillChar(ErrorsR1^, sizeof(TErrorTerm)*(Width+6), 0); - FillChar(ErrorsG1^, sizeof(TErrorTerm)*(Width+6), 0); - FillChar(ErrorsB1^, sizeof(TErrorTerm)*(Width+6), 0); - FillChar(ErrorsR2^, sizeof(TErrorTerm)*(Width+6), 0); - FillChar(ErrorsG2^, sizeof(TErrorTerm)*(Width+6), 0); - FillChar(ErrorsB2^, sizeof(TErrorTerm)*(Width+6), 0); - FillChar(ErrorsR3^, sizeof(TErrorTerm)*(Width+6), 0); - FillChar(ErrorsG3^, sizeof(TErrorTerm)*(Width+6), 0); - FillChar(ErrorsB3^, sizeof(TErrorTerm)*(Width+6), 0); - - FDirection2 := 2 * Direction; - FDirection3 := 3 * Direction; - - ErrorR0 := PErrors(longInt(ErrorsR0)+3*sizeof(TErrorTerm)); - ErrorG0 := PErrors(longInt(ErrorsG0)+3*sizeof(TErrorTerm)); - ErrorB0 := PErrors(longInt(ErrorsB0)+3*sizeof(TErrorTerm)); - ErrorR1 := PErrors(longInt(ErrorsR1)+3*sizeof(TErrorTerm)); - ErrorG1 := PErrors(longInt(ErrorsG1)+3*sizeof(TErrorTerm)); - ErrorB1 := PErrors(longInt(ErrorsB1)+3*sizeof(TErrorTerm)); - ErrorR2 := PErrors(longInt(ErrorsR2)+3*sizeof(TErrorTerm)); - ErrorG2 := PErrors(longInt(ErrorsG2)+3*sizeof(TErrorTerm)); - ErrorB2 := PErrors(longInt(ErrorsB2)+3*sizeof(TErrorTerm)); - ErrorR3 := PErrors(longInt(ErrorsR3)+3*sizeof(TErrorTerm)); - ErrorG3 := PErrors(longInt(ErrorsG3)+3*sizeof(TErrorTerm)); - ErrorB3 := PErrors(longInt(ErrorsB3)+3*sizeof(TErrorTerm)); -end; - -destructor TSteveArcheDitherer.Destroy; -begin - FreeMem(ErrorsR0); - FreeMem(ErrorsG0); - FreeMem(ErrorsB0); - FreeMem(ErrorsR1); - FreeMem(ErrorsG1); - FreeMem(ErrorsB1); - FreeMem(ErrorsR2); - FreeMem(ErrorsG2); - FreeMem(ErrorsB2); - FreeMem(ErrorsR3); - FreeMem(ErrorsG3); - FreeMem(ErrorsB3); - inherited Destroy; -end; - -{$IFOPT R+} - {$DEFINE R_PLUS} - {$RANGECHECKS OFF} -{$ENDIF} -function TSteveArcheDitherer.Dither(Red, Green, Blue: BYTE; var R, G, B: BYTE): AnsiChar; -var - ColorR , - ColorG , - ColorB : integer; // Error for current pixel - - // Propagate Stevenson & Arche error terms: - // ... ... ... (here) ... 32/200 ... - // 12/200 ... 26/200 ... 30/200 ... 16/200 - // ... 12/200 ... 26/200 ... 12/200 ... - // 5/200 ... 12/200 ... 12/200 ... 5/200 - procedure Propagate(Errors0, Errors1, Errors2, Errors3: PErrors; Error: integer); - var - TempError : integer; - begin - if (Error = 0) then - exit; - TempError := 5 * Error; - inc(Errors3[FDirection3], TempError); // Error * 5 - inc(Errors3[-FDirection3], TempError); // Error * 5 - - TempError := 12 * Error; - inc(Errors1[-FDirection3], TempError); // Error * 12 - inc(Errors2[-FDirection2], TempError); // Error * 12 - inc(Errors2[FDirection2], TempError); // Error * 12 - inc(Errors3[-Direction], TempError); // Error * 12 - inc(Errors3[Direction], TempError); // Error * 12 - - inc(Errors1[FDirection3], 16 * TempError); // Error * 16 - - TempError := 26 * Error; - inc(Errors1[-Direction], TempError); // Error * 26 - inc(Errors2[0], TempError); // Error * 26 - - inc(Errors1[Direction], 30 * Error); // Error * 30 - - inc(Errors0[FDirection2], 32 * Error); // Error * 32 - end; - -begin - // Apply red component error correction - ColorR := Red + (ErrorR0[0] + 100) DIV 200; - if (ColorR < 0) then - ColorR := 0 - else if (ColorR > 255) then - ColorR := 255; - - // Apply green component error correction - ColorG := Green + (ErrorG0[0] + 100) DIV 200; - if (ColorG < 0) then - ColorG := 0 - else if (ColorG > 255) then - ColorG := 255; - - // Apply blue component error correction - ColorB := Blue + (ErrorB0[0] + 100) DIV 200; - if (ColorB < 0) then - ColorB := 0 - else if (ColorB > 255) then - ColorB := 255; - - // Map color to palette - Result := inherited Dither(ColorR, ColorG, ColorB, R, G, B); - - // Propagate red component error - Propagate(ErrorR0, ErrorR1, ErrorR2, ErrorR3, ColorR - R); - // Propagate green component error - Propagate(ErrorG0, ErrorG1, ErrorG2, ErrorG3, ColorG - G); - // Propagate blue component error - Propagate(ErrorB0, ErrorB1, ErrorB2, ErrorB3, ColorB - B); - - // Move on to next column - if (Direction = 1) then - begin - inc(longInt(ErrorR0), sizeof(TErrorTerm)); - inc(longInt(ErrorG0), sizeof(TErrorTerm)); - inc(longInt(ErrorB0), sizeof(TErrorTerm)); - inc(longInt(ErrorR1), sizeof(TErrorTerm)); - inc(longInt(ErrorG1), sizeof(TErrorTerm)); - inc(longInt(ErrorB1), sizeof(TErrorTerm)); - inc(longInt(ErrorR2), sizeof(TErrorTerm)); - inc(longInt(ErrorG2), sizeof(TErrorTerm)); - inc(longInt(ErrorB2), sizeof(TErrorTerm)); - inc(longInt(ErrorR3), sizeof(TErrorTerm)); - inc(longInt(ErrorG3), sizeof(TErrorTerm)); - inc(longInt(ErrorB3), sizeof(TErrorTerm)); - end else - begin - dec(longInt(ErrorR0), sizeof(TErrorTerm)); - dec(longInt(ErrorG0), sizeof(TErrorTerm)); - dec(longInt(ErrorB0), sizeof(TErrorTerm)); - dec(longInt(ErrorR1), sizeof(TErrorTerm)); - dec(longInt(ErrorG1), sizeof(TErrorTerm)); - dec(longInt(ErrorB1), sizeof(TErrorTerm)); - dec(longInt(ErrorR2), sizeof(TErrorTerm)); - dec(longInt(ErrorG2), sizeof(TErrorTerm)); - dec(longInt(ErrorB2), sizeof(TErrorTerm)); - dec(longInt(ErrorR3), sizeof(TErrorTerm)); - dec(longInt(ErrorG3), sizeof(TErrorTerm)); - dec(longInt(ErrorB3), sizeof(TErrorTerm)); - end; -end; -{$IFDEF R_PLUS} - {$RANGECHECKS ON} - {$UNDEF R_PLUS} -{$ENDIF} - -{$IFOPT R+} - {$DEFINE R_PLUS} - {$RANGECHECKS OFF} -{$ENDIF} -procedure TSteveArcheDitherer.NextLine; -var - TempErrors : PErrors; -begin - FillChar(ErrorsR0^, sizeof(TErrorTerm)*(Width+6), 0); - FillChar(ErrorsG0^, sizeof(TErrorTerm)*(Width+6), 0); - FillChar(ErrorsB0^, sizeof(TErrorTerm)*(Width+6), 0); - - // Swap lines - TempErrors := ErrorsR0; - ErrorsR0 := ErrorsR1; - ErrorsR1 := ErrorsR2; - ErrorsR2 := ErrorsR3; - ErrorsR3 := TempErrors; - - TempErrors := ErrorsG0; - ErrorsG0 := ErrorsG1; - ErrorsG1 := ErrorsG2; - ErrorsG2 := ErrorsG3; - ErrorsG3 := TempErrors; - - TempErrors := ErrorsB0; - ErrorsB0 := ErrorsB1; - ErrorsB1 := ErrorsB2; - ErrorsB2 := ErrorsB3; - ErrorsB3 := TempErrors; - - inherited NextLine; - - FDirection2 := 2 * Direction; - FDirection3 := 3 * Direction; - - if (Direction = 1) then - begin - // ErrorsR0[1] gives compiler error, so we - // use PErrors(longInt(ErrorsR0)+sizeof(TErrorTerm)) instead... - ErrorR0 := PErrors(longInt(ErrorsR0)+3*sizeof(TErrorTerm)); - ErrorG0 := PErrors(longInt(ErrorsG0)+3*sizeof(TErrorTerm)); - ErrorB0 := PErrors(longInt(ErrorsB0)+3*sizeof(TErrorTerm)); - ErrorR1 := PErrors(longInt(ErrorsR1)+3*sizeof(TErrorTerm)); - ErrorG1 := PErrors(longInt(ErrorsG1)+3*sizeof(TErrorTerm)); - ErrorB1 := PErrors(longInt(ErrorsB1)+3*sizeof(TErrorTerm)); - ErrorR2 := PErrors(longInt(ErrorsR2)+3*sizeof(TErrorTerm)); - ErrorG2 := PErrors(longInt(ErrorsG2)+3*sizeof(TErrorTerm)); - ErrorB2 := PErrors(longInt(ErrorsB2)+3*sizeof(TErrorTerm)); - ErrorR3 := PErrors(longInt(ErrorsR3)+3*sizeof(TErrorTerm)); - ErrorG3 := PErrors(longInt(ErrorsG3)+3*sizeof(TErrorTerm)); - ErrorB3 := PErrors(longInt(ErrorsB3)+3*sizeof(TErrorTerm)); - end else - begin - ErrorR0 := @ErrorsR0[Width+2]; - ErrorG0 := @ErrorsG0[Width+2]; - ErrorB0 := @ErrorsB0[Width+2]; - ErrorR1 := @ErrorsR1[Width+2]; - ErrorG1 := @ErrorsG1[Width+2]; - ErrorB1 := @ErrorsB1[Width+2]; - ErrorR2 := @ErrorsR2[Width+2]; - ErrorG2 := @ErrorsG2[Width+2]; - ErrorB2 := @ErrorsB2[Width+2]; - ErrorR3 := @ErrorsR2[Width+2]; - ErrorG3 := @ErrorsG2[Width+2]; - ErrorB3 := @ErrorsB2[Width+2]; - end; -end; -{$IFDEF R_PLUS} - {$RANGECHECKS ON} - {$UNDEF R_PLUS} -{$ENDIF} - -//////////////////////////////////////////////////////////////////////////////// -// TBurkesDitherer -constructor TBurkesDitherer.Create(AWidth: integer; Lookup: TColorLookup); -begin - inherited Create(AWidth, Lookup); - - GetMem(ErrorsR0, sizeof(TErrorTerm)*(Width+4)); - GetMem(ErrorsG0, sizeof(TErrorTerm)*(Width+4)); - GetMem(ErrorsB0, sizeof(TErrorTerm)*(Width+4)); - GetMem(ErrorsR1, sizeof(TErrorTerm)*(Width+4)); - GetMem(ErrorsG1, sizeof(TErrorTerm)*(Width+4)); - GetMem(ErrorsB1, sizeof(TErrorTerm)*(Width+4)); - FillChar(ErrorsR0^, sizeof(TErrorTerm)*(Width+4), 0); - FillChar(ErrorsG0^, sizeof(TErrorTerm)*(Width+4), 0); - FillChar(ErrorsB0^, sizeof(TErrorTerm)*(Width+4), 0); - FillChar(ErrorsR1^, sizeof(TErrorTerm)*(Width+4), 0); - FillChar(ErrorsG1^, sizeof(TErrorTerm)*(Width+4), 0); - FillChar(ErrorsB1^, sizeof(TErrorTerm)*(Width+4), 0); - - FDirection2 := 2 * Direction; - ErrorR0 := PErrors(longInt(ErrorsR0)+2*sizeof(TErrorTerm)); - ErrorG0 := PErrors(longInt(ErrorsG0)+2*sizeof(TErrorTerm)); - ErrorB0 := PErrors(longInt(ErrorsB0)+2*sizeof(TErrorTerm)); - ErrorR1 := PErrors(longInt(ErrorsR1)+2*sizeof(TErrorTerm)); - ErrorG1 := PErrors(longInt(ErrorsG1)+2*sizeof(TErrorTerm)); - ErrorB1 := PErrors(longInt(ErrorsB1)+2*sizeof(TErrorTerm)); -end; - -destructor TBurkesDitherer.Destroy; -begin - FreeMem(ErrorsR0); - FreeMem(ErrorsG0); - FreeMem(ErrorsB0); - FreeMem(ErrorsR1); - FreeMem(ErrorsG1); - FreeMem(ErrorsB1); - inherited Destroy; -end; - -{$IFOPT R+} - {$DEFINE R_PLUS} - {$RANGECHECKS OFF} -{$ENDIF} -function TBurkesDitherer.Dither(Red, Green, Blue: BYTE; var R, G, B: BYTE): AnsiChar; -var - ErrorR , - ErrorG , - ErrorB : integer; // Error for current pixel - - // Propagate Burkes error terms: - // ... ... (here) 8/32 4/32 - // 2/32 4/32 8/32 4/32 2/32 - procedure Propagate(Errors0, Errors1: PErrors; Error: integer); - begin - if (Error = 0) then - exit; - inc(Error, Error); - inc(Errors1[FDirection2], Error); // Error * 2 - inc(Errors1[-FDirection2], Error); // Error * 2 - - inc(Error, Error); - inc(Errors0[FDirection2], Error); // Error * 4 - inc(Errors1[-Direction], Error); // Error * 4 - inc(Errors1[Direction], Error); // Error * 4 - - inc(Error, Error); - inc(Errors0[Direction], Error); // Error * 8 - inc(Errors1[0], Error); // Error * 8 - end; - -begin - // Apply red component error correction - ErrorR := Red + (ErrorR0[0] + 16) DIV 32; - if (ErrorR < 0) then - ErrorR := 0 - else if (ErrorR > 255) then - ErrorR := 255; - - // Apply green component error correction - ErrorG := Green + (ErrorG0[0] + 16) DIV 32; - if (ErrorG < 0) then - ErrorG := 0 - else if (ErrorG > 255) then - ErrorG := 255; - - // Apply blue component error correction - ErrorB := Blue + (ErrorB0[0] + 16) DIV 32; - if (ErrorB < 0) then - ErrorB := 0 - else if (ErrorB > 255) then - ErrorB := 255; - - // Map color to palette - Result := inherited Dither(ErrorR, ErrorG, ErrorB, R, G, B); - - // Propagate red component error - Propagate(ErrorR0, ErrorR1, ErrorR - R); - // Propagate green component error - Propagate(ErrorG0, ErrorG1, ErrorG - G); - // Propagate blue component error - Propagate(ErrorB0, ErrorB1, ErrorB - B); - - // Move on to next column - if (Direction = 1) then - begin - inc(longInt(ErrorR0), sizeof(TErrorTerm)); - inc(longInt(ErrorG0), sizeof(TErrorTerm)); - inc(longInt(ErrorB0), sizeof(TErrorTerm)); - inc(longInt(ErrorR1), sizeof(TErrorTerm)); - inc(longInt(ErrorG1), sizeof(TErrorTerm)); - inc(longInt(ErrorB1), sizeof(TErrorTerm)); - end else - begin - dec(longInt(ErrorR0), sizeof(TErrorTerm)); - dec(longInt(ErrorG0), sizeof(TErrorTerm)); - dec(longInt(ErrorB0), sizeof(TErrorTerm)); - dec(longInt(ErrorR1), sizeof(TErrorTerm)); - dec(longInt(ErrorG1), sizeof(TErrorTerm)); - dec(longInt(ErrorB1), sizeof(TErrorTerm)); - end; -end; -{$IFDEF R_PLUS} - {$RANGECHECKS ON} - {$UNDEF R_PLUS} -{$ENDIF} - -{$IFOPT R+} - {$DEFINE R_PLUS} - {$RANGECHECKS OFF} -{$ENDIF} -procedure TBurkesDitherer.NextLine; -var - TempErrors : PErrors; -begin - FillChar(ErrorsR0^, sizeof(TErrorTerm)*(Width+4), 0); - FillChar(ErrorsG0^, sizeof(TErrorTerm)*(Width+4), 0); - FillChar(ErrorsB0^, sizeof(TErrorTerm)*(Width+4), 0); - - // Swap lines - TempErrors := ErrorsR0; - ErrorsR0 := ErrorsR1; - ErrorsR1 := TempErrors; - - TempErrors := ErrorsG0; - ErrorsG0 := ErrorsG1; - ErrorsG1 := TempErrors; - - TempErrors := ErrorsB0; - ErrorsB0 := ErrorsB1; - ErrorsB1 := TempErrors; - - inherited NextLine; - - FDirection2 := 2 * Direction; - if (Direction = 1) then - begin - // ErrorsR0[1] gives compiler error, so we - // use PErrors(longInt(ErrorsR0)+sizeof(TErrorTerm)) instead... - ErrorR0 := PErrors(longInt(ErrorsR0)+2*sizeof(TErrorTerm)); - ErrorG0 := PErrors(longInt(ErrorsG0)+2*sizeof(TErrorTerm)); - ErrorB0 := PErrors(longInt(ErrorsB0)+2*sizeof(TErrorTerm)); - ErrorR1 := PErrors(longInt(ErrorsR1)+2*sizeof(TErrorTerm)); - ErrorG1 := PErrors(longInt(ErrorsG1)+2*sizeof(TErrorTerm)); - ErrorB1 := PErrors(longInt(ErrorsB1)+2*sizeof(TErrorTerm)); - end else - begin - ErrorR0 := @ErrorsR0[Width+1]; - ErrorG0 := @ErrorsG0[Width+1]; - ErrorB0 := @ErrorsB0[Width+1]; - ErrorR1 := @ErrorsR1[Width+1]; - ErrorG1 := @ErrorsG1[Width+1]; - ErrorB1 := @ErrorsB1[Width+1]; - end; -end; -{$IFDEF R_PLUS} - {$RANGECHECKS ON} - {$UNDEF R_PLUS} -{$ENDIF} - -//////////////////////////////////////////////////////////////////////////////// -// -// Octree Color Quantization Engine -// -//////////////////////////////////////////////////////////////////////////////// -// Adapted from Earl F. Glynn's ColorQuantizationLibrary, March 1998 -//////////////////////////////////////////////////////////////////////////////// -type - TOctreeNode = class; // Forward definition so TReducibleNodes can be declared - - TReducibleNodes = array[0..7] of TOctreeNode; - - TOctreeNode = Class(TObject) - public - IsLeaf : Boolean; - PixelCount : integer; - RedSum : integer; - GreenSum : integer; - BlueSum : integer; - Next : TOctreeNode; - Child : TReducibleNodes; - - constructor Create(Level: integer; ColorBits: integer; var LeafCount: integer; - var ReducibleNodes: TReducibleNodes); - destructor Destroy; override; - end; - - TColorQuantizer = class(TObject) - private - FTree : TOctreeNode; - FLeafCount : integer; - FReducibleNodes : TReducibleNodes; - FMaxColors : integer; - FColorBits : integer; - - protected - procedure AddColor(var Node: TOctreeNode; r, g, b: byte; ColorBits: integer; - Level: integer; var LeafCount: integer; var ReducibleNodes: TReducibleNodes); - procedure DeleteTree(var Node: TOctreeNode); - procedure GetPaletteColors(const Node: TOctreeNode; - var RGBQuadArray: TRGBQuadArray; var Index: integer); - procedure ReduceTree(ColorBits: integer; var LeafCount: integer; - var ReducibleNodes: TReducibleNodes); - - public - constructor Create(MaxColors: integer; ColorBits: integer); - destructor Destroy; override; - - procedure GetColorTable(var RGBQuadArray: TRGBQuadArray); - function ProcessImage(const DIB: TDIBReader): boolean; - - property ColorCount: integer read FLeafCount; - end; - -constructor TOctreeNode.Create(Level: integer; ColorBits: integer; - var LeafCount: integer; var ReducibleNodes: TReducibleNodes); -var - i : integer; -begin - PixelCount := 0; - RedSum := 0; - GreenSum := 0; - BlueSum := 0; - for i := Low(Child) to High(Child) do - Child[i] := nil; - - IsLeaf := (Level = ColorBits); - if (IsLeaf) then - begin - Next := nil; - inc(LeafCount); - end else - begin - Next := ReducibleNodes[Level]; - ReducibleNodes[Level] := self; - end; -end; - -destructor TOctreeNode.Destroy; -var - i : integer; -begin - for i := High(Child) downto Low(Child) do - Child[i].Free; -end; - -constructor TColorQuantizer.Create(MaxColors: integer; ColorBits: integer); -var - i : integer; -begin - ASSERT(ColorBits <= 8, 'ColorBits must be 8 or less'); - - FTree := nil; - FLeafCount := 0; - - // Initialize all nodes even though only ColorBits+1 of them are needed - for i := Low(FReducibleNodes) to High(FReducibleNodes) do - FReducibleNodes[i] := nil; - - FMaxColors := MaxColors; - FColorBits := ColorBits; -end; - -destructor TColorQuantizer.Destroy; -begin - if (FTree <> nil) then - DeleteTree(FTree); -end; - -procedure TColorQuantizer.GetColorTable(var RGBQuadArray: TRGBQuadArray); -var - Index : integer; -begin - Index := 0; - GetPaletteColors(FTree, RGBQuadArray, Index); -end; - -// Handles passed to ProcessImage should refer to DIB sections, not DDBs. -// In certain cases, specifically when it's called upon to process 1, 4, or -// 8-bit per pixel images on systems with palettized display adapters, -// ProcessImage can produce incorrect results if it's passed a handle to a -// DDB. -function TColorQuantizer.ProcessImage(const DIB: TDIBReader): boolean; -var - i , - j : integer; - ScanLine : pointer; - Pixel : PRGBTriple; -begin - Result := True; - - for j := 0 to DIB.Bitmap.Height-1 do - begin - Scanline := DIB.Scanline[j]; - Pixel := ScanLine; - for i := 0 to DIB.Bitmap.Width-1 do - begin - with Pixel^ do - AddColor(FTree, rgbtRed, rgbtGreen, rgbtBlue, - FColorBits, 0, FLeafCount, FReducibleNodes); - - while FLeafCount > FMaxColors do - ReduceTree(FColorbits, FLeafCount, FReducibleNodes); - inc(Pixel); - end; - end; -end; - -procedure TColorQuantizer.AddColor(var Node: TOctreeNode; r,g,b: byte; - ColorBits: integer; Level: integer; var LeafCount: integer; - var ReducibleNodes: TReducibleNodes); -const - Mask: array[0..7] of BYTE = ($80, $40, $20, $10, $08, $04, $02, $01); -var - Index : integer; - Shift : integer; -begin - // If the node doesn't exist, create it. - if (Node = nil) then - Node := TOctreeNode.Create(Level, ColorBits, LeafCount, ReducibleNodes); - - if (Node.IsLeaf) then - begin - inc(Node.PixelCount); - inc(Node.RedSum, r); - inc(Node.GreenSum, g); - inc(Node.BlueSum, b); - end else - begin - // Recurse a level deeper if the node is not a leaf. - Shift := 7 - Level; - - Index := (((r and mask[Level]) SHR Shift) SHL 2) or - (((g and mask[Level]) SHR Shift) SHL 1) or - ((b and mask[Level]) SHR Shift); - AddColor(Node.Child[Index], r, g, b, ColorBits, Level+1, LeafCount, ReducibleNodes); - end; -end; - -procedure TColorQuantizer.DeleteTree(var Node: TOctreeNode); -var - i : integer; -begin - for i := High(TReducibleNodes) downto Low(TReducibleNodes) do - if (Node.Child[i] <> nil) then - DeleteTree(Node.Child[i]); - - Node.Free; - Node := nil; -end; - -procedure TColorQuantizer.GetPaletteColors(const Node: TOctreeNode; - var RGBQuadArray: TRGBQuadArray; var Index: integer); -var - i : integer; -begin - if (Node.IsLeaf) then - begin - with RGBQuadArray[Index] do - begin - if (Node.PixelCount <> 0) then - begin - rgbRed := BYTE(Node.RedSum DIV Node.PixelCount); - rgbGreen := BYTE(Node.GreenSum DIV Node.PixelCount); - rgbBlue := BYTE(Node.BlueSum DIV Node.PixelCount); - end else - begin - rgbRed := 0; - rgbGreen := 0; - rgbBlue := 0; - end; - rgbReserved := 0; - end; - inc(Index); - end else - begin - for i := Low(Node.Child) to High(Node.Child) do - if (Node.Child[i] <> nil) then - GetPaletteColors(Node.Child[i], RGBQuadArray, Index); - end; -end; - -procedure TColorQuantizer.ReduceTree(ColorBits: integer; var LeafCount: integer; - var ReducibleNodes: TReducibleNodes); -var - RedSum , - GreenSum , - BlueSum : integer; - Children : integer; - i : integer; - Node : TOctreeNode; -begin - // Find the deepest level containing at least one reducible node - i := Colorbits - 1; - while (i > 0) and (ReducibleNodes[i] = nil) do - dec(i); - - // Reduce the node most recently added to the list at level i. - Node := ReducibleNodes[i]; - ReducibleNodes[i] := Node.Next; - - RedSum := 0; - GreenSum := 0; - BlueSum := 0; - Children := 0; - - for i := Low(ReducibleNodes) to High(ReducibleNodes) do - if (Node.Child[i] <> nil) then - begin - inc(RedSum, Node.Child[i].RedSum); - inc(GreenSum, Node.Child[i].GreenSum); - inc(BlueSum, Node.Child[i].BlueSum); - inc(Node.PixelCount, Node.Child[i].PixelCount); - Node.Child[i].Free; - Node.Child[i] := nil; - inc(Children); - end; - - Node.IsLeaf := TRUE; - Node.RedSum := RedSum; - Node.GreenSum := GreenSum; - Node.BlueSum := BlueSum; - dec(LeafCount, Children-1); -end; - -//////////////////////////////////////////////////////////////////////////////// -// -// Octree Color Quantization Wrapper -// -//////////////////////////////////////////////////////////////////////////////// -// Adapted from Earl F. Glynn's PaletteLibrary, March 1998 -//////////////////////////////////////////////////////////////////////////////// - -// Wrapper for internal use - uses TDIBReader for bitmap access -function doCreateOptimizedPaletteFromSingleBitmap(const DIB: TDIBReader; - Colors, ColorBits: integer; Windows: boolean): hPalette; -var - SystemPalette : HPalette; - ColorQuantizer : TColorQuantizer; - i : integer; - LogicalPalette : TMaxLogPalette; - RGBQuadArray : TRGBQuadArray; - Offset : integer; -begin - LogicalPalette.palVersion := $0300; - LogicalPalette.palNumEntries := Colors; -// 2003.03.06 -> - {reset palette to black} - FillChar(LogicalPalette.palPalEntry, SizeOf(LogicalPalette.palPalEntry), 0); - for i := 0 to 255 do - LogicalPalette.palPalEntry[i].peFlags := PC_NOCOLLAPSE; -// 2003.03.06 <- - - if (Windows) then - begin - // Get the windows 20 color system palette - SystemPalette := GetStockObject(DEFAULT_PALETTE); - GetPaletteEntries(SystemPalette, 0, 10, LogicalPalette.palPalEntry[0]); - //GetPaletteEntries(SystemPalette, 10, 10, LogicalPalette.palPalEntry[245]); // wrong offset - GetPaletteEntries(SystemPalette, 10, 10, LogicalPalette.palPalEntry[246]); // 2003.03.06 - Colors := 236; - Offset := 10; - LogicalPalette.palNumEntries := 256; -{ Test code -// 2003.03.06 -> - // Get the windows 20 color system palette - SystemPalette := GetStockObject(DEFAULT_PALETTE); - GetPaletteEntries(SystemPalette, 0, 10, LogicalPalette.palPalEntry[0]); - GetPaletteEntries(SystemPalette, 10, 10, LogicalPalette.palPalEntry[10]); - Colors := 236; - Offset := 20; - LogicalPalette.palNumEntries := 256; -// 2003.03.06 <- -} - end else - Offset := 0; - - // Normally for 24-bit images, use ColorBits of 5 or 6. For 8-bit images - // use ColorBits = 8. - ColorQuantizer := TColorQuantizer.Create(Colors, ColorBits); - try - ColorQuantizer.ProcessImage(DIB); - ColorQuantizer.GetColorTable(RGBQuadArray); - finally - ColorQuantizer.Free; - end; - - for i := 0 to Colors-1 do - with LogicalPalette.palPalEntry[i+Offset] do - begin - peRed := RGBQuadArray[i].rgbRed; - peGreen := RGBQuadArray[i].rgbGreen; - peBlue := RGBQuadArray[i].rgbBlue; - peFlags := RGBQuadArray[i].rgbReserved; - end; - Result := CreatePalette(pLogPalette(@LogicalPalette)^); -end; - -function CreateOptimizedPaletteFromSingleBitmap(const Bitmap: TBitmap; - Colors, ColorBits: integer; Windows: boolean): hPalette; -var - DIB : TDIBReader; -begin - DIB := TDIBReader.Create(Bitmap, pf24bit); - try - Result := doCreateOptimizedPaletteFromSingleBitmap(DIB, Colors, ColorBits, Windows); - finally - DIB.Free; - end; -end; - -function CreateOptimizedPaletteFromManyBitmaps(Bitmaps: TList; Colors, ColorBits: integer; - Windows: boolean): hPalette; -var - SystemPalette : HPalette; - ColorQuantizer : TColorQuantizer; - i : integer; - LogicalPalette : TMaxLogPalette; - RGBQuadArray : TRGBQuadArray; - Offset : integer; - DIB : TDIBReader; -begin - if (Bitmaps = nil) or (Bitmaps.Count = 0) then - Error(sInvalidBitmapList); - - LogicalPalette.palVersion := $0300; - LogicalPalette.palNumEntries := Colors; -// 2003.03.06 -> - {reset palette to black} - FillChar(LogicalPalette.palPalEntry, SizeOf(LogicalPalette.palPalEntry), 0); - for i := 0 to 255 do - LogicalPalette.palPalEntry[i].peFlags := PC_NOCOLLAPSE; -// 2003.03.06 <- - - if (Windows) then - begin - // Get the windows 20 color system palette - SystemPalette := GetStockObject(DEFAULT_PALETTE); - GetPaletteEntries(SystemPalette, 0, 10, LogicalPalette.palPalEntry[0]); - //GetPaletteEntries(SystemPalette, 10, 10, LogicalPalette.palPalEntry[245]); // wrong offset - GetPaletteEntries(SystemPalette, 10, 10, LogicalPalette.palPalEntry[246]); // 2003.03.06 - Colors := 236; - Offset := 10; - LogicalPalette.palNumEntries := 256; -{ Test code -// 2003.03.06 -> - // Get the windows 20 color system palette - SystemPalette := GetStockObject(DEFAULT_PALETTE); - GetPaletteEntries(SystemPalette, 0, 10, LogicalPalette.palPalEntry[0]); - GetPaletteEntries(SystemPalette, 10, 10, LogicalPalette.palPalEntry[10]); - Colors := 236; - Offset := 20; - LogicalPalette.palNumEntries := 256; -// 2003.03.06 <- -} - end else - Offset := 0; - - // Normally for 24-bit images, use ColorBits of 5 or 6. For 8-bit images - // use ColorBits = 8. - ColorQuantizer := TColorQuantizer.Create(Colors, ColorBits); - try - for i := 0 to Bitmaps.Count-1 do - begin - DIB := TDIBReader.Create(TBitmap(Bitmaps[i]), pf24bit); - try - ColorQuantizer.ProcessImage(DIB); - finally - DIB.Free; - end; - end; - ColorQuantizer.GetColorTable(RGBQuadArray); - finally - ColorQuantizer.Free; - end; - - for i := 0 to Colors-1 do - with LogicalPalette.palPalEntry[i+Offset] do - begin - peRed := RGBQuadArray[i].rgbRed; - peGreen := RGBQuadArray[i].rgbGreen; - peBlue := RGBQuadArray[i].rgbBlue; - peFlags := RGBQuadArray[i].rgbReserved; - end; - Result := CreatePalette(pLogPalette(@LogicalPalette)^); -end; - -//////////////////////////////////////////////////////////////////////////////// -// -// Color reduction -// -//////////////////////////////////////////////////////////////////////////////// -{$IFOPT R+} - {$DEFINE R_PLUS} - {$RANGECHECKS OFF} -{$ENDIF} -//: Reduces the color depth of a bitmap using color quantization and dithering. -function ReduceColors(Bitmap: TBitmap; ColorReduction: TColorReduction; - DitherMode: TDitherMode; ReductionBits: integer; CustomPalette: hPalette): TBitmap; -var - Palette : hPalette; - ColorLookup : TColorLookup; - Ditherer : TDitherEngine; - Row : Integer; - DIBResult : TDIBWriter; - DIBSource : TDIBReader; - SrcScanLine , - Src : PRGBTriple; - DstScanLine , - Dst : PAnsiChar; - BGR : TRGBTriple; -{$ifdef DEBUG_DITHERPERFORMANCE} - TimeStart , - TimeStop : DWORD; -{$endif} - - function GrayScalePalette: hPalette; - var - i : integer; - Pal : TMaxLogPalette; - begin - Pal.palVersion := $0300; - Pal.palNumEntries := 256; - for i := 0 to 255 do - begin - // 2009.10.10 -> - //with (Pal.palPalEntry[i]) do - with Pal.palPalEntry[i] do - // 2009.10.10 <- - begin - peRed := i; - peGreen := i; - peBlue := i; - peFlags := PC_NOCOLLAPSE; - end; - end; - Result := CreatePalette(pLogPalette(@Pal)^); - end; - - function MonochromePalette: hPalette; - var - i : integer; - Pal : TMaxLogPalette; - const - Values : array[0..1] of byte - = (0, 255); - begin - Pal.palVersion := $0300; - Pal.palNumEntries := 2; - for i := 0 to 1 do - begin - // 2009.10.10 -> - //with (Pal.palPalEntry[i]) do - with Pal.palPalEntry[i] do - // 2009.10.10 <- - begin - peRed := Values[i]; - peGreen := Values[i]; - peBlue := Values[i]; - peFlags := PC_NOCOLLAPSE; - end; - end; - Result := CreatePalette(pLogPalette(@Pal)^); - end; - - function WindowsGrayScalePalette: hPalette; - var - i : integer; - Pal : TMaxLogPalette; - const - Values : array[0..3] of byte - = (0, 128, 192, 255); - begin - Pal.palVersion := $0300; - Pal.palNumEntries := 4; - for i := 0 to 3 do - begin - // 2009.10.10 -> - //with (Pal.palPalEntry[i]) do - with Pal.palPalEntry[i] do - // 2009.10.10 <- - begin - peRed := Values[i]; - peGreen := Values[i]; - peBlue := Values[i]; - peFlags := PC_NOCOLLAPSE; - end; - end; - Result := CreatePalette(pLogPalette(@Pal)^); - end; - - function WindowsHalftonePalette: hPalette; - var - DC : HDC; - begin - DC := GDICheck(GetDC(0)); - try - Result := CreateHalfTonePalette(DC); - finally - ReleaseDC(0, DC); - end; - end; - -begin -{$ifdef DEBUG_DITHERPERFORMANCE} - timeBeginPeriod(5); - TimeStart := timeGetTime; -{$endif} - - Result := TBitmap.Create; - try - - if (ColorReduction = rmNone) then - begin - Result.Assign(Bitmap); -{$ifndef VER9x} - SetPixelFormat(Result, pf24bit); -{$endif} - exit; - end; - -{$IFNDEF VER9x} - if (Bitmap.Width*Bitmap.Height > BitmapAllocationThreshold) then - SetPixelFormat(Result, pf1bit); // To reduce resource consumption of resize -{$ENDIF} - - ColorLookup := nil; - Ditherer := nil; - DIBResult := nil; - DIBSource := nil; - Palette := 0; - try // Protect above resources - - // Dithering and color mapper only supports 24 bit bitmaps, - // so we have convert the source bitmap to the appropiate format. - DIBSource := TDIBReader.Create(Bitmap, pf24bit); - - // Create a palette based on current options - case (ColorReduction) of - rmQuantize: - Palette := doCreateOptimizedPaletteFromSingleBitmap(DIBSource, 1 SHL ReductionBits, 8, False); - rmQuantizeWindows: - Palette := CreateOptimizedPaletteFromSingleBitmap(Bitmap, 256, 8, True); - rmNetscape: - Palette := WebPalette; - rmGrayScale: - Palette := GrayScalePalette; - rmMonochrome: - Palette := MonochromePalette; - rmWindowsGray: - Palette := WindowsGrayScalePalette; - rmWindows20: - Palette := GetStockObject(DEFAULT_PALETTE); - rmWindows256: - Palette := WindowsHalftonePalette; - rmPalette: - Palette := CopyPalette(CustomPalette); - else - exit; - end; - - {.TODO -oanme -cImprovement : Gray scale conversion should be done prior to dithering/mapping. Otherwise corrected values will be converted multiple times. } - - // Create a color mapper based on current options - case (ColorReduction) of - // For some strange reason my fast and dirty color lookup - // is more precise that Windows GetNearestPaletteIndex... - // rmWindows20: - // ColorLookup := TSlowColorLookup.Create(Palette); - // rmWindowsGray: - // ColorLookup := TGrayWindowsLookup.Create(Palette); - rmQuantize: -// 2007.01.18 -> // switch back to TFastColorLookup - ColorLookup := TFastColorLookup.Create(Palette); -// ColorLookup := TSlowColorLookup.Create(Palette); // 2003-03-06 -// 2007.01.18 <- - rmNetscape: - ColorLookup := TNetscapeColorLookup.Create(Palette); - rmGrayScale: - ColorLookup := TGrayScaleLookup.Create(Palette); - rmMonochrome: - ColorLookup := TMonochromeLookup.Create(Palette); - else -// ColorLookup := TFastColorLookup.Create(Palette); - ColorLookup := TSlowColorLookup.Create(Palette); // 2003-03-06 - end; - - // Nothing to do if palette doesn't contain any colors - if (ColorLookup.Colors = 0) then - exit; - - // Create a ditherer based on current options - case (DitherMode) of - dmNearest: - Ditherer := TDitherEngine.Create(Bitmap.Width, ColorLookup); - dmFloydSteinberg: - Ditherer := TFloydSteinbergDitherer.Create(Bitmap.Width, ColorLookup); - dmStucki: - Ditherer := TStuckiDitherer.Create(Bitmap.Width, ColorLookup); - dmSierra: - Ditherer := TSierraDitherer.Create(Bitmap.Width, ColorLookup); - dmJaJuNI: - Ditherer := TJaJuNIDitherer.Create(Bitmap.Width, ColorLookup); - dmSteveArche: - Ditherer := TSteveArcheDitherer.Create(Bitmap.Width, ColorLookup); - dmBurkes: - Ditherer := TBurkesDitherer.Create(Bitmap.Width, ColorLookup); - else - exit; - end; - - // The processed bitmap is returned in pf8bit format - DIBResult := TDIBWriter.Create(Result, pf8bit, Bitmap.Width, Bitmap.Height, - Palette); - - // Process the image - Row := 0; - while (Row < Bitmap.Height) do - begin - SrcScanline := DIBSource.ScanLine[Row]; - DstScanline := DIBResult.ScanLine[Row]; - Src := pointer(longInt(SrcScanLine) + Ditherer.Column*sizeof(TRGBTriple)); - Dst := pointer(longInt(DstScanLine) + Ditherer.Column); - - while (Ditherer.Column < Ditherer.Width) and (Ditherer.Column >= 0) do - begin - BGR := Src^; - // Dither and map a single pixel - Dst^ := Ditherer.Dither(BGR.rgbtRed, BGR.rgbtGreen, BGR.rgbtBlue, - BGR.rgbtRed, BGR.rgbtGreen, BGR.rgbtBlue); - - inc(Src, Ditherer.Direction); - inc(Dst, Ditherer.Direction); - end; - - Inc(Row); - Ditherer.NextLine; - end; - finally - if (ColorLookup <> nil) then - ColorLookup.Free; - if (Ditherer <> nil) then - Ditherer.Free; - if (DIBResult <> nil) then - DIBResult.Free; - if (DIBSource <> nil) then - DIBSource.Free; - // Must delete palette after TDIBWriter since TDIBWriter uses palette - if (Palette <> 0) then - DeleteObject(Palette); - end; - except - Result.Free; - raise; - end; - -{$ifdef DEBUG_DITHERPERFORMANCE} - TimeStop := timeGetTime; - ShowMessage(format('Dithered %d pixels in %d mS, Rate %d pixels/mS (%d pixels/S)', - [Bitmap.Height*Bitmap.Width, TimeStop-TimeStart, - MulDiv(Bitmap.Height, Bitmap.Width, TimeStop-TimeStart+1), - MulDiv(Bitmap.Height, Bitmap.Width * 1000, TimeStop-TimeStart+1)])); - timeEndPeriod(5); -{$endif} -end; -{$IFDEF R_PLUS} - {$RANGECHECKS ON} - {$UNDEF R_PLUS} -{$ENDIF} - -//////////////////////////////////////////////////////////////////////////////// -// -// TGIFColorMap -// -//////////////////////////////////////////////////////////////////////////////// -const - InitColorMapSize = 16; - DeltaColorMapSize = 32; - -//: Creates an instance of a TGIFColorMap object. -constructor TGIFColorMap.Create; -begin - inherited Create; - FColorMap := nil; - FCapacity := 0; - FCount := 0; - FOptimized := False; -end; - -//: Destroys an instance of a TGIFColorMap object. -destructor TGIFColorMap.Destroy; -begin - Clear; - Changed; - inherited Destroy; -end; - -//: Empties the color map. -procedure TGIFColorMap.Clear; -begin - if (FColorMap <> nil) then - FreeMem(FColorMap); - FColorMap := nil; - FCapacity := 0; - FCount := 0; - FOptimized := False; -end; - -//: Converts a Windows color value to a RGB value. -class function TGIFColorMap.Color2RGB(Color: TColor): TGIFColor; -begin - Result.Blue := (Color shr 16) and $FF; - Result.Green := (Color shr 8) and $FF; - Result.Red := Color and $FF; -end; - -//: Converts a RGB value to a Windows color value. -class function TGIFColorMap.RGB2Color(Color: TGIFColor): TColor; -begin - Result := (Color.Blue SHL 16) OR (Color.Green SHL 8) OR Color.Red; -end; - -//: Saves the color map to a stream. -procedure TGIFColorMap.SaveToStream(Stream: TStream); -var - Dummies : integer; - Dummy : TGIFColor; -begin - if (FCount = 0) then - exit; - Stream.WriteBuffer(FColorMap^, FCount*sizeof(TGIFColor)); - Dummies := (1 SHL BitsPerPixel)-FCount; - Dummy.Red := 0; - Dummy.Green := 0; - Dummy.Blue := 0; - while (Dummies > 0) do - begin - Stream.WriteBuffer(Dummy, sizeof(TGIFColor)); - dec(Dummies); - end; -end; - -//: Loads the color map from a stream. -procedure TGIFColorMap.LoadFromStream(Stream: TStream; Count: integer); -begin - Clear; - SetCapacity(Count); - ReadCheck(Stream, FColorMap^, Count*sizeof(TGIFColor)); - FCount := Count; -end; - -//: Returns the position of a color in the color map. -function TGIFColorMap.IndexOf(Color: TColor): integer; -var - RGB : TGIFColor; -begin - RGB := Color2RGB(Color); - if (FOptimized) then - begin - // Optimized palette has most frequently occuring entries first - Result := 0; - // Reverse search to (hopefully) check latest colors first - while (Result < FCount) do - with (FColorMap^[Result]) do - begin - if (RGB.Red = Red) and (RGB.Green = Green) and (RGB.Blue = Blue) then - exit; - Inc(Result); - end; - Result := -1; - end else - begin - Result := FCount-1; - // Reverse search to (hopefully) check latest colors first - while (Result >= 0) do - with (FColorMap^[Result]) do - begin - if (RGB.Red = Red) and (RGB.Green = Green) and (RGB.Blue = Blue) then - exit; - Dec(Result); - end; - end; -end; - -procedure TGIFColorMap.SetCapacity(Size: integer); -begin - if (Size >= FCapacity) then - begin - if (Size <= InitColorMapSize) then - FCapacity := InitColorMapSize - else - FCapacity := (Size + DeltaColorMapSize - 1) DIV DeltaColorMapSize * DeltaColorMapSize; - if (FCapacity > GIFMaxColors) then - FCapacity := GIFMaxColors; - ReallocMem(FColorMap, FCapacity * sizeof(TGIFColor)); - end; -end; - -//: Imports a Windows palette into the color map. -procedure TGIFColorMap.ImportPalette(Palette: HPalette); -type - PalArray = array[byte] of TPaletteEntry; -var - Pal : PalArray; - NewCount : integer; - i : integer; -begin - Clear; - NewCount := GetPaletteEntries(Palette, 0, 256, pal); - if (NewCount = 0) then - exit; - SetCapacity(NewCount); - for i := 0 to NewCount-1 do - with FColorMap[i], Pal[i] do - begin - Red := peRed; - Green := peGreen; - Blue := peBlue; - end; - FCount := NewCount; - Changed; -end; - -//: Imports a color map structure into the color map. -procedure TGIFColorMap.ImportColorMap(Map: TColorMap; Count: integer); -begin - Clear; - if (Count = 0) then - exit; - SetCapacity(Count); - FCount := Count; - - System.Move(Map, FColorMap^, FCount * sizeof(TGIFColor)); - - Changed; -end; - -//: Imports a Windows palette structure into the color map. -procedure TGIFColorMap.ImportColorTable(Pal: pointer; Count: integer); -var - i : integer; -begin - Clear; - if (Count = 0) then - exit; - SetCapacity(Count); - for i := 0 to Count-1 do - with FColorMap[i], PRGBQuadArray(Pal)[i] do - begin - Red := rgbRed; - Green := rgbGreen; - Blue := rgbBlue; - end; - FCount := Count; - Changed; -end; - -//: Imports the color table of a DIB into the color map. -procedure TGIFColorMap.ImportDIBColors(Handle: HDC); -var - Pal : Pointer; - NewCount : integer; -begin - Clear; - GetMem(Pal, sizeof(TRGBQuad) * 256); - try - NewCount := GetDIBColorTable(Handle, 0, 256, Pal^); - ImportColorTable(Pal, NewCount); - finally - FreeMem(Pal); - end; - Changed; -end; - -//: Creates a Windows palette from the color map. -function TGIFColorMap.ExportPalette: HPalette; -var - Pal : TMaxLogPalette; - i : Integer; -begin - if (Count = 0) then - begin - Result := 0; - exit; - end; - Pal.palVersion := $300; - Pal.palNumEntries := Count; - for i := 0 to Count-1 do - with FColorMap[i], Pal.palPalEntry[i] do - begin - peRed := Red; - peGreen := Green; - peBlue := Blue; - peFlags := PC_NOCOLLAPSE; {.TODO -oanme -cImprovement : Verify that PC_NOCOLLAPSE is the correct value to use. } - end; - Result := CreatePalette(PLogPalette(@Pal)^); -end; - -//: Adds a color to the color map. -function TGIFColorMap.Add(Color: TColor): integer; -begin - if (FCount >= GIFMaxColors) then - // Color map full - Error(sTooManyColors); - - Result := FCount; - if (Result >= FCapacity) then - SetCapacity(FCount+1); - FColorMap^[FCount] := Color2RGB(Color); - inc(FCount); - FOptimized := False; - Changed; -end; - -function TGIFColorMap.AddUnique(Color: TColor): integer; -begin - // Look up color before add (same as IndexOf) - Result := IndexOf(Color); - if (Result >= 0) then - // Color already in map - exit; - - Result := Add(Color); -end; - -//: Removes a color from the color map. -procedure TGIFColorMap.Delete(Index: integer); -begin - if (Index < 0) or (Index >= FCount) then - // Color index out of range - Error(sBadColorIndex); - dec(FCount); - if (Index < FCount) then - System.Move(FColorMap^[Index + 1], FColorMap^[Index], (FCount - Index)* sizeof(TGIFColor)); - FOptimized := False; - Changed; -end; - -function TGIFColorMap.GetColor(Index: integer): TColor; -begin - if (Index < 0) or (Index >= FCount) then - begin - // Color index out of range - Warning(gsWarning, sBadColorIndex); - // Raise an exception if the color map is empty - if (FCount = 0) then - Error(sEmptyColorMap); - // Default to color index 0 - Index := 0; - end; - Result := RGB2Color(FColorMap^[Index]); -end; - -procedure TGIFColorMap.SetColor(Index: integer; Value: TColor); -begin - if (Index < 0) or (Index >= FCount) then - // Color index out of range - Error(sBadColorIndex); - FColorMap^[Index] := Color2RGB(Value); - Changed; -end; - -function TGIFColorMap.DoOptimize: boolean; -var - Usage : TColormapHistogram; - TempMap : array[0..255] of TGIFColor; - ReverseMap : TColormapReverse; - i : integer; - LastFound : boolean; - NewCount : integer; - T : TUsageCount; - Pivot : integer; - - procedure QuickSort(iLo, iHi: Integer); - var - Lo, Hi: Integer; - begin - repeat - Lo := iLo; - Hi := iHi; - Pivot := Usage[(iLo + iHi) SHR 1].Count; - repeat - while (Usage[Lo].Count - Pivot > 0) do inc(Lo); - while (Usage[Hi].Count - Pivot < 0) do dec(Hi); - if (Lo <= Hi) then - begin - T := Usage[Lo]; - Usage[Lo] := Usage[Hi]; - Usage[Hi] := T; - inc(Lo); - dec(Hi); - end; - until (Lo > Hi); - if (iLo < Hi) then - QuickSort(iLo, Hi); - iLo := Lo; - until (Lo >= iHi); - end; - -begin - if (FCount <= 1) then - begin - Result := False; - exit; - end; - - FOptimized := True; - Result := True; - - BuildHistogram(Usage); - - (* - ** Sort according to usage count - *) - QuickSort(0, FCount-1); - - (* - ** Test for table already sorted - *) - for i := 0 to FCount-1 do - if (Usage[i].Index <> i) then - break; - if (i = FCount) then - exit; - - (* - ** Build old to new map - *) - for i := 0 to FCount-1 do - ReverseMap[Usage[i].Index] := i; - - - MapImages(ReverseMap); - - (* - ** Reorder colormap - *) - LastFound := False; - NewCount := FCount; - Move(FColorMap^, TempMap, FCount * sizeof(TGIFColor)); - for i := 0 to FCount-1 do - begin - FColorMap^[ReverseMap[i]] := TempMap[i]; - // Find last used color index - if (Usage[i].Count = 0) and not(LastFound) then - begin - LastFound := True; - NewCount := i; - end; - end; - - FCount := NewCount; - - Changed; -end; - -function TGIFColorMap.GetBitsPerPixel: integer; -begin - Result := Colors2bpp(FCount); -end; - -//: Copies one color map to another. -procedure TGIFColorMap.Assign(Source: TPersistent); -begin - if (Source is TGIFColorMap) then - begin - Clear; - FCapacity := TGIFColorMap(Source).FCapacity; - FCount := TGIFColorMap(Source).FCount; - FOptimized := TGIFColorMap(Source).FOptimized; - FColorMap := AllocMem(FCapacity * sizeof(TGIFColor)); - System.Move(TGIFColorMap(Source).FColorMap^, FColorMap^, FCount * sizeof(TGIFColor)); - Changed; - end else - inherited Assign(Source); -end; - -//////////////////////////////////////////////////////////////////////////////// -// -// TGIFItem -// -//////////////////////////////////////////////////////////////////////////////// -constructor TGIFItem.Create(GIFImage: TGIFImage); -begin - inherited Create; - - FGIFImage := GIFImage; -end; - -procedure TGIFItem.Warning(Severity: TGIFSeverity; Message: string); -begin - FGIFImage.Warning(self, Severity, Message); -end; - -function TGIFItem.GetVersion: TGIFVersion; -begin - Result := gv87a; -end; - -procedure TGIFItem.LoadFromFile(const Filename: string); -var - Stream: TStream; -begin - Stream := TFileStream.Create(Filename, fmOpenRead OR fmShareDenyWrite); - try - LoadFromStream(Stream); - finally - Stream.Free; - end; -end; - -procedure TGIFItem.SaveToFile(const Filename: string); -var - Stream: TStream; -begin - Stream := TFileStream.Create(Filename, fmCreate); - try - SaveToStream(Stream); - finally - Stream.Free; - end; -end; - -//////////////////////////////////////////////////////////////////////////////// -// -// TGIFList -// -//////////////////////////////////////////////////////////////////////////////// -constructor TGIFList.Create(Image: TGIFImage); -begin - inherited Create; - FImage := Image; - FItems := TList.Create; -end; - -destructor TGIFList.Destroy; -begin - Clear; - FItems.Free; - inherited Destroy; -end; - -function TGIFList.GetItem(Index: Integer): TGIFItem; -begin - Result := TGIFItem(FItems[Index]); -end; - -procedure TGIFList.SetItem(Index: Integer; Item: TGIFItem); -begin - FItems[Index] := Item; -end; - -function TGIFList.GetCount: Integer; -begin - Result := FItems.Count; -end; - -function TGIFList.Add(Item: TGIFItem): Integer; -begin - Result := FItems.Add(Item); -end; - -procedure TGIFList.Clear; -begin - while (FItems.Count > 0) do - Delete(0); -end; - -procedure TGIFList.Delete(Index: Integer); -var - Item : TGIFItem; -begin - Item := TGIFItem(FItems[Index]); - // Delete before item is destroyed to avoid recursion - FItems.Delete(Index); - Item.Free; -end; - -procedure TGIFList.Exchange(Index1, Index2: Integer); -begin - FItems.Exchange(Index1, Index2); -end; - -function TGIFList.First: TGIFItem; -begin - Result := TGIFItem(FItems.First); -end; - -function TGIFList.IndexOf(Item: TGIFItem): Integer; -begin - Result := FItems.IndexOf(Item); -end; - -procedure TGIFList.Insert(Index: Integer; Item: TGIFItem); -begin - FItems.Insert(Index, Item); -end; - -function TGIFList.Last: TGIFItem; -begin - Result := TGIFItem(FItems.Last); -end; - -procedure TGIFList.Move(CurIndex, NewIndex: Integer); -begin - FItems.Move(CurIndex, NewIndex); -end; - -function TGIFList.Remove(Item: TGIFItem): Integer; -begin - // Note: TGIFList.Remove must not destroy item - Result := FItems.Remove(Item); -end; - -procedure TGIFList.SaveToStream(Stream: TStream); -var - i : integer; -begin - for i := 0 to FItems.Count-1 do - TGIFItem(FItems[i]).SaveToStream(Stream); -end; - -procedure TGIFList.Warning(Severity: TGIFSeverity; Message: string); -begin - Image.Warning(self, Severity, Message); -end; - -//////////////////////////////////////////////////////////////////////////////// -// -// TGIFGlobalColorMap -// -//////////////////////////////////////////////////////////////////////////////// -type - TGIFGlobalColorMap = class(TGIFColorMap) - private - FHeader : TGIFHeader; - protected - procedure Warning(Severity: TGIFSeverity; Message: string); override; - procedure BuildHistogram(var Histogram: TColormapHistogram); override; - procedure MapImages(var Map: TColormapReverse); override; - public - constructor Create(HeaderItem: TGIFHeader); - function Optimize: boolean; override; - procedure Changed; override; - end; - -constructor TGIFGlobalColorMap.Create(HeaderItem: TGIFHeader); -begin - Inherited Create; - FHeader := HeaderItem; -end; - -procedure TGIFGlobalColorMap.Warning(Severity: TGIFSeverity; Message: string); -begin - FHeader.Image.Warning(self, Severity, Message); -end; - -procedure TGIFGlobalColorMap.BuildHistogram(var Histogram: TColormapHistogram); -var - Pixel , - LastPixel : PAnsiChar; - i : integer; -begin - (* - ** Init histogram - *) - for i := 0 to Count-1 do - begin - Histogram[i].Index := i; - Histogram[i].Count := 0; - end; - - for i := 0 to FHeader.Image.Images.Count-1 do - if (FHeader.Image.Images[i].ActiveColorMap = self) then - begin - Pixel := FHeader.Image.Images[i].Data; - LastPixel := Pixel + FHeader.Image.Images[i].Width * FHeader.Image.Images[i].Height; - - (* - ** Sum up usage count for each color - *) - while (Pixel < LastPixel) do - begin - inc(Histogram[ord(Pixel^)].Count); - inc(Pixel); - end; - end; -end; - -procedure TGIFGlobalColorMap.MapImages(var Map: TColormapReverse); -var - Pixel , - LastPixel : PAnsiChar; - i : integer; -begin - for i := 0 to FHeader.Image.Images.Count-1 do - if (FHeader.Image.Images[i].ActiveColorMap = self) then - begin - Pixel := FHeader.Image.Images[i].Data; - LastPixel := Pixel + FHeader.Image.Images[i].Width * FHeader.Image.Images[i].Height; - - (* - ** Reorder all pixel to new map - *) - while (Pixel < LastPixel) do - begin -// 2008.10.19 -> -// Pixel^ := chr(Map[ord(Pixel^)]); - Pixel^ := AnsiChar(Map[ord(Pixel^)]); -// 2008.10.19 <- - inc(Pixel); - end; - - (* - ** Reorder transparent colors - *) - if (FHeader.Image.Images[i].Transparent) then - FHeader.Image.Images[i].GraphicControlExtension.TransparentColorIndex := - Map[FHeader.Image.Images[i].GraphicControlExtension.TransparentColorIndex]; - end; -end; - -function TGIFGlobalColorMap.Optimize: boolean; -begin - { Optimize with first image, Remove unused colors if only one image } - if (FHeader.Image.Images.Count > 0) then - Result := DoOptimize - else - Result := False; -end; - -procedure TGIFGlobalColorMap.Changed; -begin - FHeader.Image.Palette := 0; -end; - -//////////////////////////////////////////////////////////////////////////////// -// -// TGIFHeader -// -//////////////////////////////////////////////////////////////////////////////// -constructor TGIFHeader.Create(GIFImage: TGIFImage); -begin - inherited Create(GIFImage); - FColorMap := TGIFGlobalColorMap.Create(self); - Clear; -end; - -destructor TGIFHeader.Destroy; -begin - FColorMap.Free; - inherited Destroy; -end; - -procedure TGIFHeader.Clear; -begin - FColorMap.Clear; - FLogicalScreenDescriptor.ScreenWidth := 0; - FLogicalScreenDescriptor.ScreenHeight := 0; - FLogicalScreenDescriptor.PackedFields := 0; - FLogicalScreenDescriptor.BackgroundColorIndex := 0; - FLogicalScreenDescriptor.AspectRatio := 0; -end; - -procedure TGIFHeader.Assign(Source: TPersistent); -begin - if (Source is TGIFHeader) then - begin - ColorMap.Assign(TGIFHeader(Source).ColorMap); - FLogicalScreenDescriptor := TGIFHeader(Source).FLogicalScreenDescriptor; - end else - if (Source is TGIFColorMap) then - begin - Clear; - ColorMap.Assign(TGIFColorMap(Source)); - end else - inherited Assign(Source); -end; - -type - TGIFHeaderRec = packed record - Signature: array[0..2] of AnsiChar; { contains 'GIF' } - Version: TGIFVersionRec; { '87a' or '89a' } - end; - -const - { logical screen descriptor packed field masks } - lsdGlobalColorTable = $80; { set if global color table follows L.S.D. } - lsdColorResolution = $70; { Color resolution - 3 bits } - lsdSort = $08; { set if global color table is sorted - 1 bit } - lsdColorTableSize = $07; { size of global color table - 3 bits } - { Actual size = 2^value+1 - value is 3 bits } -procedure TGIFHeader.Prepare; -var - pack : BYTE; -begin - Pack := $00; - if (ColorMap.Count > 0) then - begin - Pack := lsdGlobalColorTable; - if (ColorMap.Optimized) then - Pack := Pack OR lsdSort; - end; - // Note: The SHL below was SHL 5 in the original source, but that looks wrong - Pack := Pack OR ((Image.ColorResolution SHL 4) AND lsdColorResolution); - Pack := Pack OR ((Image.BitsPerPixel-1) AND lsdColorTableSize); - FLogicalScreenDescriptor.PackedFields := Pack; -end; - -procedure TGIFHeader.SaveToStream(Stream: TStream); -var - GifHeader : TGIFHeaderRec; - v : TGIFVersion; -begin - v := Image.Version; - if (v = gvUnknown) then - Error(sBadVersion); - - GifHeader.Signature := 'GIF'; - GifHeader.Version := GIFVersions[v]; - - Prepare; - Stream.Write(GifHeader, sizeof(GifHeader)); - Stream.Write(FLogicalScreenDescriptor, sizeof(FLogicalScreenDescriptor)); - if (FLogicalScreenDescriptor.PackedFields AND lsdGlobalColorTable = lsdGlobalColorTable) then - ColorMap.SaveToStream(Stream); -end; - -procedure TGIFHeader.LoadFromStream(Stream: TStream); -var - GifHeader : TGIFHeaderRec; - ColorCount : integer; - Position : integer; -begin - Position := Stream.Position; - - ReadCheck(Stream, GifHeader, sizeof(GifHeader)); -// 2008.10.19 -> -// if (uppercase(GifHeader.Signature) <> 'GIF') then - if (uppercase(string(GifHeader.Signature)) <> 'GIF') then -// 2008.10.19 <- - begin - // Attempt recovery in case we are reading a GIF stored in a form by rxLib - Stream.Position := Position; - // Seek past size stored in stream - Stream.Seek(sizeof(longInt), soFromCurrent); - // Attempt to read signature again - ReadCheck(Stream, GifHeader, sizeof(GifHeader)); -// 2008.10.19 -> -// if (uppercase(GifHeader.Signature) <> 'GIF') then - if (uppercase(string(GifHeader.Signature)) <> 'GIF') then -// 2008.10.19 <- - Error(sBadSignature); - end; - - ReadCheck(Stream, FLogicalScreenDescriptor, sizeof(FLogicalScreenDescriptor)); - - if (FLogicalScreenDescriptor.PackedFields AND lsdGlobalColorTable = lsdGlobalColorTable) then - begin - ColorCount := 2 SHL (FLogicalScreenDescriptor.PackedFields AND lsdColorTableSize); - if (ColorCount < 2) or (ColorCount > 256) then - Error(sScreenBadColorSize); - ColorMap.LoadFromStream(Stream, ColorCount) - end else - ColorMap.Clear; -end; - -function TGIFHeader.GetVersion: TGIFVersion; -begin - if (FColorMap.Optimized) or (AspectRatio <> 0) then - Result := gv89a - else - Result := inherited GetVersion; -end; - -function TGIFHeader.GetBackgroundColor: TColor; -begin - Result := FColorMap[BackgroundColorIndex]; -end; - -procedure TGIFHeader.SetBackgroundColor(Color: TColor); -begin - BackgroundColorIndex := FColorMap.AddUnique(Color); -end; - -procedure TGIFHeader.SetBackgroundColorIndex(Index: BYTE); -begin - if ((Index >= FColorMap.Count) and (FColorMap.Count > 0)) then - begin - Warning(gsWarning, sBadColorIndex); - Index := 0; - end; - FLogicalScreenDescriptor.BackgroundColorIndex := Index; -end; - -function TGIFHeader.GetBitsPerPixel: integer; -begin - Result := FColorMap.BitsPerPixel; -end; - -function TGIFHeader.GetColorResolution: integer; -begin - Result := FColorMap.BitsPerPixel-1; -end; - -//////////////////////////////////////////////////////////////////////////////// -// -// TGIFLocalColorMap -// -//////////////////////////////////////////////////////////////////////////////// -type - TGIFLocalColorMap = class(TGIFColorMap) - private - FSubImage : TGIFSubImage; - protected - procedure Warning(Severity: TGIFSeverity; Message: string); override; - procedure BuildHistogram(var Histogram: TColormapHistogram); override; - procedure MapImages(var Map: TColormapReverse); override; - public - constructor Create(SubImage: TGIFSubImage); - function Optimize: boolean; override; - procedure Changed; override; - end; - -constructor TGIFLocalColorMap.Create(SubImage: TGIFSubImage); -begin - Inherited Create; - FSubImage := SubImage; -end; - -procedure TGIFLocalColorMap.Warning(Severity: TGIFSeverity; Message: string); -begin - FSubImage.Image.Warning(self, Severity, Message); -end; - -procedure TGIFLocalColorMap.BuildHistogram(var Histogram: TColormapHistogram); -var - Pixel , - LastPixel : PAnsiChar; - i : integer; -begin - Pixel := FSubImage.Data; - LastPixel := Pixel + FSubImage.Width * FSubImage.Height; - - (* - ** Init histogram - *) - for i := 0 to Count-1 do - begin - Histogram[i].Index := i; - Histogram[i].Count := 0; - end; - - (* - ** Sum up usage count for each color - *) - while (Pixel < LastPixel) do - begin - inc(Histogram[ord(Pixel^)].Count); - inc(Pixel); - end; -end; - -procedure TGIFLocalColorMap.MapImages(var Map: TColormapReverse); -var - Pixel , - LastPixel : PAnsiChar; -begin - Pixel := FSubImage.Data; - LastPixel := Pixel + FSubImage.Width * FSubImage.Height; - - (* - ** Reorder all pixel to new map - *) - while (Pixel < LastPixel) do - begin -// 2008.10.19 -> -// Pixel^ := chr(Map[ord(Pixel^)]); - Pixel^ := AnsiChar(Map[ord(Pixel^)]); -// 2008.10.19 <- - inc(Pixel); - end; - - (* - ** Reorder transparent colors - *) - if (FSubImage.Transparent) then - FSubImage.GraphicControlExtension.TransparentColorIndex := - Map[FSubImage.GraphicControlExtension.TransparentColorIndex]; -end; - -function TGIFLocalColorMap.Optimize: boolean; -begin - Result := DoOptimize; -end; - -procedure TGIFLocalColorMap.Changed; -begin - FSubImage.Palette := 0; -end; - - -//////////////////////////////////////////////////////////////////////////////// -// -// LZW Decoder -// -//////////////////////////////////////////////////////////////////////////////// -const - GIFCodeBits = 12; // Max number of bits per GIF token code - GIFCodeMax = (1 SHL GIFCodeBits)-1;// Max GIF token code - // 12 bits = 4095 - StackSize = (2 SHL GIFCodeBits); // Size of decompression stack - TableSize = (1 SHL GIFCodeBits); // Size of decompression table - -procedure TGIFSubImage.Decompress(Stream: TStream); -var - table0 : array[0..TableSize-1] of integer; - table1 : array[0..TableSize-1] of integer; - firstcode, oldcode : integer; - buf : array[0..257] of BYTE; - - Dest : PAnsiChar; - v , - xpos, ypos, pass : integer; - - stack : array[0..StackSize-1] of integer; - Source : ^integer; - BitsPerCode : integer; // number of CodeTableBits/code - InitialBitsPerCode : BYTE; - - MaxCode : integer; // maximum code, given BitsPerCode - MaxCodeSize : integer; - ClearCode : integer; // Special code to signal "Clear table" - EOFCode : integer; // Special code to signal EOF - step : integer; - i : integer; - - StartBit , // Index of bit buffer start - LastBit , // Index of last bit in buffer - LastByte : integer; // Index of last byte in buffer - get_done , - return_clear , - ZeroBlock : boolean; - ClearValue : BYTE; -{$ifdef DEBUG_DECOMPRESSPERFORMANCE} - TimeStartDecompress , - TimeStopDecompress : DWORD; -{$endif} - - function nextCode(BitsPerCode: integer): integer; - const - masks: array[0..15] of integer = - ($0000, $0001, $0003, $0007, - $000f, $001f, $003f, $007f, - $00ff, $01ff, $03ff, $07ff, - $0fff, $1fff, $3fff, $7fff); - var - StartIndex, EndIndex : integer; - ret : integer; - EndBit : integer; - count : BYTE; - begin - if (return_clear) then - begin - return_clear := False; - Result := ClearCode; - exit; - end; - - EndBit := StartBit + BitsPerCode; - - if (EndBit >= LastBit) then - begin - if (get_done) then - begin - if (StartBit >= LastBit) then - Warning(gsWarning, sDecodeTooFewBits); - Result := -1; - exit; - end; - buf[0] := buf[LastByte-2]; - buf[1] := buf[LastByte-1]; - - if (Stream.Read(count, 1) <> 1) then - begin - Result := -1; - exit; - end; - if (count = 0) then - begin - ZeroBlock := True; - get_done := TRUE; - end else - begin - // Handle premature end of file - if (Stream.Size - Stream.Position < Count) then - begin - Warning(gsWarning, sOutOfData); - // Not enough data left - Just read as much as we can get - Count := Stream.Size - Stream.Position; - end; - if (Count <> 0) then - ReadCheck(Stream, Buf[2], Count); - end; - - LastByte := 2 + count; - StartBit := (StartBit - LastBit) + 16; - LastBit := LastByte * 8; - - EndBit := StartBit + BitsPerCode; - end; - - EndIndex := EndBit DIV 8; - StartIndex := StartBit DIV 8; - - ASSERT(StartIndex <= high(buf), 'StartIndex too large'); - if (StartIndex = EndIndex) then - ret := buf[StartIndex] - else - if (StartIndex + 1 = EndIndex) then - ret := buf[StartIndex] OR (buf[StartIndex+1] SHL 8) - else - ret := buf[StartIndex] OR (buf[StartIndex+1] SHL 8) OR (buf[StartIndex+2] SHL 16); - - ret := (ret SHR (StartBit AND $0007)) AND masks[BitsPerCode]; - - Inc(StartBit, BitsPerCode); - - Result := ret; - end; - - function NextLZW: integer; - var - code, incode : integer; - i : integer; - b : BYTE; - begin - code := nextCode(BitsPerCode); - while (code >= 0) do - begin - if (code = ClearCode) then - begin - ASSERT(ClearCode < TableSize, 'ClearCode too large'); - for i := 0 to ClearCode-1 do - begin - table0[i] := 0; - table1[i] := i; - end; - for i := ClearCode to TableSize-1 do - begin - table0[i] := 0; - table1[i] := 0; - end; - BitsPerCode := InitialBitsPerCode+1; - MaxCodeSize := 2 * ClearCode; - MaxCode := ClearCode + 2; - Source := @stack; - repeat - firstcode := nextCode(BitsPerCode); - oldcode := firstcode; - until (firstcode <> ClearCode); - - Result := firstcode; - exit; - end; - if (code = EOFCode) then - begin - Result := -2; - if (ZeroBlock) then - exit; - // Eat rest of data blocks - if (Stream.Read(b, 1) <> 1) then - exit; - while (b <> 0) do - begin - Stream.Seek(b, soFromCurrent); - if (Stream.Read(b, 1) <> 1) then - exit; - end; - exit; - end; - - incode := code; - - if (code >= MaxCode) then - begin - Source^ := firstcode; - Inc(Source); - code := oldcode; - end; - - ASSERT(Code < TableSize, 'Code too large'); - while (code >= ClearCode) do - begin - Source^ := table1[code]; - Inc(Source); - if (code = table0[code]) then - Error(sDecodeCircular); - code := table0[code]; - ASSERT(Code < TableSize, 'Code too large'); - end; - - firstcode := table1[code]; - Source^ := firstcode; - Inc(Source); - - code := MaxCode; - if (code <= GIFCodeMax) then - begin - table0[code] := oldcode; - table1[code] := firstcode; - Inc(MaxCode); - if ((MaxCode >= MaxCodeSize) and (MaxCodeSize <= GIFCodeMax)) then - begin - MaxCodeSize := MaxCodeSize * 2; - Inc(BitsPerCode); - end; - end; - - oldcode := incode; - - if (longInt(Source) > longInt(@stack)) then - begin - Dec(Source); - Result := Source^; - exit; - end - end; - Result := code; - end; - - function readLZW: integer; - begin - if (longInt(Source) > longInt(@stack)) then - begin - Dec(Source); - Result := Source^; - end else - Result := NextLZW; - end; - -begin - NewImage; - - // Clear image data in case decompress doesn't complete - if (Transparent) then - // Clear to transparent color - ClearValue := GraphicControlExtension.GetTransparentColorIndex - else - // Clear to first color - ClearValue := 0; - - FillChar(FData^, FDataSize, ClearValue); - -{$ifdef DEBUG_DECOMPRESSPERFORMANCE} - TimeStartDecompress := timeGetTime; -{$endif} - - (* - ** Read initial code size in bits from stream - *) - if (Stream.Read(InitialBitsPerCode, 1) <> 1) then - exit; -// 2006.07.29 -> - if InitialBitsPerCode > 8 then - InitialBitsPerCode := 8; -// 2006.07.29 <- - (* - ** Initialize the Compression routines - *) - BitsPerCode := InitialBitsPerCode + 1; - ClearCode := 1 SHL InitialBitsPerCode; - EOFCode := ClearCode + 1; - MaxCodeSize := 2 * ClearCode; - MaxCode := ClearCode + 2; - - StartBit := 0; - LastBit := 0; - LastByte := 2; - - ZeroBlock := False; - get_done := False; - return_clear := TRUE; - - Source := @stack; - - try - if (Interlaced) then - begin - ypos := 0; - pass := 0; - step := 8; - - for i := 0 to Height-1 do - begin - Dest := FData + Width * ypos; - for xpos := 0 to width-1 do - begin - v := readLZW; - if (v < 0) then - exit; - Dest^ := AnsiChar(v); - Inc(Dest); - end; - Inc(ypos, step); - if (ypos >= height) then - repeat - if (pass > 0) then - step := step DIV 2; - Inc(pass); - ypos := step DIV 2; - until (ypos < height); - end; - end else - begin - Dest := FData; - for ypos := 0 to (height * width)-1 do - begin - v := readLZW; - if (v < 0) then - exit; - Dest^ := AnsiChar(v); - Inc(Dest); - end; - end; - finally - if (readLZW >= 0) then - ; -// raise GIFException.Create('Too much input data, ignoring extra...'); - end; -{$ifdef DEBUG_DECOMPRESSPERFORMANCE} - TimeStopDecompress := timeGetTime; - ShowMessage(format('Decompressed %d pixels in %d mS, Rate %d pixels/mS', - [Height*Width, TimeStopDecompress-TimeStartDecompress, - (Height*Width) DIV (TimeStopDecompress-TimeStartDecompress+1)])); -{$endif} -end; - -//////////////////////////////////////////////////////////////////////////////// -// -// LZW Encoder stuff -// -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -// LZW Encoder THashTable -//////////////////////////////////////////////////////////////////////////////// -const - HashKeyBits = 13; // Max number of bits per Hash Key - - HashSize = 8009; // Size of hash table - // Must be prime - // Must be > than HashMaxCode - // Must be < than HashMaxKey - - HashKeyMax = (1 SHL HashKeyBits)-1;// Max hash key value - // 13 bits = 8191 - - HashKeyMask = HashKeyMax; // $1FFF - GIFCodeMask = GIFCodeMax; // $0FFF - - HashEmpty = $000FFFFF; // 20 bits - -type - // A Hash Key is 20 bits wide. - // - The lower 8 bits are the postfix character (the new pixel). - // - The upper 12 bits are the prefix code (the GIF token). - // A KeyInt must be able to represent the integer values -1..(2^20)-1 - KeyInt = longInt; // 32 bits - CodeInt = SmallInt; // 16 bits - - THashArray = array[0..HashSize-1] of KeyInt; - PHashArray = ^THashArray; - - THashTable = class -{$ifdef DEBUG_HASHPERFORMANCE} - CountLookupFound : longInt; - CountMissFound : longInt; - CountLookupNotFound : longInt; - CountMissNotFound : longInt; -{$endif} - HashTable: PHashArray; - public - constructor Create; - destructor Destroy; override; - procedure Clear; - procedure Insert(Key: KeyInt; Code: CodeInt); - function Lookup(Key: KeyInt): CodeInt; - end; - -function HashKey(Key: KeyInt): CodeInt; -begin - Result := ((Key SHR (GIFCodeBits-8)) XOR Key) MOD HashSize; -end; - -function NextHashKey(HKey: CodeInt): CodeInt; -var - disp : CodeInt; -begin - (* - ** secondary hash (after G. Knott) - *) - disp := HashSize - HKey; - if (HKey = 0) then - disp := 1; -// disp := 13; // disp should be prime relative to HashSize, but - // it doesn't seem to matter here... - dec(HKey, disp); - if (HKey < 0) then - inc(HKey, HashSize); - Result := HKey; -end; - - -constructor THashTable.Create; -begin - ASSERT(longInt($FFFFFFFF) = -1, 'TGIFImage implementation assumes $FFFFFFFF = -1'); - - inherited Create; - GetMem(HashTable, sizeof(THashArray)); - Clear; -{$ifdef DEBUG_HASHPERFORMANCE} - CountLookupFound := 0; - CountMissFound := 0; - CountLookupNotFound := 0; - CountMissNotFound := 0; -{$endif} -end; - -destructor THashTable.Destroy; -begin -{$ifdef DEBUG_HASHPERFORMANCE} - ShowMessage( - Format('Found: %d HitRate: %.2f', - [CountLookupFound, (CountLookupFound+1)/(CountMissFound+1)])+#13+ - Format('Not found: %d HitRate: %.2f', - [CountLookupNotFound, (CountLookupNotFound+1)/(CountMissNotFound+1)])); -{$endif} - FreeMem(HashTable); - inherited Destroy; -end; - -// Clear hash table and fill with empty slots (doh!) -procedure THashTable.Clear; -{$ifdef DEBUG_HASHFILLFACTOR} -var - i , - Count : longInt; -{$endif} -begin -{$ifdef DEBUG_HASHFILLFACTOR} - Count := 0; - for i := 0 to HashSize-1 do - if (HashTable[i] SHR GIFCodeBits <> HashEmpty) then - inc(Count); - ShowMessage(format('Size: %d, Filled: %d, Rate %.4f', - [HashSize, Count, Count/HashSize])); -{$endif} - - FillChar(HashTable^, sizeof(THashArray), $FF); -end; - -// Insert new key/value pair into hash table -procedure THashTable.Insert(Key: KeyInt; Code: CodeInt); -var - HKey : CodeInt; -begin - // Create hash key from prefix string - HKey := HashKey(Key); - - // Scan for empty slot - // while (HashTable[HKey] SHR GIFCodeBits <> HashEmpty) do { Unoptimized } - while (HashTable[HKey] AND (HashEmpty SHL GIFCodeBits) <> (HashEmpty SHL GIFCodeBits)) do { Optimized } - HKey := NextHashKey(HKey); - // Fill slot with key/value pair - HashTable[HKey] := (Key SHL GIFCodeBits) OR (Code AND GIFCodeMask); -end; - -// Search for key in hash table. -// Returns value if found or -1 if not -function THashTable.Lookup(Key: KeyInt): CodeInt; -var - HKey : CodeInt; - HTKey : KeyInt; -{$ifdef DEBUG_HASHPERFORMANCE} - n : LongInt; -{$endif} -begin - // Create hash key from prefix string - HKey := HashKey(Key); - -{$ifdef DEBUG_HASHPERFORMANCE} - n := 0; -{$endif} - // Scan table for key - // HTKey := HashTable[HKey] SHR GIFCodeBits; { Unoptimized } - Key := Key SHL GIFCodeBits; { Optimized } - HTKey := HashTable[HKey] AND (HashEmpty SHL GIFCodeBits); { Optimized } - // while (HTKey <> HashEmpty) do { Unoptimized } - while (HTKey <> HashEmpty SHL GIFCodeBits) do { Optimized } - begin - if (Key = HTKey) then - begin - // Extract and return value - Result := HashTable[HKey] AND GIFCodeMask; -{$ifdef DEBUG_HASHPERFORMANCE} - inc(CountLookupFound); - inc(CountMissFound, n); -{$endif} - exit; - end; -{$ifdef DEBUG_HASHPERFORMANCE} - inc(n); -{$endif} - // Try next slot - HKey := NextHashKey(HKey); - // HTKey := HashTable[HKey] SHR GIFCodeBits; { Unoptimized } - HTKey := HashTable[HKey] AND (HashEmpty SHL GIFCodeBits); { Optimized } - end; - // Found empty slot - key doesn't exist - Result := -1; -{$ifdef DEBUG_HASHPERFORMANCE} - inc(CountLookupNotFound); - inc(CountMissNotFound, n); -{$endif} -end; - -//////////////////////////////////////////////////////////////////////////////// -// TGIFStream - Abstract GIF block stream -// -// Descendants from TGIFStream either reads or writes data in blocks -// of up to 255 bytes. These blocks are organized as a leading byte -// containing the number of bytes in the block (exclusing the count -// byte itself), followed by the data (up to 254 bytes of data). -//////////////////////////////////////////////////////////////////////////////// -type - TGIFStream = class(TStream) - private - FOnWarning : TGIFWarning; - FStream : TStream; - FOnProgress : TNotifyEvent; - FBuffer : array [BYTE] of AnsiChar; - FBufferCount : integer; - - protected - constructor Create(Stream: TStream); - - function Read(var Buffer; Count: Longint): Longint; override; - function Write(const Buffer; Count: Longint): Longint; override; - function Seek(Offset: Longint; Origin: Word): Longint; override; - - procedure Progress(Sender: TObject); dynamic; - property OnProgress: TNotifyEvent read FOnProgress write FOnProgress; - public - property Warning: TGIFWarning read FOnWarning write FOnWarning; - end; - -constructor TGIFStream.Create(Stream: TStream); -begin - inherited Create; - FStream := Stream; - FBufferCount := 1; // Reserve first byte of buffer for length -end; - -procedure TGIFStream.Progress(Sender: TObject); -begin - if Assigned(FOnProgress) then - FOnProgress(Sender); -end; - -function TGIFStream.Write(const Buffer; Count: Longint): Longint; -begin - raise Exception.Create(sInvalidStream); -end; - -function TGIFStream.Read(var Buffer; Count: Longint): Longint; -begin - raise Exception.Create(sInvalidStream); -end; - -function TGIFStream.Seek(Offset: Longint; Origin: Word): Longint; -begin - raise Exception.Create(sInvalidStream); -end; - -//////////////////////////////////////////////////////////////////////////////// -// TGIFReader - GIF block reader -//////////////////////////////////////////////////////////////////////////////// -type - TGIFReader = class(TGIFStream) - public - constructor Create(Stream: TStream); - - function Read(var Buffer; Count: Longint): Longint; override; - end; - -constructor TGIFReader.Create(Stream: TStream); -begin - inherited Create(Stream); - FBufferCount := 0; -end; - -function TGIFReader.Read(var Buffer; Count: Longint): Longint; -var - n : integer; - Dst : PAnsiChar; - size : BYTE; -begin - Dst := @Buffer; - Result := 0; - - while (Count > 0) do - begin - // Get data from buffer - while (FBufferCount > 0) and (Count > 0) do - begin - if (FBufferCount > Count) then - n := Count - else - n := FBufferCount; - Move(FBuffer, Dst^, n); - dec(FBufferCount, n); - dec(Count, n); - inc(Result, n); - inc(Dst, n); - end; - - // Refill buffer when it becomes empty - if (FBufferCount <= 0) then - begin - FStream.Read(size, 1); - {.TODO -oanme -cImprovement : Should be handled as a warning instead of an error. } - if (size >= 255) then - Error('GIF block too large'); - FBufferCount := size; - if (FBufferCount > 0) then - begin - n := FStream.Read(FBuffer, size); - if (n = FBufferCount) then - begin - Warning(self, gsWarning, sOutOfData); - break; - end; - end else - break; - end; - end; -end; - -//////////////////////////////////////////////////////////////////////////////// -// TGIFWriter - GIF block writer -//////////////////////////////////////////////////////////////////////////////// -type - TGIFWriter = class(TGIFStream) - private - FOutputDirty : boolean; - - protected - procedure FlushBuffer; - - public - constructor Create(Stream: TStream); - destructor Destroy; override; - - function Write(const Buffer; Count: Longint): Longint; override; - function WriteByte(Value: BYTE): Longint; - end; - -constructor TGIFWriter.Create(Stream: TStream); -begin - inherited Create(Stream); - FBufferCount := 1; // Reserve first byte of buffer for length - FOutputDirty := False; -end; - -destructor TGIFWriter.Destroy; -begin - inherited Destroy; - if (FOutputDirty) then - FlushBuffer; -end; - -procedure TGIFWriter.FlushBuffer; -begin - if (FBufferCount <= 0) then - exit; - - FBuffer[0] := AnsiChar(FBufferCount-1); // Block size excluding the count - FStream.WriteBuffer(FBuffer, FBufferCount); - FBufferCount := 1; // Reserve first byte of buffer for length - FOutputDirty := False; -end; - -function TGIFWriter.Write(const Buffer; Count: Longint): Longint; -var - n : integer; - Src : PAnsiChar; -begin - Result := Count; - FOutputDirty := True; - Src := @Buffer; - while (Count > 0) do - begin - // Move data to the internal buffer in 255 byte chunks - while (FBufferCount < sizeof(FBuffer)) and (Count > 0) do - begin - n := sizeof(FBuffer) - FBufferCount; - if (n > Count) then - n := Count; - Move(Src^, FBuffer[FBufferCount], n); - inc(Src, n); - inc(FBufferCount, n); - dec(Count, n); - end; - - // Flush the buffer when it is full - if (FBufferCount >= sizeof(FBuffer)) then - FlushBuffer; - end; -end; - -function TGIFWriter.WriteByte(Value: BYTE): Longint; -begin - Result := Write(Value, 1); -end; - -//////////////////////////////////////////////////////////////////////////////// -// TGIFEncoder - Abstract encoder -//////////////////////////////////////////////////////////////////////////////// -type - TGIFEncoder = class(TObject) - protected - FOnWarning : TGIFWarning; - MaxColor : integer; - BitsPerPixel : BYTE; // Bits per pixel of image - Stream : TStream; // Output stream - Width , // Width of image in pixels - Height : integer; // height of image in pixels - Interlace : boolean; // Interlace flag (True = interlaced image) - Data : PAnsiChar; // Pointer to pixel data - GIFStream : TGIFWriter; // Output buffer - - OutputBucket : longInt; // Output bit bucket - OutputBits : integer; // Current # of bits in bucket - - ClearFlag : Boolean; // True if dictionary has just been cleared - BitsPerCode , // Current # of bits per code - InitialBitsPerCode : integer; // Initial # of bits per code after - // dictionary has been cleared - MaxCode : CodeInt; // maximum code, given BitsPerCode - ClearCode : CodeInt; // Special output code to signal "Clear table" - EOFCode : CodeInt; // Special output code to signal EOF - BaseCode : CodeInt; // ... - - Pixel : PAnsiChar; // Pointer to current pixel - - cX , // Current X counter (Width - X) - Y : integer; // Current Y - Pass : integer; // Interlace pass - - function MaxCodesFromBits(Bits: integer): CodeInt; - procedure Output(Value: integer); virtual; - procedure Clear; virtual; - function BumpPixel: boolean; - procedure DoCompress; virtual; abstract; - public - procedure Compress(AStream: TStream; ABitsPerPixel: integer; - AWidth, AHeight: integer; AInterlace: boolean; AData: PAnsiChar; AMaxColor: integer); - property Warning: TGIFWarning read FOnWarning write FOnWarning; - end; - -// Calculate the maximum number of codes that a given number of bits can represent -// MaxCodes := (1^bits)-1 -function TGIFEncoder.MaxCodesFromBits(Bits: integer): CodeInt; -begin - Result := (CodeInt(1) SHL Bits) - 1; -end; - -// Stuff bits (variable sized codes) into a buffer and output them -// a byte at a time -procedure TGIFEncoder.Output(Value: integer); -const - BitBucketMask: array[0..16] of longInt = - ($0000, - $0001, $0003, $0007, $000F, - $001F, $003F, $007F, $00FF, - $01FF, $03FF, $07FF, $0FFF, - $1FFF, $3FFF, $7FFF, $FFFF); -begin - if (OutputBits > 0) then - OutputBucket := - (OutputBucket AND BitBucketMask[OutputBits]) OR (longInt(Value) SHL OutputBits) - else - OutputBucket := Value; - - inc(OutputBits, BitsPerCode); - - while (OutputBits >= 8) do - begin - GIFStream.WriteByte(OutputBucket AND $FF); - OutputBucket := OutputBucket SHR 8; - dec(OutputBits, 8); - end; - - if (Value = EOFCode) then - begin - // At EOF, write the rest of the buffer. - while (OutputBits > 0) do - begin - GIFStream.WriteByte(OutputBucket AND $FF); - OutputBucket := OutputBucket SHR 8; - dec(OutputBits, 8); - end; - end; -end; - -procedure TGIFEncoder.Clear; -begin - // just_cleared = 1; - ClearFlag := TRUE; - Output(ClearCode); -end; - -// Bump (X,Y) and data pointer to point to the next pixel -function TGIFEncoder.BumpPixel: boolean; -begin - // Bump the current X position - dec(cX); - - // If we are at the end of a scan line, set cX back to the beginning - // If we are interlaced, bump Y to the appropriate spot, otherwise, - // just increment it. - if (cX <= 0) then - begin - - if not(Interlace) then - begin - // Done - no more data - Result := False; - exit; - end; - - cX := Width; - case (Pass) of - 0: - begin - inc(Y, 8); - if (Y >= Height) then - begin - inc(Pass); - Y := 4; - end; - end; - 1: - begin - inc(Y, 8); - if (Y >= Height) then - begin - inc(Pass); - Y := 2; - end; - end; - 2: - begin - inc(Y, 4); - if (Y >= Height) then - begin - inc(Pass); - Y := 1; - end; - end; - 3: - inc(Y, 2); - end; - - if (Y >= height) then - begin - // Done - No more data - Result := False; - exit; - end; - Pixel := Data + (Y * Width); - end; - Result := True; -end; - - -procedure TGIFEncoder.Compress(AStream: TStream; ABitsPerPixel: integer; - AWidth, AHeight: integer; AInterlace: boolean; AData: PAnsiChar; AMaxColor: integer); -const - EndBlockByte = $00; // End of block marker -{$ifdef DEBUG_COMPRESSPERFORMANCE} -var - TimeStartCompress , - TimeStopCompress : DWORD; -{$endif} -begin - MaxColor := AMaxColor; - Stream := AStream; - BitsPerPixel := ABitsPerPixel; - Width := AWidth; - Height := AHeight; - Interlace := AInterlace; - Data := AData; - - if (BitsPerPixel <= 1) then - BitsPerPixel := 2; - - InitialBitsPerCode := BitsPerPixel + 1; - Stream.Write(BitsPerPixel, 1); - - // out_bits_init = init_bits; - BitsPerCode := InitialBitsPerCode; - MaxCode := MaxCodesFromBits(BitsPerCode); - - ClearCode := (1 SHL (InitialBitsPerCode - 1)); - EOFCode := ClearCode + 1; - BaseCode := EOFCode + 1; - - // Clear bit bucket - OutputBucket := 0; - OutputBits := 0; - - // Reset pixel counter - if (Interlace) then - cX := Width - else - cX := Width*Height; - // Reset row counter - Y := 0; - Pass := 0; - - GIFStream := TGIFWriter.Create(AStream); - try - GIFStream.Warning := Warning; - if (Data <> nil) and (Height > 0) and (Width > 0) then - begin -{$ifdef DEBUG_COMPRESSPERFORMANCE} - TimeStartCompress := timeGetTime; -{$endif} - - // Call compress implementation - DoCompress; - -{$ifdef DEBUG_COMPRESSPERFORMANCE} - TimeStopCompress := timeGetTime; - ShowMessage(format('Compressed %d pixels in %d mS, Rate %d pixels/mS', - [Height*Width, TimeStopCompress-TimeStartCompress, - DWORD(Height*Width) DIV (TimeStopCompress-TimeStartCompress+1)])); -{$endif} - // Output the final code. - Output(EOFCode); - end else - // Output the final code (and nothing else). - TGIFEncoder(self).Output(EOFCode); - finally - GIFStream.Free; - end; - - WriteByte(Stream, EndBlockByte); -end; - -//////////////////////////////////////////////////////////////////////////////// -// TRLEEncoder - RLE encoder -//////////////////////////////////////////////////////////////////////////////// -type - TRLEEncoder = class(TGIFEncoder) - private - MaxCodes : integer; - OutBumpInit , - OutClearInit : integer; - Prefix : integer; // Current run color - RunLengthTableMax , - RunLengthTablePixel , - OutCount , - OutClear , - OutBump : integer; - protected - function ComputeTriangleCount(count: integer; nrepcodes: integer): integer; - procedure MaxOutClear; - procedure ResetOutClear; - procedure FlushFromClear(Count: integer); - procedure FlushClearOrRepeat(Count: integer); - procedure FlushWithTable(Count: integer); - procedure Flush(RunLengthCount: integer); - procedure OutputPlain(Value: integer); - procedure Clear; override; - procedure DoCompress; override; - end; - - -procedure TRLEEncoder.Clear; -begin - OutBump := OutBumpInit; - OutClear := OutClearInit; - OutCount := 0; - RunLengthTableMax := 0; - - inherited Clear; - - BitsPerCode := InitialBitsPerCode; -end; - -procedure TRLEEncoder.OutputPlain(Value: integer); -begin - ClearFlag := False; - Output(Value); - inc(OutCount); - - if (OutCount >= OutBump) then - begin - inc(BitsPerCode); - inc(OutBump, 1 SHL (BitsPerCode - 1)); - end; - - if (OutCount >= OutClear) then - Clear; -end; - -function TRLEEncoder.ComputeTriangleCount(count: integer; nrepcodes: integer): integer; -var - PerRepeat : integer; - n : integer; - - function iSqrt(x: integer): integer; - var - r, v : integer; - begin - if (x < 2) then - begin - Result := x; - exit; - end else - begin - v := x; - r := 1; - while (v > 0) do - begin - v := v DIV 4; - r := r * 2; - end; - end; - - while (True) do - begin - v := ((x DIV r) + r) DIV 2; - if ((v = r) or (v = r+1)) then - begin - Result := r; - exit; - end; - r := v; - end; - end; - -begin - Result := 0; - PerRepeat := (nrepcodes * (nrepcodes+1)) DIV 2; - - while (Count >= PerRepeat) do - begin - inc(Result, nrepcodes); - dec(Count, PerRepeat); - end; - - if (Count > 0) then - begin - n := iSqrt(Count); - while ((n * (n+1)) >= 2*Count) do - dec(n); - while ((n * (n+1)) < 2*Count) do - inc(n); - inc(Result, n); - end; -end; - -procedure TRLEEncoder.MaxOutClear; -begin - OutClear := MaxCodes; -end; - -procedure TRLEEncoder.ResetOutClear; -begin - OutClear := OutClearInit; - if (OutCount >= OutClear) then - Clear; -end; - -procedure TRLEEncoder.FlushFromClear(Count: integer); -var - n : integer; -begin - MaxOutClear; - RunLengthTablePixel := Prefix; - n := 1; - while (Count > 0) do - begin - if (n = 1) then - begin - RunLengthTableMax := 1; - OutputPlain(Prefix); - dec(Count); - end else - if (Count >= n) then - begin - RunLengthTableMax := n; - OutputPlain(BaseCode + n - 2); - dec(Count, n); - end else - if (Count = 1) then - begin - inc(RunLengthTableMax); - OutputPlain(Prefix); - break; - end else - begin - inc(RunLengthTableMax); - OutputPlain(BaseCode + Count - 2); - break; - end; - - if (OutCount = 0) then - n := 1 - else - inc(n); - end; - ResetOutClear; -end; - -procedure TRLEEncoder.FlushClearOrRepeat(Count: integer); -var - WithClear : integer; -begin - WithClear := 1 + ComputeTriangleCount(Count, MaxCodes); - - if (WithClear < Count) then - begin - Clear; - FlushFromClear(Count); - end else - while (Count > 0) do - begin - OutputPlain(Prefix); - dec(Count); - end; -end; - -procedure TRLEEncoder.FlushWithTable(Count: integer); -var - RepeatMax , - RepeatLeft , - LeftOver : integer; -begin - RepeatMax := Count DIV RunLengthTableMax; - LeftOver := Count MOD RunLengthTableMax; - if (LeftOver <> 0) then - RepeatLeft := 1 - else - RepeatLeft := 0; - - if (OutCount + RepeatMax + RepeatLeft > MaxCodes) then - begin - RepeatMax := MaxCodes - OutCount; - LeftOver := Count - (RepeatMax * RunLengthTableMax); - RepeatLeft := 1 + ComputeTriangleCount(LeftOver, MaxCodes); - end; - - if (1 + ComputeTriangleCount(Count, MaxCodes) < RepeatMax + RepeatLeft) then - begin - Clear; - FlushFromClear(Count); - exit; - end; - MaxOutClear; - - while (RepeatMax > 0) do - begin - OutputPlain(BaseCode + RunLengthTableMax-2); - dec(RepeatMax); - end; - - if (LeftOver > 0) then - begin - if (ClearFlag) then - FlushFromClear(LeftOver) - else if (LeftOver = 1) then - OutputPlain(Prefix) - else - OutputPlain(BaseCode + LeftOver - 2); - end; - ResetOutClear; -end; - -procedure TRLEEncoder.Flush(RunLengthCount: integer); -begin - if (RunLengthCount = 1) then - begin - OutputPlain(Prefix); - exit; - end; - - if (ClearFlag) then - FlushFromClear(RunLengthCount) - else if ((RunLengthTableMax < 2) or (RunLengthTablePixel <> Prefix)) then - FlushClearOrRepeat(RunLengthCount) - else - FlushWithTable(RunLengthCount); -end; - -procedure TRLEEncoder.DoCompress; -var - Color : CodeInt; - RunLengthCount : integer; - -begin - OutBumpInit := ClearCode - 1; - - // For images with a lot of runs, making OutClearInit larger will - // give better compression. - if (BitsPerPixel <= 3) then - OutClearInit := 9 - else - OutClearInit := OutBumpInit - 1; - - // max_ocodes = (1 << GIFBITS) - ((1 << (out_bits_init - 1)) + 3); - // <=> MaxCodes := (1 SHL GIFCodeBits) - ((1 SHL (BitsPerCode - 1)) + 3); - // <=> MaxCodes := (1 SHL GIFCodeBits) - ((1 SHL (InitialBitsPerCode - 1)) + 3); - // <=> MaxCodes := (1 SHL GIFCodeBits) - (ClearCode + 3); - // <=> MaxCodes := (1 SHL GIFCodeBits) - (EOFCode + 2); - // <=> MaxCodes := (1 SHL GIFCodeBits) - (BaseCode + 1); - // <=> MaxCodes := MaxCodesFromBits(GIFCodeBits) - BaseCode; - MaxCodes := MaxCodesFromBits(GIFCodeBits) - BaseCode; - - Clear; - RunLengthCount := 0; - - Pixel := Data; - Prefix := -1; // Dummy value to make Color <> Prefix - repeat - // Fetch the next pixel - Color := CodeInt(Pixel^); - inc(Pixel); - - if (Color >= MaxColor) then - Error(sInvalidColor); - - if (RunLengthCount > 0) and (Color <> Prefix) then - begin - // End of current run - Flush(RunLengthCount); - RunLengthCount := 0; - end; - - if (Color = Prefix) then - // Increment run length - inc(RunLengthCount) - else - begin - // Start new run - Prefix := Color; - RunLengthCount := 1; - end; - until not(BumpPixel); - Flush(RunLengthCount); -end; - -//////////////////////////////////////////////////////////////////////////////// -// TLZWEncoder - LZW encoder -//////////////////////////////////////////////////////////////////////////////// -const - TableMaxMaxCode = (1 SHL GIFCodeBits); // - TableMaxFill = TableMaxMaxCode-1; // Clear table when it fills to - // this point. - // Note: Must be <= GIFCodeMax -type - TLZWEncoder = class(TGIFEncoder) - private - Prefix : CodeInt; // Current run color - FreeEntry : CodeInt; // next unused code in table - HashTable : THashTable; - protected - procedure Output(Value: integer); override; - procedure Clear; override; - procedure DoCompress; override; - end; - - -procedure TLZWEncoder.Output(Value: integer); -begin - inherited Output(Value); - - // If the next entry is going to be too big for the code size, - // then increase it, if possible. - if (FreeEntry > MaxCode) or (ClearFlag) then - begin - if (ClearFlag) then - begin - BitsPerCode := InitialBitsPerCode; - MaxCode := MaxCodesFromBits(BitsPerCode); - ClearFlag := False; - end else - begin - inc(BitsPerCode); - if (BitsPerCode = GIFCodeBits) then - MaxCode := TableMaxMaxCode - else - MaxCode := MaxCodesFromBits(BitsPerCode); - end; - end; -end; - -procedure TLZWEncoder.Clear; -begin - inherited Clear; - HashTable.Clear; - FreeEntry := ClearCode + 2; -end; - - -procedure TLZWEncoder.DoCompress; -var - Color : AnsiChar; - NewKey : KeyInt; - NewCode : CodeInt; - -begin - HashTable := THashTable.Create; - try - // clear hash table and sync decoder - Clear; - - Pixel := Data; - Prefix := CodeInt(Pixel^); - inc(Pixel); - if (Prefix >= MaxColor) then - Error(sInvalidColor); - while (BumpPixel) do - begin - // Fetch the next pixel - Color := Pixel^; - inc(Pixel); - if (ord(Color) >= MaxColor) then - Error(sInvalidColor); - - // Append Postfix to Prefix and lookup in table... - NewKey := (KeyInt(Prefix) SHL 8) OR ord(Color); - NewCode := HashTable.Lookup(NewKey); - if (NewCode >= 0) then - begin - // ...if found, get next pixel - Prefix := NewCode; - continue; - end; - - // ...if not found, output and start over - Output(Prefix); - Prefix := CodeInt(Color); - - if (FreeEntry < TableMaxFill) then - begin - HashTable.Insert(NewKey, FreeEntry); - inc(FreeEntry); - end else - Clear; - end; - Output(Prefix); - finally - HashTable.Free; - end; -end; - -//////////////////////////////////////////////////////////////////////////////// -// -// TGIFSubImage -// -//////////////////////////////////////////////////////////////////////////////// - -///////////////////////////////////////////////////////////////////////// -// TGIFSubImage.Compress -///////////////////////////////////////////////////////////////////////// -procedure TGIFSubImage.Compress(Stream: TStream); -var - Encoder : TGIFEncoder; - BitsPerPixel : BYTE; - MaxColors : integer; -begin - if (ColorMap.Count > 0) then - begin - MaxColors := ColorMap.Count; - BitsPerPixel := ColorMap.BitsPerPixel - end else - begin - BitsPerPixel := Image.BitsPerPixel; - MaxColors := 1 SHL BitsPerPixel; - end; - - // Create a RLE or LZW GIF encoder - if (Image.Compression = gcRLE) then - Encoder := TRLEEncoder.Create - else - Encoder := TLZWEncoder.Create; - try - Encoder.Warning := Image.Warning; - Encoder.Compress(Stream, BitsPerPixel, Width, Height, Interlaced, FData, MaxColors); - finally - Encoder.Free; - end; -end; - -function TGIFExtensionList.GetExtension(Index: Integer): TGIFExtension; -begin - Result := TGIFExtension(Items[Index]); -end; - -procedure TGIFExtensionList.SetExtension(Index: Integer; Extension: TGIFExtension); -begin - Items[Index] := Extension; -end; - -procedure TGIFExtensionList.LoadFromStream(Stream: TStream; Parent: TObject); -var - b : BYTE; - Extension : TGIFExtension; - ExtensionClass : TGIFExtensionClass; -begin - // Peek ahead to determine block type - if (Stream.Read(b, 1) <> 1) then - exit; - while not(b in [bsTrailer, bsImageDescriptor]) do - begin - if (b = bsExtensionIntroducer) then - begin - ExtensionClass := TGIFExtension.FindExtension(Stream); - if (ExtensionClass = nil) then - Error(sUnknownExtension); - Stream.Seek(-1, soFromCurrent); - Extension := ExtensionClass.Create(Parent as TGIFSubImage); - try - Extension.LoadFromStream(Stream); - Add(Extension); - except - Extension.Free; - raise; - end; - end else - begin - Warning(gsWarning, sBadExtensionLabel); - break; - end; - if (Stream.Read(b, 1) <> 1) then - exit; - end; - Stream.Seek(-1, soFromCurrent); -end; - -const - { image descriptor bit masks } - idLocalColorTable = $80; { set if a local color table follows } - idInterlaced = $40; { set if image is interlaced } - idSort = $20; { set if color table is sorted } - idReserved = $0C; { reserved - must be set to $00 } - idColorTableSize = $07; { size of color table as above } - -constructor TGIFSubImage.Create(GIFImage: TGIFImage); -begin - inherited Create(GIFImage); - FExtensions := TGIFExtensionList.Create(GIFImage); - FColorMap := TGIFLocalColorMap.Create(self); - FImageDescriptor.Separator := bsImageDescriptor; - FImageDescriptor.Left := 0; - FImageDescriptor.Top := 0; - FImageDescriptor.Width := 0; - FImageDescriptor.Height := 0; - FImageDescriptor.PackedFields := 0; - FBitmap := nil; - FMask := 0; - FNeedMask := True; - FData := nil; - FDataSize := 0; - FTransparent := False; - FGCE := nil; - // Remember to synchronize with TGIFSubImage.Clear -end; - -destructor TGIFSubImage.Destroy; -begin - if (FGIFImage <> nil) then - FGIFImage.Images.Remove(self); - Clear; - FExtensions.Free; - FColorMap.Free; - if (FLocalPalette <> 0) then - DeleteObject(FLocalPalette); - inherited Destroy; -end; - -procedure TGIFSubImage.Clear; -begin - FExtensions.Clear; - FColorMap.Clear; - FreeImage; - Height := 0; - Width := 0; - FTransparent := False; - FGCE := nil; - FreeBitmap; - FreeMask; - // Remember to synchronize with TGIFSubImage.Create -end; - -function TGIFSubImage.GetEmpty: Boolean; -begin - Result := ((FData = nil) or (FDataSize = 0) or (Height = 0) or (Width = 0)); -end; - -function TGIFSubImage.GetPalette: HPALETTE; -begin - if (FBitmap <> nil) and (FBitmap.Palette <> 0) then - // Use bitmaps own palette if possible - Result := FBitmap.Palette - else if (FLocalPalette <> 0) then - // Or a previously exported local palette - Result := FLocalPalette - else if (Image.DoDither) then - begin - // or create a new dither palette - FLocalPalette := WebPalette; - Result := FLocalPalette; - end - else if (ColorMap.Count > 0) then - begin - // or create a new if first time - FLocalPalette := ColorMap.ExportPalette; - Result := FLocalPalette; - end else - // Use global palette if everything else fails - Result := Image.Palette; -end; - -procedure TGIFSubImage.SetPalette(Value: HPalette); -var - NeedNewBitmap : boolean; -begin - if (Value <> FLocalPalette) then - begin - // Zap old palette - if (FLocalPalette <> 0) then - DeleteObject(FLocalPalette); - // Zap bitmap unless new palette is same as bitmaps own - NeedNewBitmap := (FBitmap <> nil) and (Value <> FBitmap.Palette); - - // Use new palette - FLocalPalette := Value; - if (NeedNewBitmap) then - begin - // Need to create new bitmap and repaint - FreeBitmap; - Image.PaletteModified := True; - Image.Changed(Self); - end; - end; -end; - -procedure TGIFSubImage.NeedImage; -begin - if (FData = nil) then - NewImage; - if (FDataSize = 0) then - Error(sEmptyImage); -end; - -procedure TGIFSubImage.NewImage; -var - NewSize : longInt; -begin - FreeImage; - NewSize := Height * Width; - if (NewSize <> 0) then - begin - GetMem(FData, NewSize); - FillChar(FData^, NewSize, 0); - end else - FData := nil; - FDataSize := NewSize; -end; - -procedure TGIFSubImage.FreeImage; -begin - if (FData <> nil) then - FreeMem(FData); - FDataSize := 0; - FData := nil; -end; - -function TGIFSubImage.GetHasBitmap: boolean; -begin - Result := (FBitmap <> nil); -end; - -procedure TGIFSubImage.SetHasBitmap(Value: boolean); -begin - if (Value <> (FBitmap <> nil)) then - begin - if (Value) then - Bitmap // Referencing Bitmap will automatically create it - else - FreeBitmap; - end; -end; - -procedure TGIFSubImage.NewBitmap; -begin - FreeBitmap; - FBitmap := TBitmap.Create; -end; - -procedure TGIFSubImage.FreeBitmap; -begin - if (FBitmap <> nil) then - begin - FBitmap.Free; - FBitmap := nil; - end; -end; - -procedure TGIFSubImage.FreeMask; -begin - if (FMask <> 0) then - begin - DeleteObject(FMask); - FMask := 0; - end; - FNeedMask := True; -end; - -function TGIFSubImage.HasMask: boolean; -begin - if (FNeedMask) and (Transparent) then - begin - // Zap old bitmap - FreeBitmap; - // Create new bitmap and mask - GetBitmap; - end; - Result := (FMask <> 0); -end; - -function TGIFSubImage.GetBounds(Index: integer): WORD; -begin - case (Index) of - 1: Result := FImageDescriptor.Left; - 2: Result := FImageDescriptor.Top; - 3: Result := FImageDescriptor.Width; - 4: Result := FImageDescriptor.Height; - else - Result := 0; // To avoid compiler warnings - end; -end; - -procedure TGIFSubImage.SetBounds(Index: integer; Value: WORD); -begin - case (Index) of - 1: DoSetBounds(Value, FImageDescriptor.Top, FImageDescriptor.Width, FImageDescriptor.Height); - 2: DoSetBounds(FImageDescriptor.Left, Value, FImageDescriptor.Width, FImageDescriptor.Height); - 3: DoSetBounds(FImageDescriptor.Left, FImageDescriptor.Top, Value, FImageDescriptor.Height); - 4: DoSetBounds(FImageDescriptor.Left, FImageDescriptor.Top, FImageDescriptor.Width, Value); - end; -end; - -{$IFOPT R+} - {$DEFINE R_PLUS} - {$RANGECHECKS OFF} -{$ENDIF} -function TGIFSubImage.DoGetDitherBitmap: TBitmap; -var - ColorLookup : TColorLookup; - Ditherer : TDitherEngine; - DIBResult : TDIB; - Src : PAnsiChar; - Dst : PAnsiChar; - - Row : integer; - Color : TGIFColor; - ColMap : PColorMap; - Index : byte; - TransparentIndex : byte; - IsTransparent : boolean; - WasTransparent : boolean; - MappedTransparentIndex: AnsiChar; - - MaskBits : PAnsiChar; - MaskDest : PAnsiChar; - MaskRow : PAnsiChar; - MaskRowWidth , - MaskRowBitWidth : integer; - Bit , - RightBit : BYTE; - -begin - Result := TBitmap.Create; - try - -{$IFNDEF VER9x} - if (Width*Height > BitmapAllocationThreshold) then - SetPixelFormat(Result, pf1bit); // To reduce resource consumption of resize -{$ENDIF} - - if (Empty) then - begin - // Set bitmap width and height - Result.Width := Width; - Result.Height := Height; - - // Build and copy palette to bitmap - Result.Palette := CopyPalette(Palette); - - exit; - end; - - ColorLookup := nil; - Ditherer := nil; - DIBResult := nil; - try // Protect above resources - ColorLookup := TNetscapeColorLookup.Create(Palette); - Ditherer := TFloydSteinbergDitherer.Create(Width, ColorLookup); - // Get DIB buffer for scanline operations - // It is assumed that the source palette is the 216 color Netscape palette - DIBResult := TDIBWriter.Create(Result, pf8bit, Width, Height, Palette); - - // Determine if this image is transparent - ColMap := ActiveColorMap.Data; - IsTransparent := FNeedMask and Transparent; - WasTransparent := False; - FNeedMask := False; - TransparentIndex := 0; - MappedTransparentIndex := #0; - if (FMask = 0) and (IsTransparent) then - begin - IsTransparent := True; - TransparentIndex := GraphicControlExtension.TransparentColorIndex; - Color := ColMap[ord(TransparentIndex)]; - MappedTransparentIndex := AnsiChar(Color.Blue DIV 51 + - MulDiv(6, Color.Green, 51) + MulDiv(36, Color.Red, 51)+1); - end; - - // Allocate bit buffer for transparency mask - MaskDest := nil; - Bit := $00; - if (IsTransparent) then - begin - MaskRowWidth := ((Width+15) DIV 16) * 2; - MaskRowBitWidth := (Width+7) DIV 8; - RightBit := $01 SHL ((8 - (Width AND $0007)) AND $0007); - GetMem(MaskBits, MaskRowWidth * Height); - FillChar(MaskBits^, MaskRowWidth * Height, 0); - end else - begin - MaskBits := nil; - MaskRowWidth := 0; - MaskRowBitWidth := 0; - RightBit := $00; - end; - - try - // Process the image - Row := 0; - MaskRow := MaskBits; - Src := FData; - while (Row < Height) do - begin - if ((Row AND $1F) = 0) then - Image.Progress(Self, psRunning, MulDiv(Row, 100, Height), - False, Rect(0,0,0,0), sProgressRendering); - - Dst := DIBResult.ScanLine[Row]; - if (IsTransparent) then - begin - // Preset all pixels to transparent - FillChar(Dst^, Width, ord(MappedTransparentIndex)); - if (Ditherer.Direction = 1) then - begin - MaskDest := MaskRow; - Bit := $80; - end else - begin - MaskDest := MaskRow + MaskRowBitWidth-1; - Bit := RightBit; - end; - end; - inc(Dst, Ditherer.Column); - - while (Ditherer.Column < Ditherer.Width) and (Ditherer.Column >= 0) do - begin - Index := ord(Src^); - Color := ColMap[ord(Index)]; - - if (IsTransparent) and (Index = TransparentIndex) then - begin - MaskDest^ := AnsiChar(byte(MaskDest^) OR Bit); - WasTransparent := True; - Ditherer.NextColumn; - end else - begin - // Dither and map a single pixel - Dst^ := Ditherer.Dither(Color.Red, Color.Green, Color.Blue, - Color.Red, Color.Green, Color.Blue); - end; - - if (IsTransparent) then - begin - if (Ditherer.Direction = 1) then - begin - Bit := Bit SHR 1; - if (Bit = $00) then - begin - Bit := $80; - inc(MaskDest, 1); - end; - end else - begin - Bit := Bit SHL 1; - if (Bit = $00) then - begin - Bit := $01; - dec(MaskDest, 1); - end; - end; - end; - - inc(Src, Ditherer.Direction); - inc(Dst, Ditherer.Direction); - end; - - if (IsTransparent) then - Inc(MaskRow, MaskRowWidth); - Inc(Row); - inc(Src, Width-Ditherer.Direction); - Ditherer.NextLine; - end; - - // Transparent paint needs a mask bitmap - if (IsTransparent) and (WasTransparent) then - FMask := CreateBitmap(Width, Height, 1, 1, MaskBits); - finally - if (MaskBits <> nil) then - FreeMem(MaskBits); - end; - finally - if (ColorLookup <> nil) then - ColorLookup.Free; - if (Ditherer <> nil) then - Ditherer.Free; - if (DIBResult <> nil) then - DIBResult.Free; - end; - except - Result.Free; - raise; - end; -end; -{$IFDEF R_PLUS} - {$RANGECHECKS ON} - {$UNDEF R_PLUS} -{$ENDIF} - -function TGIFSubImage.DoGetBitmap: TBitmap; -var - ScanLineRow : Integer; - DIBResult : TDIB; - DestScanLine , - Src : PAnsiChar; - TransparentIndex : byte; - IsTransparent : boolean; - WasTransparent : boolean; - - MaskBits : PAnsiChar; - MaskDest : PAnsiChar; - MaskRow : PAnsiChar; - MaskRowWidth : integer; - Col : integer; - MaskByte : byte; - Bit : byte; -begin - Result := TBitmap.Create; - try - -{$IFNDEF VER9x} - if (Width*Height > BitmapAllocationThreshold) then - SetPixelFormat(Result, pf1bit); // To reduce resource consumption of resize -{$ENDIF} - - if (Empty) then - begin - // Set bitmap width and height - Result.Width := Width; - Result.Height := Height; - - // Build and copy palette to bitmap - Result.Palette := CopyPalette(Palette); - - exit; - end; - - // Get DIB buffer for scanline operations - DIBResult := TDIBWriter.Create(Result, pf8bit, Width, Height, Palette); - try - - // Determine if this image is transparent - IsTransparent := FNeedMask and Transparent; - WasTransparent := False; - FNeedMask := False; - TransparentIndex := 0; - if (FMask = 0) and (IsTransparent) then - begin - IsTransparent := True; - TransparentIndex := GraphicControlExtension.TransparentColorIndex; - end; - // Allocate bit buffer for transparency mask - if (IsTransparent) then - begin - MaskRowWidth := ((Width+15) DIV 16) * 2; - GetMem(MaskBits, MaskRowWidth * Height); - FillChar(MaskBits^, MaskRowWidth * Height, 0); - IsTransparent := (MaskBits <> nil); - end else - begin - MaskBits := nil; - MaskRowWidth := 0; - end; - - try - ScanLineRow := 0; - Src := FData; - MaskRow := MaskBits; - while (ScanLineRow < Height) do - begin - DestScanline := DIBResult.ScanLine[ScanLineRow]; - - if ((ScanLineRow AND $1F) = 0) then - Image.Progress(Self, psRunning, MulDiv(ScanLineRow, 100, Height), - False, Rect(0,0,0,0), sProgressRendering); - - Move(Src^, DestScanline^, Width); - Inc(ScanLineRow); - - if (IsTransparent) then - begin - Bit := $80; - MaskDest := MaskRow; - MaskByte := 0; - for Col := 0 to Width-1 do - begin - // Set a bit in the mask if the pixel is transparent - if (Src^ = AnsiChar(TransparentIndex)) then - MaskByte := MaskByte OR Bit; - - Bit := Bit SHR 1; - if (Bit = $00) then - begin - // Store a mask byte for each 8 pixels - Bit := $80; - WasTransparent := WasTransparent or (MaskByte <> 0); - MaskDest^ := AnsiChar(MaskByte); - inc(MaskDest); - MaskByte := 0; - end; - Inc(Src); - end; - // Save the last mask byte in case the width isn't divisable by 8 - if (MaskByte <> 0) then - begin - WasTransparent := True; - MaskDest^ := AnsiChar(MaskByte); - end; - Inc(MaskRow, MaskRowWidth); - end else - Inc(Src, Width); - end; - - // Transparent paint needs a mask bitmap - if (IsTransparent) and (WasTransparent) then - FMask := CreateBitmap(Width, Height, 1, 1, MaskBits); - finally - if (MaskBits <> nil) then - FreeMem(MaskBits); - end; - finally - // Free DIB buffer used for scanline operations - DIBResult.Free; - end; - except - Result.Free; - raise; - end; -end; - -{$ifdef DEBUG_RENDERPERFORMANCE} -var - ImageCount : DWORD = 0; - RenderTime : DWORD = 0; -{$endif} -function TGIFSubImage.GetBitmap: TBitmap; -var - n : integer; -{$ifdef DEBUG_RENDERPERFORMANCE} - RenderStartTime : DWORD; -{$endif} -begin -{$ifdef DEBUG_RENDERPERFORMANCE} - if (GetAsyncKeyState(VK_CONTROL) <> 0) then - begin - ShowMessage(format('Render %d images in %d mS, Rate %d mS/image (%d images/S)', - [ImageCount, RenderTime, - RenderTime DIV (ImageCount+1), - MulDiv(ImageCount, 1000, RenderTime+1)])); - end; -{$endif} - Result := FBitmap; - if (Result <> nil) or (Empty) then - Exit; - -{$ifdef DEBUG_RENDERPERFORMANCE} - inc(ImageCount); - RenderStartTime := timeGetTime; -{$endif} - try - Image.Progress(Self, psStarting, 0, False, Rect(0,0,0,0), sProgressRendering); - try - - if (Image.DoDither) then - // Create dithered bitmap - FBitmap := DoGetDitherBitmap - else - // Create "regular" bitmap - FBitmap := DoGetBitmap; - - Result := FBitmap; - - finally - if ExceptObject = nil then - n := 100 - else - n := 0; - Image.Progress(Self, psEnding, n, Image.PaletteModified, Rect(0,0,0,0), - sProgressRendering); - // Make sure new palette gets realized, in case OnProgress event didn't. - if Image.PaletteModified then - Image.Changed(Self); - end; - except - on EAbort do ; // OnProgress can raise EAbort to cancel image load - end; -{$ifdef DEBUG_RENDERPERFORMANCE} - inc(RenderTime, timeGetTime-RenderStartTime); -{$endif} -end; - -procedure TGIFSubImage.SetBitmap(Value: TBitmap); -begin - FreeBitmap; - if (Value <> nil) then - Assign(Value); -end; - -function TGIFSubImage.GetActiveColorMap: TGIFColorMap; -begin - if (ColorMap.Count > 0) or (Image.GlobalColorMap.Count = 0) then - Result := ColorMap - else - Result := Image.GlobalColorMap; -end; - -function TGIFSubImage.GetInterlaced: boolean; -begin - Result := (FImageDescriptor.PackedFields AND idInterlaced) <> 0; -end; - -procedure TGIFSubImage.SetInterlaced(Value: boolean); -begin - if (Value) then - FImageDescriptor.PackedFields := FImageDescriptor.PackedFields OR idInterlaced - else - FImageDescriptor.PackedFields := FImageDescriptor.PackedFields AND NOT(idInterlaced); -end; - -function TGIFSubImage.GetVersion: TGIFVersion; -var - v : TGIFVersion; - i : integer; -begin - if (ColorMap.Optimized) then - Result := gv89a - else - Result := inherited GetVersion; - i := 0; - while (Result < high(TGIFVersion)) and (i < FExtensions.Count) do - begin - v := FExtensions[i].Version; - if (v > Result) then - Result := v; - end; -end; - -function TGIFSubImage.GetColorResolution: integer; -begin - Result := ColorMap.BitsPerPixel-1; -end; - -function TGIFSubImage.GetBitsPerPixel: integer; -begin - Result := ColorMap.BitsPerPixel; -end; - -function TGIFSubImage.GetBoundsRect: TRect; -begin - Result := Rect(FImageDescriptor.Left, - FImageDescriptor.Top, - FImageDescriptor.Left+FImageDescriptor.Width, - FImageDescriptor.Top+FImageDescriptor.Height); -end; - -procedure TGIFSubImage.DoSetBounds(ALeft, ATop, AWidth, AHeight: integer); -var - TooLarge : boolean; - Zap : boolean; -begin - Zap := (FImageDescriptor.Width <> Width) or (FImageDescriptor.Height <> AHeight); - FImageDescriptor.Left := ALeft; - FImageDescriptor.Top := ATop; - FImageDescriptor.Width := AWidth; - FImageDescriptor.Height := AHeight; - - // Delete existing image and bitmaps if size has changed - if (Zap) then - begin - FreeBitmap; - FreeMask; - FreeImage; - // ...and allocate a new image - NewImage; - end; - - TooLarge := False; - // Set width & height if added image is larger than existing images -{$IFDEF STRICT_MOZILLA} - // From Mozilla source: - // Work around broken GIF files where the logical screen - // size has weird width or height. [...] - if (Image.Width < AWidth) or (Image.Height < AHeight) then - begin - TooLarge := True; - Image.Width := AWidth; - Image.Height := AHeight; - Left := 0; - Top := 0; - end; -{$ELSE} - if (Image.Width < ALeft+AWidth) then - begin - if (Image.Width > 0) then - begin - TooLarge := True; - Warning(gsWarning, sBadWidth) - end; - Image.Width := ALeft+AWidth; - end; - if (Image.Height < ATop+AHeight) then - begin - if (Image.Height > 0) then - begin - TooLarge := True; - Warning(gsWarning, sBadHeight) - end; - Image.Height := ATop+AHeight; - end; -{$ENDIF} - - if (TooLarge) then - Warning(gsWarning, sScreenSizeExceeded); -end; - -procedure TGIFSubImage.SetBoundsRect(const Value: TRect); -begin - DoSetBounds(Value.Left, Value.Top, Value.Right-Value.Left+1, Value.Bottom-Value.Top+1); -end; - -function TGIFSubImage.GetClientRect: TRect; -begin - Result := Rect(0, 0, FImageDescriptor.Width, FImageDescriptor.Height); -end; - -function TGIFSubImage.GetPixel(x, y: integer): BYTE; -begin - if (x < 0) or (x > Width-1) then - Error(sBadPixelCoordinates); - Result := BYTE(PAnsiChar(longInt(Scanline[y]) + x)^); -end; - -// 2006.10.09 -> -procedure TGIFSubImage.SetPixel(x, y: integer; Value: BYTE ); -begin - if (x < 0) or (x > Width-1) or (y < 0) or (y > Height-1) then - Error(sBadPixelCoordinates); - if Value >= ActiveColorMap.FCount then - Error(sBadColorIndex); -// 2008.10.19 -> -// BYTE(PChar(longInt(Scanline[y]) + x)^) := Value; - PByte(LongInt(Scanline[y]) + x)^ := Value; -// 2008.10.19 <- -end; -// 2006.10.09 <- - -function TGIFSubImage.GetScanline(y: integer): pointer; -begin - if (y < 0) or (y > Height-1) then - Error(sBadPixelCoordinates); - NeedImage; - Result := pointer(longInt(FData) + y * Width); -end; - -procedure TGIFSubImage.Prepare; -var - Pack : BYTE; -begin - Pack := FImageDescriptor.PackedFields; - if (ColorMap.Count > 0) then - begin - Pack := idLocalColorTable; - if (ColorMap.Optimized) then - Pack := Pack OR idSort; - Pack := (Pack AND NOT(idColorTableSize)) OR (ColorResolution AND idColorTableSize); - end else - Pack := Pack AND NOT(idLocalColorTable OR idSort OR idColorTableSize); - FImageDescriptor.PackedFields := Pack; -end; - -procedure TGIFSubImage.SaveToStream(Stream: TStream); -begin - FExtensions.SaveToStream(Stream); - if (Empty) then - exit; - Prepare; - Stream.Write(FImageDescriptor, sizeof(TImageDescriptor)); - ColorMap.SaveToStream(Stream); - Compress(Stream); -end; - -procedure TGIFSubImage.LoadFromStream(Stream: TStream); -var - ColorCount : integer; - b : BYTE; -begin - Clear; - FExtensions.LoadFromStream(Stream, self); - // Check for extension without image - if (Stream.Read(b, 1) <> 1) then - exit; - Stream.Seek(-1, soFromCurrent); - if (b = bsTrailer) or (b = 0) then - exit; - - ReadCheck(Stream, FImageDescriptor, sizeof(TImageDescriptor)); - - // From Mozilla source: - // Work around more broken GIF files that have zero image - // width or height - if (FImageDescriptor.Height = 0) or (FImageDescriptor.Width = 0) then - begin - FImageDescriptor.Height := Image.Height; - FImageDescriptor.Width := Image.Width; - Warning(gsWarning, sScreenSizeExceeded); - end; - - if (FImageDescriptor.PackedFields AND idLocalColorTable = idLocalColorTable) then - begin - ColorCount := 2 SHL (FImageDescriptor.PackedFields AND idColorTableSize); - if (ColorCount < 2) or (ColorCount > 256) then - Error(sImageBadColorSize); - ColorMap.LoadFromStream(Stream, ColorCount); - end; - - Decompress(Stream); - - // On-load rendering - if (GIFImageRenderOnLoad) then - // Touch bitmap to force frame to be rendered - Bitmap; -end; - -procedure TGIFSubImage.AssignTo(Dest: TPersistent); -begin - if (Dest is TBitmap) then - Dest.Assign(Bitmap) - else - inherited AssignTo(Dest); -end; - -procedure TGIFSubImage.Assign(Source: TPersistent); -var - MemoryStream : TMemoryStream; - i : integer; - PixelFormat : TPixelFormat; - DIBSource : TDIB; - ABitmap : TBitmap; - - procedure Import8Bit(Dest: PAnsiChar); - var - y : integer; - begin - // Copy colormap -{$ifdef VER10_PLUS} - if (FBitmap.HandleType = bmDIB) then - FColorMap.ImportDIBColors(FBitmap.Canvas.Handle) - else -{$ENDIF} - FColorMap.ImportPalette(FBitmap.Palette); - // Copy pixels - for y := 0 to Height-1 do - begin - if ((y AND $1F) = 0) then - Image.Progress(Self, psRunning, MulDiv(y, 100, Height), False, Rect(0,0,0,0), sProgressConverting); - Move(DIBSource.Scanline[y]^, Dest^, Width); - inc(Dest, Width); - end; - end; - - procedure Import4Bit(Dest: PAnsiChar); - var - x, y : integer; - Scanline : PAnsiChar; - begin - // Copy colormap - FColorMap.ImportPalette(FBitmap.Palette); - // Copy pixels - for y := 0 to Height-1 do - begin - if ((y AND $1F) = 0) then - Image.Progress(Self, psRunning, MulDiv(y, 100, Height), False, Rect(0,0,0,0), sProgressConverting); - ScanLine := DIBSource.Scanline[y]; - for x := 0 to Width-1 do - begin - if (x AND $01 = 0) then -// 2008.10.19 -> -// Dest^ := chr(ord(ScanLine^) SHR 4) - Dest^ := AnsiChar(ord(ScanLine^) SHR 4) -// 2008.10.19 <- - else - begin -// 2008.10.19 -> -// Dest^ := chr(ord(ScanLine^) AND $0F); - Dest^ := AnsiChar(ord(ScanLine^) AND $0F); -// 2008.10.19 <- - inc(ScanLine); - end; - inc(Dest); - end; - end; - end; - - procedure Import1Bit(Dest: PAnsiChar); - var - x, y : integer; - Scanline : PAnsiChar; - Bit : integer; - Byte : integer; - begin - // Copy colormap - FColorMap.ImportPalette(FBitmap.Palette); - // Copy pixels - for y := 0 to Height-1 do - begin - if ((y AND $1F) = 0) then - Image.Progress(Self, psRunning, MulDiv(y, 100, Height), False, Rect(0,0,0,0), sProgressConverting); - ScanLine := DIBSource.Scanline[y]; - x := Width; - Bit := 0; - Byte := 0; // To avoid compiler warning - while (x > 0) do - begin - if (Bit = 0) then - begin - Bit := 8; - Byte := ord(ScanLine^); - inc(Scanline); - end; -// 2008.10.19 -> -// Dest^ := chr((Byte AND $80) SHR 7); - Dest^ := AnsiChar((Byte AND $80) SHR 7); -// 2008.10.19 <- - Byte := Byte SHL 1; - inc(Dest); - dec(Bit); - dec(x); - end; - end; - end; - - procedure Import24Bit(Dest: PAnsiChar); - type - TCacheEntry = record - Color : TColor; - Index : integer; - end; - const - // Size of palette cache. Must be 2^n. - // The cache holds the palette index of the last "CacheSize" colors - // processed. Hopefully the cache can speed things up a bit... Initial - // testing shows that this is indeed the case at least for non-dithered - // bitmaps. - // All the same, a small hash table would probably be much better. - CacheSize = 8; - var - i : integer; - Cache : array[0..CacheSize-1] of TCacheEntry; - LastEntry : integer; - Scanline : PRGBTriple; - Pixel : TColor; - RGBTriple : TRGBTriple absolute Pixel; - x, y : integer; - ColorMap : PColorMap; - t : byte; - label - NextPixel; - begin - for i := 0 to CacheSize-1 do - Cache[i].Index := -1; - LastEntry := 0; - - // Copy all pixels and build colormap - for y := 0 to Height-1 do - begin - if ((y AND $1F) = 0) then - Image.Progress(Self, psRunning, MulDiv(y, 100, Height), False, Rect(0,0,0,0), sProgressConverting); - ScanLine := DIBSource.Scanline[y]; - for x := 0 to Width-1 do - begin - Pixel := 0; - RGBTriple := Scanline^; - // Scan cache for color from most recently processed color to last - // recently processed. This is done because TColorMap.AddUnique is very slow. - i := LastEntry; - repeat - if (Cache[i].Index = -1) then - break; - if (Cache[i].Color = Pixel) then - begin -// 2008.10.19 -> -// Dest^ := chr(Cache[i].Index); - Dest^ := AnsiChar(Cache[i].Index); -// 2008.10.19 <- - LastEntry := i; - goto NextPixel; - end; - if (i = 0) then - i := CacheSize-1 - else - dec(i); - until (i = LastEntry); - // Color not found in cache, do it the slow way instead -// 2008.10.19 -> -// Dest^ := chr(FColorMap.AddUnique(Pixel)); - Dest^ := AnsiChar(FColorMap.AddUnique(Pixel)); -// 2008.10.19 <- - // Add color and index to cache - LastEntry := (LastEntry + 1) AND (CacheSize-1); - Cache[LastEntry].Color := Pixel; - Cache[LastEntry].Index := ord(Dest^); - - NextPixel: - Inc(Dest); - Inc(Scanline); - end; - end; - // Convert colors in colormap from BGR to RGB - ColorMap := FColorMap.Data; - i := FColorMap.Count; - while (i > 0) do - begin - t := ColorMap^[0].Red; - ColorMap^[0].Red := ColorMap^[0].Blue; - ColorMap^[0].Blue := t; - inc(integer(ColorMap), sizeof(TGIFColor)); - dec(i); - end; - end; - - procedure ImportViaDraw(ABitmap: TBitmap; Graphic: TGraphic); - begin - ABitmap.Height := Graphic.Height; - ABitmap.Width := Graphic.Width; - - // Note: Disable the call to SafeSetPixelFormat below to import - // in max number of colors with the risk of having to use - // TCanvas.Pixels to do it (very slow). - - // Make things a little easier for TGIFSubImage.Assign by converting - // pfDevice to a more import friendly format -{$ifdef SLOW_BUT_SAFE} - SafeSetPixelFormat(ABitmap, pf8bit); -{$else} -{$ifndef VER9x} - SetPixelFormat(ABitmap, pf24bit); -{$endif} -{$endif} - ABitmap.Canvas.Draw(0, 0, Graphic); - end; - - procedure AddMask(Mask: TBitmap); - var - DIBReader : TDIBReader; - TransparentIndex : integer; - i , - j : integer; - GIFPixel , - MaskPixel : PAnsiChar; - WasTransparent : boolean; - GCE : TGIFGraphicControlExtension; - begin - // Optimize colormap to make room for transparent color - ColorMap.Optimize; - // Can't make transparent if no color or colormap full - if (ColorMap.Count = 0) or (ColorMap.Count = 256) then - exit; - - // Add the transparent color to the color map - TransparentIndex := ColorMap.Add(TColor(0)); - WasTransparent := False; - - DIBReader := TDIBReader.Create(Mask, pf8bit); - try - for i := 0 to Height-1 do - begin - MaskPixel := DIBReader.Scanline[i]; - GIFPixel := Scanline[i]; - for j := 0 to Width-1 do - begin - // Change all unmasked pixels to transparent - if (MaskPixel^ <> #0) then - begin -// 2008.10.19 -> -// GIFPixel^ := chr(TransparentIndex); - GIFPixel^ := AnsiChar(TransparentIndex); -// 2008.10.19 <- - WasTransparent := True; - end; - inc(MaskPixel); - inc(GIFPixel); - end; - end; - finally - DIBReader.Free; - end; - - // Add a Graphic Control Extension if any part of the mask was transparent - if (WasTransparent) then - begin - GCE := TGIFGraphicControlExtension.Create(self); - GCE.Transparent := True; - GCE.TransparentColorIndex := TransparentIndex; - Extensions.Add(GCE); - end else - // Otherwise removed the transparency color since it wasn't used - ColorMap.Delete(TransparentIndex); - end; - - procedure AddMaskOnly(hMask: hBitmap); - var - Mask : TBitmap; - begin - if (hMask = 0) then - exit; - - // Encapsulate the mask - Mask := TBitmap.Create; - try -// Mask.Handle := hMask; // 2003.08.04 - Mask.Handle := Windows.CopyImage(hMask, IMAGE_BITMAP, 0, 0, LR_COPYRETURNORG); // 2003.08.04 - AddMask(Mask); - finally -// Mask.ReleaseHandle; // 2003.08.04 - Mask.Free; - end; - end; - - procedure AddIconMask(Icon: TIcon); - var - IconInfo : TIconInfo; - begin - if (not GetIconInfo(Icon.Handle, IconInfo)) then - exit; - - // Extract the icon mask - AddMaskOnly(IconInfo.hbmMask); - end; - - procedure AddMetafileMask(Metafile: TMetaFile); - var - Mask1 , - Mask2 : TBitmap; - - procedure DrawMetafile(ABitmap: TBitmap; Background: TColor); - begin - ABitmap.Width := Metafile.Width; - ABitmap.Height := Metafile.Height; -{$ifndef VER9x} - SetPixelFormat(ABitmap, pf24bit); -{$endif} - ABitmap.Canvas.Brush.Color := Background; - ABitmap.Canvas.Brush.Style := bsSolid; - ABitmap.Canvas.FillRect(ABitmap.Canvas.ClipRect); - ABitmap.Canvas.Draw(0,0, Metafile); - end; - - begin - // Create the metafile mask - Mask1 := TBitmap.Create; - try - Mask2 := TBitmap.Create; - try - DrawMetafile(Mask1, clWhite); - DrawMetafile(Mask2, clBlack); - Mask1.Canvas.CopyMode := cmSrcInvert; - Mask1.Canvas.Draw(0,0, Mask2); - AddMask(Mask1); - finally - Mask2.Free; - end; - finally - Mask1.Free; - end; - end; - -begin - if (Source = self) then - exit; - if (Source = nil) then - begin - Clear; - end else - // - // TGIFSubImage import - // - if (Source is TGIFSubImage) then - begin - // Zap existing colormap, extensions and bitmap - Clear; - if (TGIFSubImage(Source).Empty) then - exit; - // Copy source data - FImageDescriptor := TGIFSubImage(Source).FImageDescriptor; - FTransparent := TGIFSubImage(Source).Transparent; - // Copy image data - NewImage; - if (FData <> nil) and (TGIFSubImage(Source).Data <> nil) then - Move(TGIFSubImage(Source).Data^, FData^, FDataSize); - // Copy palette - FColorMap.Assign(TGIFSubImage(Source).ColorMap); - // Copy extensions - if (TGIFSubImage(Source).Extensions.Count > 0) then - begin - MemoryStream := TMemoryStream.Create; - try - TGIFSubImage(Source).Extensions.SaveToStream(MemoryStream); - MemoryStream.Seek(0, soFromBeginning); - Extensions.LoadFromStream(MemoryStream, Self); - finally - MemoryStream.Free; - end; - end; - - // Copy bitmap representation - // (Not really nescessary but improves performance if the bitmap is needed - // later on) - if (TGIFSubImage(Source).HasBitmap) then - begin - NewBitmap; - FBitmap.Assign(TGIFSubImage(Source).Bitmap); - end; - end else - // - // Bitmap import - // - if (Source is TBitmap) then - begin - // Zap existing colormap, extensions and bitmap - Clear; - if (TBitmap(Source).Empty) then - exit; - - Width := TBitmap(Source).Width; - Height := TBitmap(Source).Height; - - PixelFormat := GetPixelFormat(TBitmap(Source)); -{$ifdef VER9x} - // Delphi 2 TBitmaps are always DDBs. This means that if a 24 bit - // bitmap is loaded in 8 bit device mode, TBitmap.PixelFormat will - // be pf8bit, but TBitmap.Palette will be 0! - if (TBitmap(Source).Palette = 0) then - PixelFormat := pfDevice; -{$endif} - if (PixelFormat > pf8bit) or (PixelFormat = pfDevice) then - begin - // Convert image to 8 bits/pixel or less - FBitmap := ReduceColors(TBitmap(Source), Image.ColorReduction, - Image.DitherMode, Image.ReductionBits, 0); - PixelFormat := GetPixelFormat(FBitmap); - end else - begin - // Create new bitmap and copy - NewBitmap; - FBitmap.Assign(TBitmap(Source)); - end; - - // Allocate new buffer - NewImage; - - Image.Progress(Self, psStarting, 0, False, Rect(0,0,0,0), sProgressConverting); - try -{$ifdef VER9x} - // This shouldn't happen, but better safe... - if (FBitmap.Palette = 0) then - PixelFormat := pf24bit; -{$endif} - if (not(PixelFormat in [pf1bit, pf4bit, pf8bit, pf24bit])) then - PixelFormat := pf24bit; - DIBSource := TDIBReader.Create(FBitmap, PixelFormat); - try - // Copy pixels - case (PixelFormat) of - pf8bit: Import8Bit(Fdata); - pf4bit: Import4Bit(Fdata); - pf1bit: Import1Bit(Fdata); - else -// Error(sUnsupportedBitmap); - Import24Bit(Fdata); - end; - - finally - DIBSource.Free; - end; - -{$ifdef VER10_PLUS} - // Add mask for transparent bitmaps - if (TBitmap(Source).Transparent) then - AddMaskOnly(TBitmap(Source).MaskHandle); -{$endif} - - finally - if ExceptObject = nil then - i := 100 - else - i := 0; - Image.Progress(Self, psEnding, i, Image.PaletteModified, Rect(0,0,0,0), sProgressConverting); - end; - end else - // - // TGraphic import - // - if (Source is TGraphic) then - begin - // Zap existing colormap, extensions and bitmap - Clear; - if (TGraphic(Source).Empty) then - exit; - - ABitmap := TBitmap.Create; - try - // Import TIcon and TMetafile by drawing them onto a bitmap... - // ...and then importing the bitmap recursively - if (Source is TIcon) or (Source is TMetafile) then - begin - try - ImportViaDraw(ABitmap, TGraphic(Source)) - except - // If import via TCanvas.Draw fails (which it shouldn't), we try the - // Assign mechanism instead - ABitmap.Assign(Source); - end; - end else - try - ABitmap.Assign(Source); - except - // If automatic conversion to bitmap fails, we try and draw the - // graphic on the bitmap instead - ImportViaDraw(ABitmap, TGraphic(Source)); - end; - // Convert the bitmap to a GIF frame recursively - Assign(ABitmap); - finally - ABitmap.Free; - end; - - // Import transparency mask - if (Source is TIcon) then - AddIconMask(TIcon(Source)); - if (Source is TMetaFile) then - AddMetafileMask(TMetaFile(Source)); - - end else - // - // TPicture import - // - if (Source is TPicture) then - begin - // Recursively import TGraphic - Assign(TPicture(Source).Graphic); - end else - // Unsupported format - fall back to Source.AssignTo - inherited Assign(Source); -end; - -// Copied from D3 graphics.pas -// Fixed by Brian Lowe of Acro Technology Inc. 30Jan98 -function TransparentStretchBlt(DstDC: HDC; DstX, DstY, DstW, DstH: Integer; - SrcDC: HDC; SrcX, SrcY, SrcW, SrcH: Integer; MaskDC: HDC; MaskX, - MaskY: Integer): Boolean; -const - ROP_DstCopy = $00AA0029; -var - MemDC , - OrMaskDC : HDC; - MemBmp , - OrMaskBmp : HBITMAP; - Save , - OrMaskSave : THandle; - crText, crBack : TColorRef; - SavePal : HPALETTE; - -begin - Result := True; - if (Win32Platform = VER_PLATFORM_WIN32_NT) and (SrcW = DstW) and (SrcH = DstH) then - begin - MemBmp := GDICheck(CreateCompatibleBitmap(SrcDC, 1, 1)); - MemBmp := SelectObject(MaskDC, MemBmp); - try - MaskBlt(DstDC, DstX, DstY, DstW, DstH, SrcDC, SrcX, SrcY, MemBmp, MaskX, - MaskY, MakeRop4(ROP_DstCopy, SrcCopy)); - finally - MemBmp := SelectObject(MaskDC, MemBmp); - DeleteObject(MemBmp); - end; - Exit; - end; - - SavePal := 0; - MemDC := GDICheck(CreateCompatibleDC(DstDC)); - try - { Color bitmap for combining OR mask with source bitmap } - MemBmp := GDICheck(CreateCompatibleBitmap(DstDC, SrcW, SrcH)); - try - Save := SelectObject(MemDC, MemBmp); - try - { This bitmap needs the size of the source but DC of the dest } - OrMaskDC := GDICheck(CreateCompatibleDC(DstDC)); - try - { Need a monochrome bitmap for OR mask!! } - OrMaskBmp := GDICheck(CreateBitmap(SrcW, SrcH, 1, 1, nil)); - try - OrMaskSave := SelectObject(OrMaskDC, OrMaskBmp); - try - - // OrMask := 1 - // Original: BitBlt(OrMaskDC, SrcX, SrcY, SrcW, SrcH, OrMaskDC, SrcX, SrcY, WHITENESS); - // Replacement, but not needed: PatBlt(OrMaskDC, SrcX, SrcY, SrcW, SrcH, WHITENESS); - // OrMask := OrMask XOR Mask - // Not needed: BitBlt(OrMaskDC, SrcX, SrcY, SrcW, SrcH, MaskDC, SrcX, SrcY, SrcInvert); - // OrMask := NOT Mask - BitBlt(OrMaskDC, SrcX, SrcY, SrcW, SrcH, MaskDC, SrcX, SrcY, NotSrcCopy); - - // Retrieve source palette (with dummy select) - SavePal := SelectPalette(SrcDC, SystemPalette16, False); - // Restore source palette - SelectPalette(SrcDC, SavePal, False); - // Select source palette into memory buffer - if SavePal <> 0 then - SavePal := SelectPalette(MemDC, SavePal, True) - else - SavePal := SelectPalette(MemDC, SystemPalette16, True); - RealizePalette(MemDC); - - // Mem := OrMask - BitBlt(MemDC, SrcX, SrcY, SrcW, SrcH, OrMaskDC, SrcX, SrcY, SrcCopy); - // Mem := Mem AND Src -{$IFNDEF GIF_TESTMASK} // Define GIF_TESTMASK if you want to know what it does... - BitBlt(MemDC, SrcX, SrcY, SrcW, SrcH, SrcDC, SrcX, SrcY, SrcAnd); -{$ELSE} - StretchBlt(DstDC, DstX, DstY, DstW DIV 2, DstH, MemDC, SrcX, SrcY, SrcW, SrcH, SrcCopy); - StretchBlt(DstDC, DstX+DstW DIV 2, DstY, DstW DIV 2, DstH, SrcDC, SrcX, SrcY, SrcW, SrcH, SrcCopy); - exit; -{$ENDIF} - finally - if (OrMaskSave <> 0) then - SelectObject(OrMaskDC, OrMaskSave); - end; - finally - DeleteObject(OrMaskBmp); - end; - finally - DeleteDC(OrMaskDC); - end; - - crText := SetTextColor(DstDC, $00000000); - crBack := SetBkColor(DstDC, $00FFFFFF); - - { All color rendering is done at 1X (no stretching), - then final 2 masks are stretched to dest DC } - // Neat trick! - // Dst := Dst AND Mask - StretchBlt(DstDC, DstX, DstY, DstW, DstH, MaskDC, SrcX, SrcY, SrcW, SrcH, SrcAnd); - // Dst := Dst OR Mem - StretchBlt(DstDC, DstX, DstY, DstW, DstH, MemDC, SrcX, SrcY, SrcW, SrcH, SrcPaint); - - SetTextColor(DstDC, crText); - SetTextColor(DstDC, crBack); - - finally - if (Save <> 0) then - SelectObject(MemDC, Save); - end; - finally - DeleteObject(MemBmp); - end; - finally - if (SavePal <> 0) then - SelectPalette(MemDC, SavePal, False); - DeleteDC(MemDC); - end; -end; - -procedure TGIFSubImage.Draw(ACanvas: TCanvas; const Rect: TRect; - DoTransparent, DoTile: boolean); -begin - if (DoTile) then - StretchDraw(ACanvas, Rect, DoTransparent, DoTile) - else - StretchDraw(ACanvas, ScaleRect(Rect), DoTransparent, DoTile); -end; - -type - // Dummy class used to gain access to protected method TCanvas.Changed - TChangableCanvas = class(TCanvas) - end; - -procedure TGIFSubImage.StretchDraw(ACanvas: TCanvas; const Rect: TRect; - DoTransparent, DoTile: boolean); -var - MaskDC : HDC; - Save : THandle; - Tile : TRect; -{$ifdef DEBUG_DRAWPERFORMANCE} - ImageCount , - TimeStart , - TimeStop : DWORD; -{$endif} - -begin -{$ifdef DEBUG_DRAWPERFORMANCE} - TimeStart := timeGetTime; - ImageCount := 0; -{$endif} - if (DoTransparent) and (Transparent) and (HasMask) then - begin - // Draw transparent using mask - Save := 0; - MaskDC := 0; - try - MaskDC := GDICheck(CreateCompatibleDC(0)); - Save := SelectObject(MaskDC, FMask); - - if (DoTile) then - begin - Tile.Left := Rect.Left+Left; - Tile.Right := Tile.Left + Width; - while (Tile.Left < Rect.Right) do - begin - Tile.Top := Rect.Top+Top; - Tile.Bottom := Tile.Top + Height; - while (Tile.Top < Rect.Bottom) do - begin - TransparentStretchBlt(ACanvas.Handle, Tile.Left, Tile.Top, Width, Height, - Bitmap.Canvas.Handle, 0, 0, Width, Height, MaskDC, 0, 0); - Tile.Top := Tile.Top + Image.Height; - Tile.Bottom := Tile.Bottom + Image.Height; -{$ifdef DEBUG_DRAWPERFORMANCE} - inc(ImageCount); -{$endif} - end; - Tile.Left := Tile.Left + Image.Width; - Tile.Right := Tile.Right + Image.Width; - end; - end else - TransparentStretchBlt(ACanvas.Handle, Rect.Left, Rect.Top, - Rect.Right - Rect.Left, Rect.Bottom - Rect.Top, - Bitmap.Canvas.Handle, 0, 0, Width, Height, MaskDC, 0, 0); - - // Since we are not using any of the TCanvas functions (only handle) - // we need to fire the TCanvas.Changed method "manually". - TChangableCanvas(ACanvas).Changed; - - finally - if (Save <> 0) then - SelectObject(MaskDC, Save); - if (MaskDC <> 0) then - DeleteDC(MaskDC); - end; - end else - begin - if (DoTile) then - begin - Tile.Left := Rect.Left+Left; - Tile.Right := Tile.Left + Width; - while (Tile.Left < Rect.Right) do - begin - Tile.Top := Rect.Top+Top; - Tile.Bottom := Tile.Top + Height; - while (Tile.Top < Rect.Bottom) do - begin - ACanvas.StretchDraw(Tile, Bitmap); - Tile.Top := Tile.Top + Image.Height; - Tile.Bottom := Tile.Bottom + Image.Height; -{$ifdef DEBUG_DRAWPERFORMANCE} - inc(ImageCount); -{$endif} - end; - Tile.Left := Tile.Left + Image.Width; - Tile.Right := Tile.Right + Image.Width; - end; - end else - ACanvas.StretchDraw(Rect, Bitmap); - end; -{$ifdef DEBUG_DRAWPERFORMANCE} - if (GetAsyncKeyState(VK_CONTROL) <> 0) then - begin - TimeStop := timeGetTime; - ShowMessage(format('Draw %d images in %d mS, Rate %d images/mS (%d images/S)', - [ImageCount, TimeStop-TimeStart, - ImageCount DIV (TimeStop-TimeStart+1), - MulDiv(ImageCount, 1000, TimeStop-TimeStart+1)])); - end; -{$endif} -end; - -// Given a destination rect (DestRect) calculates the -// area covered by this sub image -function TGIFSubImage.ScaleRect(DestRect: TRect): TRect; -var - HeightMul , - HeightDiv : integer; - WidthMul , - WidthDiv : integer; -begin - HeightDiv := Image.Height; - HeightMul := DestRect.Bottom-DestRect.Top; - WidthDiv := Image.Width; - WidthMul := DestRect.Right-DestRect.Left; - - Result.Left := DestRect.Left + muldiv(Left, WidthMul, WidthDiv); - Result.Top := DestRect.Top + muldiv(Top, HeightMul, HeightDiv); - Result.Right := DestRect.Left + muldiv(Left+Width, WidthMul, WidthDiv); - Result.Bottom := DestRect.Top + muldiv(Top+Height, HeightMul, HeightDiv); -end; - -procedure TGIFSubImage.Crop; -var - TransparentColorIndex : byte; - CropLeft , - CropTop , - CropRight , - CropBottom : integer; - WasTransparent : boolean; - i : integer; - NewSize : integer; - NewData : PAnsiChar; - NewWidth , - NewHeight : integer; - pSource , - pDest : PAnsiChar; -begin - if (Empty) or (not Transparent) then - exit; - TransparentColorIndex := GraphicControlExtension.TransparentColorIndex; - CropLeft := 0; - CropRight := Width - 1; - CropTop := 0; - CropBottom := Height - 1; - // Find left edge - WasTransparent := True; - while (CropLeft <= CropRight) and (WasTransparent) do - begin - for i := CropTop to CropBottom do - if (Pixels[CropLeft, i] <> TransparentColorIndex) then - begin - WasTransparent := False; - break; - end; - if (WasTransparent) then - inc(CropLeft); - end; - // Find right edge - WasTransparent := True; - while (CropLeft <= CropRight) and (WasTransparent) do - begin - for i := CropTop to CropBottom do - if (pixels[CropRight, i] <> TransparentColorIndex) then - begin - WasTransparent := False; - break; - end; - if (WasTransparent) then - dec(CropRight); - end; - if (CropLeft <= CropRight) then - begin - // Find top edge - WasTransparent := True; - while (CropTop <= CropBottom) and (WasTransparent) do - begin - for i := CropLeft to CropRight do - if (pixels[i, CropTop] <> TransparentColorIndex) then - begin - WasTransparent := False; - break; - end; - if (WasTransparent) then - inc(CropTop); - end; - // Find bottom edge - WasTransparent := True; - while (CropTop <= CropBottom) and (WasTransparent) do - begin - for i := CropLeft to CropRight do - if (pixels[i, CropBottom] <> TransparentColorIndex) then - begin - WasTransparent := False; - break; - end; - if (WasTransparent) then - dec(CropBottom); - end; - end; - - if (CropLeft > CropRight) or (CropTop > CropBottom) then - begin - // Cropped to nothing - frame is invisible - Clear; - end else - begin - // Crop frame - move data - NewWidth := CropRight - CropLeft + 1; - Newheight := CropBottom - CropTop + 1; - NewSize := NewWidth * NewHeight; - GetMem(NewData, NewSize); - pSource := PAnsiChar(integer(FData) + CropTop * Width + CropLeft); - pDest := NewData; - for i := 0 to NewHeight-1 do - begin - Move(pSource^, pDest^, NewWidth); - inc(pSource, Width); - inc(pDest, NewWidth); - end; - FreeImage; - FData := NewData; - FDataSize := NewSize; - inc(FImageDescriptor.Left, CropLeft); - inc(FImageDescriptor.Top, CropTop); - FImageDescriptor.Width := NewWidth; - FImageDescriptor.Height := NewHeight; - FreeBitmap; - FreeMask - end; -end; - -procedure TGIFSubImage.Merge(Previous: TGIFSubImage); -var - SourceIndex , - DestIndex : byte; - SourceTransparent : boolean; - NeedTransparentColorIndex: boolean; - PreviousRect , - ThisRect , - MergeRect : TRect; - PreviousY , - X , - Y : integer; - pSource , - pDest : PAnsiChar; - pSourceMap , - pDestMap : PColorMap; - GCE : TGIFGraphicControlExtension; - - function CanMakeTransparent: boolean; - begin - // Is there a local color map... - if (ColorMap.Count > 0) then - // ...and is there room in it? - Result := (ColorMap.Count < 256) - // Is there a global color map... - else if (Image.GlobalColorMap.Count > 0) then - // ...and is there room in it? - Result := (Image.GlobalColorMap.Count < 256) - else - Result := False; - end; - - function GetTransparentColorIndex: byte; - var - i : integer; - begin - if (ColorMap.Count > 0) then - begin - // Get the transparent color from the local color map - Result := ColorMap.Add(TColor(0)); - end else - begin - // Are any other frames using the global color map for transparency - for i := 0 to Image.Images.Count-1 do - if (Image.Images[i] <> self) and (Image.Images[i].Transparent) and - (Image.Images[i].ColorMap.Count = 0) then - begin - // Use the same transparency color as the other frame - Result := Image.Images[i].GraphicControlExtension.TransparentColorIndex; - exit; - end; - // Get the transparent color from the global color map - Result := Image.GlobalColorMap.Add(TColor(0)); - end; - end; - -begin - // Determine if it is possible to merge this frame - if (Empty) or (Previous = nil) or (Previous.Empty) or - ((Previous.GraphicControlExtension <> nil) and - (Previous.GraphicControlExtension.Disposal in [dmBackground, dmPrevious])) then - exit; - - PreviousRect := Previous.BoundsRect; - ThisRect := BoundsRect; - - // Cannot merge unless the frames intersect - if (not IntersectRect(MergeRect, PreviousRect, ThisRect)) then - exit; - - // If the frame isn't already transparent, determine - // if it is possible to make it so - if (Transparent) then - begin - DestIndex := GraphicControlExtension.TransparentColorIndex; - NeedTransparentColorIndex := False; - end else - begin - if (not CanMakeTransparent) then - exit; - DestIndex := 0; // To avoid compiler warning - NeedTransparentColorIndex := True; - end; - - SourceTransparent := Previous.Transparent; - if (SourceTransparent) then - SourceIndex := Previous.GraphicControlExtension.TransparentColorIndex - else - SourceIndex := 0; // To avoid compiler warning - - PreviousY := MergeRect.Top - Previous.Top; - - pSourceMap := Previous.ActiveColorMap.Data; - pDestMap := ActiveColorMap.Data; - - for Y := MergeRect.Top - Top to MergeRect.Bottom - Top-1 do - begin - pSource := PAnsiChar(integer(Previous.Scanline[PreviousY]) + MergeRect.Left - Previous.Left); - pDest := PAnsiChar(integer(Scanline[Y]) + MergeRect.Left - Left); - - for X := MergeRect.Left to MergeRect.Right-1 do - begin - // Ignore pixels if either this frame's or the previous frame's pixel is transparent - if ( - not( - ((not NeedTransparentColorIndex) and (pDest^ = AnsiChar(DestIndex))) or - ((SourceTransparent) and (pSource^ = AnsiChar(SourceIndex))) - ) - ) and ( - // Replace same colored pixels with transparency - ((pDestMap = pSourceMap) and (pDest^ = pSource^)) or - (CompareMem(@(pDestMap^[ord(pDest^)]), @(pSourceMap^[ord(pSource^)]), sizeof(TGIFColor))) - ) then - begin - if (NeedTransparentColorIndex) then - begin - NeedTransparentColorIndex := False; - DestIndex := GetTransparentColorIndex; - end; - pDest^ := AnsiChar(DestIndex); - end; - inc(pDest); - inc(pSource); - end; - inc(PreviousY); - end; - - (* - ** Create a GCE if the frame wasn't already transparent and any - ** pixels were made transparent - *) - if (not Transparent) and (not NeedTransparentColorIndex) then - begin - if (GraphicControlExtension = nil) then - begin - GCE := TGIFGraphicControlExtension.Create(self); - Extensions.Add(GCE); - end else - GCE := GraphicControlExtension; - GCE.Transparent := True; - GCE.TransparentColorIndex := DestIndex; - end; - - FreeBitmap; - FreeMask -end; - -//////////////////////////////////////////////////////////////////////////////// -// -// TGIFTrailer -// -//////////////////////////////////////////////////////////////////////////////// -procedure TGIFTrailer.SaveToStream(Stream: TStream); -begin - WriteByte(Stream, bsTrailer); -end; - -procedure TGIFTrailer.LoadFromStream(Stream: TStream); -var - b : BYTE; -begin - if (Stream.Read(b, 1) <> 1) then - exit; - if (b <> bsTrailer) then - Warning(gsWarning, sBadTrailer); -end; - -//////////////////////////////////////////////////////////////////////////////// -// -// TGIFExtension registration database -// -//////////////////////////////////////////////////////////////////////////////// -type - TExtensionLeadIn = packed record - Introducer: byte; { always $21 } - ExtensionLabel: byte; - end; - - PExtRec = ^TExtRec; - TExtRec = record - ExtClass: TGIFExtensionClass; - ExtLabel: BYTE; - end; - - TExtensionList = class(TList) - public - constructor Create; - destructor Destroy; override; - procedure Add(eLabel: BYTE; eClass: TGIFExtensionClass); - function FindExt(eLabel: BYTE): TGIFExtensionClass; - procedure Remove(eClass: TGIFExtensionClass); - end; - -constructor TExtensionList.Create; -begin - inherited Create; - Add(bsPlainTextExtension, TGIFTextExtension); - Add(bsGraphicControlExtension, TGIFGraphicControlExtension); - Add(bsCommentExtension, TGIFCommentExtension); - Add(bsApplicationExtension, TGIFApplicationExtension); -end; - -destructor TExtensionList.Destroy; -var - I: Integer; -begin - for I := 0 to Count-1 do - Dispose(PExtRec(Items[I])); - inherited Destroy; -end; - -procedure TExtensionList.Add(eLabel: BYTE; eClass: TGIFExtensionClass); -var - NewRec: PExtRec; -begin - New(NewRec); - with NewRec^ do - begin - ExtLabel := eLabel; - ExtClass := eClass; - end; - inherited Add(NewRec); -end; - -function TExtensionList.FindExt(eLabel: BYTE): TGIFExtensionClass; -var - I: Integer; -begin - for I := Count-1 downto 0 do - with PExtRec(Items[I])^ do - if ExtLabel = eLabel then - begin - Result := ExtClass; - Exit; - end; - Result := nil; -end; - -procedure TExtensionList.Remove(eClass: TGIFExtensionClass); -var - I: Integer; - P: PExtRec; -begin - for I := Count-1 downto 0 do - begin - P := PExtRec(Items[I]); - if P^.ExtClass.InheritsFrom(eClass) then - begin - Dispose(P); - Delete(I); - end; - end; -end; - -var - ExtensionList: TExtensionList = nil; - -function GetExtensionList: TExtensionList; -begin - if (ExtensionList = nil) then - ExtensionList := TExtensionList.Create; - Result := ExtensionList; -end; - -//////////////////////////////////////////////////////////////////////////////// -// -// TGIFExtension -// -//////////////////////////////////////////////////////////////////////////////// -function TGIFExtension.GetVersion: TGIFVersion; -begin - Result := gv89a; -end; - -class procedure TGIFExtension.RegisterExtension(eLabel: BYTE; eClass: TGIFExtensionClass); -begin - GetExtensionList.Add(eLabel, eClass); -end; - -class function TGIFExtension.FindExtension(Stream: TStream): TGIFExtensionClass; -var - eLabel : BYTE; - SubClass : TGIFExtensionClass; - Pos : LongInt; -begin - Pos := Stream.Position; - if (Stream.Read(eLabel, 1) <> 1) then - begin - Result := nil; - exit; - end; - Result := GetExtensionList.FindExt(eLabel); - while (Result <> nil) do - begin - SubClass := Result.FindSubExtension(Stream); - if (SubClass = Result) then - break; - Result := SubClass; - end; - Stream.Position := Pos; -end; - -class function TGIFExtension.FindSubExtension(Stream: TStream): TGIFExtensionClass; -begin - Result := self; -end; - -constructor TGIFExtension.Create(ASubImage: TGIFSubImage); -begin - inherited Create(ASubImage.Image); - FSubImage := ASubImage; -end; - -destructor TGIFExtension.Destroy; -begin - if (FSubImage <> nil) then - FSubImage.Extensions.Remove(self); - inherited Destroy; -end; - -procedure TGIFExtension.SaveToStream(Stream: TStream); -var - ExtensionLeadIn : TExtensionLeadIn; -begin - ExtensionLeadIn.Introducer := bsExtensionIntroducer; - ExtensionLeadIn.ExtensionLabel := ExtensionType; - Stream.Write(ExtensionLeadIn, sizeof(ExtensionLeadIn)); -end; - -function TGIFExtension.DoReadFromStream(Stream: TStream): TGIFExtensionType; -var - ExtensionLeadIn : TExtensionLeadIn; -begin - ReadCheck(Stream, ExtensionLeadIn, sizeof(ExtensionLeadIn)); - if (ExtensionLeadIn.Introducer <> bsExtensionIntroducer) then - Error(sBadExtensionLabel); - Result := ExtensionLeadIn.ExtensionLabel; -end; - -procedure TGIFExtension.LoadFromStream(Stream: TStream); -begin - // Seek past lead-in - // Stream.Seek(sizeof(TExtensionLeadIn), soFromCurrent); - if (DoReadFromStream(Stream) <> ExtensionType) then - Error(sBadExtensionInstance); -end; - -//////////////////////////////////////////////////////////////////////////////// -// -// TGIFGraphicControlExtension -// -//////////////////////////////////////////////////////////////////////////////// -const - { Extension flag bit masks } - efInputFlag = $02; { 00000010 } - efDisposal = $1C; { 00011100 } - efTransparent = $01; { 00000001 } - efReserved = $E0; { 11100000 } - -constructor TGIFGraphicControlExtension.Create(ASubImage: TGIFSubImage); -begin - inherited Create(ASubImage); - - FGCExtension.BlockSize := 4; - FGCExtension.PackedFields := $00; - FGCExtension.DelayTime := 0; - FGCExtension.TransparentColorIndex := 0; - FGCExtension.Terminator := 0; - if (ASubImage.FGCE = nil) then - ASubImage.FGCE := self; -end; - -destructor TGIFGraphicControlExtension.Destroy; -begin - // Clear transparent flag in sub image - if (Transparent) then - SubImage.FTransparent := False; - - if (SubImage.FGCE = self) then - SubImage.FGCE := nil; - - inherited Destroy; -end; - -function TGIFGraphicControlExtension.GetExtensionType: TGIFExtensionType; -begin - Result := bsGraphicControlExtension; -end; - -function TGIFGraphicControlExtension.GetTransparent: boolean; -begin - Result := (FGCExtension.PackedFields AND efTransparent) <> 0; -end; - -procedure TGIFGraphicControlExtension.SetTransparent(Value: boolean); -begin - // Set transparent flag in sub image - SubImage.FTransparent := Value; - if (Value) then - FGCExtension.PackedFields := FGCExtension.PackedFields OR efTransparent - else - FGCExtension.PackedFields := FGCExtension.PackedFields AND NOT(efTransparent); -end; - -function TGIFGraphicControlExtension.GetTransparentColor: TColor; -begin - Result := SubImage.ActiveColorMap[TransparentColorIndex]; -end; - -procedure TGIFGraphicControlExtension.SetTransparentColor(Color: TColor); -begin - FGCExtension.TransparentColorIndex := Subimage.ActiveColorMap.AddUnique(Color); -end; - -function TGIFGraphicControlExtension.GetTransparentColorIndex: BYTE; -begin - Result := FGCExtension.TransparentColorIndex; -end; - -procedure TGIFGraphicControlExtension.SetTransparentColorIndex(Value: BYTE); -begin - if ((Value >= SubImage.ActiveColorMap.Count) and (SubImage.ActiveColorMap.Count > 0)) then - begin - Warning(gsWarning, sBadColorIndex); - Value := 0; - end; - FGCExtension.TransparentColorIndex := Value; -end; - -function TGIFGraphicControlExtension.GetDelay: WORD; -begin - Result := FGCExtension.DelayTime; -end; -procedure TGIFGraphicControlExtension.SetDelay(Value: WORD); -begin - FGCExtension.DelayTime := Value; -end; - -function TGIFGraphicControlExtension.GetUserInput: boolean; -begin - Result := (FGCExtension.PackedFields AND efInputFlag) <> 0; -end; - -procedure TGIFGraphicControlExtension.SetUserInput(Value: boolean); -begin - if (Value) then - FGCExtension.PackedFields := FGCExtension.PackedFields OR efInputFlag - else - FGCExtension.PackedFields := FGCExtension.PackedFields AND NOT(efInputFlag); -end; - -function TGIFGraphicControlExtension.GetDisposal: TDisposalMethod; -begin - Result := TDisposalMethod((FGCExtension.PackedFields AND efDisposal) SHR 2); -end; - -procedure TGIFGraphicControlExtension.SetDisposal(Value: TDisposalMethod); -begin - FGCExtension.PackedFields := FGCExtension.PackedFields AND NOT(efDisposal) - OR ((ord(Value) SHL 2) AND efDisposal); -end; - -procedure TGIFGraphicControlExtension.SaveToStream(Stream: TStream); -begin - inherited SaveToStream(Stream); - Stream.Write(FGCExtension, sizeof(FGCExtension)); -end; - -procedure TGIFGraphicControlExtension.LoadFromStream(Stream: TStream); -begin - inherited LoadFromStream(Stream); - if (Stream.Read(FGCExtension, sizeof(FGCExtension)) <> sizeof(FGCExtension)) then - begin - Warning(gsWarning, sOutOfData); - exit; - end; - // Set transparent flag in sub image - if (Transparent) then - SubImage.FTransparent := True; -end; - -//////////////////////////////////////////////////////////////////////////////// -// -// TGIFTextExtension -// -//////////////////////////////////////////////////////////////////////////////// -constructor TGIFTextExtension.Create(ASubImage: TGIFSubImage); -begin - inherited Create(ASubImage); - FText := TStringList.Create; - FPlainTextExtension.BlockSize := 12; - FPlainTextExtension.Left := 0; - FPlainTextExtension.Top := 0; - FPlainTextExtension.Width := 0; - FPlainTextExtension.Height := 0; - FPlainTextExtension.CellWidth := 0; - FPlainTextExtension.CellHeight := 0; - FPlainTextExtension.TextFGColorIndex := 0; - FPlainTextExtension.TextBGColorIndex := 0; -end; - -destructor TGIFTextExtension.Destroy; -begin - FText.Free; - inherited Destroy; -end; - -function TGIFTextExtension.GetExtensionType: TGIFExtensionType; -begin - Result := bsPlainTextExtension; -end; - -function TGIFTextExtension.GetForegroundColor: TColor; -begin - Result := SubImage.ColorMap[ForegroundColorIndex]; -end; - -procedure TGIFTextExtension.SetForegroundColor(Color: TColor); -begin - ForegroundColorIndex := SubImage.ActiveColorMap.AddUnique(Color); -end; - -function TGIFTextExtension.GetBackgroundColor: TColor; -begin - Result := SubImage.ActiveColorMap[BackgroundColorIndex]; -end; - -procedure TGIFTextExtension.SetBackgroundColor(Color: TColor); -begin - BackgroundColorIndex := SubImage.ColorMap.AddUnique(Color); -end; - -function TGIFTextExtension.GetBounds(Index: integer): WORD; -begin - case (Index) of - 1: Result := FPlainTextExtension.Left; - 2: Result := FPlainTextExtension.Top; - 3: Result := FPlainTextExtension.Width; - 4: Result := FPlainTextExtension.Height; - else - Result := 0; // To avoid compiler warnings - end; -end; - -procedure TGIFTextExtension.SetBounds(Index: integer; Value: WORD); -begin - case (Index) of - 1: FPlainTextExtension.Left := Value; - 2: FPlainTextExtension.Top := Value; - 3: FPlainTextExtension.Width := Value; - 4: FPlainTextExtension.Height := Value; - end; -end; - -function TGIFTextExtension.GetCharWidthHeight(Index: integer): BYTE; -begin - case (Index) of - 1: Result := FPlainTextExtension.CellWidth; - 2: Result := FPlainTextExtension.CellHeight; - else - Result := 0; // To avoid compiler warnings - end; -end; - -procedure TGIFTextExtension.SetCharWidthHeight(Index: integer; Value: BYTE); -begin - case (Index) of - 1: FPlainTextExtension.CellWidth := Value; - 2: FPlainTextExtension.CellHeight := Value; - end; -end; - -function TGIFTextExtension.GetColorIndex(Index: integer): BYTE; -begin - case (Index) of - 1: Result := FPlainTextExtension.TextFGColorIndex; - 2: Result := FPlainTextExtension.TextBGColorIndex; - else - Result := 0; // To avoid compiler warnings - end; -end; - -procedure TGIFTextExtension.SetColorIndex(Index: integer; Value: BYTE); -begin - case (Index) of - 1: FPlainTextExtension.TextFGColorIndex := Value; - 2: FPlainTextExtension.TextBGColorIndex := Value; - end; -end; - -procedure TGIFTextExtension.SaveToStream(Stream: TStream); -begin - inherited SaveToStream(Stream); - Stream.Write(FPlainTextExtension, sizeof(FPlainTextExtension)); - WriteStrings(Stream, FText); -end; - -procedure TGIFTextExtension.LoadFromStream(Stream: TStream); -begin - inherited LoadFromStream(Stream); - ReadCheck(Stream, FPlainTextExtension, sizeof(FPlainTextExtension)); - ReadStrings(Stream, FText); -end; - -//////////////////////////////////////////////////////////////////////////////// -// -// TGIFCommentExtension -// -//////////////////////////////////////////////////////////////////////////////// -constructor TGIFCommentExtension.Create(ASubImage: TGIFSubImage); -begin - inherited Create(ASubImage); - FText := TStringList.Create; -end; - -destructor TGIFCommentExtension.Destroy; -begin - FText.Free; - inherited Destroy; -end; - -function TGIFCommentExtension.GetExtensionType: TGIFExtensionType; -begin - Result := bsCommentExtension; -end; - -procedure TGIFCommentExtension.SaveToStream(Stream: TStream); -begin - inherited SaveToStream(Stream); - WriteStrings(Stream, FText); -end; - -procedure TGIFCommentExtension.LoadFromStream(Stream: TStream); -begin - inherited LoadFromStream(Stream); - ReadStrings(Stream, FText); -end; - -//////////////////////////////////////////////////////////////////////////////// -// -// TGIFApplicationExtension registration database -// -//////////////////////////////////////////////////////////////////////////////// -type - PAppExtRec = ^TAppExtRec; - TAppExtRec = record - AppClass: TGIFAppExtensionClass; - Ident: TGIFApplicationRec; - end; - - TAppExtensionList = class(TList) - public - constructor Create; - destructor Destroy; override; - procedure Add(eIdent: TGIFApplicationRec; eClass: TGIFAppExtensionClass); - function FindExt(eIdent: TGIFApplicationRec): TGIFAppExtensionClass; - procedure Remove(eClass: TGIFAppExtensionClass); - end; - -constructor TAppExtensionList.Create; -const - NSLoopIdent: array[0..1] of TGIFApplicationRec = - ((Identifier: 'NETSCAPE'; Authentication: '2.0'), - (Identifier: 'ANIMEXTS'; Authentication: '1.0')); -begin - inherited Create; - Add(NSLoopIdent[0], TGIFAppExtNSLoop); - Add(NSLoopIdent[1], TGIFAppExtNSLoop); -end; - -destructor TAppExtensionList.Destroy; -var - I: Integer; -begin - for I := 0 to Count-1 do - Dispose(PAppExtRec(Items[I])); - inherited Destroy; -end; - -procedure TAppExtensionList.Add(eIdent: TGIFApplicationRec; eClass: TGIFAppExtensionClass); -var - NewRec: PAppExtRec; -begin - New(NewRec); - NewRec^.Ident := eIdent; - NewRec^.AppClass := eClass; - inherited Add(NewRec); -end; - -function TAppExtensionList.FindExt(eIdent: TGIFApplicationRec): TGIFAppExtensionClass; -var - I: Integer; -begin - for I := Count-1 downto 0 do - with PAppExtRec(Items[I])^ do - if CompareMem(@Ident, @eIdent, sizeof(TGIFApplicationRec)) then - begin - Result := AppClass; - Exit; - end; - Result := nil; -end; - -procedure TAppExtensionList.Remove(eClass: TGIFAppExtensionClass); -var - I: Integer; - P: PAppExtRec; -begin - for I := Count-1 downto 0 do - begin - P := PAppExtRec(Items[I]); - if P^.AppClass.InheritsFrom(eClass) then - begin - Dispose(P); - Delete(I); - end; - end; -end; - -var - AppExtensionList: TAppExtensionList = nil; - -function GetAppExtensionList: TAppExtensionList; -begin - if (AppExtensionList = nil) then - AppExtensionList := TAppExtensionList.Create; - Result := AppExtensionList; -end; - -class procedure TGIFApplicationExtension.RegisterExtension(eIdent: TGIFApplicationRec; - eClass: TGIFAppExtensionClass); -begin - GetAppExtensionList.Add(eIdent, eClass); -end; - -class function TGIFApplicationExtension.FindSubExtension(Stream: TStream): TGIFExtensionClass; -var - eIdent : TGIFApplicationRec; - OldPos : longInt; - Size : BYTE; -begin - OldPos := Stream.Position; - Result := nil; - if (Stream.Read(Size, 1) <> 1) then - exit; - - // Some old Adobe export filters mistakenly uses a value of 10 - if (Size = 10) then - begin - {.TODO -oanme -cImprovement : replace with seek or read and check contents = 'Adobe' } - if (Stream.Read(eIdent, 10) <> 10) then - exit; - Result := TGIFUnknownAppExtension; - exit; - end else - if (Size <> sizeof(TGIFApplicationRec)) or - (Stream.Read(eIdent, sizeof(eIdent)) <> sizeof(eIdent)) then - begin - Stream.Position := OldPos; - Result := inherited FindSubExtension(Stream); - end else - begin - Result := GetAppExtensionList.FindExt(eIdent); - if (Result = nil) then - Result := TGIFUnknownAppExtension; - end; -end; - -//////////////////////////////////////////////////////////////////////////////// -// -// TGIFApplicationExtension -// -//////////////////////////////////////////////////////////////////////////////// -constructor TGIFApplicationExtension.Create(ASubImage: TGIFSubImage); -begin - inherited Create(ASubImage); - FillChar(FIdent, sizeof(FIdent), 0); -end; - -destructor TGIFApplicationExtension.Destroy; -begin - inherited Destroy; -end; - -function TGIFApplicationExtension.GetExtensionType: TGIFExtensionType; -begin - Result := bsApplicationExtension; -end; - -function TGIFApplicationExtension.GetAuthentication: AnsiString; -begin - Result := FIdent.Authentication; -end; - -procedure TGIFApplicationExtension.SetAuthentication(const Value: AnsiString); -begin - if (Length(Value) < sizeof(TGIFAuthenticationCode)) then - FillChar(FIdent.Authentication, sizeof(TGIFAuthenticationCode), 32); - StrLCopy(@(FIdent.Authentication[0]), PAnsiChar(Value), sizeof(TGIFAuthenticationCode)); -end; - -function TGIFApplicationExtension.GetIdentifier: AnsiString; -begin - Result := FIdent.Identifier; -end; - -procedure TGIFApplicationExtension.SetIdentifier(const Value: AnsiString); -begin - if (Length(Value) < sizeof(TGIFIdentifierCode)) then - FillChar(FIdent.Identifier, sizeof(TGIFIdentifierCode), 32); - StrLCopy(@(FIdent.Identifier[0]), PAnsiChar(Value), sizeof(TGIFIdentifierCode)); -end; - -procedure TGIFApplicationExtension.SaveToStream(Stream: TStream); -begin - inherited SaveToStream(Stream); - WriteByte(Stream, sizeof(FIdent)); // Block size - Stream.Write(FIdent, sizeof(FIdent)); - SaveData(Stream); -end; - -procedure TGIFApplicationExtension.LoadFromStream(Stream: TStream); -var - i : integer; -begin - inherited LoadFromStream(Stream); - i := ReadByte(Stream); - // Some old Adobe export filters mistakenly uses a value of 10 - if (i = 10) then - FillChar(FIdent, sizeOf(FIdent), 0) - else - if (i < 11) then - Error(sBadBlockSize); - - ReadCheck(Stream, FIdent, sizeof(FIdent)); - - Dec(i, sizeof(FIdent)); - // Ignore extra data - Stream.Seek(i, soFromCurrent); - - // ***FIXME*** - // If self class is TGIFApplicationExtension, this will cause an "abstract - // error". - // TGIFApplicationExtension.LoadData should read and ignore rest of block. - LoadData(Stream); -end; - -//////////////////////////////////////////////////////////////////////////////// -// -// TGIFUnknownAppExtension -// -//////////////////////////////////////////////////////////////////////////////// -constructor TGIFBlock.Create(ASize: integer); -begin - inherited Create; - FSize := ASize; - GetMem(FData, FSize); - FillChar(FData^, FSize, 0); -end; - -destructor TGIFBlock.Destroy; -begin - FreeMem(FData); - inherited Destroy; -end; - -procedure TGIFBlock.SaveToStream(Stream: TStream); -begin - Stream.Write(FSize, 1); - Stream.Write(FData^, FSize); -end; - -procedure TGIFBlock.LoadFromStream(Stream: TStream); -begin - ReadCheck(Stream, FData^, FSize); -end; - -constructor TGIFUnknownAppExtension.Create(ASubImage: TGIFSubImage); -begin - inherited Create(ASubImage); - FBlocks := TList.Create; -end; - -destructor TGIFUnknownAppExtension.Destroy; -var - i : integer; -begin - for i := 0 to FBlocks.Count-1 do - TGIFBlock(FBlocks[i]).Free; - FBlocks.Free; - inherited Destroy; -end; - - -procedure TGIFUnknownAppExtension.SaveData(Stream: TStream); -var - i : integer; -begin - for i := 0 to FBlocks.Count-1 do - TGIFBlock(FBlocks[i]).SaveToStream(Stream); - // Terminating zero - WriteByte(Stream, 0); -end; - -procedure TGIFUnknownAppExtension.LoadData(Stream: TStream); -var - b : BYTE; - Block : TGIFBlock; - i : integer; -begin - // Zap old blocks - for i := 0 to FBlocks.Count-1 do - TGIFBlock(FBlocks[i]).Free; - FBlocks.Clear; - - // Read blocks - if (Stream.Read(b, 1) <> 1) then - exit; - while (b <> 0) do - begin - Block := TGIFBlock.Create(b); - try - Block.LoadFromStream(Stream); - except - Block.Free; - raise; - end; - FBlocks.Add(Block); - if (Stream.Read(b, 1) <> 1) then - exit; - end; -end; - -//////////////////////////////////////////////////////////////////////////////// -// -// TGIFAppExtNSLoop -// -//////////////////////////////////////////////////////////////////////////////// -const - // Netscape sub block types - nbLoopExtension = 1; - nbBufferExtension = 2; - -constructor TGIFAppExtNSLoop.Create(ASubImage: TGIFSubImage); -const - NSLoopIdent: TGIFApplicationRec = (Identifier: 'NETSCAPE'; Authentication: '2.0'); -begin - inherited Create(ASubImage); - FIdent := NSLoopIdent; -end; - -procedure TGIFAppExtNSLoop.SaveData(Stream: TStream); -begin - // Write loop count - WriteByte(Stream, 1 + sizeof(FLoops)); // Size of block - WriteByte(Stream, nbLoopExtension); // Identify sub block as looping extension data - Stream.Write(FLoops, sizeof(FLoops)); // Loop count - - // Write buffer size if specified - if (FBufferSize > 0) then - begin - WriteByte(Stream, 1 + sizeof(FBufferSize)); // Size of block - WriteByte(Stream, nbBufferExtension); // Identify sub block as buffer size data - Stream.Write(FBufferSize, sizeof(FBufferSize)); // Buffer size - end; - - WriteByte(Stream, 0); // Terminating zero -end; - -procedure TGIFAppExtNSLoop.LoadData(Stream: TStream); -var - BlockSize : integer; - BlockType : integer; -begin - // Read size of first block or terminating zero - BlockSize := ReadByte(Stream); - while (BlockSize <> 0) do - begin - BlockType := ReadByte(Stream); - dec(BlockSize); - - case (BlockType AND $07) of - nbLoopExtension: - begin - if (BlockSize < sizeof(FLoops)) then - Error(sInvalidData); - // Read loop count - ReadCheck(Stream, FLoops, sizeof(FLoops)); - dec(BlockSize, sizeof(FLoops)); - end; - nbBufferExtension: - begin - if (BlockSize < sizeof(FBufferSize)) then - Error(sInvalidData); - // Read buffer size - ReadCheck(Stream, FBufferSize, sizeof(FBufferSize)); - dec(BlockSize, sizeof(FBufferSize)); - end; - end; - - // Skip/ignore unread data - if (BlockSize > 0) then - Stream.Seek(BlockSize, soFromCurrent); - - // Read size of next block or terminating zero - BlockSize := ReadByte(Stream); - end; -end; - - -//////////////////////////////////////////////////////////////////////////////// -// -// TGIFImageList -// -//////////////////////////////////////////////////////////////////////////////// -function TGIFImageList.GetImage(Index: Integer): TGIFSubImage; -begin - Result := TGIFSubImage(Items[Index]); -end; - -procedure TGIFImageList.SetImage(Index: Integer; SubImage: TGIFSubImage); -begin - Items[Index] := SubImage; -end; - -procedure TGIFImageList.LoadFromStream(Stream: TStream; Parent: TObject); -var - b : BYTE; - SubImage : TGIFSubImage; -begin - // Peek ahead to determine block type - repeat - if (Stream.Read(b, 1) <> 1) then - exit; - until (b <> 0); // Ignore 0 padding (non-compliant) - - while (b <> bsTrailer) do - begin - Stream.Seek(-1, soFromCurrent); - if (b in [bsExtensionIntroducer, bsImageDescriptor]) then - begin - SubImage := TGIFSubImage.Create(Parent as TGIFImage); - try - SubImage.LoadFromStream(Stream); - Add(SubImage); - Image.Progress(Self, psRunning, MulDiv(Stream.Position, 100, Stream.Size), - GIFImageRenderOnLoad, Rect(0,0,0,0), sProgressLoading); - except - SubImage.Free; - raise; - end; - end else - begin - Warning(gsWarning, sBadBlock); - break; - end; - repeat - if (Stream.Read(b, 1) <> 1) then - exit; - until (b <> 0); // Ignore 0 padding (non-compliant) - end; - Stream.Seek(-1, soFromCurrent); -end; - -procedure TGIFImageList.SaveToStream(Stream: TStream); -var - i : integer; -begin - for i := 0 to Count-1 do - begin - TGIFItem(Items[i]).SaveToStream(Stream); - Image.Progress(Self, psRunning, MulDiv((i+1), 100, Count), False, Rect(0,0,0,0), sProgressSaving); - end; -end; - -//////////////////////////////////////////////////////////////////////////////// -// -// TGIFPainter -// -//////////////////////////////////////////////////////////////////////////////// -constructor TGIFPainter.CreateRef(Painter: PGIFPainter; AImage: TGIFImage; - ACanvas: TCanvas; ARect: TRect; Options: TGIFDrawOptions); -begin - Create(AImage, ACanvas, ARect, Options); - PainterRef := Painter; - if (PainterRef <> nil) then - PainterRef^ := self; -end; - -constructor TGIFPainter.Create(AImage: TGIFImage; ACanvas: TCanvas; ARect: TRect; - Options: TGIFDrawOptions); -var - i : integer; - BackgroundColor : TColor; - Disposals : set of TDisposalMethod; -begin - inherited Create(True); - FreeOnTerminate := True; - Onterminate := DoOnTerminate; - FImage := AImage; - FCanvas := ACanvas; - FRect := ARect; - FActiveImage := -1; - FDrawOptions := Options; - FStarted := False; - BackupBuffer := nil; - FrameBuffer := nil; - Background := nil; - FEventHandle := 0; - // This should be a parameter, but I think I've got enough of them already... - FAnimationSpeed := FImage.AnimationSpeed; - - // An event handle is used for animation delays - if (FDrawOptions >= [goAnimate, goAsync]) and (FImage.Images.Count > 1) and - (FAnimationSpeed >= 0) then - FEventHandle := CreateEvent(nil, False, False, nil); - - // Preprocessing of extensions to determine if we need frame buffers - Disposals := []; - if (FImage.DrawBackgroundColor = clNone) then - begin - if (FImage.GlobalColorMap.Count > 0) then - BackgroundColor := FImage.BackgroundColor - else - BackgroundColor := ColorToRGB(clWindow); - end else - BackgroundColor := ColorToRGB(FImage.DrawBackgroundColor); - - // Need background buffer to clear on loop - if (goClearOnLoop in FDrawOptions) then - Include(Disposals, dmBackground); - - for i := 0 to FImage.Images.Count-1 do - if (FImage.Images[i].GraphicControlExtension <> nil) then - with (FImage.Images[i].GraphicControlExtension) do - Include(Disposals, Disposal); - - // Need background buffer to draw transparent on background - if (dmBackground in Disposals) and (goTransparent in FDrawOptions) then - begin - Background := TBitmap.Create; - Background.Height := FRect.Bottom-FRect.Top; - Background.Width := FRect.Right-FRect.Left; - // Copy background immediately - Background.Canvas.CopyMode := cmSrcCopy; - Background.Canvas.CopyRect(Background.Canvas.ClipRect, FCanvas, FRect); - end; - // Need frame- and backup buffer to restore to previous and background - if ((Disposals * [dmPrevious, dmBackground]) <> []) then - begin - BackupBuffer := TBitmap.Create; - BackupBuffer.Height := FRect.Bottom-FRect.Top; - BackupBuffer.Width := FRect.Right-FRect.Left; - BackupBuffer.Canvas.CopyMode := cmSrcCopy; - BackupBuffer.Canvas.Brush.Color := BackgroundColor; - BackupBuffer.Canvas.Brush.Style := bsSolid; -{$IFDEF DEBUG} - BackupBuffer.Canvas.Brush.Color := clBlack; - BackupBuffer.Canvas.Brush.Style := bsDiagCross; -{$ENDIF} - // Step 1: Copy destination to backup buffer - // Always executed before first frame and only once. - BackupBuffer.Canvas.CopyRect(BackupBuffer.Canvas.ClipRect, FCanvas, FRect); - FrameBuffer := TBitmap.Create; - FrameBuffer.Height := FRect.Bottom-FRect.Top; - FrameBuffer.Width := FRect.Right-FRect.Left; - FrameBuffer.Canvas.CopyMode := cmSrcCopy; - FrameBuffer.Canvas.Brush.Color := BackgroundColor; - FrameBuffer.Canvas.Brush.Style := bsSolid; -{$IFDEF DEBUG} - FrameBuffer.Canvas.Brush.Color := clBlack; - FrameBuffer.Canvas.Brush.Style := bsDiagCross; -{$ENDIF} - end; -end; - -destructor TGIFPainter.Destroy; -begin - // OnTerminate isn't called if we are running in main thread, so we must call - // it manually - if not(goAsync in DrawOptions) then - DoOnTerminate(self); - // Reraise any exptions that were eaten in the Execute method - if (ExceptObject <> nil) then - raise ExceptObject at ExceptAddress; - inherited Destroy; -end; - -procedure TGIFPainter.SetAnimationSpeed(Value: integer); -begin - if (Value < 0) then - Value := 0 - else if (Value > 1000) then - Value := 1000; - if (Value <> FAnimationSpeed) then - begin - FAnimationSpeed := Value; - // Signal WaitForSingleObject delay to abort - if (FEventHandle <> 0) then - SetEvent(FEventHandle) - else - DoRestart := True; - end; -end; - -procedure TGIFPainter.SetActiveImage(const Value: integer); -begin - if (Value >= 0) and (Value < FImage.Images.Count) then - FActiveImage := Value; -end; - -// Conditional Synchronize -procedure TGIFPainter.DoSynchronize(Method: TThreadMethod); -begin - if (Terminated) then - exit; - if (goAsync in FDrawOptions) then - // Execute Synchronized if requested... - Synchronize(Method) - else - // ...Otherwise just execute in current thread (probably main thread) - Method; -end; - -// Delete frame buffers - Executed in main thread -procedure TGIFPainter.DoOnTerminate(Sender: TObject); -begin - // It shouldn't really be nescessary to protect PainterRef in this manner - // since we are running in the main thread at this point, but I'm a little - // paranoid about the way PainterRef is being used... - if Image <> nil then // 2001.02.23 - begin // 2001.02.23 - with Image.Painters.LockList do - try - // Zap pointer to self and remove from painter list - if (PainterRef <> nil) and (PainterRef^ = self) then - PainterRef^ := nil; - finally - Image.Painters.UnLockList; - end; - Image.Painters.Remove(self); - FImage := nil; - end; // 2001.02.23 - - // Free buffers - if (BackupBuffer <> nil) then - BackupBuffer.Free; - if (FrameBuffer <> nil) then - FrameBuffer.Free; - if (Background <> nil) then - Background.Free; - - // Delete event handle - if (FEventHandle <> 0) then - CloseHandle(FEventHandle); -end; - -// Event "dispatcher" - Executed in main thread -procedure TGIFPainter.DoEvent; -begin - if (Assigned(FEvent)) then - FEvent(self); -end; - -// Non-buffered paint - Executed in main thread -procedure TGIFPainter.DoPaint; -begin - FImage.Images[ActiveImage].Draw(FCanvas, FRect, (goTransparent in FDrawOptions), - (goTile in FDrawOptions)); - FStarted := True; -end; - -// Buffered paint - Executed in main thread -procedure TGIFPainter.DoPaintFrame; -var - DrawDestination : TCanvas; - DrawRect : TRect; - DoStep2 , - DoStep3 , - DoStep5 , - DoStep6 : boolean; - SavePal , - SourcePal : HPALETTE; - - procedure ClearBackup; - var - r , - Tile : TRect; - FrameTop , - FrameHeight : integer; - ImageWidth , - ImageHeight : integer; - begin - - if (goTransparent in FDrawOptions) then - begin - // If the frame is transparent, we must remove it by copying the - // background buffer over it - if (goTile in FDrawOptions) then - begin - FrameTop := FImage.Images[ActiveImage].Top; - FrameHeight := FImage.Images[ActiveImage].Height; - ImageWidth := FImage.Width; - ImageHeight := FImage.Height; - - Tile.Left := FRect.Left + FImage.Images[ActiveImage].Left; - Tile.Right := Tile.Left + FImage.Images[ActiveImage].Width; - while (Tile.Left < FRect.Right) do - begin - Tile.Top := FRect.Top + FrameTop; - Tile.Bottom := Tile.Top + FrameHeight; - while (Tile.Top < FRect.Bottom) do - begin - BackupBuffer.Canvas.CopyRect(Tile, Background.Canvas, Tile); - Tile.Top := Tile.Top + ImageHeight; - Tile.Bottom := Tile.Bottom + ImageHeight; - end; - Tile.Left := Tile.Left + ImageWidth; - Tile.Right := Tile.Right + ImageWidth; - end; - end else - begin - r := FImage.Images[ActiveImage].ScaleRect(BackupBuffer.Canvas.ClipRect); - BackupBuffer.Canvas.CopyRect(r, Background.Canvas, r) - end; - end else - begin - // If the frame isn't transparent, we just clear the area covered by - // it to the background color. - // Tile the background unless the frame covers all of the image - if (goTile in FDrawOptions) and - ((FImage.Width <> FImage.Images[ActiveImage].Width) and - (FImage.height <> FImage.Images[ActiveImage].Height)) then - begin - FrameTop := FImage.Images[ActiveImage].Top; - FrameHeight := FImage.Images[ActiveImage].Height; - ImageWidth := FImage.Width; - ImageHeight := FImage.Height; - // ***FIXME*** I don't think this does any difference - BackupBuffer.Canvas.Brush.Color := FImage.DrawBackgroundColor; - - Tile.Left := FRect.Left + FImage.Images[ActiveImage].Left; - Tile.Right := Tile.Left + FImage.Images[ActiveImage].Width; - while (Tile.Left < FRect.Right) do - begin - Tile.Top := FRect.Top + FrameTop; - Tile.Bottom := Tile.Top + FrameHeight; - while (Tile.Top < FRect.Bottom) do - begin - BackupBuffer.Canvas.FillRect(Tile); - - Tile.Top := Tile.Top + ImageHeight; - Tile.Bottom := Tile.Bottom + ImageHeight; - end; - Tile.Left := Tile.Left + ImageWidth; - Tile.Right := Tile.Right + ImageWidth; - end; - end else - BackupBuffer.Canvas.FillRect(FImage.Images[ActiveImage].ScaleRect(FRect)); - end; - end; - -begin - if (goValidateCanvas in FDrawOptions) then - if (GetObjectType(ValidateDC) <> OBJ_DC) then - begin - Terminate; - exit; - end; - - DrawDestination := nil; - DoStep2 := (goClearOnLoop in FDrawOptions) and (FActiveImage = 0); - DoStep3 := False; - DoStep5 := False; - DoStep6 := False; -{ -Disposal mode algorithm: - -Step 1: Copy destination to backup buffer - Always executed before first frame and only once. - Done in constructor. -Step 2: Clear previous frame (implementation is same as step 6) - Done implicitly by implementation. - Only done explicitly on first frame if goClearOnLoop option is set. -Step 3: Copy backup buffer to frame buffer -Step 4: Draw frame -Step 5: Copy buffer to destination -Step 6: Clear frame from backup buffer -+------------+------------------+---------------------+------------------------+ -|New \ Old | dmNone | dmBackground | dmPrevious | -+------------+------------------+---------------------+------------------------+ -|dmNone | | | | -| |4. Paint on backup|4. Paint on backup |4. Paint on backup | -| |5. Restore |5. Restore |5. Restore | -+------------+------------------+---------------------+------------------------+ -|dmBackground| | | | -| |4. Paint on backup|4. Paint on backup |4. Paint on backup | -| |5. Restore |5. Restore |5. Restore | -| |6. Clear backup |6. Clear backup |6. Clear backup | -+------------+------------------+---------------------+------------------------+ -|dmPrevious | | | | -| | |3. Copy backup to buf|3. Copy backup to buf | -| |4. Paint on dest |4. Paint on buf |4. Paint on buf | -| | |5. Copy buf to dest |5. Copy buf to dest | -+------------+------------------+---------------------+------------------------+ -} - case (Disposal) of - dmNone, dmNoDisposal: - begin - DrawDestination := BackupBuffer.Canvas; - DrawRect := BackupBuffer.Canvas.ClipRect; - DoStep5 := True; - end; - dmBackground: - begin - DrawDestination := BackupBuffer.Canvas; - DrawRect := BackupBuffer.Canvas.ClipRect; - DoStep5 := True; - DoStep6 := True; - end; - dmPrevious: - case (OldDisposal) of - dmNone, dmNoDisposal: - begin - DrawDestination := FCanvas; - DrawRect := FRect; - end; - dmBackground, dmPrevious: - begin - DrawDestination := FrameBuffer.Canvas; - DrawRect := FrameBuffer.Canvas.ClipRect; - DoStep3 := True; - DoStep5 := True; - end; - end; - end; - - // Find source palette - SourcePal := FImage.Images[ActiveImage].Palette; - if (SourcePal = 0) then - SourcePal := SystemPalette16; // This should never happen - - SavePal := SelectPalette(DrawDestination.Handle, SourcePal, False); - RealizePalette(DrawDestination.Handle); - - // Step 2: Clear previous frame - if (DoStep2) then - ClearBackup; - - // Step 3: Copy backup buffer to frame buffer - if (DoStep3) then - FrameBuffer.Canvas.CopyRect(FrameBuffer.Canvas.ClipRect, - BackupBuffer.Canvas, BackupBuffer.Canvas.ClipRect); - - // Step 4: Draw frame - if (DrawDestination <> nil) then - FImage.Images[ActiveImage].Draw(DrawDestination, DrawRect, - (goTransparent in FDrawOptions), (goTile in FDrawOptions)); - - // Step 5: Copy buffer to destination - if (DoStep5) then - begin - FCanvas.CopyMode := cmSrcCopy; - FCanvas.CopyRect(FRect, DrawDestination, DrawRect); - end; - - if (SavePal <> 0) then - SelectPalette(DrawDestination.Handle, SavePal, False); - - // Step 6: Clear frame from backup buffer - if (DoStep6) then - ClearBackup; - - FStarted := True; -end; - -// Prefetch bitmap -// Used to force the GIF image to be rendered as a bitmap -{$ifdef SERIALIZE_RENDER} -procedure TGIFPainter.PrefetchBitmap; -begin - // Touch current bitmap to force bitmap to be rendered - if not((FImage.Images[ActiveImage].Empty) or (FImage.Images[ActiveImage].HasBitmap)) then - FImage.Images[ActiveImage].Bitmap; -end; -{$endif} - -// Main thread execution loop - This is where it all happens... -procedure TGIFPainter.Execute; -var - i : integer; - LoopCount , - LoopPoint : integer; - Looping : boolean; - Ext : TGIFExtension; - Msg : TMsg; - Delay , - OldDelay , - DelayUsed : longInt; - DelayStart , - NewDelayStart : DWORD; - - procedure FireEvent(Event: TNotifyEvent); - begin - if not(Assigned(Event)) then - exit; - FEvent := Event; - try - DoSynchronize(DoEvent); - finally - FEvent := nil; - end; - end; - -begin -{ - Disposal: - dmNone: Same as dmNodisposal - dmNoDisposal: Do not dispose - dmBackground: Clear with background color *) - dmPrevious: Previous image - *) Note: Background color should either be a BROWSER SPECIFIED Background - color (DrawBackgroundColor) or the background image if any frames are - transparent. -} - try - try - if (goValidateCanvas in FDrawOptions) then - ValidateDC := FCanvas.Handle; - DoRestart := True; - - // Loop to restart paint - while (DoRestart) and not(Terminated) do - begin - FActiveImage := 0; - // Fire OnStartPaint event - // Note: ActiveImage may be altered by the event handler - FireEvent(FOnStartPaint); - - FStarted := False; - DoRestart := False; - LoopCount := 1; - LoopPoint := FActiveImage; - Looping := False; - if (goAsync in DrawOptions) then - Delay := 0 - else - Delay := 1; // Dummy to process messages - OldDisposal := dmNoDisposal; - // Fetch delay start time - DelayStart := timeGetTime; - OldDelay := 0; - - // Loop to loop - duh! - while ((LoopCount <> 0) or (goLoopContinously in DrawOptions)) and - not(Terminated or DoRestart) do - begin - FActiveImage := LoopPoint; - - // Fire OnLoopPaint event - // Note: ActiveImage may be altered by the event handler - if (FStarted) then - FireEvent(FOnLoop); - - // Loop to animate - while (ActiveImage < FImage.Images.Count) and not(Terminated or DoRestart) do - begin - // Ignore empty images - if (FImage.Images[ActiveImage].Empty) then - break; - // Delay from previous image - if (Delay > 0) then - begin - // Prefetch frame bitmap -{$ifdef SERIALIZE_RENDER} - DoSynchronize(PrefetchBitmap); -{$else} - FImage.Images[ActiveImage].Bitmap; -{$endif} - - // Calculate inter frame delay - NewDelayStart := timeGetTime; - if (FAnimationSpeed > 0) then - begin - // Calculate number of mS used in prefetch and display - try - DelayUsed := integer(NewDelayStart-DelayStart)-OldDelay; - // Prevent feedback oscillations caused by over/undercompensation. - DelayUsed := DelayUsed DIV 2; - // Convert delay value to mS and... - // ...Adjust for time already spent converting GIF to bitmap and... - // ...Adjust for Animation Speed factor. - Delay := MulDiv(Delay * GIFDelayExp - DelayUsed, 100, FAnimationSpeed); - OldDelay := Delay; - except - Delay := GIFMaximumDelay * GIFDelayExp; - OldDelay := 0; - end; - end else - begin - if (goAsync in DrawOptions) then - Delay := longInt(INFINITE) - else - Delay := GIFMaximumDelay * GIFDelayExp; - end; - // Fetch delay start time - DelayStart := NewDelayStart; - - // Sleep in one chunk if we are running in a thread - if (goAsync in DrawOptions) then - begin - // Use of WaitForSingleObject allows TGIFPainter.Stop to wake us up - if (Delay > 0) or (FAnimationSpeed = 0) then - begin - if (WaitForSingleObject(FEventHandle, DWORD(Delay)) <> WAIT_TIMEOUT) then - begin - // Don't use interframe delay feedback adjustment if delay - // were prematurely aborted (e.g. because the animation - // speed were changed) - OldDelay := 0; - DelayStart := longInt(timeGetTime); - end; - end; - end else - begin - if (Delay <= 0) then - Delay := 1; - // Fetch start time - NewDelayStart := timeGetTime; - // If we are not running in a thread we Sleep in small chunks - // and give the user a chance to abort - while (Delay > 0) and not(Terminated or DoRestart) do - begin - if (Delay < 100) then - Sleep(Delay) - else - Sleep(100); - // Calculate number of mS delayed in this chunk - DelayUsed := integer(timeGetTime - NewDelayStart); - dec(Delay, DelayUsed); - // Reset start time for chunk - NewDelaySTart := timeGetTime; - // Application.ProcessMessages wannabe - while (not(Terminated or DoRestart)) and - (PeekMessage(Msg, 0, 0, 0, PM_REMOVE)) do - begin - if (Msg.Message <> WM_QUIT) then - begin - TranslateMessage(Msg); - DispatchMessage(Msg); - end else - begin - // Put WM_QUIT back in queue and get out of here fast - PostQuitMessage(Msg.WParam); - Terminate; - end; - end; - end; - end; - end else - Sleep(0); // Yield - if (Terminated) then - break; - - // Fire OnPaint event - // Note: ActiveImage may be altered by the event handler - FireEvent(FOnPaint); - if (Terminated) then - break; - - // Pre-draw processing of extensions - Disposal := dmNoDisposal; - for i := 0 to FImage.Images[ActiveImage].Extensions.Count-1 do - begin - Ext := FImage.Images[ActiveImage].Extensions[i]; - if (Ext is TGIFAppExtNSLoop) then - begin - // Recursive loops not supported (or defined) - if (Looping) then - continue; - Looping := True; - LoopCount := TGIFAppExtNSLoop(Ext).Loops; - if ((LoopCount = 0) or (goLoopContinously in DrawOptions)) and - (goAsync in DrawOptions) then - LoopCount := -1; // Infinite if running in separate thread -{$IFNDEF STRICT_MOZILLA} - // Loop from this image and on - // Note: This is not standard behavior - LoopPoint := ActiveImage; -{$ENDIF} - end else - if (Ext is TGIFGraphicControlExtension) then - Disposal := TGIFGraphicControlExtension(Ext).Disposal; - end; - - // Paint the image - if (BackupBuffer <> nil) then - DoSynchronize(DoPaintFrame) - else - DoSynchronize(DoPaint); - OldDisposal := Disposal; - - if (Terminated) then - break; - - Delay := GIFDefaultDelay; // Default delay - // Post-draw processing of extensions - if (FImage.Images[ActiveImage].GraphicControlExtension <> nil) then - if (FImage.Images[ActiveImage].GraphicControlExtension.Delay > 0) then - begin - Delay := FImage.Images[ActiveImage].GraphicControlExtension.Delay; - - // Enforce minimum animation delay in compliance with Mozilla - if (Delay < GIFMinimumDelay) then - Delay := GIFMinimumDelay; - - // Do not delay more than 10 seconds if running in main thread - if (Delay > GIFMaximumDelay) and not(goAsync in DrawOptions) then - Delay := GIFMaximumDelay; // Max 10 seconds - end; - // Fire OnAfterPaint event - // Note: ActiveImage may be altered by the event handler - i := FActiveImage; - FireEvent(FOnAfterPaint); - if (Terminated) then - break; - // Don't increment frame counter if event handler modified - // current frame - if (FActiveImage = i) then - Inc(FActiveImage); - // Nothing more to do unless we are animating - if not(goAnimate in DrawOptions) then - break; - end; - - if (LoopCount > 0) then - Dec(LoopCount); - if ([goAnimate, goLoop] * DrawOptions <> [goAnimate, goLoop]) then - break; - end; - if (Terminated) then // 2001.07.23 - break; // 2001.07.23 - end; - FActiveImage := -1; - // Fire OnEndPaint event - FireEvent(FOnEndPaint); - finally - // If we are running in the main thread we will have to zap our self - if not(goAsync in DrawOptions) then - Free; - end; - except - on E: Exception do - begin - // Eat exception and terminate thread... - // If we allow the exception to abort the thread at this point, the - // application will hang since the thread destructor will never be called - // and the application will wait forever for the thread to die! - Terminate; - // Clone exception - ExceptObject := E.Create(E.Message); - ExceptAddress := ExceptAddr; - end; - end; -end; - -procedure TGIFPainter.Start; -begin - if (goAsync in FDrawOptions) then - Resume; -end; - -procedure TGIFPainter.Stop; -begin - Terminate; - if (goAsync in FDrawOptions) then - begin - // Signal WaitForSingleObject delay to abort - if (FEventHandle <> 0) then - SetEvent(FEventHandle); - Priority := tpNormal; - if (Suspended) then - Resume; // Must be running before we can terminate - end; -end; - -procedure TGIFPainter.Restart; -begin - DoRestart := True; - if (Suspended) and (goAsync in FDrawOptions) then - Resume; // Must be running before we can terminate -end; - -//////////////////////////////////////////////////////////////////////////////// -// -// TColorMapOptimizer -// -//////////////////////////////////////////////////////////////////////////////// -// Used by TGIFImage to optimize local color maps to a single global color map. -// The following algorithm is used: -// 1) Build a histogram for each image -// 2) Merge histograms -// 3) Sum equal colors and adjust max # of colors -// 4) Map entries > max to entries <= 256 -// 5) Build new color map -// 6) Map images to new color map -//////////////////////////////////////////////////////////////////////////////// - -type - - POptimizeEntry = ^TOptimizeEntry; - TColorRec = record - case byte of - 0: (Value: integer); - 1: (Color: TGIFColor); - 2: (SameAs: POptimizeEntry); // Used if TOptimizeEntry.Count = 0 - end; - - TOptimizeEntry = record - Count : integer; // Usage count - OldIndex : integer; // Color OldIndex - NewIndex : integer; // NewIndex color OldIndex - Color : TColorRec; // Color value - end; - - TOptimizeEntries = array[0..255] of TOptimizeEntry; - POptimizeEntries = ^TOptimizeEntries; - - THistogram = class(TObject) - private - PHistogram : POptimizeEntries; - FCount : integer; - FColorMap : TGIFColorMap; - FList : TList; - FImages : TList; - public - constructor Create(AColorMap: TGIFColorMap); - destructor Destroy; override; - function ProcessSubImage(Image: TGIFSubImage): boolean; - function Prune: integer; - procedure MapImages(UseTransparency: boolean; NewTransparentColorIndex: byte); - property Count: integer read FCount; - property ColorMap: TGIFColorMap read FColorMap; - property List: TList read FList; - end; - - TColorMapOptimizer = class(TObject) - private - FImage : TGIFImage; - FHistogramList : TList; - FHistogram : TList; - FColorMap : TColorMap; - FFinalCount : integer; - FUseTransparency : boolean; - FNewTransparentColorIndex: byte; - protected - procedure ProcessImage; - procedure MergeColors; - procedure MapColors; - procedure ReplaceColorMaps; - public - constructor Create(AImage: TGIFImage); - destructor Destroy; override; - procedure Optimize; - end; - -function CompareColor(Item1, Item2: Pointer): integer; -begin - Result := POptimizeEntry(Item2)^.Color.Value - POptimizeEntry(Item1)^.Color.Value; -end; - -function CompareCount(Item1, Item2: Pointer): integer; -begin - Result := POptimizeEntry(Item2)^.Count - POptimizeEntry(Item1)^.Count; -end; - -constructor THistogram.Create(AColorMap: TGIFColorMap); -var - i : integer; -begin - inherited Create; - - FCount := AColorMap.Count; - FColorMap := AColorMap; - - FImages := TList.Create; - - // Allocate memory for histogram - GetMem(PHistogram, FCount * sizeof(TOptimizeEntry)); - FList := TList.Create; - - FList.Capacity := FCount; - - // Move data to histogram and initialize - for i := 0 to FCount-1 do - with PHistogram^[i] do - begin - FList.Add(@PHistogram^[i]); - OldIndex := i; - Count := 0; - Color.Value := 0; - Color.Color := AColorMap.Data^[i]; - NewIndex := 256; // Used to signal unmapped - end; -end; - -destructor THistogram.Destroy; -begin - FImages.Free; - FList.Free; - FreeMem(PHistogram); - inherited Destroy; -end; - -//: Build a color histogram -function THistogram.ProcessSubImage(Image: TGIFSubImage): boolean; -var - Size : integer; - Pixel : PAnsiChar; - IsTransparent , - WasTransparent : boolean; - OldTransparentColorIndex: byte; -begin - Result := False; - if (Image.Empty) then - exit; - - FImages.Add(Image); - - Pixel := Image.data; - Size := Image.Width * Image.Height; - - IsTransparent := Image.Transparent; - if (IsTransparent) then - OldTransparentColorIndex := Image.GraphicControlExtension.TransparentColorIndex - else - OldTransparentColorIndex := 0; // To avoid compiler warning - WasTransparent := False; - - (* - ** Sum up usage count for each color - *) - while (Size > 0) do - begin - // Ignore transparent pixels - if (not IsTransparent) or (ord(Pixel^) <> OldTransparentColorIndex) then - begin - // Check for invalid color index - if (ord(Pixel^) >= FCount) then - begin - Pixel^ := #0; // ***FIXME*** Isn't this an error condition? - Image.Warning(gsWarning, sInvalidColor); - end; - - with PHistogram^[ord(Pixel^)] do - begin - // Stop if any color reaches the max count - if (Count = high(integer)) then - break; - inc(Count); - end; - end else - WasTransparent := WasTransparent or IsTransparent; - inc(Pixel); - dec(Size); - end; - - (* - ** Clear frames transparency flag if the frame claimed to - ** be transparent, but wasn't - *) - if (IsTransparent and not WasTransparent) then - begin - Image.GraphicControlExtension.TransparentColorIndex := 0; - Image.GraphicControlExtension.Transparent := False; - end; - - Result := WasTransparent; -end; - -//: Removed unused color entries from the histogram -function THistogram.Prune: integer; -var - i, j : integer; -begin - (* - ** Sort by usage count - *) - FList.Sort(CompareCount); - - (* - ** Determine number of used colors - *) - for i := 0 to FCount-1 do - // Find first unused color entry - if (POptimizeEntry(FList[i])^.Count = 0) then - begin - // Zap unused colors - for j := i to FCount-1 do - POptimizeEntry(FList[j])^.Count := -1; // Use -1 to signal unused entry - // Remove unused entries - FCount := i; - FList.Count := FCount; - break; - end; - - Result := FCount; -end; - -//: Convert images from old color map to new color map -procedure THistogram.MapImages(UseTransparency: boolean; NewTransparentColorIndex: byte); -var - i : integer; - Size : integer; - Pixel : PAnsiChar; - ReverseMap : array[byte] of byte; - IsTransparent : boolean; - OldTransparentColorIndex: byte; -begin - (* - ** Build NewIndex map - *) - for i := 0 to List.Count-1 do - ReverseMap[POptimizeEntry(List[i])^.OldIndex] := POptimizeEntry(List[i])^.NewIndex; - - (* - ** Reorder all images using this color map - *) - for i := 0 to FImages.Count-1 do - with TGIFSubImage(FImages[i]) do - begin - Pixel := Data; - Size := Width * Height; - - // Determine frame transparency - IsTransparent := (Transparent) and (UseTransparency); - if (IsTransparent) then - begin - OldTransparentColorIndex := GraphicControlExtension.TransparentColorIndex; - // Map transparent color - GraphicControlExtension.TransparentColorIndex := NewTransparentColorIndex; - end else - OldTransparentColorIndex := 0; // To avoid compiler warning - - // Map all pixels to new color map - while (Size > 0) do - begin - // Map transparent pixels to the new transparent color index and... - if (IsTransparent) and (ord(Pixel^) = OldTransparentColorIndex) then - Pixel^ := AnsiChar(NewTransparentColorIndex) - else - // ... all other pixels to their new color index - Pixel^ := AnsiChar(ReverseMap[ord(Pixel^)]); - dec(size); - inc(Pixel); - end; - end; -end; - -constructor TColorMapOptimizer.Create(AImage: TGIFImage); -begin - inherited Create; - FImage := AImage; - FHistogramList := TList.Create; - FHistogram := TList.Create; -end; - -destructor TColorMapOptimizer.Destroy; -var - i : integer; -begin - FHistogram.Free; - - for i := FHistogramList.Count-1 downto 0 do - THistogram(FHistogramList[i]).Free; - FHistogramList.Free; - - inherited Destroy; -end; - -procedure TColorMapOptimizer.ProcessImage; -var - Hist : THistogram; - i : integer; - ProcessedImage : boolean; -begin - FUseTransparency := False; - (* - ** First process images using global color map - *) - if (FImage.GlobalColorMap.Count > 0) then - begin - Hist := THistogram.Create(FImage.GlobalColorMap); - ProcessedImage := False; - // Process all images that are using the global color map - for i := 0 to FImage.Images.Count-1 do - if (FImage.Images[i].ColorMap.Count = 0) and (not FImage.Images[i].Empty) then - begin - ProcessedImage := True; - // Note: Do not change order of statements. Shortcircuit evaluation not desired! - FUseTransparency := Hist.ProcessSubImage(FImage.Images[i]) or FUseTransparency; - end; - // Keep the histogram if any images used the global color map... - if (ProcessedImage) then - FHistogramList.Add(Hist) - else // ... otherwise delete it - Hist.Free; - end; - - (* - ** Next process images that have a local color map - *) - for i := 0 to FImage.Images.Count-1 do - if (FImage.Images[i].ColorMap.Count > 0) and (not FImage.Images[i].Empty) then - begin - Hist := THistogram.Create(FImage.Images[i].ColorMap); - FHistogramList.Add(Hist); - // Note: Do not change order of statements. Shortcircuit evaluation not desired! - FUseTransparency := Hist.ProcessSubImage(FImage.Images[i]) or FUseTransparency; - end; -end; - -procedure TColorMapOptimizer.MergeColors; -var - Entry, SameEntry : POptimizeEntry; - i : integer; -begin - (* - ** Sort by color value - *) - FHistogram.Sort(CompareColor); - - (* - ** Merge same colors - *) - SameEntry := POptimizeEntry(FHistogram[0]); - for i := 1 to FHistogram.Count-1 do - begin - Entry := POptimizeEntry(FHistogram[i]); - ASSERT(Entry^.Count > 0, 'Unused entry exported from THistogram'); - if (Entry^.Color.Value = SameEntry^.Color.Value) then - begin - // Transfer usage count to first entry - inc(SameEntry^.Count, Entry^.Count); - Entry^.Count := 0; // Use 0 to signal merged entry - Entry^.Color.SameAs := SameEntry; // Point to master - end else - SameEntry := Entry; - end; -end; - -procedure TColorMapOptimizer.MapColors; -var - i, j : integer; - Delta, BestDelta : integer; - BestIndex : integer; - MaxColors : integer; -begin - (* - ** Sort by usage count - *) - FHistogram.Sort(CompareCount); - - (* - ** Handle transparency - *) - if (FUseTransparency) then - MaxColors := 255 - else - MaxColors := 256; - - (* - ** Determine number of colors used (max 256) - *) - FFinalCount := FHistogram.Count; - for i := 0 to FFinalCount-1 do - if (i >= MaxColors) or (POptimizeEntry(FHistogram[i])^.Count = 0) then - begin - FFinalCount := i; - break; - end; - - (* - ** Build color map and reverse map for final entries - *) - for i := 0 to FFinalCount-1 do - begin - POptimizeEntry(FHistogram[i])^.NewIndex := i; - FColorMap[i] := POptimizeEntry(FHistogram[i])^.Color.Color; - end; - - (* - ** Map colors > 256 to colors <= 256 and build NewIndex color map - *) - for i := FFinalCount to FHistogram.Count-1 do - with POptimizeEntry(FHistogram[i])^ do - begin - // Entries with a usage count of -1 is unused - ASSERT(Count <> -1, 'Internal error: Unused entry exported'); - // Entries with a usage count of 0 has been merged with another entry - if (Count = 0) then - begin - // Use mapping of master entry - ASSERT(Color.SameAs.NewIndex < 256, 'Internal error: Mapping to unmapped color'); - NewIndex := Color.SameAs.NewIndex; - end else - begin - // Search for entry with nearest color value - BestIndex := 0; - BestDelta := 255*3; - for j := 0 to FFinalCount-1 do - begin - Delta := ABS((POptimizeEntry(FHistogram[j])^.Color.Color.Red - Color.Color.Red) + - (POptimizeEntry(FHistogram[j])^.Color.Color.Green - Color.Color.Green) + - (POptimizeEntry(FHistogram[j])^.Color.Color.Blue - Color.Color.Blue)); - if (Delta < BestDelta) then - begin - BestDelta := Delta; - BestIndex := j; - end; - end; - NewIndex := POptimizeEntry(FHistogram[BestIndex])^.NewIndex;; - end; - end; - - (* - ** Add transparency color to new color map - *) - if (FUseTransparency) then - begin - FNewTransparentColorIndex := FFinalCount; - FColorMap[FFinalCount].Red := 0; - FColorMap[FFinalCount].Green := 0; - FColorMap[FFinalCount].Blue := 0; - inc(FFinalCount); - end; -end; - -procedure TColorMapOptimizer.ReplaceColorMaps; -var - i : integer; -begin - // Zap all local color maps - for i := 0 to FImage.Images.Count-1 do - if (FImage.Images[i].ColorMap <> nil) then - FImage.Images[i].ColorMap.Clear; - // Store optimized global color map - FImage.GlobalColorMap.ImportColorMap(FColorMap, FFinalCount); - FImage.GlobalColorMap.Optimized := True; -end; - -procedure TColorMapOptimizer.Optimize; -var - Total : integer; - i, j : integer; -begin - // Stop all painters during optimize... - FImage.PaintStop; - // ...and prevent any new from starting while we are doing our thing - FImage.Painters.LockList; - try - - (* - ** Process all sub images - *) - ProcessImage; - - // Prune histograms and calculate total number of colors - Total := 0; - for i := 0 to FHistogramList.Count-1 do - inc(Total, THistogram(FHistogramList[i]).Prune); - - // Allocate global histogram - FHistogram.Clear; - FHistogram.Capacity := Total; - - // Move data pointers from local histograms to global histogram - for i := 0 to FHistogramList.Count-1 do - with THistogram(FHistogramList[i]) do - for j := 0 to Count-1 do - begin - ASSERT(POptimizeEntry(List[j])^.Count > 0, 'Unused entry exported from THistogram'); - FHistogram.Add(List[j]); - end; - - (* - ** Merge same colors - *) - MergeColors; - - (* - ** Build color map and NewIndex map for final entries - *) - MapColors; - - (* - ** Replace local colormaps with global color map - *) - ReplaceColorMaps; - - (* - ** Process images for each color map - *) - for i := 0 to FHistogramList.Count-1 do - THistogram(FHistogramList[i]).MapImages(FUseTransparency, FNewTransparentColorIndex); - - (* - ** Delete the frame's old bitmaps and palettes - *) - for i := 0 to FImage.Images.Count-1 do - begin - FImage.Images[i].HasBitmap := False; - FImage.Images[i].Palette := 0; - end; - - finally - FImage.Painters.UnlockList; - end; -end; - -//////////////////////////////////////////////////////////////////////////////// -// -// TGIFImage -// -//////////////////////////////////////////////////////////////////////////////// -constructor TGIFImage.Create; -begin - inherited Create; - FImages := TGIFImageList.Create(self); - FHeader := TGIFHeader.Create(self); - FPainters := TThreadList.Create; - FGlobalPalette := 0; - // Load defaults - FDrawOptions := GIFImageDefaultDrawOptions; - ColorReduction := GIFImageDefaultColorReduction; - FReductionBits := GIFImageDefaultColorReductionBits; - FDitherMode := GIFImageDefaultDitherMode; - FCompression := GIFImageDefaultCompression; - FThreadPriority := GIFImageDefaultThreadPriority; - FAnimationSpeed := GIFImageDefaultAnimationSpeed; - - FDrawBackgroundColor := clNone; - IsDrawing := False; - IsInsideGetPalette := False; - FForceFrame := -1; // 2004.03.09 - NewImage; -end; - -destructor TGIFImage.Destroy; -var - i : integer; -begin - PaintStop; - with FPainters.LockList do - try - for i := Count-1 downto 0 do - TGIFPainter(Items[i]).FImage := nil; - finally - FPainters.UnLockList; - end; - - Clear; - FPainters.Free; - FImages.Free; - FHeader.Free; - inherited Destroy; -end; - -procedure TGIFImage.Clear; -begin - PaintStop; - FreeBitmap; - FImages.Clear; - FHeader.ColorMap.Clear; - FHeader.Height := 0; - FHeader.Width := 0; - FHeader.Prepare; - Palette := 0; -end; - -procedure TGIFImage.NewImage; -begin - Clear; -end; - -function TGIFImage.GetVersion: TGIFVersion; -var - v : TGIFVersion; - i : integer; -begin - Result := gvUnknown; - for i := 0 to FImages.Count-1 do - begin - v := FImages[i].Version; - if (v > Result) then - Result := v; - if (v >= high(TGIFVersion)) then - break; - end; -end; - -function TGIFImage.GetColorResolution: integer; -var - i : integer; -begin - Result := FHeader.ColorResolution; - for i := 0 to FImages.Count-1 do - if (FImages[i].ColorResolution > Result) then - Result := FImages[i].ColorResolution; -end; - -function TGIFImage.GetBitsPerPixel: integer; -var - i : integer; -begin - Result := FHeader.BitsPerPixel; - for i := 0 to FImages.Count-1 do - if (FImages[i].BitsPerPixel > Result) then - Result := FImages[i].BitsPerPixel; -end; - -function TGIFImage.GetBackgroundColorIndex: BYTE; -begin - Result := FHeader.BackgroundColorIndex; -end; - -procedure TGIFImage.SetBackgroundColorIndex(const Value: BYTE); -begin - FHeader.BackgroundColorIndex := Value; -end; - -function TGIFImage.GetBackgroundColor: TColor; -begin - Result := FHeader.BackgroundColor; -end; - -procedure TGIFImage.SetBackgroundColor(const Value: TColor); -begin - FHeader.BackgroundColor := Value; -end; - -function TGIFImage.GetAspectRatio: BYTE; -begin - Result := FHeader.AspectRatio; -end; - -procedure TGIFImage.SetAspectRatio(const Value: BYTE); -begin - FHeader.AspectRatio := Value; -end; - -procedure TGIFImage.SetDrawOptions(Value: TGIFDrawOptions); -begin - if (FDrawOptions = Value) then - exit; - - if (DrawPainter <> nil) then - DrawPainter.Stop; - - FDrawOptions := Value; - // Zap all bitmaps - Pack; - Changed(self); -end; - -function TGIFImage.GetAnimate: Boolean; -begin // 2002.07.07 - Result:= goAnimate in DrawOptions; -end; - -procedure TGIFImage.SetAnimate(const Value: Boolean); -begin // 2002.07.07 - if Value then - DrawOptions:= DrawOptions + [goAnimate] - else - DrawOptions:= DrawOptions - [goAnimate]; -end; - -procedure TGIFImage.SetForceFrame(const Value: Integer); -begin // 2004.03.09 - FForceFrame := Value; - Changed(Self); -end; - -procedure TGIFImage.SetAnimationSpeed(Value: integer); -begin - if (Value < 0) then - Value := 0 - else if (Value > 1000) then - Value := 1000; - if (Value <> FAnimationSpeed) then - begin - FAnimationSpeed := Value; - // Use the FPainters threadlist to protect FDrawPainter from being modified - // by the thread while we mess with it - with FPainters.LockList do - try - if (FDrawPainter <> nil) then - FDrawPainter.AnimationSpeed := FAnimationSpeed; - finally - // Release the lock on FPainters to let paint thread kill itself - FPainters.UnLockList; - end; - end; -end; - -procedure TGIFImage.SetReductionBits(Value: integer); -begin - if (Value < 3) or (Value > 8) then - Error(sInvalidBitSize); - FReductionBits := Value; -end; - -procedure TGIFImage.OptimizeColorMap; -var - ColorMapOptimizer : TColorMapOptimizer; -begin - ColorMapOptimizer := TColorMapOptimizer.Create(self); - try - ColorMapOptimizer.Optimize; - finally - ColorMapOptimizer.Free; - end; -end; - -procedure TGIFImage.Optimize(Options: TGIFOptimizeOptions; - ColorReduction: TColorReduction; DitherMode: TDitherMode; - ReductionBits: integer); -var - i , - j : integer; - Delay : integer; - GCE : TGIFGraphicControlExtension; - ThisRect , - NextRect , - MergeRect : TRect; - Prog , - MaxProg : integer; - - function Scan(Buf: PAnsiChar; Value: Byte; Count: integer): boolean; assembler; - asm - PUSH EDI - MOV EDI, Buf - MOV ECX, Count - MOV AL, Value - REPNE SCASB - MOV EAX, False - JNE @@1 - MOV EAX, True -@@1:POP EDI - end; - -begin - if (Empty) then - exit; - // Stop all painters during optimize... - PaintStop; - // ...and prevent any new from starting while we are doing our thing - FPainters.LockList; - try - Progress(Self, psStarting, 0, False, Rect(0,0,0,0), sProgressOptimizing); - try - - Prog := 0; - MaxProg := Images.Count*6; - - // Sort color map by usage and remove unused entries - if (ooColorMap in Options) then - begin - // Optimize global color map - if (GlobalColorMap.Count > 0) then - GlobalColorMap.Optimize; - // Optimize local color maps - for i := 0 to Images.Count-1 do - begin - inc(Prog); - if (Images[i].ColorMap.Count > 0) then - begin - Images[i].ColorMap.Optimize; - Progress(Self, psRunning, MulDiv(Prog, 100, MaxProg), False, - Rect(0,0,0,0), sProgressOptimizing); - end; - end; - end; - - // Remove passive elements, pass 1 - if (ooCleanup in Options) then - begin - // Check for transparency flag without any transparent pixels - for i := 0 to Images.Count-1 do - begin - inc(Prog); - if (Images[i].Transparent) then - begin - if not(Scan(Images[i].Data, - Images[i].GraphicControlExtension.TransparentColorIndex, - Images[i].DataSize)) then - begin - Images[i].GraphicControlExtension.Transparent := False; - Progress(Self, psRunning, MulDiv(Prog, 100, MaxProg), False, - Rect(0,0,0,0), sProgressOptimizing); - end; - end; - end; - - // Change redundant disposal modes - for i := 0 to Images.Count-2 do - begin - inc(Prog); - if (Images[i].GraphicControlExtension <> nil) and - (Images[i].GraphicControlExtension.Disposal in [dmPrevious, dmBackground]) and - (not Images[i+1].Transparent) then - begin - ThisRect := Images[i].BoundsRect; - NextRect := Images[i+1].BoundsRect; - if (not IntersectRect(MergeRect, ThisRect, NextRect)) then - continue; - // If the next frame completely covers the current frame, - // change the disposal mode to dmNone - if (EqualRect(MergeRect, NextRect)) then - Images[i].GraphicControlExtension.Disposal := dmNone; - Progress(Self, psRunning, MulDiv(Prog, 100, MaxProg), False, - Rect(0,0,0,0), sProgressOptimizing); - end; - end; - end else - inc(Prog, 2*Images.Count); - - // Merge layers of equal pixels (remove redundant pixels) - if (ooMerge in Options) then - begin - // Merge from last to first to avoid intefering with merge - for i := Images.Count-1 downto 1 do - begin - inc(Prog); - j := i-1; - // If the "previous" frames uses dmPrevious disposal mode, we must - // instead merge with the frame before the previous - while (j > 0) and - ((Images[j].GraphicControlExtension <> nil) and - (Images[j].GraphicControlExtension.Disposal = dmPrevious)) do - dec(j); - // Merge - Images[i].Merge(Images[j]); - Progress(Self, psRunning, MulDiv(Prog, 100, MaxProg), False, - Rect(0,0,0,0), sProgressOptimizing); - end; - end else - inc(Prog, Images.Count); - - // Crop transparent areas - if (ooCrop in Options) then - begin - for i := Images.Count-1 downto 0 do - begin - inc(Prog); - if (not Images[i].Empty) and (Images[i].Transparent) then - begin - // Remember frames delay in case frame is deleted - Delay := Images[i].GraphicControlExtension.Delay; - // Crop - Images[i].Crop; - // If the frame was completely transparent we remove it - if (Images[i].Empty) then - begin - // Transfer delay to previous frame in case frame was deleted - if (i > 0) and (Images[i-1].Transparent) then - Images[i-1].GraphicControlExtension.Delay := - Images[i-1].GraphicControlExtension.Delay + Delay; - Images.Delete(i); - end; - Progress(Self, psRunning, MulDiv(Prog, 100, MaxProg), False, - Rect(0,0,0,0), sProgressOptimizing); - end; - end; - end else - inc(Prog, Images.Count); - - // Remove passive elements, pass 2 - inc(Prog, Images.Count); - if (ooCleanup in Options) then - begin - for i := Images.Count-1 downto 0 do - begin - // Remove comments and application extensions - for j := Images[i].Extensions.Count-1 downto 0 do - if (Images[i].Extensions[j] is TGIFCommentExtension) or - (Images[i].Extensions[j] is TGIFTextExtension) or - (Images[i].Extensions[j] is TGIFUnknownAppExtension) or - ((Images[i].Extensions[j] is TGIFAppExtNSLoop) and - ((i > 0) or (Images.Count = 1))) then - Images[i].Extensions.Delete(j); - if (Images[i].GraphicControlExtension <> nil) then - begin - GCE := Images[i].GraphicControlExtension; - // Zap GCE if all of the following are true: - // * No delay or only one image - // * Not transparent - // * No prompt - // * No disposal or only one image - if ((GCE.Delay = 0) or (Images.Count = 1)) and - (not GCE.Transparent) and - (not GCE.UserInput) and - ((GCE.Disposal in [dmNone, dmNoDisposal]) or (Images.Count = 1)) then - begin - GCE.Free; - end; - end; - // Zap frame if it has become empty - if (Images[i].Empty) and (Images[i].Extensions.Count = 0) then - Images[i].Free; - end; - Progress(Self, psRunning, MulDiv(Prog, 100, MaxProg), False, - Rect(0,0,0,0), sProgressOptimizing); - end else - - // Reduce color depth - if (ooReduceColors in Options) then - begin - if (ColorReduction = rmPalette) then - Error(sInvalidReduction); - {.TODO -oanme -cFeature : Implement ooReduceColors option. } - // Not implemented! - end; - finally - if ExceptObject = nil then - i := 100 - else - i := 0; - Progress(Self, psEnding, i, False, Rect(0,0,0,0), sProgressOptimizing); - end; - finally - FPainters.UnlockList; - end; -end; - -procedure TGIFImage.Pack; -var - i : integer; -begin - // Zap bitmaps and palettes - FreeBitmap; - Palette := 0; - for i := 0 to FImages.Count-1 do - begin - FImages[i].Bitmap := nil; - FImages[i].Palette := 0; - end; - - // Only pack if no global colormap and a single image - if (FHeader.ColorMap.Count > 0) or (FImages.Count <> 1) then - exit; - - // Copy local colormap to global - FHeader.ColorMap.Assign(FImages[0].ColorMap); - // Zap local colormap - FImages[0].ColorMap.Clear; -end; - -procedure TGIFImage.SaveToStream(Stream: TStream); -var - n : Integer; -begin - Progress(Self, psStarting, 0, False, Rect(0,0,0,0), sProgressSaving); - try - // Write header - FHeader.SaveToStream(Stream); - // Write images - FImages.SaveToStream(Stream); - // Write trailer - with TGIFTrailer.Create(self) do - try - SaveToStream(Stream); - finally - Free; - end; - finally - if ExceptObject = nil then - n := 100 - else - n := 0; - Progress(Self, psEnding, n, True, Rect(0,0,0,0), sProgressSaving); - end; -end; - -// 2006.07.09 -> -{$IFDEF FIXHEADER_WIDTHHEIGHT_SILENT} -procedure TGIFImage.FixHeaderWidthHeight; -var - i, w, h: Integer; -begin - for i := 0 to Images.Count - 1 do - begin - w := Images.SubImages[i].Left + Images.SubImages[i].Width; - h := Images.SubImages[i].Top + Images.SubImages[i].Height; - if w > Header.Width then - Header.Width := w; - if h > Header.Height then - Header.Height := h; - end; -end; -{$ENDIF} -// 2006.07.09 <- - -procedure TGIFImage.LoadFromStream(Stream: TStream); -var - n : Integer; - Position : integer; -begin - Progress(Self, psStarting, 0, False, Rect(0,0,0,0), sProgressLoading); - try - // Zap old image - Clear; - Position := Stream.Position; - try - // Read header - FHeader.LoadFromStream(Stream); - // Read images - FImages.LoadFromStream(Stream, self); - {$IFDEF FIXHEADER_WIDTHHEIGHT_SILENT} - FixHeaderWidthHeight; // 2006.07.09 - {$ENDIF} - // Read trailer - with TGIFTrailer.Create(self) do - try - LoadFromStream(Stream); - finally - Free; - end; - except - // Restore stream position in case of error. - // Not required, but "a nice thing to do" - Stream.Position := Position; - raise; - end; - finally - if ExceptObject = nil then - n := 100 - else - n := 0; - Progress(Self, psEnding, n, True, Rect(0,0,0,0), sProgressLoading); - end; -end; - -procedure TGIFImage.LoadFromResourceName(Instance: THandle; const ResName: String); -// 2002.07.07 -var - Stream: TCustomMemoryStream; -begin - Stream := TResourceStream.Create(Instance, ResName, RT_RCDATA); - try - LoadFromStream(Stream); - finally - Stream.Free; - end; -end; - -function TGIFImage.GetBitmap: TBitmap; -begin - if not(Empty) then - begin - Result := FBitmap; - if (Result <> nil) then - exit; - FBitmap := TBitmap.Create; - Result := FBitmap; - FBitmap.OnChange := Changed; - // Use first image as default - if (Images.Count > 0) then - begin - if (Images[0].Width = Width) and (Images[0].Height = Height) then - begin - // Use first image as it has same dimensions - FBitmap.Assign(Images[0].Bitmap); - end else - begin - // Draw first image on bitmap - FBitmap.Palette := CopyPalette(Palette); - FBitmap.Height := Height; - FBitmap.Width := Width; - Images[0].Draw(FBitmap.Canvas, FBitmap.Canvas.ClipRect, False, False); - end; - end; - end else - Result := nil -end; - -// Create a new (empty) bitmap -function TGIFImage.NewBitmap: TBitmap; -begin - Result := FBitmap; - if (Result <> nil) then - exit; - FBitmap := TBitmap.Create; - Result := FBitmap; - FBitmap.OnChange := Changed; - // Draw first image on bitmap - FBitmap.Palette := CopyPalette(Palette); - FBitmap.Height := Height; - FBitmap.Width := Width; -end; - -procedure TGIFImage.FreeBitmap; -begin - if (DrawPainter <> nil) then - DrawPainter.Stop; - - if (FBitmap <> nil) then - begin - FBitmap.Free; - FBitmap := nil; - end; -end; - -function TGIFImage.Add(Source: TPersistent): integer; -var - Image : TGIFSubImage; -begin - Image := nil; // To avoid compiler warning - not needed. - if (Source is TGraphic) then - begin - Image := TGIFSubImage.Create(self); - try - Image.Assign(Source); - // ***FIXME*** Documentation should explain the inconsistency here: - // TGIFimage does not take ownership of Source after TGIFImage.Add() and - // therefore does not delete Source. - except - Image.Free; - raise; - end; - end else - if (Source is TGIFSubImage) then - Image := TGIFSubImage(Source) - else - Error(sUnsupportedClass); - - Result := FImages.Add(Image); - - FreeBitmap; - Changed(self); -end; - -function TGIFImage.GetEmpty: Boolean; -begin - Result := (FImages.Count = 0); -end; - -function TGIFImage.GetHeight: Integer; -begin - Result := FHeader.Height; -end; - -function TGIFImage.GetWidth: Integer; -begin - Result := FHeader.Width; -end; - -function TGIFImage.GetIsTransparent: Boolean; -var - i : integer; -begin - Result := False; - for i := 0 to Images.Count-1 do - if (Images[i].GraphicControlExtension <> nil) and - (Images[i].GraphicControlExtension.Transparent) then - begin - Result := True; - exit; - end; -end; - -function TGIFImage.Equals(Graphic: TGraphic): Boolean; -begin - Result := (Graphic = self); -end; - -function TGIFImage.GetPalette: HPALETTE; -begin - // Check for recursion - // (TGIFImage.GetPalette->TGIFSubImage.GetPalette->TGIFImage.GetPalette etc...) - if (IsInsideGetPalette) then - Error(sNoColorTable); - IsInsideGetPalette := True; - try - Result := 0; - if (FBitmap <> nil) and (FBitmap.Palette <> 0) then - // Use bitmaps own palette if possible - Result := FBitmap.Palette - else if (FGlobalPalette <> 0) then - // Or a previously exported global palette - Result := FGlobalPalette - else if (DoDither) then - begin - // or create a new dither palette - FGlobalPalette := WebPalette; - Result := FGlobalPalette; - end else - if (FHeader.ColorMap.Count > 0) then - begin - // or create a new if first time - FGlobalPalette := FHeader.ColorMap.ExportPalette; - Result := FGlobalPalette; - end else - if (FImages.Count > 0) then - // This can cause a recursion if no global palette exist and image[0] - // hasn't got one either. Checked by the IsInsideGetPalette semaphor. - Result := FImages[0].Palette; - finally - IsInsideGetPalette := False; - end; -end; - -procedure TGIFImage.SetPalette(Value: HPalette); -var - NeedNewBitmap : boolean; -begin - if (Value <> FGlobalPalette) then - begin - // Zap old palette - if (FGlobalPalette <> 0) then - DeleteObject(FGlobalPalette); - - // Zap bitmap unless new palette is same as bitmaps own - NeedNewBitmap := (FBitmap <> nil) and (Value <> FBitmap.Palette); - - // Use new palette - FGlobalPalette := Value; - - if (NeedNewBitmap) then - begin - // Need to create new bitmap and repaint - FreeBitmap; - PaletteModified := True; - Changed(Self); - end; - end; -end; - -// Obsolete -// procedure TGIFImage.Changed(Sender: TObject); -// begin -// inherited Changed(Sender); -// end; - -procedure TGIFImage.SetHeight(Value: Integer); -var - i : integer; -begin - for i := 0 to Images.Count-1 do - if (Images[i].Top + Images[i].Height > Value) then - Error(sBadHeight); - if (Value <> Header.Height) then - begin - Header.Height := Value; - FreeBitmap; - Changed(self); - end; -end; - -procedure TGIFImage.SetWidth(Value: Integer); -var - i : integer; -begin - for i := 0 to Images.Count-1 do - if (Images[i].Left + Images[i].Width > Value) then - Error(sBadWidth); - if (Value <> Header.Width) then - begin - Header.Width := Value; - FreeBitmap; - Changed(self); - end; -end; - -procedure TGIFImage.WriteData(Stream: TStream); -begin - if (GIFImageOptimizeOnStream) then - Optimize([ooCrop, ooMerge, ooCleanup, ooColorMap, ooReduceColors], rmNone, dmNearest, 8); - - inherited WriteData(Stream); -end; - -procedure TGIFImage.AssignTo(Dest: TPersistent); -begin - if (Dest is TBitmap) then - Dest.Assign(Bitmap) - else - inherited AssignTo(Dest); -end; - -{.TODO 1 -oanme -cImprovement : Better handling of TGIFImage.Assign(Empty TBitmap). } -procedure TGIFImage.Assign(Source: TPersistent); -var - i : integer; - Image : TGIFSubImage; -begin - if (Source = self) then - exit; - if (Source = nil) then - begin - Clear; - end else - // - // TGIFImage import - // - if (Source is TGIFImage) then - begin - Clear; - // Temporarily copy event handlers to be able to generate progress events - // during the copy and handle copy errors - OnProgress := TGIFImage(Source).OnProgress; - try - FOnWarning := TGIFImage(Source).OnWarning; - Progress(Self, psStarting, 0, False, Rect(0,0,0,0), sProgressCopying); - try - FHeader.Assign(TGIFImage(Source).Header); - FThreadPriority := TGIFImage(Source).ThreadPriority; - FDrawBackgroundColor := TGIFImage(Source).DrawBackgroundColor; - FDrawOptions := TGIFImage(Source).DrawOptions; - FColorReduction := TGIFImage(Source).ColorReduction; - FDitherMode := TGIFImage(Source).DitherMode; - FForceFrame := TGIFImage(Source).ForceFrame; // 2004.03.09 -// 2002.07.07 -> - FOnWarning:= TGIFImage(Source).FOnWarning; - FOnStartPaint:= TGIFImage(Source).FOnStartPaint; - FOnPaint:= TGIFImage(Source).FOnPaint; - FOnEndPaint:= TGIFImage(Source).FOnEndPaint; - FOnAfterPaint:= TGIFImage(Source).FOnAfterPaint; - FOnLoop:= TGIFImage(Source).FOnLoop; -// 2002.07.07 <- - for i := 0 to TGIFImage(Source).Images.Count-1 do - begin - Image := TGIFSubImage.Create(self); - Image.Assign(TGIFImage(Source).Images[i]); - Add(Image); - Progress(Self, psRunning, MulDiv((i+1), 100, TGIFImage(Source).Images.Count), - False, Rect(0,0,0,0), sProgressCopying); - end; - {$IFDEF FIXHEADER_WIDTHHEIGHT_SILENT} - FixHeaderWidthHeight; // 2006.07.09 - {$ENDIF} - finally - if ExceptObject = nil then - i := 100 - else - i := 0; - Progress(Self, psEnding, i, False, Rect(0,0,0,0), sProgressCopying); - end; - finally - // Reset event handlers - FOnWarning := nil; - OnProgress := nil; - end; - end else - // - // Import via TGIFSubImage.Assign - // - begin - Clear; - Image := TGIFSubImage.Create(self); - try - Image.Assign(Source); - Add(Image); - except - on E: EConvertError do - begin - Image.Free; - // Unsupported format - fall back to Source.AssignTo - inherited Assign(Source); - end; - else - // Unknown conversion error - Image.Free; - raise; - end; - end; -end; - -procedure TGIFImage.LoadFromClipboardFormat(AFormat: Word; AData: THandle; - APalette: HPALETTE); -{$IFDEF REGISTER_TGIFIMAGE} -var - Size : Longint; - Buffer : Pointer; - Stream : TMemoryStream; - Bmp : TBitmap; -{$ENDIF} // 2002.07.07 -begin // 2002.07.07 -{$IFDEF REGISTER_TGIFIMAGE} // 2002.07.07 - if (AData = 0) then - AData := GetClipboardData(AFormat); - if (AData <> 0) and (AFormat = CF_GIF) then - begin - // Get size and pointer to data - Size := GlobalSize(AData); - Buffer := GlobalLock(AData); - try - Stream := TMemoryStream.Create; - try - // Copy data to a stream - Stream.SetSize(Size); - Move(Buffer^, Stream.Memory^, Size); - // Load GIF from stream - LoadFromStream(Stream); - finally - Stream.Free; - end; - finally - GlobalUnlock(AData); - end; - end else - if (AData <> 0) and (AFormat = CF_BITMAP) then - begin - // No GIF on clipboard - try loading a bitmap instead - Bmp := TBitmap.Create; - try - Bmp.LoadFromClipboardFormat(AFormat, AData, APalette); - Assign(Bmp); - finally - Bmp.Free; - end; - end else - Error(sUnknownClipboardFormat); -{$ELSE} // 2002.07.07 - Error(sGIFToClipboard); // 2002.07.07 -{$ENDIF} // 2002.07.07 -end; - -procedure TGIFImage.SaveToClipboardFormat(var AFormat: Word; var AData: THandle; - var APalette: HPALETTE); -{$IFDEF REGISTER_TGIFIMAGE} -var - Stream : TMemoryStream; - Data : THandle; - Buffer : Pointer; -{$ENDIF} // 2002.07.07 -begin // 2002.07.07 -{$IFDEF REGISTER_TGIFIMAGE} // 2002.07.07 - if (Empty) then - exit; - // First store a bitmap version on the clipboard... - Bitmap.SaveToClipboardFormat(AFormat, AData, APalette); - // ...then store a GIF - Stream := TMemoryStream.Create; - try - // Save the GIF to a memory stream - SaveToStream(Stream); - Stream.Position := 0; - // Allocate some memory for the GIF data - Data := GlobalAlloc(HeapAllocFlags, Stream.Size); - try - if (Data <> 0) then - begin - Buffer := GlobalLock(Data); - try - // Copy GIF data from stream memory to clipboard memory - Move(Stream.Memory^, Buffer^, Stream.Size); - finally - GlobalUnlock(Data); - end; - // Transfer data to clipboard - if (SetClipboardData(CF_GIF, Data) = 0) then - Error(sFailedPaste); - end; - except - GlobalFree(Data); - raise; - end; - finally - Stream.Free; - end; -{$ELSE} // 2002.07.07 - Error(sGIFToClipboard); // 2002.07.07 -{$ENDIF} // 2002.07.07 -end; - -function TGIFImage.GetColorMap: TGIFColorMap; -begin - Result := FHeader.ColorMap; -end; - -function TGIFImage.GetDoDither: boolean; -begin - Result := (goDither in DrawOptions) and - (((goAutoDither in DrawOptions) and DoAutoDither) or - not(goAutoDither in DrawOptions)); -end; - -{$IFDEF VER9x} -procedure TGIFImage.Progress(Sender: TObject; Stage: TProgressStage; - PercentDone: Byte; RedrawNow: Boolean; const R: TRect; const Msg: string); -begin - if Assigned(FOnProgress) then - FOnProgress(Sender, Stage, PercentDone, RedrawNow, R, Msg); -end; -{$ENDIF} - -procedure TGIFImage.StopDraw; -{$IFNDEF VER14_PLUS} // 2001.07.23 -var - Msg : TMsg; - ThreadWindow : HWND; -{$ENDIF} // 2001.07.23 -begin - repeat - // Use the FPainters threadlist to protect FDrawPainter from being modified - // by the thread while we mess with it - with FPainters.LockList do - try - if (FDrawPainter = nil) then - break; - - // Tell thread to terminate - FDrawPainter.Stop; - - // No need to wait for "thread" to terminate if running in main thread - if not(goAsync in FDrawPainter.DrawOptions) then - break; - - finally - // Release the lock on FPainters to let paint thread kill itself - FPainters.UnLockList; - end; - -{$IFDEF VER14_PLUS} -// 2002.07.07 - if (GetCurrentThreadID = MainThreadID) then - while CheckSynchronize do {loop}; -{$ELSE} - // Process Messages to make Synchronize work - // (Instead of Application.ProcessMessages) -//{$IFDEF VER14_PLUS} // 2001.07.23 -// Break; // 2001.07.23 -// Sleep(0); // Yield // 2001.07.23 -//{$ELSE} // 2001.07.23 - ThreadWindow := FindWindow('TThreadWindow', nil); - while PeekMessage(Msg, ThreadWindow, CM_DESTROYWINDOW, CM_EXECPROC, PM_REMOVE) do - begin - if (Msg.Message <> WM_QUIT) then - begin - TranslateMessage(Msg); - DispatchMessage(Msg); - end else - begin - PostQuitMessage(Msg.WParam); - exit; - end; - end; -{$ENDIF} // 2001.07.23 - Sleep(0); // Yield - - until (False); - FreeBitmap; -end; - -procedure TGIFImage.Draw(ACanvas: TCanvas; const Rect: TRect); -var - Canvas : TCanvas; - DestRect : TRect; -{$IFNDEF VER14_PLUS} // 2001.07.23 - Msg : TMsg; - ThreadWindow : HWND; -{$ENDIF} // 2001.07.23 - - procedure DrawTile(Rect: TRect; Bitmap: TBitmap); - var - Tile : TRect; - begin - if (goTile in FDrawOptions) then - begin - // Note: This design does not handle transparency correctly! - Tile.Left := Rect.Left; - Tile.Right := Tile.Left + Width; - while (Tile.Left < Rect.Right) do - begin - Tile.Top := Rect.Top; - Tile.Bottom := Tile.Top + Height; - while (Tile.Top < Rect.Bottom) do - begin - ACanvas.StretchDraw(Tile, Bitmap); - Tile.Top := Tile.Top + Height; - Tile.Bottom := Tile.Top + Height; - end; - Tile.Left := Tile.Left + Width; - Tile.Right := Tile.Left + Width; - end; - end else - ACanvas.StretchDraw(Rect, Bitmap); - end; - -begin - // Prevent recursion(s(s(s))) - if (IsDrawing) or (FImages.Count = 0) then - exit; - - IsDrawing := True; - try - // Copy bitmap to canvas if we are already drawing - // (or have drawn but are finished) - if (FImages.Count = 1) or // Only one image - (not (goAnimate in FDrawOptions)) then // Don't animate - begin - // 2004.03.09 -> - if (FForceFrame >= 0) and (FForceFrame < FImages.Count) then - FImages[FForceFrame].Draw(ACanvas, Rect, (goTransparent in FDrawOptions), (goTile in FDrawOptions)) - else - // 2004.03.09 <- - FImages[0].Draw(ACanvas, Rect, (goTransparent in FDrawOptions), (goTile in FDrawOptions)); - exit; - end else - if (FBitmap <> nil) and not(goDirectDraw in FDrawOptions) then - begin - DrawTile(Rect, Bitmap); - exit; - end; - - // Use the FPainters threadlist to protect FDrawPainter from being modified - // by the thread while we mess with it - with FPainters.LockList do - try - // If we are already painting on the canvas in goDirectDraw mode - // and at the same location, just exit and let the painter do - // its thing when it's ready - if (FDrawPainter <> nil) and (FDrawPainter.Canvas = ACanvas) and - EqualRect(FDrawPainter.Rect, Rect) then - exit; - - // Kill the current paint thread - StopDraw; - - if not(goDirectDraw in FDrawOptions) then - begin - // Create a bitmap to draw on - NewBitmap; - Canvas := FBitmap.Canvas; - DestRect := Canvas.ClipRect; - // Initialize bitmap canvas with background image - Canvas.CopyRect(DestRect, ACanvas, Rect); - end else - begin - Canvas := ACanvas; - DestRect := Rect; - end; - - // Create new paint thread - InternalPaint(@FDrawPainter, Canvas, DestRect, FDrawOptions); - - if (FDrawPainter <> nil) then - begin - // Launch thread - FDrawPainter.Start; - - if not(goDirectDraw in FDrawOptions) then - begin -{$IFDEF VER14_PLUS} -// 2002.07.07 - while (FDrawPainter <> nil) and (not FDrawPainter.Terminated) and - (not FDrawPainter.Started) do - begin - if not CheckSynchronize then - Sleep(0); // Yield - end; -{$ELSE} -//{$IFNDEF VER14_PLUS} // 2001.07.23 - ThreadWindow := FindWindow('TThreadWindow', nil); - // Wait for thread to render first frame - while (FDrawPainter <> nil) and (not FDrawPainter.Terminated) and - (not FDrawPainter.Started) do - // Process Messages to make Synchronize work - // (Instead of Application.ProcessMessages) - if PeekMessage(Msg, ThreadWindow, CM_DESTROYWINDOW, CM_EXECPROC, PM_REMOVE) then - begin - if (Msg.Message <> WM_QUIT) then - begin - TranslateMessage(Msg); - DispatchMessage(Msg); - end else - begin - PostQuitMessage(Msg.WParam); - exit; - end; - end else - Sleep(0); // Yield -{$ENDIF} // 2001.07.23 - // Draw frame to destination - DrawTile(Rect, Bitmap); - end; - end; - finally - FPainters.UnLockList; - end; - - finally - IsDrawing := False; - end; -end; - -// Internal pain(t) routine used by Draw() -function TGIFImage.InternalPaint(Painter: PGifPainter; ACanvas: TCanvas; - const Rect: TRect; Options: TGIFDrawOptions): TGIFPainter; -begin - if (Empty) or (Rect.Left >= Rect.Right) or (Rect.Top >= Rect.Bottom) then - begin - Result := nil; - if (Painter <> nil) then - Painter^ := Result; - exit; - end; - - // Draw in main thread if only one image - if (Images.Count = 1) then - Options := Options - [goAsync, goAnimate]; - - Result := TGIFPainter.CreateRef(Painter, self, ACanvas, Rect, Options); - FPainters.Add(Result); - Result.OnStartPaint := FOnStartPaint; - Result.OnPaint := FOnPaint; - Result.OnAfterPaint := FOnAfterPaint; - Result.OnLoop := FOnLoop; - Result.OnEndPaint := FOnEndPaint; - - if not(goAsync in Options) then - begin - // Run in main thread - Result.Execute; - // Note: Painter threads executing in the main thread are freed upon exit - // from the Execute method, so no need to do it here. - Result := nil; - if (Painter <> nil) then - Painter^ := Result; - end else - Result.Priority := FThreadPriority; -end; - -function TGIFImage.Paint(ACanvas: TCanvas; const Rect: TRect; - Options: TGIFDrawOptions): TGIFPainter; -begin - Result := InternalPaint(nil, ACanvas, Rect, Options); - if (Result <> nil) then - // Run in separate thread - Result.Start; -end; - -procedure TGIFImage.PaintStart; -var - i : integer; -begin - with FPainters.LockList do - try - for i := 0 to Count-1 do - TGIFPainter(Items[i]).Start; - finally - FPainters.UnLockList; - end; -end; - -procedure TGIFImage.PaintStop; -var - Ghosts : integer; - i : integer; -{$IFNDEF VER14_PLUS} // 2001.07.23 - Msg : TMsg; - ThreadWindow : HWND; -{$ENDIF} // 2001.07.23 - -{$IFNDEF VER14_PLUS} // 2001.07.23 - procedure KillThreads; - var - i : integer; - begin - with FPainters.LockList do - try - for i := Count-1 downto 0 do - if (goAsync in TGIFPainter(Items[i]).DrawOptions) then - begin - TerminateThread(TGIFPainter(Items[i]).Handle, 0); - Delete(i); - end; - finally - FPainters.UnLockList; - end; - end; -{$ENDIF} // 2001.07.23 - -begin - try - // Loop until all have died - repeat - with FPainters.LockList do - try - if (Count = 0) then - exit; - - // Signal painters to terminate - // Painters will attempt to remove them self from the - // painter list when they die - Ghosts := Count; - for i := Ghosts-1 downto 0 do - begin - if not(goAsync in TGIFPainter(Items[i]).DrawOptions) then - dec(Ghosts); - TGIFPainter(Items[i]).Stop; - end; - finally - FPainters.UnLockList; - end; - - // If all painters were synchronous, there's no purpose waiting for them - // to terminate, because they are running in the main thread. - if (Ghosts = 0) then - exit; -{$IFDEF VER14_PLUS} -// 2002.07.07 - if (GetCurrentThreadID = MainThreadID) then - while CheckSynchronize do {loop}; -{$ELSE} - // Process Messages to make TThread.Synchronize work - // (Instead of Application.ProcessMessages) -//{$IFDEF VER14_PLUS} // 2001.07.23 -// Exit; // 2001.07.23 -//{$ELSE} // 2001.07.23 - ThreadWindow := FindWindow('TThreadWindow', nil); - if (ThreadWindow = 0) then - begin - KillThreads; - Exit; - end; - while PeekMessage(Msg, ThreadWindow, CM_DESTROYWINDOW, CM_EXECPROC, PM_REMOVE) do - begin - if (Msg.Message <> WM_QUIT) then - begin - TranslateMessage(Msg); - DispatchMessage(Msg); - end else - begin - KillThreads; - Exit; - end; - end; -{$ENDIF} // 2001.07.23 - Sleep(0); - until (False); - finally - FreeBitmap; - end; -end; - -procedure TGIFImage.PaintPause; -var - i : integer; -begin - with FPainters.LockList do - try - for i := 0 to Count-1 do - TGIFPainter(Items[i]).Suspend; - finally - FPainters.UnLockList; - end; -end; - -procedure TGIFImage.PaintResume; -var - i : integer; -begin - // Implementation is currently same as PaintStart, but don't call PaintStart - // in case its implementation changes - with FPainters.LockList do - try - for i := 0 to Count-1 do - TGIFPainter(Items[i]).Start; - finally - FPainters.UnLockList; - end; -end; - -procedure TGIFImage.PaintRestart; -var - i : integer; -begin - with FPainters.LockList do - try - for i := 0 to Count-1 do - TGIFPainter(Items[i]).Restart; - finally - FPainters.UnLockList; - end; -end; - -procedure TGIFImage.Warning(Sender: TObject; Severity: TGIFSeverity; Message: string); -begin - if (Assigned(FOnWarning)) then - FOnWarning(Sender, Severity, Message); -end; - -{$IFDEF VER12_PLUS} - {$IFNDEF VER14_PLUS} // not anymore need for Delphi 6 and up // 2001.07.23 -type - TDummyThread = class(TThread) - protected - procedure Execute; override; - end; -procedure TDummyThread.Execute; -begin -end; - {$ENDIF} // 2001.07.23 -{$ENDIF} - -var - DesktopDC: HDC; -{$IFDEF VER12_PLUS} - {$IFNDEF VER14_PLUS} // not anymore need for Delphi 6 and up // 2001.07.23 - DummyThread: TThread; - {$ENDIF} // 2001.07.23 -{$ENDIF} - -//////////////////////////////////////////////////////////////////////////////// -// -// Initialization -// -//////////////////////////////////////////////////////////////////////////////// - -initialization -{$IFDEF REGISTER_TGIFIMAGE} - TPicture.RegisterFileFormat('GIF', sGIFImageFile, TGIFImage); -// 2008.10.19 -> -{$IFDEF VER20_PLUS} - CF_GIF := RegisterClipboardFormat(PWideChar(sGIFImageFile)); -{$ELSE} - CF_GIF := RegisterClipboardFormat(PAnsiChar(sGIFImageFile)); -{$ENDIF} -// 2008.10.19 <- - TPicture.RegisterClipboardFormat(CF_GIF, TGIFImage); -{$ENDIF} - DesktopDC := GetDC(0); - try - PaletteDevice := (GetDeviceCaps(DesktopDC, BITSPIXEL) * GetDeviceCaps(DesktopDC, PLANES) <= 8); - DoAutoDither := PaletteDevice; - finally - ReleaseDC(0, DesktopDC); - end; - -{$IFDEF VER9x} - // Note: This doesn't return the same palette as the Delphi 3 system palette - // since the true system palette contains 20 entries and the Delphi 3 system - // palette only contains 16. - // For our purpose this doesn't matter since we do not care about the actual - // colors (or their number) in the palette. - // Stock objects doesn't have to be deleted. - SystemPalette16 := GetStockObject(DEFAULT_PALETTE); -{$ENDIF} -{$IFDEF VER12_PLUS} - // Make sure that at least one thread always exist. - // This is done to circumvent a race condition bug in Delphi 4.x and later: - // When threads are deleted and created in rapid succesion, a situation might - // arise where the thread window is deleted *after* the threads it controls - // has been created. See the Delphi Bug Lists for more information. - {$IFNDEF VER14_PLUS} // not anymore need for Delphi 6 and up // 2001.07.23 - DummyThread := TDummyThread.Create(True); - {$ENDIF} // 2001.07.23 -{$ENDIF} - -//////////////////////////////////////////////////////////////////////////////// -// -// Finalization -// -//////////////////////////////////////////////////////////////////////////////// -finalization - ExtensionList.Free; - AppExtensionList.Free; -{$IFNDEF VER9x} - {$IFDEF REGISTER_TGIFIMAGE} - TPicture.UnregisterGraphicClass(TGIFImage); - {$ENDIF} - {$IFDEF VER100} - if (pf8BitBitmap <> nil) then - pf8BitBitmap.Free; - {$ENDIF} -{$ENDIF} -{$IFDEF VER12_PLUS} - {$IFNDEF VER14_PLUS} // not anymore need for Delphi 6 and up // 2001.07.23 - if (DummyThread <> nil) then -// 2006.10.16 -> -// DummyThread.Free; - begin - DummyThread.Resume; - DummyThread.WaitFor; - DummyThread.Free; - end; -// 2006.10.16 <- - {$ENDIF} // 2001.07.23 -{$ENDIF} -end. - diff --git a/Src/3rdParty/LICENSE b/Src/3rdParty/LICENSE deleted file mode 100644 index b733afad0..000000000 --- a/Src/3rdParty/LICENSE +++ /dev/null @@ -1,16 +0,0 @@ -Files in the Src/3rdParty directory are licensed as follows: - -GIFImage.pas ------------- -Covered by the "TGifImage" license. See Docs/License.html#tgifimage. - -LVEx.pas --------- -This file, and the accompanying resource file (LVEx.res), are freeware copyright -(c) 1999-2009 Vadim Crits. For details of the terms and conditions of use see -Docs/License.html#tlistviewex. - -All Other Files ---------------- -All other files in the directory include licensing information in their -comments. \ No newline at end of file diff --git a/Src/3rdParty/PJMD5.pas b/Src/3rdParty/PJMD5.pas index 17665da65..f9d57b00b 100644 --- a/Src/3rdParty/PJMD5.pas +++ b/Src/3rdParty/PJMD5.pas @@ -1,12 +1,12 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2010-2013, Peter Johnson (www.delphidabbler.com). + * Copyright (C) 2010-2014, Peter Johnson (www.delphidabbler.com). * - * $Rev$ - * $Date$ + * $Rev: 1515 $ + * $Date: 2014-01-11 02:36:28 +0000 (Sat, 11 Jan 2014) $ * * This unit is a Delphi Pascal implementation of the MD5 Message-Digest * algorithm. See RFC1321 (http://www.faqs.org/rfcs/rfc1321.html). @@ -31,12 +31,15 @@ {$UNDEF CANCOMPILE} {$UNDEF RTLNAMESPACES} {$IFDEF CONDITIONALEXPRESSIONS} - {$IF CompilerVersion >= 20.0} // Delphi 2009 - {$DEFINE CANCOMPILE} + {$IF CompilerVersion >= 24.0} // Delphi XE3 and later + {$LEGACYIFEND ON} // NOTE: this must come before all $IFEND directives {$IFEND} - {$IF CompilerVersion >= 23.0} // Delphi XE2 + {$IF CompilerVersion >= 23.0} // Delphi XE2 and later {$DEFINE RTLNAMESPACES} {$IFEND} + {$IF CompilerVersion >= 20.0} // Delphi 2009 and later + {$DEFINE CANCOMPILE} + {$IFEND} {$ENDIF} {$IFNDEF CANCOMPILE} {$MESSAGE FATAL 'Delphi 2009 or later required'} diff --git a/Src/3rdParty/PJShellFolders.pas b/Src/3rdParty/PJShellFolders.pas index ee1a3587b..c333b8021 100644 --- a/Src/3rdParty/PJShellFolders.pas +++ b/Src/3rdParty/PJShellFolders.pas @@ -1,12 +1,12 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2001-2013, Peter Johnson (www.delphidabbler.com). + * Copyright (C) 2001-2014, Peter Johnson (www.delphidabbler.com). * - * $Rev$ - * $Date$ + * $Rev: 1515 $ + * $Date: 2014-01-11 02:36:28 +0000 (Sat, 11 Jan 2014) $ * * Run time unit that defines shell folders components, classes and routines. } @@ -27,17 +27,20 @@ interface {$DEFINE DELPHI5ANDUP} {$ENDIF} {$IFDEF CONDITIONALEXPRESSIONS} - {$IF CompilerVersion >= 14.0} // Delphi 6 and later - {$DEFINE DELPHI5ANDUP} - {$DEFINE DELPHI6ANDUP} + {$IF CompilerVersion >= 24.0} // Delphi XE3 and later + {$LEGACYIFEND ON} // NOTE: this must come before all $IFEND directives + {$IFEND} + {$IF CompilerVersion >= 23.0} // Delphi XE2 and later + {$DEFINE RTLNameSpaces} {$IFEND} {$IF CompilerVersion >= 15.0} // Delphi 7 and later {$DEFINE DELPHI7ANDUP} {$WARN UNSAFE_CODE OFF} {$WARN UNSAFE_CAST OFF} {$IFEND} - {$IF CompilerVersion >= 23.0} // Delphi XE2 - {$DEFINE RTLNameSpaces} + {$IF CompilerVersion >= 14.0} // Delphi 6 and later + {$DEFINE DELPHI5ANDUP} + {$DEFINE DELPHI6ANDUP} {$IFEND} {$ENDIF} diff --git a/Src/3rdParty/PJStreamWrapper.pas b/Src/3rdParty/PJStreamWrapper.pas index 10e91b78a..fbbdf13fe 100644 --- a/Src/3rdParty/PJStreamWrapper.pas +++ b/Src/3rdParty/PJStreamWrapper.pas @@ -1,12 +1,12 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2000-2013, Peter Johnson (www.delphidabbler.com). + * Copyright (C) 2000-2014, Peter Johnson (www.delphidabbler.com). * - * $Rev$ - * $Date$ + * $Rev: 1515 $ + * $Date: 2014-01-11 02:36:28 +0000 (Sat, 11 Jan 2014) $ * * Defines the TPJStreamWrapper class. This is a base class for descendant * classes that "wrap" a TStream class to provide some form of filter or @@ -22,14 +22,17 @@ {$UNDEF SUPPORTS_TSTREAM64} {$UNDEF RTLNAMESPACES} {$IFDEF CONDITIONALEXPRESSIONS} - {$IF CompilerVersion >= 14.0} // >= Delphi 6 - {$DEFINE SUPPORTS_TSTREAM64} + {$IF CompilerVersion >= 24.0} // Delphi XE3 and later + {$LEGACYIFEND ON} // NOTE: this must come before all $IFEND directives + {$IFEND} + {$IF CompilerVersion >= 23.0} // Delphi XE2 and later + {$DEFINE RTLNAMESPACES} {$IFEND} - {$IF CompilerVersion >= 18.0} // >= Delphi 2006 + {$IF CompilerVersion >= 18.0} // Delphi 2006 and later {$DEFINE SUPPORTS_STRICT} {$IFEND} - {$IF CompilerVersion >= 23.0} // >= Delphi XE2 - {$DEFINE RTLNAMESPACES} + {$IF CompilerVersion >= 14.0} // Delphi 6 and later + {$DEFINE SUPPORTS_TSTREAM64} {$IFEND} {$ENDIF} diff --git a/Src/3rdParty/PJSysInfo.pas b/Src/3rdParty/PJSysInfo.pas index 478bb94b5..342fd7750 100644 --- a/Src/3rdParty/PJSysInfo.pas +++ b/Src/3rdParty/PJSysInfo.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2001-2015, Peter Johnson (@delphidabbler). - * - * $Rev: 2002 $ - * $Date: 2015-11-30 14:45:35 +0000 (Mon, 30 Nov 2015) $ + * Copyright (C) 2001-2024, Peter Johnson (https://gravatar.com/delphidabbler). * * This unit contains various static classes, constants, type definitions and * global variables for use in providing information about the host computer and @@ -24,39 +21,16 @@ * 3: When run on operating systems up to and including Windows 8 running the * host program in compatibility mode causes some variables and TPJOSInfo * methods to be "spoofed" into returning information about the emulated - * OS. When run on Windows 8.1 and later details of the actual host - * operating system are always returned and the emulated OS is ignored. - * - * ACKNOWLEDGEMENTS - * - * Thanks to the following who have contributed to this project: - * - * - Guillermo Fazzolari (bug fix in v2.0.1) - * - * - Laurent Pierre (Many PRODUCT_* constants and suggested GetProductInfo API - * code used in v3.0 and later) + * OS. When run on Windows 8.1 details of the actual host operating system + * are always returned and the emulated OS is ignored. * - * - Rich Habedank (bug fix in r228 and testing of bug fixes reported as - * issues #31 (https://code.google.com/p/ddab-lib/issues/detail?id=31) and - * #33 (https://code.google.com/p/ddab-lib/issues/detail?id=33) + * 4: On Windows 10 and later the correct operating system will only be + * reported if the application declares the operating systems it supports + * in its manifest. * - * The project also draws on the work of: - * - * - Achim Kalwa <delphi@achim-kalwa.de> who translated the versionhelpers.h - * header into Pascal. Some of the IsReallyWindowsXXXXOrGreater methods of - * TPJOSInfo and the TestWindowsVersion routine are based closely on his - * work. - * - * - Brendan grant for his ideas presented in the Code Project article at - * http://bit.ly/1mDKTu3 - * - * - Kendall Sullivan for the code on which TPJComputerInfo.IsAdmin is based. - * See http://edn.embarcadero.com/article/26752. - * - * - norgepaul for the code on which TPJComputerInfo.IsUACActive is based. See - * his answer on Stack Overflow at http://tinyurl.com/avlztmg. + * ACKNOWLEDGEMENTS * - * ***** END LICENSE BLOCK ***** + * See Docs/Acknowledgements.md } @@ -85,6 +59,7 @@ {$UNDEF RTLNAMESPACES} // No support for RTL namespaces in unit names {$UNDEF HASUNIT64} // UInt64 type not defined {$UNDEF INLINEMETHODS} // No support for inline methods +{$UNDEF HASTBYTES} // TBytes not defined // Undefine facilities not available in earlier compilers // Note: Delphi 1 to 3 is not included since the code will not compile on these @@ -106,6 +81,9 @@ {$IF CompilerVersion >= 24.0} // Delphi XE3 and later {$LEGACYIFEND ON} // NOTE: this must come before all $IFEND directives {$IFEND} + {$IF CompilerVersion >= 18.5} // Delphi 2007 Win32 and later + {$DEFINE HASTBYTES} + {$IFEND} {$IF CompilerVersion >= 23.0} // Delphi XE2 and later {$DEFINE RTLNAMESPACES} {$IFEND} @@ -141,6 +119,11 @@ interface System.SysUtils, System.Classes, Winapi.Windows; {$ENDIF} +{$IFNDEF HASTBYTES} +// Compiler doesn't have TBytes: define it +type + TBytes = array of Byte; +{$ENDIF} type // Windows types not defined in all supported Delphi VCLs @@ -260,101 +243,195 @@ interface // These Windows-defined constants are required for use with the // GetProductInfo API call used with Windows Vista and later - // ** Thanks to Laurent Pierre for providing these definitions. - // ** Additional definitions were obtained from - // http://msdn.microsoft.com/en-us/library/ms724358 - PRODUCT_BUSINESS = $00000006; - PRODUCT_BUSINESS_N = $00000010; - PRODUCT_CLUSTER_SERVER = $00000012; - PRODUCT_CLUSTER_SERVER_V = $00000040; - PRODUCT_CORE = $00000065; - PRODUCT_CORE_N = $00000062; - PRODUCT_CORE_COUNTRYSPECIFIC = $00000063; - PRODUCT_CORE_SINGLELANGUAGE = $00000064; - PRODUCT_DATACENTER_EVALUATION_SERVER = $00000050; - PRODUCT_DATACENTER_SERVER = $00000008; - PRODUCT_DATACENTER_SERVER_CORE = $0000000C; - PRODUCT_DATACENTER_SERVER_CORE_V = $00000027; - PRODUCT_DATACENTER_SERVER_V = $00000025; - PRODUCT_EDUCATION = $00000079; - PRODUCT_EDUCATION_N = $0000007A; - PRODUCT_ENTERPRISE = $00000004; - PRODUCT_ENTERPRISE_E = $00000046; - PRODUCT_ENTERPRISE_N_EVALUATION = $00000054; - PRODUCT_ENTERPRISE_N = $0000001B; - PRODUCT_ENTERPRISE_EVALUATION = $00000048; - PRODUCT_ENTERPRISE_SERVER = $0000000A; - PRODUCT_ENTERPRISE_SERVER_CORE = $0000000E; - PRODUCT_ENTERPRISE_SERVER_CORE_V = $00000029; - PRODUCT_ENTERPRISE_SERVER_IA64 = $0000000F; - PRODUCT_ENTERPRISE_SERVER_V = $00000026; - PRODUCT_ESSENTIALBUSINESS_SERVER_MGMT = $0000003B; - PRODUCT_ESSENTIALBUSINESS_SERVER_ADDL = $0000003C; - PRODUCT_ESSENTIALBUSINESS_SERVER_MGMTSVC = $0000003D; - PRODUCT_ESSENTIALBUSINESS_SERVER_ADDLSVC = $0000003E; - PRODUCT_HOME_BASIC = $00000002; - PRODUCT_HOME_BASIC_E = $00000043; - PRODUCT_HOME_BASIC_N = $00000005; - PRODUCT_HOME_PREMIUM = $00000003; - PRODUCT_HOME_PREMIUM_E = $00000044; - PRODUCT_HOME_PREMIUM_N = $0000001A; - PRODUCT_HOME_PREMIUM_SERVER = $00000022; - PRODUCT_HOME_SERVER = $00000013; - PRODUCT_HYPERV = $0000002A; - PRODUCT_MEDIUMBUSINESS_SERVER_MANAGEMENT = $0000001E; - PRODUCT_MEDIUMBUSINESS_SERVER_MESSAGING = $00000020; - PRODUCT_MEDIUMBUSINESS_SERVER_SECURITY = $0000001F; - PRODUCT_MOBILE_CORE = $00000068; - PRODUCT_MOBILE_ENTERPRISE = $00000085; - PRODUCT_MULTIPOINT_STANDARD_SERVER = $0000004C; - PRODUCT_MULTIPOINT_PREMIUM_SERVER = $0000004D; - PRODUCT_PROFESSIONAL = $00000030; - PRODUCT_PROFESSIONAL_E = $00000045; - PRODUCT_PROFESSIONAL_N = $00000031; - PRODUCT_PROFESSIONAL_WMC = $00000067; - PRODUCT_SB_SOLUTION_SERVER = $00000032; - PRODUCT_SB_SOLUTION_SERVER_EM = $00000036; - PRODUCT_SERVER_FOR_SB_SOLUTIONS = $00000033; - PRODUCT_SERVER_FOR_SB_SOLUTIONS_EM = $00000037; - PRODUCT_SERVER_FOR_SMALLBUSINESS = $00000018; - PRODUCT_SERVER_FOR_SMALLBUSINESS_V = $00000023; - PRODUCT_SERVER_FOUNDATION = $00000021; - PRODUCT_SMALLBUSINESS_SERVER = $00000009; - PRODUCT_SMALLBUSINESS_SERVER_PREMIUM = $00000019; - PRODUCT_SMALLBUSINESS_SERVER_PREMIUM_CORE = $0000003F; - PRODUCT_SOLUTION_EMBEDDEDSERVER = $00000038; - PRODUCT_STANDARD_EVALUATION_SERVER = $0000004F; - PRODUCT_STANDARD_SERVER = $00000007; - PRODUCT_STANDARD_SERVER_CORE = $0000000D; - PRODUCT_STANDARD_SERVER_V = $00000024; - PRODUCT_STANDARD_SERVER_CORE_V = $00000028; - PRODUCT_STANDARD_SERVER_SOLUTIONS = $00000034; - PRODUCT_STANDARD_SERVER_SOLUTIONS_CORE = $00000035; - PRODUCT_STARTER = $0000000B; - PRODUCT_STARTER_E = $00000042; - PRODUCT_STARTER_N = $0000002F; - PRODUCT_STORAGE_ENTERPRISE_SERVER = $00000017; - PRODUCT_STORAGE_ENTERPRISE_SERVER_CORE = $0000002E; - PRODUCT_STORAGE_EXPRESS_SERVER = $00000014; - PRODUCT_STORAGE_EXPRESS_SERVER_CORE = $0000002B; - PRODUCT_STORAGE_STANDARD_EVALUATION_SERVER = $00000060; - PRODUCT_STORAGE_STANDARD_SERVER = $00000015; - PRODUCT_STORAGE_STANDARD_SERVER_CORE = $0000002C; - PRODUCT_STORAGE_WORKGROUP_EVALUATION_SERVER = $0000005F; - PRODUCT_STORAGE_WORKGROUP_SERVER = $00000016; - PRODUCT_STORAGE_WORKGROUP_SERVER_CORE = $0000002D; - PRODUCT_UNDEFINED = $00000000; - PRODUCT_ULTIMATE = $00000001; - PRODUCT_ULTIMATE_E = $00000047; - PRODUCT_ULTIMATE_N = $0000001C; - PRODUCT_WEB_SERVER = $00000011; - PRODUCT_WEB_SERVER_CORE = $0000001D; - PRODUCT_UNLICENSED = $ABCDABCD; + // NOTE: PRODUCT_xxx constants marked with an asterisk comment have no + // associated description hard wired into this unit. + // ** Thanks to Laurent Pierre for providing these definitions originally. + // ** Subsequent additions were obtained from https://tinyurl.com/3rhhbs2z + // ** and the Windows 11 24H2 SDK + PRODUCT_UNDEFINED = $00000000; + PRODUCT_ULTIMATE = $00000001; + PRODUCT_HOME_BASIC = $00000002; + PRODUCT_HOME_PREMIUM = $00000003; + PRODUCT_ENTERPRISE = $00000004; + PRODUCT_HOME_BASIC_N = $00000005; + PRODUCT_BUSINESS = $00000006; + PRODUCT_STANDARD_SERVER = $00000007; + PRODUCT_DATACENTER_SERVER = $00000008; + PRODUCT_SMALLBUSINESS_SERVER = $00000009; + PRODUCT_ENTERPRISE_SERVER = $0000000A; + PRODUCT_STARTER = $0000000B; + PRODUCT_DATACENTER_SERVER_CORE = $0000000C; + PRODUCT_STANDARD_SERVER_CORE = $0000000D; + PRODUCT_ENTERPRISE_SERVER_CORE = $0000000E; + PRODUCT_ENTERPRISE_SERVER_IA64 = $0000000F; + PRODUCT_BUSINESS_N = $00000010; + PRODUCT_WEB_SERVER = $00000011; + PRODUCT_CLUSTER_SERVER = $00000012; + PRODUCT_HOME_SERVER = $00000013; + PRODUCT_STORAGE_EXPRESS_SERVER = $00000014; + PRODUCT_STORAGE_STANDARD_SERVER = $00000015; + PRODUCT_STORAGE_WORKGROUP_SERVER = $00000016; + PRODUCT_STORAGE_ENTERPRISE_SERVER = $00000017; + PRODUCT_SERVER_FOR_SMALLBUSINESS = $00000018; + PRODUCT_SMALLBUSINESS_SERVER_PREMIUM = $00000019; + PRODUCT_HOME_PREMIUM_N = $0000001A; + PRODUCT_ENTERPRISE_N = $0000001B; + PRODUCT_ULTIMATE_N = $0000001C; + PRODUCT_WEB_SERVER_CORE = $0000001D; + PRODUCT_MEDIUMBUSINESS_SERVER_MANAGEMENT = $0000001E; + PRODUCT_MEDIUMBUSINESS_SERVER_SECURITY = $0000001F; + PRODUCT_MEDIUMBUSINESS_SERVER_MESSAGING = $00000020; + PRODUCT_SERVER_FOUNDATION = $00000021; + PRODUCT_HOME_PREMIUM_SERVER = $00000022; + PRODUCT_SERVER_FOR_SMALLBUSINESS_V = $00000023; + PRODUCT_STANDARD_SERVER_V = $00000024; + PRODUCT_DATACENTER_SERVER_V = $00000025; + PRODUCT_ENTERPRISE_SERVER_V = $00000026; + PRODUCT_DATACENTER_SERVER_CORE_V = $00000027; + PRODUCT_STANDARD_SERVER_CORE_V = $00000028; + PRODUCT_ENTERPRISE_SERVER_CORE_V = $00000029; + PRODUCT_HYPERV = $0000002A; + PRODUCT_STORAGE_EXPRESS_SERVER_CORE = $0000002B; + PRODUCT_STORAGE_STANDARD_SERVER_CORE = $0000002C; + PRODUCT_STORAGE_WORKGROUP_SERVER_CORE = $0000002D; + PRODUCT_STORAGE_ENTERPRISE_SERVER_CORE = $0000002E; + PRODUCT_STARTER_N = $0000002F; + PRODUCT_PROFESSIONAL = $00000030; + PRODUCT_PROFESSIONAL_N = $00000031; + PRODUCT_SB_SOLUTION_SERVER = $00000032; + PRODUCT_SERVER_FOR_SB_SOLUTIONS = $00000033; + PRODUCT_STANDARD_SERVER_SOLUTIONS = $00000034; + PRODUCT_STANDARD_SERVER_SOLUTIONS_CORE = $00000035; + PRODUCT_SB_SOLUTION_SERVER_EM = $00000036; + PRODUCT_SERVER_FOR_SB_SOLUTIONS_EM = $00000037; + PRODUCT_SOLUTION_EMBEDDEDSERVER = $00000038; + PRODUCT_SOLUTION_EMBEDDEDSERVER_CORE = $00000039; // * + PRODUCT_PROFESSIONAL_EMBEDDED = $0000003A; // * + PRODUCT_ESSENTIALBUSINESS_SERVER_MGMT = $0000003B; + PRODUCT_ESSENTIALBUSINESS_SERVER_ADDL = $0000003C; + PRODUCT_ESSENTIALBUSINESS_SERVER_MGMTSVC = $0000003D; + PRODUCT_ESSENTIALBUSINESS_SERVER_ADDLSVC = $0000003E; + PRODUCT_SMALLBUSINESS_SERVER_PREMIUM_CORE = $0000003F; + PRODUCT_CLUSTER_SERVER_V = $00000040; + PRODUCT_EMBEDDED = $00000041; // * + PRODUCT_STARTER_E = $00000042; + PRODUCT_HOME_BASIC_E = $00000043; + PRODUCT_HOME_PREMIUM_E = $00000044; + PRODUCT_PROFESSIONAL_E = $00000045; + PRODUCT_ENTERPRISE_E = $00000046; + PRODUCT_ULTIMATE_E = $00000047; + PRODUCT_ENTERPRISE_EVALUATION = $00000048; + PRODUCT_MULTIPOINT_STANDARD_SERVER = $0000004C; + PRODUCT_MULTIPOINT_PREMIUM_SERVER = $0000004D; + PRODUCT_STANDARD_EVALUATION_SERVER = $0000004F; + PRODUCT_DATACENTER_EVALUATION_SERVER = $00000050; + PRODUCT_ENTERPRISE_N_EVALUATION = $00000054; + PRODUCT_EMBEDDED_AUTOMOTIVE = $00000055; // * + PRODUCT_EMBEDDED_INDUSTRY_A = $00000056; // * + PRODUCT_THINPC = $00000057; // * + PRODUCT_EMBEDDED_A = $00000058; // * + PRODUCT_EMBEDDED_INDUSTRY = $00000059; // * + PRODUCT_EMBEDDED_E = $0000005A; // * + PRODUCT_EMBEDDED_INDUSTRY_E = $0000005B; // * + PRODUCT_EMBEDDED_INDUSTRY_A_E = $0000005C; // * + PRODUCT_STORAGE_WORKGROUP_EVALUATION_SERVER = $0000005F; + PRODUCT_STORAGE_STANDARD_EVALUATION_SERVER = $00000060; + PRODUCT_CORE_ARM = $00000061; + PRODUCT_CORE_N = $00000062; + PRODUCT_CORE_COUNTRYSPECIFIC = $00000063; + PRODUCT_CORE_SINGLELANGUAGE = $00000064; + PRODUCT_CORE = $00000065; + PRODUCT_PROFESSIONAL_WMC = $00000067; + PRODUCT_MOBILE_CORE = $00000068; + PRODUCT_EMBEDDED_INDUSTRY_EVAL = $00000069; // * + PRODUCT_EMBEDDED_INDUSTRY_E_EVAL = $0000006A; // * + PRODUCT_EMBEDDED_EVAL = $0000006B; // * + PRODUCT_EMBEDDED_E_EVAL = $0000006C; // * + PRODUCT_NANO_SERVER = $0000006D; // * + PRODUCT_CLOUD_STORAGE_SERVER = $0000006E; // * + PRODUCT_CORE_CONNECTED = $0000006F; // * + PRODUCT_PROFESSIONAL_STUDENT = $00000070; // * + PRODUCT_CORE_CONNECTED_N = $00000071; // * + PRODUCT_PROFESSIONAL_STUDENT_N = $00000072; // * + PRODUCT_CORE_CONNECTED_SINGLELANGUAGE = $00000073; // * + PRODUCT_CORE_CONNECTED_COUNTRYSPECIFIC = $00000074; // * + PRODUCT_CONNECTED_CAR = $00000075; // * + PRODUCT_INDUSTRY_HANDHELD = $00000076; // * + PRODUCT_PPI_PRO = $00000077; // * + PRODUCT_ARM64_SERVER = $00000078; // * + PRODUCT_EDUCATION = $00000079; + PRODUCT_EDUCATION_N = $0000007A; + PRODUCT_IOTUAP = $0000007B; + PRODUCT_CLOUD_HOST_INFRASTRUCTURE_SERVER = $0000007C; // * + PRODUCT_ENTERPRISE_S = $0000007D; + PRODUCT_ENTERPRISE_S_N = $0000007E; + PRODUCT_PROFESSIONAL_S = $0000007F; // * + PRODUCT_PROFESSIONAL_S_N = $00000080; // * + PRODUCT_ENTERPRISE_S_EVALUATION = $00000081; + PRODUCT_ENTERPRISE_S_N_EVALUATION = $00000082; + PRODUCT_IOTUAPCOMMERCIAL = $00000083; + PRODUCT_MOBILE_ENTERPRISE = $00000085; + PRODUCT_HOLOGRAPHIC = $00000087; // * + PRODUCT_HOLOGRAPHIC_BUSINESS = $00000088; // * + PRODUCT_PRO_SINGLE_LANGUAGE = $0000008A; // * + PRODUCT_PRO_CHINA = $0000008B; // * + PRODUCT_ENTERPRISE_SUBSCRIPTION = $0000008C; // * + PRODUCT_ENTERPRISE_SUBSCRIPTION_N = $0000008D; // * + PRODUCT_DATACENTER_NANO_SERVER = $0000008F; + PRODUCT_STANDARD_NANO_SERVER = $00000090; + PRODUCT_DATACENTER_A_SERVER_CORE = $00000091; + PRODUCT_STANDARD_A_SERVER_CORE = $00000092; + PRODUCT_DATACENTER_WS_SERVER_CORE = $00000093; + PRODUCT_STANDARD_WS_SERVER_CORE = $00000094; + PRODUCT_UTILITY_VM = $00000095; // * + PRODUCT_DATACENTER_EVALUATION_SERVER_CORE = $0000009F; // * + PRODUCT_STANDARD_EVALUATION_SERVER_CORE = $000000A0; // * + PRODUCT_PRO_WORKSTATION = $000000A1; + PRODUCT_PRO_WORKSTATION_N = $000000A2; + PRODUCT_PRO_FOR_EDUCATION = $000000A4; + PRODUCT_PRO_FOR_EDUCATION_N = $000000A5; // * + PRODUCT_AZURE_SERVER_CORE = $000000A8; // * + PRODUCT_AZURE_NANO_SERVER = $000000A9; // * + PRODUCT_ENTERPRISEG = $000000AB; // * + PRODUCT_ENTERPRISEGN = $000000AC; // * + PRODUCT_SERVERRDSH = $000000AF; + PRODUCT_CLOUD = $000000B2; // * + PRODUCT_CLOUDN = $000000B3; // * + PRODUCT_HUBOS = $000000B4; // * + PRODUCT_ONECOREUPDATEOS = $000000B6; // * + PRODUCT_CLOUDE = $000000B7; // * + PRODUCT_IOTOS = $000000B9; // * + PRODUCT_CLOUDEN = $000000BA; // * + PRODUCT_IOTEDGEOS = $000000BB; // * + PRODUCT_IOTENTERPRISE = $000000BC; + PRODUCT_LITE = $000000BD; // * + PRODUCT_IOTENTERPRISE_S = $000000BF; + PRODUCT_XBOX_SYSTEMOS = $000000C0; // * + PRODUCT_XBOX_GAMEOS = $000000C2; // * + PRODUCT_XBOX_ERAOS = $000000C3; // * + PRODUCT_XBOX_DURANGOHOSTOS = $000000C4; // * + PRODUCT_XBOX_SCARLETTHOSTOS = $000000C5; // * + PRODUCT_XBOX_KEYSTONE = $000000C6; // * + PRODUCT_AZURE_SERVER_CLOUDHOST = $000000C7; // * + PRODUCT_AZURE_SERVER_CLOUDMOS = $000000C8; // * + PRODUCT_CLOUDEDITIONN = $000000CA; // * + PRODUCT_CLOUDEDITION = $000000CB; // * + PRODUCT_VALIDATION = $000000CC; // * + PRODUCT_IOTENTERPRISESK = $000000CD; // * + PRODUCT_IOTENTERPRISEK = $000000CE; // * + PRODUCT_IOTENTERPRISESEVAL = $000000CF; // * + PRODUCT_AZURE_SERVER_AGENTBRIDGE = $000000D0; // * + PRODUCT_AZURE_SERVER_NANOHOST = $000000D1; // * + PRODUCT_WNC = $000000D2; // * + PRODUCT_AZURESTACKHCI_SERVER_CORE = $00000196; // * + PRODUCT_DATACENTER_SERVER_AZURE_EDITION = $00000197; + PRODUCT_DATACENTER_SERVER_CORE_AZURE_EDITION = $00000198; // * + PRODUCT_UNLICENSED = $ABCDABCD; // These constants are required for use with GetSystemMetrics to detect // certain editions. GetSystemMetrics returns non-zero when passed these flags // if the associated edition is present. - // Obtained from http://msdn.microsoft.com/en-us/library/ms724385 + // Obtained from https://msdn.microsoft.com/en-us/library/ms724385 SM_TABLETPC = 86; // Detects XP Tablet Edition SM_MEDIACENTER = 87; // Detects XP Media Center Edition SM_STARTER = 88; // Detects XP Starter Edition @@ -363,19 +440,20 @@ interface // These constants are required when examining the // TSystemInfo.wProcessorArchitecture member. - // Only constants marked * are defined in the MS 2008 SDK - PROCESSOR_ARCHITECTURE_UNKNOWN = $FFFF; // Unknown architecture. + // Only constants marked ** are defined in MS docs at 2022-12-31 + PROCESSOR_ARCHITECTURE_UNKNOWN = $FFFF; // Unknown architecture * PROCESSOR_ARCHITECTURE_INTEL = 0; // x86 * PROCESSOR_ARCHITECTURE_MIPS = 1; // MIPS architecture PROCESSOR_ARCHITECTURE_ALPHA = 2; // Alpha architecture PROCESSOR_ARCHITECTURE_PPC = 3; // PPC architecture PROCESSOR_ARCHITECTURE_SHX = 4; // SHX architecture - PROCESSOR_ARCHITECTURE_ARM = 5; // ARM architecture - PROCESSOR_ARCHITECTURE_IA64 = 6; // Intel Itanium Processor Family * + PROCESSOR_ARCHITECTURE_ARM = 5; // ARM architecture * + PROCESSOR_ARCHITECTURE_IA64 = 6; // Intel Itanium based * PROCESSOR_ARCHITECTURE_ALPHA64 = 7; // Alpha64 architecture PROCESSOR_ARCHITECTURE_MSIL = 8; // MSIL architecture PROCESSOR_ARCHITECTURE_AMD64 = 9; // x64 (AMD or Intel) * PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 = 10; // IA32 on Win64 architecture + PROCESSOR_ARCHITECTURE_ARM64 = 12; // ARM64 architecture * // These constants are provided in case the obsolete // TSystemInfo.dwProcessorType needs to be used. @@ -443,8 +521,11 @@ interface osWin8Point1, // Windows 8.1 osWinSvr2012R2, // Windows Server 2012 R2 osWin10, // Windows 10 - // TODO: Update following comment to correct name once released - osWin10Svr // Windows 10 Server Technical Preview + osWin10Svr, // Windows 2016 Server + osWinSvr2019, // Windows 2019 Server + osWin11, // Windows 11 + osWinSvr2022, // Windows 2022 Server + osWinServer // Windows Server (between Server 2019 & 2022) ); type @@ -465,6 +546,17 @@ interface bmSafeModeNetwork // Booted in safe node with networking ); +type + // Various Windows 10 & 11 release versions + TPJWin10PlusVersion = ( + win10plusNA, + win10plusUnknown, + win10v1507, win10v1511, win10v1607, win10v1703, win10v1709, win10v1803, + win10v1809, win10v1903, win10v1909, win10v2004, win10v20H2, win10v21H1, + win10v21H2, win10v22H2, + win11v21H2, win11v22H2, win11v23H2, win11v24H2 + ); + type /// <summary>Class of exception raised by code in this unit.</summary> EPJSysInfo = class(Exception); @@ -484,10 +576,13 @@ TPJOSInfo = class(TObject) /// <returns>True if suite is installed, False if not installed or not an /// NT platform OS.</returns> class function CheckSuite(const Suite: Integer): Boolean; + {$IFDEF INLINEMETHODS}inline;{$ENDIF} - /// <summary>Gets product edition from registry.</summary> - /// <remarks>Needed to get edition for NT4 pre SP6.</remarks> - class function EditionFromReg: string; + /// <summary>Gets product edition from registry for NT4 pre SP6.</remarks> + class function NTEditionFromReg: string; + + /// <summary>Gets edition ID from registry.</summary> + class function EditionIDFromReg: string; /// <summary>Checks registry to see if NT4 Service Pack 6a is installed. /// </summary> @@ -509,6 +604,18 @@ TPJOSInfo = class(TObject) class function IsReallyWindowsVersionOrGreater(MajorVersion, MinorVersion, ServicePackMajor: Word): Boolean; + /// <summary>Checks if the operating system is Windows 10 or later, with a + /// version identifier the same or later than the given version identifier. + /// </summary> + /// <remarks> + /// <para>WARNING: Windows 11 versions are always considered to be later + /// Windows 10 versions, even if the Windows 10 version was released after + /// the Windows 11 version.</para> + /// <para><c>AVersion</c> must not be one of <c>win10plusNA</c> or + /// <c>win10plusUnknown</c>.</para> + class function IsWindows10PlusVersionOrLater( + const AVersion: TPJWin10PlusVersion): Boolean; + public /// <summary>Checks if the OS can be "spoofed" by specifying a @@ -522,14 +629,17 @@ TPJOSInfo = class(TObject) /// <summary>Checks if the OS is on the Windows 9x platform.</summary> class function IsWin9x: Boolean; + {$IFDEF INLINEMETHODS}inline;{$ENDIF} /// <summary>Checks if the OS is on the Windows NT platform.</summary> class function IsWinNT: Boolean; + {$IFDEF INLINEMETHODS}inline;{$ENDIF} /// <summary>Checks if the program is hosted on Win32s.</summary> /// <remarks>This is unlikely to ever return True since Delphi does not run /// on Win32s.</remarks> class function IsWin32s: Boolean; + {$IFDEF INLINEMETHODS}inline;{$ENDIF} /// <summary>Checks if a 32 bit program is running under WOW64 on a 64 bit /// operating system.</summary> @@ -542,17 +652,21 @@ TPJOSInfo = class(TObject) /// <summary>Checks if Windows Media Center is installed.</summary> class function IsMediaCenter: Boolean; + {$IFDEF INLINEMETHODS}inline;{$ENDIF} /// <summary>Checks if the program is running on a tablet PC OS.</summary> class function IsTabletPC: Boolean; + {$IFDEF INLINEMETHODS}inline;{$ENDIF} /// <summary>Checks if the program is running under Windows Terminal Server /// as a client session.</summary> class function IsRemoteSession: Boolean; + {$IFDEF INLINEMETHODS}inline;{$ENDIF} /// <summary>Checks of the host operating system has pen extensions /// installed.</summary> class function HasPenExtensions: Boolean; + {$IFDEF INLINEMETHODS}inline;{$ENDIF} /// <summary>Returns the host OS platform identifier.</summary> class function Platform: TPJOSPlatform; @@ -582,7 +696,7 @@ TPJOSInfo = class(TObject) /// <remarks> /// <para>Windows has added significant OS updates that bump the build /// number but do not declare themselves as service packs: e.g. the Windows - /// 10 TH2 update.</para> + /// 10 TH2 update, aka Version 1511.</para> /// <para>This method is used to report such updates in addition to /// updates that declare themselves as service packs, while the ServicePack /// method only reports declared 'official' service packs.</para> @@ -594,11 +708,13 @@ TPJOSInfo = class(TObject) /// <remarks>0 is returned in no service pack is installed, if the host OS /// is not on the NT platform.</remarks> class function ServicePackMajor: Integer; + {$IFDEF INLINEMETHODS}inline;{$ENDIF} /// <summary>Returns the minor version number of any NT platform service /// pack.</summary> - /// <remarks>Invalid is ServicePackMinor returns 0.</remarks> + /// <remarks>Invalid if ServicePackMajor returns 0.</remarks> class function ServicePackMinor: Integer; + {$IFDEF INLINEMETHODS}inline;{$ENDIF} /// <summary>Returns the product edition for an NT platform OS.</summary> /// <remarks>The empty string is returned if the OS is not on the NT @@ -611,6 +727,9 @@ TPJOSInfo = class(TObject) /// <summary>Returns the Windows product ID of the host OS.</summary> class function ProductID: string; + /// <summary>Returns the digital product ID of the host OS.</summary> + class function DigitalProductID: TBytes; + /// <summary>Organisation to which Windows is registered, if any.</summary> class function RegisteredOrganisation: string; @@ -746,6 +865,46 @@ TPJOSInfo = class(TObject) class function IsReallyWindows10OrGreater: Boolean; {$IFDEF INLINEMETHODS}inline;{$ENDIF} + /// <summary>Returns an identifier representing a Windows 10 or 11 + /// version.</summary> + /// <remarks>If the OS is earlier than Windows 10 then <c>win10plusNA</c> + /// is returned. If the OS is Windows 10 or later but is a dev, beta etc. + /// build whose version can't be detected then <c>win10plusUnknown</c> is + /// returned.</remarks> + class function Windows10PlusVersion: TPJWin10PlusVersion; + + /// <summary>Returns the version name of a the current operating system, if + /// it is Windows 10 or later.</summary> + /// <remarks> + /// <para>NOTE: some Windows 10 and 11 versions have the same string. + /// </para> + /// <para>If the OS is earlier than Windows 10 then an empty string is + /// returned. If the OS is Windows 10 or later but is a dev, beta etc. + /// build whose version can't be detected then 'Unknown' is returned. + /// </para> + /// </remarks> + class function Windows10PlusVersionName: string; + + /// <summary>Checks if the operating system is Windows 10 or later, with a + /// version identifier the same or later than <c>AVersion</c>. + /// </summary> + /// <remarks><c>AVersion</c> must be a valid Windows 10 version + /// identifier, with a name that begins with <c>win10v</c>.</remarks> + /// <exception><c>EPJSysInfo</c> raised if <c>AVersion</c> is not a valid + /// Windows 10 version identifier.</exception> + class function IsWindows10VersionOrLater( + const AVersion: TPJWin10PlusVersion): Boolean; + + /// <summary>Checks if the operating system is Windows 11 or later, with a + /// version identifier the same or later than <c>AVersion</c>. + /// </summary> + /// <remarks><c>AVersion</c> must be a valid Windows 11 version + /// identifier, with a name that begins with <c>win11v</c>.</remarks> + /// <exception><c>EPJSysInfo</c> raised if <c>AVersion</c> is not a valid + /// Windows 11 version identifier.</exception> + class function IsWindows11VersionOrLater( + const AVersion: TPJWin10PlusVersion): Boolean; + /// <summary>Checks if the OS is a server version.</summary> /// <remarks> /// <para>For Windows 2000 and later the result always relates to the @@ -755,6 +914,21 @@ TPJOSInfo = class(TObject) /// <para>WARNING: For Windows 10 this method is likely to succeed only if /// the application is correctly manifested.</para> class function IsWindowsServer: Boolean; + + /// <summary>Returns any revision number for the OS.</summary> + /// <remarks> + /// <para>If the OS does not provide any revision information then zero is + /// returned.</para> + /// <para>This value is read fromt he registry therefore it is possible + /// that this value could be spoofed.</para> + /// </remarks> + class function RevisionNumber: Integer; + + /// <summary>Returns the repository branch from which the OS release was] + /// built.</summary> + /// <remarks>Returns the empty string if no build branch information is + /// available.</remarks> + class function BuildBranch: string; end; type @@ -792,11 +966,22 @@ TPJComputerInfo = class(TObject) /// processor.</remarks> class function ProcessorName: string; + /// <summary>Returns the speed of the computer's processor in MHz. + /// </summary> + /// <remarks> + /// <para>On multi-processor systems this is the speed of the 1st + /// processor.</para> + /// <para>0 is returned if the information is not a available.</para> + /// </remarks> + class function ProcessorSpeedMHz: Cardinal; + /// <summary>Checks if the host computer has a 64 bit processor.</summary> class function Is64Bit: Boolean; + {$IFDEF INLINEMETHODS}inline;{$ENDIF} /// <summary>Checks if a network is present on host computer.</summary> class function IsNetworkPresent: Boolean; + {$IFDEF INLINEMETHODS}inline;{$ENDIF} /// <summary>Returns the OS mode used when host computer was last booted. /// </summary> @@ -809,7 +994,7 @@ TPJComputerInfo = class(TObject) /// <para>WARNING: True is also returned when running in Windows 9x /// compatibility mode on a Windows NT platform system, regardless of /// whether the user has admin privileges or not.</para> - /// <para>Based on code at http://edn.embarcadero.com/article/26752</para> + /// <para>Based on a former Embarcadero article.</para> /// </remarks> class function IsAdmin: Boolean; @@ -821,7 +1006,7 @@ TPJComputerInfo = class(TObject) /// earlier compatibility mode on Windows Vista or later, regardless of /// whether UAC is enabled or not.</para> /// <para>Based on code on Stack Overflow, answer by norgepaul, at - /// http://tinyurl.com/avlztmg</para> + /// https://tinyurl.com/avlztmg</para> /// </remarks> class function IsUACActive: Boolean; @@ -926,6 +1111,8 @@ TPJSystemFolders = class(TObject) // Description of any OS service pack. Win32CSDVersionEx: string = ''; + // OS Revision number. Zero if revision number not available. + Win32RevisionNumber: Integer = 0; // Flag that indicates if extended version information is available. Win32HaveExInfo: Boolean = False; // Major version number of the latest Service Pack installed on the system. If @@ -968,6 +1155,8 @@ implementation sUnknownPlatform = 'Unrecognised operating system platform'; sUnknownProduct = 'Unrecognised operating system product'; sBadRegType = 'Unsupported registry type'; + sBadRegIntType = 'Integer value expected in registry'; + sBadRegBinType = 'Binary value expected in registry'; sBadProcHandle = 'Bad process handle'; @@ -978,13 +1167,14 @@ implementation UInt64 = Int64; {$ENDIF} - const // Map of product codes per GetProductInfo API to product names + // Names are not available for all PRODUCT_xxx values. // ** Laurent Pierre supplied original code on which this map is based // It has been modified and extended using MSDN documentation at - // http://msdn.microsoft.com/en-us/library/ms724358 - cProductMap: array[1..87] of record + // https://msdn.microsoft.com/en-us/library/ms724358 and + // https://tinyurl.com/5684558v (learn.microsoft.com) + cProductMap: array[1..107] of record Id: Cardinal; // product ID Name: string; // product name end = ( @@ -997,23 +1187,19 @@ implementation (Id: PRODUCT_CLUSTER_SERVER_V; Name: 'Server Hyper Core V';), (Id: PRODUCT_CORE; - Name: 'Core / Home';), - (Id: PRODUCT_CORE_N; - Name: 'Core N or Home N';), + Name: 'Home (Core)';), (Id: PRODUCT_CORE_COUNTRYSPECIFIC; - Name: 'Core / Home China';), + Name: 'Home (Core) China';), + (Id: PRODUCT_CORE_N; + Name: 'Home (Core) N';), (Id: PRODUCT_CORE_SINGLELANGUAGE; - Name: 'Core / Home Single Language';), - (Id: PRODUCT_MOBILE_CORE; - Name: 'Mobile'), - (Id: PRODUCT_MOBILE_ENTERPRISE; - Name: 'Mobile Enterprise'), - (Id: PRODUCT_EDUCATION; - Name: 'Education'), - (Id: PRODUCT_EDUCATION_N; - Name: 'Education N'), + Name: 'Home (Core) Single Language';), (Id: PRODUCT_DATACENTER_EVALUATION_SERVER; Name: 'Server Datacenter (evaluation installation)';), + (Id: PRODUCT_DATACENTER_A_SERVER_CORE; + Name: 'Server Datacenter, Semi-Annual Channel (core installation)';), + (Id: PRODUCT_STANDARD_A_SERVER_CORE; + Name: 'Server Standard, Semi-Annual Channel (core installation)';), (Id: PRODUCT_DATACENTER_SERVER; Name: 'Server Datacenter (full installation)';), (Id: PRODUCT_DATACENTER_SERVER_CORE; @@ -1022,16 +1208,28 @@ implementation Name: 'Server Datacenter without Hyper-V (core installation)';), (Id: PRODUCT_DATACENTER_SERVER_V; Name: 'Server Datacenter without Hyper-V (full installation)';), + (Id: PRODUCT_EDUCATION; + Name: 'Education'), + (Id: PRODUCT_EDUCATION_N; + Name: 'Education N'), (Id: PRODUCT_ENTERPRISE; Name: 'Enterprise';), (Id: PRODUCT_ENTERPRISE_E; Name: 'Enterprise E';), - (Id: PRODUCT_ENTERPRISE_N_EVALUATION; - Name: 'Enterprise N (evaluation installation)';), - (Id: PRODUCT_ENTERPRISE_N; - Name: 'Enterprise N';), (Id: PRODUCT_ENTERPRISE_EVALUATION; Name: 'Server Enterprise (evaluation installation)';), + (Id: PRODUCT_ENTERPRISE_N; + Name: 'Enterprise N';), + (Id: PRODUCT_ENTERPRISE_N_EVALUATION; + Name: 'Enterprise N (evaluation installation)';), + (Id: PRODUCT_ENTERPRISE_S; + Name: 'Enterprise 2015 LTSB';), + (Id: PRODUCT_ENTERPRISE_S_EVALUATION; + Name: 'Enterprise 2015 LTSB Evaluation';), + (Id: PRODUCT_ENTERPRISE_S_N; + Name: 'Enterprise 2015 LTSB N';), + (Id: PRODUCT_ENTERPRISE_S_N_EVALUATION; + Name: 'Enterprise 2015 LTSB N Evaluation';), (Id: PRODUCT_ENTERPRISE_SERVER; Name: 'Server Enterprise (full installation)';), (Id: PRODUCT_ENTERPRISE_SERVER_CORE; @@ -1042,14 +1240,14 @@ implementation Name: 'Server Enterprise for Itanium-based Systems';), (Id: PRODUCT_ENTERPRISE_SERVER_V; Name: 'Server Enterprise without Hyper-V (full installation)';), - (Id: PRODUCT_ESSENTIALBUSINESS_SERVER_MGMT; - Name: 'Windows Essential Server Solution Management'), (Id: PRODUCT_ESSENTIALBUSINESS_SERVER_ADDL; Name: 'Windows Essential Server Solution Additional'), - (Id: PRODUCT_ESSENTIALBUSINESS_SERVER_MGMTSVC; - Name: 'Windows Essential Server Solution Management SVC'), (Id: PRODUCT_ESSENTIALBUSINESS_SERVER_ADDLSVC; Name: 'Windows Essential Server Solution Additional SVC'), + (Id: PRODUCT_ESSENTIALBUSINESS_SERVER_MGMT; + Name: 'Windows Essential Server Solution Management'), + (Id: PRODUCT_ESSENTIALBUSINESS_SERVER_MGMTSVC; + Name: 'Windows Essential Server Solution Management SVC'), (Id: PRODUCT_HOME_BASIC; Name: 'Home Basic';), (Id: PRODUCT_HOME_BASIC_E; @@ -1067,25 +1265,43 @@ implementation (Id: PRODUCT_HOME_SERVER; Name: 'Home Storage Server';), (Id: PRODUCT_HYPERV; - Name: 'Hyper-V Server'), + Name: 'Hyper-V Server';), + (Id: PRODUCT_IOTENTERPRISE; + Name: 'IoT Enterprise';), + (Id: PRODUCT_IOTENTERPRISE_S; + Name: 'IoT Enterprise LTSC'), + (Id: PRODUCT_IOTUAP; + Name: 'IoT Core';), + (Id: PRODUCT_IOTUAPCOMMERCIAL; + Name: 'IoT Core Commercial';), (Id: PRODUCT_MEDIUMBUSINESS_SERVER_MANAGEMENT; Name: 'Essential Business Server Management Server';), (Id: PRODUCT_MEDIUMBUSINESS_SERVER_MESSAGING; Name: 'Essential Business Server Messaging Server';), (Id: PRODUCT_MEDIUMBUSINESS_SERVER_SECURITY; Name: 'Essential Business Server Security Server';), - (Id: PRODUCT_MULTIPOINT_STANDARD_SERVER; - Name: 'MultiPoint Server Standard (full installation)';), + (Id: PRODUCT_MOBILE_CORE; + Name: 'Mobile'), + (Id: PRODUCT_MOBILE_ENTERPRISE; + Name: 'Mobile Enterprise'), (Id: PRODUCT_MULTIPOINT_PREMIUM_SERVER; Name: 'MultiPoint Server Premium (full installation)';), + (Id: PRODUCT_MULTIPOINT_STANDARD_SERVER; + Name: 'MultiPoint Server Standard (full installation)';), + (Id: PRODUCT_PRO_WORKSTATION; + Name: 'Pro for Workstations';), + (Id: PRODUCT_PRO_WORKSTATION_N; + Name: 'Pro for Workstations N';), (Id: PRODUCT_PROFESSIONAL; - Name: 'Professional';), + Name: 'Pro (Professional)';), (Id: PRODUCT_PROFESSIONAL_E; Name: 'Professional E';), (Id: PRODUCT_PROFESSIONAL_N; - Name: 'Professional N';), + Name: 'Pro (Professional) N';), (Id: PRODUCT_PROFESSIONAL_WMC; Name: 'Professional with Media Center';), + (Id: PRODUCT_SB_SOLUTION_SERVER; + Name: 'Small Business Server Essentials';), (Id: PRODUCT_SB_SOLUTION_SERVER_EM; Name: 'Server For SB Solutions EM';), (Id: PRODUCT_SERVER_FOR_SB_SOLUTIONS; @@ -1095,11 +1311,9 @@ implementation (Id: PRODUCT_SERVER_FOR_SMALLBUSINESS; Name: 'Server for Essential Server Solutions';), (Id: PRODUCT_SERVER_FOR_SMALLBUSINESS_V; - Name: 'Server 2008 without Hyper-V for Essential Server Solutions';), + Name: 'Server without Hyper-V for Essential Server Solutions';), (Id: PRODUCT_SERVER_FOUNDATION; Name: 'Server Foundation';), - (Id: PRODUCT_SB_SOLUTION_SERVER; - Name: 'Small Business Server Essentials';), (Id: PRODUCT_SMALLBUSINESS_SERVER; Name: 'Small Business Server';), (Id: PRODUCT_SMALLBUSINESS_SERVER_PREMIUM; @@ -1117,7 +1331,7 @@ implementation (Id: PRODUCT_STANDARD_SERVER_CORE_V; Name: 'Server Standard without Hyper-V (core installation)';), (Id: PRODUCT_STANDARD_SERVER_V; - Name: 'Server Standard without Hyper-V (full installation)';), + Name: 'Server Standard without Hyper-V';), (Id: PRODUCT_STANDARD_SERVER_SOLUTIONS; Name: 'Server Solutions Premium';), (Id: PRODUCT_STANDARD_SERVER_SOLUTIONS_CORE; @@ -1148,18 +1362,34 @@ implementation Name: 'Storage Server Workgroup';), (Id: PRODUCT_STORAGE_WORKGROUP_SERVER_CORE; Name: 'Storage Server Workgroup (core installation)';), - (Id: PRODUCT_UNDEFINED; - Name: 'An unknown product';), (Id: PRODUCT_ULTIMATE; Name: 'Ultimate';), (Id: PRODUCT_ULTIMATE_E; Name: 'Ultimate E';), (Id: PRODUCT_ULTIMATE_N; Name: 'Ultimate N';), + (Id: PRODUCT_UNDEFINED; + Name: 'An unknown product';), (Id: PRODUCT_WEB_SERVER; Name: 'Web Server (full installation)';), (Id: PRODUCT_WEB_SERVER_CORE; Name: 'Web Server (core installation)';), + (Id: PRODUCT_CORE_ARM; + Name: 'Windows RT';), + (Id: PRODUCT_DATACENTER_NANO_SERVER; + Name: 'Windows Server Datacenter Edition (Nano Server installation)';), + (Id: PRODUCT_STANDARD_NANO_SERVER; + Name: 'Windows Server Standard Edition (Nano Server installation)';), + (Id: PRODUCT_DATACENTER_WS_SERVER_CORE; + Name: 'Windows Server Datacenter Edition (Server Core installation)';), + (Id: PRODUCT_STANDARD_WS_SERVER_CORE; + Name: 'Windows Server Standard Edition (Server Core installation)';), + (Id: PRODUCT_PRO_FOR_EDUCATION; + Name: 'Windows 10 Pro Education';), + (Id: PRODUCT_SERVERRDSH; + Name: 'Windows 10 Enterprise for Virtual Desktops';), + (Id: PRODUCT_DATACENTER_SERVER_AZURE_EDITION; + Name: 'Windows Server Datacenter: Azure Edition';), (Id: Cardinal(PRODUCT_UNLICENSED); Name: 'Unlicensed product';) ); @@ -1172,6 +1402,447 @@ implementation 'Software\Microsoft\Windows NT\CurrentVersion' ); +type + // Record used to map a build number to a release name + // Generally used in arrays + TBuildNameMap = record + Build: Integer; + LoRev: Integer; + HiRev: Integer; + Name: string; + Version: Word; + end; + + TWin10PlusVersionSet = set of TPJWin10PlusVersion; + +const + { + Known windows build numbers. + Sources: + https://en.wikipedia.org/wiki/List_of_Microsoft_Windows_versions + https://en.wikipedia.org/wiki/Windows_NT + https://en.wikipedia.org/wiki/Windows_10_version_history + https://en.wikipedia.org/wiki/Windows_11_version_history + https://blogs.windows.com/windows-insider/tag/windows-insider-program/ + https://en.wikipedia.org/wiki/Windows_Server + https://en.wikipedia.org/wiki/Windows_Server_2019 + https://en.wikipedia.org/wiki/Windows_Server_2016 + https://en.wikipedia.org/wiki/Windows_Server_2022 + https://tinyurl.com/y8tfadm2 (MS Windows Server release information) + https://docs.microsoft.com/en-us/lifecycle/products/windows-server-2022 + https://tinyurl.com/yj5e72jt (MS Win 10 release info) + https://tinyurl.com/kd3weeu7 (MS Server release info) + + Note: + For Vista and Win 7 we have to add service pack number to these values to + get actual build number. For Win 8 onwards we just use the build numbers + as is. + + References: + [^1] MS community blog post https://tinyurl.com/3c8e3hsc + [^2] https://en.wikipedia.org/wiki/Windows_11_version_history + } + + { + End of support (EOS) information for Windows Vista to Windows 8.1 + + Version | Mainstream EOS | Extended EOS + --------|----------------|------------- + Vista | 2012-04-10 | 2017-04-11 + 7 | 2015-01-13 | 2020-01-14 + 8 | N/a | 2016-01-12 + 8.1 | 2018-01-09 | 2023-01-10 + + See below for Windows 10 & 11 end of support information. + } + + + // Windows Vista ------------------------------------------------------------- + WinVista_Base_Build = 6000; + + // Windows 7 ----------------------------------------------------------------- + Win7_Base_Build = 7600; + + // Windows 8 ----------------------------------------------------------------- + Win8_Build = 9200; // Build number used for all Win 8/Svr 2012 + Win8Point1_Build = 9600; // Build number used for all Win 8.1/Svr 2012 R2 + + // Windows 10 ---------------------------------------------------------------- + + // Version 1507 preview builds + // Preview builds with major/minor version number 6.4 + // Expired by 2015-04-30 [^1]: + // 9841, 9860, 9879 + // Preview builds with major/minor version number 10.0 + // Expired by 2015-10-15 [^1]: + // 9926, 10041, 10049, 10061, 10074, 10122, 10130, 10158, 10159, 10162, + // 10166 + + // Version 1511 preview builds + // Expired by 2016-07-30 [^1]: + // 10525, 10532, 10547, 10565, 10576 + + // Version 1607 previews + Win10_1607_Preview_Builds: array[0..5] of Integer = ( + // Expired 2016-07-30 [^1]: + // 11082, 11099 + // Expired 2016-08-01 [^1]: + // 11102, 14251, 14257, 14267, 14271, 14279, 14291, 14295, 14316, 14328, + // 14332, 14342, 14352, 14361 + // Expired 2016-10-15 [^1]: + // 14366, 14367, 14371, 14372, + 14376, 14379, 14383, 14385, // unknown expiry date [^1] + 14388, 14390 // permanently activated [^1] + ); + + // Version 1703 previews + Win10_1703_Preview_Builds: array[0..26] of Integer = ( + 14901, 14905, 14915, 14926, 14931, 14936, 14942, 14946, 14951, 14955, + 14959, 14965, 14971, 14986, 15002, 15007, 15014, 15019, 15025, 15031, + 15042, 15046, 15048, 15055, 15058, 15060, 15061 + ); + + // Version 1709 previews + Win10_1709_Preview_Builds: array[0..23] of Integer = ( + 16170, 16176, 16179, 16184, 16188, 16193, 16199, 16212, 16215, 16226, + 16232, 16237, 16241, 16251, 16257, 16273, 16275, 16278, 16281, 16288, + 16291, 16294, 16296, 16299 {rev 0 only} + ); + + // Version 1803 previews + Win10_1803_Preview_Builds: array[0..21] of Integer = ( + 16353, 16362, 17004, 17017, 17025, 17035, 17040, 17046, 17063, 17074, + 17083, 17093, 17101, 17107, 17110, 17112, 17115, 17120, 17123, 17127, + 17128, 17133 + ); + + // Version 1809 previews + Win10_1809_Preview_Builds: array[0..33] of Integer = ( + 17604, 17618, 17623, 17627, 17634, 17639, 17643, 17650, 17655, 17661, + 17666, 17672, 17677, 17682, 17686, 17692, 17704, 17711, 17713, 17723, + 17728, 17730, 17733, 17735, 17738, 17741, 17744, 17746, 17751, 17754, + 17755, 17758, 17760, 17763 {rev 0 only} + ); + + // Version 1903 previews + Win10_1903_Preview_Builds: array[0..30] of Integer = ( + 18204, 18214, 18219, 18234, 18237, 18242, 18247, 18252, 18262, 18267, + 18272, 18277, 18282, 18290, 18298, 18305, 18309, 18312, 18317, 18323, + 18329, 18334, 18342, 18343, 18346, 18348, 18351, 18353, 18356, 18358, + 18361 + ); + + // Single build number used for 3 purposes: + // 1903 preview - revs 0, 30, 53, 86, 113 + // 1903 release - revs 116..1256 + // 1909 preview - revs 10000, 10005, 10006, 10012, 10014, 10015, + // 10019, 10022, 10024 + Win10_19XX_Shared_Build = 18362; + + // Version 1909 previews used build 18362 rev 10000 and later (see above) + + // Version 2004 previews + Win10_2004_Preview_Builds: array[0..43] of Integer = ( + 18836, 18841, 18845, 18850, 18855, 18860, 18865, 18875, 18885, 18890, + 18894, 18895, 18898, 18908, 18912, 18917, 18922, 18932, 18936, 18941, + 18945, 18950, 18956, 18963, 18965, 18970, 18975, 18980, 18985, 18990, + 18995, 18999, 19002, 19008, 19013, 19018, 19023, 19025, 19028, 19030, + 19033, 19035, 19037, + 19041 {revs 0, 21, 84, 113, 153, 172, 173, 207, 208 only} + ); + + // Version 20H2 previews: all used 19042, also used for release + Win10_20H2_Preview_Builds: array[0..0] of Integer = ( + 19042 + ); + + { + End of support information for Windows 10 versions (as of 2024-10-01). + GAC = General Availablity Channel. + LTSC = Long Term Support Channel. + + Version | GAC | LTSC + --------|------------|------------ + 1507 | ended | 2025-10-14 + 1511 | ended | N/a + 1607 | ended | 2026-10-13 + 1703 | ended | N/a + 1709 | ended | N/a + 1803 | ended | N/a + 1809 | ended | 2029-01-09 + 1903 | ended | N/a + 1909 | ended | N/a + 2004 | ended | N/a + 20H2 | ended | N/a + 21H1 | ended | N/a + 21H2 | ended | 2032-01-13 + 22H2 | 2025-10-14 | N/a + } + + // Win 10 release build numbers + Win10_1507_Build = 10240; + Win10_1511_Build = 10586; + Win10_1607_Build = 14393; + Win10_1703_Build = 15063; + Win10_1709_Build = 16299; + Win10_1803_Build = 17134; + Win10_1809_Build = 17763; + Win10_1903_Build = Win10_19XX_Shared_Build; + Win10_1909_Build = 18363; + Win10_2004_Build = 19041; + Win10_20H2_Build = 19042; + Win10_21H1_Build = 19043; // See **REF3** End of service @ rev 2364 + Win10_21H2_Build = 19044; // See **REF4** + Win10_22H2_Build = 19045; // See **REF5** + + // Map of Win 10 builds from 1st release (version 1507) to version 20H2 + // Later Win 10 releases have special handling and aren't in the build map + // + // NOTE: The following versions that are still being maintained per the above + // table have HiRev = MaxInt while the unsupported versions have HiRev set to + // the final build number. + Win10_BuildMap: array[0..10] of TBuildNameMap = ( + (Build: Win10_1507_Build; LoRev: 16484; HiRev: MaxInt; + Name: 'Version 1507'; Version: Ord(win10v1507)), + (Build: Win10_1511_Build; LoRev: 0; HiRev: 1540; + Name: 'Version 1511: November Update'; Version: Ord(win10v1511)), + (Build: Win10_1607_Build; LoRev: 0; HiRev: MaxInt; + Name: 'Version 1607: Anniversary Update'; Version: Ord(win10v1607)), + (Build: Win10_1703_Build; LoRev: 0; HiRev: 2679; + Name: 'Version 1703: Creators Update'; Version: Ord(win10v1703)), + (Build: Win10_1709_Build; LoRev: 15; HiRev: 2166; + Name: 'Version 1709: Fall Creators Update'; Version: Ord(win10v1709)), + (Build: Win10_1803_Build; LoRev: 1; HiRev: 2208; + Name: 'Version 1803: April 2018 Update'; Version: Ord(win10v1803)), + (Build: Win10_1809_Build; LoRev: 1; HiRev: MaxInt; + Name: 'Version 1809: October 2018 Update'; Version: Ord(win10v1809)), + (Build: Win10_1903_Build; LoRev: 116; HiRev: 1256; + Name: 'Version 1903: May 2019 Update'; Version: Ord(win10v1903)), + (Build: Win10_1909_Build; LoRev: 327; HiRev: 2274; + Name: 'Version 1909: November 2019 Update'; Version: Ord(win10v1909)), + (Build: Win10_2004_Build; LoRev: 264; HiRev: 1415; + Name: 'Version 2004: May 2020 Update'; Version: Ord(win10v2004)), + (Build: Win10_20H2_Build; LoRev: 572; HiRev: 2965; + Name: 'Version 20H2: October 2020 Update'; Version: Ord(win10v20H2)) + ); + + // Set of Windows 10 version identifiers + Win10_Versions: TWin10PlusVersionSet = [ + win10v1507, win10v1511, win10v1607, win10v1703, win10v1709, win10v1803, + win10v1809, win10v1903, win10v1909, win10v2004, win10v20H2, win10v21H1, + win10v21H2, win10v22H2 + ]; + + // Windows 10 slow ring, fast ring and skip-ahead channels were all expired + // well before 2022-12-31 and are not detected. (In fact there was never any + // detection of the slow ring and skip-ahead channels). + + // Windows 11 ---------------------------------------------------------------- + + // NOTE: All releases of Windows 11 report version 10.0 + + { + End of support (EOS) information for Windows 11 versions (as of 2024-10-01). + + Version | Home, Pro | Education, + | etc EOS | Enterprise + | | etc EOS + --------|------------|------------ + 21H2 | ENDED | 2024-10-08 + 22H2 | 2024-10-08 | 2025-10-14 + 23H2 | 2025-11-11 | 2026-11-10 + 24H2 | 2026-10-13 | 2027-10-12 + } + + // 1st build released branded as Windows 11 + // Insider version, Dev channel, v10.0.21996.1 + Win11_Dev_Build = 21996; + + // Windows 11 version 21H2 - see **REF6** in implementation for details + Win11_21H2_Build = 22000; + + // Windows 11 version 22H2 + // + // Build 22621 was the original beta build. Same build used for releases and + // various other channels. + // See **REF1** in implementation + Win11_22H2_Build = 22621; + + // Windows 11 version 22H3 + // See **REF10** in implementation + Win11_23H2_Build = 22631; + + // Windows 11 version 22H4 + // See **REF11** in implementation + Win11_24H2_Build = 26100; + + // "Preview Builds of October 2022 component update in Beta Channel" + // See **REF2** in implementation + Win11_Oct22Component_BetaChannel_Build = 22622; + + // "Preview Builds of February 2023 component update in Beta Channel" + // See **REF7** in implementation + Win11_Feb23Component_BetaChannel_Build = 22623; + + // "Preview builds of May 2023 component update in Beta Channel" + // See **REF8** in implementation + Win11_May23Component_BetaChannel_Build = 22624; + + // "Preview builds of future component update in Beta Channel" + // See **REF9** in implementation + Win11_FutureComponent_BetaChannel_Build = 22635; + + // "Preview builds of future component update in Dev Channel" + // See **REF12** in implementation + Win11_FutureComponent_DevChannel_Build = 26120; + + // Windows 11 Dev channel releases with version string "Dev" [^2] + // pre Win 11 release (expired 2021/10/31): + // 22449, 22454, 22458, 22463, + // pre Win 11 release (expired 2022/09/15): + // 22468, + // post Win 11 release, pre Win 11 22H2 beta release (expired 2022/09/15): + // 22471, 22478, 22483, 22489, 22494, 22499, 22504, 22509, 22518, 22523, + // 22526, 22533, 22538, 22543, 22557, 22563, + + // Windows 11 Dev channel releases with version string "22H2" [^2] + // pre Win 11 22H2 beta release (expired 2022/09/15): + // 22567, 22572, 22579 + // post Win 11 22H2 beta release (expired 2022/09/15): + // 25115, 25120, 25126, 25131, 25136, 25140, 25145, 25151, 25158, 25163, + // 25169, 25174, 25179, + // post Win 11 22H2 beta release (expired 2023/09/15): + // 25182, 25188, 25193, 25197, 25201, 25206, 25211, + // post Win 11 22H2 release, ni_release string (expired 2023/09/15): + // 25217, 25227, 25231, 25236, 25247, 25252, 25262, 25267, 25272, 25276, + // 25281, 25284, 25290, 25295, 25300, 25309, + // post Win 11 22H2 release, ni_prerelease string (expired 2023/09/15): + // 23403, 23419, 23424, 23430, 23435, 23440, 23451, 23466, 23471, 23475, + // 23481, 23486, 23493, 23506, 23511, 23516, 23521, + // post Win 11 22H2 release, ni_prerelease string (expired 2024-09-15): + // 23526, 23531, 23536, 23541, 23545, 23550, 23555, 23560, 23565, 23570, + // 23575, 23580, 23585, 23590, 23595, 23601, 23606, 23612, 23615, 23619, + // 23620 + + // Preview builds of Windows 11 in the Canary Channel with version string + // "22H2" [^2] + // expired 2023-09-15: + // 25314, 25324, 25330, 25336, 25346, 25352, 25357, 25370, + + // Preview builds of Windows 11 in the Canary Channel with version string + // "23H2" [^2] + // Expired 2023-09-15: + // 25375, 25381, 25387, 25393, 25905, 25915, 25921, 25926, + // Expired 2024-09-15: + // 25931, 25936, 25941, 25947, 25951, 25967, 25977, 25982, 25987, 25992, + // 25997, 26002, 26010, 26016, 26020, 26040, 26063, 26200, 26212, 26217, + // 26227, 26231, 26236, 26241, 26244, 26252, 26257, 27686. + + // Windows 11 Dev & Beta channel builds with version string "22H2" [^2] + Win11_22H2_DevAndBetaChannel_Builds: array[0..1] of Integer = ( + // Expired 2022/09/15: + // 22581, 22593, 22598 + // Unknown expiry date: + 22610, 22616 + ); + + // Windows 11 Preview, Dev & Canary channel builds with version "24H2" [^2] + Win11_24H2_DevAndCanaryChannel_Builds: array[0..1] of Integer = ( + // Expired 2024-09-15: + // 26052, 26058, 26080, 26085, + // Unknown expiry date: + 26090 {Dev revs:1,112; Canary revs: 1}, + 26100 {Dev revs:1,268; Canary revs: 1} + ); + + Win11_24H2_CanaryChannel_Builds: array[0..0] of Integer = ( + // expiring 2025-09-15: + 27695 + ); + + Win11_First_Build = Win11_Dev_Build; // First build number of Windows 11 + + // Windows server v10.0 version ---------------------------------------------- + + // These are the Windows server versions that (with one exception) report + // version 10.0. There's always an exception with Windows versioning! + + // Last build numbers of each "major" release before moving on to the next + Win2016_Last_Build = 17134; + Win2019_Last_Build = 18363; + WinServer_Last_Build = 19042; + + // Set of Windows 10 version identifiers + Win11_Versions: TWin10PlusVersionSet = [ + win11v21H2, win11v22H2, win11v23H2, win11v24H2 + ]; + + { + End of support information for all Windows Server versions. + + Version | End date + -----------------------------------|------------ + Windows NT 3.1 | 2000-12-31 + Windows NT 3.5 | 2001-12-31 + Windows NT 3.51 | 2001-12-31 + Windows NT 4.0 | 2004-12-31 + Windows 2000 | 2010-07-13 + Windows Server 2003 | 2015-07-14 + Windows Server 2003 R2 | 2015-07-14 + Windows Server 2008 | 2020-01-14 + Windows Server 2008 R2 | 2020-01-14 + Windows Server 2012 | 2023-10-10 + Windows Server 2012 R2 | 2023-10-10 + Windows Server 2016, version 1607 | 2027-01-12 + Windows Server 2016, version 1709 | 2019-04-09 + Windows Server 2016, version 1803 | 2019-11-12 + Windows Server 2019, version 1809 | 2029-01-09 + Windows Server 2019, version 1903 | 2020-12-08 + Windows Server 2019, version 1909 | 2021-05-11 + Windows Server, version 2004 | 2021-12-14 + Windows Server, version 20H2 | 2022-08-09 + Windows Server 2022, version 21H2 | 2031-10-14 + } + + // Map of Windows server releases that are named straightforwardly + WinServerSimpleBuildMap: array[0..12] of TBuildNameMap = ( + // Windows Server 2016 + (Build: 10074; LoRev: 0; HiRev: MaxInt; Name: 'Technical Preview 2'; + Version: 0), + (Build: 10514; LoRev: 0; HiRev: MaxInt; Name: 'Technical Preview 3'; + Version: 0), + (Build: 10586; LoRev: 0; HiRev: MaxInt; Name: 'Technical Preview 4'; + Version: 0), + (Build: 14300; LoRev: 0; HiRev: MaxInt; Name: 'Technical Preview 5'; + Version: 0), + (Build: 14393; LoRev: 0; HiRev: MaxInt; Name: 'Version 1607'; Version: 0), + (Build: 16299; LoRev: 0; HiRev: MaxInt; Name: 'Version 1709'; Version: 0), + (Build: Win2016_Last_Build; LoRev: 0; HiRev: MaxInt; Name: 'Version 1803'; + Version: 0), + // Windows Server 2019 + (Build: 17763; LoRev: 0; HiRev: MaxInt; Name: 'Version 1809'; Version: 0), + (Build: 18362; LoRev: 0; HiRev: MaxInt; Name: 'Version 1903'; Version: 0), + (Build: Win2019_Last_Build; LoRev: 0; HiRev: MaxInt; Name: 'Version 1909'; + Version: 0), + // Windows Server (no year number) + (Build: 19041; LoRev: 0; HiRev: MaxInt; Name: 'Version 2004'; Version: 0), + (Build: WinServer_Last_Build; LoRev: 0; HiRev: MaxInt; + Name: 'Version 20H2'; Version: 0), + // Windows Server 2022 + (Build: 20348; LoRev: 0; HiRev: MaxInt; Name: 'Version 21H2'; Version: 0) + ); + + // Windows server releases needing special handling + + // Server 2016 Technical Preview 1: reports version 6.4 instead of 10.0! + Win2016_TP1_Build = 9841; + + // Server 2019 Insider Preview builds: require format strings in names + Win2019_IP_Builds: array[0..9] of Integer = ( + 17623, 17627, 17666, 17692, 17709, 17713, 17723, 17733, 17738, 17744 + ); + + type // Function type of the GetNativeSystemInfo and GetSystemInfo functions TGetSystemInfo = procedure(var lpSystemInfo: TSystemInfo); stdcall; @@ -1207,6 +1878,7 @@ implementation InternalMinorVersion: LongWord = 0; InternalBuildNumber: Integer = 0; InternalCSDVersion: string = ''; + InternalRevisionNumber: Integer = 0; // Internal variable recording processor architecture information InternalProcessorArchitecture: Word = 0; // Internal variable recording additional update information. @@ -1215,12 +1887,22 @@ implementation // ** At present this variable is only used for Windows 10. InternalExtraUpdateInfo: string = ''; + InternalWin1011Version: TPJWin10PlusVersion = win10plusNA; + // Flag required when opening registry with specified access flags {$IFDEF REGACCESSFLAGS} const KEY_WOW64_64KEY = $0100; // registry access flag not defined in all Delphis {$ENDIF} +// Checks if integer V is in the range of values defined by VLo and VHi, +// inclusive. +function IsInRange(const V, VLo, VHi: Integer): Boolean; +begin + Assert(VLo <= VHi); + Result := (V >= VLo) and (V <= VHi); +end; + // Tests Windows version (major, minor, service pack major & service pack minor) // against the given values using the given comparison condition and return // True if the given version matches the current one or False if not @@ -1268,9 +1950,12 @@ function TestWindowsVersion(wMajorVersion, wMinorVersion, ); end; -// Checks if given build number matches that of the current OS. -// Assumes VerifyVersionInfo & VerSetConditionMask APIs functions are available -function IsBuildNumber(BuildNumber: DWORD): Boolean; +// Checks how the OS build number compares to the given TestBuildNumber +// according to operator Op. +// Op must be one of VER_EQUAL, VER_GREATER, VER_GREATER_EQUAL, VER_LESS or +// VER_LESS_EQUAL. +// Assumes VerifyVersionInfo & VerSetConditionMask APIs functions are available. +function TestBuildNumber(Op, TestBuildNumber: DWORD): Boolean; var OSVI: TOSVersionInfoEx; POSVI: POSVersionInfoEx; @@ -1279,12 +1964,94 @@ function IsBuildNumber(BuildNumber: DWORD): Boolean; Assert(Assigned(VerSetConditionMask) and Assigned(VerifyVersionInfo)); FillChar(OSVI, SizeOf(OSVI), 0); OSVI.dwOSVersionInfoSize := SizeOf(OSVI); - OSVI.dwBuildNumber := BuildNumber; + OSVI.dwBuildNumber := TestBuildNumber; POSVI := @OSVI; - ConditionalMask := VerSetConditionMask(0, VER_BUILDNUMBER, VER_EQUAL); + ConditionalMask := VerSetConditionMask(0, VER_BUILDNUMBER, Op); Result := VerifyVersionInfo(POSVI, VER_BUILDNUMBER, ConditionalMask); end; +// Checks if given build number matches that of the current OS. +// Assumes VerifyVersionInfo & VerSetConditionMask APIs functions are available. +function IsBuildNumber(BuildNumber: DWORD): Boolean; + {$IFDEF INLINEMETHODS}inline;{$ENDIF} +begin + Result := TestBuildNumber(VER_EQUAL, BuildNumber); +end; + +// Checks if any of the given build numbers match that of the current OS. +// If current build number is in the list, FoundBN is set to the found build +// number and True is returned. Otherwise False is returned and FoundBN is set +// to zero. +function FindBuildNumberFrom(const BNs: array of Integer; var FoundBN: Integer): + Boolean; +var + I: Integer; +begin + FoundBN := 0; + Result := False; + for I := Low(BNs) to High(BNs) do + begin + if IsBuildNumber(BNs[I]) then + begin + FoundBN := BNs[I]; + Result := True; + Break; + end; + end; +end; + +// Checks if any of the build numbers in the given array match that of the +// current OS AND if the OS revision number is in the specified range. If so +// then the build number that was found then True is returned, and the build +// number and it's associated text are passed back in the FoundBN and FoundExtra +// parameters respectively. Otherwise False is returned, FoundBN is set to 0 and +// FoundExtra is set to ''. +function FindBuildNameAndExtraFrom(const Infos: array of TBuildNameMap; + var FoundBN: Integer; var FoundExtra: string; var FoundVersion: Word): + Boolean; +var + I: Integer; +begin + FoundBN := 0; + FoundExtra := ''; + Result := False; + for I := Low(Infos) to High(Infos) do + begin + if IsBuildNumber(Infos[I].Build) and + IsInRange(InternalRevisionNumber, Infos[I].LoRev, Infos[I].HiRev) then + begin + FoundBN := Infos[I].Build; + FoundExtra := Infos[I].Name; + FoundVersion := Infos[I].Version; + Result := True; + Break; + end; + end; +end; + +function FindWin10PreviewBuildNameAndExtraFrom(const Builds: array of Integer; + const Win10Version: string; var FoundBN: Integer; var FoundExtra: string): + Boolean; +var + I: Integer; +begin + FoundBN := 0; + FoundExtra := ''; + Result := False; + for I := Low(Builds) to High(Builds) do + begin + if IsBuildNumber(Builds[I]) then + begin + FoundBN := Builds[I]; + FoundExtra := Format( + 'Version %s Preview Build %d', [Win10Version, FoundBN] + ); + Result := True; + Break; + end; + end; +end; + // Checks if the OS has the given product type. // Assumes VerifyVersionInfo & VerSetConditionMask APIs functions are available function IsWindowsProductType(ProductType: Byte): Boolean; @@ -1380,8 +2147,8 @@ function GetEnvVar(const VarName: string): string; // Checks if host OS is Windows 2000 or earlier, including any Win9x OS. // This is a helper function for RegCreate and RegOpenKeyReadOnly and avoids -// avoids using TPJOSInfo to ensure that an infinite loop is not set up with -// TPJOSInfo calling back into RegCreate. +// using TPJOSInfo to ensure that an infinite loop is not set up with TPJOSInfo +// calling back into RegCreate. function IsWin2000OrEarlier: Boolean; begin // NOTE: all Win9x OSs have InternalMajorVersion < 5, so we don't need to @@ -1396,7 +2163,8 @@ function IsWin2000OrEarlier: Boolean; function RegCreate: TRegistry; begin {$IFDEF REGACCESSFLAGS} - //! Fix for issue #14 (http://bit.n/eWkw9X) suggested by Steffen Schaff. + //! Fix for issue #14 (https://sourceforge.net/p/ddablib/tickets/14/) + //! suggested by Steffen Schaff. //! Later modified to allow for fact that Windows 2000 fails if //! KEY_WOW64_64KEY is used. if IsWin2000OrEarlier then @@ -1465,6 +2233,59 @@ function GetRegistryString(const RootKey: HKEY; end; end; +function GetRegistryInt(const RootKey: HKEY; const SubKey, Name: string): + Integer; +var + Reg: TRegistry; // registry access object + ValueInfo: TRegDataInfo; // info about registry value +begin + Result := 0; + // Open registry at required root key + Reg := RegCreate; + try + Reg.RootKey := RootKey; + if RegOpenKeyReadOnly(Reg, SubKey) and Reg.ValueExists(Name) then + begin + // Check if registry value is integer + Reg.GetDataInfo(Name, ValueInfo); + if ValueInfo.RegData <> rdInteger then + raise EPJSysInfo.Create(sBadRegIntType); + Result := Reg.ReadInteger(Name); + end; + finally + // Close registry + Reg.CloseKey; + Reg.Free; + end; +end; + +function GetRegistryBytes(const RootKey: HKEY; const SubKey, Name: string): + TBytes; +var + Reg: TRegistry; // registry access object + ValueInfo: TRegDataInfo; // info about registry value +begin + SetLength(Result, 0); + // Open registry at required root key + Reg := RegCreate; + try + Reg.RootKey := RootKey; + if RegOpenKeyReadOnly(Reg, SubKey) and Reg.ValueExists(Name) then + begin + // Check if registry value is integer + Reg.GetDataInfo(Name, ValueInfo); + if ValueInfo.RegData <> rdBinary then + raise EPJSysInfo.Create(sBadRegBinType); + SetLength(Result, ValueInfo.DataSize); + Reg.ReadBinaryData(Name, Result[0], Length(Result)); + end; + finally + // Close registry + Reg.CloseKey; + Reg.Free; + end; +end; + // Gets string info for given value from Windows current version key in // registry. function GetCurrentVersionRegStr(ValName: string): string; @@ -1490,18 +2311,23 @@ procedure InitPlatformIdEx; GetVersionEx: TGetVersionEx; // pointer to GetVersionEx API function GetProductInfo: TGetProductInfo; // pointer to GetProductInfo API function SI: TSystemInfo; // structure from GetSystemInfo API call -const - // Known windows build numbers. - // Source: https://en.wikipedia.org/wiki/Windows_NT - // for Vista and Win 7 we have to add service pack number to these values to - // get actual build number - WinVistaBaseBuild = 6000; - Win7BaseBuild = 7600; - // for Win 8 onwards we just use the build numbers as is - Win8Build = 9200; - Win8Point1Build = 9600; - Win10TH1Build = 10240; - Win10TH2Build = 10586; + VersionEx: Word; // gets extra version info (Win 10/11) + + // Get OS's revision number from registry. + function GetOSRevisionNumber(const IsNT: Boolean): Integer; + begin + Result := GetRegistryInt( + HKEY_LOCAL_MACHINE, CurrentVersionRegKeys[IsNT], 'UBR' + ); + end; + + // Append "Moment N" to InternalExtraUpdateInfo + procedure AppendMomentToInternalExtraUpdateInfo(N: Cardinal); + begin + InternalExtraUpdateInfo := InternalExtraUpdateInfo + + ' Moment ' + IntToStr(N); + end; + begin // Load version query functions used externally to this routine VerSetConditionMask := LoadKernelFunc('VerSetConditionMask'); @@ -1510,6 +2336,7 @@ procedure InitPlatformIdEx; {$ELSE} VerifyVersionInfo := LoadKernelFunc('VerifyVersionInfoA'); {$ENDIF} + if not UseGetVersionAPI then begin // Not using GetVersion and GetVersionEx functions to get version info @@ -1521,43 +2348,60 @@ procedure InitPlatformIdEx; Win32ServicePackMinor := 0; // we don't use suite mask any more! Win32SuiteMask := 0; - // platform for all OSs tested for this way are NT: the NewGetVersion calls - // below indirectly call VerifyVersionInfo API, which is only defined for - // Windows 2000 and later. + // platform for all OSs tested for this way is always NT: the NewGetVersion + // calls below indirectly call VerifyVersionInfo API, which is only defined + // for Windows 2000 and later. InternalPlatform := VER_PLATFORM_WIN32_NT; + InternalRevisionNumber := GetOSRevisionNumber(True); Win32HaveExInfo := True; NewGetVersion( InternalMajorVersion, InternalMinorVersion, Win32ServicePackMajor, Win32ServicePackMinor ); + // Test possible product types to see which one we have + if IsWindowsProductType(VER_NT_WORKSTATION) then + Win32ProductType := VER_NT_WORKSTATION + else if IsWindowsProductType(VER_NT_DOMAIN_CONTROLLER) then + Win32ProductType := VER_NT_DOMAIN_CONTROLLER + else if IsWindowsProductType(VER_NT_SERVER) then + Win32ProductType := VER_NT_SERVER + else + Win32ProductType := 0; // NOTE: It's going to be very slow to test for all possible build numbers, - // so I've just narrowed the search down using the information at - // http://en.wikipedia.org/wiki/Windows_NT + // so I've narrowed the search down using the information at + // https://en.wikipedia.org/wiki/Windows_NT case InternalMajorVersion of 6: begin case InternalMinorVersion of 0: // Vista - InternalBuildNumber := WinVistaBaseBuild + Win32ServicePackMajor; + InternalBuildNumber := WinVista_Base_Build + Win32ServicePackMajor; 1: // Windows 7 - InternalBuildNumber := Win7BaseBuild + Win32ServicePackMajor; + InternalBuildNumber := Win7_Base_Build + Win32ServicePackMajor; 2: // Windows 8 (no known SPs) if Win32ServicePackMajor = 0 then - InternalBuildNumber := Win8Build; + InternalBuildNumber := Win8_Build; 3: // Windows 8.1 (no known SPs) if Win32ServicePackMajor = 0 then - InternalBuildNumber := Win8Point1Build; - + InternalBuildNumber := Win8Point1_Build; + 4: + if (Win32ProductType = VER_NT_DOMAIN_CONTROLLER) + or (Win32ProductType = VER_NT_SERVER) then + begin + // Windows 2016 Server tech preview 1 + InternalBuildNumber := Win2016_TP1_Build; + InternalExtraUpdateInfo := 'Technical Preview 6'; + end; end; if Win32ServicePackMajor > 0 then // ** Tried to read this info from registry, but for some weird // reason the required value is reported as non-existant by // TRegistry, even though it is present in registry. - // ** Seems there is some kind of regitry "spoofing" going on (see + // ** Seems there is some kind of registry "spoofing" going on (see // below. InternalCSDVersion := Format( 'Service Pack %d', [Win32ServicePackMajor] @@ -1567,41 +2411,542 @@ procedure InitPlatformIdEx; begin case InternalMinorVersion of 0: + // ** As of 2022/06/01 all releases of Windows 10 **and** + // Windows 11 report major version 10 and minor version 0 + // Well that's helpful!! + if (Win32ProductType <> VER_NT_DOMAIN_CONTROLLER) + and (Win32ProductType <> VER_NT_SERVER) then begin - // TODO: Revist when server version released to check if same build - // number(s) - // Windows 10 TH1 branch release - if IsBuildNumber(Win10TH1Build) then - InternalBuildNumber := Win10TH1Build - // Windows 10 TH2 branch release - else if IsBuildNumber(Win10TH2Build) then + if FindBuildNameAndExtraFrom( + Win10_BuildMap, InternalBuildNumber, InternalExtraUpdateInfo, + VersionEx + ) then begin - InternalBuildNumber := Win10TH2Build; - InternalExtraUpdateInfo := 'TH2: November Update'; - end; + InternalWin1011Version := + TPJWin10PlusVersion(VersionEx); + end + else if IsBuildNumber(Win10_21H1_Build) then + begin + // **REF3** + InternalBuildNumber := Win10_21H1_Build; + InternalWin1011Version := win10v21H1; + case InternalRevisionNumber of + 985, 1023, 1052, 1055, 1081, 1082, 1083, 1110, 1151, 1165, 1202, + 1237, 1266, 1288, 1320, 1348, 1387, 1415, 1466, 1469, 1503, + 1526, 1566, 1586, 1620, 1645, 1682, 1706, 1708, 1741, 1766, + 1767, 1806, 1826, 1865, 1889, 1949, 2006, 2075, 2130, 2132, + 2193, 2194, 2251, 2311, 2364 {final build}: + InternalExtraUpdateInfo := 'Version 21H1'; + 1147, 1149, 1200, 1263, 1319, 1379, 1381: + InternalExtraUpdateInfo := Format( + 'Version 21H1 [Release Preview Channel v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + 844, 867, 899, 906, 928, 962, 964: + InternalExtraUpdateInfo := Format( + 'Version 21H1 [Beta Channel v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + else + InternalExtraUpdateInfo := Format( + 'Version 21H1 [Unknown release v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + end; + end + else if IsBuildNumber(Win10_21H2_Build) then + begin + // **REF4** + // From 21H2 Windows 10 moves from a 6 monthly update cycle to a + // yearly cycle + InternalBuildNumber := Win10_21H2_Build; + InternalWin1011Version := win10v21H2; + case InternalRevisionNumber of + 1288, 1348, 1387, 1415, 1466, 1469, 1503, 1526, 1566, 1586, + 1620, 1645, 1682, 1706, 1708, 1741, 1766, 1767, 1806, 1826, + 1865, 1889, 1949, 2006, 2075, 2130, 2132, 2193, 2194, 2251, + 2311, 2364, 2486, 2546, 2604, 2673, 2728, 2788, 2846, 2965, + 3086, 3208, 3324, 3448, 3570, 3693, 3803, 3930, 4046, + 4170, 4291, 4412, 4529, 4651, 4780, 4894 .. MaxInt: + InternalExtraUpdateInfo := 'Version 21H2'; + 1147, 1149, 1151, 1165, 1200, 1202, 1237, 1263, 1266, 1319, + 1320, 1379, 1381, 1499, 1618, 1679, 1737, 1739, 1862, + 1947, 2192, 2545: + InternalExtraUpdateInfo := Format( + 'Version 21H2 [Release Preview Channel v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + else + InternalExtraUpdateInfo := Format( + 'Version 21H2 [Unknown release v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + end; + end + else if IsBuildNumber(Win10_22H2_Build) then + begin + // **REF5** + InternalBuildNumber := Win10_22H2_Build; + InternalWin1011Version := win10v22H2; + case InternalBuildNumber of + 2006, 2130, 2132, 2193, 2194, 2251, 2311, 2364, 2486, 2546, + 2604, 2673, 2728, 2788, 2846, 2913, 2965, 3031, 3086, 3208, + 3271, 3324, 3393, 3448, 3516, 3570, 3636, 3693, 3758, 3803, + 3930, 3996, 4046, 4123, 4170, 4239, 4291, 4355, 4412, 4474, + 4529, 4598, 4651, 4717, 4780, 4842, 4894, 4957 .. MaxInt: + InternalExtraUpdateInfo := 'Version 22H2'; + 1865, 1889, 1949, 2075, 2301, 2670, 2787, 2908, 3030, 3154, + 3155, 3269, 3391, 3513, 3754, 3757, 3992, 4116, 4233, 4235, + 4353, 4472: + InternalExtraUpdateInfo := Format( + 'Version 22H2 [Release Preview Channel v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + 4593, 4713, 4955: + InternalExtraUpdateInfo := Format( + 'Version 22H2 ' + + '[Beta and Release Preview Channels v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + else + InternalExtraUpdateInfo := Format( + 'Version 22H2 [Unknown release v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + end; + end + // Win 11 releases are reporting v10.0 + // Details taken from: https://tinyurl.com/usupsz4a + else if IsBuildNumber(Win11_Dev_Build) then + begin + InternalBuildNumber := Win11_Dev_Build; + InternalWin1011Version := win10plusUnknown; + InternalExtraUpdateInfo := Format( + 'Dev [Insider v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ) + end + else if IsBuildNumber(Win11_21H2_Build) then + begin + // **REF6** + // There are several Win 11 releases with this build number + // Which release we're talking about depends on the revision + // number. + // *** Amazingly one of them, revision 194, is the 1st public + // release of Win 11 -- well hidden eh?! + InternalBuildNumber := Win11_21H2_Build; + InternalWin1011Version := win11v21H2; + case InternalRevisionNumber of + 194, 258, 282, 348, 376, 434, 438, 469, 493, 527, 556, 593, 613, + 652, 675, 708, 739, 740, 778, 795, 832, 856, 918, 978, 1042, + 1098, 1100, 1165, 1219, 1281, 1335, 1455, 1516, 1574, 1641, + 1696, 1761, 1817, 1880, 1936, 2003, 2057, 2124, 2176, 2245, + 2295, 2360, 2416, 2482, 2538, 2600, 2652, 2713, 2777, + 2836, 2899, 2960, 3019, 3079, 3147, 3197 .. MaxInt: + // Public releases of Windows 11 + InternalExtraUpdateInfo := 'Version 21H2'; + 51, 65, 71: + InternalExtraUpdateInfo := Format( + 'Version 21H2 [Dev Channel v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + 100, 120, 132, 160, 168: + InternalExtraUpdateInfo := Format( + 'Version 21H2 [Dev & Beta Channels v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + 176, 184, 346, 466, 526, 588: + InternalExtraUpdateInfo := Format( + 'Version 21H2 ' + + '[Beta & Release Preview Channels v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + 651, 706, 776, 829, 917, 1041, 1163, 1279, 1515, 1639, 1757, + 1879, 2001, 2121, 2243, 2359, 2479: + InternalExtraUpdateInfo := Format( + 'Version 21H2 Release Preview Channel v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + else + InternalExtraUpdateInfo := Format( + 'Version 21H2 [Unknown release v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + end; + end + else if IsBuildNumber(Win11_22H2_Build) then + begin + // **REF1** + InternalBuildNumber := Win11_22H2_Build; + InternalWin1011Version := win11v22H2; + case InternalRevisionNumber of + 382, 521, 525, 608, 674, 675, 755, 819, 900, 963, 1105, 1194, + 1265, 1344, 1413, 1485, 1555, 1635, 1702, 1778, 1848, 1926, + 1928, 1992, 2070, 2134, 2215, 2283, 2361, 2428, 2506, 2715, + 2792, 2861, 3007, 3085, 3155, 3235, 3296, 3374, 3447, 3527, + 3593, 3672, 3737, 3810, 3880, 3958, 4037, 4112, 4169, 4249 + .. MaxInt: + begin + InternalExtraUpdateInfo := 'Version 22H2'; + case InternalRevisionNumber of + 675: AppendMomentToInternalExtraUpdateInfo(1); + 1344: AppendMomentToInternalExtraUpdateInfo(2); + 1778: AppendMomentToInternalExtraUpdateInfo(3); + 2361: AppendMomentToInternalExtraUpdateInfo(4); + 3235: AppendMomentToInternalExtraUpdateInfo(5); + end; + end; + 1: + InternalExtraUpdateInfo := Format( + 'Version 22H2 [Beta & Release Preview v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + 105, 169, 232, 317, 457, 607, 754, 898, 1192, 1343, 1483, 1631, + 1776, 2066, 2213, 2359, 2500, 2787, 3078, 3227, 3371, 3520, + 3668, 3807, 3951, 4108, 4247: + InternalExtraUpdateInfo := Format( + 'Version 22H2 [Release Preview v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + 160, 290, 436, 440, 450, 575, 586, 590, 598, 601, 730, 741, 746, + 870, 875, 885, 891, 1020, 1028, 1037, 1095, 1180, 1245, 1250, + 1255, 1325, 1391, 1465, 1470, 1537, 1546, 1616, 1680, 1690, + 1755, 1825, 1830, 1835, 1900, 1906, 1972, 2048, 2050, 2115, + 2129, 2191, 2199, 2262, 2265, 2271, 2338: + InternalExtraUpdateInfo := Format( + 'Version 22H2 [Beta v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + else + InternalExtraUpdateInfo := Format( + 'Version 22H2 [Unknown release v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + end; + end + else if IsBuildNumber(Win11_23H2_Build) then + begin + // **REF10** + InternalBuildNumber := Win11_23H2_Build; + InternalWin1011Version := win11v23H2; + case InternalRevisionNumber of + 2428, 2506, 2715, 2792, 2861, 3007, 3085, 3155, 3235 {Moment 5}, + 3296, 3374, 3447, 3527, 3593, 3672, 3737, 3810, 3880, 3958, + 4037, 4112, 4169, 4249 .. MaxInt: + InternalExtraUpdateInfo := 'Version 23H2'; + 1825, 1830, 1835, 1900, 1906, 1972: + begin + // revisions 1825..1972 had version string "22H2" + InternalWin1011Version := win11v22H2; + InternalExtraUpdateInfo := Format( + 'Version 22H2 [Beta v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + end; + 2048, 2050, 2115, 2129, 2191, 2199, 2262, 2265, 2271, 2338: + InternalExtraUpdateInfo := Format( + 'Version 23H2 [Beta v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + 2361, 2787, 3078, 3227, 3371, 3520, 3668, 3807, 3951, 4108, + 4247: + InternalExtraUpdateInfo := Format( + 'Version 23H2 [Release Preview v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + else + InternalExtraUpdateInfo := Format( + 'Version 23H2 [Unknown release v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + end; + end + else if IsBuildNumber(Win11_24H2_Build) then + begin + // **REF11** + InternalBuildNumber := Win11_24H2_Build; + InternalWin1011Version := win11v24H2; + case InternalRevisionNumber of + 1742, 1882 .. MaxInt: + InternalExtraUpdateInfo := 'Version 24H2'; + 560, 712, 863, 994, 1000, 1150, 1297, 1301, 1457, 1586, 1591: + InternalExtraUpdateInfo := Format( + 'Version 24H2 [Release Preview v10.0.%d.%d', + [InternalBuildNumber, InternalRevisionNumber] + ); + 1: + InternalExtraUpdateInfo := Format( + 'Version 24H2 [Dev & Canary Channel v10.0.%d.%d', + [InternalBuildNumber, InternalRevisionNumber] + ); + 268: + InternalExtraUpdateInfo := Format( + 'Version 24H2 [Dev Channel v10.0.%d.%d', + [InternalBuildNumber, InternalRevisionNumber] + ); + else + InternalExtraUpdateInfo := Format( + 'Version 24H2 [Unknown release v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + end; + end + else if FindBuildNumberFrom( + Win11_24H2_DevAndCanaryChannel_Builds, InternalBuildNumber + ) then + begin + // Win11 builds in Canary, Dev & Preview channels with version + // string "24H2" + InternalWin1011Version := win10plusUnknown; + InternalExtraUpdateInfo := Format( + 'Dev or Canary Channel Version 24H2 v10.0.%d.%d', + [InternalBuildNumber, InternalRevisionNumber] + ); + end + else if FindBuildNumberFrom( + Win11_24H2_CanaryChannel_Builds, InternalBuildNumber + ) then + begin + // Win11 builds in Canary channel with version string "24H2" + InternalWin1011Version := win10plusUnknown; + InternalExtraUpdateInfo := Format( + 'Canary Channel Version 24H2 v10.0.%d.%d', + [InternalBuildNumber, InternalRevisionNumber] + ); + end + else if IsBuildNumber(Win11_Oct22Component_BetaChannel_Build) then + begin + // **REF2** + InternalBuildNumber := Win11_Oct22Component_BetaChannel_Build; + InternalWin1011Version := win10plusUnknown; + case InternalRevisionNumber of + 290, 436, 440, 450, 575, 586, 590, 598, 601: + InternalExtraUpdateInfo := Format( + 'Version 22H2 [October Component Update v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + else + InternalExtraUpdateInfo := Format( + 'Version 22H2 [Unknown release v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + end; + end + else if FindBuildNumberFrom( + Win11_22H2_DevAndBetaChannel_Builds, InternalBuildNumber + ) then + begin + // Win 11 Dev & Beta channel builds with version string "22H2" + InternalWin1011Version := win10plusUnknown; + InternalExtraUpdateInfo := Format( + 'Dev & Beta Channels v10.0.%d.%d (22H2)', + [InternalBuildNumber, InternalRevisionNumber] + ); + end + else if IsBuildNumber(Win11_Feb23Component_BetaChannel_Build) then + begin + // **REF7** + InternalBuildNumber := Win11_Feb23Component_BetaChannel_Build; + InternalWin1011Version := win10plusUnknown; + case InternalRevisionNumber of + 730, 741, 746, 870, 875, 885, 891, 1020, 1028, 1037, 1095, + 1180, 1245, 1250, 1255, 1325 .. MaxInt: + InternalExtraUpdateInfo := Format( + 'February 2023 Component Update Beta v10.0.%d.%d', + [InternalBuildNumber, InternalRevisionNumber] + ); + else + InternalExtraUpdateInfo := Format( + 'February 2023 Component Update [Unknown Beta v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + end; + end + else if IsBuildNumber(Win11_May23Component_BetaChannel_Build) then + begin + // **REF8** + InternalBuildNumber := Win11_May23Component_BetaChannel_Build; + InternalWin1011Version := win10plusUnknown; + case InternalRevisionNumber of + 1391, 1465, 1470, 1537, 1546, 1610, 1616, 1680, 1690, 1755 .. + MaxInt: + InternalExtraUpdateInfo := Format( + 'May 2023 Component Update Beta v10.0.%d.%d', + [InternalBuildNumber, InternalRevisionNumber] + ); + else + InternalExtraUpdateInfo := Format( + 'May 2023 Component Update [Unknown Beta v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + end; + end + else if IsBuildNumber(Win11_FutureComponent_BetaChannel_Build) then + begin + // **REF9** + InternalBuildNumber := Win11_FutureComponent_BetaChannel_Build; + InternalWin1011Version := win10plusUnknown; + case InternalRevisionNumber of + 2419, 2483, 2486, 2552, 2700, 2771, 2776, 2841, 2850, 2915, + 2921, 3061, 3066, 3130, 3139, 3140, 3209, 3212, 3276, 3286, + 3350, 3420, 3430, 3495, 3500, 3566, 3570, 3575, 3640, 3646, + 3720, 3785, 3790, 3858, 3930, 3936, 4000, 4005, 4010, 4076, + 4082, 4145, 4225, 4291 .. MaxInt: + InternalExtraUpdateInfo := Format( + 'Future Component Update Beta v10.0.%d.%d', + [InternalBuildNumber, InternalRevisionNumber] + ); + else + InternalExtraUpdateInfo := Format( + 'Future Component Update [Unknown Beta v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + end; + end + else if IsBuildNumber(Win11_FutureComponent_DevChannel_Build) then + begin + // **REF12** + InternalBuildNumber := Win11_FutureComponent_DevChannel_Build; + InternalWin1011Version := win10plusUnknown; + case InternalRevisionNumber of + 461, 470, 670, 751, 770, 961, 1252, 1330, 1340, 1350, 1542, + 1843, 1912 .. MaxInt: + InternalExtraUpdateInfo := Format( + 'Future Component Update Dev Channel v10.0.%d.%d', + [InternalBuildNumber, InternalRevisionNumber] + ); + else + InternalExtraUpdateInfo := Format( + 'Future Component Update [Unknown Beta v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + end; + end + // End with some much less likely cases + // NOTE: All the following tests MUST come after the last call to + // FindBuildNameAndExtraFrom() for non-server OSs because some + // build numbers are common to both sets of tests and the + // following rely on FindBuildNameAndExtraFrom() to have + // filtered out releases. + else if FindWin10PreviewBuildNameAndExtraFrom( + Win10_20H2_Preview_Builds, '20H2', + InternalBuildNumber, InternalExtraUpdateInfo + ) then + begin + InternalWin1011Version := win10v20H2; + end + else if FindWin10PreviewBuildNameAndExtraFrom( + Win10_2004_Preview_Builds, '2004', + InternalBuildNumber, InternalExtraUpdateInfo + ) then + begin + InternalWin1011Version := win10v2004; + end + else if IsBuildNumber(Win10_19XX_Shared_Build) then + begin + // If we get here the Win10_19XX_Shared_Build will either be a + // preview of Version 1903 or 1909 + InternalBuildNumber := Win10_19XX_Shared_Build; + if IsInRange(InternalRevisionNumber, 0, 113) then + begin + InternalWin1011Version := win10v1903; + InternalExtraUpdateInfo := Format( + 'Version 1903 Preview Build %d.%d', + [InternalBuildNumber, InternalRevisionNumber] + ) + end + else if IsInRange(InternalRevisionNumber, 10000, 10024) then + begin + InternalWin1011Version := win10v1909; + InternalExtraUpdateInfo := Format( + 'Version 1909 Preview Build %d.%d', + [InternalBuildNumber, InternalRevisionNumber] + ); + end; + end + else if FindWin10PreviewBuildNameAndExtraFrom( + Win10_1903_Preview_Builds, '1903', + InternalBuildNumber, InternalExtraUpdateInfo + ) then + begin + InternalWin1011Version := win10v1903; + end + else if FindWin10PreviewBuildNameAndExtraFrom( + Win10_1809_Preview_Builds, '1809', + InternalBuildNumber, InternalExtraUpdateInfo + ) then + begin + InternalWin1011Version := win10v1809; + end + else if FindWin10PreviewBuildNameAndExtraFrom( + Win10_1803_Preview_Builds, '1803', + InternalBuildNumber, InternalExtraUpdateInfo + ) then + begin + InternalWin1011Version := win10v1803; + end + else if FindWin10PreviewBuildNameAndExtraFrom( + Win10_1709_Preview_Builds, '1709', + InternalBuildNumber, InternalExtraUpdateInfo + ) then + begin + InternalWin1011Version := win10v1709; + end + else if FindWin10PreviewBuildNameAndExtraFrom( + Win10_1703_Preview_Builds, '1703', + InternalBuildNumber, InternalExtraUpdateInfo + ) then + begin + InternalWin1011Version := win10v1703; + end + else if FindWin10PreviewBuildNameAndExtraFrom( + Win10_1607_Preview_Builds, '1607', + InternalBuildNumber, InternalExtraUpdateInfo + ) then + begin + InternalWin1011Version := win10v1607; + end + end + else // Win32ProductType in [VER_NT_DOMAIN_CONTROLLER, VER_NT_SERVER] + begin + // Check for the easy-to-handle Win Server v10. builds, i.e. the + // ones where Extra Update Info is just plain text. + if FindBuildNameAndExtraFrom( + WinServerSimpleBuildMap, + InternalBuildNumber, + InternalExtraUpdateInfo, + VersionEx // unused + ) then + begin + // Nothing to do: required internal variables set in function call + end + else if FindBuildNumberFrom( + Win2019_IP_Builds, InternalBuildNumber + ) then + begin + // Windows 2019 Insider preview builds require build number in + // Extra Update Info. + InternalExtraUpdateInfo := Format( + 'Insider Preview Build %d', [InternalBuildNumber] + ); + end end; end; end; end; // ** If InternalBuildNumber is 0 when we get here then we failed to get it - // We no longer look in registry as of SVN commit r2001, because this is - // can get spoofed. E.g. when running on Windows 10 TH2 registry call is - // returning build number of 7600 even though regedit reveals it to be - // 10586 ! + // We no longer look in registry as of SVN commit r2001 (Git commit + // d44aea3e6e0ed7bd317398252fcf862051b159f7 in ddablib/sysinfo on + // GitHub), because this can get spoofed. E.g. when running on Windows 10 + // TH2 registry call is returning build number of 7600 even though + // regedit reveals it to be 10586 ! // So we must now consider a build number of 0 as indicating an unknown // build number. + // But note that some users report that their registry is returning + // correct value. I really hate Windows!!! // ** Seems like more registry spoofing (see above). - // Test possible product types to see which one we have - if IsWindowsProductType(VER_NT_WORKSTATION) then - Win32ProductType := VER_NT_WORKSTATION - else if IsWindowsProductType(VER_NT_DOMAIN_CONTROLLER) then - Win32ProductType := VER_NT_DOMAIN_CONTROLLER - else if IsWindowsProductType(VER_NT_SERVER) then - Win32ProductType := VER_NT_SERVER - else - Win32ProductType := 0; end else begin @@ -1613,6 +2958,9 @@ procedure InitPlatformIdEx; InternalMinorVersion := Win32MinorVersion; InternalBuildNumber := Win32BuildNumber; InternalCSDVersion := Win32CSDVersion; + InternalRevisionNumber := GetOSRevisionNumber( + InternalPlatform = VER_PLATFORM_WIN32_NT + ); // Try to get extended information {$IFDEF UNICODE} GetVersionEx := LoadKernelFunc('GetVersionExW'); @@ -1661,10 +3009,19 @@ procedure InitPlatformIdEx; GetSystemInfoFn(SI); // Get processor architecture InternalProcessorArchitecture := SI.wProcessorArchitecture; + // Store revision number + Win32RevisionNumber := InternalRevisionNumber; end; { TPJOSInfo } +class function TPJOSInfo.BuildBranch: string; +begin + Result := GetRegistryString( + HKEY_LOCAL_MACHINE, CurrentVersionRegKeys[IsWinNT], 'BuildBranch' + ); +end; + class function TPJOSInfo.BuildNumber: Integer; begin Result := InternalBuildNumber; @@ -1682,11 +3039,15 @@ class function TPJOSInfo.CheckSuite(const Suite: Integer): Boolean; class function TPJOSInfo.Description: string; - // Adds a non-empty string to end of result, preceded by space. - procedure AppendToResult(const Str: string); + // Adds a non-empty string to end of result, optionally preceded by space. + procedure AppendToResult(const Str: string; const WantSpace: Boolean = True); begin if Str <> '' then - Result := Result + ' ' + Str; + begin + if WantSpace then + Result := Result + ' '; + Result := Result + Str; + end; end; begin @@ -1702,15 +3063,22 @@ class function TPJOSInfo.Description: string; // For NT3/4 append version number after product AppendToResult(Format('%d.%d', [MajorVersion, MinorVersion])); AppendToResult(Edition); - AppendToResult(ServicePack); // does nothing if no service pack + AppendToResult(ServicePackEx); // does nothing if no service pack AppendToResult(Format('(Build %d)', [BuildNumber])); end else begin // Windows 2000 and later: don't include version number AppendToResult(Edition); - AppendToResult(ServicePack); // does nothing if no service pack - AppendToResult(Format('(Build %d)', [BuildNumber])); + if (ServicePackEx <> '') then + AppendToResult(', ' + ServicePackEx, False); + if InternalRevisionNumber > 0 then + AppendToResult( + Format(', Build %d.%d', [BuildNumber, InternalRevisionNumber]), + False + ) + else + AppendToResult(Format(', Build %d', [BuildNumber]), False); end; end; ospWin9x: @@ -1719,6 +3087,13 @@ class function TPJOSInfo.Description: string; end; end; +class function TPJOSInfo.DigitalProductID: TBytes; +begin + Result := GetRegistryBytes( + HKEY_LOCAL_MACHINE, CurrentVersionRegKeys[IsWinNT], 'DigitalProductId' + ); +end; + class function TPJOSInfo.Edition: string; begin // This method is based on sample C++ code from MSDN @@ -1728,12 +3103,16 @@ class function TPJOSInfo.Edition: string; osWin7, osWinSvr2008R2, osWin8, osWinSvr2012, osWin8Point1, osWinSvr2012R2, - osWin10, osWin10Svr: + osWin10, osWin11, osWin10Svr, osWinSvr2019, osWinSvr2022, osWinServer: begin // For v6.0 and later we ignore the suite mask and use the new // PRODUCT_ flags from the GetProductInfo() function to determine the // edition + // 1st try to find edition name from lookup table Result := EditionFromProductInfo; + if Result = '' then + // no matching entry in lookup: get from registry + Result := EditionIDFromReg; // append 64-bit if 64 bit system if InternalProcessorArchitecture = PROCESSOR_ARCHITECTURE_AMD64 then Result := Result + ' (64-bit)'; @@ -1743,8 +3122,8 @@ class function TPJOSInfo.Edition: string; osWinSvr2003, osWinSvr2003R2: begin // We check different processor architectures and act accordingly - // This code closely based on MS's sample code found at - // http://msdn2.microsoft.com/en-us/library/ms724429 + // This code closely based on sample code by Microsoft that is no longer + // available if InternalProcessorArchitecture = PROCESSOR_ARCHITECTURE_IA64 then begin if CheckSuite(VER_SUITE_DATACENTER) then @@ -1837,7 +3216,7 @@ class function TPJOSInfo.Edition: string; end else // NT before SP6: we read required info from registry - Result := EditionFromReg; + Result := NTEditionFromReg; end; end; end; @@ -1857,19 +3236,10 @@ class function TPJOSInfo.EditionFromProductInfo: string; end; end; -class function TPJOSInfo.EditionFromReg: string; -var - EditionCode: string; // OS product edition code stored in registry +class function TPJOSInfo.EditionIDFromReg: string; begin - EditionCode := ProductTypeFromReg; - if CompareText(EditionCode, 'WINNT') = 0 then - Result := 'WorkStation' - else if CompareText(EditionCode, 'LANMANNT') = 0 then - Result := 'Server' - else if CompareText(EditionCode, 'SERVERNT') = 0 then - Result := 'Advanced Server'; - Result := Result + Format( - ' %d.%d', [InternalMajorVersion, InternalMinorVersion] + Result := GetRegistryString( + HKEY_LOCAL_MACHINE, CurrentVersionRegKeys[IsWinNT], 'EditionID' ); end; @@ -2000,8 +3370,8 @@ class function TPJOSInfo.IsReallyWindows10OrGreater: Boolean; class function TPJOSInfo.IsReallyWindowsVersionOrGreater(MajorVersion, MinorVersion, ServicePackMajor: Word): Boolean; begin - Assert(MajorVersion >= HiByte(_WIN32_WINNT_WIN2K)); - if Assigned(VerSetConditionMask) and Assigned(VerifyVersionInfo) then + if (MajorVersion >= HiByte(_WIN32_WINNT_WIN2K)) + and Assigned(VerSetConditionMask) and Assigned(VerifyVersionInfo) then Result := TestWindowsVersion( MajorVersion, MinorVersion, ServicePackMajor, 0, VER_GREATER_EQUAL ) @@ -2092,6 +3462,29 @@ class function TPJOSInfo.IsWin9x: Boolean; Result := Platform = ospWin9x; end; +class function TPJOSInfo.IsWindows10PlusVersionOrLater( + const AVersion: TPJWin10PlusVersion): Boolean; +begin + Assert(not (AVersion in [win10plusNA, win10plusUnknown])); + Result := IsReallyWindows10OrGreater and (Windows10PlusVersion >= AVersion); +end; + +class function TPJOSInfo.IsWindows10VersionOrLater( + const AVersion: TPJWin10PlusVersion): Boolean; +begin + if not (AVersion in Win10_Versions) then + raise EPJSysInfo.Create('Invalid Windows 10 version: can''t compare'); + Result := IsWindows10PlusVersionOrLater(AVersion); +end; + +class function TPJOSInfo.IsWindows11VersionOrLater( + const AVersion: TPJWin10PlusVersion): Boolean; +begin + if not (AVersion in Win11_Versions) then + raise EPJSysInfo.Create('Invalid Windows 11 version: can''t compare'); + Result := IsWindows10PlusVersionOrLater(AVersion); +end; + class function TPJOSInfo.IsWindowsServer: Boolean; var OSVI: TOSVersionInfoEx; @@ -2149,6 +3542,22 @@ class function TPJOSInfo.MinorVersion: Integer; Result := InternalMinorVersion; end; +class function TPJOSInfo.NTEditionFromReg: string; +var + EditionCode: string; // OS product edition code stored in registry +begin + EditionCode := ProductTypeFromReg; + if CompareText(EditionCode, 'WINNT') = 0 then + Result := 'WorkStation' + else if CompareText(EditionCode, 'LANMANNT') = 0 then + Result := 'Server' + else if CompareText(EditionCode, 'SERVERNT') = 0 then + Result := 'Advanced Server'; + Result := Result + Format( + ' %d.%d', [InternalMajorVersion, InternalMinorVersion] + ); +end; + class function TPJOSInfo.Platform: TPJOSPlatform; begin case InternalPlatform of @@ -2235,13 +3644,24 @@ class function TPJOSInfo.Product: TPJOSProduct; 3: // NOTE: Version 6.3 may only be reported by Windows if the // application is "manifested" for Windows 8.1. See - // http://bit.ly/MJSO8Q. Getting the OS via VerifyVersionInfo - // instead of GetVersion or GetVersionEx should work round this - // for Windows 8.1 (i.e. version 6.3). + // https://tinyurl.com/2s384ha4. Getting the OS via + // VerifyVersionInfo instead of GetVersion or GetVersionEx should + // work round this for Windows 8.1 (i.e. version 6.3). if not IsServer then Result := osWin8Point1 else Result := osWinSvr2012R2; + 4: + if IsServer then + // Version 6.4 was used for Windows 2016 server tech preview 1. + // This version *may* only be detected by Windows if the + // application is "manifested" for the correct Windows version. + // See https://bit.ly/MJSO8Q. + Result := osWin10Svr + // Version 6.4 was also used for some early Windows 10 preview + // builds, but they have all expired so detection has been + // removed. + // See https://tinyurl.com/3c8e3hsc else // Higher minor version: must be an unknown later OS Result := osWinLater @@ -2251,16 +3671,37 @@ class function TPJOSInfo.Product: TPJOSProduct; begin // NOTE: Version 10 and later may only be reported by Windows if the // application is "manifested" for the correct Windows version. See - // http://bit.ly/MJSO8Q. Previously, getting the OS from + // https://bit.ly/MJSO8Q. Previously, getting the OS from // VerifyVersionInfo instead of GetVersion or GetVersionEx worked // round this, but MS deprecated this in Windows 10, reverting // VerifyVersionInfo to work like GetVersion. WHY????!!!! case InternalMinorVersion of 0: if not IsServer then - Result := osWin10 + begin + if TestBuildNumber(VER_LESS, Win11_First_Build) then + Result := osWin10 + else + // ** As of 2021-10-05 Win 11 is reporting version 10.0! + Result := osWin11; + end else - Result := osWin10Svr; + begin + if TestBuildNumber( + VER_LESS_EQUAL, Win2016_Last_Build + ) then + Result := osWin10Svr + else if TestBuildNumber( + VER_LESS_EQUAL, Win2019_Last_Build + ) then + Result := osWinSvr2019 + else if TestBuildNumber( + VER_LESS_EQUAL, WinServer_Last_Build + ) then + Result := osWinServer + else + Result := osWinSvr2022; + end; end; end; else @@ -2284,7 +3725,7 @@ class function TPJOSInfo.ProductID: string; class function TPJOSInfo.ProductName: string; begin case Product of - osUnknownWinNT, osUnknownWin9x, osUnknownWin32s: Result := ''; + osUnknown, osUnknownWinNT, osUnknownWin9x, osUnknownWin32s: Result := ''; osWinNT: Result := 'Windows NT'; osWin2K: Result := 'Windows 2000'; osWinXP: Result := 'Windows XP'; @@ -2305,8 +3746,11 @@ class function TPJOSInfo.ProductName: string; osWin8Point1: Result := 'Windows 8.1'; osWinSvr2012R2: Result := 'Windows Server 2012 R2'; osWin10: Result := 'Windows 10'; - // TODO: Update osWin10Svr description once OS is released and named - osWin10Svr: Result := 'Windows Server Technical Preview'; + osWin10Svr: Result := 'Windows Server 2016'; + osWinSvr2019: Result := 'Windows Server 2019'; + osWin11: Result := 'Windows 11'; + osWinSvr2022: Result := 'Windows Server 2022'; + osWinServer: Result := 'Windows Server'; else raise EPJSysInfo.Create(sUnknownProduct); end; @@ -2335,9 +3779,14 @@ class function TPJOSInfo.RegisteredOwner: string; ); end; +class function TPJOSInfo.RevisionNumber: Integer; +begin + Result := InternalRevisionNumber; +end; + class function TPJOSInfo.ServicePack: string; begin - // Assume to service pack + // Assume no service pack Result := ''; case Platform of ospWin9x: @@ -2390,6 +3839,29 @@ class function TPJOSInfo.ServicePackMinor: Integer; Result := Win32ServicePackMinor; end; +class function TPJOSInfo.Windows10PlusVersion: TPJWin10PlusVersion; +begin + Result := InternalWin1011Version; +end; + +class function TPJOSInfo.Windows10PlusVersionName: string; +const + cVersions: array[TPJWin10PlusVersion] of string = ( + // Not windows 10+ + '', + // Windows 10+ with unknown version string + 'Unknown', + // Windows 10 + '1507', '1511', '1607', '1703', '1709', + '1803', '1809', '1903', '1909', '2004', + '20H2', '21H1', '21H2', '22H2', + // Windows 11 + '21H2', '22H2', '23H2', '24H2' + ); +begin + Result := cVersions[Windows10PlusVersion]; +end; + { TPJComputerInfo } class function TPJComputerInfo.BiosVendor: string; @@ -2518,8 +3990,7 @@ class function TPJComputerInfo.IsUACActive: Boolean; class function TPJComputerInfo.MACAddress: string; type - // Based on code at MSDN knowledge base Q118623 article at - // http://support.microsoft.com/kb/q118623/} + // Based on former MSDN knowledge base article Q118623. // According to MSDN this method should fail on Windows 6.0 (Vista) and later. // It has been known to fail on Vista, but works on Vista Home Premium SP1! // It would seem that the call will succeed if there's an active network with @@ -2576,18 +4047,17 @@ class function TPJComputerInfo.MACAddress: string; if NetBiosSucceeded(Netbios(@Ncb)) then begin // we have a MAC address: return it - with Adapter.Adapt do - Result := Format( - '%.2x-%.2x-%.2x-%.2x-%.2x-%.2x', - [ - Ord(adapter_address[0]), - Ord(adapter_address[1]), - Ord(adapter_address[2]), - Ord(adapter_address[3]), - Ord(adapter_address[4]), - Ord(adapter_address[5]) - ] - ); + Result := Format( + '%.2x-%.2x-%.2x-%.2x-%.2x-%.2x', + [ + Ord(Adapter.Adapt.adapter_address[0]), + Ord(Adapter.Adapt.adapter_address[1]), + Ord(Adapter.Adapt.adapter_address[2]), + Ord(Adapter.Adapt.adapter_address[3]), + Ord(Adapter.Adapt.adapter_address[4]), + Ord(Adapter.Adapt.adapter_address[5]) + ] + ); Exit; end; end; @@ -2629,6 +4099,17 @@ class function TPJComputerInfo.ProcessorName: string; ); end; +class function TPJComputerInfo.ProcessorSpeedMHz: Cardinal; +begin + Result := Cardinal( + GetRegistryInt( + HKEY_LOCAL_MACHINE, + 'HARDWARE\DESCRIPTION\System\CentralProcessor\0\', + '~MHz' + ) + ); +end; + class function TPJComputerInfo.SystemManufacturer: string; begin Result := GetRegistryString( diff --git a/Src/3rdParty/PJVersionInfo.pas b/Src/3rdParty/PJVersionInfo.pas index 6f9b22f63..d4e0eb8a4 100644 --- a/Src/3rdParty/PJVersionInfo.pas +++ b/Src/3rdParty/PJVersionInfo.pas @@ -1,12 +1,12 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 1998-2013, Peter Johnson (www.delphidabbler.com). + * Copyright (C) 1998-2014, Peter Johnson (www.delphidabbler.com). * - * $Rev$ - * $Date$ + * $Rev: 1515 $ + * $Date: 2014-01-11 02:36:28 +0000 (Sat, 11 Jan 2014) $ * * Version Information Component. The component reads version information from * executable files. @@ -35,14 +35,17 @@ {$ENDIF} // Switch off unsafe code warnings if switch supported {$IFDEF CONDITIONALEXPRESSIONS} - {$IF CompilerVersion >= 15.0} // >= Delphi 7 - {$WARN UNSAFE_CODE OFF} + {$IF CompilerVersion >= 24.0} // Delphi XE3 and later + {$LEGACYIFEND ON} // NOTE: this must come before all $IFEND directives + {$IFEND} + {$IF CompilerVersion >= 23.0} // Delphi XE2 and later + {$DEFINE Supports_RTLNameSpaces} {$IFEND} - {$IF CompilerVersion >= 18.0} // >= Delphi 2006 + {$IF CompilerVersion >= 18.0} // Delphi 2006 and later {$DEFINE Supports_AdvancedRecords} {$IFEND} - {$IF CompilerVersion >= 23.0} // Delphi XE2 - {$DEFINE Supports_RTLNameSpaces} + {$IF CompilerVersion >= 15.0} // Delphi 7 and later + {$WARN UNSAFE_CODE OFF} {$IFEND} {$ENDIF} diff --git a/Src/3rdParty/PJWdwState.pas b/Src/3rdParty/PJWdwState.pas index dd3c8092f..a99ee5bc1 100644 --- a/Src/3rdParty/PJWdwState.pas +++ b/Src/3rdParty/PJWdwState.pas @@ -1,12 +1,12 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 1999-2013, Peter Johnson (www.delphidabbler.com). + * Copyright (C) 1999-2014, Peter Johnson (www.delphidabbler.com). * - * $Rev$ - * $Date$ + * $Rev: 1966 $ + * $Date: 2014-10-28 01:20:04 +0000 (Tue, 28 Oct 2014) $ * * DelphiDabbler Window state components. } @@ -15,11 +15,14 @@ unit PJWdwState; // Conditional defines -// Note: Delphi 1/2 not included since code will not compile on these compilers +// Note: There is no version checking for Delphi 1 and 2 not since this unit +// will not compile with those compilers. {$DEFINE WarnDirs} // $WARN compiler directives available {$DEFINE RegAccessFlags} // TRegistry access flags available +{$DEFINE RequiresFileCtrl} // FileCtrl unit is required for ForceDirectories {$UNDEF RTLNameSpaces} // Don't qualify RTL units names with namespaces {$UNDEF TScrollStyleMoved} // TScrollStyle hasn't moved to System.UITypes units +{$UNDEF SupportsPathDelim} // PathDelim and related routine not defined {$IFDEF VER100} // Delphi 3 {$UNDEF WarnDirs} {$UNDEF RegAccessFlags} @@ -32,15 +35,18 @@ {$UNDEF WarnDirs} {$UNDEF RegAccessFlags} {$ENDIF} -{$IFDEF VER140} // Delphi 6 - {$UNDEF WarnDirs} -{$ENDIF} {$IFDEF CONDITIONALEXPRESSIONS} - {$IF CompilerVersion >= 23.0} // Delphi XE2 + {$IF CompilerVersion >= 24.0} // Delphi XE3 and later + {$LEGACYIFEND ON} // NOTE: this must come before all $IFEND directives + {$DEFINE TScrollStyleMoved} + {$IFEND} + {$IF CompilerVersion >= 23.0} // Delphi XE2 and later {$DEFINE RTLNameSpaces} {$IFEND} - {$IF CompilerVersion >= 24.0} // Delphi XE3 - {$DEFINE TScrollStyleMoved} + {$IF CompilerVersion >= 14.0} // Delphi 6 and later + {$DEFINE SupportsPathDelim} + {$UNDEF WarnDirs} + {$UNDEF RequiresFileCtrl} {$IFEND} {$ENDIF} @@ -54,7 +60,11 @@ interface System.Classes, Vcl.Controls, Winapi.Messages, Winapi.Windows, Vcl.Forms, System.SysUtils, System.Win.Registry; {$ELSE} - Classes, Controls, Messages, Windows, Forms, SysUtils, Registry; + Classes, Controls, Messages, Windows, Forms, SysUtils, Registry + {$IFDEF RequiresFileCtrl} + , FileCtrl // needed for ForceDirectories since it's not in SysUtils yet. + {$ENDIF} + ; {$ENDIF} @@ -65,6 +75,7 @@ interface // instructs MDI child components they can restore their windows PJM_RESTOREMDICHILD = WM_USER + 1; + type TPJCustomWdwState = class; @@ -637,15 +648,42 @@ TPJWdwState = class(TPJCustomWdwState) { TPJWdwStateGetRegData: Type of event that is triggered just before registry is accessed. It allows - handler to change the registry HKEY and sub key to be used. - @param RootKey [in/out] Registry root key. Default value passed in. May be - changed in event handler. + handler to change the registry root key and sub key to be used. + @param RootKey [in/out] Registry root key. Default HKEY value passed in. + May be changed in event handler. @param SubKey [in/out] Registry sub key. Default value passed in. May be changed in event handler. } TPJWdwStateGetRegData = procedure(var RootKey: HKEY; var SubKey: string) of object; + {TPJRegRootKey: + Enumeration of values that represent the registry root keys supported by + TPJRegWdwState. Each value represents and maps to the similarly named + HKEY_* constant, as shown in the comments. + } + TPJRegRootKey = ( + hkClassesRoot, // HKEY_CLASSES_ROOT + hkCurrentUser, // HKEY_CURRENT_USER + hkLocalMachine, // HKEY_LOCAL_MACHINE + hkUsers, // HKEY_USERS + hkPerformanceData, // HKEY_PERFORMANCE_DATA + hkCurrentConfig, // HKEY_CURRENT_CONFIG + hkDynData // HKEY_DYN_DATA + ); + + { + TPJWdwStateGetRegDataEx: + Type of event that is triggered just before registry is accessed. It allows + handler to change the registry root key and sub key to be used. + @param RootKeyEx [in/out] Registry root key. Default TPJRegRootKey value + passed in. May be changed in event handler. + @param SubKey [in/out] Registry sub key. Default value passed in. May be + changed in event handler. + } + TPJWdwStateGetRegDataEx = procedure(var RootKeyEx: TPJRegRootKey; + var SubKey: string) of object; + { TPJWdwStateRegAccessEvent: Type of event that is triggered after registry is opened, ready for access. @@ -668,23 +706,34 @@ TPJWdwState = class(TPJCustomWdwState) } TPJRegWdwState = class(TPJCustomWdwState) private // properties - fRootKey: HKEY; - {Value of RootKey property} + fRootKeyEx: TPJRegRootKey; + {Value of RootKeyEx property} fSubKey: string; {Value of SubKey property} fOnGetRegData: TPJWdwStateGetRegData; {Event handler for OnGetRegData event} + fOnGetRegDataEx: TPJWdwStateGetRegDataEx; + {Event handler for OnGetRegDataEx event} fOnGettingRegData: TPJWdwStateRegAccessEvent; // Added by BJM {Event handler for OnGettingRegData event} fOnPuttingRegData: TPJWdwStateRegAccessEvent; // Added by BJM {Event handler for OnPuttingRegData event} + function GetRootKey: HKEY; + {Read accessor for RootKey property. + @return Required property value. + } + procedure SetRootKey(const Value: HKEY); + {Write accessor for RootKey property. + @param Value [in] New property value. + @exception ERangeError raised if value is not a recognised HKEY_* value. + } procedure SetSubKey(const Value: string); {Write accessor method for SubKey property. @param Value [in] New property value. If Value='' then the property is set to \Software\<App File Name>\Window\<Form Name>. } protected - procedure GetRegInfo(var ARootKey: HKEY; var ASubKey: string); + procedure GetRegInfo(var ARootKey: TPJRegRootKey; var ASubKey: string); {Triggers OnGetRegData event to get registry root key and sub key to be used when restoring / saving window state. @param ARootKey [in/out] Required root key value. Set to value of @@ -729,9 +778,17 @@ TPJRegWdwState = class(TPJCustomWdwState) // Published inherited property property OnReadWdwState; // New properties - property RootKey: HKEY read fRootKey write fRootKey + property RootKey: HKEY read GetRootKey write SetRootKey default HKEY_CURRENT_USER; - {Registry root key to use. Must be set to a valid HKEY value} + {Registry root key to use. Must be set to a valid HKEY value. Setting this + property also sets RootKeyEx to a corresponding value} + property RootKeyEx: TPJRegRootKey read fRootKeyEx write fRootKeyEx + stored False default hkCurrentUser; + {Registry root key to use as specified by a value from the TPJRegRootKey + enumeration. Setting this property also sets RootKey to a corresponding + value. + NOTE: This property is provided to make it easier to set root keys at + design time to avoid remembering the root key value as an integer} property SubKey: string read fSubKey write SetSubKey; {The sub-key below root key where window state is to be stored. If set to empty string the value of '/Software/<Program Name>/Window/<Form Name>' @@ -739,9 +796,17 @@ TPJRegWdwState = class(TPJCustomWdwState) property OnGetRegData: TPJWdwStateGetRegData read fOnGetRegData write fOnGetRegData; {Event triggered just before registry is read when restoring and saving - window state. Allows handler to change registry HKEY and subkey to be used - to store window state. If this event is handled then RootKey and SubKey - properties are ignored} + window state. Allows handler to change root key and subkey to be used to + store window state. Root key is specified via its HKEY value. If this + event is handled then RootKey, RootKeyEx and SubKey properties are all + ignored} + property OnGetRegDataEx: TPJWdwStateGetRegDataEx + read fOnGetRegDataEx write fOnGetRegDataEx; + {Event triggered just before registry is read when restoring and saving + window state. Allows handler to change root key and subkey to be used to + store window state. Root key is specified via its TPJRegRootKey value. If + this event is handled then RootKey, RootKeyEx and SubKey properties are + all ignored} property OnGettingRegData: TPJWdwStateRegAccessEvent // Added by BJM read fOnGettingRegData write fOnGettingRegData; {Event triggered when component is reading window state data from @@ -786,6 +851,26 @@ procedure Register; ); end; +{$IFNDEF SupportsPathDelim} +// Definitions used for versions of Delphi that don't implement the following +// constant and function in SysUtils. + +const + // File path delimiter + PathDelim = '/'; + +// Ensures that given directory or path ends with exactly one path delimiter. +function IncludeTrailingPathDelimiter(const PathOrDir: string): string; +begin + Result := PathOrDir; + // remove all trailing path delimiters if any, to get rid of any duplicates + while (Result <> '') and (Result[Length(Result)] = PathDelim) do + Result := Copy(Result, 1, Length(Result) - 1); + // add a single trailing delimiter + Result := Result + PathDelim; +end; +{$ENDIF} + { TPJWdwStateHook } procedure TPJWdwStateHook.CMShowingChanged(var Msg: TMessage); @@ -1647,6 +1732,43 @@ procedure TPJWdwState.SaveWdwState(const Left, Top, Width, Height, { TPJRegWdwState } +resourcestring + // Error messages + sErrBadHKEY = '%d is not a valid HKEY value.'; + +const + // Map of supported HKEY_ constants onto corresponding TPJRegRootKey values. + RegRootKeyMap: array[TPJRegRootKey] of HKEY = ( + HKEY_CLASSES_ROOT, // hkClassesRoot + HKEY_CURRENT_USER, // hkCurrentUser + HKEY_LOCAL_MACHINE, // hkLocalMachine + HKEY_USERS, // hkUsers + HKEY_PERFORMANCE_DATA, // hkPerformanceData + HKEY_CURRENT_CONFIG, // hkCurrentConfig + HKEY_DYN_DATA // hkDynData + ); + +function TryHKEYToCode(const RootKey: HKEY; var Value: TPJRegRootKey): Boolean; + {Attempts to convert a HKEY value into the corresponding TPJRegRootKey value. + @param RootKey [in] HKEY value to convert. + @param Value [in/out] Set to TPJRegRootKey value corresponding to RootKey. + Value is undefined if RootKey has no corresponding TPJRegRootKey value. + @return True if RootKey is valid and has corresponding TPJRegRootKey value + or False of not. + } +var + Code: TPJRegRootKey; +begin + Result := True; + for Code := Low(TPJRegRootKey) to High(TPJRegRootKey) do + if RegRootKeyMap[Code] = RootKey then + begin + Value := Code; + Exit; + end; + Result := False; +end; + function ReadRegInt(const Reg: TRegistry; const AName: string; const ADefault: Integer): Integer; {Reads integer value from current sub key in registry, using a default value @@ -1709,26 +1831,45 @@ constructor TPJRegWdwState.Create(AOwner: TComponent); } begin inherited Create(AOwner); - fRootKey := HKEY_CURRENT_USER; + fRootKeyEx := hkCurrentUser; SetSubKey(''); end; -procedure TPJRegWdwState.GetRegInfo(var ARootKey: HKEY; +procedure TPJRegWdwState.GetRegInfo(var ARootKey: TPJRegRootKey; var ASubKey: string); - {Triggers OnGetRegData event to get registry root key and sub key to be used - when restoring / saving window state. + {Triggers the OnGetRegDateEx event or, if that is not assigned, the + OnGetRegData event, to get registry root key and sub key to be used when + restoring / saving window state. @param ARootKey [in/out] Required root key value. Set to value of RootKey property by default. May be changed in event handler. @param ASubKey [in/ou] Required sub key. Set to value of SubKey property when called. May be changed in event handler. } +var + RootHKey: HKEY; // used to get root key via its HKEY value begin - // Use RootKey and SubKey property values by default - ARootKey := RootKey; + // Use RootKeyEx and SubKey property values by default + ARootKey := RootKeyEx; ASubKey := SubKey; - // Allow user to change these by handling OnGetRegData event - if Assigned(fOnGetRegData) then - fOnGetRegData(ARootKey, ASubKey); + // Allow user to change these by handling either OnGetRegDataEx or + // OnGetRegData event + if Assigned(fOnGetRegDataEx) then + fOnGetRegDataEx(ARootKey, ASubKey) + else if Assigned(fOnGetRegData) then + begin + RootHKey := RegRootKeyMap[ARootKey]; + fOnGetRegData(RootHKey, ASubKey); + if not TryHKEYToCode(RootHKey, ARootKey) then + raise ERangeError.CreateFmt(sErrBadHKEY, [RootHKey]); + end; +end; + +function TPJRegWdwState.GetRootKey: HKEY; + {Read accessor for RootKey property. + @return Required property value. + } +begin + Result := RegRootKeyMap[fRootKeyEx]; end; procedure TPJRegWdwState.ReadWdwState(var Left, Top, Width, Height, @@ -1747,16 +1888,16 @@ procedure TPJRegWdwState.ReadWdwState(var Left, Top, Width, Height, value is the ordinal value of a TWindowState value. } var - Reg: TRegistry; // instance of registry object used to read info - ARootKey: HKEY; // registry root key where window state is stored - ASubKey: string; // sub key of registry from which to read window state + Reg: TRegistry; // instance of registry object used to read info + ARootKey: TPJRegRootKey; // registry root key where window state is stored + ASubKey: string; // registry sub key from which to read window state begin // Get registry keys from which to read window state GetRegInfo(ARootKey, ASubKey); // Open registry at required key Reg := SafeCreateReg; try - Reg.RootKey := ARootKey; + Reg.RootKey := RegRootKeyMap[ARootKey]; if Reg.OpenKey(ASubKey, False) then begin // Read position, size and state of window @@ -1785,16 +1926,16 @@ procedure TPJRegWdwState.SaveWdwState(const Left, Top, Width, Height, value of a TWindowState value. } var - Reg: TRegistry; // instance of registry object class used to write info - ARootKey: HKEY; // registry root key where window state is stored - ASubKey: string; // sub key of registry in which to save window state + Reg: TRegistry; // instance of registry object used to write info + ARootKey: TPJRegRootKey; // registry root key where window state is stored + ASubKey: string; // sub key of registry in which to save window state begin // Get registry keys in which to save window state GetRegInfo(ARootKey, ASubKey); // Open registry at required key Reg := SafeCreateReg; try - Reg.RootKey := ARootKey; + Reg.RootKey := RegRootKeyMap[ARootKey]; if Reg.OpenKey(ASubKey, True) then begin // Write window size, position and state from registry @@ -1812,6 +1953,19 @@ procedure TPJRegWdwState.SaveWdwState(const Left, Top, Width, Height, end; end; +procedure TPJRegWdwState.SetRootKey(const Value: HKEY); + {Write accessor for RootKey property. + @param Value [in] New property value. + @exception ERangeError raised if value is not a recognised HKEY_* value. + } +begin + if not TryHKEYToCode(Value, fRootKeyEx) then + begin + fRootKeyEx := hkCurrentUser; + raise ERangeError.CreateFmt(sErrBadHKEY, [Value]); + end; +end; + procedure TPJRegWdwState.SetSubKey(const Value: string); {Write accessor method for SubKey property. @param Value [in] New property value. If Value='' then the property is set diff --git a/Src/3rdParty/UEncrypt.pas b/Src/3rdParty/UEncrypt.pas deleted file mode 100644 index dab3f3f0e..000000000 --- a/Src/3rdParty/UEncrypt.pas +++ /dev/null @@ -1,158 +0,0 @@ -{ - * Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - * - * Sourced from: http://www.swissdelphicenter.ch/torry/showcode.php?id=1243 - * Author: Steve Schafer - * Homepage: http://www.teamb.com - * - * Added to CodeSnip project SVN Repo on 11 Aug 2009 at r165 and since modified. - * See logs in SVN repository for details. - * - * $Rev$ - * $Date$ -} - -unit UEncrypt; - -interface - -function Decrypt(const S: RawByteString; Key: Word): RawByteString; -function Encrypt(const S: RawByteString; Key: Word): RawByteString; - -implementation - -const - C1 = 52845; - C2 = 22719; - -function Decode(const S: RawByteString): RawByteString; -const - Map: array[AnsiChar] of Byte = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, 63, 52, 53, - 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, - 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, - 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, - 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, - 46, 47, 48, 49, 50, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0); -var - I: LongInt; -begin - case Length(S) of - 2: - begin - I := Map[S[1]] + (Map[S[2]] shl 6); - SetLength(Result, 1); - Move(I, Result[1], Length(Result)) - end; - 3: - begin - I := Map[S[1]] + (Map[S[2]] shl 6) + (Map[S[3]] shl 12); - SetLength(Result, 2); - Move(I, Result[1], Length(Result)) - end; - 4: - begin - I := Map[S[1]] + (Map[S[2]] shl 6) + (Map[S[3]] shl 12) + - (Map[S[4]] shl 18); - SetLength(Result, 3); - Move(I, Result[1], Length(Result)) - end - end -end; - -function PreProcess(const S: RawByteString): RawByteString; -var - SS: RawByteString; -begin - SS := S; - Result := ''; - while SS <> '' do - begin - Result := Result + Decode(Copy(SS, 1, 4)); - Delete(SS, 1, 4) - end -end; - -function InternalDecrypt(const S: RawByteString; Key: Word): RawByteString; -var - I: Word; - Seed: Word; -begin - Result := S; - Seed := Key; - for I := 1 to Length(Result) do - begin - Result[I] := AnsiChar(Byte(Result[I]) xor (Seed shr 8)); - Seed := (Byte(S[I]) + Seed) * Word(C1) + Word(C2) - end -end; - -function Decrypt(const S: RawByteString; Key: Word): RawByteString; -begin - Result := InternalDecrypt(PreProcess(S), Key) -end; - -function Encode(const S: RawByteString): RawByteString; -const - Map: array[0..63] of AnsiChar = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + - 'abcdefghijklmnopqrstuvwxyz0123456789+/'; -var - I: LongInt; -begin - I := 0; - Move(S[1], I, Length(S)); - case Length(S) of - 1: - Result := Map[I mod 64] + Map[(I shr 6) mod 64]; - 2: - Result := Map[I mod 64] + Map[(I shr 6) mod 64] + - Map[(I shr 12) mod 64]; - 3: - Result := Map[I mod 64] + Map[(I shr 6) mod 64] + - Map[(I shr 12) mod 64] + Map[(I shr 18) mod 64] - end -end; - -function PostProcess(const S: RawByteString): RawByteString; -var - SS: RawByteString; -begin - SS := S; - Result := ''; - while SS <> '' do - begin - Result := Result + Encode(Copy(SS, 1, 3)); - Delete(SS, 1, 3) - end -end; - -function InternalEncrypt(const S: RawByteString; Key: Word): RawByteString; -var - I: Word; - Seed: Word; -begin - Result := S; - Seed := Key; - for I := 1 to Length(Result) do - begin - Result[I] := AnsiChar(Byte(Result[I]) xor (Seed shr 8)); - Seed := (Byte(Result[I]) + Seed) * Word(C1) + Word(C2) - end -end; - -function Encrypt(const S: RawByteString; Key: Word): RawByteString; -begin - Result := PostProcess(InternalEncrypt(S, Key)) -end; - -end. - diff --git a/Src/ActiveText.UHTMLRenderer.pas b/Src/ActiveText.UHTMLRenderer.pas index a000d69f6..14ad5a3bb 100644 --- a/Src/ActiveText.UHTMLRenderer.pas +++ b/Src/ActiveText.UHTMLRenderer.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2009-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2009-2025, Peter Johnson (gravatar.com/delphidabbler). * * Provides a class that renders active text as HTML. } @@ -20,9 +17,9 @@ interface uses // Delphi - SysUtils, Graphics, Generics.Collections, + SysUtils, // Project - ActiveText.UMain, UBaseObjects, UCSSBuilder, UHTMLUtils; + ActiveText.UMain, UHTMLUtils; type @@ -50,7 +47,6 @@ TTagInfo = class(TObject) TCSSStyles = class(TObject) strict private var - fWrapperClass: string; fElemClassMap: array[TActiveTextActionElemKind] of string; procedure SetElemClass(ElemKind: TActiveTextActionElemKind; const Value: string); inline; @@ -58,7 +54,6 @@ TCSSStyles = class(TObject) inline; public constructor Create; - property WrapperClass: string read fWrapperClass write fWrapperClass; property ElemClasses[Kind: TActiveTextActionElemKind]: string read GetElemClass write SetElemClass; end; @@ -66,40 +61,45 @@ TCSSStyles = class(TObject) var fCSSStyles: TCSSStyles; fBuilder: TStringBuilder; - fInBlock: Boolean; + fLevel: Integer; fTagInfoMap: TTagInfoMap; + fIsStartOfTextLine: Boolean; + fLINestingDepth: Cardinal; + fTagGen: THTMLClass; + const + IndentMult = 2; procedure InitialiseTagInfoMap; - procedure InitialiseRender; - procedure RenderTextElem(Elem: IActiveTextTextElem); - procedure RenderBlockActionElem(Elem: IActiveTextActionElem); - procedure RenderInlineActionElem(Elem: IActiveTextActionElem); - procedure FinaliseRender; + function RenderTag(const TagElem: IActiveTextActionElem): string; + function RenderText(const TextElem: IActiveTextTextElem): string; function MakeOpeningTag(const Elem: IActiveTextActionElem): string; function MakeClosingTag(const Elem: IActiveTextActionElem): string; public - constructor Create; + constructor Create(const ATagGenerator: THTMLClass = nil); destructor Destroy; override; function Render(ActiveText: IActiveText): string; - property Styles: TCSSStyles read fCSSStyles; end; implementation - uses - // Project - UColours, UCSSUtils, UFontHelper, UIStringList; + UConsts, UIStringList, UStrUtils; { TActiveTextHTML } -constructor TActiveTextHTML.Create; +constructor TActiveTextHTML.Create(const ATagGenerator: THTMLClass); begin inherited Create; fCSSStyles := TCSSStyles.Create; fBuilder := TStringBuilder.Create; + fLINestingDepth := 0; InitialiseTagInfoMap; + if not Assigned(ATagGenerator) then + // default behaviour before ATagGenerator parameter was added + fTagGen := TXHTML + else + fTagGen := ATagGenerator; end; destructor TActiveTextHTML.Destroy; @@ -113,22 +113,6 @@ destructor TActiveTextHTML.Destroy; inherited; end; -procedure TActiveTextHTML.FinaliseRender; -begin - fBuilder.AppendLine(THTML.ClosingTag('div')); -end; - -procedure TActiveTextHTML.InitialiseRender; -var - WrapperClassAttr: IHTMLAttributes; -begin - if fCSSStyles.WrapperClass <> '' then - WrapperClassAttr := THTMLAttributes.Create('class', fCSSStyles.WrapperClass) - else - WrapperClassAttr := nil; - fBuilder.AppendLine(THTML.OpeningTag('div', WrapperClassAttr)); -end; - procedure TActiveTextHTML.InitialiseTagInfoMap; var NullAttrs: TTagInfo.TTagAttrCallback; @@ -137,7 +121,10 @@ procedure TActiveTextHTML.InitialiseTagInfoMap; ElemKind: TActiveTextActionElemKind; const Tags: array[TActiveTextActionElemKind] of string = ( - 'a', 'strong', 'em', 'var', 'p', 'span', 'h2', 'code' + 'a' {ekLink}, 'strong' {ekStrong}, 'em' {ekEm}, 'var' {ekVar}, 'p' {ekPara}, + 'span' {ekWarning}, 'h2' {ekHeading}, 'code' {ekMono}, + 'ul' {ekUnorderedList}, 'ol' {ekUnorderedList}, 'li' {ekListItem}, + 'div' {ekBlock}, 'div' {ekDocument} ); begin NullAttrs := function(Elem: IActiveTextActionElem): IHTMLAttributes @@ -164,7 +151,7 @@ procedure TActiveTextHTML.InitialiseTagInfoMap; function TActiveTextHTML.MakeClosingTag(const Elem: IActiveTextActionElem): string; begin - Result := THTML.ClosingTag(fTagInfoMap[Elem.Kind].Name); + Result := fTagGen.ClosingTag(fTagInfoMap[Elem.Kind].Name); end; function TActiveTextHTML.MakeOpeningTag(const Elem: IActiveTextActionElem): @@ -179,67 +166,89 @@ function TActiveTextHTML.MakeOpeningTag(const Elem: IActiveTextActionElem): Attrs := THTMLAttributes.Create; Attrs.Add('class', fCSSStyles.ElemClasses[Elem.Kind]) end; - Result := THTML.OpeningTag(fTagInfoMap[Elem.Kind].Name, Attrs); + Result := fTagGen.OpeningTag(fTagInfoMap[Elem.Kind].Name, Attrs); end; function TActiveTextHTML.Render(ActiveText: IActiveText): string; var - Elem: IActiveTextElem; - TextElem: IActiveTextTextElem; - ActionElem: IActiveTextActionElem; + Elem: IActiveTextElem; // each element in active text object + TextElem: IActiveTextTextElem; // an active text text element + TagElem: IActiveTextActionElem; // an active text action element + Text: string; + SrcLines: IStringList; + SrcLine: string; + DestLines: IStringList; + DestLine: string; begin - fBuilder.Clear; - fInBlock := False; - InitialiseRender; + if not ActiveText.HasContent then + Exit(''); + Text := ''; + fLevel := 0; for Elem in ActiveText do begin if Supports(Elem, IActiveTextTextElem, TextElem) then - RenderTextElem(TextElem) - else if Supports(Elem, IActiveTextActionElem, ActionElem) then - begin - if ActionElem.DisplayStyle = dsBlock then - RenderBlockActionElem(ActionElem) - else - RenderInlineActionElem(ActionElem); - end; + Text := Text + RenderText(TextElem) + else if Supports(Elem, IActiveTextActionElem, TagElem) then + Text := Text + RenderTag(TagElem); + end; + SrcLines := TIStringList.Create(Text, EOL, False); + DestLines := TIStringList.Create; + for SrcLine in SrcLines do + begin + DestLine := StrTrimRight(SrcLine); + if not StrIsEmpty(DestLine) then + DestLines.Add(DestLine); end; - FinaliseRender; - Result := fBuilder.ToString; + Result := DestLines.GetText(EOL, False); end; -procedure TActiveTextHTML.RenderBlockActionElem(Elem: IActiveTextActionElem); +function TActiveTextHTML.RenderTag(const TagElem: IActiveTextActionElem): + string; begin - case Elem.State of - fsOpen: - begin - fBuilder.Append(MakeOpeningTag(Elem)); - fInBlock := True; - end; + Result := ''; + case TagElem.State of fsClose: begin - fInBlock := False; - fBuilder.AppendLine(MakeClosingTag(Elem)); + Result := MakeClosingTag(TagElem); + if TActiveTextElemCaps.DisplayStyleOf(TagElem.Kind) = dsBlock then + begin + Dec(fLevel); + Result := EOL + StrOfSpaces(IndentMult * fLevel) + Result + EOL; + fIsStartOfTextLine := True; + end; end; - end; -end; - -procedure TActiveTextHTML.RenderInlineActionElem(Elem: IActiveTextActionElem); -begin - if not fInBlock then - Exit; - case Elem.State of fsOpen: - fBuilder.Append(MakeOpeningTag(Elem)); - fsClose: - fBuilder.Append(MakeClosingTag(Elem)); + begin + Result := MakeOpeningTag(TagElem); + if TActiveTextElemCaps.DisplayStyleOf(TagElem.Kind) = dsBlock then + begin + Result := EOL + StrOfSpaces(IndentMult * fLevel) + Result + EOL; + Inc(fLevel); + fIsStartOfTextLine := True; + end + else if TActiveTextElemCaps.DisplayStyleOf(TagElem.Kind) = dsInline then + begin + if fIsStartOfTextLine then + begin + Result := StrOfSpaces(IndentMult * fLevel) + Result; + fIsStartOfTextLine := False; + end; + end; + end; end; end; -procedure TActiveTextHTML.RenderTextElem(Elem: IActiveTextTextElem); +function TActiveTextHTML.RenderText(const TextElem: IActiveTextTextElem): + string; begin - if not fInBlock then - Exit; - fBuilder.Append(THTML.Entities(Elem.Text)); + if fIsStartOfTextLine then + begin + Result := StrOfSpaces(IndentMult * fLevel); + fIsStartOfTextLine := False; + end + else + Result := ''; + Result := Result + fTagGen.Entities(TextElem.Text); end; { TActiveTextHTML.TCSSStyles } @@ -247,13 +256,15 @@ procedure TActiveTextHTML.RenderTextElem(Elem: IActiveTextTextElem); constructor TActiveTextHTML.TCSSStyles.Create; const DefaultClasses: array[TActiveTextActionElemKind] of string = ( - 'external-link', '', '', '', '', 'warning', '', '' + 'external-link' {ekLink}, '' {ekStrong}, '' {ekEm}, '' {ekVar}, '' {ekPara}, + 'warning' {ekWarning}, '' {ekHeading}, '' {ekMono}, '' {ekUnorderedList}, + '' {ekOrderedList}, '' {ekListItem}, '' {ekBlock}, + 'active-text' {ekDocument} ); var ElemKind: TActiveTextActionElemKind; begin inherited Create; - fWrapperClass := 'active-text'; for ElemKind := Low(TActiveTextActionElemKind) to High(TActiveTextActionElemKind) do SetElemClass(ElemKind, DefaultClasses[ElemKind]); diff --git a/Src/ActiveText.UMain.pas b/Src/ActiveText.UMain.pas index f9b3f3d3f..eebd3db1e 100644 --- a/Src/ActiveText.UMain.pas +++ b/Src/ActiveText.UMain.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2008-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2008-2023, Peter Johnson (gravatar.com/delphidabbler). * * Provides interfaces, a factory class and implementation of "active text". * Active text is text that can have actions performed on it. Actions may @@ -123,14 +120,19 @@ TActiveTextAttrNames = record type /// <summary>Supported types of active text action elements.</summary> TActiveTextActionElemKind = ( - ekLink, // link element: has a URL (inline) - ekStrong, // text formatted as strong (inline) - ekEm, // text formatted as emphasised (inline) - ekVar, // text formatted as variable (inline) - ekPara, // delimits a paragraph (block level) - ekWarning, // text formatted as a warning (inline) - ekHeading, // delimits a heading (block level) - ekMono // text formatted as mono spaced (inline) + ekLink, // link element: has a URL (inline) + ekStrong, // text formatted as strong (inline) + ekEm, // text formatted as emphasised (inline) + ekVar, // text formatted as variable (inline) + ekPara, // delimits a paragraph (block) + ekWarning, // text formatted as a warning (inline) + ekHeading, // delimits a heading (block level) + ekMono, // text formatted as mono spaced (inline) + ekUnorderedList, // container for unordered lists (block) + ekOrderedList, // container for ordered list (block) + ekListItem, // list item (block) + ekBlock, // container for unexpected text outside block (block) + ekDocument // contains whole document (block) ); type @@ -160,12 +162,6 @@ TActiveTextAttrNames = record function GetAttrs: IActiveTextAttrs; /// <summary>Object describing element's attributes.</summary> property Attrs: IActiveTextAttrs read GetAttrs; - /// <summary>Returns value that indicates whether element is an inline or - /// block element.</summary> - function GetDisplayStyle: TActiveTextDisplayStyle; - /// <summary>Indicates whether element displays inline or as a block. - /// </summary> - property DisplayStyle: TActiveTextDisplayStyle read GetDisplayStyle; end; type @@ -181,15 +177,32 @@ TActiveTextAttrNames = record /// <summary>Appends elements from another given active text object to the /// current object.</summary> procedure Append(const ActiveText: IActiveText); + /// <summary>Returns a new IActiveText instance containing just the first + /// block of the current object.</summary> + /// <remarks> + /// <para>The first block is the content of the block level tag that starts + /// the active text. If this block has child blocks (for e.g. an unordered + /// list) then they are included.</para> + /// <para>If the current object is empty then an empty object is returned. + /// </para> + /// </remarks> + function FirstBlock: IActiveText; /// <summary>Checks if the active text object contains any elements. /// </summary> function IsEmpty: Boolean; + /// <summary>Checks if the active text object has text content.</summary> + function HasContent: Boolean; /// <summary>Checks if the active text object contains only plain text. /// </summary> /// <remarks>Plain text is considered to be active text with no action - /// elements except for "para". This can rendered in plain text with no - /// loss of formatting.</remarks> + /// elements except for "document" or "block". This can rendered in plain + /// text with no loss of formatting.</remarks> function IsPlainText: Boolean; + /// <summary>Checks if the active text object is a valid active text + /// document.</summary> + /// <remarks>A valid document is either empty or it is surrounded by + /// matching ekDocument elements.</remarks> + function IsValidActiveTextDocument: Boolean; /// <summary>Returns element at given index in active text object's element /// list.</summary> function GetElem(Idx: Integer): IActiveTextElem; @@ -276,6 +289,158 @@ TActiveTextFactory = class(TNoConstructObject) IActiveTextAttrs; overload; end; +type + /// <summary>Provides information about the capabilities of each supported + /// active text element.</summary> + TActiveTextElemCaps = record + strict private + type + /// <summary>Fields used to define an active text element's capabilities. + /// </summary> + TCaps = record + public + var + /// <summary>Determines how element is to be displayed.</summary> + DisplayStyle: TActiveTextDisplayStyle; + /// <summary>Specifies whether plain text can be contained within the + /// element.</summary> + PermitsText: Boolean; + /// <summary>Specifies the elements that are permitted as child + /// elements of this element. + PermittedChildElems: TActiveTextActionElemKinds; + end; + const + /// <summary>Set of block level elements.</summary> + BlockElems = [ + ekPara, ekHeading, ekUnorderedList, ekOrderedList, ekListItem, + ekBlock, ekDocument + ]; + /// <summary>Set of block level elements that can directly contain text + /// and inline elements.</summary> + TextContentBlocks = [ + ekPara, ekHeading, ekBlock + ]; + /// <summary>Set of block level elements that can contain only blocks + /// that are not container blocks.</summary> + ContainerBlocks = [ + ekDocument, ekListItem + ]; + /// <summary>Set of inline elements.</summary> + InlineElems = [ + ekLink, ekStrong, ekEm, ekVar, ekWarning, ekMono + ]; + /// <summary>Set of all elements.</summary> + AllElems = BlockElems + InlineElems; + /// <summary>Map of all elements to their capabilities.</summary> + Map: array[TActiveTextActionElemKind] of TCaps = + ( + ( + // ekLink + // may contain any inline elements but no block elements + DisplayStyle: dsInline; + PermitsText: True; + PermittedChildElems: InlineElems - [ekLink]; + ), + ( + // ekStrong + // may contain any inline elements but no block elements + DisplayStyle: dsInline; + PermitsText: True; + PermittedChildElems: InlineElems; + ), + ( + // ekEm + // may contain any inline elements but no block elements + DisplayStyle: dsInline; + PermitsText: True; + PermittedChildElems: InlineElems; + ), + ( + // ekVar + // may contain any inline elements but no block elements + DisplayStyle: dsInline; + PermitsText: True; + PermittedChildElems: InlineElems; + ), + ( + // ekPara + // may contain any inline elements but no block elements + DisplayStyle: dsBlock; + PermitsText: True; + PermittedChildElems: InlineElems; + ), + ( + // ekWarning + // may contain any inline elements but no block elements + DisplayStyle: dsInline; + PermitsText: True; + PermittedChildElems: InlineElems; + ), + ( + // ekHeading + // may contain any inline elements but no block elements + DisplayStyle: dsBlock; + PermitsText: True; + PermittedChildElems: InlineElems; + ), + ( + // ekMono + // may contain any inline elements but no block elements + DisplayStyle: dsInline; + PermitsText: True; + PermittedChildElems: InlineElems; + ), + ( + // ekUnorderedList + // may contain only list item elements + DisplayStyle: dsBlock; + PermitsText: False; + PermittedChildElems: [ekListItem]; + ), + ( + // ekOrderedList + // may contain only list item elements + DisplayStyle: dsBlock; + PermitsText: False; + PermittedChildElems: [ekListItem]; + ), + ( + // ekListItem + // may contain only block elements, but not itself or other + // block containers + DisplayStyle: dsBlock; + PermitsText: False; + PermittedChildElems: BlockElems - ContainerBlocks; + ), + ( + // ekBlock + // may contain any inline elements but no block elements + DisplayStyle: dsBlock; + PermitsText: True; + PermittedChildElems: InlineElems; + ), + ( + // ekDocument + // may contain only block elements, but not itself or other + // block containers + DisplayStyle: dsBlock; + PermitsText: False; + PermittedChildElems: BlockElems - ContainerBlocks; + ) + ); + public + /// <summary>Returns the display type of the given element.</summary> + class function DisplayStyleOf(const Elem: TActiveTextActionElemKind): + TActiveTextDisplayStyle; static; + /// <summary>Checks whether the given element can contain text.</summary> + class function CanContainText(const Elem: TActiveTextActionElemKind): + Boolean; static; + /// <summary>Checks whether the given child element is permitted as a child + /// of the given parent element.</summary> + class function IsPermittedChildElem( + const Parent, Child: TActiveTextActionElemKind): Boolean; static; + end; + implementation @@ -284,7 +449,10 @@ implementation // Delphi SysUtils, // Project - IntfCommon; + IntfCommon, + UConsts, + UStrUtils, + UUtils; type @@ -324,18 +492,40 @@ TActiveText = class(TInterfacedObject, /// </summary> /// <remarks>Method of IActiveText.</remarks> procedure Append(const ActiveText: IActiveText); + /// <summary>Returns a new IActiveText instance containing just the first + /// block of the current object.</summary> + /// <remarks> + /// <para>The first block is the content of the block level tag that starts + /// the active text. If this block has child blocks (for e.g. an unordered + /// list) then they are included.</para> + /// <para>If the current object is empty then an empty object is returned. + /// </para> + /// <para>Method of IActiveText.</para> + /// </remarks> + function FirstBlock: IActiveText; /// <summary>Checks if the element list is empty.</summary> /// <remarks>Method of IActiveText.</remarks> function IsEmpty: Boolean; + /// <summary>Checks if the active text object has text content.</summary> + /// <remarks>Method of IActiveText.</remarks> + function HasContent: Boolean; /// <summary>Checks if the active text object contains only plain text. /// </summary> /// <remarks> /// <para>Plain text is considered to be active text with no action - /// elements except for "para". This can rendered in plain text with no - /// loss of formatting.</para> + /// elements except for "document" or "block". This can rendered in plain + /// text with no loss of formatting.</para> /// <para>Method of IActiveText.</para> /// </remarks> function IsPlainText: Boolean; + /// <summary>Checks if the active text object is a valid active text + /// document.</summary> + /// <remarks> + /// <para>A valid document is either empty or it is surrounded by matching + /// ekDocument elements.</para> + /// <para>Method of IActiveText.</para> + /// </remarks> + function IsValidActiveTextDocument: Boolean; /// <summary>Returns element at given index in element list.</summary> /// <remarks>Method of IActiveText.</remarks> function GetElem(Idx: Integer): IActiveTextElem; @@ -357,7 +547,7 @@ TActiveTextTextElem = class(TInterfacedObject, fText: string; public /// <summary>Object constructor. Records given element text and sets - /// required kind for a text element.</summary> + /// required Elem for a text element.</summary> constructor Create(const Text: string); /// <summary>Assigns properties of another object to this object.</summary> /// <param name="Src">IInterface [in] Object whose properties are to be @@ -378,7 +568,7 @@ TActiveTextActionElem = class(TInterfacedObject, IActiveTextElem, IActiveTextActionElem, IAssignable, IClonable ) strict private - /// <summary>Kind of element encapsulated by this object.</summary> + /// <summary>Elem of element encapsulated by this object.</summary> fKind: TActiveTextActionElemKind; /// <summary>State of element: opening or closing.</summary> fState: TActiveTextElemState; @@ -386,13 +576,14 @@ TActiveTextActionElem = class(TInterfacedObject, fAttrs: IActiveTextAttrs; public /// <summary>Object constructor. Creates an action element.</summary> - /// <param name="Kind">TActiveTextElemKind [in] Required kind of element. + /// <param name="AKind">TActiveTextElemKind [in] Required Elem of element. + /// </param> + /// <param name="AAttrs">IActiveTextAttrs [in] Element's attributes. /// </param> - /// <param name="Attrs">IActiveTextAttrs [in] Element's attributes.</param> - /// <param name="State">TActiveTextElemState [in] State of element: opening - /// or closing.</param> - constructor Create(const Kind: TActiveTextActionElemKind; - Attrs: IActiveTextAttrs; const State: TActiveTextElemState); + /// <param name="AState">TActiveTextElemState [in] State of element: + /// opening or closing.</param> + constructor Create(const AKind: TActiveTextActionElemKind; + AAttrs: IActiveTextAttrs; const AState: TActiveTextElemState); /// <summary>Assigns properties of another object to this object.</summary> /// <param name="Src">IInterface [in] Object whose properties are to be /// assigned. Src must support IActiveTextActionElem.</param> @@ -404,7 +595,7 @@ TActiveTextActionElem = class(TInterfacedObject, /// <summary>Returns a cloned instance of this object.</summary> /// <remarks>Method of IClonable.</remarks> function Clone: IInterface; - /// <summary>Returns kind of action represented by this element.</summary> + /// <summary>Returns Elem of action represented by this element.</summary> /// <remarks>Method of IActiveTextActionElem.</remarks> function GetKind: TActiveTextActionElemKind; /// <summary>Returns state of element.</summary> @@ -413,10 +604,6 @@ TActiveTextActionElem = class(TInterfacedObject, /// <summary>Returns object describing element's attributes.</summary> /// <remarks>Method of IActiveTextActionElem.</remarks> function GetAttrs: IActiveTextAttrs; - /// <summary>Returns value that indicates whether element is an inline or - /// block element.</summary> - /// <remarks>Method of IActiveTextActionElem.</remarks> - function GetDisplayStyle: TActiveTextDisplayStyle; end; type @@ -534,15 +721,43 @@ function TActiveText.AddElem(const Elem: IActiveTextElem): Integer; end; procedure TActiveText.Append(const ActiveText: IActiveText); + + function IsDocumentElem(Elem: IActiveTextElem): Boolean; + var + ActiveElem: IActiveTextActionElem; + begin + if not Supports(Elem, IActiveTextActionElem, ActiveElem) then + Exit(False); + Result := ActiveElem.Kind = ekDocument; + end; + var Elem: IActiveTextElem; // references each element in elems - NewElem: IActiveTextElem; + SelfCopy: IActiveText; // temporary copy of this object begin + // *** Don't call Clone or Assign here: they call backinto this method. + + // Make a copy of elements of self + SelfCopy := TActiveText.Create; + for Elem in fElems do + SelfCopy.AddElem((Elem as IClonable).Clone as IActiveTextElem); + + // Clear own elems and add document start element + fElems.Clear; + AddElem(TActiveTextFactory.CreateActionElem(ekDocument, fsOpen)); + + // Copy own elements back to fElems, skipping ekDocument elems + for Elem in SelfCopy do + if not IsDocumentElem(Elem) then + AddElem((Elem as IClonable).Clone as IActiveTextElem); + + // Copy active text to be assigned, skipping its ekDocument elems for Elem in ActiveText do - begin - NewElem := (Elem as IClonable).Clone as IActiveTextElem; - AddElem(NewElem); - end; + if not IsDocumentElem(Elem) then + AddElem((Elem as IClonable).Clone as IActiveTextElem); + + // Add closing ekDocument Elem + AddElem(TActiveTextFactory.CreateActionElem(ekDocument, fsClose)); end; procedure TActiveText.Assign(const Src: IInterface); @@ -572,6 +787,78 @@ destructor TActiveText.Destroy; inherited; end; +function TActiveText.FirstBlock: IActiveText; +var + Elem: IActiveTextElem; + ActionElem: IActiveTextActionElem; + Block: IActiveTextActionElem; + Idx: Integer; + EndOfBlockFound: Boolean; + HasDocElems: Boolean; + FirstBlockIdx: Integer; +begin + Result := TActiveText.Create; + if IsEmpty then + Exit; + + HasDocElems := IsValidActiveTextDocument; + if HasDocElems then + begin + // We have ekDocument elements wrapping document: 1st true blue should be + // next element + if GetCount < 4 then + Exit; + FirstBlockIdx := 1; + end + else + begin + // No ekDocument elements: 1st true block is should be first element + if GetCount < 2 then + Exit; + FirstBlockIdx := 0; + end; + + // Element at FirstBlockIdx must be a valid block opening element + Elem := GetElem(FirstBlockIdx); + GetIntf(Elem, IActiveTextElem, Block); + if not Assigned(Block) + or (TActiveTextElemCaps.DisplayStyleOf(Block.Kind) <> dsBlock) + or (Block.State <> fsOpen) then + raise EBug.Create( + ClassName + '.FirstBlock: block opener expected after ekDocument element' + ); + + // We have required block: add document opener element and block element + Result.AddElem(TActiveTextFactory.CreateActionElem(ekDocument, fsOpen)); + Result.AddElem(Elem); + + // Scan through remaining elements, copying them to output as we go. Halt when + // (or if) matching closing block found. + EndOfBlockFound := False; + Idx := Succ(FirstBlockIdx); + while Idx < Pred(GetCount) do + begin + Elem := GetElem(Idx); + Result.AddElem(Elem); + if Supports(Elem, IActiveTextActionElem, ActionElem) + and (ActionElem.Kind = Block.Kind) + and (ActionElem.State = fsClose) then + begin + EndOfBlockFound := True; + Break; + end; + Inc(Idx); + end; + // No closing block found + if not EndOfBlockFound then + raise EBug.Create( + ClassName + '.FirstBlock: Matching closer for first block not found' + ); + + // Add document close elem (closing block elem added in loop above) + Result.AddElem(TActiveTextFactory.CreateActionElem(ekDocument, fsClose)); +end; + function TActiveText.GetCount: Integer; begin Result := fElems.Count; @@ -587,6 +874,18 @@ function TActiveText.GetEnumerator: TEnumerator<IActiveTextElem>; Result := fElems.GetEnumerator; end; +function TActiveText.HasContent: Boolean; +var + Elem: IActiveTextElem; + TextElem: IActiveTextTextElem; +begin + Result := False; + for Elem in fElems do + if Supports(Elem, IActiveTextTextElem, TextElem) + and (TextElem.Text <> '') then + Exit(True); +end; + function TActiveText.IsEmpty: Boolean; begin Result := fElems.Count = 0; @@ -600,12 +899,25 @@ function TActiveText.IsPlainText: Boolean; for Elem in fElems do begin if Supports(Elem, IActiveTextActionElem, ActionElem) - and (ActionElem.Kind <> ekPara) then + and not (ActionElem.Kind in [ekBlock, ekDocument]) then Exit(False); end; Result := True; end; +function TActiveText.IsValidActiveTextDocument: Boolean; +var + DocStartElem, DocEndElem: IActiveTextActionElem; +begin + if IsEmpty then + Exit(True); + Result := (GetCount >= 2) + and Supports(fElems[0], IActiveTextActionElem, DocStartElem) + and (DocStartElem.Kind = ekDocument) and (DocStartElem.State = fsOpen) + and Supports(fElems[Pred(GetCount)], IActiveTextActionElem, DocEndElem) + and (DocEndElem.Kind = ekDocument) and (DocEndElem.State = fsClose); +end; + function TActiveText.ToString: string; var Elem: IActiveTextElem; @@ -620,13 +932,13 @@ function TActiveText.ToString: string; if Supports(Elem, IActiveTextTextElem, TextElem) then SB.Append(TextElem.Text); if Supports(Elem, IActiveTextActionElem, ActionElem) - and (ActionElem.DisplayStyle = dsBlock) + and (TActiveTextElemCaps.DisplayStyleOf(ActionElem.Kind) = dsBlock) and (ActionElem.State = fsClose) then // new line at end of block to separate text at end of closing block // from text at start of following block SB.AppendLine; end; - Result := SB.ToString; + Result := StrTrimRight(SB.ToString) + EOL; // ensure single final EOL(s) finally SB.Free; end; @@ -677,13 +989,13 @@ function TActiveTextActionElem.Clone: IInterface; Result := TActiveTextActionElem.Create(GetKind, Attrs, GetState); end; -constructor TActiveTextActionElem.Create(const Kind: TActiveTextActionElemKind; - Attrs: IActiveTextAttrs; const State: TActiveTextElemState); +constructor TActiveTextActionElem.Create(const AKind: TActiveTextActionElemKind; + AAttrs: IActiveTextAttrs; const AState: TActiveTextElemState); begin inherited Create; - fAttrs := Attrs; - fState := State; - fKind := Kind; + fAttrs := AAttrs; + fState := AState; + fKind := AKind; end; function TActiveTextActionElem.GetAttrs: IActiveTextAttrs; @@ -691,14 +1003,6 @@ function TActiveTextActionElem.GetAttrs: IActiveTextAttrs; Result := fAttrs; end; -function TActiveTextActionElem.GetDisplayStyle: TActiveTextDisplayStyle; -begin - if GetKind in [ekPara, ekHeading] then - Result := dsBlock - else - Result := dsInline; -end; - function TActiveTextActionElem.GetKind: TActiveTextActionElemKind; begin Result := fKind; @@ -758,5 +1062,25 @@ function TActiveTextAttrs.GetEnumerator: TEnumerator<TPair<string, string>>; Result := fMap.GetEnumerator; end; +{ TActiveTextElemCapsMap } + +class function TActiveTextElemCaps.CanContainText( + const Elem: TActiveTextActionElemKind): Boolean; +begin + Result := Map[Elem].PermitsText; +end; + +class function TActiveTextElemCaps.DisplayStyleOf( + const Elem: TActiveTextActionElemKind): TActiveTextDisplayStyle; +begin + Result := Map[Elem].DisplayStyle; +end; + +class function TActiveTextElemCaps.IsPermittedChildElem( + const Parent, Child: TActiveTextActionElemKind): Boolean; +begin + Result := Child in Map[Parent].PermittedChildElems; +end; + end. diff --git a/Src/ActiveText.UMarkdownRenderer.pas b/Src/ActiveText.UMarkdownRenderer.pas new file mode 100644 index 000000000..d3678015b --- /dev/null +++ b/Src/ActiveText.UMarkdownRenderer.pas @@ -0,0 +1,927 @@ +{ + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/ + * + * Copyright (C) 2025, Peter Johnson (gravatar.com/delphidabbler). + * + * Implements class that renders active text in Markdown format. +} + + +unit ActiveText.UMarkdownRenderer; + +interface + +uses + // Delphi + SysUtils, + Generics.Collections, + // Project + ActiveText.UMain, + UIStringList; + + +type + /// <summary>Renders active text in Markdown format.</summary> + TActiveTextMarkdown = class(TObject) + strict private + type + + /// <summary>Kinds of inline Markdown formatting.</summary> + TInlineElemKind = ( + iekPlain, // no formatting e.g. text => text + iekWeakEmphasis, // weak emphasis (italic) e.g. text => *text* + iekStrongEmphasis, // strong emphasis (bold) e.g. text => **text** + iekLink, // link e.g. text,url => [text](url) + iekInlineCode // inline code e.g. text => `text` + ); + + /// <summary>Representation of an inline Markdown element.</summary> + TInlineElem = record + strict private + var + fFormatterKind: TInlineElemKind; + fMarkdown: string; + fAttrs: IActiveTextAttrs; + fCanRenderElem: TPredicate<TInlineElemKind>; + public + constructor Create(const AFormatterKind: TInlineElemKind; + const ACanRenderElem: TPredicate<TInlineElemKind>; + const AAttrs: IActiveTextAttrs); + property Kind: TInlineElemKind read fFormatterKind; + property Markdown: string read fMarkdown write fMarkdown; + property Attrs: IActiveTextAttrs read fAttrs; + property CanRenderElem: TPredicate<TInlineElemKind> read fCanRenderElem; + end; + + /// <summary>Stack of inline Markdown elements.</summary> + /// <remarks>Used in rendering all the inline elements within a block. + /// </remarks> + TInlineElemStack = class (TStack<TInlineElem>) + strict private + public + procedure Push(const AFmtKind: TInlineElemKind; + const ACanRenderElem: TPredicate<TInlineElemKind>; + const AAttrs: IActiveTextAttrs); reintroduce; + function IsEmpty: Boolean; + function IsOpen(const AFmtKind: TInlineElemKind): Boolean; + function NestingDepthOf(const AFmtKind: TInlineElemKind): Integer; + procedure AppendMarkdown(const AMarkdown: string); + constructor Create; + destructor Destroy; override; + end; + + /// <summary>Kinds of Markdown containers.</summary> + TContainerKind = ( + ckPlain, // represents main document + ckBulleted, // represents an unordered list item + ckNumbered // represents an ordered list item + ); + + /// <summary>Encapsulates the state of a list (ordered or unordered). + /// </summary> + TListState = record + public + ListNumber: Cardinal; + ListKind: TContainerKind; + constructor Create(AListKind: TContainerKind); + end; + + /// <summary>A stack of currently open lists, with the current, most + /// nested at the top of the stack.</summary> + /// <remarks>Used to keep track of list nesting.</remarks> + TListStack = class(TStack<TListState>) + public + constructor Create; + destructor Destroy; override; + procedure IncTopListNumber; + end; + + /// <summary>Base class for classes that represent a chunk of a Markdown + /// document. A Markdown document contains a sequence of chunks, each of + /// which is either a block level element or a container of other chunks + /// at a deeper level.</summary> + TContentChunk = class abstract + strict private + var + fDepth: UInt8; + fClosed: Boolean; + public + constructor Create(const ADepth: UInt8); + procedure Close; + function IsClosed: Boolean; + procedure Render(const ALines: IStringList); virtual; abstract; + property Depth: UInt8 read fDepth; + end; + + /// <summary>Base class for container chunks that hold a sequence of + /// other chunks at a given depth within a Markdown document.</summary> + TContainer = class abstract (TContentChunk) + strict private + fContent: TObjectList<TContentChunk>; + public + constructor Create(const ADepth: UInt8); + destructor Destroy; override; + function IsEmpty: Boolean; + procedure Add(const AChunk: TContentChunk); + function LastChunk: TContentChunk; + function Content: TArray<TContentChunk>; + function TrimEmptyBlocks: TArray<TContentChunk>; + procedure Render(const ALines: IStringList); override; abstract; + end; + + /// <summary>Encapsulate the Markdown document. Contains a sequence of + /// other chunks within the top level of the document.</summary> + TDocument = class sealed (TContainer) + public + procedure Render(const ALines: IStringList); override; + end; + + /// <summary>Encapsulates a generalised list item, that is a container + /// for chunks at a deeper level within the document.</summary> + TListItem = class abstract (TContainer) + strict private + fNumber: UInt8; + public + constructor Create(const ADepth: UInt8; const ANumber: UInt8); + procedure Render(const ALines: IStringList); override; abstract; + property Number: UInt8 read fNumber; + end; + + /// <summary>Encapsulates a bullet list item that contains a sequence of + /// chunks that belong to the list item.</summary> + TBulletListItem = class sealed (TListItem) + public + constructor Create(const ADepth: UInt8; const ANumber: UInt8); + procedure Render(const ALines: IStringList); override; + end; + + /// <summary>Encapsulates a numbered list item that contains a sequence + /// of chunks that belong to the list item.</summary> + TNumberListItem = class sealed (TListItem) + public + constructor Create(const ADepth: UInt8; const ANumber: UInt8); + procedure Render(const ALines: IStringList); override; + end; + + /// <summary>Encapsulates a generalised Markdown block level item. + /// </summary> + TBlock = class abstract (TContentChunk) + strict private + var + fMarkdownStack: TInlineElemStack; + public + constructor Create(const ADepth: UInt8); + destructor Destroy; override; + property MarkdownStack: TInlineElemStack read fMarkdownStack; + function IsEmpty: Boolean; + procedure Render(const ALines: IStringList); override; abstract; + function RenderStr: string; virtual; abstract; + function LookupElemKind( + const AActiveTextKind: TActiveTextActionElemKind): TInlineElemKind; + end; + + /// <summary>Encapsulates a "fake" Markdown block that is used + /// to contain any active text that exists outside a block level tag or + /// whose direct parent is a list item.</summary> + TSimpleBlock = class sealed (TBlock) + public + procedure Render(const ALines: IStringList); overload; override; + function RenderStr: string; override; + end; + + /// <summary>Encapsulates a Markdown paragraph.</summary> + TParaBlock = class sealed (TBlock) + public + procedure Render(const ALines: IStringList); overload; override; + function RenderStr: string; override; + end; + + /// <summary>Encapsulates a markdown heading (assumed to be at level 2). + /// </summary> + THeadingBlock = class sealed (TBlock) + public + procedure Render(const ALines: IStringList); overload; override; + function RenderStr: string; override; + end; + + /// <summary>A stack of currently open containers.</summary> + /// <remarks>Used to track the parentage of the currently open container. + /// </remarks> + TContainerStack = class(TStack<TContainer>); + + strict private + var + /// <summary>Contains all the content chunks belonging to the top level + /// Markdown document.</summary> + fDocument: TDocument; + /// <summary>Stack that tracks the parentage of any currently open list. + /// </summary> + fListStack: TListStack; + /// <summary>Stack that tracks the parentage of the currently open + /// container.</summary> + fContainerStack: TContainerStack; + /// <summary>Closes and renders the Markdown for the currently open inline + /// element in the given Markdown block.</summary> + procedure CloseInlineElem(const Block: TBlock); + procedure ParseTextElem(Elem: IActiveTextTextElem); + procedure ParseBlockActionElem(Elem: IActiveTextActionElem); + procedure ParseInlineActionElem(Elem: IActiveTextActionElem); + procedure Parse(ActiveText: IActiveText); + public + constructor Create; + destructor Destroy; override; + /// <summary>Parses the given active text and returns a Markdown + /// representation of it.</summary> + function Render(ActiveText: IActiveText): string; + end; + + +implementation + +uses + // Project + UConsts, + UExceptions, + UMarkdownUtils, + UStrUtils; + + +{ TActiveTextMarkdown } + +procedure TActiveTextMarkdown.CloseInlineElem(const Block: TBlock); +var + MElem: TInlineElem; + Markdown: string; +begin + MElem := Block.MarkdownStack.Peek; + // Render markdown + Markdown := ''; + if MElem.CanRenderElem(MElem.Kind) then + begin + // Element should be output, wrapping its markdown + case MElem.Kind of + iekWeakEmphasis: + if not StrIsEmpty(MElem.Markdown) then + Markdown := TMarkdown.WeakEmphasis(MElem.Markdown); + iekStrongEmphasis: + if not StrIsEmpty(MElem.Markdown) then + Markdown := TMarkdown.StrongEmphasis(MElem.Markdown); + iekLink: + if StrIsEmpty(MElem.Attrs[TActiveTextAttrNames.Link_URL]) then + begin + Markdown := MElem.Markdown; // no URL: emit bare markdown + end + else + begin + // we have URL + if not StrIsEmpty(MElem.Markdown) then + // we have inner markdown: emit standard link + Markdown := TMarkdown.Link( + MElem.Markdown, MElem.Attrs[TActiveTextAttrNames.Link_URL] + ) + else + // no inner text: emit bare URL + Markdown := TMarkdown.BareURL( + MElem.Attrs[TActiveTextAttrNames.Link_URL] + ); + end; + iekInlineCode: + if not StrIsEmpty(MElem.Markdown) then + begin + // Note: <mono>`foo`</mono> should be rendered as `` `foo` ``, not + // ```foo```, but for any other leading or trailing character than ` + // don't prefix with space. + // Also don't add space for other leading / trailing chars, so + // <mono>[foo]</mono> is rendered as `[foo]` and <mono>[`foo`]</mono> + // is rendered as ``[`foo`]`` + Markdown := MElem.Markdown; + if Markdown[1] = '`' then + Markdown := ' ' + Markdown; + if Markdown[Length(Markdown)] = '`' then + Markdown := Markdown + ' '; + Markdown := TMarkdown.InlineCode(Markdown); + end; + end; + end + else + // Ingoring element: keep its inner markdown + Markdown := MElem.Markdown; + // Pop stack & add markdown to that of new stack top + Block.MarkdownStack.Pop; + // stack should contain at least a block element below all inline elements + Assert(not Block.MarkdownStack.IsEmpty); + Block.MarkdownStack.AppendMarkdown(Markdown); +end; + +constructor TActiveTextMarkdown.Create; +begin + fDocument := TDocument.Create(0); + fContainerStack := TContainerStack.Create; + fListStack := TListStack.Create; +end; + +destructor TActiveTextMarkdown.Destroy; +begin + fListStack.Free; + fContainerStack.Free; + fDocument.Free; + inherited; +end; + +procedure TActiveTextMarkdown.Parse(ActiveText: IActiveText); +var + Elem: IActiveTextElem; + TextElem: IActiveTextTextElem; + ActionElem: IActiveTextActionElem; +begin + fContainerStack.Clear; + fContainerStack.Push(fDocument); + + if ActiveText.IsEmpty then + Exit; + + Assert( + Supports(ActiveText[0], IActiveTextActionElem, ActionElem) + and (ActionElem.Kind = ekDocument), + ClassName + '.Parse: Expected ekDocument at start of active text' + ); + + for Elem in ActiveText do + begin + if Supports(Elem, IActiveTextTextElem, TextElem) then + ParseTextElem(TextElem) + else if Supports(Elem, IActiveTextActionElem, ActionElem) then + begin + if TActiveTextElemCaps.DisplayStyleOf(ActionElem.Kind) = dsBlock then + ParseBlockActionElem(ActionElem) + else + ParseInlineActionElem(ActionElem); + end; + end; + +end; + +procedure TActiveTextMarkdown.ParseBlockActionElem(Elem: IActiveTextActionElem); +var + CurContainer, NewContainer: TContainer; +begin + + CurContainer := fContainerStack.Peek; + + case Elem.State of + + fsOpen: + begin + case Elem.Kind of + ekDocument: + ; // do nothing + ekUnorderedList: + fListStack.Push(TListState.Create(ckBulleted)); + ekOrderedList: + fListStack.Push(TListState.Create(ckNumbered)); + ekListItem: + begin + fListStack.IncTopListNumber; + case fListStack.Peek.ListKind of + ckBulleted: + NewContainer := TBulletListItem.Create( + fContainerStack.Peek.Depth + 1, fListStack.Peek.ListNumber + ); + ckNumbered: + NewContainer := TNumberListItem.Create( + fContainerStack.Peek.Depth + 1, fListStack.Peek.ListNumber + ); + else + raise EBug.Create( + ClassName + '.ParseBlockActionElem: Unknown list item type' + ); + end; + CurContainer.Add(NewContainer); + fContainerStack.Push(NewContainer); + end; + ekBlock: + CurContainer.Add(TSimpleBlock.Create(CurContainer.Depth)); + ekPara: + CurContainer.Add(TParaBlock.Create(CurContainer.Depth)); + ekHeading: + CurContainer.Add(THeadingBlock.Create(CurContainer.Depth)); + end; + end; + + fsClose: + begin + case Elem.Kind of + ekDocument: + ; // do nothing + ekUnorderedList, ekOrderedList: + fListStack.Pop; + ekListItem: + begin + fContainerStack.Pop; + CurContainer.Close; + end; + ekBlock, ekPara, ekHeading: + CurContainer.LastChunk.Close; + end; + end; + end; +end; + +procedure TActiveTextMarkdown.ParseInlineActionElem( + Elem: IActiveTextActionElem); +var + CurContainer: TContainer; + Block: TBlock; +begin + // Find last open block: create one if necessary + CurContainer := fContainerStack.Peek; + if not CurContainer.IsEmpty and (CurContainer.LastChunk is TBlock) + and not CurContainer.LastChunk.IsClosed then + Block := CurContainer.LastChunk as TBlock + else + begin + Block := TSimpleBlock.Create(CurContainer.Depth); + CurContainer.Add(Block); + end; + + case Elem.State of + fsOpen: + begin + + CurContainer := fContainerStack.Peek; + if not CurContainer.IsEmpty and (CurContainer.LastChunk is TBlock) + and not CurContainer.LastChunk.IsClosed then + Block := CurContainer.LastChunk as TBlock + else + begin + Block := TSimpleBlock.Create(CurContainer.Depth); + CurContainer.Add(Block); + end; + + case Elem.Kind of + + ekLink, ekStrong, ekWarning, ekEm, ekVar: + begin + Block.MarkdownStack.Push( + Block.LookupElemKind(Elem.Kind), + function (AKind: TInlineElemKind): Boolean + begin + Assert(AKind in [iekWeakEmphasis, iekStrongEmphasis, iekLink]); + Result := (Block.MarkdownStack.NestingDepthOf(AKind) = 0) + and not Block.MarkdownStack.IsOpen(iekInlineCode); + end, + Elem.Attrs + ); + end; + + ekMono: + Block.MarkdownStack.Push( + Block.LookupElemKind(Elem.Kind), + function (AKind: TInlineElemKind): Boolean + begin + Assert(AKind = iekInlineCode); + Result := Block.MarkdownStack.NestingDepthOf(AKind) = 0; + end, + Elem.Attrs + ); + end; + end; + + fsClose: + begin + CurContainer := fContainerStack.Peek; + Assert(not CurContainer.IsEmpty or not (CurContainer.LastChunk is TBlock)); + Block := CurContainer.LastChunk as TBlock; + CloseInlineElem(Block); + end; + end; +end; + +procedure TActiveTextMarkdown.ParseTextElem(Elem: IActiveTextTextElem); +var + CurContainer: TContainer; + Block: TBlock; +begin + CurContainer := fContainerStack.Peek; + if not CurContainer.IsEmpty and (CurContainer.LastChunk is TBlock) + and not CurContainer.LastChunk.IsClosed then + Block := CurContainer.LastChunk as TBlock + else + begin + Block := TSimpleBlock.Create(CurContainer.Depth); + CurContainer.Add(Block); + end; + if not Block.MarkdownStack.IsOpen(iekInlineCode) then + Block.MarkdownStack.AppendMarkdown(TMarkdown.EscapeText(Elem.Text)) + else + Block.MarkdownStack.AppendMarkdown(Elem.Text); +end; + +function TActiveTextMarkdown.Render(ActiveText: IActiveText): string; +var + Document: IStringList; +begin + Parse(ActiveText); + Assert(fContainerStack.Count = 1); + + Document := TIStringList.Create; + fContainerStack.Peek.Render(Document); + Result := Document.GetText(EOL, True); + while StrContainsStr(EOL2 + EOL, Result) do + Result := StrReplace(Result, EOL2 + EOL, EOL2); + Result := StrTrim(Result) + EOL; +end; + +{ TActiveTextMarkdown.TInlineElem } + +constructor TActiveTextMarkdown.TInlineElem.Create( + const AFormatterKind: TInlineElemKind; + const ACanRenderElem: TPredicate<TInlineElemKind>; + const AAttrs: IActiveTextAttrs); +begin + // Assign fields from parameters + fFormatterKind := AFormatterKind; + fMarkdown := ''; + fAttrs := AAttrs; + fCanRenderElem := ACanRenderElem; + + // Set defaults for nil fields + if not Assigned(AAttrs) then + fAttrs := TActiveTextFactory.CreateAttrs; + + if not Assigned(ACanRenderElem) then + fCanRenderElem := + function (AFmtKind: TInlineElemKind): Boolean + begin + Result := True; + end; +end; + +{ TActiveTextMarkdown.TInlineElemStack } + +procedure TActiveTextMarkdown.TInlineElemStack.AppendMarkdown( + const AMarkdown: string); +var + Elem: TInlineElem; +begin + Elem := Pop; + Elem.Markdown := Elem.Markdown + AMarkdown; + inherited Push(Elem); +end; + +constructor TActiveTextMarkdown.TInlineElemStack.Create; +begin + inherited Create; + // Push root element onto stack that receives all rendered markdown + // This element can always be rendered, has no attributes and no special chars + Push(iekPlain, nil, {nil, }nil); +end; + +destructor TActiveTextMarkdown.TInlineElemStack.Destroy; +begin + inherited; +end; + +function TActiveTextMarkdown.TInlineElemStack.IsEmpty: Boolean; +begin + Result := Count = 0; +end; + +function TActiveTextMarkdown.TInlineElemStack.IsOpen( + const AFmtKind: TInlineElemKind): Boolean; +var + Elem: TInlineElem; +begin + Result := False; + for Elem in Self do + if Elem.Kind = AFmtKind then + Exit(True); +end; + +function TActiveTextMarkdown.TInlineElemStack.NestingDepthOf( + const AFmtKind: TInlineElemKind): Integer; +var + Elem: TInlineElem; +begin + Result := -1; + for Elem in Self do + if (Elem.Kind = AFmtKind) then + Inc(Result); +end; + +procedure TActiveTextMarkdown.TInlineElemStack.Push( + const AFmtKind: TInlineElemKind; + const ACanRenderElem: TPredicate<TInlineElemKind>; + const AAttrs: IActiveTextAttrs); +begin + inherited Push( + TInlineElem.Create(AFmtKind, ACanRenderElem, AAttrs) + ); +end; + +{ TActiveTextMarkdown.TListState } + +constructor TActiveTextMarkdown.TListState.Create(AListKind: TContainerKind); +begin + ListKind := AListKind; + ListNumber := 0; +end; + +{ TActiveTextMarkdown.TListStack } + +constructor TActiveTextMarkdown.TListStack.Create; +begin + inherited Create; +end; + +destructor TActiveTextMarkdown.TListStack.Destroy; +begin + inherited; +end; + +procedure TActiveTextMarkdown.TListStack.IncTopListNumber; +var + State: TListState; +begin + State := Pop; + Inc(State.ListNumber); + Push(State); +end; + +{ TActiveTextMarkdown.TContentChunk } + +procedure TActiveTextMarkdown.TContentChunk.Close; +begin + fClosed := True; +end; + +constructor TActiveTextMarkdown.TContentChunk.Create(const ADepth: UInt8); +begin + inherited Create; + fDepth := ADepth; + fClosed := False; +end; + +function TActiveTextMarkdown.TContentChunk.IsClosed: Boolean; +begin + Result := fClosed; +end; + +{ TActiveTextMarkdown.TContainer } + +procedure TActiveTextMarkdown.TContainer.Add(const AChunk: TContentChunk); +begin + fContent.Add(AChunk); +end; + +function TActiveTextMarkdown.TContainer.Content: TArray<TContentChunk>; +begin + Result := fContent.ToArray; +end; + +constructor TActiveTextMarkdown.TContainer.Create(const ADepth: UInt8); +begin + inherited Create(ADepth); + fContent := TObjectList<TContentChunk>.Create(True); +end; + +destructor TActiveTextMarkdown.TContainer.Destroy; +begin + fContent.Free; + inherited; +end; + +function TActiveTextMarkdown.TContainer.IsEmpty: Boolean; +begin + Result := fContent.Count = 0; +end; + +function TActiveTextMarkdown.TContainer.LastChunk: TContentChunk; +begin + Result := fContent.Last; +end; + +function TActiveTextMarkdown.TContainer.TrimEmptyBlocks: TArray<TContentChunk>; +var + TrimmedBlocks: TList<TContentChunk>; + Chunk: TContentChunk; +begin + TrimmedBlocks := TList<TContentChunk>.Create; + try + for Chunk in fContent do + begin + if (Chunk is TBlock) then + begin + if not (Chunk as TBlock).IsEmpty then + TrimmedBlocks.Add(Chunk); + end + else + TrimmedBlocks.Add(Chunk); + end; + Result := TrimmedBlocks.ToArray; + finally + TrimmedBlocks.Free; + end; +end; + +{ TActiveTextMarkdown.TDocument } + +procedure TActiveTextMarkdown.TDocument.Render(const ALines: IStringList); +var + Chunk: TContentChunk; +begin + for Chunk in Self.TrimEmptyBlocks do + begin + Chunk.Render(ALines); + end; +end; + +{ TActiveTextMarkdown.TListItem } + +constructor TActiveTextMarkdown.TListItem.Create(const ADepth: UInt8; const ANumber: UInt8); +begin + inherited Create(ADepth); + fNumber := ANumber; +end; + +{ TActiveTextMarkdown.TBulletListItem } + +constructor TActiveTextMarkdown.TBulletListItem.Create(const ADepth: UInt8; const ANumber: UInt8); +begin + inherited Create(ADepth, ANumber); +end; + +procedure TActiveTextMarkdown.TBulletListItem.Render(const ALines: IStringList); +var + Idx: Integer; + StartIdx: Integer; + Trimmed: TArray<TContentChunk>; + ItemText: string; + + procedure AddBulletItem(const AMarkdown: string); + begin + ALines.Add(TMarkdown.BulletListItem(AMarkdown, Depth - 1)); + end; + +begin + Trimmed := TrimEmptyBlocks; + StartIdx := 0; + if Length(Trimmed) > 0 then + begin + if (Trimmed[0] is TBlock) then + begin + ItemText := (Trimmed[0] as TBlock).RenderStr; + if StrStartsStr(EOL, ItemText) then + ALines.Add(''); + AddBulletItem(StrTrimLeft(ItemText)); + Inc(StartIdx); + end + else + begin + AddBulletItem(''); + end; + for Idx := StartIdx to Pred(Length(Trimmed)) do + Trimmed[Idx].Render(ALines); + end + else + begin + AddBulletItem(''); + end; +end; + +{ TActiveTextMarkdown.TNumberListItem } + +constructor TActiveTextMarkdown.TNumberListItem.Create(const ADepth: UInt8; const ANumber: UInt8); +begin + inherited Create(ADepth, ANumber); +end; + +procedure TActiveTextMarkdown.TNumberListItem.Render(const ALines: IStringList); +var + Idx: Integer; + StartIdx: Integer; + Trimmed: TArray<TContentChunk>; + ItemText: string; + + procedure AddNumberItem(const AMarkdown: string); + begin + ALines.Add(TMarkdown.NumberListItem(AMarkdown, Number, Depth - 1)); + end; + +begin + Trimmed := TrimEmptyBlocks; + StartIdx := 0; + if Length(Trimmed) > 0 then + begin + if (Trimmed[0] is TBlock) then + begin + ItemText := (Trimmed[0] as TBlock).RenderStr; + if StrStartsStr(EOL, ItemText) then + ALines.Add(''); + AddNumberItem(StrTrimLeft(ItemText)); + Inc(StartIdx); + end + else + begin + AddNumberItem(''); + end; + for Idx := StartIdx to Pred(Length(Trimmed)) do + Trimmed[Idx].Render(ALines); + end + else + begin + AddNumberItem(''); + end; +end; + +{ TActiveTextMarkdown.TBlock } + +constructor TActiveTextMarkdown.TBlock.Create(const ADepth: UInt8); +begin + inherited Create(ADepth); + fMarkdownStack := TInlineElemStack.Create; +end; + +destructor TActiveTextMarkdown.TBlock.Destroy; +begin + fMarkdownStack.Free; + inherited; +end; + +function TActiveTextMarkdown.TBlock.IsEmpty: Boolean; +var + MDElem: TInlineElem; +begin + Result := True; + if fMarkdownStack.IsEmpty then + Exit; + for MDElem in fMarkdownStack do + if not StrIsEmpty(MDElem.Markdown, True) then + Exit(False); +end; + +function TActiveTextMarkdown.TBlock.LookupElemKind( + const AActiveTextKind: TActiveTextActionElemKind): TInlineElemKind; +begin + case AActiveTextKind of + ekLink: Result := iekLink; + ekStrong, ekWarning: Result := iekStrongEmphasis; + ekEm, ekVar: Result := iekWeakEmphasis; + ekMono: Result := iekInlineCode; + else + raise EBug.Create( + ClassName + '.LookupElemKind: Invalid inline active text element kind' + ); + end; +end; + +{ TActiveTextMarkdown.TSimpleBlock } + +procedure TActiveTextMarkdown.TSimpleBlock.Render(const ALines: IStringList); +begin + Assert(not MarkdownStack.IsEmpty); + ALines.Add(RenderStr); + ALines.Add(''); +end; + +function TActiveTextMarkdown.TSimpleBlock.RenderStr: string; +begin + Result := TMarkdown.Paragraph( + StrTrimLeft(MarkdownStack.Peek.Markdown), Depth + ); +end; + +{ TActiveTextMarkdown.TParaBlock } + +procedure TActiveTextMarkdown.TParaBlock.Render(const ALines: IStringList); +begin + Assert(not MarkdownStack.IsEmpty); + ALines.Add(RenderStr); +end; + +function TActiveTextMarkdown.TParaBlock.RenderStr: string; +begin + Result := EOL + TMarkdown.Paragraph( + StrTrimLeft(MarkdownStack.Peek.Markdown), Depth + ) + EOL; +end; + +{ TActiveTextMarkdown.THeadingBlock } + +procedure TActiveTextMarkdown.THeadingBlock.Render(const ALines: IStringList); +begin + Assert(not MarkdownStack.IsEmpty); + ALines.Add(RenderStr); +end; + +function TActiveTextMarkdown.THeadingBlock.RenderStr: string; +begin + Result := EOL + TMarkdown.Heading( + StrTrimLeft(MarkdownStack.Peek.Markdown), 2, Depth + ) + EOL; +end; + +end. + diff --git a/Src/ActiveText.URTFRenderer.pas b/Src/ActiveText.URTFRenderer.pas index 43561c691..216dcdf42 100644 --- a/Src/ActiveText.URTFRenderer.pas +++ b/Src/ActiveText.URTFRenderer.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2012-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2012-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a class and helpers that create RTF representations of active text * with customised styling. @@ -48,11 +45,34 @@ TActiveTextRTFStyleMap = class(TObject) type TActiveTextRTF = class(TObject) strict private + const + // Difference between indent levels in twips + IndentDelta = 360; + // RTF Bullet character + Bullet = #$2022; + type + TListKind = (lkNumber, lkBullet); + TListState = record + public + ListNumber: Cardinal; + ListKind: TListKind; + constructor Create(AListKind: TListKind); + end; + TLIState = record + IsFirstPara: Boolean; + Prefix: string; + constructor Create(AIsFirstPara: Boolean; const APrefix: string); + end; var fElemStyleMap: TActiveTextRTFStyleMap; fDisplayURLs: Boolean; fURLStyle: TRTFStyle; - fInBlock: Boolean; + fBlockStack: TStack<TActiveTextActionElemKind>; + fListStack: TStack<TListState>; + fIndentStack: TStack<SmallInt>; + fLIStack: TStack<TLIState>; + fIndentLevel: Byte; // logical indent level + fInPara: Boolean; procedure SetElemStyleMap(const ElemStyleMap: TActiveTextRTFStyleMap); procedure Initialise(const Builder: TRTFBuilder); procedure RenderTextElem(Elem: IActiveTextTextElem; @@ -63,6 +83,7 @@ TActiveTextRTF = class(TObject) const Builder: TRTFBuilder); procedure RenderURL(Elem: IActiveTextActionElem; const Builder: TRTFBuilder); + function CanEmitInline: Boolean; public constructor Create; destructor Destroy; override; @@ -79,8 +100,10 @@ implementation uses + // Delphi + SysUtils, Generics.Defaults, // Project - SysUtils, Generics.Defaults; + UConsts, UStrUtils; { TActiveTextRTFStyleMap } @@ -158,15 +181,32 @@ procedure TActiveTextRTFStyleMap.MakeMonochrome; { TActiveTextRTF } +function TActiveTextRTF.CanEmitInline: Boolean; +begin + if fBlockStack.Count <= 0 then + Exit(False); + Result := TActiveTextElemCaps.CanContainText(fBlockStack.Peek); +end; + constructor TActiveTextRTF.Create; begin inherited Create; fElemStyleMap := TActiveTextRTFStyleMap.Create; fURLStyle := TRTFStyle.CreateNull; + fBlockStack := TStack<TActiveTextActionElemKind>.Create; + fListStack := TStack<TListState>.Create; + fIndentStack := TStack<SmallInt>.Create; + fLIStack := TStack<TLIState>.Create; + fIndentLevel := 0; + fInPara := False; end; destructor TActiveTextRTF.Destroy; begin + fLIStack.Free; + fIndentStack.Free; + fListStack.Free; + fBlockStack.Free; fElemStyleMap.Free; inherited; end; @@ -192,14 +232,13 @@ procedure TActiveTextRTF.Render(ActiveText: IActiveText; ActionElem: IActiveTextActionElem; begin Initialise(RTFBuilder); - fInBlock := False; for Elem in ActiveText do begin if Supports(Elem, IActiveTextTextElem, TextElem) then RenderTextElem(TextElem, RTFBuilder) else if Supports(Elem, IActiveTextActionElem, ActionElem) then begin - if ActionElem.DisplayStyle = dsBlock then + if TActiveTextElemCaps.DisplayStyleOf(ActionElem.Kind) = dsBlock then RenderBlockActionElem(ActionElem, RTFBuilder) else RenderInlineActionElem(ActionElem, RTFBuilder); @@ -209,19 +248,146 @@ procedure TActiveTextRTF.Render(ActiveText: IActiveText; procedure TActiveTextRTF.RenderBlockActionElem(Elem: IActiveTextActionElem; const Builder: TRTFBuilder); + + procedure OpenListContainer(const ListKind: TListKind); + begin + fListStack.Push(TListState.Create(ListKind)); + Inc(fIndentLevel); + Builder.BeginGroup; + end; + + function IndentTwips: SmallInt; + begin + Result := fElemStyleMap[ekListItem].IndentLevelToTwips(fIndentLevel) + end; + +var + ListState: TListState; + LIState: TLIState; + Style: TRTFStyle; begin case Elem.State of fsOpen: begin - fInBlock := True; - Builder.BeginGroup; - Builder.ApplyStyle(fElemStyleMap[Elem.Kind]); + fInPara := False; + fBlockStack.Push(Elem.Kind); + case Elem.Kind of + ekPara, ekHeading, ekBlock: + begin + Builder.BeginGroup; + Style := fElemStyleMap[Elem.Kind]; + if fLIStack.Count > 0 then + begin + Builder.SetTabStops([IndentTwips]); + if fLIStack.Peek.IsFirstPara then + begin + Builder.SetIndents( + IndentTwips, -fElemStyleMap[ekListItem].IndentDelta + ); + if (fListStack.Count > 0) then + begin + if fListStack.Peek.ListNumber = 1 then + begin + Style.Capabilities := Style.Capabilities + [scParaSpacing]; + if fListStack.Peek.ListKind = lkNumber then + Style.ParaSpacing := TRTFParaSpacing.Create( + fElemStyleMap[ekOrderedList].ParaSpacing.Before, 0.0 + ) + else + Style.ParaSpacing := TRTFParaSpacing.Create( + fElemStyleMap[ekUnorderedList].ParaSpacing.Before, 0.0 + ) + end + else if fListStack.Peek.ListNumber > 1 then + begin + if Elem.Kind = ekHeading then + begin + Style.Capabilities := Style.Capabilities + [scParaSpacing]; + Style.ParaSpacing := fElemStyleMap[ekPara].ParaSpacing; + end; + end; + end; + Builder.ApplyStyle(Style); + Builder.AddText(fLIStack.Peek.Prefix); + Builder.AddText(TAB); + fInPara := True; + end + else + begin + Builder.ApplyStyle(Style); + Builder.SetIndents(IndentTwips, 0); + end; + end + else + begin + Builder.ApplyStyle(Style); + Builder.SetIndents(IndentTwips, 0); + end; + end; + ekUnorderedList: + OpenListContainer(lkBullet); + ekOrderedList: + OpenListContainer(lkNumber); + ekListItem: + begin + // Update list number of current list + ListState := fListStack.Pop; + Inc(ListState.ListNumber, 1); + fListStack.Push(ListState); + Builder.BeginGroup; + Builder.ApplyStyle(fElemStyleMap[Elem.Kind]); + case fListStack.Peek.ListKind of + lkNumber: + begin + fLIStack.Push( + TLIState.Create( + True, IntToStr(fListStack.Peek.ListNumber) + '.' + ) + ); + end; + lkBullet: + begin + fLIStack.Push(TLIState.Create(True, Bullet)); + end; + end; + Builder.ClearParaFormatting; + end; + end; end; fsClose: begin - Builder.EndPara; - Builder.EndGroup; - fInBlock := False; + case Elem.Kind of + ekPara, ekHeading, ekBlock: + begin + if (fLIStack.Count > 0) and (fLIStack.Peek.IsFirstPara) then + begin + // Update item at top of LI stack to record not first para + LIState := fLIStack.Pop; + LIState.IsFirstPara := False; + fLIStack.Push(LIState); + end; + if fInPara then + Builder.EndPara; + Builder.EndGroup; + end; + ekUnorderedList, ekOrderedList: + begin + if fInPara then + Builder.EndPara; + Builder.EndGroup; + fListStack.Pop; + Dec(fIndentLevel); + end; + ekListItem: + begin + if fInPara then + Builder.EndPara; + Builder.EndGroup; + fLIStack.Pop; + end; + end; + fBlockStack.Pop; + fInPara := False; end; end; end; @@ -229,7 +395,7 @@ procedure TActiveTextRTF.RenderBlockActionElem(Elem: IActiveTextActionElem; procedure TActiveTextRTF.RenderInlineActionElem(Elem: IActiveTextActionElem; const Builder: TRTFBuilder); begin - if not fInBlock then + if not CanEmitInline then Exit; case Elem.State of fsOpen: @@ -248,10 +414,20 @@ procedure TActiveTextRTF.RenderInlineActionElem(Elem: IActiveTextActionElem; procedure TActiveTextRTF.RenderTextElem(Elem: IActiveTextTextElem; const Builder: TRTFBuilder); +var + TheText: string; begin - if not fInBlock then + if not CanEmitInline then Exit; - Builder.AddText(Elem.Text); + TheText := Elem.Text; + // no white space emitted after block start until 1st non-white space + // character encountered + if not fInPara then + TheText := StrTrimLeft(Elem.Text); + if TheText = '' then + Exit; + Builder.AddText(TheText); + fInPara := True; end; procedure TActiveTextRTF.RenderURL(Elem: IActiveTextActionElem; @@ -274,5 +450,22 @@ procedure TActiveTextRTF.SetElemStyleMap( fElemStyleMap.Assign(ElemStyleMap); end; +{ TActiveTextRTF.TListState } + +constructor TActiveTextRTF.TListState.Create(AListKind: TListKind); +begin + ListNumber := 0; + ListKind := AListKind; +end; + +{ TActiveTextRTF.TLIState } + +constructor TActiveTextRTF.TLIState.Create(AIsFirstPara: Boolean; + const APrefix: string); +begin + IsFirstPara := AIsFirstPara; + Prefix := APrefix; +end; + end. diff --git a/Src/ActiveText.UTextRenderer.pas b/Src/ActiveText.UTextRenderer.pas index 0ee25a60b..eaec317ff 100644 --- a/Src/ActiveText.UTextRenderer.pas +++ b/Src/ActiveText.UTextRenderer.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2012-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements class that renders active text as plain text in fixed width, word * wrapped paragraphs. @@ -18,17 +15,52 @@ interface uses - SysUtils, - ActiveText.UMain; + SysUtils, Generics.Collections, + ActiveText.UMain, + UConsts; type TActiveTextTextRenderer = class(TObject) strict private + const + /// <summary>Special space character used to indicate the start of a list + /// item.</summary> + /// <remarks>This special character is a necessary kludge because some + /// code that renders active text as formatted plain text strips away + /// leading #32 characters as part of the formatting process. Therefore + /// indentation in list items is lost if #32 characters are used for it. + /// NBSP was chosen since it should render the same as a space if not + /// removed.</remarks> + LISpacer = NBSP; // Do not localise. Must be <> #32 + /// <summary>Bullet character used when rendering unordered list items. + /// </summary> + Bullet = '*'; // Do not localise. Must be <> #32 and <> LISpacer + DefaultIndentDelta = 2; + type + TListKind = (lkNumber, lkBullet); + TListState = record + public + ListNumber: Cardinal; + ListKind: TListKind; + constructor Create(AListKind: TListKind); + end; + TLIState = record + IsFirstPara: Boolean; + constructor Create(AIsFirstPara: Boolean); + end; var fDisplayURLs: Boolean; - fInBlock: Boolean; fParaBuilder: TStringBuilder; fDocBuilder: TStringBuilder; + fBlocksStack: TStack<TActiveTextActionElemKind>; + fListStack: TStack<TListState>; + fLIStack: TStack<TLIState>; + fIndent: UInt16; + fInPara: Boolean; + fInListItem: Boolean; + fIndentDelta: UInt8; + function CanEmitInline: Boolean; + procedure AppendToPara(const AText: string); procedure InitialiseRender; procedure FinaliseRender; procedure OutputParagraph; @@ -36,32 +68,68 @@ TActiveTextTextRenderer = class(TObject) procedure RenderBlockActionElem(Elem: IActiveTextActionElem); procedure RenderInlineActionElem(Elem: IActiveTextActionElem); procedure RenderURL(Elem: IActiveTextActionElem); + function Render(ActiveText: IActiveText): string; public constructor Create; destructor Destroy; override; property DisplayURLs: Boolean read fDisplayURLs write fDisplayURLs default False; - function Render(ActiveText: IActiveText): string; + property IndentDelta: UInt8 read fIndentDelta write fIndentDelta + default DefaultIndentDelta; + function RenderWrapped(ActiveText: IActiveText; const PageWidth, + LMargin: Cardinal): string; end; implementation uses + // Delphi + Character, + // Project + UIStringList, UStrUtils; { TActiveTextTextRenderer } +procedure TActiveTextTextRenderer.AppendToPara(const AText: string); +begin + if AText = '' then + Exit; + fParaBuilder.Append(AText); + fInPara := True; +end; + +function TActiveTextTextRenderer.CanEmitInline: Boolean; +begin + if fBlocksStack.Count <= 0 then + Exit(False); + Result := TActiveTextElemCaps.CanContainText(fBlocksStack.Peek); +end; + constructor TActiveTextTextRenderer.Create; begin + Assert(LISpacer <> ' ', ClassName + '.Create: LISpacer can''t be #32'); + Assert(Bullet <> ' ', ClassName + '.Create: Bullet can''t be #32'); + Assert(Bullet <> LISpacer, ClassName + '.Create: Bullet = LISpacer'); inherited Create; fParaBuilder := TStringBuilder.Create; fDocBuilder := TStringBuilder.Create; fDisplayURLs := False; + fBlocksStack := TStack<TActiveTextActionElemKind>.Create; + fListStack := TStack<TListState>.Create; + fLIStack := TStack<TLIState>.Create; + fIndent := 0; + fInPara := False; + fInListItem := False; + fIndentDelta := DefaultIndentDelta; end; destructor TActiveTextTextRenderer.Destroy; begin + fLIStack.Free; + fListStack.Free; + fBlocksStack.Free; fDocBuilder.Free; fParaBuilder.Free; inherited; @@ -79,11 +147,33 @@ procedure TActiveTextTextRenderer.InitialiseRender; end; procedure TActiveTextTextRenderer.OutputParagraph; +var + LIState: TLIState; begin if fParaBuilder.Length = 0 then Exit; - fDocBuilder.AppendLine(StrTrim(fParaBuilder.ToString)); + fDocBuilder.Append(StrOfChar(NBSP, fIndent)); + if fInListItem and not fLIStack.Peek.IsFirstPara then + // Do we need fInListItem? - test for non-empty list stack? + // if we do need it, put it on list stack + fDocBuilder.Append(StrOfChar(NBSP, IndentDelta)); + if fLIStack.Count > 0 then + begin + if not fLIStack.Peek.IsFirstPara then + begin + fDocBuilder.Append(StrOfChar(NBSP, IndentDelta)); + end + else + begin + // Update item at top of stack + LIState := fLIStack.Pop; + LIState.IsFirstPara := False; + fLIStack.Push(LIState); + end; + end; + fDocBuilder.AppendLine(StrTrimRight(fParaBuilder.ToString)); fParaBuilder.Clear; + fInPara := False; end; function TActiveTextTextRenderer.Render(ActiveText: IActiveText): string; @@ -93,14 +183,13 @@ function TActiveTextTextRenderer.Render(ActiveText: IActiveText): string; ActionElem: IActiveTextActionElem; begin InitialiseRender; - fInBlock := False; for Elem in ActiveText do begin if Supports(Elem, IActiveTextTextElem, TextElem) then RenderTextElem(TextElem) else if Supports(Elem, IActiveTextActionElem, ActionElem) then begin - if ActionElem.DisplayStyle = dsBlock then + if TActiveTextElemCaps.DisplayStyleOf(ActionElem.Kind) = dsBlock then RenderBlockActionElem(ActionElem) else RenderInlineActionElem(ActionElem); @@ -112,16 +201,72 @@ function TActiveTextTextRenderer.Render(ActiveText: IActiveText): string; procedure TActiveTextTextRenderer.RenderBlockActionElem( Elem: IActiveTextActionElem); + + procedure OpenListContainer(const ListKind: TListKind); + begin + if (fListStack.Count > 0) and (fInPara) then + OutputParagraph; + fListStack.Push(TListState.Create(ListKind)); + Inc(fIndent, IndentDelta); + end; + + procedure AddListMarker(const Marker: string); + begin + fParaBuilder.Append(Marker); + fParaBuilder.Append(StringOfChar(NBSP, IndentDelta - Length(Marker))); + end; + +var + ListState: TListState; begin case Elem.State of fsOpen: begin - fInBlock := True; + fBlocksStack.Push(Elem.Kind); + case Elem.Kind of + ekPara, ekHeading, ekBlock: + {Do nothing} ; + ekUnorderedList: + OpenListContainer(lkBullet); + ekOrderedList: + OpenListContainer(lkNumber); + ekListItem: + begin + // Update list number of current list + ListState := fListStack.Pop; + Inc(ListState.ListNumber, 1); + fListStack.Push(ListState); + // Push this list item to list item stack + fLIStack.Push(TLIState.Create(True)); + // Act depending on current list kind + case fListStack.Peek.ListKind of + lkNumber: + AddListMarker(IntToStr(fListStack.Peek.ListNumber)); + lkBullet: + AddListMarker(Bullet); + end; + end; + end; end; fsClose: begin - OutputParagraph; - fInBlock := False; + case Elem.Kind of + ekPara, ekHeading, ekBlock: + OutputParagraph; + ekUnorderedList, ekOrderedList: + begin + OutputParagraph; + fListStack.Pop; + Dec(fIndent, IndentDelta); + end; + ekListItem: + begin + OutputParagraph; + fInListItem := False; + fLIStack.Pop; + end; + end; + fBlocksStack.Pop; end; end; end; @@ -129,17 +274,27 @@ procedure TActiveTextTextRenderer.RenderBlockActionElem( procedure TActiveTextTextRenderer.RenderInlineActionElem( Elem: IActiveTextActionElem); begin - if not fInBlock then + if not CanEmitInline then Exit; if (Elem.Kind = ekLink) and (Elem.State = fsClose) and fDisplayURLs then RenderURL(Elem); + // else ignore element: formatting elements have no effect on plain text end; procedure TActiveTextTextRenderer.RenderTextElem(Elem: IActiveTextTextElem); +var + TheText: string; begin - if not fInBlock then + if not CanEmitInline then + Exit; + TheText := Elem.Text; + // no white space emitted after block start until 1st non-white space + // character encountered + if not fInPara then + TheText := StrTrimLeft(Elem.Text); + if TheText = '' then Exit; - fParaBuilder.Append(Elem.Text); + AppendToPara(TheText); end; procedure TActiveTextTextRenderer.RenderURL(Elem: IActiveTextActionElem); @@ -147,7 +302,101 @@ procedure TActiveTextTextRenderer.RenderURL(Elem: IActiveTextActionElem); sURL = ' (%s)'; // formatting for URLs from hyperlinks begin Assert(Elem.Kind = ekLink, ClassName + '.RenderURL: Not a link element'); - fParaBuilder.AppendFormat(sURL, [Elem.Attrs[TActiveTextAttrNames.Link_URL]]); + AppendToPara(Format(sURL, [Elem.Attrs[TActiveTextAttrNames.Link_URL]])); +end; + +function TActiveTextTextRenderer.RenderWrapped(ActiveText: IActiveText; + const PageWidth, LMargin: Cardinal): + string; +var + Paras: IStringList; + Para: string; + ParaIndent: UInt16; + WrappedPara: string; + Offset: Int16; + + // Calculate indent of paragraph by counting LISpacer characters inserted by + // Render method + function CalcParaIndent: UInt16; + var + Ch: Char; + begin + Result := 0; + for Ch in Para do + begin + if Ch <> LISpacer then + Break; + Inc(Result); + end; + end; + + // Calculate if we are currently processing a list item by detecting Bullet, + // digits and LISpacer characters inserted by Render method + function IsListItem: Boolean; + var + Remainder: string; + Digits: string; + Ch: Char; + begin + Result := False; + // Strip any leading spacer chars from start of para + Remainder := StrTrimLeftChars(Para, LISpacer); + // Check for bullet list: starts with bullet character then spacer + if StrStartsStr(Bullet + LISpacer, Remainder) then + Exit(True); + // Check for number list: starts with digit(s) then spacer + Digits := ''; + for Ch in Remainder do + if TCharacter.IsDigit(Ch) then + Digits := Digits + Ch + else + Break; + if (Digits <> '') and + StrStartsStr(Digits + LISpacer, Remainder) then + Exit(True); + end; + +begin + Result := ''; + Paras := TIStringList.Create(Render(ActiveText), EOL, True); + for Para in Paras do + begin + if IsListItem then + begin + Offset := -IndentDelta; + ParaIndent := CalcParaIndent + LMargin + IndentDelta; + end + else + begin + Offset := 0; + ParaIndent := CalcParaIndent + LMargin; + end; + WrappedPara := StrWrap( + StrReplace(Para, LISpacer, ' '), + PageWidth - ParaIndent, + ParaIndent, + Offset + ); + if Result <> '' then + Result := Result + EOL; + Result := Result + StrTrimRight(WrappedPara); + end; + Result := StrTrimRight(Result); +end; + +{ TActiveTextTextRenderer.TListState } + +constructor TActiveTextTextRenderer.TListState.Create(AListKind: TListKind); +begin + ListNumber := 0; + ListKind := AListKind; +end; + +{ TActiveTextTextRenderer.TLIState } + +constructor TActiveTextTextRenderer.TLIState.Create(AIsFirstPara: Boolean); +begin + IsFirstPara := AIsFirstPara; end; end. diff --git a/Src/ActiveText.UValidator.pas b/Src/ActiveText.UValidator.pas index d1fcc50a7..8cc68143c 100644 --- a/Src/ActiveText.UValidator.pas +++ b/Src/ActiveText.UValidator.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2011-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2011-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a container record that provides methods to validate active text * object. @@ -39,16 +36,11 @@ TErrorInfo = record public /// <summary>Error code.</summary> Code: TErrorCode; - /// <summary>Reference to element causing problem.</summary> - /// <remarks>May be nil if error doesn't relate to an element. - /// </remarks> - Element: IActiveTextElem; /// <summary>Description of error.</summary> Description: string; /// <summary>Constructs a record. Sets fields from parameter values. /// </summary> - constructor Create(const ACode: TErrorCode; AElement: IActiveTextElem; - const ADescription: string); overload; + constructor Create(const ACode: TErrorCode; const ADescription: string); end; strict private /// <summary>Validates given link element.</summary> @@ -59,6 +51,14 @@ TErrorInfo = record /// <returns>Boolean. True on success or False on failure.</returns> class function ValidateLink(LinkElem: IActiveTextActionElem; out ErrInfo: TErrorInfo): Boolean; static; + /// <summary>Validates document structure.</summary> + /// <param name="ActiveText">IActiveText [in] Active text to be validated. + /// </param> + /// <param name="ErrInfo">TErrorInfo [out] Contains error information if + /// validation fails. Undefined if validation succeeds.</param> + /// <returns>Boolean. True on success or False on failure.</returns> + class function ValidateDocumentStructure(ActiveText: IActiveText; + out ErrInfo: TErrorInfo): Boolean; static; public /// <summary>Validates given active text.</summary> /// <param name="ActiveText">IActiveText [in] Active text to be validated. @@ -95,6 +95,9 @@ class function TActiveTextValidator.Validate(ActiveText: IActiveText; begin if ActiveText.IsEmpty then Exit(True); + // Validate document structure + if not ValidateDocumentStructure(ActiveText, ErrInfo) then + Exit(False); // Validate elements for Elem in ActiveText do begin @@ -118,6 +121,16 @@ class function TActiveTextValidator.Validate(ActiveText: IActiveText): Boolean; Result := Validate(ActiveText, Dummy); end; +class function TActiveTextValidator.ValidateDocumentStructure( + ActiveText: IActiveText; out ErrInfo: TErrorInfo): Boolean; +resourcestring + sNoDocTags = 'Document must start and end with document tags'; +begin + Result := ActiveText.IsValidActiveTextDocument; + if not Result then + ErrInfo := TErrorInfo.Create(errBadStructure, sNoDocTags); +end; + class function TActiveTextValidator.ValidateLink( LinkElem: IActiveTextActionElem; out ErrInfo: TErrorInfo): Boolean; resourcestring @@ -153,7 +166,7 @@ TProtocolInfo = record < Length(PI.Protocol) + PI.MinURLLength then begin ErrInfo := TErrorInfo.Create( - errBadLinkURL, LinkElem, Format(sURLLengthErr, [URL]) + errBadLinkURL, Format(sURLLengthErr, [URL]) ); Exit(False); end; @@ -163,17 +176,16 @@ TProtocolInfo = record // No supported protocol Result := False; ErrInfo := TErrorInfo.Create( - errBadLinkProtocol, LinkElem, Format(sURLProtocolErr, [URL]) + errBadLinkProtocol, Format(sURLProtocolErr, [URL]) ); end; { TActiveTextValidator.TErrorInfo } constructor TActiveTextValidator.TErrorInfo.Create(const ACode: TErrorCode; - AElement: IActiveTextElem; const ADescription: string); + const ADescription: string); begin Code := ACode; - Element := AElement; Description := ADescription; end; diff --git a/Src/AutoGen/LICENSE b/Src/AutoGen/LICENSE deleted file mode 100644 index 3bf73d21e..000000000 --- a/Src/AutoGen/LICENSE +++ /dev/null @@ -1,7 +0,0 @@ -Files in the Src/AutoGen directory are auto-generated from other files and are -governed by the licenses that pertain to those files. - -For a list of such files see ReadMe.txt in this directory. - -The ReadMe.txt file itself has any copyright dedicated to the Public Domain. -http://creativecommons.org/publicdomain/zero/1.0/ \ No newline at end of file diff --git a/Src/Browser.IntfDocHostUI.pas b/Src/Browser.IntfDocHostUI.pas index 1c8fda179..25d84c217 100644 --- a/Src/Browser.IntfDocHostUI.pas +++ b/Src/Browser.IntfDocHostUI.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). * * Interfaces, records and constants used when hosting the IE WebBrowser Control * or automating IE to replace the menus, toolbars, and context menus. The diff --git a/Src/Browser.UControlHelper.pas b/Src/Browser.UControlHelper.pas index 44116b621..3f6fdfa4d 100644 --- a/Src/Browser.UControlHelper.pas +++ b/Src/Browser.UControlHelper.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2007-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2007-2021, Peter Johnson (gravatar.com/delphidabbler). * * Defines a static class that provides helper methods for manipulating and * interogating web browser controls. diff --git a/Src/Browser.UController.pas b/Src/Browser.UController.pas index 92cf57d4a..375d8cf90 100644 --- a/Src/Browser.UController.pas +++ b/Src/Browser.UController.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). * * Class that hosts the IE web browser control and enables both direct loading * and saving of browser's HTML and customisation of the user interface. Much of diff --git a/Src/Browser.UHTMLEvents.pas b/Src/Browser.UHTMLEvents.pas index 9c53db45c..3096db404 100644 --- a/Src/Browser.UHTMLEvents.pas +++ b/Src/Browser.UHTMLEvents.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2007-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2007-2021, Peter Johnson (gravatar.com/delphidabbler). * * Provides sinks for events triggered by HTML documents loaded in a TWebBrowser * control. Sinks for the HTMLDocumentEvents2 and HTMLWindowEvents2 diff --git a/Src/Browser.UHighlighter.pas b/Src/Browser.UHighlighter.pas index 0e3d4f30a..68f2ac0e2 100644 --- a/Src/Browser.UHighlighter.pas +++ b/Src/Browser.UHighlighter.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2025, Peter Johnson (gravatar.com/delphidabbler). * * Class that highlights text in web browser that match a search criteria. } @@ -197,7 +194,7 @@ function TWBHighlighter.HighlightWord(const Word: string; begin // Apply highlight to found text by spanning it with highlight style SpanAttrs := THTMLAttributes.Create('style', fHighLightStyle); - Range.pasteHTML(THTML.CompoundTag('span', SpanAttrs, Range.htmlText)); + Range.pasteHTML(TXHTML.CompoundTag('span', SpanAttrs, Range.htmlText)); Inc(Result); end else diff --git a/Src/Browser.UIOMgr.pas b/Src/Browser.UIOMgr.pas index e7e62b020..e9839d37b 100644 --- a/Src/Browser.UIOMgr.pas +++ b/Src/Browser.UIOMgr.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). * * Class that wraps the IE web browser control and provides ability to load and * save HTML from files, streams or strings. Also simplifies navigation to @@ -190,7 +187,6 @@ procedure TWBIOMgr.DocCompleteHandler(Sender: TObject; const pDisp: IDispatch; begin // Top level document has finished loading iff pDisp contains reference to // browser control's default interface. - // See http://support.microsoft.com/kb/180366 if pDisp = (fWB.DefaultInterface as IDispatch) then fDocLoaded := True; end; diff --git a/Src/Browser.UNulUIHandler.pas b/Src/Browser.UNulUIHandler.pas index 1f3c037c1..b7a6794c0 100644 --- a/Src/Browser.UNulUIHandler.pas +++ b/Src/Browser.UNulUIHandler.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). * * Defines a class that provides a "do-nothing" implementation of the * IDocHostUIHandler interface. All methods are "stubbed out" to return values diff --git a/Src/Browser.UUIMgr.pas b/Src/Browser.UUIMgr.pas index 9bc26f695..5ee8189c8 100644 --- a/Src/Browser.UUIMgr.pas +++ b/Src/Browser.UUIMgr.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). * * Contains class that implements IDocHostUIHandler interface and allows * customisation of IE web browser control's user interface, message diff --git a/Src/ClassHelpers.RichEdit.pas b/Src/ClassHelpers.RichEdit.pas new file mode 100644 index 000000000..f82600e6f --- /dev/null +++ b/Src/ClassHelpers.RichEdit.pas @@ -0,0 +1,53 @@ +{ + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/ + * + * Copyright (C) 2025, Peter Johnson (gravatar.com/delphidabbler). + * + * Class helper for TRichEdit. +} + +unit ClassHelpers.RichEdit; + +interface + +uses + // Delphi + ComCtrls, + // Project + URTFUtils; + +type + TRichEditHelper = class helper for TRichEdit + public + procedure Load(const ARTFMarkup: TRTFMarkup); + end; + +implementation + +uses + // Delphi + SysUtils, + Classes; + +{ TRichEditHelper } + +procedure TRichEditHelper.Load(const ARTFMarkup: TRTFMarkup); +var + Stream: TStream; +begin + PlainText := False; + Stream := TMemoryStream.Create; + try + ARTFMarkup.ToStream(Stream); + Stream.Position := 0; + // must set MaxLength or long documents may not display + MaxLength := Stream.Size; + Lines.LoadFromStream(Stream, TEncoding.ASCII); + finally + Stream.Free; + end; +end; + +end. diff --git a/Src/ClassHelpers.UActions.pas b/Src/ClassHelpers.UActions.pas new file mode 100644 index 000000000..d37881ac7 --- /dev/null +++ b/Src/ClassHelpers.UActions.pas @@ -0,0 +1,43 @@ +{ + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/ + * + * Copyright (C) 2012-2024, Peter Johnson (gravatar.com/delphidabbler). + * + * Class helper for TCustomActionList + * + * Extracted in 2024 from original UClassHelpers unit (2012-2021) +} + +unit ClassHelpers.UActions; + +interface + +uses + // Delphi + ActnList; + +type + /// <summary>Class helper that adds a method to TCustomActionList that can + /// update all the actions in the list.</summary> + TActionListHelper = class helper for TCustomActionList + public + /// <summary>Updates all actions in the action list by calling their Update + /// methods.</summary> + procedure Update; + end; + +implementation + +{ TActionListHelper } + +procedure TActionListHelper.Update; +var + Action: TContainedAction; // each action in list +begin + for Action in Self do + Action.Update; +end; + +end. diff --git a/Src/ClassHelpers.UControls.pas b/Src/ClassHelpers.UControls.pas new file mode 100644 index 000000000..2ea885ee0 --- /dev/null +++ b/Src/ClassHelpers.UControls.pas @@ -0,0 +1,68 @@ +{ + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/ + * + * Copyright (C) 2012-2024, Peter Johnson (gravatar.com/delphidabbler). + * + * Class helper for TControl. + * + * Extracted in 2024 from original UClassHelpers unit (2012-2021). +} + +unit ClassHelpers.UControls; + +interface + +uses + // Delphi + Controls, Menus; + +type + /// <summary>Class helper that adds functionality to TControl.</summary> + TControlHelper = class helper for TControl + public + /// <summary>Gets reference to pop-up menu assigned to protected PopupMenu + /// property.</summary> + function GetPopupMenu: TPopupMenu; + /// <summary>Checks if protected PopupMenu property is assigned.</summary> + function HasPopupMenu: Boolean; + /// <summary>Refreshes control's action. Any changes in action that affect + /// state of control are reflected in control.</summary> + procedure RefreshAction; + /// <summary>Refreshes all owned controls to reflect any changes in their + /// associated actions.</summary> + procedure RefreshActions; + end; + +implementation + +{ TControlHelper } + +function TControlHelper.GetPopupMenu: TPopupMenu; +begin + Result := PopupMenu; +end; + +function TControlHelper.HasPopupMenu: Boolean; +begin + Result := Assigned(PopupMenu); +end; + +procedure TControlHelper.RefreshAction; +begin + if Assigned(Action) then + ActionChange(Action, False); +end; + +procedure TControlHelper.RefreshActions; +var + Idx: Integer; // loops through all controls +begin + for Idx := 0 to Pred(ComponentCount) do + if Components[Idx] is TControl then + (Components[Idx] as TControl).RefreshAction; +end; + +end. + diff --git a/Src/UClassHelpers.pas b/Src/ClassHelpers.UGraphics.pas similarity index 61% rename from Src/UClassHelpers.pas rename to Src/ClassHelpers.UGraphics.pas index 240910a9a..d63866cc6 100644 --- a/Src/UClassHelpers.pas +++ b/Src/ClassHelpers.UGraphics.pas @@ -1,44 +1,22 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2012-2013, Peter Johnson (www.delphidabbler.com). + * Copyright (C) 2012-2024, Peter Johnson (gravatar.com/delphidabbler). * - * $Rev$ - * $Date$ + * Provides class helpers for VCL image classes. * - * Provides various class helpers for VCL classes. + * Extracted from in 2024 original UClassHelpers unit (2012-2021). } - -unit UClassHelpers; - +unit ClassHelpers.UGraphics; interface - uses // Delphi - Controls, Menus, ImgList, Graphics, ActnList; - - -type - /// <summary>Class helper that adds functionality to TControl.</summary> - TControlHelper = class helper for TControl - public - /// <summary>Gets reference to pop-up menu assigned to protected PopupMenu - /// property.</summary> - function GetPopupMenu: TPopupMenu; - /// <summary>Checks if protected PopupMenu property is assigned.</summary> - function HasPopupMenu: Boolean; - /// <summary>Refreshes control's action. Any changes in action that affect - /// state of control are reflected in control.</summary> - procedure RefreshAction; - /// <summary>Refreshes all owned controls to reflect any changes in their - /// associated actions.</summary> - procedure RefreshActions; - end; + ImgList, Graphics, GIFImg; type /// <summary>Class helper that adds a method to TCustomImageList that can @@ -64,51 +42,26 @@ TImageListHelper = class helper for TCustomImageList end; type - /// <summary>Class helper that adds a method to TCustomActionList that can - /// update all the actions in the list.</summary> - TActionListHelper = class helper for TCustomActionList + /// <summary>Class helper that adds a method to TGIFImage that adds a similar + /// method to one present in 3rd party TGIFImage to load an image from + /// resources.</summary> + TGIFImageHelper = class helper for TGIFImage public - /// <summary>Updates all actions in the action list by calling their Update - /// methods.</summary> - procedure Update; + /// <summary>Load a GIF image from given resource.</summary> + /// <param name="Module">HINSTANCE [in] Module containing resource.</param> + /// <param name="ResName">string [in] Name of resource to be loaded. + /// </param> + /// <param name="ResType">PChar [in] Type of resource to be loaded.</param> + procedure LoadFromResource(const Module: HMODULE; const ResName: string; + const ResType: PChar); end; - implementation - uses // Delphi Classes; - -{ TControlHelper } - -function TControlHelper.GetPopupMenu: TPopupMenu; -begin - Result := PopupMenu; -end; - -function TControlHelper.HasPopupMenu: Boolean; -begin - Result := Assigned(PopupMenu); -end; - -procedure TControlHelper.RefreshAction; -begin - if Assigned(Action) then - ActionChange(Action, False); -end; - -procedure TControlHelper.RefreshActions; -var - Idx: Integer; // loops through all controls -begin - for Idx := 0 to Pred(ComponentCount) do - if Components[Idx] is TControl then - (Components[Idx] as TControl).RefreshAction; -end; - { TImageListHelper } procedure TImageListHelper.LoadFromResource(ResType: PChar; @@ -167,15 +120,19 @@ procedure TImageListHelper.LoadFromResource(ResType: PChar; end; end; -{ TActionListHelper } +{ TGIFImageHelper } -procedure TActionListHelper.Update; +procedure TGIFImageHelper.LoadFromResource(const Module: HMODULE; + const ResName: string; const ResType: PChar); var - Action: TContainedAction; // each action in list + Stm: TResourceStream; begin - for Action in Self do - Action.Update; + Stm := TResourceStream.Create(Module, ResName, ResType); + try + LoadFromStream(Stm); + finally + Stm.Free; + end; end; end. - diff --git a/Src/CodeSnip.cfg.tplt b/Src/CodeSnip.cfg.tplt index b0cc9e8c9..15a6c786b 100644 --- a/Src/CodeSnip.cfg.tplt +++ b/Src/CodeSnip.cfg.tplt @@ -30,12 +30,12 @@ -M -$M16384,1048576 -K$00400000 --E"..\Exe" --N0"..\Bin" --U"..\Bin;3rdParty" --O"..\Bin;3rdParty" --I"..\Bin;3rdParty" --R"..\Bin;3rdParty" +-E"..\_build\exe" +-N0"..\_build\bin" +-U"..\_build\bin;3rdParty" +-O"..\_build\bin;3rdParty" +-I"..\_build\bin;3rdParty" +-R"..\_build\bin;3rdParty" -w-SYMBOL_PLATFORM -w+EXPLICIT_STRING_CAST_LOSS -w+CVT_WIDENING_STRING_LOST diff --git a/Src/CodeSnip.dpr b/Src/CodeSnip.dpr index 78c718ef7..fa718dacc 100644 --- a/Src/CodeSnip.dpr +++ b/Src/CodeSnip.dpr @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2025, Peter Johnson (gravatar.com/delphidabbler). * * CodeSnip application project file. } @@ -36,7 +33,6 @@ program CodeSnip; uses Forms, Windows, - GIFImage in '3rdParty\GIFImage.pas', LVEx in '3rdParty\LVEx.pas', PJMD5 in '3rdParty\PJMD5.pas', PJShellFolders in '3rdParty\PJShellFolders.pas', @@ -44,7 +40,6 @@ uses PJSysInfo in '3rdParty\PJSysInfo.pas', PJVersionInfo in '3rdParty\PJVersionInfo.pas', PJWdwState in '3rdParty\PJWdwState.pas', - UEncrypt in '3rdParty\UEncrypt.pas', IntfExternalObj in 'AutoGen\IntfExternalObj.pas', ActiveText.UHTMLRenderer in 'ActiveText.UHTMLRenderer.pas', ActiveText.UMain in 'ActiveText.UMain.pas', @@ -71,6 +66,7 @@ uses DB.UCategory in 'DB.UCategory.pas', DB.UDatabaseIO in 'DB.UDatabaseIO.pas', DB.UMain in 'DB.UMain.pas', + DB.UMetaData in 'DB.UMetaData.pas', DB.USnippet in 'DB.USnippet.pas', DB.USnippetKind in 'DB.USnippetKind.pas', DBIO.UFileIOIntf in 'DBIO.UFileIOIntf.pas', @@ -81,6 +77,8 @@ uses Favourites.UFavourites in 'Favourites.UFavourites.pas', Favourites.UPersist in 'Favourites.UPersist.pas', FirstRun.FmV4ConfigDlg in 'FirstRun.FmV4ConfigDlg.pas' {V4ConfigDlg}, + FirstRun.FmWhatsNew in 'FirstRun.FmWhatsNew.pas' {WhatsNewDlg}, + FirstRun.FmWhatsNew.FrHTML in 'FirstRun.FmWhatsNew.FrHTML.pas' {WhatsNewHTMLFrame: TFrame}, FirstRun.UConfigFile in 'FirstRun.UConfigFile.pas', FirstRun.UDatabase in 'FirstRun.UDatabase.pas', FirstRun.UIniFile in 'FirstRun.UIniFile.pas', @@ -94,7 +92,6 @@ uses FmCategoryEditDlg in 'FmCategoryEditDlg.pas' {CategoryEditDlg}, FmCodeExportDlg in 'FmCodeExportDlg.pas' {CodeExportDlg}, FmCodeImportDlg in 'FmCodeImportDlg.pas' {CodeImportDlg}, - FmCodeSubmitDlg in 'FmCodeSubmitDlg.pas' {CodeSubmitDlg}, FmCompErrorDlg in 'FmCompErrorDlg.pas' {CompErrorDlg}, FmCompilersDlg in 'FmCompilersDlg.pas' {CompilersDlg}, FmCompilersDlg.FrBase in 'FmCompilersDlg.FrBase.pas' {CompilersDlgBaseFrame: TFrame}, @@ -108,7 +105,6 @@ uses FmDBUpdateDlg in 'FmDBUpdateDlg.pas' {DBUpdateDlg}, FmDeleteCategoryDlg in 'FmDeleteCategoryDlg.pas' {DeleteCategoryDlg}, FmDependenciesDlg in 'FmDependenciesDlg.pas' {DependenciesDlg}, - FmDonateDlg in 'FmDonateDlg.pas' {DonateDlg}, FmDuplicateSnippetDlg in 'FmDuplicateSnippetDlg.pas' {DuplicateSnippetDlg}, FmEasterEgg in 'FmEasterEgg.pas' {EasterEggForm}, FmFavouritesDlg in 'FmFavouritesDlg.pas' {FavouritesDlg}, @@ -123,13 +119,9 @@ uses FmHelpAware in 'FmHelpAware.pas' {HelpAwareForm}, FmMain in 'FmMain.pas' {MainForm}, FmNewHiliterNameDlg in 'FmNewHiliterNameDlg.pas' {NewHiliterNameDlg}, - FmNewsDlg in 'FmNewsDlg.pas' {NewsDlg}, FmPreferencesDlg in 'FmPreferencesDlg.pas' {PreferencesDlg}, FmPreviewDlg in 'FmPreviewDlg.pas' {PreviewDlg}, FmPrintDlg in 'FmPrintDlg.pas' {PrintDlg}, - FmProgramUpdatesDlg in 'FmProgramUpdatesDlg.pas' {ProgramUpdatesDlg}, - FmProxyServerDlg in 'FmProxyServerDlg.pas' {ProxyServerDlg}, - FmRegistrationDlg in 'FmRegistrationDlg.pas' {RegistrationDlg}, FmRenameCategoryDlg in 'FmRenameCategoryDlg.pas' {RenameCategoryDlg}, FmSelectionSearchDlg in 'FmSelectionSearchDlg.pas' {SelectionSearchDlg}, FmSnippetsEditorDlg in 'FmSnippetsEditorDlg.pas' {SnippetsEditorDlg}, @@ -140,7 +132,7 @@ uses FmTrappedBugReportDlg in 'FmTrappedBugReportDlg.pas' {TrappedBugReportDlg}, FmUserBugReportDlg in 'FmUserBugReportDlg.pas' {UserBugReportDlg}, FmUserDataPathDlg in 'FmUserDataPathDlg.pas' {UserDataPathDlg}, - FmUserDataPathDlg.FrProgress in 'FmUserDataPathDlg.FrProgress.pas' {UserDataPathDlgProgressFrame: TFrame}, + FrProgress in 'FrProgress.pas' {ProgressFrame: TFrame}, FmUserHiliterMgrDlg in 'FmUserHiliterMgrDlg.pas' {UserHiliterMgrDlg}, FmWaitDlg in 'FmWaitDlg.pas' {WaitDlg}, FmWizardDlg in 'FmWizardDlg.pas' {WizardDlg}, @@ -159,11 +151,9 @@ uses FrHTMLPreview in 'FrHTMLPreview.pas' {HTMLPreviewFrame: TFrame}, FrHTMLTpltDlg in 'FrHTMLTpltDlg.pas' {HTMLTpltDlgFrame: TFrame}, FrMemoPreview in 'FrMemoPreview.pas' {MemoPreviewFrame: TFrame}, - FrNewsPrefs in 'FrNewsPrefs.pas' {NewsPrefsFrame: TFrame}, FrOverview in 'FrOverview.pas' {OverviewFrame: TFrame}, FrPrefsBase in 'FrPrefsBase.pas' {PrefsBaseFrame: TFrame}, FrPrintingPrefs in 'FrPrintingPrefs.pas' {PrintingPrefsFrame: TFrame}, - FrRSSNews in 'FrRSSNews.pas' {RSSNewsFrame: TFrame}, FrRTFPreview in 'FrRTFPreview.pas' {RTFPreviewFrame: TFrame}, FrRTFShowCase in 'FrRTFShowCase.pas' {RTFShowCaseFrame: TFrame}, FrCheckedTV in 'FrCheckedTV.pas' {CheckedTVFrame: TFrame}, @@ -174,7 +164,6 @@ uses FrSourcePrefs in 'FrSourcePrefs.pas' {SourcePrefsFrame: TFrame}, FrTextPreview in 'FrTextPreview.pas' {TextPreviewFrame: TFrame}, FrTitled in 'FrTitled.pas' {TitledFrame: TFrame}, - FrUpdatePrefs in 'FrUpdatePrefs.pas' {UpdatePrefsFrame: TFrame}, Hiliter.UAttrs in 'Hiliter.UAttrs.pas', Hiliter.UCSS in 'Hiliter.UCSS.pas', Hiliter.UFileHiliter in 'Hiliter.UFileHiliter.pas', @@ -188,16 +177,12 @@ uses IntfFrameMgrs in 'IntfFrameMgrs.pas', IntfNotifier in 'IntfNotifier.pas', IntfPreview in 'IntfPreview.pas', - Notifications.UDisplayMgr in 'Notifications.UDisplayMgr.pas', - Notifications.UData in 'Notifications.UData.pas', - Notifications.UDisplayThread in 'Notifications.UDisplayThread.pas', - Notifications.UQueue in 'Notifications.UQueue.pas', - Notifications.URecorderThread in 'Notifications.URecorderThread.pas', - Notifications.UWindow in 'Notifications.UWindow.pas', SWAG.UCommon in 'SWAG.UCommon.pas', SWAG.UImporter in 'SWAG.UImporter.pas', SWAG.UReader in 'SWAG.UReader.pas', - SWAG.USnippetCache in 'SWAG.USnippetCache.pas', + SWAG.UPacketCache in 'SWAG.UPacketCache.pas', + SWAG.UVersion in 'SWAG.UVersion.pas', + SWAG.UXMLProcessor in 'SWAG.UXMLProcessor.pas', UActionFactory in 'UActionFactory.pas', UAnchors in 'UAnchors.pas', UAppInfo in 'UAppInfo.pas', @@ -207,7 +192,7 @@ uses UBrowseProtocol in 'UBrowseProtocol.pas', UCategoryAction in 'UCategoryAction.pas', UCategoryListAdapter in 'UCategoryListAdapter.pas', - UClassHelpers in 'UClassHelpers.pas', + ClassHelpers.UControls in 'ClassHelpers.UControls.pas', UClipboardHelper in 'UClipboardHelper.pas', UCodeImportExport in 'UCodeImportExport.pas', UCodeImportMgr in 'UCodeImportMgr.pas', @@ -224,7 +209,6 @@ uses UConsoleApp in 'UConsoleApp.pas', UConsts in 'UConsts.pas', UContainers in 'UContainers.pas', - UContributors in 'UContributors.pas', UControlStateMgr in 'UControlStateMgr.pas', UCopyInfoMgr in 'UCopyInfoMgr.pas', UCopySourceMgr in 'UCopySourceMgr.pas', @@ -241,6 +225,7 @@ uses UDetailPageLoader in 'UDetailPageLoader.pas', UDetailTabAction in 'UDetailTabAction.pas', UDialogMgr in 'UDialogMgr.pas', + UDirectoryCopier in 'UDirectoryCopier.pas', UDispatchList in 'UDispatchList.pas', UDlgHelper in 'UDlgHelper.pas', UDOSDateTime in 'UDOSDateTime.pas', @@ -248,7 +233,6 @@ uses UEditSnippetAction in 'UEditSnippetAction.pas', UEmailHelper in 'UEmailHelper.pas', UEncodings in 'UEncodings.pas', - UEncryptor in 'UEncryptor.pas', UExceptions in 'UExceptions.pas', UExeFileType in 'UExeFileType.pas', UFileProtocol in 'UFileProtocol.pas', @@ -304,21 +288,17 @@ uses UOverviewTreeState in 'UOverviewTreeState.pas', UPageSetupDialogEx in 'UPageSetupDialogEx.pas', UPageSetupDlgMgr in 'UPageSetupDlgMgr.pas', - UPaypalDonateAction in 'UPaypalDonateAction.pas', UPipe in 'UPipe.pas', UPreferences in 'UPreferences.pas', UPrintDocuments in 'UPrintDocuments.pas', UPrintEngine in 'UPrintEngine.pas', UPrintInfo in 'UPrintInfo.pas', UPrintMgr in 'UPrintMgr.pas', - UProgramUpdateChecker in 'UProgramUpdateChecker.pas', UProtocols in 'UProtocols.pas', UQuery in 'UQuery.pas', UREMLDataIO in 'UREMLDataIO.pas', UReservedCategories in 'UReservedCategories.pas', UResourceUtils in 'UResourceUtils.pas', - URFC2822Date in 'URFC2822Date.pas', - URSS20 in 'URSS20.pas', URTFBuilder in 'URTFBuilder.pas', URTFCategoryDoc in 'URTFCategoryDoc.pas', URTFSnippetDoc in 'URTFSnippetDoc.pas', @@ -333,12 +313,10 @@ uses USelectionIOMgr in 'USelectionIOMgr.pas', USettings in 'USettings.pas', UShowCaseCtrl in 'UShowCaseCtrl.pas', - UShowPrefsPageAction in 'UShowPrefsPageAction.pas', USimpleDispatch in 'USimpleDispatch.pas', USingleton in 'USingleton.pas', USnipKindListAdapter in 'USnipKindListAdapter.pas', USnippetAction in 'USnippetAction.pas', - USnippetCreditsParser in 'USnippetCreditsParser.pas', USnippetDoc in 'USnippetDoc.pas', USnippetExtraHelper in 'USnippetExtraHelper.pas', USnippetHTML in 'USnippetHTML.pas', @@ -357,7 +335,6 @@ uses UStringReader in 'UStringReader.pas', UStructs in 'UStructs.pas', UStrUtils in 'UStrUtils.pas', - USystemID in 'USystemID.pas', USystemInfo in 'USystemInfo.pas', UTaggedTextLexer in 'UTaggedTextLexer.pas', UTestCompile in 'UTestCompile.pas', @@ -366,22 +343,18 @@ uses UTestUnitDlgMgr in 'UTestUnitDlgMgr.pas', UTextSnippetDoc in 'UTextSnippetDoc.pas', UThemesEx in 'UThemesEx.pas', - UThreadGroup in 'UThreadGroup.pas', UToolButtonEx in 'UToolButtonEx.pas', UTVCheckBoxes in 'UTVCheckBoxes.pas', UUIWidgetImages in 'UUIWidgetImages.pas', UUniqueID in 'UUniqueID.pas', UUnitAnalyser in 'UUnitAnalyser.pas', UUnitsChkListMgr in 'UUnitsChkListMgr.pas', - UUpdateCheckers in 'UUpdateCheckers.pas', UURIEncode in 'UURIEncode.pas', - UURIParams in 'UURIParams.pas', + UUrl in 'UUrl.pas', UUrlMonEx in 'UUrlMonEx.pas', UUserDBBackup in 'UUserDBBackup.pas', UUserDBMgr in 'UUserDBMgr.pas', UUserDBMove in 'UUserDBMove.pas', - UUserDetails in 'UUserDetails.pas', - UUserDetailsPersist in 'UUserDetailsPersist.pas', UUtils in 'UUtils.pas', UVersionInfo in 'UVersionInfo.pas', UView in 'UView.pas', @@ -396,19 +369,18 @@ uses UXMLDocConsts in 'UXMLDocConsts.pas', UXMLDocHelper in 'UXMLDocHelper.pas', UXMLDocumentEx in 'UXMLDocumentEx.pas', - Web.UBaseWebService in 'Web.UBaseWebService.pas', - Web.UCharEncodings in 'Web.UCharEncodings.pas', - Web.UCodeSubmitter in 'Web.UCodeSubmitter.pas', - Web.UDBDownloadMgr in 'Web.UDBDownloadMgr.pas', - Web.UDownloadMonitor in 'Web.UDownloadMonitor.pas', - Web.UExceptions in 'Web.UExceptions.pas', - Web.UHTTPEx in 'Web.UHTTPEx.pas', - Web.UInfo in 'Web.UInfo.pas', - Web.UProgramUpdateMgr in 'Web.UProgramUpdateMgr.pas', - Web.URegistrar in 'Web.URegistrar.pas', - Web.UStdWebService in 'Web.UStdWebService.pas', - Web.USWAGRESTMgr in 'Web.USWAGRESTMgr.pas', - Web.UXMLRequestor in 'Web.UXMLRequestor.pas'; + FmDeleteUserDBDlg in 'FmDeleteUserDBDlg.pas' {DeleteUserDBDlg}, + Compilers.UAutoDetect in 'Compilers.UAutoDetect.pas', + Compilers.USettings in 'Compilers.USettings.pas', + FmRegisterCompilersDlg in 'FmRegisterCompilersDlg.pas' {RegisterCompilersDlg}, + ClassHelpers.UGraphics in 'ClassHelpers.UGraphics.pas', + ClassHelpers.UActions in 'ClassHelpers.UActions.pas', + USaveInfoMgr in 'USaveInfoMgr.pas', + ClassHelpers.RichEdit in 'ClassHelpers.RichEdit.pas', + UHTMLSnippetDoc in 'UHTMLSnippetDoc.pas', + UMarkdownUtils in 'UMarkdownUtils.pas', + ActiveText.UMarkdownRenderer in 'ActiveText.UMarkdownRenderer.pas', + UMarkdownSnippetDoc in 'UMarkdownSnippetDoc.pas'; // Include resources {$Resource ExternalObj.tlb} // Type library file diff --git a/Src/CodeSnip.dproj b/Src/CodeSnip.dproj index cf4f0b87b..5eaa734a3 100644 --- a/Src/CodeSnip.dproj +++ b/Src/CodeSnip.dproj @@ -16,9 +16,9 @@ <PropertyGroup Condition="'$(Base)'!=''"> <DCC_UnitAlias>WinTypes=Windows;WinProcs=Windows;DbiTypes=BDE;DbiProcs=BDE;DbiErrs=BDE;$(DCC_UnitAlias)</DCC_UnitAlias> <DCC_SYMBOL_PLATFORM>false</DCC_SYMBOL_PLATFORM> - <DCC_ExeOutput>..\Exe</DCC_ExeOutput> - <DCC_UnitSearchPath>..\Bin;3rdParty;$(DCC_UnitSearchPath)</DCC_UnitSearchPath> - <DCC_DependencyCheckOutputName>..\Exe\CodeSnip.exe</DCC_DependencyCheckOutputName> + <DCC_ExeOutput>..\_build\exe</DCC_ExeOutput> + <DCC_UnitSearchPath>..\_build\bin;3rdParty;$(DCC_UnitSearchPath)</DCC_UnitSearchPath> + <DCC_DependencyCheckOutputName>..\_build\exe\CodeSnip.exe</DCC_DependencyCheckOutputName> <DCC_CVT_WIDENING_STRING_LOST>true</DCC_CVT_WIDENING_STRING_LOST> <DCC_CVT_ACHAR_TO_WCHAR>true</DCC_CVT_ACHAR_TO_WCHAR> <DCC_Platform>x86</DCC_Platform> @@ -29,14 +29,13 @@ <DCC_S>false</DCC_S> <DCC_F>false</DCC_F> <DCC_SymbolReferenceInfo>1</DCC_SymbolReferenceInfo> - <DCC_DcuOutput>..\Bin</DCC_DcuOutput> + <DCC_DcuOutput>..\_build\bin</DCC_DcuOutput> <DCC_E>false</DCC_E> </PropertyGroup> <ItemGroup> <DelphiCompile Include="CodeSnip.dpr"> <MainSource>MainSource</MainSource> </DelphiCompile> - <DCCReference Include="3rdParty\GIFImage.pas"/> <DCCReference Include="3rdParty\LVEx.pas"/> <DCCReference Include="3rdParty\PJMD5.pas"/> <DCCReference Include="3rdParty\PJShellFolders.pas"/> @@ -44,7 +43,6 @@ <DCCReference Include="3rdParty\PJSysInfo.pas"/> <DCCReference Include="3rdParty\PJVersionInfo.pas"/> <DCCReference Include="3rdParty\PJWdwState.pas"/> - <DCCReference Include="3rdParty\UEncrypt.pas"/> <DCCReference Include="AutoGen\IntfExternalObj.pas"/> <DCCReference Include="ActiveText.UHTMLRenderer.pas"/> <DCCReference Include="ActiveText.UMain.pas"/> @@ -71,6 +69,7 @@ <DCCReference Include="DB.UCategory.pas"/> <DCCReference Include="DB.UDatabaseIO.pas"/> <DCCReference Include="DB.UMain.pas"/> + <DCCReference Include="DB.UMetaData.pas"/> <DCCReference Include="DB.USnippet.pas"/> <DCCReference Include="DB.USnippetKind.pas"/> <DCCReference Include="DBIO.UFileIOIntf.pas"/> @@ -83,6 +82,13 @@ <DCCReference Include="FirstRun.FmV4ConfigDlg.pas"> <Form>V4ConfigDlg</Form> </DCCReference> + <DCCReference Include="FirstRun.FmWhatsNew.pas"> + <Form>WhatsNewDlg</Form> + </DCCReference> + <DCCReference Include="FirstRun.FmWhatsNew.FrHTML.pas"> + <Form>WhatsNewHTMLFrame</Form> + <DesignClass>TFrame</DesignClass> + </DCCReference> <DCCReference Include="FirstRun.UConfigFile.pas"/> <DCCReference Include="FirstRun.UDatabase.pas"/> <DCCReference Include="FirstRun.UIniFile.pas"/> @@ -112,9 +118,6 @@ <DCCReference Include="FmCodeImportDlg.pas"> <Form>CodeImportDlg</Form> </DCCReference> - <DCCReference Include="FmCodeSubmitDlg.pas"> - <Form>CodeSubmitDlg</Form> - </DCCReference> <DCCReference Include="FmCompErrorDlg.pas"> <Form>CompErrorDlg</Form> </DCCReference> @@ -156,9 +159,6 @@ <DCCReference Include="FmDependenciesDlg.pas"> <Form>DependenciesDlg</Form> </DCCReference> - <DCCReference Include="FmDonateDlg.pas"> - <Form>DonateDlg</Form> - </DCCReference> <DCCReference Include="FmDuplicateSnippetDlg.pas"> <Form>DuplicateSnippetDlg</Form> </DCCReference> @@ -201,9 +201,6 @@ <DCCReference Include="FmNewHiliterNameDlg.pas"> <Form>NewHiliterNameDlg</Form> </DCCReference> - <DCCReference Include="FmNewsDlg.pas"> - <Form>NewsDlg</Form> - </DCCReference> <DCCReference Include="FmPreferencesDlg.pas"> <Form>PreferencesDlg</Form> </DCCReference> @@ -213,15 +210,6 @@ <DCCReference Include="FmPrintDlg.pas"> <Form>PrintDlg</Form> </DCCReference> - <DCCReference Include="FmProgramUpdatesDlg.pas"> - <Form>ProgramUpdatesDlg</Form> - </DCCReference> - <DCCReference Include="FmProxyServerDlg.pas"> - <Form>ProxyServerDlg</Form> - </DCCReference> - <DCCReference Include="FmRegistrationDlg.pas"> - <Form>RegistrationDlg</Form> - </DCCReference> <DCCReference Include="FmRenameCategoryDlg.pas"> <Form>RenameCategoryDlg</Form> </DCCReference> @@ -253,8 +241,8 @@ <DCCReference Include="FmUserDataPathDlg.pas"> <Form>UserDataPathDlg</Form> </DCCReference> - <DCCReference Include="FmUserDataPathDlg.FrProgress.pas"> - <Form>UserDataPathDlgProgressFrame</Form> + <DCCReference Include="FrProgress.pas"> + <Form>ProgressFrame</Form> <DesignClass>TFrame</DesignClass> </DCCReference> <DCCReference Include="FmUserHiliterMgrDlg.pas"> @@ -326,10 +314,6 @@ <Form>MemoPreviewFrame</Form> <DesignClass>TFrame</DesignClass> </DCCReference> - <DCCReference Include="FrNewsPrefs.pas"> - <Form>NewsPrefsFrame</Form> - <DesignClass>TFrame</DesignClass> - </DCCReference> <DCCReference Include="FrOverview.pas"> <Form>OverviewFrame</Form> <DesignClass>TFrame</DesignClass> @@ -342,10 +326,6 @@ <Form>PrintingPrefsFrame</Form> <DesignClass>TFrame</DesignClass> </DCCReference> - <DCCReference Include="FrRSSNews.pas"> - <Form>RSSNewsFrame</Form> - <DesignClass>TFrame</DesignClass> - </DCCReference> <DCCReference Include="FrRTFPreview.pas"> <Form>RTFPreviewFrame</Form> <DesignClass>TFrame</DesignClass> @@ -386,10 +366,6 @@ <Form>TitledFrame</Form> <DesignClass>TFrame</DesignClass> </DCCReference> - <DCCReference Include="FrUpdatePrefs.pas"> - <Form>UpdatePrefsFrame</Form> - <DesignClass>TFrame</DesignClass> - </DCCReference> <DCCReference Include="Hiliter.UAttrs.pas"/> <DCCReference Include="Hiliter.UCSS.pas"/> <DCCReference Include="Hiliter.UFileHiliter.pas"/> @@ -403,16 +379,12 @@ <DCCReference Include="IntfFrameMgrs.pas"/> <DCCReference Include="IntfNotifier.pas"/> <DCCReference Include="IntfPreview.pas"/> - <DCCReference Include="Notifications.UDisplayMgr.pas"/> - <DCCReference Include="Notifications.UData.pas"/> - <DCCReference Include="Notifications.UDisplayThread.pas"/> - <DCCReference Include="Notifications.UQueue.pas"/> - <DCCReference Include="Notifications.URecorderThread.pas"/> - <DCCReference Include="Notifications.UWindow.pas"/> <DCCReference Include="SWAG.UCommon.pas"/> <DCCReference Include="SWAG.UImporter.pas"/> <DCCReference Include="SWAG.UReader.pas"/> - <DCCReference Include="SWAG.USnippetCache.pas"/> + <DCCReference Include="SWAG.UPacketCache.pas"/> + <DCCReference Include="SWAG.UVersion.pas"/> + <DCCReference Include="SWAG.UXMLProcessor.pas"/> <DCCReference Include="UActionFactory.pas"/> <DCCReference Include="UAnchors.pas"/> <DCCReference Include="UAppInfo.pas"/> @@ -422,7 +394,7 @@ <DCCReference Include="UBrowseProtocol.pas"/> <DCCReference Include="UCategoryAction.pas"/> <DCCReference Include="UCategoryListAdapter.pas"/> - <DCCReference Include="UClassHelpers.pas"/> + <DCCReference Include="ClassHelpers.UControls.pas"/> <DCCReference Include="UClipboardHelper.pas"/> <DCCReference Include="UCodeImportExport.pas"/> <DCCReference Include="UCodeImportMgr.pas"/> @@ -439,7 +411,6 @@ <DCCReference Include="UConsoleApp.pas"/> <DCCReference Include="UConsts.pas"/> <DCCReference Include="UContainers.pas"/> - <DCCReference Include="UContributors.pas"/> <DCCReference Include="UControlStateMgr.pas"/> <DCCReference Include="UCopyInfoMgr.pas"/> <DCCReference Include="UCopySourceMgr.pas"/> @@ -456,6 +427,7 @@ <DCCReference Include="UDetailPageLoader.pas"/> <DCCReference Include="UDetailTabAction.pas"/> <DCCReference Include="UDialogMgr.pas"/> + <DCCReference Include="UDirectoryCopier.pas"/> <DCCReference Include="UDispatchList.pas"/> <DCCReference Include="UDlgHelper.pas"/> <DCCReference Include="UDOSDateTime.pas"/> @@ -463,7 +435,6 @@ <DCCReference Include="UEditSnippetAction.pas"/> <DCCReference Include="UEmailHelper.pas"/> <DCCReference Include="UEncodings.pas"/> - <DCCReference Include="UEncryptor.pas"/> <DCCReference Include="UExceptions.pas"/> <DCCReference Include="UExeFileType.pas"/> <DCCReference Include="UFileProtocol.pas"/> @@ -519,21 +490,17 @@ <DCCReference Include="UOverviewTreeState.pas"/> <DCCReference Include="UPageSetupDialogEx.pas"/> <DCCReference Include="UPageSetupDlgMgr.pas"/> - <DCCReference Include="UPaypalDonateAction.pas"/> <DCCReference Include="UPipe.pas"/> <DCCReference Include="UPreferences.pas"/> <DCCReference Include="UPrintDocuments.pas"/> <DCCReference Include="UPrintEngine.pas"/> <DCCReference Include="UPrintInfo.pas"/> <DCCReference Include="UPrintMgr.pas"/> - <DCCReference Include="UProgramUpdateChecker.pas"/> <DCCReference Include="UProtocols.pas"/> <DCCReference Include="UQuery.pas"/> <DCCReference Include="UREMLDataIO.pas"/> <DCCReference Include="UReservedCategories.pas"/> <DCCReference Include="UResourceUtils.pas"/> - <DCCReference Include="URFC2822Date.pas"/> - <DCCReference Include="URSS20.pas"/> <DCCReference Include="URTFBuilder.pas"/> <DCCReference Include="URTFCategoryDoc.pas"/> <DCCReference Include="URTFSnippetDoc.pas"/> @@ -548,12 +515,10 @@ <DCCReference Include="USelectionIOMgr.pas"/> <DCCReference Include="USettings.pas"/> <DCCReference Include="UShowCaseCtrl.pas"/> - <DCCReference Include="UShowPrefsPageAction.pas"/> <DCCReference Include="USimpleDispatch.pas"/> <DCCReference Include="USingleton.pas"/> <DCCReference Include="USnipKindListAdapter.pas"/> <DCCReference Include="USnippetAction.pas"/> - <DCCReference Include="USnippetCreditsParser.pas"/> <DCCReference Include="USnippetDoc.pas"/> <DCCReference Include="USnippetExtraHelper.pas"/> <DCCReference Include="USnippetHTML.pas"/> @@ -572,7 +537,6 @@ <DCCReference Include="UStringReader.pas"/> <DCCReference Include="UStructs.pas"/> <DCCReference Include="UStrUtils.pas"/> - <DCCReference Include="USystemID.pas"/> <DCCReference Include="USystemInfo.pas"/> <DCCReference Include="UTaggedTextLexer.pas"/> <DCCReference Include="UTestCompile.pas"/> @@ -581,22 +545,18 @@ <DCCReference Include="UTestUnitDlgMgr.pas"/> <DCCReference Include="UTextSnippetDoc.pas"/> <DCCReference Include="UThemesEx.pas"/> - <DCCReference Include="UThreadGroup.pas"/> <DCCReference Include="UToolButtonEx.pas"/> <DCCReference Include="UTVCheckBoxes.pas"/> <DCCReference Include="UUIWidgetImages.pas"/> <DCCReference Include="UUniqueID.pas"/> <DCCReference Include="UUnitAnalyser.pas"/> <DCCReference Include="UUnitsChkListMgr.pas"/> - <DCCReference Include="UUpdateCheckers.pas"/> <DCCReference Include="UURIEncode.pas"/> - <DCCReference Include="UURIParams.pas"/> + <DCCReference Include="UUrl.pas"/> <DCCReference Include="UUrlMonEx.pas"/> <DCCReference Include="UUserDBBackup.pas"/> <DCCReference Include="UUserDBMgr.pas"/> <DCCReference Include="UUserDBMove.pas"/> - <DCCReference Include="UUserDetails.pas"/> - <DCCReference Include="UUserDetailsPersist.pas"/> <DCCReference Include="UUtils.pas"/> <DCCReference Include="UVersionInfo.pas"/> <DCCReference Include="UView.pas"/> @@ -611,19 +571,22 @@ <DCCReference Include="UXMLDocConsts.pas"/> <DCCReference Include="UXMLDocHelper.pas"/> <DCCReference Include="UXMLDocumentEx.pas"/> - <DCCReference Include="Web.UBaseWebService.pas"/> - <DCCReference Include="Web.UCharEncodings.pas"/> - <DCCReference Include="Web.UCodeSubmitter.pas"/> - <DCCReference Include="Web.UDBDownloadMgr.pas"/> - <DCCReference Include="Web.UDownloadMonitor.pas"/> - <DCCReference Include="Web.UExceptions.pas"/> - <DCCReference Include="Web.UHTTPEx.pas"/> - <DCCReference Include="Web.UInfo.pas"/> - <DCCReference Include="Web.UProgramUpdateMgr.pas"/> - <DCCReference Include="Web.URegistrar.pas"/> - <DCCReference Include="Web.UStdWebService.pas"/> - <DCCReference Include="Web.USWAGRESTMgr.pas"/> - <DCCReference Include="Web.UXMLRequestor.pas"/> + <DCCReference Include="FmDeleteUserDBDlg.pas"> + <Form>DeleteUserDBDlg</Form> + </DCCReference> + <DCCReference Include="Compilers.UAutoDetect.pas"/> + <DCCReference Include="Compilers.USettings.pas"/> + <DCCReference Include="FmRegisterCompilersDlg.pas"> + <Form>RegisterCompilersDlg</Form> + </DCCReference> + <DCCReference Include="ClassHelpers.UGraphics.pas"/> + <DCCReference Include="ClassHelpers.UActions.pas"/> + <DCCReference Include="USaveInfoMgr.pas"/> + <DCCReference Include="ClassHelpers.RichEdit.pas"/> + <DCCReference Include="UHTMLSnippetDoc.pas"/> + <DCCReference Include="UMarkdownUtils.pas"/> + <DCCReference Include="ActiveText.UMarkdownRenderer.pas"/> + <DCCReference Include="UMarkdownSnippetDoc.pas"/> <None Include="CodeSnip.todo"/> <BuildConfiguration Include="Base"> <Key>Base</Key> diff --git a/Src/CompilerChecks.inc b/Src/CompilerChecks.inc index ffe0d8b06..31efffcca 100644 --- a/Src/CompilerChecks.inc +++ b/Src/CompilerChecks.inc @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2009-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2009-2021, Peter Johnson (gravatar.com/delphidabbler). * * Include file that checks details of compiler to determine whether or not * CodeSnip can be compiled. diff --git a/Src/Compilers.UAutoDetect.pas b/Src/Compilers.UAutoDetect.pas new file mode 100644 index 000000000..40d1bebea --- /dev/null +++ b/Src/Compilers.UAutoDetect.pas @@ -0,0 +1,115 @@ +{ + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/ + * + * Copyright (C) 2022, Peter Johnson (gravatar.com/delphidabbler). + * + * Implements a static class that can detect and register Delphi compilers + * present on the user's system that are not yet registered with CodeSnip. +} + + +unit Compilers.UAutoDetect; + +interface + +uses + Compilers.UGlobals, + Compilers.UCompilers, + UBaseObjects; + +type + TCompilerAutoDetect = class(TNoConstructObject) + public + type + TCallback = reference to procedure (Compiler: ICompiler); + strict private + class procedure DoCallback(const Callback: TCallback; + Compiler: ICompiler); + public + class procedure RegisterCompilers(Compilers: ICompilers; + const Callback: TCallback = nil); overload; + class procedure RegisterSpecificCompilers(AllCompilers: ICompilers; + const RegList: TCompilerList; const Callback: TCallback = nil); + class procedure ListRegisterableCompilers(Compilers: ICompilers; + const Registerable: TCompilerList); + end; + +implementation + +uses + SysUtils; + +{ TCompilerAutoDetect } + +class procedure TCompilerAutoDetect.DoCallback( + const Callback: TCallback; Compiler: ICompiler); +begin + if Assigned(Callback) then + Callback(Compiler); +end; + +class procedure TCompilerAutoDetect.ListRegisterableCompilers( + Compilers: ICompilers; const Registerable: TCompilerList); +var + Compiler: ICompiler; +begin + Registerable.Clear; + for Compiler in Compilers do + begin + if not Supports(Compiler, ICompilerAutoDetect) then + Continue; // compiler can't be auto-detected/registered + if not (Compiler as ICompilerAutoDetect).IsInstalled then + Continue; // compiler is not installed on the user's system + if Compiler.IsAvailable then + Continue; // compiler installed & already registered for use by CodeSnip + if not (Compiler as ICompilerAutoDetect).GetCanAutoInstall then + Continue; // user has excluded this compiler from being auto-registered + // We get here then we have an installed, un-registered, auto-detectable + // compiler with permission to register + Registerable.Add(Compiler); + end; +end; + +class procedure TCompilerAutoDetect.RegisterSpecificCompilers( + AllCompilers: ICompilers; const RegList: TCompilerList; + const Callback: TCallback); +var + Compiler: ICompiler; +begin + for Compiler in AllCompilers do + begin + if RegList.IndexOf(Compiler) >= 0 then + begin + Assert(Supports(Compiler, ICompilerAutoDetect), ClassName + + '.RegisterCompilers: Compiler does not support ICompilerAutoDetect'); + if (Compiler as ICompilerAutoDetect).DetectExeFile then + DoCallback(Callback, Compiler); + end; + end; +end; + +class procedure TCompilerAutoDetect.RegisterCompilers(Compilers: ICompilers; + const Callback: TCallback); +var + Registerable: TCompilerList; + Compiler: ICompiler; +begin + Registerable := TCompilerList.Create; + try + ListRegisterableCompilers(Compilers, Registerable); + for Compiler in Registerable do + begin + Assert(Supports(Compiler, ICompilerAutoDetect), ClassName + + '.RegisterCompilers: Compiler does not support ICompilerAutoDetect'); + if (Compiler as ICompilerAutoDetect).DetectExeFile then + DoCallback(Callback, Compiler); + end; + finally + Registerable.Free; + end; +end; + +end. + diff --git a/Src/Compilers.UBDS.pas b/Src/Compilers.UBDS.pas index b26c7882e..509e0f993 100644 --- a/Src/Compilers.UBDS.pas +++ b/Src/Compilers.UBDS.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2006-2015, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2006-2024, Peter Johnson (gravatar.com/delphidabbler). * * Class that controls and provides information about Borland CodeGear and * Embarcadero "BDS" Win32 compilers. @@ -145,6 +142,18 @@ function TBDSCompiler.GetIDString: string; Result := 'DXE8'; ciD10S: Result := 'D10S'; + ciD101B: + Result := 'D101B'; + ciD102T: + Result := 'D102T'; + ciD103R: + Result := 'D103R'; + ciD104S: + Result := 'D104S'; + ciD11A: + Result := 'D11A'; + ciD12A: + Result := 'D12Y'; else raise EBug.Create(ClassName + '.GetIDString: Invalid ID'); end; @@ -152,16 +161,24 @@ function TBDSCompiler.GetIDString: string; function TBDSCompiler.GetName: string; resourcestring - sCompilerName = 'Delphi %d'; // template for name of compiler - sDelphiXE = 'Delphi XE'; // name of Delphi XE compiler - sDelphiXE2 = 'Delphi XE2'; // name of Delphi XE2 compiler - sDelphiXE3 = 'Delphi XE3'; // name of Delphi XE3 compiler - sDelphiXE4 = 'Delphi XE4'; // name of Delphi XE4 compiler - sDelphiXE5 = 'Delphi XE5'; // name of Delphi XE5 compiler - sDelphiXE6 = 'Delphi XE6'; // name of Delphi XE6 compiler - sDelphiXE7 = 'Delphi XE7'; // name of Delphi XE7 compiler - sDelphiXE8 = 'Delphi XE8'; // name of Delphi XE8 compiler - sDelphi10S = 'Delphi 10 Seattle'; // name of Delphi 10 compiler + // template for name of compiler with simple integer version number + sCompilerName = 'Delphi %d'; + // names of compilers not suitable for use with template + sDelphiXE = 'Delphi XE'; + sDelphiXE2 = 'Delphi XE2'; + sDelphiXE3 = 'Delphi XE3'; + sDelphiXE4 = 'Delphi XE4'; + sDelphiXE5 = 'Delphi XE5'; + sDelphiXE6 = 'Delphi XE6'; + sDelphiXE7 = 'Delphi XE7'; + sDelphiXE8 = 'Delphi XE8'; + sDelphi10S = 'Delphi 10'; // Seattle + sDelphi101B = 'Delphi 10.1'; // Berlin + sDelphi102T = 'Delphi 10.2'; // Tokyo + sDelphi103R = 'Delphi 10.3'; // Rio + sDelphi104S = 'Delphi 10.4'; // Sydney + sDelphi11A = 'Delphi 11.x'; // Alexandria + sDelphi12A = 'Delphi 12.x'; // Athens begin case GetID of ciDXE: @@ -182,6 +199,18 @@ function TBDSCompiler.GetName: string; Result := sDelphiXE8; ciD10S: Result := sDelphi10S; + ciD101B: + Result := sDelphi101B; + ciD102T: + Result := sDelphi102T; + ciD103R: + Result := sDelphi103R; + ciD104S: + Result := sDelphi104S; + ciD11A: + Result := sDelphi11A; + ciD12A: + Result := sDelphi12A; else Result := Format(sCompilerName, [ProductVersion]); end; @@ -211,6 +240,12 @@ function TBDSCompiler.InstallationRegKey: string; ciDXE7 : Result := '\Software\Embarcadero\BDS\15.0'; ciDXE8 : Result := '\Software\Embarcadero\BDS\16.0'; ciD10S : Result := '\Software\Embarcadero\BDS\17.0'; + ciD101B : Result := '\Software\Embarcadero\BDS\18.0'; + ciD102T : Result := '\Software\Embarcadero\BDS\19.0'; + ciD103R : Result := '\Software\Embarcadero\BDS\20.0'; + ciD104S : Result := '\Software\Embarcadero\BDS\21.0'; + ciD11A : Result := '\Software\Embarcadero\BDS\22.0'; + ciD12A : Result := '\Software\Embarcadero\BDS\23.0'; else raise EBug.Create(ClassName + '.InstallationRegKey: Invalid ID'); end; end; diff --git a/Src/Compilers.UBorland.pas b/Src/Compilers.UBorland.pas index 0024be877..79d680290 100644 --- a/Src/Compilers.UBorland.pas +++ b/Src/Compilers.UBorland.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2006-2014, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2006-2021, Peter Johnson (gravatar.com/delphidabbler). * * Abstract base class for classes that control and provide information about * Borland compilers. @@ -38,11 +35,22 @@ TBorlandCompiler = class(TCompilerBase) var fId: TCompilerID; {Identifies compiler} + // Flags whether user permits compiler to be auto installed. + fCanAutoInstall: Boolean; function InstallPathFromReg(const RootKey: HKEY): string; {Gets compiler install root path from given registry root key, if present. @param RootKey [in] Given registry root key. @return Required root path or '' if not compiler not installed. } + /// <summary>Gets the path to the compiler exe file if the compiler is + /// registered as installed on the user's computer.</summary> + /// <param name="ExePath">string [out] Set to path to compiler executable + /// file. Empty string if compiler not installed.</param> + /// <returns>Boolean. True if compiler is registered as installed or False + /// otherwise.</returns> + /// <remarks>Does not check if compiler exe is actually present, just if + /// it is registered.</remarks> + function GetExePathIfInstalled(out ExePath: string): Boolean; strict protected function SearchDirParams: string; override; {One of more parameters that define any search directories to be passed @@ -64,10 +72,35 @@ TBorlandCompiler = class(TCompilerBase) @param Obj Compiler object to copy. } { ICompilerAutoDetect } + /// <summary>Detects and records path to command line compiler exe file, + /// if compiler is registered as installed.</summary> + /// <returns>Boolean. True if compiler is registered as installed, False + /// otherwise.</returns> + /// <remarks> + /// <para>Does not check if the compiler exe file actually exists.</para> + /// <para>Does not set compiler exe file if compiler is not installed. + /// </para> + /// <para>Method of ICompilerAutoDetect.</para> + /// </remarks> function DetectExeFile: Boolean; - {Detects and records path to command line compiler if present. - @return True if compiler path found, false otherwise. - } + /// <summary>Checks if the compiler is installed on the user's system. + /// </summary> + /// <returns>Boolean. True if compiler is physically installed, False + /// otherwise.</returns> + /// <remarks> + /// <para>Checks if compiler exe is actually present.</para> + /// <para>Method of ICompilerAutoDetect.</para> + /// </remarks> + function IsInstalled: Boolean; + /// <summary>Checks if the compiler is permitted to be automatically + /// installed.</summary> + /// <remarks>Method of ICompilerAutoDetect.</remarks> + function GetCanAutoInstall: Boolean; + /// <summary>Determines whether the compiler can be automatically + /// installed.</summary> + /// <remarks>Method of ICompilerAutoDetect.</remarks> + procedure SetCanAutoInstall(const Value: Boolean); + { ICompiler } function GetDefaultSwitches: string; override; {Returns default command line switches for compiler. @@ -91,7 +124,7 @@ implementation uses // Delphi - SysUtils, Registry, + SysUtils, Registry, IOUtils, // Project UIStringList, UStrUtils, USystemInfo; @@ -112,6 +145,7 @@ constructor TBorlandCompiler.CreateCopy(const Obj: TBorlandCompiler); begin inherited CreateCopy(Obj); fId := Obj.GetID; + fCanAutoInstall := Obj.GetCanAutoInstall; end; procedure TBorlandCompiler.DeleteObjFiles(const Path, Project: string); @@ -131,17 +165,16 @@ function TBorlandCompiler.DetectExeFile: Boolean; @return True if compiler path found, false otherwise. } var - InstDir: string; // installation root directory + ExePath: string; begin - // try HKLM - InstDir := InstallPathFromReg(HKEY_LOCAL_MACHINE); - if InstDir = '' then - // in case install was for user only, try HKCU - InstDir := InstallPathFromReg(HKEY_CURRENT_USER); - if InstDir = '' then - Exit(False); - SetExecFile(IncludeTrailingPathDelimiter(InstDir) + 'Bin\DCC32.exe'); - Result := True; + Result := GetExePathIfInstalled(ExePath); + if Result then + SetExecFile(ExePath); +end; + +function TBorlandCompiler.GetCanAutoInstall: Boolean; +begin + Result := fCanAutoInstall; end; function TBorlandCompiler.GetDefaultSwitches: string; @@ -164,6 +197,22 @@ function TBorlandCompiler.GetDefaultSwitches: string; + '-$P+'; // Open string params ON end; +function TBorlandCompiler.GetExePathIfInstalled(out ExePath: string): Boolean; +var + InstDir: string; +begin + ExePath := ''; + // try HKLM + InstDir := InstallPathFromReg(HKEY_LOCAL_MACHINE); + if InstDir = '' then + // in case install was for user only, try HKCU + InstDir := InstallPathFromReg(HKEY_CURRENT_USER); + if InstDir = '' then + Exit(False); + ExePath := TPath.Combine(InstDir, 'Bin\DCC32.exe'); + Result := True; +end; + function TBorlandCompiler.GetID: TCompilerID; {Provides the unique id of the compiler. @return Compiler id. @@ -195,6 +244,15 @@ function TBorlandCompiler.InstallPathFromReg(const RootKey: HKEY): string; end; end; +function TBorlandCompiler.IsInstalled: Boolean; +var + ExePath: string; +begin + if not GetExePathIfInstalled(ExePath) then + Exit(False); + Result := TFile.Exists(ExePath, False); +end; + function TBorlandCompiler.SearchDirParams: string; {One of more parameters that define any search directories to be passed to compiler on command line. @@ -212,5 +270,10 @@ function TBorlandCompiler.SearchDirParams: string; + ' ' + StrQuoteSpaced('-R' + Dirs.GetText(';', False)); end; +procedure TBorlandCompiler.SetCanAutoInstall(const Value: Boolean); +begin + fCanAutoInstall := Value; +end; + end. diff --git a/Src/Compilers.UCompilerBase.pas b/Src/Compilers.UCompilerBase.pas index 54f28479c..e7517c33a 100644 --- a/Src/Compilers.UCompilerBase.pas +++ b/Src/Compilers.UCompilerBase.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). * * Abstract base class for classes that control and provide information about * compilers. Also provides a specialised exception class. diff --git a/Src/Compilers.UCompilers.pas b/Src/Compilers.UCompilers.pas index 170f012e5..92b6e7881 100644 --- a/Src/Compilers.UCompilers.pas +++ b/Src/Compilers.UCompilers.pas @@ -2,12 +2,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). * * Provides a class that maintains a list of all supported compilers and creates * a global singleton instance of the list. Also provides a class that can @@ -23,6 +20,8 @@ interface uses + // Delphi + Generics.Collections, // Project Compilers.UGlobals, UBaseObjects; @@ -67,13 +66,18 @@ TCompilersFactory = class(TNoConstructObject) } end; + TCompilerList = class(TList<ICompiler>) + public + constructor Create; + end; + implementation uses // Delphi - Generics.Collections, SysUtils, + Generics.Defaults, SysUtils, // Project Compilers.UBDS, Compilers.UDelphi, Compilers.UFreePascal, Compilers.USearchDirs, IntfCommon, UConsts, UExceptions, UIStringList, @@ -331,6 +335,12 @@ procedure TPersistCompilers.Load(const Compilers: ICompilers); // Load search directories SearchDirNames := Storage.GetStrings('SearchDirCount', 'SearchDir%d'); Compiler.SetSearchDirs(TSearchDirs.Create(SearchDirNames.ToArray)); + + // Check if compiler can be auto-detected + if Supports(Compiler, ICompilerAutoDetect) then + (Compiler as ICompilerAutoDetect).SetCanAutoInstall( + Storage.GetBoolean('CanAutoInstall', True) + ); end; end; @@ -366,10 +376,28 @@ procedure TPersistCompilers.Save(const Compilers: ICompilers); Storage.SetString('Namespaces', Compiler.GetRTLNamespaces); SearchDirNames := TIStringList.Create(Compiler.GetSearchDirs.ToStrings); Storage.SetStrings('SearchDirCount', 'SearchDir%d', SearchDirNames); + if Supports(Compiler, ICompilerAutoDetect) then + Storage.SetBoolean( + 'CanAutoInstall', (Compiler as ICompilerAutoDetect).GetCanAutoInstall + ); // save the data Storage.Save; end; end; +{ TCompilerList } + +constructor TCompilerList.Create; +begin + inherited Create( + TDelegatedComparer<ICompiler>.Create( + function (const Left, Right: ICompiler): Integer + begin + Result := Ord(Left.GetID) - Ord(Right.GetID); + end + ) + ) +end; + end. diff --git a/Src/Compilers.UDelphi.pas b/Src/Compilers.UDelphi.pas index dc91a3963..6ca68436e 100644 --- a/Src/Compilers.UDelphi.pas +++ b/Src/Compilers.UDelphi.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). * * Class that controls and provides information about the Delphi v2-7 compilers. } diff --git a/Src/Compilers.UFreePascal.pas b/Src/Compilers.UFreePascal.pas index 70d89c810..82c4fbc38 100644 --- a/Src/Compilers.UFreePascal.pas +++ b/Src/Compilers.UFreePascal.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). * * Implements class that wraps the Free Pascal compiler. Controls compilation, * processes compiler output and provides information about the compiler. diff --git a/Src/Compilers.UGlobals.pas b/Src/Compilers.UGlobals.pas index f21abf1be..7e660a166 100644 --- a/Src/Compilers.UGlobals.pas +++ b/Src/Compilers.UGlobals.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2015, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2023, Peter Johnson (gravatar.com/delphidabbler). * * Declares various types that describe the compiler and compilation results and * defines interfaces to compiler objects. @@ -42,6 +39,12 @@ interface ciDXE7, // Delphi XE7 ciDXE8, // Delphi XE8 ciD10S, // Delphi 10 Seattle + ciD101B, // Delphi 10.1 Berlin + ciD102T, // Delphi 10.2 Tokyo + ciD103R, // Delphi 10.3 Rio + ciD104S, // Delphi 10.4 Sydney, + ciD11A, // Delphi 11.x Alexandria + ciD12A, // Delphi 12 Athens ciFPC // Free Pascal ); @@ -54,7 +57,8 @@ interface /// compilers.</summary> cBDSCompilers = [ ciD2005w32, ciD2006w32, ciD2007, ciD2009w32, ciD2010, ciDXE, ciDXE2, - ciDXE3, ciDXE4, ciDXE5, ciDXE6, ciDXE7, ciDXE8, ciD10S + ciDXE3, ciDXE4, ciDXE5, ciDXE6, ciDXE7, ciDXE8, ciD10S, ciD101B, ciD102T, + ciD103R, ciD104S, ciD11A, ciD12A ]; const @@ -296,7 +300,7 @@ interface property Count: Integer read GetCount; /// <summary>Number of compilers installed on this computer and made - /// available CodeSnip.</summary> + /// available to CodeSnip.</summary> property AvailableCount: Integer read GetAvailableCount; /// <summary>Checks if any compilers in the list are displayable.</summary> @@ -324,6 +328,15 @@ interface /// <summary>Detects and records the full path of the compiler's /// executable.</summary> function DetectExeFile: Boolean; + /// <summary>Checks if the compiler is installed on the user's system. + /// </summary> + function IsInstalled: Boolean; + /// <summary>Checks if the compiler is permitted to be automatically + /// installed.</summary> + function GetCanAutoInstall: Boolean; + /// <summary>Determines whether the compiler can be automatically + /// installed.</summary> + procedure SetCanAutoInstall(const Value: Boolean); end; diff --git a/Src/Compilers.URunner.pas b/Src/Compilers.URunner.pas index 31b50ec9e..96630ca1f 100644 --- a/Src/Compilers.URunner.pas +++ b/Src/Compilers.URunner.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2006-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2006-2021, Peter Johnson (gravatar.com/delphidabbler). * * Implements a class that executes a compiler and captures its output and exit * code. Also provides specialised exception object that captures information diff --git a/Src/Compilers.USearchDirs.pas b/Src/Compilers.USearchDirs.pas index 611b7765d..5c2199016 100644 --- a/Src/Compilers.USearchDirs.pas +++ b/Src/Compilers.USearchDirs.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2011-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2011-2021, Peter Johnson (gravatar.com/delphidabbler). * * Implements a class that encapsulates a list of search directories for use * by the compiler. diff --git a/Src/Compilers.USettings.pas b/Src/Compilers.USettings.pas new file mode 100644 index 000000000..2a1fe77ae --- /dev/null +++ b/Src/Compilers.USettings.pas @@ -0,0 +1,83 @@ +{ + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/ + * + * Copyright (C) 2022-2023, Peter Johnson (gravatar.com/delphidabbler). + * + * Class that reads and writes settings that apply to all compilers. +} + + +unit Compilers.USettings; + +interface + +uses + // Delphi + SysUtils, + // Project + UBaseObjects, + USettings; + +type + /// <summary>Manages settings that apply to all compilers.</summary> + TCompilerSettings = class(TNoConstructObject) + strict private + const + AllCompilersConfigSection = ssCompilers; + PermitStartupDetectionKey = 'PermitStartupDetection'; + class function ReadStorage: ISettingsSection; + class procedure DoSaveProperty(const WriteProp: TProc<ISettingsSection>); + class procedure SaveProperty(const Key: string; const Value: Boolean); + class function GetPermitStartupDetection: Boolean; static; + class procedure SetPermitStartupDetection(const Value: Boolean); static; + public + class property PermitStartupDetection: Boolean + read GetPermitStartupDetection write SetPermitStartupDetection + default True; + end; + +implementation + +{ TCompilerSettings } + +class procedure TCompilerSettings.DoSaveProperty( + const WriteProp: TProc<ISettingsSection>); +var + Stg: ISettingsSection; +begin + Stg := ReadStorage; + WriteProp(Stg); + Stg.Save; +end; + +class function TCompilerSettings.GetPermitStartupDetection: Boolean; +begin + Result := ReadStorage.GetBoolean(PermitStartupDetectionKey, True); +end; + +class function TCompilerSettings.ReadStorage: ISettingsSection; +begin + Result := Settings.ReadSection(AllCompilersConfigSection); +end; + +class procedure TCompilerSettings.SaveProperty(const Key: string; + const Value: Boolean); +begin + DoSaveProperty( + procedure(Stg: ISettingsSection) + begin + Stg.SetBoolean(Key, Value) + end + ); +end; + +class procedure TCompilerSettings.SetPermitStartupDetection( + const Value: Boolean); +begin + SaveProperty(PermitStartupDetectionKey, Value); +end; + +end. + diff --git a/Src/DB.UCategory.pas b/Src/DB.UCategory.pas index 815486fa3..c5860d3c0 100644 --- a/Src/DB.UCategory.pas +++ b/Src/DB.UCategory.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2011-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2011-2021, Peter Johnson (gravatar.com/delphidabbler). * * Objects, records etc that encapsulate a category, its data and lists of * categories. diff --git a/Src/DB.UDatabaseIO.pas b/Src/DB.UDatabaseIO.pas index e791ca19f..4fe633fbb 100644 --- a/Src/DB.UDatabaseIO.pas +++ b/Src/DB.UDatabaseIO.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2008-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2008-2021, Peter Johnson (gravatar.com/delphidabbler). * * Implements objects that can load data into the Database object from both the * user and main databases. Also provides a class that can write the user diff --git a/Src/DB.UMain.pas b/Src/DB.UMain.pas index 1f8daf38b..6b61183e1 100644 --- a/Src/DB.UMain.pas +++ b/Src/DB.UMain.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2023, Peter Johnson (gravatar.com/delphidabbler). * * Defines a singleton object and subsidiary classes that encapsulate the * snippets and categories in the CodeSnip database and user defined databases. @@ -957,11 +954,9 @@ procedure TDatabase.Load; try // Load main database: MUST do this first since user database can // reference objects in main database - with TDatabaseIOFactory.CreateMainDBLoader do - Load(fSnippets, fCategories, Factory); + TDatabaseIOFactory.CreateMainDBLoader.Load(fSnippets, fCategories, Factory); // Load any user database - with TDatabaseIOFactory.CreateUserDBLoader do - Load(fSnippets, fCategories, Factory); + TDatabaseIOFactory.CreateUserDBLoader.Load(fSnippets, fCategories, Factory); fUpdated := False; except // If an exception occurs clear the database @@ -987,8 +982,7 @@ procedure TDatabase.Save; // Create object that can provide required information about user database Provider := TUserDataProvider.Create(fSnippets, fCategories); // Use a writer object to write out the database - with TDatabaseIOFactory.CreateWriter do - Write(fSnippets, fCategories, Provider); + TDatabaseIOFactory.CreateWriter.Write(fSnippets, fCategories, Provider); fUpdated := False; end; diff --git a/Src/DB.UMetaData.pas b/Src/DB.UMetaData.pas new file mode 100644 index 000000000..1c26520f3 --- /dev/null +++ b/Src/DB.UMetaData.pas @@ -0,0 +1,961 @@ +{ + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/ + * + * Copyright (C) 2020-2021, Peter Johnson (gravatar.com/delphidabbler). + * + * Provides meta data for the current main database and for any database + * updates. +} + + +unit DB.UMetaData; + +{ + Notes About Database Versions And Meta Files. + ============================================= + + Versions + -------- + + v1 of the Code Snippets Database was not considered to have a version number + until the arrival of v2. Therefore v1 did not have any means of identifying + its version number. Although the database format changed slightly over time + there is not enough historical information to identify different minor + releases, so all are considered to be v1.0.0.0 + + Database v2 has a VERSION file that specifies the version number as a dotted + quad, of the form v2.0.0.0. + + Note that semantic versioning is now being used so any database with major + version 2 will be backwards compatible with earlier v2 minor release. + + If a breaking change is introduced the major version will be bumped to v3 and + so on. + + Meta Files + ---------- + + Database v1 had only two meta files: + + contrib.txt - lists of contributors to the database, one per line + + testers.txt - lists of database testers, one per line + If those two files are present the database is assumed to be v1 + + Database v2 has the following meta files: + + VERSION - version number (v2.x.x.x) + + CONTRIBUTORS - lists of contributors to the database, one per line + + TESTERS - lists of database testers, one per line + + LICENSE - full text of the license that applies to the snippets + + LICENSE-INFO - furter information about the license + + For database v1 the license text, license information are hard-wired in the + absence of meta files. As noted above the version number is deduced. + + File encoding + ------------- + + All meta files are plain text. + + Early v1 database meta files were encoded in the system default encoding. + Later v1 databases encoded meta files in UTF-8. To distinguish between UTF-8 + and default encoded files the UTF-8 files use the UTF-8 preamble (BOM). + + v2 database meta files are all encoded in UTF-8 with preamble (BOM). + + Future major versions + --------------------- + + Future major database releases MUST include backwards compatibility with + earlier versions of CodeSnip in as much as those versions must be able to + easily detect and reject the format. + + To achieve this new major releases MUST include a VERSION file encoded in + UTF-8 with BOM. Existing code can detect and will reject any unsupported + version. + + CodeSnip support + ---------------- + + CodeSnip versions earlier than v4.16 are only ever delivered v1 database files + via their built in web update code. There is no way for these versions to + access v2 databases. + + CodeSnip from v4.16 get their main snippets database from files downloaded + manually. All such databases are v2. CodeSnip v4.16 also supports v1 format + because such a database may be resident on the user's computer at install + time. +} + + +interface + + +uses + // VCL + Classes, + // Project + UExceptions, + UIStringList, + UVersionInfo; + + +type + + /// <summary>Record providing information about the main database license. + /// </summary> + TDBLicenseInfo = record + strict private + fName: string; + fSPDX: string; + fURL: string; + fText: string; + public + /// <summary>Record constructor: sets all fields of record.</summary> + constructor Create(const AName, ASPDX, AURL, AText: string); + /// <summary>Name of license.</summary> + property Name: string read fName; + /// <summary>Open Source Initiative SPDX short idenitifier for licenses. + /// </summary> + /// <remarks>If the license is not supported by the Open Source Initiative + /// then this property will be the empty string.</remarks> + property SPDX: string read fSPDX; + /// <summary>URL of license online.</summary> + /// <remarks>Optional.</remarks> + property URL: string read fURL; + /// <summary>Full text of license.</summary> + property Text: string read fText; + /// <summary>Returns a string containing license name followed by any URL + /// in parentheses.</summary> + /// <remarks>If no URL is available then only the license name is returned. + /// </remarks> + function NameWithURL: string; + end; + + /// <summary>Record providing informaton about the main database copyright. + /// </summary> + TDBCopyrightInfo = record + strict private + fDate: string; + fHolder: string; + fHolderURL: string; + public + /// <summary>Record constructor: sets all fields of record.</summary> + constructor Create(const ADate, AHolder, AHolderURL: string); + /// <summary>Copyright date.</summary> + /// <remarks>May be a single year or a range: e.g. 2020 or 2012-2016. + /// </remarks> + property Date: string read fDate; + /// <summary>Name of copyright holder.</summary> + property Holder: string read fHolder; + /// <summary>URL of main copyright holder.</summary> + /// <remarks>Optional.</remarks> + property HolderURL: string read fHolderURL; + end; + + /// <summary>Interface supported by classes providing database meta data. + /// </summary> + IDBMetaData = interface(IInterface) + /// <summary>Returns database version number.</summary> + /// <remarks>A null version number is returned if the meta data does not + /// come from a recognised database.</remarks> + function GetVersion: TVersionNumber; + /// <summary>Returns database license information.</summary> + /// <remarks>Return value is meaningless if the meta data does not come + /// from a supported database.</remarks> + function GetLicenseInfo: TDBLicenseInfo; + /// <summary>Returns database copyright informatiom.</summary> + /// <remarks>Return value is meaningless if the meta data does not come + /// from a supported database.</remarks> + function GetCopyrightInfo: TDBCopyrightInfo; + /// <summary>Returns list of contributors to database.</summary> + /// <remarks>Return value is meaningless if the meta data does not come + /// from a supported database.</remarks> + function GetContributors: IStringList; + /// <summary>Returns list of testers of database.</summary> + /// <remarks>Return value is meaningless if the meta data does not come + /// from a supported database.</remarks> + function GetTesters: IStringList; + /// <summary>Checks if meta data is recognised as belonging to a valid + /// database, whether supported or not.</summary> + function IsRecognised: Boolean; + /// <summary>Checks if meta data is recognised as belonging to a supported + /// database version.</summary> + function IsSupportedVersion: Boolean; + /// <summary>Checks if meta data is corrupt.</summary> + /// <summary>Should only be called if meta data belongs to a supported + /// database. An exception should be raised if called on unsupported + /// versions.</summary> + function IsCorrupt: Boolean; + /// <summary>Refreshes the meta information by re-reading from database + /// meta files.</summary> + procedure Refresh; + end; + + /// <summary>Factory that creates instances of objects that provide + /// information about the main database and database updates.</summary> + TMainDBMetaDataFactory = record + public + /// <summary>Returns instance of class that provides meta data for the + /// main database.</summary> + class function MainDBMetaDataInstance: IDBMetaData; static; + /// <summary>Returns instance of class that provides meta data for the + /// database update stored in the given folder.</summary> + class function UpdateMetaDataInstance(const UpdateDir: string): + IDBMetaData; static; + end; + + /// <summary>Class of exception raised by meta data classes.</summary> + EDBMetaData = class(ECodeSnip); + + +implementation + + +uses + // Project + SysUtils, + IOUtils, + Types, + // VCL + UAppInfo, + UEncodings, + UIOUtils, + UResourceUtils, + UStructs, + UStrUtils; + + +const + DBValidVersions: TRange = (Min: 1; Max: 2); + +type + + /// <summary>Provides names of meta data files supported by various database + /// versions.</summary> + TDBMetaFileNames = record + public + const + ContributorsV1 = 'contrib.txt'; + TestersV1 = 'testers.txt'; + VersionV2AndLater = 'VERSION'; + LicenseV2 = 'LICENSE'; + LicenseInfoV2 = 'LICENSE-INFO'; + ContributorsV2 = 'CONTRIBUTORS'; + TestersV2 = 'TESTERS'; + end; + + /// <summary>Abstract base class for classes that access or emulate database + /// meta data files.</summary> + TDBMetaFiles = class abstract(TObject) + strict private + var + /// <summary>Directory of database for which meta data files are being + /// accessed.</summary> + fDBDir: string; + /// <summary>Returns encoding used by given meta file.</summary> + function GetFileEncoding(const FileName: TFileName): TEncoding; + strict protected + /// <summary>Makes a fully specified path to a given database file. + /// </summary> + /// <remarks>FileName must contain no path information.</remarks> + function MakePath(const FileName: string): string; + /// <summary>Checks if a given file exists in database directory.</summary> + function DBFileExists(const FileName: string): Boolean; + /// <summary>Reads all lines from given file and returns them as an array. + /// </summary> + /// <remarks>FileName must contain no path information.</remarks> + function ReadFileLines(const FileName: TFileName): TStringDynArray; + /// <summary>Reads all text from given file and returns it.</summary> + /// <remarks>FileName must contain no path information.</remarks> + function ReadFileText(const FileName: TFileName): string; + public + /// <summary>Constructs object that accesses meta data database files in + /// given directory.</summary> + constructor Create(const DBDir: string); + /// <summary>Returns text of the version file, or surrogate value. + /// </summary> + function Version: string; virtual; abstract; + /// <summary>Returns content license text file or surrogate value. + /// </summary> + function LicenseText: string; virtual; abstract; + /// <summary>Returns lines of license information file or surrogate value. + /// </summary> + function LicenseInfo: TStringDynArray; virtual; abstract; + /// <summary>Returns lines of contributors file or surrogate value. + /// </summary> + function Contributors: TStringDynArray; virtual; abstract; + /// <summary>Returns lines of testers file or surrogate value.</summary> + function Testers: TStringDynArray; virtual; abstract; + /// <summary>Checks if all the expected meta files are present. Returns + /// True if so or False if not.</summary> + /// <remarks>ENotSupportedException must be raised if called on an + /// unsupported database version.</remarks> + function AreAllFilesPresent: Boolean; virtual; abstract; + end; + + /// <summary>Class that accesses content of version 1 main database meta data + /// files.</summary> + /// <remarks>Not all meta files are present in version main databases so + /// invariant placeholder content is provided to substitute for missing + /// files.</remarks> + TV1DBMetaFiles = class sealed(TDBMetaFiles) + strict private + const + cContributorsFile = TDBMetaFileNames.ContributorsV1; + cTestersFile = TDBMetaFileNames.TestersV1; + public + /// <summary>Returns an surrogate, invariant value of 1 for the version + /// number.</summary> + /// <remarks>No version file exists for this database version. The value + /// returned is deduced using documentation from the current code snippets + /// database project.</remarks> + function Version: string; override; + /// <summary>Returns an surrogate, invariant value for the license text. + /// </summary> + /// <remarks>No license text file exists for this database version. The + /// value returned is based on documentation of the database.</remarks> + function LicenseText: string; override; + /// <summary>Returns an surrogate, invariant value for the lines of license + /// information.</summary> + function LicenseInfo: TStringDynArray; override; + /// <summary>Returns the lines of the contributors file.</summary> + function Contributors: TStringDynArray; override; + /// <summary>Returns the lines of the testers file.</summary> + function Testers: TStringDynArray; override; + /// <summary>Checks if all the expected meta files are present. Returns + /// True if so or False if not.</summary> + function AreAllFilesPresent: Boolean; override; + end; + + /// <summary>Class that accesses content of supported version 2 main database + /// meta files.</summary> + TV2DBMetaFiles = class sealed(TDBMetaFiles) + strict private + const + cVersionFile = TDBMetaFileNames.VersionV2AndLater; + cLicenseFile = TDBMetaFileNames.LicenseV2; + cLicenseInfoFile = TDBMetaFileNames.LicenseInfoV2; + cContributorsFile = TDBMetaFileNames.ContributorsV2; + cTestersFile = TDBMetaFileNames.TestersV2; + public + /// <summary>Returns the contents of the version file.</summary> + function Version: string; override; + /// <summary>Returns the contents of the license text file.</summary> + function LicenseText: string; override; + /// <summary>Returns the lines of the license info file.</summary> + function LicenseInfo: TStringDynArray; override; + /// <summary>Returns the lines of the contributors file.</summary> + function Contributors: TStringDynArray; override; + /// <summary>Returns the lines of the testers file.</summary> + function Testers: TStringDynArray; override; + /// <summary>Checks if all the expected meta files are present. Returns + /// True if so or False if not.</summary> + function AreAllFilesPresent: Boolean; override; + end; + + /// <summary>Class that represents later versions of database meta file + /// formats.</summary> + /// <remarks>These formats have a valid version file but the version is not + /// supported and nothing is known about any other meta data files.</remarks> + TLaterDBMetaFiles = class sealed(TDBMetaFiles) + strict private + const + cVersionFile = TDBMetaFileNames.VersionV2AndLater; + public + /// <summary>Returns the contents of the version file.</summary> + function Version: string; override; + /// <summary>Returns the empty string.</summary> + /// <remarks>The file format is unknown, so the license text file cannot be + /// read and there is no information to deduce the value.</remarks> + function LicenseText: string; override; + /// <summary>Returns an empty string array.</summary> + /// <remarks>The file format is unknown, so the license information file + /// cannot be read and there is no information to deduce the value. + /// </remarks> + function LicenseInfo: TStringDynArray; override; + /// <summary>Returns an empty string array.</summary> + /// <remarks>The file format is unknown, so the contributors file cannot be + /// read and there is no information to deduce the value.</remarks> + function Contributors: TStringDynArray; override; + /// <summary>Returns an empty string array.</summary> + /// <remarks>The file format is unknown, so the testers file cannot be read + /// and there is no information to deduce the value.</remarks> + function Testers: TStringDynArray; override; + /// <summary>Checks if all the expected meta files are present only if + /// the meta files come from a supported database format.</summary> + /// <exception>ENotSupportedException always raised since there is no way + /// of knowing what files should be present in an unsupported database + /// format.</exception> + function AreAllFilesPresent: Boolean; override; + end; + + /// <summary>Class that is present to represent unknown database meta file + /// formats. Also used when database is not present.</summary> + /// <remarks>Accesses no files and returns null results for all methods + /// except IsVersionSupported.</remarks> + TUnknownOrMissingMetaFiles = class sealed(TDBMetaFiles) + public + /// <summary>Returns the empty string.</summary> + /// <remarks>The file format is unknown, so the version file cannot be read + /// and there is no information to deduce the value.</remarks> + function Version: string; override; + /// <summary>Returns the empty string.</summary> + /// <remarks>The file format is unknown, so the license text file cannot be + /// read and there is no information to deduce the value.</remarks> + function LicenseText: string; override; + /// <summary>Returns an empty string array.</summary> + /// <remarks>The file format is unknown, so the license information file + /// cannot be read and there is no information to deduce the value. + /// </remarks> + function LicenseInfo: TStringDynArray; override; + /// <summary>Returns an empty string array.</summary> + /// <remarks>The file format is unknown, so the contributors file cannot be + /// read and there is no information to deduce the value.</remarks> + function Contributors: TStringDynArray; override; + /// <summary>Returns an empty string array.</summary> + /// <remarks>The file format is unknown, so the testers file cannot be read + /// and there is no information to deduce the value.</remarks> + function Testers: TStringDynArray; override; + /// <summary>Checks if all the expected meta files are present only if + /// the meta files come from a supported database format.</summary> + /// <exception>ENotSupportedException always raised since there is no way + /// of knowing what files should be present in an unrecognised database + /// format.</exception> + function AreAllFilesPresent: Boolean; override; + end; + + /// <summary>Factory to create the correct instance of database meta file + /// classes that can read the version of a database in a given folder. + /// </summary> + TDBMetaFilesFactory = record + public + class function GetInstance(const DBDir: string): TDBMetaFiles; + static; + end; + + /// <summary>Abstract base class for classes that provide main database meta + /// data.</summary> + TAbstractMainDBMetaData = class abstract(TInterfacedObject) + strict private + var + fMetaFiles: TDBMetaFiles; + fIsVersionLoaded: Boolean; + fVersion: TVersionNumber; + fIsLicenseAndCopyrightInfoLoaded: Boolean; + fLicenseInfo: TDBLicenseInfo; + fCopyrightInfo: TDBCopyrightInfo; + fContributors: IStringList; + fTesters: IStringList; + + procedure LoadLicenseAndCopyrightInfo; + + strict protected + function GetDBDir: string; virtual; abstract; + public + constructor Create; + procedure AfterConstruction; override; + destructor Destroy; override; + /// <summary>Returns database version number.</summary> + /// <remarks> + /// <para>A null version number is returned if the meta data does not come + /// from a recognised database.</para> + /// <para>Method of IDBMetaData.</para> + /// </remarks> + function GetVersion: TVersionNumber; + /// <summary>Returns database license information.</summary> + /// <remarks> + /// <para>Return value is meaningless if the meta data does not come + /// from a supported database.</para> + /// <para>Method of IDBMetaData.</para> + /// </remarks> + function GetLicenseInfo: TDBLicenseInfo; + /// <summary>Returns database copyright informatiom.</summary> + /// <remarks> + /// <para>Return value is meaningless if the meta data does not come + /// from a supported database.</para> + /// <para>Method of IDBMetaData.</para> + /// </remarks> + function GetCopyrightInfo: TDBCopyrightInfo; + /// <summary>Returns list of contributors to database.</summary> + /// <remarks> + /// <para>Return value is meaningless if the meta data does not come + /// from a supported database.</para> + /// <para>Method of IDBMetaData.</para> + /// </remarks> + function GetContributors: IStringList; + /// <summary>Returns list of testers of database.</summary> + /// <remarks> + /// <para>Return value is meaningless if the meta data does not come + /// from a supported database.</para> + /// <para>Method of IDBMetaData.</para> + /// </remarks> + function GetTesters: IStringList; + /// <summary>Checks if meta data is recognised as belonging to a valid + /// database, whether supported or not.</summary> + /// <remarks>Method of IDBMetaData.</remarks> + function IsRecognised: Boolean; + /// <summary>Checks if meta data is recognised as belonging to a supported + /// database version.</summary> + /// <remarks>Method of IDBMetaData.</remarks> + function IsSupportedVersion: Boolean; + /// <summary>Checks if meta data is corrupt.</summary> + /// <remarks> + /// <para>Should only be called if meta data belongs to a supported + /// database.</para> + /// <para>Method of IDBMetaData.</para> + /// </remarks> + /// <exception>ENotSupportedException raised if called on an unsupported + /// database.</exception> + function IsCorrupt: Boolean; + /// <summary>Refreshes the meta information by re-reading from database + /// meta files.</summary> + /// <remarks>Method of IDBMetaData.</summary> + procedure Refresh; + end; + + /// <summary>Class that provides meta data for the main database.</summary> + TMainDBMetaData = class sealed(TAbstractMainDBMetaData, IDBMetaData) + strict protected + function GetDBDir: string; override; + end; + + /// <summary>Class that provides meta data for update database directories. + /// </summary> + TUpdateDBMetaData = class sealed(TAbstractMainDBMetaData, IDBMetaData) + strict private + var + fUpdateDir: string; + strict protected + function GetDBDir: string; override; + public + constructor Create(const UpdateDir: string); + end; + +{ TMainDBMetaDataFactory } + +class function TMainDBMetaDataFactory.MainDBMetaDataInstance: + IDBMetaData; +begin + Result := TMainDBMetaData.Create; +end; + +class function TMainDBMetaDataFactory.UpdateMetaDataInstance( + const UpdateDir: string): IDBMetaData; +begin + Result := TUpdateDBMetaData.Create(UpdateDir); +end; + +{ TAbstractMainDBMetaData } + +procedure TAbstractMainDBMetaData.AfterConstruction; +begin + inherited; + Refresh; +end; + +constructor TAbstractMainDBMetaData.Create; +begin + inherited; +// Refresh; +end; + +destructor TAbstractMainDBMetaData.Destroy; +begin + fMetaFiles.Free; + inherited; +end; + +function TAbstractMainDBMetaData.GetContributors: IStringList; +begin + if not Assigned(fContributors) then + fContributors := TIStringList.Create(fMetaFiles.Contributors); + Result := fContributors; +end; + +function TAbstractMainDBMetaData.GetCopyrightInfo: TDBCopyrightInfo; +begin + if not fIsLicenseAndCopyrightInfoLoaded then + LoadLicenseAndCopyrightInfo; + Result := fCopyrightInfo; +end; + +function TAbstractMainDBMetaData.GetLicenseInfo: TDBLicenseInfo; +begin + if not fIsLicenseAndCopyrightInfoLoaded then + LoadLicenseAndCopyrightInfo; + Result := fLicenseInfo; +end; + +function TAbstractMainDBMetaData.GetTesters: IStringList; +begin + if not Assigned(fTesters) then + fTesters := TIStringList.Create(fMetaFiles.Testers); + Result := fTesters; +end; + +function TAbstractMainDBMetaData.GetVersion: TVersionNumber; +begin + if not fIsVersionLoaded then + begin + if not TVersionNumber.TryStrToVersionNumber( + StrTrim(fMetaFiles.Version), fVersion + ) then + fVersion := TVersionNumber.Nul; + end; + fIsVersionLoaded := True; + Result := fVersion; +end; + +function TAbstractMainDBMetaData.IsCorrupt: Boolean; +resourcestring + sNotSupportedError = 'Can''t call IDBMetaData.IsCorrupt for an unsupported ' + + 'database version'; +begin + if not IsSupportedVersion then + raise ENotSupportedException.Create(sNotSupportedError); + Result := not fMetaFiles.AreAllFilesPresent; +end; + +function TAbstractMainDBMetaData.IsRecognised: Boolean; +begin + Result := not GetVersion.IsNull; +end; + +function TAbstractMainDBMetaData.IsSupportedVersion: Boolean; +var + ThisVersion: TVersionNumber; +begin + ThisVersion := GetVersion; + Result := DBValidVersions.Contains(ThisVersion.V1); +end; + +procedure TAbstractMainDBMetaData.LoadLicenseAndCopyrightInfo; +var + SL: TStringList; +begin + if fIsLicenseAndCopyrightInfoLoaded then + Exit; + SL := TStringList.Create; + try + StrArrayToStrList(fMetaFiles.LicenseInfo, SL); + fLicenseInfo := TDBLicenseInfo.Create( + SL.Values['LicenseName'], + SL.Values['LicenseSPDX'], + SL.Values['LicenseURL'], + fMetaFiles.LicenseText + ); + fCopyrightInfo := TDBCopyrightInfo.Create( + SL.Values['CopyrightDate'], + SL.Values['CopyrightHolder'], + SL.Values['CopyrightHolderURL'] + ); + finally + SL.Free; + end; + fIsLicenseAndCopyrightInfoLoaded := True; +end; + +procedure TAbstractMainDBMetaData.Refresh; +begin + FreeAndNil(fMetaFiles); + fMetaFiles := TDBMetaFilesFactory.GetInstance(GetDBDir); + fIsVersionLoaded := False; + fIsLicenseAndCopyrightInfoLoaded := False; + fContributors := nil; + fTesters := nil; +end; + +{ TMainDBMetaData } + +function TMainDBMetaData.GetDBDir: string; +begin + Result := TAppInfo.AppDataDir; +end; + +{ TUpdateDBMetaData } + +constructor TUpdateDBMetaData.Create(const UpdateDir: string); +begin + inherited Create; + fUpdateDir := ExcludeTrailingPathDelimiter(UpdateDir); +end; + +function TUpdateDBMetaData.GetDBDir: string; +begin + Result := fUpdateDir; +end; + +{ TDBMetaFiles } + +constructor TDBMetaFiles.Create(const DBDir: string); +begin + inherited Create; + fDBDir := DBDir; +end; + +function TDBMetaFiles.DBFileExists(const FileName: string): Boolean; +begin + Result := TFile.Exists(MakePath(FileName), False); +end; + +function TDBMetaFiles.GetFileEncoding(const FileName: TFileName): TEncoding; +begin + // Old v1 database meta files may be in the system default encodings, v1 and + // all v2 and later use UTF-8 with BOM. + if TFileIO.CheckBOM(MakePath(FileName), TEncoding.UTF8) then + Result := TEncoding.UTF8 + else + Result := TEncoding.Default; +end; + +function TDBMetaFiles.MakePath(const FileName: string): string; +begin + Assert(not StrContainsStr(PathDelim, FileName), + ClassName + '.MakePath: FileName must be a base file name.'); + Result := IncludeTrailingPathDelimiter(fDBDir) + FileName; +end; + +function TDBMetaFiles.ReadFileLines(const FileName: TFileName): TStringDynArray; +var + Encoding: TEncoding; +begin + if not DBFileExists(FileName) then + begin + SetLength(Result, 0); + Exit; + end; + Encoding := GetFileEncoding(FileName); + try + Result := TFileIO.ReadAllLines(MakePath(FileName), Encoding, True); + finally + TEncodingHelper.FreeEncoding(Encoding); + end; +end; + +function TDBMetaFiles.ReadFileText(const FileName: TFileName): string; +begin + if not DBFileExists(FileName) then + Exit(''); + Result := TFileIO.ReadAllText( + MakePath(FileName), GetFileEncoding(FileName), True + ); +end; + +{ TV1DBMetaFiles } + +function TV1DBMetaFiles.AreAllFilesPresent: Boolean; +begin + Result := DBFileExists(cContributorsFile) and DBFileExists(cTestersFile); +end; + +function TV1DBMetaFiles.Contributors: TStringDynArray; +begin + Result := ReadFileLines(cContributorsFile) +end; + +function TV1DBMetaFiles.LicenseInfo: TStringDynArray; +begin + Result := TStringDynArray.Create( + 'LicenseName=MIT License', + 'LicenseSPDX=MIT', + 'LicenseURL=https://opensource.org/licenses/MIT', + 'CopyrightDate=2005-2016', + 'CopyrightHolder=Peter Johnson & Contributors', + 'CopyrightHolderURL=https://gravatar.com/delphidabbler' + ); +end; + +function TV1DBMetaFiles.LicenseText: string; +begin + Result := LoadResourceAsString(HInstance, 'CSDBLICENSE', RT_RCDATA, etUTF8); +end; + +function TV1DBMetaFiles.Testers: TStringDynArray; +begin + Result := ReadFileLines(cTestersFile); +end; + +function TV1DBMetaFiles.Version: string; +begin + Result := '1'; +end; + +{ TV2DBMetaFiles } + +function TV2DBMetaFiles.AreAllFilesPresent: Boolean; +begin + Result := DBFileExists(cVersionFile) + and DBFileExists(cLicenseFile) + and DBFileExists(cLicenseInfoFile) + and DBFileExists(cContributorsFile) + and DBFileExists(cTestersFile); +end; + +function TV2DBMetaFiles.Contributors: TStringDynArray; +begin + Result := ReadFileLines(cContributorsFile); +end; + +function TV2DBMetaFiles.LicenseInfo: TStringDynArray; +begin + Result := ReadFileLines(cLicenseInfoFile); +end; + +function TV2DBMetaFiles.LicenseText: string; +begin + Result := StrTrimRight(ReadFileText(cLicenseFile)); +end; + +function TV2DBMetaFiles.Testers: TStringDynArray; +begin + Result := ReadFileLines(cTestersFile); +end; + +function TV2DBMetaFiles.Version: string; +begin + Result := StrTrim(ReadFileText(cVersionFile)); +end; + +{ TLaterDBMetaFiles } + +function TLaterDBMetaFiles.AreAllFilesPresent: Boolean; +resourcestring + sNotSupportedError = 'Calling %s.AreAllFilesPresent is not supported for an ' + + 'unsupported database format'; +begin + raise ENotSupportedException.CreateFmt(sNotSupportedError, [ClassName]); +end; + +function TLaterDBMetaFiles.Contributors: TStringDynArray; +begin + SetLength(Result, 0); +end; + +function TLaterDBMetaFiles.LicenseInfo: TStringDynArray; +begin + SetLength(Result, 0); +end; + +function TLaterDBMetaFiles.LicenseText: string; +begin + Result := ''; +end; + +function TLaterDBMetaFiles.Testers: TStringDynArray; +begin + SetLength(Result, 0); +end; + +function TLaterDBMetaFiles.Version: string; +begin + Result := StrTrim(ReadFileText(cVersionFile)); +end; + +{ TUnknownOrMissingMetaFiles } + +function TUnknownOrMissingMetaFiles.AreAllFilesPresent: Boolean; +resourcestring + sNotSupportedError = 'Calling %s.AreAllFilesPresent is not supported for an ' + + 'unrecognised database format or missing database'; +begin + raise ENotSupportedException.CreateFmt(sNotSupportedError, [ClassName]); +end; + +function TUnknownOrMissingMetaFiles.Contributors: TStringDynArray; +begin + SetLength(Result, 0); +end; + +function TUnknownOrMissingMetaFiles.LicenseInfo: TStringDynArray; +begin + SetLength(Result, 0); +end; + +function TUnknownOrMissingMetaFiles.LicenseText: string; +begin + Result := ''; +end; + +function TUnknownOrMissingMetaFiles.Testers: TStringDynArray; +begin + SetLength(Result, 0); +end; + +function TUnknownOrMissingMetaFiles.Version: string; +begin + Result := ''; +end; + +{ TDBMetaFilesFactory } + +class function TDBMetaFilesFactory.GetInstance(const DBDir: string): + TDBMetaFiles; +var + VersionFile: string; + VersionStr: string; + Version: TVersionNumber; + DBPath: string; +begin + if not TDirectory.Exists(ExcludeTrailingPathDelimiter(DBDir)) then + // Database is not installed + Exit(TUnknownOrMissingMetaFiles.Create(DBDir)); + + DBPath := IncludeTrailingPathDelimiter(DBDir); + + // Check if VERSION file exists: + // Yes: + // either: version is invalid - database format unknown + // or: version is 2.x.x.x - database format v2 recognised + // or: version is >2 - database format recognised but not supported + // No: + // either: expected v1 meta files exist - database format v1 recognised + // or: no v1 meta files - database format unknown + VersionFile := DBPath + TDBMetaFileNames.VersionV2AndLater; + if TFile.Exists(VersionFile, False) then + begin + VersionStr := TFileIO.ReadAllText(VersionFile, TEncoding.UTF8, True); + if not TVersionNumber.TryStrToVersionNumber(VersionStr, Version) then + Result := TUnknownOrMissingMetaFiles.Create(DBDir) + else if Version.V1 = 2 then + Result := TV2DBMetaFiles.Create(DBDir) + else + Result := TLaterDBMetaFiles.Create(DBDir); + end + else + begin + if TFile.Exists(DBPath + TDBMetaFileNames.ContributorsV1, False) + and TFile.Exists(DBPath + TDBMetaFileNames.TestersV1, False) then + Result := TV1DBMetaFiles.Create(DBDir) + else + Result := TUnknownOrMissingMetaFiles.Create(DBDir); + end; +end; + +{ TDBLicenseInfo } + +constructor TDBLicenseInfo.Create(const AName, ASPDX, AURL, AText: string); +begin + fName := AName; + fSPDX := ASPDX; + fURL := AURL; + fText := AText; +end; + +function TDBLicenseInfo.NameWithURL: string; +begin + Result := fName; + if fURL <> '' then + Result := Result + ' (' + fURL + ')'; +end; + +{ TDBCopyrightInfo } + +constructor TDBCopyrightInfo.Create(const ADate, AHolder, AHolderURL: string); +begin + fDate := ADate; + fHolder := AHolder; + fHolderURL := AHolderURL; +end; + +end. diff --git a/Src/DB.USnippet.pas b/Src/DB.USnippet.pas index 90fb6155e..5af498162 100644 --- a/Src/DB.USnippet.pas +++ b/Src/DB.USnippet.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2011-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2011-2022, Peter Johnson (gravatar.com/delphidabbler). * * Objects, records etc that encapsulate a code snippet, its data and lists of * code snippets. @@ -331,10 +328,17 @@ TSnippetList = class(TObject) {Counts number of snippets in list. @return Number of snippets in list. } - function IsEmpty: Boolean; inline; + function IsEmpty: Boolean; overload; inline; {Checks if list is empty. @return True if list is empty, False otehrwise. } + function IsEmpty(const UserDefined: Boolean): Boolean; overload; inline; + {Checks if sub-set of list from either from or not from use defined + database is empty. + @param UserDefined [in] Flags whether to check for snippets in user + database (True) or in main database (False). + @return True if required subset is empty, False if not empty. + } property Items[Idx: Integer]: TSnippet read GetItem; default; {List of snippets} end; @@ -767,6 +771,11 @@ function TSnippetList.IsEmpty: Boolean; Result := Count = 0; end; +function TSnippetList.IsEmpty(const UserDefined: Boolean): Boolean; +begin + Result := Count(UserDefined) = 0; +end; + function TSnippetList.IsEqual(const AList: TSnippetList): Boolean; {Checks if this list contains same snippets as another list. @param AList [in] List of snippets to compare. diff --git a/Src/DB.USnippetKind.pas b/Src/DB.USnippetKind.pas index 8cd96532c..45fbed42e 100644 --- a/Src/DB.USnippetKind.pas +++ b/Src/DB.USnippetKind.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2009-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2009-2021, Peter Johnson (gravatar.com/delphidabbler). * * Defines a record that provides information about the different snippet kinds * enumerated by TSnippetKind along with a static class that provides an diff --git a/Src/DBIO.UFileIOIntf.pas b/Src/DBIO.UFileIOIntf.pas index 229e2247d..f9c88923e 100644 --- a/Src/DBIO.UFileIOIntf.pas +++ b/Src/DBIO.UFileIOIntf.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). * * Defines interfaces implemented by classes that read and write physical code * snippets database files. Also provides an exception class for data I/O diff --git a/Src/DBIO.UIniDataReader.pas b/Src/DBIO.UIniDataReader.pas index bb61b2b39..90b0c9657 100644 --- a/Src/DBIO.UIniDataReader.pas +++ b/Src/DBIO.UIniDataReader.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2015, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements code that reads the main CodeSnip database from .ini and .dat * files. @@ -237,7 +234,10 @@ implementation 'Delphi2', 'Delphi3', 'Delphi4', 'Delphi5', 'Delphi6', 'Delphi7', 'Delphi2005Win32', 'Delphi2006Win32', 'Delphi2007', 'Delphi2009Win32', 'Delphi2010', 'DelphiXE', 'DelphiXE2', 'DelphiXE3', 'DelphiXE4', - 'DelphiXE5', 'DelphiXE6', 'DelphiXE7', 'DelphiXE8', 'Delphi10S', 'FPC' + 'DelphiXE5', 'DelphiXE6', 'DelphiXE7', 'DelphiXE8', 'Delphi10S', + 'Delphi101B', 'Delphi102T', 'Delphi103R', 'Delphi104S', 'Delphi11A', + 'Delphi12A', + 'FPC' ); { TIniDataReader } diff --git a/Src/DBIO.UNulDataReader.pas b/Src/DBIO.UNulDataReader.pas index ba079d1cf..ab6e8ecf2 100644 --- a/Src/DBIO.UNulDataReader.pas +++ b/Src/DBIO.UNulDataReader.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2008-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2008-2021, Peter Johnson (gravatar.com/delphidabbler). * * Implements a do nothing data reader for use when a database does not exist. } diff --git a/Src/DBIO.UXMLDataIO.pas b/Src/DBIO.UXMLDataIO.pas index c989444d1..0d1c0c0fc 100644 --- a/Src/DBIO.UXMLDataIO.pas +++ b/Src/DBIO.UXMLDataIO.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2008-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2008-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements classes that can read and write databases stored in XML format * with associated source code files. @@ -953,7 +950,7 @@ procedure TXMLDataWriter.WriteSnippetProps(const SnippetName: string; ); fXMLDoc.CreateElement(SnippetNode, cDisplayNameNode, Props.DisplayName); // extra node is only written if extra property has a value - if not Props.Extra.IsEmpty then + if Props.Extra.HasContent then begin fXMLDoc.CreateElement( SnippetNode, diff --git a/Src/ExternalObj.ridl b/Src/ExternalObj.ridl index 5e327b55b..fcf28f613 100644 --- a/Src/ExternalObj.ridl +++ b/Src/ExternalObj.ridl @@ -1,12 +1,9 @@ /* * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2006-2015, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2006-2021, Peter Johnson (gravatar.com/delphidabbler). * * Source for type library that defines the interface to extensions to the * browser control DOM's "external object". @@ -15,7 +12,7 @@ [ uuid(DA95AEFB-3FB5-4A9E-9F9D-A53DD05CA7D4), - version(12.0), + version(14.0), helpstring("CodeSnip DOM External Object Extender Type Library"), custom(DE77BA64-517C-11D1-A2DA-0000F8773CE9, 117441012), custom(DE77BA63-517C-11D1-A2DA-0000F8773CE9, 1219706147) @@ -27,19 +24,19 @@ library ExternalObj importlib("stdole2.tlb"); /* - * V12 interface of extension to browser DOM's "external" object. + * V14 interface of extension to browser DOM's "external" object. */ - interface IWBExternal12; + interface IWBExternal14; [ uuid(BA971829-ED4D-4092-BCAE-4B5DB1A2D74A), - version(12.0), + version(14.0), helpstring("DOM external object extender"), dual, oleautomation ] - interface IWBExternal12: IDispatch + interface IWBExternal14: IDispatch { /* * Update database from internet. @@ -69,50 +66,31 @@ library ExternalObj [id(0x0000006C)] HRESULT _stdcall EditSnippet([in] BSTR SnippetName); - /* - * Displays Donate dialog box. - */ - [id(0x0000006D)] - HRESULT _stdcall Donate(void); - /* * Display identified category. * @param CatID [in] ID of category to display. */ [id(0x0000006E)] HRESULT _stdcall DisplayCategory([in] BSTR CatID, [in] VARIANT_BOOL NewTab); - [id(0x0000006F)] /* * Open Snippets Editor ready to create a new snippet. */ + [id(0x0000006F)] HRESULT _stdcall NewSnippet(void); - [id(0x00000070)] /* * Show news items from CodeSnip news feed. */ + [id(0x00000070)] HRESULT _stdcall ShowNews(void); - [id(0x00000071)] - - /* - * Check for program updates. - */ - HRESULT _stdcall CheckForUpdates(void); - [id(0x00000072)] /* * Display the program's About box. */ + [id(0x00000071)] HRESULT _stdcall ShowAboutBox(void); - [id(0x00000073)] - /* - * Displays a tab in the Preferences dialogue box. - * @param TagCls [in] Class name of frame hosting required dialogue box - * page. - */ - HRESULT _stdcall ShowPrefsPage([in] BSTR ClsName); }; }; \ No newline at end of file diff --git a/Src/Favourites.UFavourites.pas b/Src/Favourites.UFavourites.pas index bc5475a1a..0a5154621 100644 --- a/Src/Favourites.UFavourites.pas +++ b/Src/Favourites.UFavourites.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2013-2021, Peter Johnson (gravatar.com/delphidabbler). * * Defines types used to encapsulate a list of a user's favourite snippets. } diff --git a/Src/Favourites.UManager.pas b/Src/Favourites.UManager.pas index c9f0fcb58..da119c289 100644 --- a/Src/Favourites.UManager.pas +++ b/Src/Favourites.UManager.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2013-2021, Peter Johnson (gravatar.com/delphidabbler). * * Defines a class used to manage interaction with, and updating of, the user's * favourites. diff --git a/Src/Favourites.UPersist.pas b/Src/Favourites.UPersist.pas index 7aa981597..9f73ab49d 100644 --- a/Src/Favourites.UPersist.pas +++ b/Src/Favourites.UPersist.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2013-2021, Peter Johnson (gravatar.com/delphidabbler). * * Defines an advanced record that can persist a list of favourites to and from * a file on a per user basis. diff --git a/Src/FirstRun.FmV4ConfigDlg.dfm b/Src/FirstRun.FmV4ConfigDlg.dfm index d5657e2d7..cfc977d4b 100644 --- a/Src/FirstRun.FmV4ConfigDlg.dfm +++ b/Src/FirstRun.FmV4ConfigDlg.dfm @@ -15,6 +15,10 @@ inherited V4ConfigDlg: TV4ConfigDlg object tsIntro: TTabSheet Caption = 'tsIntro' TabVisible = False + ExplicitLeft = 0 + ExplicitTop = 0 + ExplicitWidth = 0 + ExplicitHeight = 0 object lblIntro1: TLabel Left = 0 Top = 3 @@ -70,6 +74,10 @@ inherited V4ConfigDlg: TV4ConfigDlg Caption = 'tsConfigFile' ImageIndex = 1 TabVisible = False + ExplicitLeft = 0 + ExplicitTop = 0 + ExplicitWidth = 0 + ExplicitHeight = 0 object lblCopyConfig: TLabel Left = 0 Top = 3 @@ -97,6 +105,10 @@ inherited V4ConfigDlg: TV4ConfigDlg Caption = 'tsUserDB' ImageIndex = 2 TabVisible = False + ExplicitLeft = 0 + ExplicitTop = 0 + ExplicitWidth = 0 + ExplicitHeight = 0 object lblUserDB1: TLabel Left = 0 Top = 3 @@ -136,6 +148,10 @@ inherited V4ConfigDlg: TV4ConfigDlg Caption = 'tsSummary' ImageIndex = 3 TabVisible = False + ExplicitLeft = 0 + ExplicitTop = 0 + ExplicitWidth = 0 + ExplicitHeight = 0 object lblSummaryPrefix: TLabel Left = 0 Top = 3 @@ -170,6 +186,10 @@ inherited V4ConfigDlg: TV4ConfigDlg Caption = 'tsFinish' ImageIndex = 4 TabVisible = False + ExplicitLeft = 0 + ExplicitTop = 0 + ExplicitWidth = 0 + ExplicitHeight = 0 object lblFinish1: TLabel Left = 0 Top = 3 diff --git a/Src/FirstRun.FmV4ConfigDlg.pas b/Src/FirstRun.FmV4ConfigDlg.pas index 22086217e..122d67563 100644 --- a/Src/FirstRun.FmV4ConfigDlg.pas +++ b/Src/FirstRun.FmV4ConfigDlg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2012-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2012-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a wizard dialogue box that may be displayed on the first run of * CodeSnip v4 to get user to decide whether what data to bring forward from @@ -117,6 +114,12 @@ TAligner = class(TInterfacedObject, IFormAligner) procedure AlignForm(const AForm: TCustomForm); end; strict protected + /// <summary>Modifies window creation parameters to ensure the dialgue box + /// displays a button in the task bar.</summary> + /// <remarks>This is necessary because the dialogue box is displayed before + /// CodeSnip's main window is shown, so there is no suitable button + /// displayed yet.</remarks> + procedure CreateParams(var Params: TCreateParams); override; /// <summary>Returns instance of form aligner object.</summary> function GetAligner: IFormAligner; override; /// <summary>Arranges controls within each tab sheet.</summary> @@ -162,6 +165,8 @@ implementation uses + // VCL + Windows, // Project UConsts, UCtrlArranger, UMessageBox, UStructs; @@ -271,6 +276,12 @@ procedure TV4ConfigDlg.CreateBulletPage(TS: TTabSheet; end; end; +procedure TV4ConfigDlg.CreateParams(var Params: TCreateParams); +begin + inherited; + Params.ExStyle := Params.ExStyle OR WS_EX_APPWINDOW; +end; + function TV4ConfigDlg.DatabaseAvailable: Boolean; begin Result := fFirstRun.HaveOldUserDB; @@ -278,14 +289,16 @@ function TV4ConfigDlg.DatabaseAvailable: Boolean; class procedure TV4ConfigDlg.Execute(AOwner: TComponent; const FirstRun: TFirstRun); +var + Dlg: TV4ConfigDlg; begin - with InternalCreate(AOwner) do - try - fFirstRun := FirstRun; - ShowModal; - finally - Free; - end; + Dlg := InternalCreate(AOwner); + try + Dlg.fFirstRun := FirstRun; + Dlg.ShowModal; + finally + Dlg.Free; + end; end; procedure TV4ConfigDlg.FormCloseQuery(Sender: TObject; var CanClose: Boolean); @@ -341,9 +354,7 @@ function TV4ConfigDlg.HeadingText(const PageIdx: Integer): string; procedure TV4ConfigDlg.ListChanges; resourcestring - sRegistration = 'Program registration information has been lost.'; sHiliter = 'Syntax highlighter customisations have been lost.'; - sProxyPwd = 'Your proxy server password needs to be re-entered.'; sSourceFormat = 'Source code formatting preferences may have been lost.'; var Changes: IStringList; @@ -353,12 +364,8 @@ procedure TV4ConfigDlg.ListChanges; // there are changes to config file: show in bullet list lblFinish2.Visible := True; Changes := TIStringList.Create; - if frcRegistration in fCfgChanges then - Changes.Add(sRegistration); if frcHiliter in fCfgChanges then Changes.Add(sHiliter); - if frcProxyPwd in fCfgChanges then - Changes.Add(sProxyPwd); if frcSourceFormat in fCfgChanges then Changes.Add(sSourceFormat); CreateBulletPage( diff --git a/Src/FrRSSNews.dfm b/Src/FirstRun.FmWhatsNew.FrHTML.dfm similarity index 67% rename from Src/FrRSSNews.dfm rename to Src/FirstRun.FmWhatsNew.FrHTML.dfm index 15a3370db..c67d0ffdf 100644 --- a/Src/FrRSSNews.dfm +++ b/Src/FirstRun.FmWhatsNew.FrHTML.dfm @@ -1,13 +1,8 @@ -inherited RSSNewsFrame: TRSSNewsFrame +inherited WhatsNewHTMLFrame: TWhatsNewHTMLFrame inherited pnlBrowser: TPanel - BevelOuter = bvLowered inherited wbBrowser: TWebBrowser - Left = 1 - Top = 1 - Width = 314 - Height = 234 ControlData = { - 4C000000742000002F1800000000000000000000000000000000000000000000 + 4C000000A9200000641800000000000000000000000000000000000000000000 000000004C000000000000000000000001000000E0D057007335CF11AE690800 2B2E126208000000000000004C0000000114020000000000C000000000000046 8000000000000000000000000000000000000000000000000000000000000000 diff --git a/Src/FirstRun.FmWhatsNew.FrHTML.pas b/Src/FirstRun.FmWhatsNew.FrHTML.pas new file mode 100644 index 000000000..749ea3122 --- /dev/null +++ b/Src/FirstRun.FmWhatsNew.FrHTML.pas @@ -0,0 +1,105 @@ +{ + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/ + * + * Copyright (C) 2020-2022, Peter Johnson (gravatar.com/delphidabbler). + * + * Frame that displays HTML of "what's new" message in a TWebBrowser control. +} + + +unit FirstRun.FmWhatsNew.FrHTML; + +interface + +uses + // VCL + OleCtrls, + SHDocVw, + Classes, + Controls, + ExtCtrls, + // Project + FrBrowserBase, + UCSSBuilder; + + +type + TWhatsNewHTMLFrame = class(TBrowserBaseFrame) + strict protected + procedure BuildCSS(const CSSBuilder: TCSSBuilder); override; + {Generates CSS classes specific to this frame. This CSS is added + to that provided by parent class. + @param CSSBuilder [in] Object used to build the CSS code. + } + public + constructor Create(AOwner: TComponent); override; + {Object constructor. Sets up frame and initialises web browser. + @param AOwner [in] Component that owns the frame (must be a form). + } + procedure Initialise(const HTML: string); + end; + + +implementation + + +uses + // VCL + Graphics, + // Project + Browser.UUIMgr, + UCSSUtils, + UFontHelper; + +{$R *.dfm} + + +{ TWhatsNewHTMLFrame } + +procedure TWhatsNewHTMLFrame.BuildCSS(const CSSBuilder: TCSSBuilder); +var + CSSFont: TFont; // font used to set CSS properties +begin + inherited; + CSSFont := TFont.Create; + try + TFontHelper.SetContentFont(CSSFont); + CSSFont.Size := CSSFont.Size + 2; + CSSBuilder.AddSelector('body') + .AddProperty(TCSS.FontProps(CSSFont)) + .AddProperty(TCSS.MarginProp(0, 8, 0, 8)); + CSSBuilder.AddSelector('.lead') + .AddProperty(TCSS.FontSizeProp(CSSFont.Size + 2)) + .AddProperty(TCSS.FontWeightProp(cfwBold)) + .AddProperty(TCSS.ColorProp($233bc2)); + // Sets paragraph margins and padding + CSSBuilder.AddSelector('p') + .AddProperty(TCSS.MarginProp(cssTop, 6)) + .AddProperty(TCSS.MarginProp(cssBottom, 0)) + .AddProperty(TCSS.PaddingProp(0)); + CSSBuilder.AddSelector('ul') + .AddProperty(TCSS.MarginProp(cssTop, 6)) + .AddProperty(TCSS.MarginProp(cssBottom, 0)) + .AddProperty(TCSS.PaddingProp(0)); + CSSBuilder.AddSelector('li') + .AddProperty(TCSS.MarginProp(cssTop, 6)); + finally + CSSFont.Free; + end; +end; + +constructor TWhatsNewHTMLFrame.Create(AOwner: TComponent); +begin + inherited; + WBController.UIMgr.ScrollbarStyle := sbsNormal; + WBController.UIMgr.AllowTextSelection := False; +end; + +procedure TWhatsNewHTMLFrame.Initialise(const HTML: string); +begin + WBController.IOMgr.LoadFromString(HTML); +end; + +end. diff --git a/Src/FmDonateDlg.dfm b/Src/FirstRun.FmWhatsNew.dfm similarity index 66% rename from Src/FmDonateDlg.dfm rename to Src/FirstRun.FmWhatsNew.dfm index 66b6f531c..47dd35480 100644 --- a/Src/FmDonateDlg.dfm +++ b/Src/FirstRun.FmWhatsNew.dfm @@ -1,11 +1,11 @@ -inherited DonateDlg: TDonateDlg - Caption = 'Donate' +inherited WhatsNewDlg: TWhatsNewDlg + Caption = 'What'#39's New' ExplicitWidth = 474 - ExplicitHeight = 354 + ExplicitHeight = 375 PixelsPerInch = 96 TextHeight = 13 inherited pnlBody: TPanel - inline frmContent: TFixedHTMLDlgFrame + inline frmHTML: TWhatsNewHTMLFrame Left = 0 Top = 0 Width = 377 @@ -36,26 +36,6 @@ inherited DonateDlg: TDonateDlg end end inherited btnHelp: TButton - TabOrder = 3 Visible = False end - inherited btnClose: TButton - Default = False - TabOrder = 2 - end - object btnDoDonate: TButton - Left = 8 - Top = 304 - Width = 145 - Height = 25 - Caption = 'Make &Donation' - Default = True - Font.Charset = DEFAULT_CHARSET - Font.Color = clWindowText - Font.Height = -11 - Font.Name = 'Tahoma' - Font.Style = [fsBold] - ParentFont = False - TabOrder = 1 - end end diff --git a/Src/FirstRun.FmWhatsNew.pas b/Src/FirstRun.FmWhatsNew.pas new file mode 100644 index 000000000..4cc6e9778 --- /dev/null +++ b/Src/FirstRun.FmWhatsNew.pas @@ -0,0 +1,146 @@ +{ + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/ + * + * Copyright (C) 2020-2023, Peter Johnson (gravatar.com/delphidabbler). + * + * Implements dialogue box that may be displayed the first time CodeSnip 4.x.x + * is run after an update. The dialogue box displays a HTML page that draws + * attention to key changes in the updated version of CodeSnip. +} + + +unit FirstRun.FmWhatsNew; + +interface + + +uses + // VCL + Controls, + Forms, + StdCtrls, + ExtCtrls, + Classes, + // Project + FirstRun.FmWhatsNew.FrHTML, + FmGenericViewDlg, + FrBrowserBase, + IntfAligner, + UBaseObjects; + + +type + /// <summary>Dialogue box that may be displayed the first time CodeSnip 4.x.x + /// is run after an update. The dialogue box displays a HTML page that draws + /// attention to key changes in the updated version of CodeSnip.</summary> + TWhatsNewDlg = class(TGenericViewDlg, INoPublicConstruct) + frmHTML: TWhatsNewHTMLFrame; + strict private + type + /// <summary>Custom form aligner class for wizard.</summary> + TAligner = class(TInterfacedObject, IFormAligner) + public + /// <summary>Aligns dialogue box at centre of primary monitor. + /// </summary> + procedure AlignForm(const AForm: TCustomForm); + end; + strict protected + /// <summary>Returns instance of form aligner object.</summary> + function GetAligner: IFormAligner; override; + /// <summary>Sets form caption and loads HTML to be displayed from + /// resources.</summary> + procedure ConfigForm; override; + /// <summary>Sets size of the HTML section of the display and aranges other + /// controls around that.</summary> + procedure ArrangeForm; override; + /// <summary>Modifies window creation parameters to ensure the dialgue box + /// displays a button in the task bar.</summary> + /// <remarks>This is necessary because the dialogue box is displayed before + /// CodeSnip's main window is shown, so there is no suitable button + /// displayed yet.</remarks> + procedure CreateParams(var Params: TCreateParams); override; + public + /// <summary>Displays dialogue box with given owner.</summary> + class procedure Execute(AOwner: TComponent); + end; + + +implementation + + +uses + // VCL + SysUtils, + Math, + Windows, + // Project + UAppInfo, + UConsts, + UEncodings, + UResourceUtils, + UStructs; + +{$R *.dfm} + + +{ TWhatsNewDlg } + +procedure TWhatsNewDlg.ArrangeForm; +const + cBodyWidth = 500; + cBodyHeight = 450; +begin + pnlBody.Width := cBodyWidth; + pnlBody.Height := cBodyHeight; + inherited; +end; + +procedure TWhatsNewDlg.ConfigForm; +resourcestring + sDlgTitle = 'Welcome to CodeSnip v%s'; +begin + Caption := Format(sDlgTitle, [TAppInfo.ProgramReleaseVersion]); + frmHTML.Initialise( + LoadResourceAsString(HInstance, 'dlg-whatsnew.html', RT_HTML, etUTF8) + ); +end; + +procedure TWhatsNewDlg.CreateParams(var Params: TCreateParams); +begin + inherited; + Params.ExStyle := Params.ExStyle OR WS_EX_APPWINDOW; +end; + +class procedure TWhatsNewDlg.Execute(AOwner: TComponent); +var + Dlg: TWhatsNewDlg; +begin + Dlg := InternalCreate(AOwner); + try + Dlg.ShowModal; + finally + Dlg.Free; + end; +end; + +function TWhatsNewDlg.GetAligner: IFormAligner; +begin + Result := TAligner.Create; +end; + +{ TWhatsNewDlg.TAligner } + +procedure TWhatsNewDlg.TAligner.AlignForm(const AForm: TCustomForm); +var + WorkArea: TRectEx; +begin + // This form is designed for display centred on desktop, so assume it fits + WorkArea := Screen.WorkAreaRect; + AForm.Left := WorkArea.Left + (WorkArea.Width - AForm.Width) div 2; + AForm.Top := WorkArea.Top + (WorkArea.Height - AForm.Height) div 2; +end; + +end. + diff --git a/Src/FirstRun.UConfigFile.pas b/Src/FirstRun.UConfigFile.pas index 20e20b308..314eaaf62 100644 --- a/Src/FirstRun.UConfigFile.pas +++ b/Src/FirstRun.UConfigFile.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2007-2015, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2007-2022, Peter Johnson (gravatar.com/delphidabbler). * * Implements class that manages the updating of older config files to the * current format. @@ -19,6 +16,11 @@ interface +uses + // Project + UVersionInfo; + + type /// <summary>Class that manages the updating of older config files to the /// current format. Missing files will also be created.</summary> @@ -63,7 +65,10 @@ TConfigFileUpdater = class abstract(TObject) /// <summary>Checks if program version in config file is same as current /// program version.</summary> function IsCurrentProgramVer: Boolean; overload; - /// <summary>Stamps config file with current and file version.</summary> + class function PreviousProgramVer(const CfgFileName: string): + TVersionNumber; overload; + function PreviousProgramVer: TVersionNumber; overload; + /// <summary>Stamps config file with current file version.</summary> procedure Stamp; virtual; end; @@ -77,7 +82,7 @@ TUserConfigFileUpdater = class(TConfigFileUpdater) strict private const /// <summary>Current user config file version.</summary> - FileVersion = 15; + FileVersion = 20; strict protected /// <summary>Returns current user config file version.</summary> class function GetFileVersion: Integer; override; @@ -85,20 +90,28 @@ TUserConfigFileUpdater = class(TConfigFileUpdater) {$IFNDEF PORTABLE} /// <summary>Updates config file currently in original (pre v1.9) format to /// current format.</summary> + /// <remarks>Standard edition only.</remarks> procedure UpdateFromOriginal; /// <summary>Deletes any highlighter preferences.</summary> + /// <remarks>Standard edition only.</remarks> procedure DeleteHighligherPrefs; - /// <summary>Checks a proxy password is present in file.</summary> - function HasProxyPassword: Boolean; - /// <summary>Deletes proxy password entry.</summary> - procedure DeleteProxyPassword; + {$ENDIF} /// <summary>Updates Prefs:CodeGen section from format prior to version 9 /// to version 9 and later format.</summary> procedure UpdateCodeGenEntries; /// <summary>Deletes unused key that determines detail pane index. /// </summary> procedure DeleteDetailsPaneIndex; - {}{$ENDIF} + /// <summary>Deletes proxy server section.</summary> + procedure DeleteProxyServerSection; + /// <summary>Deletes unused Prefs:News section.</summary> + procedure DeleteNewsPrefs; + /// <summary>Deletes unused Prefs:Updating section.</summary> + procedure DeleteUpdatingPrefs; + /// <summary>Deletes unused UpdateChecks section.</summary> + procedure DeleteUpdateChecks; + /// <summary>Deletes unused UserInfo section.</summary> + procedure DeleteUserInfo; /// <summary>Effectively renames MainWindow section used prior to version /// 11 as WindowState:MainForm.</summary> procedure RenameMainWindowSection; @@ -126,17 +139,33 @@ TCommonConfigFileUpdater = class(TConfigFileUpdater) strict private const /// <summary>Current common config file version.</summary> - FileVersion = 6; + FileVersion = 7; strict protected /// <summary>Returns current common config file version.</summary> class function GetFileVersion: Integer; override; public /// <summary>Stamps config file with current program and file versions. /// </summary> - /// <remarks>Note that the user config file has program version written to + /// <remarks> + /// <para>Note that the user config file has program version written to /// a different section to common config file, hence need for overridden - /// methods.</remarks> + /// methods.</para> + /// <para>Does nothing in portable edition.</para> + /// </remarks> procedure Stamp; override; + {$IFNDEF PORTABLE} + /// <summary>Deletes program registration information from application + /// section.</summary> + /// <remarks>Standard edition only.</remarks> + procedure DeleteRegistrationInfo; + /// <summary>Deletes program key from application section.</summary> + /// <remarks>Standard edition only.</remarks> + procedure DeleteProgramKey; + {$ELSE} + /// <summary>Deletes and common config file</summary> + /// <remarks>Portable edition only.</remarks> + procedure DeleteCfgFile; + {$ENDIF} end; @@ -213,10 +242,25 @@ class function TConfigFileUpdater.IsCurrentProgramVer( var CfgProgVer: string; // program version from config file begin - CfgProgVer := GetIniString('IniFile', 'ProgramVersion', '', CfgFileName); + CfgProgVer := PreviousProgramVer(CfgFileName); Result := CfgProgVer = TAppInfo.ProgramReleaseVersion; end; +function TConfigFileUpdater.PreviousProgramVer: TVersionNumber; +begin + Result := PreviousProgramVer(fCfgFileName); +end; + +class function TConfigFileUpdater.PreviousProgramVer( + const CfgFileName: string): TVersionNumber; +begin + if not TVersionNumber.TryStrToVersionNumber( + GetIniString('IniFile', 'ProgramVersion', '', CfgFileName), + Result + ) then + Exit(TVersionNumber.Nul); +end; + procedure TConfigFileUpdater.Stamp; begin if not TFile.Exists(fCfgFileName, False) then @@ -267,14 +311,12 @@ procedure TUserConfigFileUpdater.CreateDefaultCodeGenEntries; SetIniString('Prefs:CodeGen', 'Warning7.MinCompiler', '20.00', CfgFileName); end; -{$IFNDEF PORTABLE} procedure TUserConfigFileUpdater.DeleteDetailsPaneIndex; begin if not TFile.Exists(CfgFileName, False) then CreateNewFile; DeleteIniKey('MainWindow', 'DetailTab', CfgFileName); end; -{$ENDIF} {$IFNDEF PORTABLE} procedure TUserConfigFileUpdater.DeleteHighligherPrefs; @@ -285,26 +327,45 @@ procedure TUserConfigFileUpdater.DeleteHighligherPrefs; end; {$ENDIF} -{$IFNDEF PORTABLE} -procedure TUserConfigFileUpdater.DeleteProxyPassword; +procedure TUserConfigFileUpdater.DeleteNewsPrefs; begin if not TFile.Exists(CfgFileName, False) then CreateNewFile; - SetIniString('ProxyServer', 'Password', '', CfgFileName); + DeleteIniSection('Prefs:News', CfgFileName); end; -{$ENDIF} -class function TUserConfigFileUpdater.GetFileVersion: Integer; +procedure TUserConfigFileUpdater.DeleteProxyServerSection; begin - Result := FileVersion; + if not TFile.Exists(CfgFileName, False) then + CreateNewFile; + DeleteIniSection('ProxyServer', CfgFileName); end; -{$IFNDEF PORTABLE} -function TUserConfigFileUpdater.HasProxyPassword: Boolean; +procedure TUserConfigFileUpdater.DeleteUpdateChecks; begin - Result := GetIniString('ProxyServer', 'Password', '', CfgFileName) <> ''; + if not TFile.Exists(CfgFileName, False) then + CreateNewFile; + DeleteIniSection('UpdateChecks', CfgFileName); +end; + +procedure TUserConfigFileUpdater.DeleteUpdatingPrefs; +begin + if not TFile.Exists(CfgFileName, False) then + CreateNewFile; + DeleteIniSection('Prefs:Updating', CfgFileName); +end; + +procedure TUserConfigFileUpdater.DeleteUserInfo; +begin + if not TFile.Exists(CfgFileName, False) then + CreateNewFile; + DeleteIniSection('UserInfo', CfgFileName); +end; + +class function TUserConfigFileUpdater.GetFileVersion: Integer; +begin + Result := FileVersion; end; -{$ENDIF} procedure TUserConfigFileUpdater.RenameMainWindowSection; begin @@ -365,7 +426,6 @@ procedure TUserConfigFileUpdater.Stamp; ); end; -{$IFNDEF PORTABLE} procedure TUserConfigFileUpdater.UpdateCodeGenEntries; begin // Key that determines if warnings are emitted changes from SwitchOffWarnings @@ -385,7 +445,6 @@ procedure TUserConfigFileUpdater.UpdateCodeGenEntries; else SetIniInt('Prefs:CodeGen', 'EmitWarnDirs', 0, CfgFileName); end; -{$ENDIF} procedure TUserConfigFileUpdater.UpdateFindXRefs; begin @@ -483,10 +542,38 @@ procedure TUserConfigFileUpdater.UpdateNamespaces; UpdateForCompiler('DXE7'); UpdateForCompiler('DXE8'); UpdateForCompiler('D10S'); + UpdateForCompiler('D101B'); end; { TCommonConfigFileUpdater } +{$IFDEF PORTABLE} +procedure TCommonConfigFileUpdater.DeleteCfgFile; +begin + if TFile.Exists(CfgFileName, False) then + TFile.Delete(CfgFileName); +end; +{$ENDIF} + +{$IFNDEF PORTABLE} +procedure TCommonConfigFileUpdater.DeleteProgramKey; +begin + if not TFile.Exists(CfgFileName, False) then + CreateNewFile; + DeleteIniKey('Application', 'Key', CfgFileName); +end; +{$ENDIF} + +{$IFNDEF PORTABLE} +procedure TCommonConfigFileUpdater.DeleteRegistrationInfo; +begin + if not TFile.Exists(CfgFileName, False) then + CreateNewFile; + DeleteIniKey('Application', 'RegCode', CfgFileName); + DeleteIniKey('Application', 'RegName', CfgFileName); +end; +{$ENDIF} + class function TCommonConfigFileUpdater.GetFileVersion: Integer; begin Result := FileVersion; @@ -494,10 +581,12 @@ class function TCommonConfigFileUpdater.GetFileVersion: Integer; procedure TCommonConfigFileUpdater.Stamp; begin + {$IFNDEF PORTABLE} inherited; SetIniString( 'Application', 'Version', TAppInfo.ProgramReleaseVersion, CfgFileName ); + {$ENDIF} end; end. diff --git a/Src/FirstRun.UDatabase.pas b/Src/FirstRun.UDatabase.pas index a07fbbb48..994c86989 100644 --- a/Src/FirstRun.UDatabase.pas +++ b/Src/FirstRun.UDatabase.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2008-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2008-2021, Peter Johnson (gravatar.com/delphidabbler). * * Implements a class that handles updating of user database from an earlier * version. diff --git a/Src/FirstRun.UIniFile.pas b/Src/FirstRun.UIniFile.pas index 11d66e9c8..120ba48b0 100644 --- a/Src/FirstRun.UIniFile.pas +++ b/Src/FirstRun.UIniFile.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2012-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2012-2021, Peter Johnson (gravatar.com/delphidabbler). * * Ini file access helper routines for use with first-run startup code. } diff --git a/Src/FirstRun.UInstallInfo.pas b/Src/FirstRun.UInstallInfo.pas index 553239201..45514043c 100644 --- a/Src/FirstRun.UInstallInfo.pas +++ b/Src/FirstRun.UInstallInfo.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2008-2016, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2008-2021, Peter Johnson (gravatar.com/delphidabbler). * * Implements a class that provides information about CodeSnip installations. } diff --git a/Src/FirstRun.UMain.pas b/Src/FirstRun.UMain.pas index 9eeb14e4b..e5b610edc 100644 --- a/Src/FirstRun.UMain.pas +++ b/Src/FirstRun.UMain.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2008-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2008-2021, Peter Johnson (gravatar.com/delphidabbler). * * Implements classes that handle application start up, determine if it the * first run of the current version and perform necessary updates to per-user @@ -22,16 +19,14 @@ interface uses // Project - FirstRun.UConfigFile, FirstRun.UDatabase, FirstRun.UInstallInfo; + FirstRun.UConfigFile, FirstRun.UDatabase, FirstRun.UInstallInfo, UVersionInfo; type /// <summary>Enumeration of changes that can be made to brought forward /// config files that result in data loss.</summary> TFirstRunCfgChanges = ( - frcRegistration, // local registration record lost frcHiliter, // syntax highlighter customisation lost - frcProxyPwd, // internet proxy password lost frcSourceFormat // source code output formatting lost ); @@ -60,11 +55,6 @@ TFirstRun = class(TObject) /// <summary>Object used to copy forward older versions of user database. /// </summary> fDatabase: TUserDatabaseUpdater; - {$IFNDEF PORTABLE} - /// <summary>Checks if config file uses earlier format for storing proxy - /// server passwords.</summary> - function HasOldStyleProxyPwd: Boolean; - {}{$ENDIF} public /// <summary>Constructs object and owned object.</summary> constructor Create; @@ -89,8 +79,17 @@ TFirstRun = class(TObject) /// <summary>Creates a new, empty, Unicode encoded per-user config file for /// current installation.</summary> procedure CreateEmptyUserCfgFile; - /// <summary> + /// <summary>Checks if the program has been updated since the last run. + /// </summary> + /// <remarks>Compares the version number stored in the brought forward + /// config file against the current program version number from resources. + /// </remarks> function IsProgramUpdated: Boolean; + /// <summary>Checks if the previous program's version number, as specified + /// in the user config file, is contained in the range Lo..Hi with range + /// endpoints determined by IntervalEndPoints.</summary> + function IsPreviousProgramVerInRange(const Lo, Hi: TVersionNumber; + const IntervalEndPoints: TVersionNumber.TIntervalEndPoints): Boolean; end; type @@ -118,7 +117,8 @@ TFirstRunMgr = class(TObject) class function IsProgramUpdated: Boolean; public /// <summary>Runs start-up checks to detect if program has been run before - /// and performs any required user config and user database updates. + /// and performs any required user config and user database updates. In + /// some circumstances a "what's new" dialogue box may be displayed. /// </summary> class procedure Execute; end; @@ -129,9 +129,10 @@ implementation uses // Delphi - SysUtils, IOUtils, Forms - {$IFNDEF PORTABLE} + SysUtils, IOUtils, Forms, // Project + FirstRun.FmWhatsNew + {$IFNDEF PORTABLE} , FirstRun.FmV4ConfigDlg; {$ELSE} @@ -187,13 +188,6 @@ destructor TFirstRun.Destroy; inherited; end; -{$IFNDEF PORTABLE} -function TFirstRun.HasOldStyleProxyPwd: Boolean; -begin - Result := (fUserConfigFile.FileVer <= 6) and fUserConfigFile.HasProxyPassword; -end; -{$ENDIF} - function TFirstRun.HaveOldUserCfgFile: Boolean; begin Result := TFile.Exists(fInstallInfo.PreviousUserConfigFileName, False); @@ -204,6 +198,15 @@ function TFirstRun.HaveOldUserDB: Boolean; Result := TFile.Exists(fInstallInfo.PreviousUserDatabaseFileName, False); end; +function TFirstRun.IsPreviousProgramVerInRange(const Lo, Hi: TVersionNumber; + const IntervalEndPoints: TVersionNumber.TIntervalEndPoints): Boolean; +var + PrevProgVer: TVersionNumber; +begin + PrevProgVer := fUserConfigFile.PreviousProgramVer; + Result := PrevProgVer.IsInRange(Lo, Hi, IntervalEndPoints); +end; + function TFirstRun.IsProgramUpdated: Boolean; begin Result := fUserConfigFile.IsCurrentProgramVer; @@ -219,7 +222,6 @@ procedure TFirstRun.UpdateUserCfgFile(out Changes: TFirstRunCfgChangeSet); begin fUserConfigFile.UpdateFromOriginal; Include(Changes, frcHiliter); - Include(Changes, frcRegistration); Include(Changes, frcSourceFormat); end; piV1_9, piV2: @@ -227,14 +229,7 @@ procedure TFirstRun.UpdateUserCfgFile(out Changes: TFirstRunCfgChangeSet); fUserConfigFile.DeleteHighligherPrefs; Include(Changes, frcHiliter); end; - piV3: - begin - if HasOldStyleProxyPwd then - begin - fUserConfigFile.DeleteProxyPassword; - Include(Changes, frcProxyPwd); - end; - end; + piV3: ; // do nothing end; {$ENDIF} @@ -245,13 +240,11 @@ procedure TFirstRun.UpdateUserCfgFile(out Changes: TFirstRunCfgChangeSet); // we rely on this for portable version. fUserConfigFile.CreateDefaultCodeGenEntries; - {$IFNDEF PORTABLE} if fUserConfigFile.FileVer < 9 then begin fUserConfigFile.DeleteDetailsPaneIndex; fUserConfigFile.UpdateCodeGenEntries; end; - {$ENDIF} if fUserConfigFile.FileVer < 11 then fUserConfigFile.RenameMainWindowSection; @@ -262,16 +255,47 @@ procedure TFirstRun.UpdateUserCfgFile(out Changes: TFirstRunCfgChangeSet); if fUserConfigFile.FileVer < 15 then fUserConfigFile.UpdateFindXRefs; + + if fUserConfigFile.FileVer < 16 then + begin + fUserConfigFile.DeleteNewsPrefs; + fUserConfigFile.DeleteProxyServerSection; + fUserConfigFile.DeleteUpdatingPrefs; + fUserConfigFile.DeleteUpdateChecks; + fUserConfigFile.DeleteUserInfo; + end; + + {$IFNDEF PORTABLE} + // No need to delete sections of common config file on portable edition + // because the entire file is deleted below! + if fCommonConfigFile.FileVer < 7 then + begin + fCommonConfigFile.DeleteRegistrationInfo; + fCommonConfigFile.DeleteProgramKey; + end; + {$ENDIF} + fUserConfigFile.Stamp; + + {$IFNDEF PORTABLE} // NOTE: strictly speaking we only need to stamp common config file in // portable version. Installer does this in normal version. However, it does // no harm to stamp this file twice - belt and braces! fCommonConfigFile.Stamp; + {$ELSE} + fCommonConfigFile.DeleteCfgFile; + {$ENDIF} end; { TFirstRunMgr } class procedure TFirstRunMgr.Execute; +const + // Version numbers specifying a range in which previous program version must + // lie in order to display "What's New" dialogue box. + // Range is [NeedWhatsNewLoVerIncl, NeedWhatsNewHiVerExcl) + NeedWhatsNewLoVerIncl: TVersionNumber = (V1: 4; V2: 0; V3: 0; V4: 0); + NeedWhatsNewHiVerExcl: TVersionNumber = (V1: 4; V2: 16; V3: 0; V4: 0); var FR: TFirstRun; Changes: TFirstRunCfgChangeSet; @@ -297,7 +321,15 @@ class procedure TFirstRunMgr.Execute; begin FR := TFirstRun.Create; try - FR.UpdateUserCfgFile(Changes); + // We display "What's New" dialogue box if previous program version number + // is in the given range + if FR.IsPreviousProgramVerInRange( + NeedWhatsNewLoVerIncl, + NeedWhatsNewHiVerExcl, + TVersionNumber.TIntervalEndPoints.iepHalfOpenHi + ) then + TWhatsNewDlg.Execute(Application); + FR.UpdateUserCfgFile(Changes); // we ignore Changes [out] param value finally FR.Free; end; diff --git a/Src/FmAboutDlg.dfm b/Src/FmAboutDlg.dfm index 63858166b..cfc0062f7 100644 --- a/Src/FmAboutDlg.dfm +++ b/Src/FmAboutDlg.dfm @@ -162,19 +162,4 @@ inherited AboutDlg: TAboutDlg end end end - inherited btnHelp: TButton - TabOrder = 3 - end - inherited btnClose: TButton - TabOrder = 2 - end - object btnRegister: TButton - Left = 8 - Top = 304 - Width = 113 - Height = 25 - Caption = '&Register CodeSnip...' - TabOrder = 1 - OnClick = btnRegisterClick - end end diff --git a/Src/FmAboutDlg.pas b/Src/FmAboutDlg.pas index 44e9d453f..a26397685 100644 --- a/Src/FmAboutDlg.pas +++ b/Src/FmAboutDlg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2016, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2025, Peter Johnson (gravatar.com/delphidabbler). * * Implements the program's About dialogue box. } @@ -19,67 +16,70 @@ interface uses - // Delphi - Forms, ComCtrls, StdCtrls, Controls, ExtCtrls, Classes, Messages, + // VCL + Forms, + ComCtrls, + StdCtrls, + Controls, + ExtCtrls, + Classes, + Messages, // Project - Browser.UHTMLEvents, FmGenericViewDlg, FrBrowserBase, FrHTMLDlg, - FrHTMLTpltDlg, UContributors, UCSSBuilder; + Browser.UHTMLEvents, + DB.UMetaData, + FmGenericViewDlg, + FrBrowserBase, + FrHTMLDlg, + FrHTMLTpltDlg, + UCSSBuilder, + UIStringList; type - { - TPathInfoBox: - Component that displays a path in a group box with an associated button that - displays the path in Windows Explorer. - } + /// <summary>Custom component that displays a path in a group box with an + /// associated button to display the path in Windows Explorer.</summary> TPathInfoBox = class(TCustomGroupBox) strict private - fPathLbl: TLabel; // Label that displays path - fViewBtn: TButton; // Button that displays path in explorer + var + /// <summary>Label that displays path.</summary> + fPathLbl: TLabel; + /// <summary>Button that displays path in explorer.</summary> + fViewBtn: TButton; + /// <summary>Read accessor for Path property. Gets and returns value from + /// path label.</summary> function GetPath: string; - {Read accessor for Path property. Gets value from label. - @return Property value. - } + /// <summary>Write accessor for Path property. Stores given value in path + /// label and updates state of button.</summary> procedure SetPath(const Value: string); - {Write accessor for Path property. Stores value in label and updates state - of button. - @param Value [in] New property value. - } + /// <summary>Button click event handler. Displays folder stored in Path + /// property in Windows Explorer.</summary> procedure BtnClick(Sender: TObject); - {Button click event handler. Displays folder stored in Path property in - Windows Explorer. - @param Sender [in] Not used. - } + /// <summary>Handles font changes by resizing control to allow for new font + /// size.</summary> + /// <param name="Msg">TMessage [in/out] Not used.</param> procedure FontChange(var Msg: TMessage); message CM_FONTCHANGED; - {Handles font changes by resizing control to allow for new font size. - @param Msg [in/out] Not used. - } + /// <summary>Resizes and re-arranges control and its sub-components. + /// </summary> procedure ReArrange; - {Resizes and re-arranges control and its sub-components. - } strict protected + /// <summary>Handles control resizing. Re-arranges control's + /// sub-components.</summary> procedure Resize; override; - {Handles control resizing. Re-arranges control's sub-components. - } public + /// <summary>Component constructor. Creates sub-components and arranges + /// them.</summary> constructor Create(AOwner: TComponent); override; - {Component constructor. Creates sub-components and arranges them. - @param AOwner [in] Owning component. - } + /// <summary>Path displayed in group box and displayed by view button. + /// </summary> property Path: string read GetPath write SetPath; - {Path displayed in group box and displayed by view button} end; - { - TAboutDlg: - Implements an about dialog box that uses web browser controls to display - information about the program and the database. HTML templates containing - the dialog box content are loaded from resources. Also provides access to - the program's easter egg. - } + /// <summary>Implements program' about dialogue box.</summary> + /// <remarks>Displays information about the program, the main database and + /// the program's user and application folders and config files. Also + /// provides access to the program's easter egg.</remarks> TAboutDlg = class(TGenericViewDlg) - btnRegister: TButton; bvlSeparator: TBevel; frmDatabase: THTMLTpltDlgFrame; frmProgram: THTMLTpltDlgFrame; @@ -91,7 +91,6 @@ TAboutDlg = class(TGenericViewDlg) tsPaths: TTabSheet; btnViewAppConfig: TButton; btnViewUserConfig: TButton; - procedure btnRegisterClick(Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); /// <summary>Handles event triggered when user clicks on one of page @@ -100,68 +99,71 @@ TAboutDlg = class(TGenericViewDlg) /// a tab is clicked.</remarks> procedure pcDetailMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); + /// <summary>Handles button click event to display application config file. + /// </summary> procedure btnViewAppConfigClick(Sender: TObject); + /// <summary>Handles button click event to display per-user config file. + /// </summary> procedure btnViewUserConfigClick(Sender: TObject); strict private - fMainDBPathGp: TPathInfoBox; // control that displays main database folder - fUserDBPathGp: TPathInfoBox; // control that displays user database folder - fInstallPathGp: TPathInfoBox; // control that displays program install path + var + /// <summary>Control that displays main database folder.</summary> + fMainDBPathGp: TPathInfoBox; + /// <summary>Control that displays user database folder.</summary> + fUserDBPathGp: TPathInfoBox; + /// <summary>Control that displays program install path.</summary> + fInstallPathGp: TPathInfoBox; + /// <summary>Provides access to main database meta data.</summary> + fMetaData: IDBMetaData; + /// <summary>Handles title frame's OnHTMLEvent event. Checks for mouse + /// events relating to display of the easter egg and acts accordingly. + /// </summary> + /// <param name="Sender">TObject [in] Not used.</param> + /// <param name="EventInfo">THTMLEventInfo [in] Object providing + /// information about the event.</param> procedure HTMLEventHandler(Sender: TObject; const EventInfo: THTMLEventInfo); - {Handles title frame's OnHTMLEvent event. Checks for easter-egg related - mouse events on icon image and acts accordingly. - @param Sender [in] Not used. - @param EventInfo [in] Object providing information about the event. - } - function RegistrationHTML: string; - {Builds HTML used to display registration information. - @return Required HTML. - } - function ContribListHTML(const ContribClass: TContributorsClass): string; - {Builds HTML used to display list of contributors or creates an error - message if contributor list is not available. - @param ContribClass [in] Type of contributor class to use. This - determines names that are displayed. - @return Required HTML. - } + /// <summary>Builds HTML used to display list of contributors or an error + /// message if the list is empty.</summary> + /// <param name="ContribList">IStringList [in] List of contributors to + /// display.</param> + /// <returns>string. Required HTML.</returns> + function ContribListHTML(ContribList: IStringList): string; + /// <summary>Displays content of a config file in a dialogue box or an + /// error message if the file does not exist.</summary> + /// <param name="FileName">string [in] Name of config file to display. + /// </param> + /// <param name="DlgTitle">string [in] Title of dialogue box.</param> procedure ViewConfigFile(const FileName, DlgTitle: string); - {Displays content of a config file in a preview dialogue box. If file - does not exist an error message is displayed. - @param FileName [in] Name of config file. - @param DlgTitle [in] Title of preview dialogue box. - } strict protected + /// <summary>Configures form by creating owned object and custom controls + /// and initialising HTML frames.</summary> + /// <remarks>Called from ancestor class.</remarks> procedure ConfigForm; override; - {Configures form by creating custom controls and initialising HTML frames. - Called from ancestor class. - } - procedure InitForm; override; - {Initialises form's controls. Called from ancestor class. - } - procedure InitHTMLFrames; - {Initialises HTML frames to use required template document with - placeholders replaced by required values. - } + /// <summary>Arranges controls on form.</summary> + /// <remarks>Called from ancestor class.</remarks> procedure ArrangeForm; override; - {Adjusts position of registration button on bottom button line. Called - from ancestor class. - } + /// <summary>Initialises HTML frames to use required HTML templates and + /// resolves all template placeholders.</summary> + procedure InitHTMLFrames; + /// <summary>Updates CSS used for HTML displayed in title frame.</summary> + /// <param name="Sender">TObject [in] Not used.</param> + /// <param name="CSSBuilder">TCSSBuilder [in] Object used to update CSS. + /// </param> procedure UpdateTitleCSS(Sender: TObject; const CSSBuilder: TCSSBuilder); - {Updates CSS used for HTML displayed in title frame. - @param Sender [in] Not used. - @param CSSBuilder [in] Object used to update CSS. - } + /// <summary>Updates CSS used for HTML displayed in detail frames. + /// </summary> + /// <param name="Sender">TObject [in] Not used.</param> + /// <param name="CSSBuilder">TCSSBuilder [in] Object used to update CSS. + /// </param> + /// <remarks>Details frames form the body of the About Box on the Program + /// and Database tabs.</remarks> procedure UpdateDetailCSS(Sender: TObject; const CSSBuilder: TCSSBuilder); - {Updates CSS used for HTML displayed in detail (i.e. program and database) - frames. - @param Sender [in] Not used. - @param CSSBuilder [in] Object used to update CSS. - } public + /// <summary>Displays dialog box.</summary> + /// <param name="AOwner">TComponent [in] Component that owns this dialogus + /// box.</param> class procedure Execute(AOwner: TComponent); - {Displays dialog box. - @param AOwner [in] Component that owns this dialog box. - } end; @@ -170,41 +172,39 @@ implementation uses // Delphi - SysUtils, Graphics, Math, Windows, ShellAPI, IOUtils, + SysUtils, + Graphics, + Math, + Windows, + ShellAPI, + IOUtils, // Project - FmEasterEgg, FmPreviewDlg, FmRegistrationDlg, UAppInfo, UColours, UConsts, - UCSSUtils, UCtrlArranger, UEncodings, UFontHelper, UGraphicUtils, UHTMLUtils, - UHTMLTemplate, UIOUtils, UMessageBox, UResourceUtils, UThemesEx; - - -{ - NOTE: - - The about box uses three HTML templates. These are stored in RT_HTML resources - as: - + "dlg-about-head-tplt.html" - + "dlg-about-program-tplt.html" - + "dlg-about-database-tplt.html". - - The following placeholders are used in one or more of the templates. The - placeholders are replaced by their values within this unit: - - <%Release%> program release number - <%ResURL%> url of programs HTML resources - <%Registered%> info about whether program is registered - <%ContribList%> list of program contributors - <%TesterList%> list of program testers - <%Year%> current year -} - + DB.UMain, + FmEasterEgg, + FmPreviewDlg, + UAppInfo, + UColours, + UConsts, + UCSSUtils, + UCtrlArranger, + UEncodings, + UFontHelper, + UGraphicUtils, + UHTMLUtils, + UHTMLTemplate, + UIOUtils, + UMessageBox, + UResourceUtils, + UStrUtils, + UThemesEx, + UVersionInfo; {$R *.dfm} +/// <summary>Displays Windows Explorer showing a specific folder.</summary> +/// <param name="Folder">string [in] Folder to explore.</param> +/// <returns>Boolean. True if explorer displayed, False if not.</returns> function ExploreFolder(const Folder: string): Boolean; - {Displays Windows Explorer showing a specified folder. - @param Folder [in] Folder to explore. - @return True if explorer displayed, False if not. - } begin if TDirectory.Exists(Folder) then Result := ShellExecute( @@ -217,9 +217,6 @@ function ExploreFolder(const Folder: string): Boolean; { TAboutDlg } procedure TAboutDlg.ArrangeForm; - {Adjusts position of registration button on bottom button line. Called from - ancestor class. - } var PathTabHeight: Integer; begin @@ -245,17 +242,6 @@ procedure TAboutDlg.ArrangeForm; pnlBody.ClientHeight := pnlTitle.Height + bvlSeparator.Height + pcDetail.Height; inherited; - btnRegister.Left := pnlBody.Left; - btnRegister.Top := btnHelp.Top; -end; - -procedure TAboutDlg.btnRegisterClick(Sender: TObject); - {Displays registration wizard when "Register CodeSnip" button is clicked. - @param Sender [in] Not used. - } -begin - if TRegistrationDlg.Execute(Self) then - btnRegister.Hide; // hide registration button now that program registered OK end; procedure TAboutDlg.btnViewAppConfigClick(Sender: TObject); @@ -273,18 +259,11 @@ procedure TAboutDlg.btnViewUserConfigClick(Sender: TObject); end; procedure TAboutDlg.ConfigForm; - {Configures form by creating custom controls and initialising HTML frames. - Called from ancestor class. - } + // Creates and initialises a custom path information control with given + // caption, path and tab order.</summary> function CreatePathInfoBox(const Caption, Path: string; const TabOrder: Integer): TPathInfoBox; - {Creates and initialises a custom path information control. - @param Caption [in] Group box caption. - @param Path [in] Path to be displayed. - @param TabOrder [in] Tab order of info box. - @return New control. - } begin Result := TPathInfoBox.CreateParented(tsPaths.Handle); Result.Parent := tsPaths; @@ -301,6 +280,8 @@ procedure TAboutDlg.ConfigForm; sUserDBPathGpCaption = 'User Database Directory'; begin inherited; + // Create meta data object for main database + fMetaData := TMainDBMetaDataFactory.MainDBMetaDataInstance; // Creates required custom controls fInstallPathGp := CreatePathInfoBox( sInstallPathGpCaption, TAppInfo.AppExeDir, 0 @@ -317,63 +298,46 @@ procedure TAboutDlg.ConfigForm; InitHTMLFrames; end; -function TAboutDlg.ContribListHTML(const ContribClass: TContributorsClass): +function TAboutDlg.ContribListHTML(ContribList: IStringList): string; - {Builds HTML used to display list of contributors or creates an error - message if contributor list is not available. - @param ContribClass [in] Type of contributor class to use. This determines - names that are displayed. - @return Required HTML. - } resourcestring // Error string used when contributor file not available - sNoContributors = 'List not available, please update database.'; + sNoContributors = 'No contributors list available. Database may be corrupt'; var - Contributors: TContributors; // contributors to database Contributor: string; // name of a contributor DivAttrs: IHTMLAttributes; // attributes of div tag begin Result := ''; - // Get list of contributors - Contributors := ContribClass.Create; - try - if not Contributors.IsError then - begin - for Contributor in Contributors do - Result := Result - + THTML.CompoundTag('div', THTML.Entities(Contributor)) - + EOL; - end - else - begin - // List couldn't be found: display warning message - DivAttrs := THTMLAttributes.Create('class', 'warning'); - Result := THTML.CompoundTag( - 'div', DivAttrs, THTML.Entities(sNoContributors) - ); - end; - finally - FreeAndNil(Contributors); + if ContribList.Count > 0 then + begin + for Contributor in ContribList do + Result := Result + + TXHTML.CompoundTag('div', TXHTML.Entities(Contributor)) + + EOL; + end + else + begin + // List couldn't be found: display warning message + DivAttrs := THTMLAttributes.Create('class', 'warning'); + Result := TXHTML.CompoundTag( + 'div', DivAttrs, TXHTML.Entities(sNoContributors) + ); end; end; class procedure TAboutDlg.Execute(AOwner: TComponent); - {Displays dialog box. - @param AOwner [in] Component that owns this dialog box. - } +var + Dlg: TAboutDlg; begin - with Create(AOwner) do - try - ShowModal; - finally - Free; - end; + Dlg := Create(AOwner); + try + Dlg.ShowModal; + finally + Dlg.Free; + end; end; procedure TAboutDlg.FormCreate(Sender: TObject); - {Form initialisation event handler. Sets handler that updates frame's CSS. - @param Sender [in] Not used. - } begin inherited; frmTitle.OnBuildCSS := UpdateTitleCSS; @@ -382,9 +346,6 @@ procedure TAboutDlg.FormCreate(Sender: TObject); end; procedure TAboutDlg.FormDestroy(Sender: TObject); - {Form destruction event handler. Frees non-owned controls. - @param Sender [in] Not used. - } begin inherited; fInstallPathGp.Free; @@ -394,11 +355,6 @@ procedure TAboutDlg.FormDestroy(Sender: TObject); procedure TAboutDlg.HTMLEventHandler(Sender: TObject; const EventInfo: THTMLEventInfo); - {Handles title frame's OnHTMLEvent event. Checks for easter-egg related mouse - events on icon image and acts accordingly. - @param Sender [in] Not used. - @param EventInfo [in] Object providing information about the event. - } const cIconImgId = 'icon'; // id of icon image begin @@ -429,24 +385,10 @@ procedure TAboutDlg.HTMLEventHandler(Sender: TObject; end; end; -procedure TAboutDlg.InitForm; - {Initialises form's controls. - } -begin - inherited; - // Decide whether to display register button - btnRegister.Visible := not TAppInfo.IsRegistered; -end; - procedure TAboutDlg.InitHTMLFrames; - {Initialises HTML frames to use required template document with placeholders - replaced by required values. - } - // --------------------------------------------------------------------------- + // Initialises and loads HTML into title frame. procedure InitTitleFrame; - {Initialises and loads HTML into title frame. - } begin frmTitle.Initialise( 'dlg-about-head-tplt.html', @@ -460,38 +402,106 @@ procedure TAboutDlg.InitHTMLFrames; frmTitle.OnHTMLEvent := HTMLEventHandler; end; + // Initialises and loads HTML into program frame. procedure InitProgramFrame; - {Initialises and loads HTML into program frame. - } begin pcDetail.ActivePage := tsProgram; // display page to let browser load OK frmProgram.Initialise( 'dlg-about-program-tplt.html', procedure(Tplt: THTMLTemplate) begin - Tplt.ResolvePlaceholderHTML('Registered', RegistrationHTML); + // Do nothing: no template placeholders now registration removed end ); end; + // Initialises and loads HTML into database frame. procedure InitDatabaseFrame; - {Initialises and loads HTML into database frame. - } begin - pcDetail.ActivePage := tsDatabase; // display page to let browser load OK + // Ensure browser loads page so we can process it + pcDetail.ActivePage := tsDatabase; + frmDatabase.Initialise( 'dlg-about-database-tplt.html', procedure(Tplt: THTMLTemplate) + var + IsDBAvalable: Boolean; + IsMetaDataAvailable: Boolean; + IsLicenseInfoAvailable: Boolean; + + function DBVersion: string; + var + Ver: TVersionNumber; + begin + Ver := fMetaData.GetVersion; + if Ver.V1 = 1 then + Result := '1' + else + Result := Ver; + end; + begin + // Resolve conditionally displayed block placeholders + IsDBAvalable := Database.Snippets.Count(False) > 0; + IsMetaDataAvailable := fMetaData.IsSupportedVersion + and not fMetaData.IsCorrupt; + IsLicenseInfoAvailable := IsMetaDataAvailable + and (fMetaData.GetLicenseInfo.Name <> '') + and (fMetaData.GetCopyrightInfo.Date <> '') + and (fMetaData.GetCopyrightInfo.Holder <> ''); + Tplt.ResolvePlaceholderHTML( + 'DBAvailable', TCSS.BlockDisplayProp(IsDBAvalable) + ); Tplt.ResolvePlaceholderHTML( - 'ContribList', ContribListHTML(TCodeContributors) + 'DBNotAvailable', TCSS.BlockDisplayProp(not IsDBAvalable) ); Tplt.ResolvePlaceholderHTML( - 'TesterList', ContribListHTML(TTesters) + 'MetaDataAvailable', TCSS.BlockDisplayProp(IsMetaDataAvailable) ); + Tplt.ResolvePlaceholderHTML( + 'MetaDataNotAvailable', TCSS.BlockDisplayProp(not IsMetaDataAvailable) + ); + Tplt.ResolvePlaceholderHTML( + 'LicenseInfoAvailable', TCSS.BlockDisplayProp(IsLicenseInfoAvailable) + ); + Tplt.ResolvePlaceholderHTML( + 'LicenseInfoAvailableInline', + TCSS.InlineDisplayProp(IsLicenseInfoAvailable) + ); + Tplt.ResolvePlaceholderHTML( + 'LicenseInfoNotAvailable', + TCSS.BlockDisplayProp(not IsLicenseInfoAvailable) + ); + + // Resolve content placeholders Tplt.ResolvePlaceholderText( - 'Year', FormatDateTime('YYYY', Now) + 'CopyrightYear', fMetaData.GetCopyrightInfo.Date + ); + Tplt.ResolvePlaceholderText( + 'CopyrightHolders', fMetaData.GetCopyrightInfo.Holder + ); + Tplt.ResolvePlaceholderHTML( + 'DBLicense', + StrIf( + fMetaData.GetLicenseInfo.URL <> '', + TXHTML.CompoundTag( + 'a', + THTMLAttributes.Create([ + THTMLAttribute.Create('href', fMetaData.GetLicenseInfo.URL), + THTMLAttribute.Create('class', 'external-link') + ]), + TXHTML.Entities(fMetaData.GetLicenseInfo.Name) + ), + TXHTML.Entities(fMetaData.GetLicenseInfo.Name) + ) + ); + Tplt.ResolvePlaceholderHTML( + 'ContribList', ContribListHTML(fMetaData.GetContributors) + ); + Tplt.ResolvePlaceholderHTML( + 'TesterList', ContribListHTML(fMetaData.GetTesters) ); + Tplt.ResolvePlaceholderText('Version', DBVersion); end ); end; @@ -510,82 +520,42 @@ procedure TAboutDlg.pcDetailMouseDown(Sender: TObject; Button: TMouseButton; pcDetail.SetFocus; end; -function TAboutDlg.RegistrationHTML: string; - {Builds HTML used to display registration information. - @return Required HTML. - } -resourcestring - // Registration messages - sRegisteredMessage = 'Registered to %0:s.'; - sUnregisteredMessage = 'Unregistered copy:'; - sRegistrationPrompt = 'Please click the button below to register CodeSnip.'; -var - SpanAttrs: IHTMLAttributes; // attributes of span tag -begin - if TAppInfo.IsRegistered then - Result := THTML.Entities( - Format(sRegisteredMessage, [TAppInfo.RegisteredUser]) - ) - else - begin - SpanAttrs := THTMLAttributes.Create('class', 'warning'); - Result := - THTML.CompoundTag( - 'span', SpanAttrs, THTML.Entities(sUnregisteredMessage) - ) + - THTML.Entities(' ' + sRegistrationPrompt); - end; -end; - procedure TAboutDlg.UpdateDetailCSS(Sender: TObject; const CSSBuilder: TCSSBuilder); - {Updates CSS used for HTML displayed in detail (i.e. program and database) - frames. - @param Sender [in] Not used. - @param CSSBuilder [in] Object used to update CSS. - } var ContentFont: TFont; // font used for content begin // Modify body's margin and, for themed windows, background colour - with CSSBuilder.Selectors['body'] do - begin - ContentFont := TFont.Create; - try - TFontHelper.SetContentFont(ContentFont); - AddProperty(TCSS.FontProps(ContentFont)); - if ThemeServicesEx.ThemesEnabled then - AddProperty(TCSS.BackgroundColorProp(ThemeServicesEx.GetTabBodyColour)); - AddProperty(UCSSUtils.TCSS.MarginProp(0, 2, 6, 2)); - finally - FreeAndNil(ContentFont); - end; + ContentFont := TFont.Create; + try + TFontHelper.SetContentFont(ContentFont); + CSSBuilder.Selectors['body'] + .AddProperty(TCSS.FontProps(ContentFont)) + .AddProperty(UCSSUtils.TCSS.MarginProp(0, 2, 6, 2)) + .AddPropertyIf( + ThemeServicesEx.ThemesEnabled, + TCSS.BackgroundColorProp(ThemeServicesEx.GetTabBodyColour) + ); + finally + FreeAndNil(ContentFont); end; // Put border round scroll box - with CSSBuilder.AddSelector('.scrollbox') do - AddProperty(UCSSUtils.TCSS.BorderProp(cssAll, 1, cbsSolid, clBorder)); + CSSBuilder.AddSelector('.scrollbox') + .AddProperty(UCSSUtils.TCSS.BorderProp(cssAll, 1, cbsSolid, clBorder)); // Set colours and font style of contributors and testers headings - with CSSBuilder.AddSelector('.contrib-head, .tester-head') do - begin - AddProperty(TCSS.BackgroundColorProp(clBtnFace)); - AddProperty(TCSS.ColorProp(clBtnText)); - AddProperty(TCSS.FontWeightProp(cfwBold)); - end; + CSSBuilder.AddSelector('.contrib-head, .tester-head') + .AddProperty(TCSS.BackgroundColorProp(clBtnFace)) + .AddProperty(TCSS.ColorProp(clBtnText)) + .AddProperty(TCSS.FontWeightProp(cfwBold)); end; procedure TAboutDlg.UpdateTitleCSS(Sender: TObject; const CSSBuilder: TCSSBuilder); - {Updates CSS used for HTML displayed in title frame. - @param Sender [in] Not used. - @param CSSBuilder [in] Object used to update CSS. - } begin // Set body colour, and put border round it - with CSSBuilder.Selectors['body'] do - begin - AddProperty(TCSS.BackgroundColorProp(clWindow)); - AddProperty(TCSS.PaddingProp(4)); - end; + CSSBuilder.Selectors['body'] + .AddProperty(TCSS.BackgroundColorProp(clWindow)) + .AddProperty(TCSS.PaddingProp(4)); end; procedure TAboutDlg.ViewConfigFile(const FileName, DlgTitle: string); @@ -609,19 +579,12 @@ procedure TAboutDlg.ViewConfigFile(const FileName, DlgTitle: string); { TPathInfoBox } procedure TPathInfoBox.BtnClick(Sender: TObject); - {Button click event handler. Displays folder stored in Path property in - Windows Explorer. - @param Sender [in] Not used. - } begin if Assigned(fPathLbl) and (fPathLbl.Caption <> '') then ExploreFolder(fPathLbl.Caption); end; constructor TPathInfoBox.Create(AOwner: TComponent); - {Component constructor. Creates sub-components and arranges them. - @param AOwner [in] Owning component. - } resourcestring // Hint attached to view button sViewBtnHint = 'Explore...|Display the path in Windows Explorer'; @@ -655,25 +618,17 @@ constructor TPathInfoBox.Create(AOwner: TComponent); end; procedure TPathInfoBox.FontChange(var Msg: TMessage); - {Handles font changes by resizing control to allow for new font size. - @param Msg [in/out] Not used. - } begin inherited; ReArrange; end; function TPathInfoBox.GetPath: string; - {Read accessor for Path property. Gets value from label. - @return Property value. - } begin Result := fPathLbl.Caption; end; procedure TPathInfoBox.ReArrange; - {Resizes and re-arranges control and its sub-components. - } begin TCtrlArranger.SetLabelHeight(fPathLbl); Height := Max(fPathLbl.Height, fViewBtn.Height) + 24; @@ -687,18 +642,12 @@ procedure TPathInfoBox.ReArrange; end; procedure TPathInfoBox.Resize; - {Handles control resizing. Re-arranges control's sub-components. - } begin inherited; ReArrange; end; procedure TPathInfoBox.SetPath(const Value: string); - {Write accessor for Path property. Stores value in label and updates state - of button. - @param Value [in] New property value. - } resourcestring // hints used when path doesn't exist sShortPathDoesNotExist = 'Path does not exist'; diff --git a/Src/FmActiveTextPreviewDlg.pas b/Src/FmActiveTextPreviewDlg.pas index 85cde30b7..747b40f20 100644 --- a/Src/FmActiveTextPreviewDlg.pas +++ b/Src/FmActiveTextPreviewDlg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2009-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2009-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a dialogue box that displays active text rendered from REML markup * or plain text. @@ -146,14 +143,16 @@ class procedure TActiveTextPreviewDlg.Execute(const AOwner: TComponent; @param AOwner [in] Component that owns this dialog box. @param ActiveText [in] Active text to be displayed as HTML. } +var + Dlg: TActiveTextPreviewDlg; begin - with InternalCreate(AOwner) do - try - fActiveText := ActiveText; - ShowModal; - finally - Free; - end; + Dlg := InternalCreate(AOwner); + try + Dlg.fActiveText := ActiveText; + Dlg.ShowModal; + finally + Dlg.Free; + end; end; procedure TActiveTextPreviewDlg.HTMLEventHandler(Sender: TObject; @@ -165,6 +164,7 @@ procedure TActiveTextPreviewDlg.HTMLEventHandler(Sender: TObject; } var ALink: IDispatch; // reference to the any link that was clicked + ProtocolHander: TProtocol; resourcestring // Button captions for choice dialog box sClose = 'Close'; @@ -198,12 +198,12 @@ procedure TActiveTextPreviewDlg.HTMLEventHandler(Sender: TObject; ) = cViewLinkRes then begin // User wants to view link: use protocol handler to display it - with TProtocolFactory.CreateHandler(TAnchors.GetURL(ALink)) do - try - Execute; - finally - Free; - end; + ProtocolHander := TProtocolFactory.CreateHandler(TAnchors.GetURL(ALink)); + try + ProtocolHander.Execute; + finally + ProtocolHander.Free; + end; end; end; end; @@ -217,43 +217,62 @@ procedure TActiveTextPreviewDlg.UpdateCSS(Sender: TObject; const MaxHTMLHeight = 240; // max height of rendered HTML var - ContentFont: TFont; // font used for #content tab + ContentFont: TFont; // font used for #content tab begin ContentFont := TFont.Create; try TFontHelper.SetContentFont(ContentFont); // Set rendered REML container - with CSSBuilder.AddSelector('#content') do - begin - AddProperty(TCSS.FontProps(ContentFont)); - AddProperty(TCSS.BackgroundColorProp(clWindow)); - AddProperty(TCSS.PaddingProp(0, 6, 6, 6)); - AddProperty(TCSS.MarginProp(cssTop, 6)); - AddProperty(TCSS.BorderProp(cssAll, 1, cbsSolid, clBorder)); - AddProperty(TCSS.OverflowProp(covAuto)); - AddProperty(TCSS.WidthProp(cluAuto, 0)); - // Use height instead of maxheight if IE 6 or lower - if TIEInfo.SupportsCSSMaxHeight then - AddProperty(TCSS.MaxHeightProp(MaxHTMLHeight)) - else - AddProperty(TCSS.HeightProp(MaxHTMLHeight)); - end; - with CSSBuilder.AddSelector('.active-text h2') do - begin - AddProperty(TCSS.MarginProp(4, 0, 0, 0)); - AddProperty(TCSS.FontWeightProp(cfwBold)); - AddProperty(TCSS.FontSizeProp(ContentFont.Size + 1)); - end; - with CSSBuilder.AddSelector('.active-text p') do - AddProperty(TCSS.MarginProp(4, 0, 0, 0)); + CSSBuilder.EnsureSelector('#content') + .AddProperty(TCSS.FontProps(ContentFont)) + .AddProperty(TCSS.BackgroundColorProp(clWindow)) + .AddProperty(TCSS.PaddingProp(0, 6, 6, 6)) + .AddProperty(TCSS.MarginProp(cssTop, 6)) + .AddProperty(TCSS.BorderProp(cssAll, 1, cbsSolid, clBorder)) + .AddProperty(TCSS.OverflowProp(covAuto)) + .AddProperty(TCSS.WidthProp(cluAuto, 0)) + .AddPropertyIf( + TIEInfo.SupportsCSSMaxHeight, + TCSS.MaxHeightProp(MaxHTMLHeight), + TCSS.HeightProp(MaxHTMLHeight) + ); + CSSBuilder.EnsureSelector('.active-text h2') + .AddProperty(TCSS.MarginProp(4, 0, 0, 0)) + .AddProperty(TCSS.FontWeightProp(cfwBold)) + .AddProperty(TCSS.FontSizeProp(ContentFont.Size + 1)); + CSSBuilder.EnsureSelector('.active-text p') + .AddProperty(TCSS.MarginProp(4, 0, 0, 0)); // Show or hide text about links depending on if links are present - with CSSBuilder.AddSelector('#linktext') do - begin - if ContainsLinks then - AddProperty(TCSS.DisplayProp(cdsInline)) - else - AddProperty(TCSS.DisplayProp(cdsNone)); - end; + CSSBuilder.EnsureSelector('#linktext') + .AddPropertyIf( + ContainsLinks, TCSS.DisplayProp(cdsInline), TCSS.DisplayProp(cdsNone) + ); + // Set up lists + CSSBuilder.EnsureSelector('.active-text ul') + .AddProperty(TCSS.MarginProp(cssAll, 0)) + .AddProperty(TCSS.MarginProp(cssTop, 4)) + .AddProperty(TCSS.PaddingProp(cssAll, 0)) + .AddProperty(TCSS.PaddingProp(cssLeft, 24)) + .AddProperty(TCSS.ListStylePositionProp(clspOutside)) + .AddProperty(TCSS.ListStyleTypeProp(clstDisc)); + CSSBuilder.EnsureSelector('.active-text ol') + .AddProperty(TCSS.MarginProp(cssAll, 0)) + .AddProperty(TCSS.MarginProp(cssTop, 4)) + .AddProperty(TCSS.PaddingProp(cssAll, 0)) + .AddProperty(TCSS.PaddingProp(cssLeft, 32)) + .AddProperty(TCSS.ListStylePositionProp(clspOutside)) + .AddProperty(TCSS.ListStyleTypeProp(clstDecimal)); + CSSBuilder.EnsureSelector('.active-text li') + .AddProperty(TCSS.PaddingProp(cssAll, 0)) + .AddProperty(TCSS.MarginProp(cssAll, 0)); + CSSBuilder.EnsureSelector('.active-text li ol') + .AddProperty(TCSS.MarginProp(cssTop, 0)); + CSSBuilder.EnsureSelector('.active-text li ul') + .AddProperty(TCSS.MarginProp(cssTop, 0)); + CSSBuilder.EnsureSelector('.active-text ul li') + .AddProperty(TCSS.PaddingProp(cssLeft, 8)); + CSSBuilder.EnsureSelector('.active-text ul li ol li') + .AddProperty(TCSS.PaddingProp(cssLeft, 0)); finally ContentFont.Free; end; diff --git a/Src/FmAddCategoryDlg.pas b/Src/FmAddCategoryDlg.pas index b661ab765..27ebb3258 100644 --- a/Src/FmAddCategoryDlg.pas +++ b/Src/FmAddCategoryDlg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2009-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2009-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a dialogue box that permits user to add a new user defined * category to the database. @@ -136,13 +133,15 @@ class function TAddCategoryDlg.Execute(AOwner: TComponent): Boolean; @param AOwner [in] Component that owns dialog box. @param CatList [in] List of categories available for deletion. } +var + Dlg: TAddCategoryDlg; begin - with InternalCreate(AOwner) do - try - Result := ShowModal = mrOK; - finally - Free; - end; + Dlg := InternalCreate(AOwner); + try + Result := Dlg.ShowModal = mrOK; + finally + Dlg.Free; + end; end; procedure TAddCategoryDlg.UpdateOKBtn; diff --git a/Src/FmBase.pas b/Src/FmBase.pas index e769c1cdf..a20163b2c 100644 --- a/Src/FmBase.pas +++ b/Src/FmBase.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2024, Peter Johnson (gravatar.com/delphidabbler). * * Implements a form that provides the ancestor of all forms in the application. * Provides default names for form window classes along with various operations @@ -139,7 +136,8 @@ implementation // Delphi SysUtils, Windows, Menus, // Project - UAppInfo, UBaseObjects, UClassHelpers, UFontHelper, UKeysHelper, UMenus, + ClassHelpers.UControls, + UAppInfo, UBaseObjects, UFontHelper, UKeysHelper, UMenus, UNulFormAligner, UStrUtils; {$R *.dfm} diff --git a/Src/FmBugReportBaseDlg.pas b/Src/FmBugReportBaseDlg.pas index 632b0a5fb..9d2a1161b 100644 --- a/Src/FmBugReportBaseDlg.pas +++ b/Src/FmBugReportBaseDlg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2009-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2009-2023, Peter Johnson (gravatar.com/delphidabbler). * * Provides a base class and common functionality for bug report dialogue boxes. } @@ -58,7 +55,7 @@ implementation // Delphi ExtActns, // Project - UColours, UCtrlArranger, UFontHelper, Web.UInfo; + UColours, UCtrlArranger, UFontHelper, UUrl; {$R *.dfm} @@ -98,16 +95,18 @@ procedure TBugReportBaseDlg.GoToTracker; {Displays online bug tracker. Descendants should override to add extra functionality. } +var + BrowseAction: TBrowseURL; begin // NOTE: Don't change actBugTracker to TBrowseURL and delete this. Subclasses // must be able to override this method. - with TBrowseURL.Create(nil) do - try - URL := TWebInfo.BugTrackerURL; - Execute; - finally - Free; - end; + BrowseAction := TBrowseURL.Create(nil); + try + BrowseAction.URL := TURL.CodeSnipBugTracker; + BrowseAction.Execute; + finally + BrowseAction.Free; + end; end; procedure TBugReportBaseDlg.lblBugTrackerClick(Sender: TObject); diff --git a/Src/FmCategoryEditDlg.pas b/Src/FmCategoryEditDlg.pas index 8c22e6f1f..d7356df46 100644 --- a/Src/FmCategoryEditDlg.pas +++ b/Src/FmCategoryEditDlg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2009-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2009-2021, Peter Johnson (gravatar.com/delphidabbler). * * Implements an abstract base class for dialogue boxes used to edit user * defined categories. diff --git a/Src/FmCodeExportDlg.pas b/Src/FmCodeExportDlg.pas index b82619efc..39cfdae8e 100644 --- a/Src/FmCodeExportDlg.pas +++ b/Src/FmCodeExportDlg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2008-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2008-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a dialogue box that gets snippets to be exported and creates an * export file containing the selected snippets. @@ -192,14 +189,16 @@ class procedure TCodeExportDlg.Execute(const AOwner: TComponent; @param Snippet [in] Reference to a snippet to pre-select in snippets check list box. If nil or not user-defined then no snippet is pre-selected. } +var + Dlg: TCodeExportDlg; begin - with InternalCreate(AOwner) do - try - SelectSnippet(Snippet); - ShowModal; - finally - Free; - end; + Dlg := InternalCreate(AOwner); + try + Dlg.SelectSnippet(Snippet); + Dlg.ShowModal; + finally + Dlg.Free; + end; end; procedure TCodeExportDlg.SelectSnippet(const Snippet: TSnippet); @@ -233,9 +232,7 @@ procedure TCodeExportDlg.WriteOutputFile; var OutData: TEncodedData; // receives export file content begin - OutData := TCodeExporter.ExportSnippets( - TUserInfo.CreateNul, frmSnippets.SelectedSnippets - ); + OutData := TCodeExporter.ExportSnippets(frmSnippets.SelectedSnippets); TFileIO.WriteAllBytes(StrTrim(edFile.Text), OutData.Data); end; diff --git a/Src/FmCodeImportDlg.dfm b/Src/FmCodeImportDlg.dfm index ebd03dfba..f3cc01a43 100644 --- a/Src/FmCodeImportDlg.dfm +++ b/Src/FmCodeImportDlg.dfm @@ -1,7 +1,7 @@ inherited CodeImportDlg: TCodeImportDlg Caption = 'Import Wizard' ExplicitWidth = 565 - ExplicitHeight = 433 + ExplicitHeight = 436 PixelsPerInch = 96 TextHeight = 13 inherited pnlBody: TPanel @@ -9,7 +9,7 @@ inherited CodeImportDlg: TCodeImportDlg ExplicitHeight = 321 inherited pcWizard: TPageControl Height = 288 - ActivePage = tsUpdate + ActivePage = tsInfo ExplicitHeight = 288 object tsInfo: TTabSheet Caption = 'tsInfo' @@ -33,7 +33,7 @@ inherited CodeImportDlg: TCodeImportDlg object lblFile: TLabel Left = 0 Top = 8 - Width = 220 + Width = 230 Height = 13 Caption = 'Open import &file: (click button to browse for file)' FocusControl = edFile @@ -41,7 +41,7 @@ inherited CodeImportDlg: TCodeImportDlg object lblLoadFile: TLabel Left = 0 Top = 72 - Width = 172 + Width = 179 Height = 13 Caption = 'Click the Next button to read the file.' end @@ -67,60 +67,6 @@ inherited CodeImportDlg: TCodeImportDlg TabOrder = 1 end end - object tsUserInfo: TTabSheet - Caption = 'tsUserInfo' - ImageIndex = 2 - TabVisible = False - object lblName: TLabel - Left = 0 - Top = 8 - Width = 31 - Height = 13 - Caption = 'Name:' - end - object lblEmail: TLabel - Left = 0 - Top = 45 - Width = 28 - Height = 13 - Caption = 'Email:' - end - object lblComments: TLabel - Left = 0 - Top = 85 - Width = 52 - Height = 13 - Caption = 'Comments:' - end - object edComments: TMemo - Left = 72 - Top = 82 - Width = 297 - Height = 154 - ParentColor = True - ReadOnly = True - ScrollBars = ssVertical - TabOrder = 2 - end - object edName: TEdit - Left = 72 - Top = 3 - Width = 297 - Height = 21 - ParentColor = True - ReadOnly = True - TabOrder = 0 - end - object edEmail: TEdit - Left = 72 - Top = 42 - Width = 297 - Height = 21 - ParentColor = True - ReadOnly = True - TabOrder = 1 - end - end object tsUpdate: TTabSheet Caption = 'tsUpdate' ImageIndex = 3 @@ -128,7 +74,7 @@ inherited CodeImportDlg: TCodeImportDlg object lblImportList: TLabel Left = 0 Top = 53 - Width = 86 + Width = 91 Height = 13 Caption = 'Imported &snippets:' FocusControl = lvImports @@ -136,7 +82,7 @@ inherited CodeImportDlg: TCodeImportDlg object lblSelectedSnippet: TLabel Left = 0 Top = 217 - Width = 82 + Width = 83 Height = 13 Caption = 'S&elected snippet:' FocusControl = edRename @@ -148,9 +94,9 @@ inherited CodeImportDlg: TCodeImportDlg Height = 40 AutoSize = False Caption = - 'The imported functions are listed below. Select, deselect and re' + - 'name as required then click the "Update" button to update the da' + - 'tabase. Use "Cancel" to abandon the import.' + 'The functions to be imported are listed below. Select, deselect ' + + 'and rename as required then click the "Update" button to update ' + + 'the database. Use "Cancel" to abandon the import.' WordWrap = True end object lvImports: TListView diff --git a/Src/FmCodeImportDlg.pas b/Src/FmCodeImportDlg.pas index 03e66b146..86f0fbef2 100644 --- a/Src/FmCodeImportDlg.pas +++ b/Src/FmCodeImportDlg.pas @@ -1,12 +1,9 @@ -{ +{ * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2011-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2011-2025, Peter Johnson (gravatar.com/delphidabbler). * * Implements a wizard dialogue box that handles the import of user defined * snippets into the database. Permits snippets from the import file to be @@ -20,9 +17,17 @@ interface uses // Delphi - Classes, ActnList, Controls, ComCtrls, StdCtrls, ExtCtrls, Forms, + Classes, + ActnList, + Controls, + ComCtrls, + StdCtrls, + ExtCtrls, + Forms, // Project - FmWizardDlg, UBaseObjects, UCodeImportMgr; + FmWizardDlg, + UBaseObjects, + UCodeImportMgr; type /// <summary> @@ -33,21 +38,14 @@ interface TCodeImportDlg = class(TWizardDlg, INoPublicConstruct) tsInfo: TTabSheet; tsFile: TTabSheet; - tsUserInfo: TTabSheet; tsUpdate: TTabSheet; lblIntro: TLabel; lblFile: TLabel; edFile: TEdit; btnBrowse: TButton; tsFinish: TTabSheet; - lblName: TLabel; - lblEmail: TLabel; - lblComments: TLabel; - edComments: TMemo; lvImports: TListView; lblImportList: TLabel; - edName: TEdit; - edEmail: TEdit; lblLoadFile: TLabel; btnRename: TButton; edRename: TEdit; @@ -74,13 +72,12 @@ TCodeImportDlg = class(TWizardDlg, INoPublicConstruct) strict private const // Indices of wizard pages - cIntroPage = 0; - cFilePage = 1; - cUserInfoPage = 2; // displayed only there is user info - cUpdatePage = 3; - cFinishPage = 4; + cIntroPage = 0; + cFilePage = 1; + cUpdatePage = 2; + cFinishPage = 3; // Index of subitems in list view - cLVActionIdx = 1; + cLVActionIdx = 1; cLVImportName = 0; var /// <summary>Reference to import manager object used to perform import @@ -103,8 +100,6 @@ TCodeImportDlg = class(TWizardDlg, INoPublicConstruct) /// error message if not.</summary> class procedure CanOpenDialogClose(Sender: TObject; var CanClose: Boolean); - /// <summary>Populates controls on user information page.</summary> - procedure InitUserInfo; /// <summary>Displays current details of all snippets in import file in /// list view on update page.</summary> procedure InitImportInfo; @@ -156,14 +151,6 @@ TCodeImportDlg = class(TWizardDlg, INoPublicConstruct) /// <remarks>Overridden method called from ancestor class.</remarks> procedure MoveForward(const PageIdx: Integer; var CanMove: Boolean); override; - /// <summary>Determines index of page following page indexed by PageIdx. - /// Skips user info page if there is no user info.</summary> - /// <remarks>Overridden method called from ancestor class.</remarks> - function NextPage(const PageIdx: Integer): Integer; override; - /// <summary>Determines index of page preceding page indexed by PageIdx. - /// Skips user info page if there is no user info.</summary> - /// <remarks>Overridden method called from ancestor class.</remarks> - function PrevPage(const PageIdx: Integer): Integer; override; public /// <summary>Displays wizard, passing a reference to import manager object /// to be used for import operations. Returns True if wizard finishes or @@ -178,10 +165,16 @@ implementation uses // Delphi - SysUtils, Dialogs, + SysUtils, + Dialogs, // Project - UCtrlArranger, UExceptions, UMessageBox, UOpenDialogEx, UOpenDialogHelper, - USnippetValidator, UStrUtils; + UCtrlArranger, + UExceptions, + UMessageBox, + UOpenDialogEx, + UOpenDialogHelper, + USnippetValidator, + UStrUtils; {$R *.dfm} @@ -235,21 +228,18 @@ procedure TCodeImportDlg.actRenameUpdate(Sender: TObject); procedure TCodeImportDlg.ArrangeForm; begin TCtrlArranger.SetLabelHeights(Self); + // Arrange controls on tab sheets + // tsInfo - { nothing to do } + // nothing to do + // tsFile TCtrlArranger.AlignVCentres( TCtrlArranger.BottomOf(lblFile, 6), [edFile, btnBrowse] ); lblLoadFile.Top := TCtrlArranger.BottomOf([edFile, btnBrowse], 12); - // tsUserInfo - TCtrlArranger.AlignVCentres(8, [lblName, edName]); - TCtrlArranger.AlignVCentres( - TCtrlArranger.BottomOf([lblName, edName], 8), [lblEmail, edEmail] - ); - lblComments.Top := TCtrlArranger.BottomOf([lblEmail, edEmail], 8); - edComments.Top := lblComments.Top; + // tsUpdate lblImportList.Top := TCtrlArranger.BottomOf(lblModifyInstructions, 8); lvImports.Top := TCtrlArranger.BottomOf(lblImportList, 6); @@ -257,12 +247,13 @@ procedure TCodeImportDlg.ArrangeForm; TCtrlArranger.AlignVCentres( TCtrlArranger.BottomOf(lblSelectedSnippet, 6), [edRename, btnRename] ); + // tsFinish sbFinish.Top := TCtrlArranger.BottomOf(lblFinish, 6); // Size body pnlBody.ClientHeight := TCtrlArranger.MaxContainerHeight( - [tsInfo, tsFile, tsUserInfo, tsUpdate, tsFinish] + [tsInfo, tsFile, tsUpdate, tsFinish] ) + pnlBody.ClientHeight - tsInfo.Height; // Arrange inherited controls and size the form @@ -272,7 +263,6 @@ procedure TCodeImportDlg.ArrangeForm; procedure TCodeImportDlg.BeginPage(const PageIdx: Integer); begin case PageIdx of - cUserInfoPage: InitUserInfo; cUpdatePage: InitImportInfo; cFinishPage: PresentResults; end; @@ -306,13 +296,15 @@ function TCodeImportDlg.CountImportSnippets: Integer; class function TCodeImportDlg.Execute(AOwner: TComponent; const ImportMgr: TCodeImportMgr): Boolean; +var + Dlg: TCodeImportDlg; begin - with InternalCreate(AOwner, ImportMgr) do - try - Result := ShowModal = mrOK; - finally - Free; - end; + Dlg := InternalCreate(AOwner, ImportMgr); + try + Result := Dlg.ShowModal = mrOK; + finally + Dlg.Free; + end; end; function TCodeImportDlg.GetFileNameFromEditCtrl: string; @@ -332,14 +324,12 @@ function TCodeImportDlg.HeadingText(const PageIdx: Integer): string; // Page headings sIntroPageheading = 'Import snippets from a file'; sFilePage = 'Choose import file'; - sUserInfoPage = 'User information'; sUpdatePage = 'Edit import and update database'; sFinishPage = 'Import complete'; begin case PageIdx of cIntroPage: Result := sIntroPageheading; cFilePage: Result := sFilePage; - cUserInfoPage: Result := sUserInfoPage; cUpdatePage: Result := sUpdatePage; cFinishPage: Result := sFinishPage; end; @@ -380,13 +370,6 @@ procedure TCodeImportDlg.InitImportInfo; end; end; -procedure TCodeImportDlg.InitUserInfo; -begin - edName.Text := fImportMgr.UserInfo.Details.Name; - edEmail.Text := fImportMgr.UserInfo.Details.Email; - edComments.Text := fImportMgr.UserInfo.Comments; -end; - constructor TCodeImportDlg.InternalCreate(AOwner: TComponent; const ImportMgr: TCodeImportMgr); begin @@ -430,22 +413,14 @@ procedure TCodeImportDlg.MoveForward(const PageIdx: Integer; end; end; -function TCodeImportDlg.NextPage(const PageIdx: Integer): Integer; -begin - case PageIdx of - cFilePage: - if fImportMgr.UserInfo.IsNul then - Exit(cUpdatePage); - end; - Result := inherited NextPage(PageIdx); -end; - procedure TCodeImportDlg.PresentResults; // --------------------------------------------------------------------------- /// Creates a label containing name of an imported snippet and adds it to /// scroll box with top at given position. procedure AddLabel(var Top: Integer; const SnippetName: string); + const + Bullet = #$2022; var Lbl: TLabel; begin @@ -453,7 +428,7 @@ procedure TCodeImportDlg.PresentResults; Lbl.Parent := sbFinish; Lbl.Left := 0; Lbl.Top := Top; - Lbl.Caption := '� ' + SnippetName; + Lbl.Caption := Bullet + ' ' + SnippetName; Top := TCtrlArranger.BottomOf(Lbl, 2); end; // --------------------------------------------------------------------------- @@ -471,16 +446,6 @@ procedure TCodeImportDlg.PresentResults; end; end; -function TCodeImportDlg.PrevPage(const PageIdx: Integer): Integer; -begin - case PageIdx of - cUpdatePage: - if fImportMgr.UserInfo.IsNul then - Exit(cFilePage); - end; - Result := inherited PrevPage(PageIdx); -end; - procedure TCodeImportDlg.ReadImportFile; begin fImportMgr.Import(GetFileNameFromEditCtrl); diff --git a/Src/FmCodeSubmitDlg.dfm b/Src/FmCodeSubmitDlg.dfm deleted file mode 100644 index b45f70970..000000000 --- a/Src/FmCodeSubmitDlg.dfm +++ /dev/null @@ -1,277 +0,0 @@ -inherited CodeSubmitDlg: TCodeSubmitDlg - Caption = 'Code Submission Wizard' - ExplicitWidth = 565 - ExplicitHeight = 435 - PixelsPerInch = 96 - TextHeight = 13 - inherited pnlBody: TPanel - inherited pcWizard: TPageControl - ActivePage = tsFinished - object tsIntro: TTabSheet - Caption = 'tsIntro' - TabVisible = False - inline frmIntro: TFixedHTMLDlgFrame - Left = 0 - Top = 0 - Width = 369 - Height = 238 - Align = alClient - TabOrder = 0 - TabStop = True - ExplicitWidth = 369 - inherited pnlBrowser: TPanel - Width = 369 - Height = 238 - inherited wbBrowser: TWebBrowser - Width = 369 - Height = 238 - ControlData = { - 4C00000023260000991800000000000000000000000000000000000000000000 - 000000004C000000000000000000000001000000E0D057007335CF11AE690800 - 2B2E126208000000000000004C0000000114020000000000C000000000000046 - 8000000000000000000000000000000000000000000000000000000000000000 - 00000000000000000100000000000000000000000000000000000000} - end - end - end - end - object tsSnippets: TTabSheet - Caption = 'tsSnippets' - ImageIndex = 1 - TabVisible = False - object lblSnippets: TLabel - Left = 0 - Top = 8 - Width = 198 - Height = 13 - Caption = '&Select the snippet(s) you want to submit:' - FocusControl = frmSnippets - end - object lblSnippetPrompt: TLabel - Left = 0 - Top = 221 - Width = 221 - Height = 13 - Caption = 'One or more snippets must be selected' - Font.Charset = DEFAULT_CHARSET - Font.Color = clWindowText - Font.Height = -11 - Font.Name = 'Tahoma' - Font.Style = [fsBold] - ParentFont = False - Visible = False - end - inline frmSnippets: TSelectUserSnippetsFrame - Left = 0 - Top = 32 - Width = 369 - Height = 180 - TabOrder = 0 - TabStop = True - ExplicitTop = 32 - ExplicitWidth = 369 - ExplicitHeight = 180 - inherited tvChecked: TTreeView - Width = 369 - Height = 180 - ExplicitWidth = 369 - ExplicitHeight = 180 - end - end - end - object tsUserInfo: TTabSheet - Caption = 'tsUserInfo' - ImageIndex = 2 - TabVisible = False - object lblName: TLabel - Left = 0 - Top = 8 - Width = 31 - Height = 13 - Caption = '&Name:' - FocusControl = edName - end - object lblEmail: TLabel - Left = 0 - Top = 43 - Width = 70 - Height = 13 - Caption = '&Email Address:' - FocusControl = edEMail - end - object lblComments: TLabel - Left = 0 - Top = 104 - Width = 79 - Height = 13 - Caption = 'Your &Comments:' - FocusControl = edComments - end - object edName: TEdit - Left = 88 - Top = 3 - Width = 185 - Height = 21 - TabOrder = 0 - end - object edEMail: TEdit - Left = 88 - Top = 40 - Width = 185 - Height = 21 - TabOrder = 1 - end - object edComments: TMemo - Left = 0 - Top = 123 - Width = 369 - Height = 112 - TabOrder = 3 - end - inline frmPrivacy: TFixedHTMLDlgFrame - Left = 0 - Top = 69 - Width = 369 - Height = 5 - TabOrder = 2 - TabStop = True - ExplicitTop = 69 - ExplicitWidth = 369 - ExplicitHeight = 5 - inherited pnlBrowser: TPanel - Width = 369 - Height = 5 - ExplicitWidth = 369 - ExplicitHeight = 18 - inherited wbBrowser: TWebBrowser - Width = 369 - Height = 5 - TabStop = False - ExplicitTop = -2 - ExplicitWidth = 369 - ExplicitHeight = 18 - ControlData = { - 4C00000023260000840000000000000000000000000000000000000000000000 - 000000004C000000000000000000000001000000E0D057007335CF11AE690800 - 2B2E126208000000000000004C0000000114020000000000C000000000000046 - 8000000000000000000000000000000000000000000000000000000000000000 - 00000000000000000100000000000000000000000000000000000000} - end - end - end - end - object tsLicense: TTabSheet - Caption = 'tsLicense' - ImageIndex = 5 - TabVisible = False - ExplicitLeft = 8 - ExplicitTop = 11 - object chkAgreeLicense: TCheckBox - Left = 3 - Top = 200 - Width = 363 - Height = 17 - Caption = 'I confirm my &agreement with statements 1 and 2 above' - Font.Charset = DEFAULT_CHARSET - Font.Color = clWindowText - Font.Height = -11 - Font.Name = 'Tahoma' - Font.Style = [fsBold] - ParentFont = False - TabOrder = 0 - end - inline frmLicenseTerms: TFixedHTMLDlgFrame - Left = 0 - Top = 0 - Width = 369 - Height = 169 - TabOrder = 1 - TabStop = True - ExplicitWidth = 369 - ExplicitHeight = 169 - inherited pnlBrowser: TPanel - Width = 369 - Height = 169 - inherited wbBrowser: TWebBrowser - Width = 369 - Height = 169 - ControlData = { - 4C00000023260000771100000000000000000000000000000000000000000000 - 000000004C000000000000000000000001000000E0D057007335CF11AE690800 - 2B2E126208000000000000004C0000000114020000000000C000000000000046 - 8000000000000000000000000000000000000000000000000000000000000000 - 00000000000000000100000000000000000000000000000000000000} - end - end - end - end - object tsSubmit: TTabSheet - Caption = 'tsSubmit' - ImageIndex = 4 - TabVisible = False - ExplicitLeft = 0 - object btnPreview: TButton - Left = 136 - Top = 164 - Width = 98 - Height = 25 - Caption = '&Preview Data...' - TabOrder = 0 - OnClick = btnPreviewClick - end - inline frmSubmit: TFixedHTMLDlgFrame - Left = 0 - Top = 0 - Width = 366 - Height = 145 - TabOrder = 1 - TabStop = True - ExplicitWidth = 366 - ExplicitHeight = 145 - inherited pnlBrowser: TPanel - Width = 366 - Height = 145 - inherited wbBrowser: TWebBrowser - Width = 366 - Height = 145 - ControlData = { - 4C000000D4250000FC0E00000000000000000000000000000000000000000000 - 000000004C000000000000000000000001000000E0D057007335CF11AE690800 - 2B2E126208000000000000004C0000000114020000000000C000000000000046 - 8000000000000000000000000000000000000000000000000000000000000000 - 00000000000000000100000000000000000000000000000000000000} - end - end - end - end - object tsFinished: TTabSheet - Caption = 'tsFinished' - ImageIndex = 3 - TabVisible = False - inline frmFinished: TFixedHTMLDlgFrame - Left = 0 - Top = 0 - Width = 369 - Height = 238 - Align = alClient - TabOrder = 0 - TabStop = True - inherited pnlBrowser: TPanel - Width = 369 - Height = 238 - inherited wbBrowser: TWebBrowser - Width = 369 - Height = 238 - ControlData = { - 4C00000023260000991800000000000000000000000000000000000000000000 - 000000004C000000000000000000000001000000E0D057007335CF11AE690800 - 2B2E126208000000000000004C0000000114020000000000C000000000000046 - 8000000000000000000000000000000000000000000000000000000000000000 - 00000000000000000100000000000000000000000000000000000000} - end - end - end - end - end - end -end diff --git a/Src/FmCodeSubmitDlg.pas b/Src/FmCodeSubmitDlg.pas deleted file mode 100644 index e132564e7..000000000 --- a/Src/FmCodeSubmitDlg.pas +++ /dev/null @@ -1,507 +0,0 @@ -{ - * This Source Code Form is subject to the terms of the Mozilla Public License, - * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ - * - * Copyright (C) 2008-2014, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ - * - * Implements a wizard dialogue box that gathers data about and submits a user's - * code submission for inclusion in the database. -} - - -unit FmCodeSubmitDlg; - - -interface - - -uses - // Delphi - StdCtrls, Forms, ComCtrls, Controls, ExtCtrls, Classes, - // Project - DB.USnippet, FmWizardDlg, FrBrowserBase, FrCheckedTV, FrFixedHTMLDlg, - FrHTMLDlg, FrSelectSnippetsBase, FrSelectUserSnippets, UBaseObjects, - UEncodings, UExceptions; - - -type - - { - TCodeSubmitDlg: - Implements a wizard dialog that gathers data about and submits a user's - code submission for inclusion in the database. - } - TCodeSubmitDlg = class(TWizardDlg, INoPublicConstruct) - btnPreview: TButton; - edComments: TMemo; - edEMail: TEdit; - edName: TEdit; - frmSnippets: TSelectUserSnippetsFrame; - lblComments: TLabel; - lblEmail: TLabel; - lblName: TLabel; - lblSnippetPrompt: TLabel; - lblSnippets: TLabel; - tsFinished: TTabSheet; - tsIntro: TTabSheet; - tsSnippets: TTabSheet; - tsUserInfo: TTabSheet; - tsSubmit: TTabSheet; - frmPrivacy: TFixedHTMLDlgFrame; - tsLicense: TTabSheet; - chkAgreeLicense: TCheckBox; - frmLicenseTerms: TFixedHTMLDlgFrame; - frmIntro: TFixedHTMLDlgFrame; - frmSubmit: TFixedHTMLDlgFrame; - frmFinished: TFixedHTMLDlgFrame; - procedure btnPreviewClick(Sender: TObject); - strict private - var - fData: TEncodedData; // Contains submission as XML document - procedure SelectSnippet(const Snippet: TSnippet); - {Selects the specified snippet in the check list of snippets or clears - selections. - @param Snippet [in] Snippet to be selected in the list. If Snippet is - nil then list is cleared of selections. - } - procedure SnippetListChange(Sender: TObject); - {Handles change events in list of snippets. Updates state of button on - snippet page and display of prompt if there is an error. - @param Sender [in] Not used. - } - procedure FocusFirstControl(const PageIdx: Integer); - {Focusses first control on page if necessary. - @param ParamIdx [in] Index of page for which focus is required. - } - procedure ValidatePage(PageIdx: Integer); - {Validates user entries on wizard pages. - @param PageIdx [in] Index of page to be validated. - @except Raises EDataEntry exceptions with reference to relevant when an - error is encountered. - } - procedure BuildSubmission; - {Builds XML document containing details of submission and stores in a - stream. - } - procedure DoSubmit; - {Attempts to submit the XML code to the DelphiDabbler website. - @except ECodeSubmitDlg raised on web service or script errors. - } - procedure SaveUserData; - {Saves content of some wizard controls to persistent storage. - } - strict protected - procedure ArrangeForm; override; - {Aligns controls vertically where necessary to accomodate height of - controls that depend on UI font. - } - procedure ConfigForm; override; - {Loads required HTML into HTML frame and modified fonts where required. - } - procedure InitForm; override; - {Initialises some wizard controls from persistent data. - } - function HeadingText(const PageIdx: Integer): string; override; - {Gets text to be displayed in a wizard page. - @param PageIdx [in] Page for which heading is required. - @return The required heading. - } - procedure UpdateButtons(const PageIdx: Integer); override; - {Updates wizard buttons depending on current page and state. - @param PageIdx [in] Index of current page. - } - procedure MoveForward(const PageIdx: Integer; var CanMove: Boolean); - override; - {Called when about to move forward to a new page. Prevents movement if - there is an error on the current page. Handles entry errors by refocussing - control where error occured. - @param PageIdx [in] Index of page we are about move to. - @param CanMove [in/out] Flag indicating whether page change is allowed. - Defaults to true. - } - procedure BeginPage(const PageIdx: Integer); override; - {Called when a wizard page is first displayed. Focusses first control and - performs any action required. - @param PageIdx [in] Index of page to be initialised. - } - constructor InternalCreate(AOwner: TComponent); override; - {Protected class constructor. Initialise objects required by this wizard. - } - public - class procedure Execute(const AOwner: TComponent; const Snippet: TSnippet); - {Excutes code submission dialog box. Submits code snippet to DelphiDabbler - web service if user OKs. - @param AOwner [in] Component that owns and parent's dialog box. - @param Snippet [in] Reference to any snippet to be selected in snippets - list. If nil nothing is selected. - } - end; - - { - ECodeSubmitDlg: - Class of exception raised by errors in TCodeSubmitDlg. - } - ECodeSubmitDlg = class(ECodeSnip); - - -implementation - - -uses - // Delphi - Graphics, - // Project - FmPreviewDlg, UCodeImportExport, UConsts, UCtrlArranger, UEmailHelper, - UFontHelper, UMessageBox, UStrUtils, UUserDetails, UUserDetailsPersist, - Web.UCodeSubmitter, Web.UExceptions; - - -{$R *.dfm} - -const - // Indices of wizard pages - cIntroPageIdx = 0; - cSnippetsPageIdx = 1; - cUserInfoPageIdx = 2; - cLicensePageIdx = 3; - cSubmitPageIdx = 4; - cFinishPageIdx = 5; - - -{ TCodeSubmitDlg } - -procedure TCodeSubmitDlg.ArrangeForm; - {Aligns controls vertically where necessary to accomodate height of controls - that depend on UI font. - } -begin - inherited; - TCtrlArranger.SetLabelHeights(Self); - // tsIntro - { nothing to do } - // tsSnippets - lblSnippetPrompt.Top := tsSnippets.Height - lblSnippetPrompt.Height - 0; - frmSnippets.Top := TCtrlArranger.BottomOf(lblSnippets, 4); - frmSnippets.Height := lblSnippetPrompt.Top - frmSnippets.Top - 8; - // tsUserInfo - frmPrivacy.Top := TCtrlArranger.BottomOf(edEmail, 8); - frmPrivacy.Height := frmPrivacy.DocHeight; - lblComments.Top := TCtrlArranger.BottomOf(frmPrivacy, 8); - edComments.Top := TCtrlArranger.BottomOf(lblComments, 4); - edComments.Height := tsUserInfo.Height - edComments.Top; - // tsLicense - frmLicenseTerms.Left := 0; - frmLicenseTerms.Top := 0; - frmLicenseTerms.Width := tsLicense.ClientWidth; - frmLicenseTerms.Height := frmLicenseTerms.DocHeight; - chkAgreeLicense.Top := TCtrlArranger.BottomOf(frmLicenseTerms, 16); - // tsSubmit - frmSubmit.Left := 0; - frmSubmit.Top := 0; - frmSubmit.Width := tsSubmit.ClientWidth; - frmSubmit.Height := frmSubmit.DocHeight; - btnPreview.Top := TCtrlArranger.BottomOf(frmSubmit, 8); - TCtrlArranger.AlignHCentresTo([frmSubmit], [btnPreview]); - // tsFinished - { nothing to do } -end; - -procedure TCodeSubmitDlg.BeginPage(const PageIdx: Integer); - {Called when a wizard page is first displayed. Focusses first control and - performs any action required. - @param PageIdx [in] Index of page to be initialised. - } -begin - FocusFirstControl(PageIdx); - case PageIdx of - cSubmitPageIdx: BuildSubmission; - cFinishPageIdx: SaveUserData; - end; -end; - -procedure TCodeSubmitDlg.btnPreviewClick(Sender: TObject); - {Handles Preview button click event. Displays XML data to be submitted in a - preview dialog box. - @param Sender [in] Not used. - } -begin - TPreviewDlg.Execute(Self, fData, dtPlainText); -end; - -procedure TCodeSubmitDlg.BuildSubmission; - {Builds XML document containing details of submission and stores in a stream. - } -begin - Assert(not frmSnippets.SelectedSnippets.IsEmpty, - ClassName + '.BuildSubmission: No snippets selected'); - Assert(edName.Text <> '', - ClassName + '.BuildSubmission: No user name provided'); - Assert(IsValidEmailAddress(StrTrim(edEmail.Text)), - ClassName + '.BuildSubmission: Invalid or no email address specified'); - // Build the document - fData := TCodeExporter.ExportSnippets( - TUserInfo.Create( - TUserDetails.Create(edName.Text, edEmail.Text), - StrTrim(edComments.Text) - ), - frmSnippets.SelectedSnippets - ); -end; - -procedure TCodeSubmitDlg.ConfigForm; - {Loads required HTML into HTML frame and modified fonts where required. - } -begin - inherited; - pcWizard.ActivePage := tsFinished; - frmFinished.Initialise('dlg-codesubmit-finished.html'); - pcWizard.ActivePage := tsSubmit; - frmSubmit.Initialise('dlg-codesubmit-submit.html'); - pcWizard.ActivePage := tsLicense; - frmLicenseTerms.Initialise('dlg-codesubmit-license.html'); - pcWizard.ActivePage := tsUserInfo; - frmPrivacy.Initialise('frm-emailprivacy.html'); - pcWizard.ActivePage := tsIntro; - frmIntro.Initialise('dlg-codesubmit-intro.html'); - - frmSnippets.CanCollapse := True; - - TFontHelper.SetDefaultBaseFonts( - [chkAgreeLicense.Font, lblSnippetPrompt.Font] - ); -end; - -procedure TCodeSubmitDlg.DoSubmit; - {Attempts to submit the XML code to the DelphiDabbler website. - @except ECodeSubmitDlg raised on web service or script errors. - } -resourcestring - // Web service / server error meesage - sWebServerError = 'Submission failed because web server reported ' - + 'HTTP Error %0:d: %1:s'; -var - WebSvc: TCodeSubmitter; // communicates with web service -begin - try - // Do the submission - WebSvc := TCodeSubmitter.Create; - try - Screen.Cursor := crHourglass; - Enabled := False; - // POST the data - WebSvc.SubmitData(fData.Data); - finally - WebSvc.Free; - Enabled := True; - Screen.Cursor := crDefault; - end; - except - // handle any exceptions from submission: we convert expected exceptions to - // ECodeSubmitDlg to save triggering this dialog again: others are re-raised - on E: EHTTPError do - // error on web server: make more friendly - raise ECodeSubmitDlg.CreateFmt( - sWebServerError, [E.HTTPErrorCode, StrTrim(E.Message)] - ); - end; -end; - -class procedure TCodeSubmitDlg.Execute(const AOwner: TComponent; - const Snippet: TSnippet); - {Excutes code submission dialog box. Submits code snippet to DelphiDabbler - web service if user OKs. - @param AOwner [in] Component that owns and parent's dialog box. - @param Snippet [in] Reference to any snippet to be selected in snippets - list. If nil nothing is selected. - } -begin - with InternalCreate(AOwner) do - try - SelectSnippet(Snippet); - ShowModal; - finally - Free; - end; -end; - -procedure TCodeSubmitDlg.FocusFirstControl(const PageIdx: Integer); - {Focusses first control on page if necessary. - @param ParamIdx [in] Index of page for which focus is required. - } -begin - case PageIdx of - cSnippetsPageIdx: frmSnippets.SetFocus; - cUserInfoPageIdx: edName.SetFocus; - end; -end; - -function TCodeSubmitDlg.HeadingText(const PageIdx: Integer): string; - {Gets text to be displayed in a wizard page. - @param PageIdx [in] Page for which heading is required. - @return The required heading. - } -resourcestring - // Pages headings - sIntroHeading = 'Submit code to the online database'; - sSnippetsHeading = 'Select snippets'; - sUserInfoHeading = 'About you'; - sLicenseHeading = 'License agreement'; - sSubmitHeading = 'Ready to submit'; - sFinishHeading = 'Submission complete'; -begin - case PageIdx of - cIntroPageIdx: Result := sIntroHeading; - cSnippetsPageIdx: Result := sSnippetsHeading; - cUserInfoPageIdx: Result := sUserInfoHeading; - cLicensePageIdx: Result := sLicenseHeading; - cSubmitPageIdx: Result := sSubmitHeading; - cFinishPageIdx: Result := sFinishHeading; - end; -end; - -procedure TCodeSubmitDlg.InitForm; - {Initialises some wizard controls from persistent data. - } -var - UserDetails: TUserDetails; // user details from settings -begin - inherited; - UserDetails := TUserDetailsPersist.Load; - edName.Text := UserDetails.Name; - edEmail.Text := UserDetails.Email; -end; - -constructor TCodeSubmitDlg.InternalCreate(AOwner: TComponent); - {Protected class constructor. Initialise objects required by this wizard. - } -begin - inherited; - frmSnippets.OnChange := SnippetListChange; -end; - -procedure TCodeSubmitDlg.MoveForward(const PageIdx: Integer; - var CanMove: Boolean); - {Called when about to move forward to a new page. Prevents movement if there - is an error on the current page. Handles entry errors by refocussing control - where error occured. - @param PageIdx [in] Index of page we are about move to. - @param CanMove [in/out] Flag indicating whether page change is allowed. - Defaults to true. - } -begin - CanMove := False; - try - ValidatePage(PageIdx); - case PageIdx of - cSubmitPageIdx: DoSubmit; - end; - CanMove := True; - except - on E: EDataEntry do - begin - // Error occurred in control: refocus it - TMessageBox.Error(Self, E.Message); - if Assigned(E.Ctrl) then - E.Ctrl.SetFocus; - end; - end; -end; - -procedure TCodeSubmitDlg.SaveUserData; - {Saves content of some wizard controls to persistent storage. - } -begin - TUserDetailsPersist.Update(TUserDetails.Create(edName.Text, edEMail.Text)); -end; - -procedure TCodeSubmitDlg.SelectSnippet(const Snippet: TSnippet); - {Selects the specified snippet in the check list of snippets or clears - selections. - @param Snippet [in] Snippet to be selected in the list. If Snippet is nil - then list is cleared of selections. - } -var - List: TSnippetList; // list containing only one snippet -begin - if not Assigned(Snippet) or not Snippet.UserDefined then - frmSnippets.SelectedSnippets := nil - else - begin - List := TSnippetList.Create; - try - List.Add(Snippet); - frmSnippets.SelectedSnippets := List; - finally - List.Free; - end; - end; -end; - -procedure TCodeSubmitDlg.SnippetListChange(Sender: TObject); - {Handles change events in list of snippets. Updates state of button on snippet - page and display of prompt if there is an error. - @param Sender [in] Not used. - } -begin - if CurrentPage = cSnippetsPageIdx then - UpdateButtons(CurrentPage); - lblSnippetPrompt.Visible := frmSnippets.SelectedSnippets.IsEmpty; -end; - -procedure TCodeSubmitDlg.UpdateButtons(const PageIdx: Integer); - {Updates wizard buttons depending on current page and state. - @param PageIdx [in] Index of current page. - } -resourcestring - sSubmitBtnCaption = '&Submit'; // submit button caption -begin - inherited; - // We change button caption on submit page - case PageIdx of - cSubmitPageIdx: btnNext.Caption := sSubmitBtnCaption; - end; -end; - -procedure TCodeSubmitDlg.ValidatePage(PageIdx: Integer); - {Validates user entries on wizard pages. - @param PageIdx [in] Index of page to be validated. - @except Raises EDataEntry exceptions with reference to relevant when an - error is encountered. - } -resourcestring - // Error messages - sNoSnippets = 'Please select at least one snippet'; - sNoName = 'Please enter your name or nickname'; - sNoEmail = 'Please enter an email address'; - sBadEmail = 'Email address is not valid'; - sNoLicenseAgreement = 'You can''t submit the snippet(s) unless you agree ' - + 'to the licensing and authority conditions on this page.' + EOL2 - + 'If you agree please tick the checkbox the press Next.' + EOL2 - + 'If you are unable to agree please click Cancel to abort your ' - + 'submission.'; -begin - case PageIdx of - cSnippetsPageIdx: - if frmSnippets.SelectedSnippets.Count = 0 then - raise EDataEntry.Create(sNoSnippets, frmSnippets); - cUserInfoPageIdx: - begin - if edName.Text = '' then - raise EDataEntry.Create(sNoName, edName); - if edEmail.Text = '' then - raise EDataEntry.Create(sNoEmail, edEmail); - if not IsValidEmailAddress(StrTrim(edEmail.Text)) then - raise EDataEntry.Create(sBadEmail, edEmail); - end; - cLicensePageIdx: - if not chkAgreeLicense.Checked then - raise EDataEntry.Create(sNoLicenseAgreement, chkAgreeLicense); - end; -end; - -end. - diff --git a/Src/FmCompErrorDlg.pas b/Src/FmCompErrorDlg.pas index 73d546132..56744cc6a 100644 --- a/Src/FmCompErrorDlg.pas +++ b/Src/FmCompErrorDlg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2025, Peter Johnson (gravatar.com/delphidabbler). * * Implements a dialogue box that displays compiler error and warning logs. } @@ -183,18 +180,19 @@ class procedure TCompErrorDlg.Execute(const AOwner: TComponent; const ASnippet: TSnippet; const ACompilers: ICompilers); var Compiler: ICompiler; // each supported compiler + Dlg: TCompErrorDlg; begin Assert(Assigned(ACompilers), ClassName + '.Execute: ACompilers is nil'); - with InternalCreate(AOwner) do - try - fSnippet := ASnippet; - for Compiler in ACompilers do - if Compiler.HasErrorsOrWarnings then - fRequiredCompilers.Add(Compiler); - ShowModal; - finally - Free; - end; + Dlg := InternalCreate(AOwner); + try + Dlg.fSnippet := ASnippet; + for Compiler in ACompilers do + if Compiler.HasErrorsOrWarnings then + Dlg.fRequiredCompilers.Add(Compiler); + Dlg.ShowModal; + finally + Dlg.Free; + end; end; procedure TCompErrorDlg.FormCreate(Sender: TObject); @@ -343,7 +341,7 @@ function TCompErrorDlg.TCompilerLog.LogListHTML: string; begin Result := ''; for Line in fLog do - Result := Result + THTML.CompoundTag('li', THTML.Entities(Line)) + EOL; + Result := Result + TXHTML.CompoundTag('li', TXHTML.Entities(Line)) + EOL; end; end. diff --git a/Src/FmCompilersDlg.FrBase.pas b/Src/FmCompilersDlg.FrBase.pas index b390256b4..b8d3a03f5 100644 --- a/Src/FmCompilersDlg.FrBase.pas +++ b/Src/FmCompilersDlg.FrBase.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2011-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2011-2021, Peter Johnson (gravatar.com/delphidabbler). * * Implements a base class for all frames used for editing compiler information * in TCompilersDlg. diff --git a/Src/FmCompilersDlg.FrCompiler.dfm b/Src/FmCompilersDlg.FrCompiler.dfm index 1d844035a..91c15db22 100644 --- a/Src/FmCompilersDlg.FrCompiler.dfm +++ b/Src/FmCompilersDlg.FrCompiler.dfm @@ -1,4 +1,7 @@ inherited CompilersDlgCompilerFrame: TCompilersDlgCompilerFrame + DesignSize = ( + 362 + 235) object lblCompilerPath: TLabel Left = 4 Top = 4 @@ -46,9 +49,17 @@ inherited CompilersDlgCompilerFrame: TCompilersDlgCompilerFrame object chkShowInMain: TCheckBox Left = 4 Top = 88 - Width = 273 + Width = 320 Height = 17 Caption = 'Display &results for this compiler in details pane' TabOrder = 3 end + object chkPermitAutoDetect: TCheckBox + Left = 4 + Top = 111 + Width = 320 + Height = 17 + Caption = 'Permit &auto-detection && registration of this compiler' + TabOrder = 4 + end end diff --git a/Src/FmCompilersDlg.FrCompiler.pas b/Src/FmCompilersDlg.FrCompiler.pas index d3efeaf63..b4657ab02 100644 --- a/Src/FmCompilersDlg.FrCompiler.pas +++ b/Src/FmCompilersDlg.FrCompiler.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2011-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2011-2022, Peter Johnson (gravatar.com/delphidabbler). * * Implements a frame used to edit executable file name of compiler being edited * in TCompilersDlg. @@ -37,6 +34,7 @@ TCompilersDlgCompilerFrame = class(TCompilersDlgBaseFrame) btnBrowse: TButton; btnClear: TButton; chkShowInMain: TCheckBox; + chkPermitAutoDetect: TCheckBox; /// <summary>Displays file open dialog box and places entered file name in /// compiler file name edit control.</summary> procedure btnBrowseClick(Sender: TObject); @@ -101,6 +99,7 @@ procedure TCompilersDlgCompilerFrame.ArrangeControls; ); btnClear.Top := TCtrlArranger.BottomOf([edCompilerPath, btnBrowse], 8); chkShowInMain.Top := TCtrlArranger.BottomOf(btnClear, 24); + chkPermitAutoDetect.Top := TCtrlArranger.BottomOf(chkShowInMain, 8); end; procedure TCompilersDlgCompilerFrame.btnBrowseClick(Sender: TObject); @@ -167,12 +166,22 @@ procedure TCompilersDlgCompilerFrame.Initialise; begin edCompilerPath.Text := Compiler.GetExecFile; chkShowInMain.Checked := Compiler.GetDisplayable; + chkPermitAutoDetect.Visible := Supports(Compiler, ICompilerAutoDetect); + if chkPermitAutoDetect.Visible then + chkPermitAutoDetect.Checked := + (Compiler as ICompilerAutoDetect).GetCanAutoInstall + else + chkPermitAutoDetect.Checked := False; end; procedure TCompilersDlgCompilerFrame.UpdateCompiler; begin Compiler.SetExecFile(GetCompilerPath); Compiler.SetDisplayable(chkShowInMain.Checked); + if Supports(Compiler, ICompilerAutoDetect) then + (Compiler as ICompilerAutoDetect).SetCanAutoInstall( + chkPermitAutoDetect.Checked + ); end; function TCompilersDlgCompilerFrame.ValidateFileName(const FileName: string; diff --git a/Src/FmCompilersDlg.FrLog.pas b/Src/FmCompilersDlg.FrLog.pas index 04309eb71..581cd2bb7 100644 --- a/Src/FmCompilersDlg.FrLog.pas +++ b/Src/FmCompilersDlg.FrLog.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2011-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2011-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a frame used to change log file prefixes used for a compiler being * edited in TCompilersDlg. @@ -129,46 +126,43 @@ procedure TCompilersDlgLogFrame.vleLogPrefixesDrawCell(Sender: TObject; ACol, // Get reference to value editor ValEd := Sender as TValueListEditor; ValEd.Canvas.Font := ValEd.Font; - with ValEd.Canvas do + if gdFixed in State then + begin + // Set colours for fixed cells (non-editable) + ValEd.Canvas.Brush.Color := clBtnFace; + ValEd.Canvas.Font.Color := ValEd.Font.Color; + end + else + begin + // Set colours for editable cell + ValEd.Canvas.Brush.Color := ValEd.Color; + ValEd.Canvas.Font.Color := ValEd.Font.Color; + end; + // Colour the current cell + ValEd.Canvas.FillRect(Rect); + if gdFixed in State then + begin + // draw vertical line at right edge of fixed cell to act as border + ValEd.Canvas.Pen.Color := clBtnShadow; + ValEd.Canvas.MoveTo(Rect.Right - 1, Rect.Top); + ValEd.Canvas.LineTo(Rect.Right - 1, Rect.Bottom); + end; + // Display required text + ValEd.Canvas.TextOut( + Rect.Left + 2 , + Rect.Top + (ValEd.RowHeights[ARow] - ValEd.Canvas.TextHeight('X')) div 2, + ValEd.Cells[ACol, ARow] + ); + if (ACol = 0) and (ValEd.Selection.Top = ARow) then begin - if gdFixed in State then - begin - // Set colours for fixed cells (non-editable) - Brush.Color := clBtnFace; - Font.Color := ValEd.Font.Color; - end - else - begin - // Set colours for editable cell - Brush.Color := ValEd.Color; - Font.Color := ValEd.Font.Color; - end; - // Colour the current cell - FillRect(Rect); - if gdFixed in State then - begin - // draw vertical line at right edge of fixed cell to act as border - Pen.Color := clBtnShadow; - MoveTo(Rect.Right - 1, Rect.Top); - LineTo(Rect.Right - 1, Rect.Bottom); - end; - // Display required text - TextOut( - Rect.Left + 2 , - Rect.Top + (ValEd.RowHeights[ARow] - TextHeight('X')) div 2, - ValEd.Cells[ACol, ARow] + // This is a fixed cell which has selected editable cell adjacent to it + // draw an arrow at the RHS of this cell that points to selected cell + ValEd.Canvas.Pen.Color := clHighlight; + GraphUtil.DrawArrow( + ValEd.Canvas, + sdRight, + Point(Rect.Right - 8, (Rect.Top + Rect.Bottom) div 2 - 4), 4 ); - if (ACol = 0) and (ValEd.Selection.Top = ARow) then - begin - // This is a fixed cell which has selected editable cell adjacent to it - // draw an arrow at the RHS of this cell that points to selected cell - Pen.Color := clHighlight; - GraphUtil.DrawArrow( - ValEd.Canvas, - sdRight, - Point(Rect.Right - 8, (Rect.Top + Rect.Bottom) div 2 - 4), 4 - ); - end; end; end; diff --git a/Src/FmCompilersDlg.FrNamespaces.pas b/Src/FmCompilersDlg.FrNamespaces.pas index f480d1065..93fa88b58 100644 --- a/Src/FmCompilersDlg.FrNamespaces.pas +++ b/Src/FmCompilersDlg.FrNamespaces.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2013-2021, Peter Johnson (gravatar.com/delphidabbler). * * Implements a frame used to edit namespaces used for a compiler being edited * in TCompilersDlg. diff --git a/Src/FmCompilersDlg.FrSearchDirs.pas b/Src/FmCompilersDlg.FrSearchDirs.pas index cef968d2a..57b2195d3 100644 --- a/Src/FmCompilersDlg.FrSearchDirs.pas +++ b/Src/FmCompilersDlg.FrSearchDirs.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2011-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2011-2024, Peter Johnson (gravatar.com/delphidabbler). * * Implements a frame used to edit lists of search directories used for a * compiler being edited in TCompilersDlg. @@ -112,7 +109,10 @@ implementation // Delphi SysUtils, Windows, Graphics, // Project - UBrowseForFolderDlg, UClassHelpers, UCtrlArranger, UStrUtils; + ClassHelpers.UActions, + ClassHelpers.UControls, + ClassHelpers.UGraphics, + UBrowseForFolderDlg, UCtrlArranger, UStrUtils; {$R *.dfm} diff --git a/Src/FmCompilersDlg.FrSwitches.pas b/Src/FmCompilersDlg.FrSwitches.pas index 41c861e42..d5c2dcf09 100644 --- a/Src/FmCompilersDlg.FrSwitches.pas +++ b/Src/FmCompilersDlg.FrSwitches.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2011-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2011-2021, Peter Johnson (gravatar.com/delphidabbler). * * Implements a frame used to edit switches used for a compiler being edited in * TCompilersDlg. diff --git a/Src/FmCompilersDlg.UBannerMgr.pas b/Src/FmCompilersDlg.UBannerMgr.pas index ffd0326ec..4c5ad6557 100644 --- a/Src/FmCompilersDlg.UBannerMgr.pas +++ b/Src/FmCompilersDlg.UBannerMgr.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2011-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2011-2021, Peter Johnson (gravatar.com/delphidabbler). * * Implements a class that manages display of a compiler name on a gradient * filled background in a paint box. diff --git a/Src/FmCompilersDlg.UCompilerListMgr.pas b/Src/FmCompilersDlg.UCompilerListMgr.pas index 6d291bbbb..63ffefe0f 100644 --- a/Src/FmCompilersDlg.UCompilerListMgr.pas +++ b/Src/FmCompilersDlg.UCompilerListMgr.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2011-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2011-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a class that manages display of compiler names in an owner draw * list box. @@ -37,6 +34,8 @@ TCompilerListMgr = class(TObject) /// <summary>Reference to managed list box.</summary> /// <remarks>Must be owner draw.</remarks> fLB: TListBox; + fMapIdxToComp: TArray<TCompilerID>; + fMapCompToIdx: array[TCompilerID] of Integer; /// <summary>List of compilers to be displayed in list box.</summary> fCompilers: ICompilers; /// <summary>Reference to OnSelect event handler.</summary> @@ -90,11 +89,16 @@ implementation constructor TCompilerListMgr.Create(const LB: TListBox; const Compilers: ICompilers); +var + CompID: TCompilerID; begin inherited Create; fLB := LB; fLB.OnClick := LBClickHandler; fLB.OnDrawItem := LBDrawItemHandler; + fLB.Clear; + for CompID := Low(TCompilerID) to High(TCompilerID) do + fLB.Items.Add(''); fCompilers := Compilers; end; @@ -106,19 +110,27 @@ procedure TCompilerListMgr.DoSelect; function TCompilerListMgr.GetSelected: ICompiler; begin - Result := fCompilers[TCompilerID(fLB.ItemIndex)]; + Result := fCompilers[fMapIdxToComp[fLB.ItemIndex]]; end; procedure TCompilerListMgr.Initialise; var CompID: TCompilerID; // loops thru supported compilers + Idx: Integer; begin inherited; + // Add empty list items - one per supported compiler. Note we don't need item // text since we handle drawing of list items ourselves and get details from // compiler objects. + SetLength(fMapIdxToComp, Length(fMapCompToIdx)); + Idx := High(fMapIdxToComp); for CompID := Low(TCompilerID) to High(TCompilerID) do - fLB.Items.Add(''); + begin + fMapIdxToComp[Idx] := CompID; + fMapCompToIdx[CompID] := Idx; + Dec(Idx); + end; // Select first compiler in list and trigger selection event for it fLB.ItemIndex := 0; DoSelect; @@ -142,7 +154,7 @@ procedure TCompilerListMgr.LBDrawItemHandler(Control: TWinControl; ItemRect := Rect; // Compiler object associated with list item - Compiler := fCompilers[TCompilerID(Index)]; + Compiler := fCompilers[fMapIdxToComp[Index]]; // Use bold font if compiler available if Compiler.IsAvailable then @@ -211,7 +223,7 @@ procedure TCompilerListMgr.Refresh(Compiler: ICompiler); var InvalidRect: TRectEx; begin - InvalidRect := fLB.ItemRect(Ord(Compiler.GetID)); + InvalidRect := fLB.ItemRect(fMapCompToIdx[Compiler.GetID]); InvalidateRect(fLB.Handle, @InvalidRect, False); end; diff --git a/Src/FmCompilersDlg.dfm b/Src/FmCompilersDlg.dfm index 80eb26dbb..f9155a014 100644 --- a/Src/FmCompilersDlg.dfm +++ b/Src/FmCompilersDlg.dfm @@ -1,18 +1,18 @@ inherited CompilersDlg: TCompilersDlg Caption = 'Configure Compilers' ClientHeight = 381 - ClientWidth = 524 - ExplicitWidth = 530 - ExplicitHeight = 409 + ClientWidth = 588 + ExplicitWidth = 594 + ExplicitHeight = 410 PixelsPerInch = 96 TextHeight = 13 inherited pnlBody: TPanel - Width = 497 - ExplicitWidth = 497 + Width = 539 + ExplicitWidth = 539 object pbBanner: TPaintBox - Left = 128 + Left = 169 Top = 0 - Width = 369 + Width = 370 Height = 23 Color = clActiveCaption Font.Charset = DEFAULT_CHARSET @@ -26,7 +26,7 @@ inherited CompilersDlg: TCompilersDlg object lbCompilers: TListBox Left = 0 Top = 0 - Width = 121 + Width = 163 Height = 292 Style = lbOwnerDrawFixed Ctl3D = True @@ -36,7 +36,7 @@ inherited CompilersDlg: TCompilersDlg TabOrder = 0 end object pcCompiler: TPageControl - Left = 127 + Left = 169 Top = 29 Width = 370 Height = 263 @@ -45,8 +45,6 @@ inherited CompilersDlg: TCompilersDlg OnMouseDown = pcCompilerMouseDown object tsCompiler: TTabSheet Caption = 'Compiler' - ExplicitWidth = 313 - ExplicitHeight = 199 inline frmCompiler: TCompilersDlgCompilerFrame Left = 0 Top = 0 @@ -54,14 +52,11 @@ inherited CompilersDlg: TCompilersDlg Height = 235 Align = alClient TabOrder = 0 - ExplicitHeight = 199 end end object tsSwitches: TTabSheet Caption = 'Switches' ImageIndex = 2 - ExplicitWidth = 313 - ExplicitHeight = 199 inline frmSwitches: TCompilersDlgSwitchesFrame Left = 0 Top = 0 @@ -69,14 +64,23 @@ inherited CompilersDlg: TCompilersDlg Height = 235 Align = alClient TabOrder = 0 - ExplicitHeight = 199 + inherited btnDefSwitches: TButton + ExplicitLeft = 293 + end + inherited btnAdd: TButton + ExplicitLeft = 222 + end + inherited btnReplace: TButton + ExplicitLeft = 293 + end + inherited btnDelete: TButton + ExplicitLeft = 222 + end end end object tsNamespaces: TTabSheet Caption = 'Namespaces' ImageIndex = 4 - ExplicitWidth = 313 - ExplicitHeight = 199 inline frmNamespaces: TCompilersDlgNamespacesFrame Left = 0 Top = 0 @@ -84,15 +88,23 @@ inherited CompilersDlg: TCompilersDlg Height = 235 Align = alClient TabOrder = 0 - ExplicitWidth = 313 - ExplicitHeight = 199 + inherited btnDefSwitches: TButton + ExplicitLeft = 293 + end + inherited btnAdd: TButton + ExplicitLeft = 222 + end + inherited btnReplace: TButton + ExplicitLeft = 293 + end + inherited btnDelete: TButton + ExplicitLeft = 222 + end end end object tsSearchDirs: TTabSheet Caption = 'Search Paths' ImageIndex = 3 - ExplicitWidth = 313 - ExplicitHeight = 199 inline frmSearchDirs: TCompilersDlgSearchDirsFrame Left = 0 Top = 0 @@ -100,14 +112,17 @@ inherited CompilersDlg: TCompilersDlg Height = 235 Align = alClient TabOrder = 0 - ExplicitHeight = 199 + inherited edPath: TEdit + ExplicitWidth = 322 + end + inherited btnBrowse: TButton + ExplicitLeft = 331 + end end end object tsLog: TTabSheet Caption = 'Output Log' ImageIndex = 1 - ExplicitWidth = 313 - ExplicitHeight = 199 inline frmLog: TCompilersDlgLogFrame Left = 0 Top = 0 @@ -115,19 +130,21 @@ inherited CompilersDlg: TCompilersDlg Height = 235 Align = alClient TabOrder = 0 - ExplicitHeight = 199 + inherited vleLogPrefixes: TValueListEditor + ExplicitWidth = 354 + end end end end end inherited btnHelp: TButton - TabOrder = 4 + TabOrder = 5 end inherited btnCancel: TButton - TabOrder = 3 + TabOrder = 4 end inherited btnOK: TButton - TabOrder = 2 + TabOrder = 3 OnClick = btnOKClick end object btnDetect: TButton @@ -136,7 +153,17 @@ inherited CompilersDlg: TCompilersDlg Width = 153 Height = 25 Caption = '&Detect Delphi Compilers' - TabOrder = 1 + TabOrder = 2 OnClick = btnDetectClick end + object chkStartupDetection: TCheckBox + Left = 8 + Top = 336 + Width = 417 + Height = 17 + Caption = + 'Automatically register newly installed Delphi compilers at progr' + + 'am startup' + TabOrder = 1 + end end diff --git a/Src/FmCompilersDlg.pas b/Src/FmCompilersDlg.pas index 244420263..e78f4fd14 100644 --- a/Src/FmCompilersDlg.pas +++ b/Src/FmCompilersDlg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2022, Peter Johnson (gravatar.com/delphidabbler). * * Implements a dialogue box where the user can configure the Pascal compilers * that are to be used by CodeSnip. @@ -48,6 +45,7 @@ TCompilersDlg = class(TGenericOKDlg, INoPublicConstruct) frmSearchDirs: TCompilersDlgSearchDirsFrame; tsNamespaces: TTabSheet; frmNamespaces: TCompilersDlgNamespacesFrame; + chkStartupDetection: TCheckBox; /// <summary>When Auto Detect Compilers button is clicked, sets executable /// program path for each installed compiler that can detect its own path. /// </summary> @@ -125,7 +123,13 @@ implementation uses // Project - Compilers.UCompilers, IntfCommon, UCtrlArranger, UExeFileType, UFontHelper, + Compilers.UAutoDetect, + Compilers.UCompilers, + Compilers.USettings, + IntfCommon, + UCtrlArranger, + UExeFileType, + UFontHelper, UMessageBox; @@ -145,14 +149,19 @@ procedure TCompilersDlg.ArrangeForm; ); // size dialogue and arrange inherited controls inherited; - // arrange extra button in bottom button line - btnDetect.Left := pnlBody.Left; - btnDetect.Top := btnHelp.Top; + TCtrlArranger.AlignLefts([btnDetect, chkStartupDetection], pnlBody.Left); + // place chkStartupDetection below bevel under body panel + TCtrlArranger.MoveBelow(bvlBottom, chkStartupDetection, 8); + // push buttons below chkStartupDetection + TCtrlArranger.AlignTops( + [btnDetect, btnOK, btnCancel, btnHelp], + TCtrlArranger.BottomOf(chkStartupDetection, 8) + ); + // stretch form height to accomodate insertion of chkStartupDetection + ClientHeight := TCtrlArranger.BottomOf(btnHelp, 6); end; procedure TCompilersDlg.btnDetectClick(Sender: TObject); -var - Compiler: ICompiler; // refers to each compiler resourcestring // Text displayed in confirmation box sOKToDetect = 'Detected compiler file names will overwrite any existing ' @@ -162,19 +171,14 @@ procedure TCompilersDlg.btnDetectClick(Sender: TObject); Exit; // Record any changes to current compiler UpdateCurrentCompiler; - // Loop thru all compilers attempting to detect exe files - for Compiler in fLocalCompilers do - begin - if Supports(Compiler, ICompilerAutoDetect) then + // Register any available compilers + TCompilerAutoDetect.RegisterCompilers(fLocalCompilers, + procedure (RegisteredCompiler: ICompiler) begin - if (Compiler as ICompilerAutoDetect).DetectExeFile then - begin - // Update currently displayed compiler details - if Compiler.GetID = fCurCompiler.GetID then - UpdateEditFrames; - end; - end; - end; + if RegisteredCompiler.GetID = fCurCompiler.GetID then + UpdateEditFrames; + end + ); // Redisplay compiler list and current compiler title to reflect any changes fCompListMgr.Refresh; fBannerMgr.Refresh; @@ -237,20 +241,24 @@ class function TCompilersDlg.Execute(AOwner: TComponent; const ACompilers: ICompilers): Boolean; var Persister: IPersistCompilers; // object used to save object to storage + Dlg: TCompilersDlg; // dialogue box instance begin - with InternalCreate(AOwner) do - try - (fLocalCompilers as IAssignable).Assign(ACompilers); - Result := ShowModal = mrOK; - if Result then - begin - (ACompilers as IAssignable).Assign(fLocalCompilers); - Persister := TPersistCompilers.Create; - Persister.Save(ACompilers); - end; - finally - Free; + Dlg := InternalCreate(AOwner); + try + (Dlg.fLocalCompilers as IAssignable).Assign(ACompilers); + Dlg.chkStartupDetection.Checked := TCompilerSettings.PermitStartupDetection; + Result := Dlg.ShowModal = mrOK; + if Result then + begin + (ACompilers as IAssignable).Assign(Dlg.fLocalCompilers); + Persister := TPersistCompilers.Create; + Persister.Save(ACompilers); + TCompilerSettings.PermitStartupDetection := + Dlg.chkStartupDetection.Checked; end; + finally + Dlg.Free; + end; end; procedure TCompilersDlg.FormCreate(Sender: TObject); diff --git a/Src/FmDBUpdateDlg.dfm b/Src/FmDBUpdateDlg.dfm index 842e489a7..73d2af7ba 100644 --- a/Src/FmDBUpdateDlg.dfm +++ b/Src/FmDBUpdateDlg.dfm @@ -1,102 +1,164 @@ inherited DBUpdateDlg: TDBUpdateDlg - Left = 350 - Top = 171 - BorderIcons = [biMinimize, biMaximize] - Caption = 'Update From Web' - ClientHeight = 344 - OnCloseQuery = FormCloseQuery - ExplicitWidth = 474 - ExplicitHeight = 372 + Left = 0 + Top = 0 + Caption = 'Install or Update DelphiDabbler Snippets Database' + Position = poDesigned + ExplicitWidth = 565 + ExplicitHeight = 436 PixelsPerInch = 96 TextHeight = 13 inherited pnlBody: TPanel - Width = 329 - Height = 137 - ExplicitWidth = 329 - ExplicitHeight = 137 - object lblUpdateFromWeb: TLabel - Left = 0 - Top = 0 - Width = 329 - Height = 41 - Align = alTop - AutoSize = False - Caption = - 'To download or update the code snippets database please connect ' + - 'to the internet and then click the "Update from Web" button.' - WordWrap = True + Width = 409 + ExplicitWidth = 409 + inherited pnlHead: TPanel + Width = 409 + ExplicitWidth = 409 end - object lblError: TLabel - Left = 0 - Top = 0 - Width = 62 - Height = 13 - Cursor = crHandPoint - Caption = 'details below' - Visible = False + inherited pcWizard: TPageControl + Width = 409 + ActivePage = tsFolder + ExplicitWidth = 409 + object tsIntro: TTabSheet + Caption = 'tsIntro' + TabVisible = False + inline frmIntro: THTMLTpltDlgFrame + Left = 0 + Top = 0 + Width = 401 + Height = 236 + TabOrder = 0 + TabStop = True + ExplicitWidth = 401 + inherited pnlBrowser: TPanel + Width = 401 + ExplicitWidth = 401 + inherited wbBrowser: TWebBrowser + Width = 401 + ExplicitWidth = 320 + ExplicitHeight = 240 + ControlData = { + 4C00000072290000641800000000000000000000000000000000000000000000 + 000000004C000000000000000000000001000000E0D057007335CF11AE690800 + 2B2E126208000000000000004C0000000114020000000000C000000000000046 + 8000000000000000000000000000000000000000000000000000000000000000 + 00000000000000000100000000000000000000000000000000000000} + end + end + end + end + object tsFolder: TTabSheet + Caption = 'tsFolder' + ImageIndex = 1 + TabVisible = False + object lblFolder: TLabel + Left = 0 + Top = 8 + Width = 322 + Height = 13 + Caption = + 'Enter database download &folder: (click button to browse for fol' + + 'der)' + FocusControl = edPath + end + object lblFolderPageInfo: TLabel + Left = 0 + Top = 72 + Width = 220 + Height = 13 + Caption = 'Click the Next button when ready to proceed.' + end + object edPath: TEdit + Left = 0 + Top = 28 + Width = 332 + Height = 21 + TabOrder = 0 + end + object btnBrowse: TButton + Left = 338 + Top = 28 + Width = 27 + Height = 21 + Action = actBrowse + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Tahoma' + Font.Style = [fsBold] + ParentFont = False + TabOrder = 1 + end + end + object tsLoad: TTabSheet + Caption = 'tsLoad' + ImageIndex = 2 + TabVisible = False + inline frmLoad: TFixedHTMLDlgFrame + Left = 0 + Top = 0 + Width = 316 + Height = 236 + TabOrder = 0 + TabStop = True + inherited pnlBrowser: TPanel + inherited wbBrowser: TWebBrowser + ControlData = { + 4C000000A9200000641800000000000000000000000000000000000000000000 + 000000004C000000000000000000000001000000E0D057007335CF11AE690800 + 2B2E126208000000000000004C0000000114020000000000C000000000000046 + 8000000000000000000000000000000000000000000000000000000000000000 + 00000000000000000100000000000000000000000000000000000000} + end + end + end + end + object tsFinish: TTabSheet + Caption = 'tsFinish' + ImageIndex = 3 + TabVisible = False + inline frmFinish: TFixedHTMLDlgFrame + Left = 0 + Top = 0 + Width = 316 + Height = 236 + TabOrder = 0 + TabStop = True + inherited pnlBrowser: TPanel + inherited wbBrowser: TWebBrowser + ControlData = { + 4C000000A9200000641800000000000000000000000000000000000000000000 + 000000004C000000000000000000000001000000E0D057007335CF11AE690800 + 2B2E126208000000000000004C0000000114020000000000C000000000000046 + 8000000000000000000000000000000000000000000000000000000000000000 + 00000000000000000100000000000000000000000000000000000000} + end + end + end + end end - object lblHeadline: TLabel - Left = 0 + inline frmProgress: TProgressFrame + Left = 57 Top = 0 - Width = 62 - Height = 13 - Alignment = taCenter - Caption = 'lblHeadline' - Font.Charset = DEFAULT_CHARSET - Font.Color = clWindowText - Font.Height = -11 - Font.Name = 'Tahoma' - Font.Style = [fsBold] - ParentFont = False - Visible = False - end - object edProgress: TMemo - Left = 0 - Top = 19 - Width = 329 - Height = 112 - BevelInner = bvSpace - BevelKind = bkTile - BorderStyle = bsNone - ParentColor = True - ReadOnly = True - ScrollBars = ssVertical - TabOrder = 1 + Width = 320 + Height = 82 + ParentBackground = False + TabOrder = 2 Visible = False + ExplicitLeft = 57 + ExplicitHeight = 82 + inherited pnlBody: TPanel + Height = 82 + ExplicitHeight = 82 + end end - object btnDoUpdate: TButton - Left = 88 - Top = 47 - Width = 153 - Height = 41 - Caption = '&Update from Web' - TabOrder = 0 - OnClick = btnDoUpdateClick - end - end - inherited btnHelp: TButton - TabOrder = 4 - end - inherited btnClose: TButton - TabOrder = 2 end - object btnCancel: TButton - Left = 120 - Top = 304 - Width = 91 - Height = 25 - Cancel = True - Caption = 'Cancel Update' - TabOrder = 3 - OnClick = btnCancelClick - end - object btnNews: TButton - Left = 8 - Top = 304 - Width = 91 - Height = 25 - Caption = 'Latest &News...' - TabOrder = 1 - OnClick = btnNewsClick + object alMain: TActionList + Left = 448 + Top = 208 + object actBrowse: TAction + Caption = '...' + Hint = 'Browse for database folder' + OnExecute = actBrowseExecute + end end end diff --git a/Src/FmDBUpdateDlg.pas b/Src/FmDBUpdateDlg.pas index 7fdc67c65..1ffc7347f 100644 --- a/Src/FmDBUpdateDlg.pas +++ b/Src/FmDBUpdateDlg.pas @@ -1,14 +1,12 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2013, Peter Johnson (www.delphidabbler.com). + * Copyright (C) 2005-2023, Peter Johnson (gravatar.com/delphidabbler). * - * $Rev$ - * $Date$ - * - * Implements a dialogue box that updates the CodeSnip database from web. + * Implements a wizard dialogue box that handles the updating of the main + * DelphiDabbler Code Snippets database. } @@ -20,129 +18,133 @@ interface uses // Project - Forms, StdCtrls, Controls, ExtCtrls, Classes, Messages, - // Delphi - FmGenericViewDlg, UBaseObjects, UDBUpdateMgr, UMemoProgBarMgr; - - + SysUtils, + Windows, + Classes, + ActnList, + StdCtrls, + Forms, + ComCtrls, + Controls, + ExtCtrls, + // VCL + FmWizardDlg, + FrBrowserBase, + FrFixedHTMLDlg, + FrHTMLDlg, + FrHTMLTpltDlg, + FrProgress, + UBaseObjects, + UCodeImportMgr, + UCSSBuilder, + UControlStateMgr, + UDBUpdateMgr; type - /// <summary>Dialogue box that checks to see if an update to the local copy - /// of the online Code Snippets database is available and downloads it if so. - /// </summary> - TDBUpdateDlg = class(TGenericViewDlg, INoPublicConstruct) - btnCancel: TButton; - btnDoUpdate: TButton; - lblUpdateFromWeb: TLabel; - lblError: TLabel; - edProgress: TMemo; - lblHeadline: TLabel; - btnNews: TButton; - /// <summary>Handles click on cancel button and cancels the update. - /// </summary> - procedure btnCancelClick(Sender: TObject); - /// <summary>Handles click on update button and performs update process. - /// </summary> - /// <remarks>Database is only updated if updates are available.</remarks> - procedure btnDoUpdateClick(Sender: TObject); - /// <summary>Performs required initialisation on form creation.</summary> + /// <summary>Wizard dialogue box that handles the updating of the main + /// DelphiDabbler Code Snippets database from disk.</summary> + TDBUpdateDlg = class(TWizardDlg, INoPublicConstruct) + tsIntro: TTabSheet; + tsFolder: TTabSheet; + frmIntro: THTMLTpltDlgFrame; + lblFolder: TLabel; + edPath: TEdit; + lblFolderPageInfo: TLabel; + btnBrowse: TButton; + alMain: TActionList; + actBrowse: TAction; + tsLoad: TTabSheet; + tsFinish: TTabSheet; + frmLoad: TFixedHTMLDlgFrame; + frmProgress: TProgressFrame; + frmFinish: TFixedHTMLDlgFrame; + /// <summary>Displays standard Browse For Folder dialogue box to get + /// directory containing updated database files.</summary> + procedure actBrowseExecute(Sender: TObject); + /// <summary>Constructs owned object.</summary> procedure FormCreate(Sender: TObject); - /// <summary>Checks if form can close in response to user request. - /// </summary> - procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean); - /// <summary>Tidies up on form destruction.</summary> + /// <summary>Destroys owned objects.</summary> procedure FormDestroy(Sender: TObject); - /// <summary>Handles click on News button by displaying News dialogue box. - /// </summary> - procedure btnNewsClick(Sender: TObject); - strict private - type - /// <summary>Enumeration that specifies display style of 'headline' text - /// in dialogue box.</summary> - THeadlineStyle = (hsNormal, hsCancelled, hsError); strict private + const + // Indices of wizard pages + cIntroPage = 0; + cSelectFolderPage = 1; + cLoadDatabasePage = 2; + cFinishPage = 3; var - /// <summary>Manages display of progress bar in prgress memo control. - /// </summary> - fProgressBarMgr: TMemoProgBarMgr; /// <summary>Flag that indicates if local database was updated.</summary> fDataUpdated: Boolean; - /// <summary>Flag that indicates if user cancelled update.</summary> - fCancelled: Boolean; - /// <summary>Object manages download and update process.</summary> - fUpdateMgr: TDBUpdateMgr; - - /// <summary>Handles activation of application by refreshing the display. - /// </summary> - /// <remarks>This is necessary since hiding the dialogue window by - /// switching to another appliction then switching back causes some of the - /// controls to be hidden. Additionally, some controls are not always - /// displayed correctly when dialogue box is first displayed.</remarks> - procedure WMActivateApp(var Msg: TMessage); message WM_ACTIVATEAPP; - - /// <summary>Returns the directory containing the local database files. - /// </summary> - function GetDataDir: string; - - /// <summary>Handles events triggered by the update manager object to - /// report current status.</summary> - /// <param name="Sender">TObject [in] Object triggering event.</param> - /// <param name="Status">TDBUpdateMgr.TStatus [in] Current status of update - /// manager.</param> - /// <param name="Cancel">Boolean [in/out] Flag that can be set True to - /// indicate that the update should be cancelled.</param> - procedure UpdateStatusHandler(Sender: TObject; Status: TDBUpdateMgr.TStatus; - var Cancel: Boolean); - - /// <summary>Handles OnProgress event triggered by the database update - /// manager when downloading the database. Displays progress in a progress - /// bar.</summary> - /// <param name="Sender">TObject [in] Object triggering event.</param> - /// <param name="BytesReceived">Int64 [in] Total number of bytes received - /// to date.</param> - /// <param name="BytesExpected">Int64 [in] Total number of bytes to be - /// downloaded.</param> - /// <param name="Cancel">Boolean [in/out] Flag that can be set True to - /// indicate that the update should be cancelled.</param> - /// <remarks>NOTE: Setting cancel to True does not cancel the download, - /// which runs to completion. Instead the update process is cancelled after - /// the download completes.</remarks> - procedure DownloadProgressHandler(Sender: TObject; const BytesReceived, - BytesExpected: Int64; var Cancel: Boolean); - - /// <summary>Handles OnProgress event triggered by the file updater when - /// when downloading the database. Displays progress in a progress bar. - /// </summary> - /// <param name="Sender">TObject [in] Object triggering event.</param> - /// <param name="Progress">Bytes [in] Pecentage progress to date.</param> - /// <param name="Cancel">Boolean [in,out] Flag that event handler can set - /// True to abort the update.</param> - /// <remarks>NOTE: Setting cancel to True does not cancel the file update, - /// which runs to completion. Instead the update process is cancelled after - /// the file update completes.</remarks> - procedure FileUpdateProgressHandler(Sender: TObject; const Percent: Byte; - var Cancel: Boolean); - - /// <summary>Displays the given progress message.</summary> - procedure ProgressMsg(const Msg: string); - - /// <summary>Displays a 'headline' message.</summary> - /// <param name="Msg">string [in] Message to display.</param> - /// <param name="Kind">THeadlineStyle [in] Style of message to display. + /// <summary>Object used to disable and enable all controls on the form. + /// </summary> + fControlStateMgr: TControlStateMgr; + + /// <summary>Handles HTML template frame's OnBuildCSS event. Adds + /// additional CSS required by HTML in this form.</summary> + /// <param name="Sender">TObject [in] Reference to object triggering event. /// </param> - procedure HeadlineMsg(const Msg: string; - const Kind: THeadlineStyle = hsNormal); + /// <param name="CSSBuilder">TCSSBuilder [in] Object used to construct the + /// CSS.</param> + procedure BuildCSS(Sender: TObject; const CSSBuilder: TCSSBuilder); + + /// <summary>Validates entries on wizard pages indetified by the page + /// index.</summary> + procedure ValidatePage(const PageIdx: Integer); + + /// <summary>Retrieves import directory name from edit control where it is + /// entered.</summary> + function GetDirNameFromEditCtrl: string; + + /// <summary>Handles OnFileUpdateProgress event of TDBUpdateMgr by updating + /// progress display.</summary> + procedure CopyProgress(Sender: TObject; const Percentage: Single); + + /// <summary>Performs database update.</summary> + procedure DoUpdate; + + /// <summary>Handles given exception, converting expected exceptions into + /// ECodeSnip and re-raising all other unchanged.</summary> + /// <exception>Always raises a new exception.</exception> + /// <remarks>This method is designed to handle exceptions raised when the + /// main database is updated.</remarks> + procedure HandleException(const E: Exception); strict protected - /// <summary>Positions this dialogue box's controls with the form. - /// </summary> + /// <summary>Initialises wizard pages that display HTML content.</summary> + /// <remarks>Overridden method called from ancestor class.</remarks> + procedure ConfigForm; override; + + /// <summary>Aligns and arranges controls in each tab sheet and sizes + /// dialog box to accomodate controls.</summary> + /// <remarks>Overridden method called from ancestor class.</remarks> procedure ArrangeForm; override; - /// <summary>Initialises the form's controls.</summary> - procedure InitForm; override; + /// <summary>Protected constructor that sets up form.</summary> + constructor InternalCreate(AOwner: TComponent); override; + + /// <summary>Returns text of heading on page indexed by PageIdx.</summary> + /// <remarks>Overridden method called from ancestor class.</remarks> + function HeadingText(const PageIdx: Integer): string; override; + + /// <summary>Validates a specified page then performs any action required + /// before next page is displayed.</summary> + /// <param name="PageIdx">Integer [in] Index of page to be checked before + /// moving to next page.</param> + /// <param name="CanMove">Boolean [in/out] Set True to permit next page to + /// be displayed or False to inhibit this. Defaults to True.</param> + /// <remarks>Overridden method called from ancestor class.</remarks> + procedure MoveForward(const PageIdx: Integer; var CanMove: Boolean); + override; + + /// <summary>Updates state and caption of buttons on page index by + /// PageIdx.</summary> + /// <remarks>Implementation of abstract method called from ancestor class. + /// </remarks> + procedure UpdateButtons(const PageIdx: Integer); override; public + /// <summary>Displays the dialogue box and performs any required database /// update.</summary> /// <param name="AOwner">TComponent [in] Component that owns the dialogue @@ -152,289 +154,316 @@ TDBUpdateDlg = class(TGenericViewDlg, INoPublicConstruct) /// update was performed for any reason (i.e. cancelling, loca database is /// up to date or an error occurred.</returns> class function Execute(AOwner: TComponent): Boolean; + end; implementation - uses - // Delphi - SysUtils, + // VCL + IOUtils, + Math, // Project - FmNewsDlg, UAppInfo, UColours, UConsts, UCtrlArranger, UStrUtils, UUtils; - + UAppInfo, + UBrowseForFolderDlg, + UColours, + UCtrlArranger, + UCSSUtils, + UExceptions, + UHTMLTemplate, + UMessageBox, + UStructs, + UStrUtils, + UUrl; {$R *.dfm} +{ TGetDDabSnippetsDlg } + +procedure TDBUpdateDlg.actBrowseExecute(Sender: TObject); +var + Dlg: TBrowseForFolderDlg; // browse for folder standard dialogue box resourcestring - // Progress report messages - sLoggingOn = 'Logging on to web server'; - sCheckForUpdates = 'Checking for updates'; - sDownloading = 'Downloading database'; - sDownloaded = 'Database downloaded'; - sUpdating = 'Updating local database'; - sUpToDate = 'Database is up to date'; - sLoggingOff = 'Logging off'; - sCompleted = 'Update completed'; - sCancelled = 'Update cancelled'; - sCancelling = 'Cancelling...'; - // Dialog box messages - sRunning = 'Performing update'; - sUpdtSuccess = 'Files updated successfully'; - sUpdtUpToDate = 'Database is up to date'; - sUpdtCancelled = 'Update cancelled'; - sUpdtError = '%0:s:'; - // Detailed error message - sErrorDetail = 'Full details of "%0:s" error message are:' - + EOL2 + '%1:s'; - -{ TDBUpdateDlg } + sDlgTitle = 'Choose Database Download Directory'; + sDlgHeading = 'Choose an empty directory or create a new one'; +begin + Dlg := TBrowseForFolderDlg.Create(nil); + try + Dlg.Title := sDlgTitle; + Dlg.Headline := sDlgHeading; + Dlg.MakeFolderBtnVisible := True; + if Dlg.Execute then + edPath.Text := Dlg.FolderName; + finally + Dlg.Free; + end; +end; procedure TDBUpdateDlg.ArrangeForm; +var + HTMLFrameHeight: Integer; + HTMLFrameClientWidth: Integer; begin - // Arrange inherited controls - inherited; TCtrlArranger.SetLabelHeights(Self); - // Controls in initial display - btnDoUpdate.Top := TCtrlArranger.BottomOf(lblUpdateFromWeb, 16); - // Arrange additonal cancel button - btnCancel.Left := btnClose.Left + btnClose.Width - btnCancel.Width; - btnCancel.Top := btnClose.Top; - // Arrange "latest news" button - btnNews.Left := 8; - btnNews.Top := btnClose.Top; - // Align error label - lblError.Left := (pnlBody.Width - lblError.Width) div 2; -end; -procedure TDBUpdateDlg.btnCancelClick(Sender: TObject); -begin + // Arrange controls on tab sheets + + HTMLFrameHeight := MaxIntValue([ + frmIntro.DocHeight, frmLoad.DocHeight, frmFinish.DocHeight + ]); + HTMLFrameClientWidth := frmIntro.ClientWidth; + + // tsInfo + frmIntro.Height := HTMLFrameHeight; + frmIntro.Top := 0; + frmIntro.Left := 0; + + // tsFolder + TCtrlArranger.AlignVCentres( + TCtrlArranger.BottomOf(lblFolder, 6), [edPath, btnBrowse] + ); + lblFolderPageInfo.Top := TCtrlArranger.BottomOf([edPath, btnBrowse], 12); + + // tsLoad + frmLoad.Height := HTMLFrameHeight; + frmLoad.ClientWidth := HTMLFrameClientWidth; + frmLoad.Top := 0; + frmLoad.Left := 0; + + // tsFinish + frmFinish.Height := HTMLFrameHeight; + frmFinish.ClientWidth := HTMLFrameClientWidth; + frmFinish.Top := 0; + frmFinish.Left := 0; + + // Size body + pnlBody.ClientWidth := TCtrlArranger.MaxContainerWidth( + [tsIntro, tsFolder, tsLoad, tsFinish] + ) + pnlBody.ClientWidth - tsIntro.Width; + pnlBody.ClientHeight := TCtrlArranger.MaxContainerHeight( + [tsIntro, tsFolder, tsLoad, tsFinish] + ) + pnlBody.ClientHeight - tsIntro.Height; + + // Arrange inherited controls and size the form inherited; - // Sets cancelled flag checked during download - fCancelled := True; - ProgressMsg(sCancelling); end; -procedure TDBUpdateDlg.btnDoUpdateClick(Sender: TObject); +procedure TDBUpdateDlg.BuildCSS(Sender: TObject; + const CSSBuilder: TCSSBuilder); begin inherited; - // Create update manager - fUpdateMgr := TDBUpdateMgr.Create(GetDataDir, 'Manual'); - try - fUpdateMgr.OnStatus := UpdateStatusHandler; - fUpdateMgr.OnDownloadProgress := DownloadProgressHandler; - fUpdateMgr.OnFileUpdateProgress := FileUpdateProgressHandler; - // Update control visibility - lblUpdateFromWeb.Visible := False; - lblError.Visible := False; - btnDoUpdate.Visible := False; - btnClose.Visible := False; - btnCancel.Visible := True; - edProgress.Visible := True; - HeadlineMsg(sRunning); - // Reset download flag to allow download to continue. Clicking cancel button - // sets this flag true - fCancelled := False; - // Perform the download and display message depending on result - fDataUpdated := False; - case fUpdateMgr.Execute of - urUpdated: - begin - // connection to web succeeded and files were updated - HeadlineMsg(sUpdtSuccess); - fDataUpdated := True; - end; - urNoUpdate: - begin - // connection to web succeeded but no files were updated - HeadlineMsg(sUpdtUpToDate); - end; - urCancelled: - begin - // user cancelled update - HeadlineMsg(sUpdtCancelled, hsCancelled); - end; - urError: - begin - // an error occured during update - fProgressBarMgr.Hide; - ProgressMsg(''); - ProgressMsg(fUpdateMgr.LongError); - HeadlineMsg(Format(sUpdtError, [fUpdateMgr.ShortError]), hsError); - end; - end; - finally - FreeAndNil(fUpdateMgr); - // Reset cancelled flag - fCancelled := False; - // Hide cancel button and show close button - btnCancel.Visible := False; - btnClose.Visible := True; - end; + // Create .framed border style + CSSBuilder.AddSelector('.framed') + .AddProperty(TCSS.BorderProp(cssAll, 1, cbsSolid, clBorder)) + .AddProperty(TCSS.PaddingProp(4)); end; -procedure TDBUpdateDlg.btnNewsClick(Sender: TObject); +procedure TDBUpdateDlg.ConfigForm; +resourcestring + sProgressFrameDesc = 'Copying files...'; begin - TNewsDlg.Execute(Self); + // Initialise Intro HTML frame + pcWizard.ActivePage := tsIntro; + frmIntro.OnBuildCSS := BuildCSS; + frmIntro.Initialise( + 'dlg-dbupdate-intro-tplt.html', + procedure (Tplt: THTMLTemplate) + begin + Tplt.ResolvePlaceholderText( + 'CSDBReleaseURL', + TURL.CodeSnippetsDBReleases + ); + end + ); + + // Initialise Load HTML frame + pcWizard.ActivePage := tsLoad; + frmLoad.OnBuildCSS := BuildCSS; + frmLoad.Initialise('dlg-dbupdate-load.html'); + + // Initialise Finish HTML frame + pcWizard.ActivePage := tsFinish; + frmFinish.OnBuildCSS := BuildCSS; + frmFinish.Initialise('dlg-dbupdate-finish.html'); + + // Initialise progress display frame + frmProgress.Visible := False; + frmProgress.Range := TRange.Create(0, 100); + frmProgress.Description := sProgressFrameDesc; end; -procedure TDBUpdateDlg.DownloadProgressHandler(Sender: TObject; - const BytesReceived, BytesExpected: Int64; var Cancel: Boolean); +procedure TDBUpdateDlg.CopyProgress(Sender: TObject; + const Percentage: Single); begin - if BytesReceived = 0 then - fProgressBarMgr.Max := BytesExpected; - fProgressBarMgr.Position := BytesReceived; - Cancel := fCancelled; + frmProgress.Progress := Round(Percentage); Application.ProcessMessages; end; -class function TDBUpdateDlg.Execute(AOwner: TComponent): Boolean; +procedure TDBUpdateDlg.DoUpdate; + + procedure PrepareUIForUpdate; + begin + Enabled := False; + fControlStateMgr.Update; + frmLoad.Visible := False; + frmProgress.Show(pnlBody); + end; + + procedure RestoreUI; + begin + frmProgress.Hide; + frmLoad.Visible := True; + Enabled := True; + fControlStateMgr.Update; + end; + +var + UpdateMgr: TDBUpdateMgr; begin - with InternalCreate(AOwner) do + fDataUpdated := False; + PrepareUIForUpdate; + try + UpdateMgr := TDBUpdateMgr.Create( + TAppInfo.AppDataDir, GetDirNameFromEditCtrl + ); + UpdateMgr.OnFileUpdateProgress := CopyProgress; try - ShowModal; - Result := fDataUpdated; - finally - Free; + try + UpdateMgr.Execute; + fDataUpdated := True; + finally + UpdateMgr.Free; + end; + except + on E: Exception do + HandleException(E); end; + finally + RestoreUI; + end; end; -procedure TDBUpdateDlg.FileUpdateProgressHandler(Sender: TObject; - const Percent: Byte; var Cancel: Boolean); -begin - if Percent = 0 then - fProgressBarMgr.Max := 100; - fProgressBarMgr.Position := Percent; - Cancel := fCancelled; - Application.ProcessMessages; -end; - -procedure TDBUpdateDlg.FormCloseQuery(Sender: TObject; - var CanClose: Boolean); +class function TDBUpdateDlg.Execute(AOwner: TComponent): Boolean; +var + Dlg: TDBUpdateDlg; begin - inherited; - if btnClose.Visible then - CanClose := True - else - begin - CanClose := False; - fCancelled := True; + Dlg := InternalCreate(AOwner); + try + Dlg.ShowModal; + Result := Dlg.fDataUpdated; + finally + Dlg.Free; end; end; procedure TDBUpdateDlg.FormCreate(Sender: TObject); begin inherited; - // Record that no update yet taken place - fDataUpdated := False; - // Object that handles location and display of progress bar - fProgressBarMgr := TMemoProgBarMgr.Create(edProgress); + fControlStateMgr := TControlStateMgr.Create(Self); end; procedure TDBUpdateDlg.FormDestroy(Sender: TObject); begin + fControlStateMgr.Free; inherited; - FreeAndNil(fProgressBarMgr); end; -function TDBUpdateDlg.GetDataDir: string; +function TDBUpdateDlg.GetDirNameFromEditCtrl: string; begin - Result := TAppInfo.AppDataDir; + Result := StrTrim(edPath.Text); end; -procedure TDBUpdateDlg.HeadlineMsg(const Msg: string; - const Kind: THeadlineStyle); +procedure TDBUpdateDlg.HandleException(const E: Exception); begin - // Display message - lblHeadline.Caption := Msg; - lblHeadline.Visible := True; - // Determine appearance - case Kind of - hsNormal: - begin - // Normal message: standard colours - lblHeadline.ParentFont := True; - lblError.Visible := False; - end; - hsCancelled: - begin - // Cancel message: show in warning text colour - lblHeadline.Font.Color := clWarningText; - lblError.Visible := False; - end; - hsError: - begin - // Error message: show in warning text colour followed by extra error - // info. Make sure headline ends in one space to separate headline from - // error message - lblHeadline.Caption := StrTrimRight(lblHeadline.Caption) + ' '; - lblHeadline.Font.Color := clWarningText; - TCtrlArranger.MoveToRightOf(lblHeadline, lblError); - lblError.Top := lblHeadline.Top; - lblError.Visible := True; - end; - end; + if (E is EInOutError) + or (E is ENotSupportedException) + or (E is EDirectoryNotFoundException) + or (E is EPathTooLongException) + or (E is EArgumentException) + or (E is EDBUpdateValidationError) then + raise ECodeSnip.Create(E.Message); + raise E; end; -procedure TDBUpdateDlg.InitForm; +function TDBUpdateDlg.HeadingText(const PageIdx: Integer): string; +resourcestring + sIntroHeading = 'Download the database'; + sSelectFolderHeading = 'Select database download folder'; + sLoadDataseHeading = 'Install the database'; + sFinishHeading = 'Database installed'; begin - inherited; - btnCancel.Visible := False; - btnClose.Visible := True; + case PageIdx of + cIntroPage: Result := sIntroHeading; + cSelectFolderPage: Result := sSelectFolderHeading; + cLoadDatabasePage: Result := sLoadDataseHeading; + cFinishPage: Result := sFinishHeading; + end; end; -procedure TDBUpdateDlg.ProgressMsg(const Msg: string); +constructor TDBUpdateDlg.InternalCreate(AOwner: TComponent); begin - edProgress.Lines.Add(Msg); + Assert(Supports(Self, INoPublicConstruct), ClassName + '.InternalCreate: ' + + 'Form''s protected constructor can''t be called'); + inherited InternalCreate(AOwner); end; -procedure TDBUpdateDlg.UpdateStatusHandler(Sender: TObject; - Status: TDBUpdateMgr.TStatus; var Cancel: Boolean); +procedure TDBUpdateDlg.MoveForward(const PageIdx: Integer; + var CanMove: Boolean); begin - // Update UI according to status - case Status of - usLogOn: - ProgressMsg(sLoggingOn); - usCheckForUpdates: - ProgressMsg(sCheckForUpdates); - usDownloadStart: - begin - ProgressMsg(sDownloading + ' '); - fProgressBarMgr.Show(edProgress.Lines.Count - 1); - end; - usDownloadEnd: - begin - fProgressBarMgr.Hide; - ProgressMsg(sDownloaded); - end; - usUpdating: - begin - ProgressMsg(sUpdating + ' '); - fProgressBarMgr.Show(edProgress.Lines.Count - 1); + // NOTE: Will never be called if PageIdx is last page. + CanMove := False; + try + ValidatePage(PageIdx); + case PageIdx of + cLoadDatabasePage: DoUpdate; end; - usNoUpdate: - ProgressMsg(sUpToDate); - usLogOff: - ProgressMsg(sLoggingOff); - usCompleted: + CanMove := True; + except + on E: EDataEntry do begin - fProgressBarMgr.Hide; - ProgressMsg(sCompleted); + TMessageBox.Error(Self, E.Message); + if Assigned(E.Ctrl) then + E.Ctrl.SetFocus; end; - usCancelled: - ProgressMsg(sCancelled); + on E: ECodeImportMgr do + TMessageBox.Error(Self, E.Message); end; - // Application needs to process messages to allow any UI updates - Application.ProcessMessages; - // Halt update if user cancelled - Cancel := fCancelled; end; -procedure TDBUpdateDlg.WMActivateApp(var Msg: TMessage); +procedure TDBUpdateDlg.UpdateButtons(const PageIdx: Integer); +resourcestring + sLoadPageBtn = '&Load'; begin - Refresh; + inherited; + case PageIdx of + cLoadDatabasePage: btnNext.Caption := sLoadPageBtn; + cFinishPage: btnBack.Enabled := False; + end; end; -end. +procedure TDBUpdateDlg.ValidatePage(const PageIdx: Integer); + + procedure ValidateSelectFolderPage; + resourcestring + sNoDirNameError = 'No update directory specified. Please enter one.'; + begin + if GetDirNameFromEditCtrl = '' then + raise EDataEntry.Create(sNoDirNameError, edPath); + try + TDBUpdateMgr.ValidateUpdate(GetDirNameFromEditCtrl); + except + on E: EDBUpdateValidationError do + raise EDataEntry.Create(E.Message, edPath); + else + raise; + end; + end; +begin + case PageIdx of + cSelectFolderPage: ValidateSelectFolderPage; + end; +end; + +end. diff --git a/Src/FmDeleteCategoryDlg.pas b/Src/FmDeleteCategoryDlg.pas index 6d4f63cd7..3f6a65b0f 100644 --- a/Src/FmDeleteCategoryDlg.pas +++ b/Src/FmDeleteCategoryDlg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2009-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2009-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a dialogue box that permits user to select and delete a user * defined category. @@ -140,14 +137,16 @@ class function TDeleteCategoryDlg.Execute(AOwner: TComponent; @param AOwner [in] Component that owns dialog box. @param CatList [in] List of categories available for deletion. } +var + Dlg: TDeleteCategoryDlg; begin - with InternalCreate(AOwner) do - try - fCategories := CatList; - Result := ShowModal = mrOK; - finally - Free; - end; + Dlg := InternalCreate(AOwner); + try + Dlg.fCategories := CatList; + Result := Dlg.ShowModal = mrOK; + finally + Dlg.Free; + end; end; procedure TDeleteCategoryDlg.SelectionChangeHandler(Sender: TObject); diff --git a/Src/FmDeleteUserDBDlg.dfm b/Src/FmDeleteUserDBDlg.dfm new file mode 100644 index 000000000..1bd52b82a --- /dev/null +++ b/Src/FmDeleteUserDBDlg.dfm @@ -0,0 +1,48 @@ +inherited DeleteUserDBDlg: TDeleteUserDBDlg + Caption = 'Delete User Database' + ExplicitWidth = 474 + ExplicitHeight = 375 + PixelsPerInch = 96 + TextHeight = 13 + inherited pnlBody: TPanel + object edConfirm: TEdit + Left = 0 + Top = 216 + Width = 201 + Height = 21 + TabOrder = 0 + end + inline frmWarning: TFixedHTMLDlgFrame + Left = 0 + Top = 0 + Width = 369 + Height = 210 + Align = alTop + TabOrder = 1 + TabStop = True + ExplicitWidth = 369 + ExplicitHeight = 210 + inherited pnlBrowser: TPanel + Width = 369 + Height = 210 + ExplicitWidth = 369 + ExplicitHeight = 210 + inherited wbBrowser: TWebBrowser + Width = 369 + Height = 210 + ExplicitWidth = 369 + ExplicitHeight = 210 + ControlData = { + 4C00000023260000B41500000000000000000000000000000000000000000000 + 000000004C000000000000000000000001000000E0D057007335CF11AE690800 + 2B2E126208000000000000004C0000000114020000000000C000000000000046 + 8000000000000000000000000000000000000000000000000000000000000000 + 00000000000000000100000000000000000000000000000000000000} + end + end + end + end + inherited btnOK: TButton + OnClick = btnOKClick + end +end diff --git a/Src/FmDeleteUserDBDlg.pas b/Src/FmDeleteUserDBDlg.pas new file mode 100644 index 000000000..d51be0681 --- /dev/null +++ b/Src/FmDeleteUserDBDlg.pas @@ -0,0 +1,111 @@ +{ + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/ + * + * Copyright (C) 2022-2023, Peter Johnson (gravatar.com/delphidabbler). + * + * Implements a dialogue box that asks user to confirm deletion of user-defined + * snippets database. +} + + +unit FmDeleteUserDBDlg; + +interface + +uses + // Delphi + Forms, StdCtrls, Controls, ExtCtrls, Classes, + // Project + FmGenericOKDlg, + FrBrowserBase, FrHTMLDlg, FrFixedHTMLDlg, + UBaseObjects; + +type + TDeleteUserDBDlg = class(TGenericOKDlg, INoPublicConstruct) + edConfirm: TEdit; + frmWarning: TFixedHTMLDlgFrame; + procedure btnOKClick(Sender: TObject); + strict private + const + cConfirmText = 'DELETE MY SNIPPETS'; + var + fPermissionGranted: Boolean; + strict protected + /// <summary>Protected constructor that sets up form.</summary> + constructor InternalCreate(AOwner: TComponent); override; + procedure ConfigForm; override; + procedure ArrangeForm; override; + function IsValidPassword: Boolean; + public + class function Execute(AOwner: TComponent): Boolean; + end; + +implementation + +uses + // Delphi + SysUtils, + // Project + UCtrlArranger, UMessageBox; + +{$R *.dfm} + +procedure TDeleteUserDBDlg.ArrangeForm; +begin + frmWarning.Height := frmWarning.DocHeight; + edConfirm.Left := 0; + TCtrlArranger.MoveBelow(frmWarning, edConfirm, 12); + TCtrlArranger.AlignHCentresTo([frmWarning], [edConfirm]); + pnlBody.ClientHeight := TCtrlArranger.TotalControlHeight(pnlBody) + 8; + inherited; +end; + +procedure TDeleteUserDBDlg.btnOKClick(Sender: TObject); +resourcestring + sBadPassword = 'Invalid confirmation text entered'; +begin + inherited; + fPermissionGranted := IsValidPassword; + if not fPermissionGranted then + begin + TMessageBox.Error(Self, sBadPassword); + edConfirm.Text := ''; + ModalResult := mrNone; + end; +end; + +procedure TDeleteUserDBDlg.ConfigForm; +begin + inherited; +// frmWarning.OnBuildCSS := BuildCSS; + frmWarning.Initialise('dlg-dbdelete.html'); +end; + +class function TDeleteUserDBDlg.Execute(AOwner: TComponent): Boolean; +var + Dlg: TDeleteUserDBDlg; +begin + Dlg := InternalCreate(AOwner); + try + Dlg.ShowModal; + Result := Dlg.fPermissionGranted; + finally + Dlg.Free; + end; +end; + +constructor TDeleteUserDBDlg.InternalCreate(AOwner: TComponent); +begin + Assert(Supports(Self, INoPublicConstruct), ClassName + '.InternalCreate: ' + + 'Form''s protected constructor can''t be called'); + inherited InternalCreate(AOwner); +end; + +function TDeleteUserDBDlg.IsValidPassword: Boolean; +begin + Result := edConfirm.Text = cConfirmText; +end; + +end. diff --git a/Src/FmDependenciesDlg.pas b/Src/FmDependenciesDlg.pas index b3672533b..e1705a01e 100644 --- a/Src/FmDependenciesDlg.pas +++ b/Src/FmDependenciesDlg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2009-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2009-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a dialogue box that displays all the dependencies and dependents * of a snippet. @@ -324,43 +321,47 @@ procedure TDependenciesDlg.DisplayCircularRefWarning; class function TDependenciesDlg.Execute(const AOwner: TComponent; const Snippet: TSnippet; const Tabs: TTabIDs; const PermitSelection: Boolean; const AHelpKeyword: string): ISearch; +var + Dlg: TDependenciesDlg; begin Assert(Tabs <> [], ClassName + '.Execute: Tabs is []'); - with InternalCreate(AOwner) do - try - fSnippetID := Snippet.ID; - fDisplayName := Snippet.DisplayName; - fDependsList := Snippet.Depends; - fTabs := Tabs; - fCanSelect := PermitSelection; - HelpKeyword := AHelpKeyword; - if ShowModal = mrOK then - Result := fSearch - else - Result := nil; - finally - Free; - end; + Dlg := InternalCreate(AOwner); + try + Dlg.fSnippetID := Snippet.ID; + Dlg.fDisplayName := Snippet.DisplayName; + Dlg.fDependsList := Snippet.Depends; + Dlg.fTabs := Tabs; + Dlg.fCanSelect := PermitSelection; + Dlg.HelpKeyword := AHelpKeyword; + if Dlg.ShowModal = mrOK then + Result := Dlg.fSearch + else + Result := nil; + finally + Dlg.Free; + end; end; class procedure TDependenciesDlg.Execute(const AOwner: TComponent; const SnippetID: TSnippetID; const DisplayName: string; const DependsList: TSnippetList; const Tabs: TTabIDs; const AHelpKeyword: string); +var + Dlg: TDependenciesDlg; begin Assert(Tabs <> [], ClassName + '.Execute: Tabs is []'); - with InternalCreate(AOwner) do - try - fSnippetID := SnippetID; - fDisplayName := DisplayName; - fDependsList := DependsList; - fTabs := Tabs; - fCanSelect := False; - HelpKeyword := AHelpKeyword; - ShowModal; - finally - Free; - end; + Dlg := InternalCreate(AOwner); + try + Dlg.fSnippetID := SnippetID; + Dlg.fDisplayName := DisplayName; + Dlg.fDependsList := DependsList; + Dlg.fTabs := Tabs; + Dlg.fCanSelect := False; + Dlg.HelpKeyword := AHelpKeyword; + Dlg.ShowModal; + finally + Dlg.Free; + end; end; procedure TDependenciesDlg.FormDestroy(Sender: TObject); diff --git a/Src/FmDonateDlg.pas b/Src/FmDonateDlg.pas deleted file mode 100644 index 92e212eef..000000000 --- a/Src/FmDonateDlg.pas +++ /dev/null @@ -1,145 +0,0 @@ -{ - * This Source Code Form is subject to the terms of the Mozilla Public License, - * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ - * - * Copyright (C) 2009-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ - * - * Implements a dialogue box that displays information about donating to support - * CodeSnip along with button to access Paypal donation web page in default - * browser. -} - - -unit FmDonateDlg; - - -interface - - -uses - // Delphi - StdCtrls, Controls, ExtCtrls, Classes, Forms, - // Project - FmGenericViewDlg, FrBrowserBase, FrFixedHTMLDlg, FrHTMLDlg, UCSSBuilder; - - -type - { - TDonateDlg: - Dialog box that displays information about donating to support CodeSnip - along with a button to display the Paypal donation web page. - } - TDonateDlg = class(TGenericViewDlg) - btnDoDonate: TButton; - frmContent: TFixedHTMLDlgFrame; - procedure FormCreate(Sender: TObject); - strict private - procedure UpdateCSS(Sender: TObject; const CSSBuilder: TCSSBuilder); - {Modifies CSS used to display dialog box body to achieve required - appearance. - @param Sender [in] Not used. - @param CSSBuilder [in] Object used to modify CSS. - } - strict protected - procedure ConfigForm; override; - {Initialises HTML frame and sets UI font for emboldened Donate button. - Called from ancestor class. - } - procedure ArrangeForm; override; - {Sizes dialog to fit content and adjusts position of donation button on - bottom line. Called from ancestor class. - } - public - class procedure Execute(const AOwner: TComponent); - {Displays dialog box. - @param AOwner [in] Component that owns this dialog box. - } - end; - - -implementation - - -uses - // Delphi - SysUtils, Graphics, - // Project - UCSSUtils, UFontHelper, UPaypalDonateAction; - -{$R *.dfm} - -{ TDonateDlg } - -procedure TDonateDlg.ArrangeForm; - {Sizes dialog to fit content and adjusts position of donation button on bottom - line. Called from ancestor class. - } -begin - // set body panel height from size of content - pnlBody.Height := frmContent.DocHeight; - // size dialog box - inherited; - btnDoDonate.Left := pnlBody.Left; - btnDoDonate.Top := btnClose.Top; -end; - -procedure TDonateDlg.ConfigForm; - {Initialises HTML frame and sets UI font for emboldened Donate button. - } -begin - inherited; - TFontHelper.SetDefaultBaseFont(btnDoDonate.Font); - frmContent.OnBuildCSS := UpdateCSS; - frmContent.Initialise('dlg-donate.html'); -end; - -class procedure TDonateDlg.Execute(const AOwner: TComponent); - {Displays dialog box. - @param AOwner [in] Component that owns this dialog box. - } -begin - with Create(AOwner) do - try - ShowModal; - finally - Free; - end; -end; - -procedure TDonateDlg.FormCreate(Sender: TObject); - {Form construction event handler. Assigns donation button's action. - } -begin - inherited; - btnDoDonate.Action := TPaypalDonateAction.Create(Self); -end; - -procedure TDonateDlg.UpdateCSS(Sender: TObject; const CSSBuilder: TCSSBuilder); - {Modifies CSS used to display dialog box body to achieve required appearance. - @param Sender [in] Not used. - @param CSSBuilder [in] Object used to modify CSS. - } -var - ContentFont: TFont; // font used for dialog box content (not controls) -begin - // Build content font and apply to HTML frame - ContentFont := TFont.Create; - try - TFontHelper.SetContentFont(ContentFont); - with CSSBuilder.Selectors['body'] do - begin - AddProperty(TCSS.FontProps(ContentFont)); - AddProperty(TCSS.BackgroundColorProp(clWindow)); - AddProperty(TCSS.PaddingProp(0, 6, 6, 6)); - end; - finally - FreeAndNil(ContentFont); - end; -end; - -end. - diff --git a/Src/FmDuplicateSnippetDlg.pas b/Src/FmDuplicateSnippetDlg.pas index 0db87ce55..4c207e683 100644 --- a/Src/FmDuplicateSnippetDlg.pas +++ b/Src/FmDuplicateSnippetDlg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2012-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2012-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a dialogue box which can create a duplicate copy of asnippet. } @@ -140,18 +137,20 @@ function TDuplicateSnippetDlg.DisallowedNames: IStringList; class function TDuplicateSnippetDlg.Execute(const AOwner: TComponent; const ASnippet: TSnippet): Boolean; +var + Dlg: TDuplicateSnippetDlg; resourcestring sCaption = 'Duplicate %s'; // dialog box caption begin Assert(Assigned(ASnippet), ClassName + '.Execute: ASnippet is nil'); - with InternalCreate(AOwner) do - try - Caption := Format(sCaption, [ASnippet.DisplayName]); - fSnippet := ASnippet; - Result := ShowModal = mrOK; - finally - Free; - end; + Dlg := InternalCreate(AOwner); + try + Dlg.Caption := Format(sCaption, [ASnippet.DisplayName]); + Dlg.fSnippet := ASnippet; + Result := Dlg.ShowModal = mrOK; + finally + Dlg.Free; + end; end; procedure TDuplicateSnippetDlg.HandleException(const E: Exception); diff --git a/Src/FmEasterEgg.pas b/Src/FmEasterEgg.pas index 031c08228..31bdf2b38 100644 --- a/Src/FmEasterEgg.pas +++ b/Src/FmEasterEgg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2009-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2009-2023, Peter Johnson (gravatar.com/delphidabbler). * * Defines a form that hosts the program's easter egg. } @@ -110,13 +107,15 @@ class procedure TEasterEggForm.Execute(const AOwner: TComponent); {Displays easter egg modally. @param AOwner [in] Component that owns this form. } +var + EggForm: TEasterEggForm; begin - with Create(AOwner) do - try - ShowModal; - finally - Free; - end; + EggForm := Create(AOwner); + try + EggForm.ShowModal; + finally + EggForm.Free; + end; end; procedure TEasterEggForm.FormClose(Sender: TObject; var Action: TCloseAction); diff --git a/Src/FmFavouritesDlg.pas b/Src/FmFavouritesDlg.pas index d996e25b3..7213d1852 100644 --- a/Src/FmFavouritesDlg.pas +++ b/Src/FmFavouritesDlg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2013-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a dialogue box that displays and manages the user's favourite * snippets. @@ -409,39 +406,38 @@ procedure TFavouritesDlg.ConfigForm; end; procedure TFavouritesDlg.CreateLV; + + procedure AddColumn(const ACaption: string; const AWidth: Integer); + var + Col: TListColumn; + begin + Col := fLVFavs.Columns.Add; + Col.Caption := ACaption; + Col.Width := AWidth; + end; + resourcestring sSnippetName = 'Snippet'; sLastAccessed = 'Last used'; begin fLVFavs := TListViewEx.Create(Self); - with fLVFavs do - begin - Parent := pnlBody; - Height := 240; - Width := 360; - HideSelection := False; - ReadOnly := True; - RowSelect := True; - TabOrder := 0; - TabStop := True; - ViewStyle := vsReport; - SortImmediately := False; - with Columns.Add do - begin - Caption := sSnippetName; - Width := 180; - end; - with Columns.Add do - begin - Caption := sLastAccessed; - Width := 140; - end; - OnDblClick := LVDoubleClick; - OnCompare := LVFavouritesCompare; - OnCreateItemClass := LVFavouriteCreateItemClass; - OnCustomDrawItem := LVCustomDrawItem; - OnCustomDrawSubItem := LVCustomDrawSubItem; - end; + fLVFavs.Parent := pnlBody; + fLVFavs.Height := 240; + fLVFavs.Width := 360; + fLVFavs.HideSelection := False; + fLVFavs.ReadOnly := True; + fLVFavs.RowSelect := True; + fLVFavs.TabOrder := 0; + fLVFavs.TabStop := True; + fLVFavs.ViewStyle := vsReport; + fLVFavs.SortImmediately := False; + AddColumn(sSnippetName, 180); + AddColumn(sLastAccessed, 140); + fLVFavs.OnDblClick := LVDoubleClick; + fLVFavs.OnCompare := LVFavouritesCompare; + fLVFavs.OnCreateItemClass := LVFavouriteCreateItemClass; + fLVFavs.OnCustomDrawItem := LVCustomDrawItem; + fLVFavs.OnCustomDrawSubItem := LVCustomDrawSubItem; end; class procedure TFavouritesDlg.Display(AOwner: TComponent; diff --git a/Src/FmFindCompilerDlg.dfm b/Src/FmFindCompilerDlg.dfm index c34a479f7..ccf79cd44 100644 --- a/Src/FmFindCompilerDlg.dfm +++ b/Src/FmFindCompilerDlg.dfm @@ -29,8 +29,8 @@ inherited FindCompilerDlg: TFindCompilerDlg end object lbCompilerVers: TCheckListBox Left = 0 - Top = 16 - Width = 136 + Top = 19 + Width = 154 Height = 186 OnClickCheck = lbCompilerVersClickCheck IntegralHeight = True diff --git a/Src/FmFindCompilerDlg.pas b/Src/FmFindCompilerDlg.pas index abc4ee470..958827bf5 100644 --- a/Src/FmFindCompilerDlg.pas +++ b/Src/FmFindCompilerDlg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2016, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a dialogue box that is used to select criteria for searches for * snippets that compile or don't compile with selected compilers. @@ -58,6 +55,7 @@ TFindCompilerDlg = class(TGenericOKDlg, INoPublicConstruct) fSearchParams: TCompilerSearchParams; // Persistent compiler search options fSearch: ISearch; // Search entered by user fRefinePreviousSearch: Boolean; // Whether to refine previous search + fMapIdxToComp: TArray<TCompilerID>; // Maps list idx to comp ID of entry procedure UpdateOKBtn; {Updates state of OK button according to whether valid entries made in @@ -324,15 +322,17 @@ class function TFindCompilerDlg.Execute(const AOwner: TComponent; @return True if user OKs and search object created or false if user cancels and search object is nil. } +var + Dlg: TFindCompilerDlg; begin - with InternalCreate(AOwner) do - try - Result := (ShowModal = mrOK); - ASearch := fSearch; - RefineExisting := fRefinePreviousSearch; - finally - Free; - end; + Dlg := InternalCreate(AOwner); + try + Result := (Dlg.ShowModal = mrOK); + ASearch := Dlg.fSearch; + RefineExisting := Dlg.fRefinePreviousSearch; + finally + Dlg.Free; + end; end; procedure TFindCompilerDlg.FormCreate(Sender: TObject); @@ -364,13 +364,25 @@ procedure TFindCompilerDlg.InitForm; Option: TCompilerSearchOption; // loops thru possible compiler search options SelOption: Integer; // selected search option Compiler: ICompiler; // references each compiler + CompID: TCompilerID; begin inherited; + // Set up index map that reverses order of compilers + SetLength(fMapIdxToComp, fCompilers.Count); + Idx := High(fMapIdxToComp); + for CompID := Low(TCompilerID) to High(TCompilerID) do + begin + fMapIdxToComp[Idx] := CompID; + Dec(Idx); + end; + // Set up list of compilers and check appropriate ones // we store compiler ID in listbox's Objects[] property - for Compiler in fCompilers do + // Use mapping to reverse order of compilers in list + for Idx := Low(fMapIdxToComp) to High(fMapIdxToComp) do begin - Idx := lbCompilerVers.Items.AddObject( + Compiler := fCompilers[fMapIdxToComp[Idx]]; + lbCompilerVers.Items.AddObject( Compiler.GetName, TObject(Compiler.GetID) ); lbCompilerVers.Checked[Idx] := Compiler.GetID in fSearchParams.Compilers; diff --git a/Src/FmFindTextDlg.pas b/Src/FmFindTextDlg.pas index d6bc502f1..383234cef 100644 --- a/Src/FmFindTextDlg.pas +++ b/Src/FmFindTextDlg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a dialogue box that is used to select criteria for text searches. * @@ -253,15 +250,17 @@ class function TFindTextDlg.Execute(const AOwner: TComponent; @return True if user OKs and search object created or false if user cancels and search object is nil. } +var + Dlg: TFindTextDlg; begin - with InternalCreate(AOwner) do - try - Result := (ShowModal = mrOK); - ASearch := fSearch; - RefineExisting := fRefinePreviousSearch; - finally - Free; - end; + Dlg := InternalCreate(AOwner); + try + Result := (Dlg.ShowModal = mrOK); + ASearch := Dlg.fSearch; + RefineExisting := Dlg.fRefinePreviousSearch; + finally + Dlg.Free; + end; end; procedure TFindTextDlg.FormCreate(Sender: TObject); diff --git a/Src/FmFindXRefsDlg.pas b/Src/FmFindXRefsDlg.pas index b2ab09ae4..0d301ef98 100644 --- a/Src/FmFindXRefsDlg.pas +++ b/Src/FmFindXRefsDlg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2006-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2006-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a dialogue box that is used to select criteria for searches for * cross referenced snippets. @@ -253,16 +250,18 @@ class function TFindXRefsDlg.Execute(const AOwner: TComponent; @return True if user OKs and search object created or false if user cancels and search object is nil. } +var + Dlg: TFindXRefsDlg; begin Assert(Assigned(Snippet), ClassName + '.Execute: Snippet is nil'); - with InternalCreate(AOwner) do - try - fSnippet := Snippet; - Result := (ShowModal = mrOK); - ASearch := fSearch; - finally - Free; - end; + Dlg := InternalCreate(AOwner); + try + Dlg.fSnippet := Snippet; + Result := (Dlg.ShowModal = mrOK); + ASearch := Dlg.fSearch; + finally + Dlg.Free; + end; end; procedure TFindXRefsDlg.FormCreate(Sender: TObject); diff --git a/Src/FmGenericDlg.pas b/Src/FmGenericDlg.pas index 7752f03f6..918ebe35c 100644 --- a/Src/FmGenericDlg.pas +++ b/Src/FmGenericDlg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). * * Implements a base class for all the program's dialogue boxes. } diff --git a/Src/FmGenericModalDlg.pas b/Src/FmGenericModalDlg.pas index 5a4f1695b..20d96f679 100644 --- a/Src/FmGenericModalDlg.pas +++ b/Src/FmGenericModalDlg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2013-2021, Peter Johnson (gravatar.com/delphidabbler). * * Implements an abstract base class for all the program's modal dialogue boxes. } diff --git a/Src/FmGenericNonModalDlg.pas b/Src/FmGenericNonModalDlg.pas index 528af8904..647f0589a 100644 --- a/Src/FmGenericNonModalDlg.pas +++ b/Src/FmGenericNonModalDlg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2013-2021, Peter Johnson (gravatar.com/delphidabbler). * * Implements a base class for all the program's non-modal dialogue boxes. } diff --git a/Src/FmGenericOKDlg.pas b/Src/FmGenericOKDlg.pas index 0bfffc87f..6e46b618e 100644 --- a/Src/FmGenericOKDlg.pas +++ b/Src/FmGenericOKDlg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). * * Implements a base class for modal dialogue boxes that have both OK and Cancel * buttons. diff --git a/Src/FmGenericViewDlg.pas b/Src/FmGenericViewDlg.pas index 18f5cabf0..fddc1ad67 100644 --- a/Src/FmGenericViewDlg.pas +++ b/Src/FmGenericViewDlg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). * * Implements a base class for modal dialogue boxes that have a single Close * button. diff --git a/Src/FmHelpAware.pas b/Src/FmHelpAware.pas index 0b9a5bbe6..58c5ca1b0 100644 --- a/Src/FmHelpAware.pas +++ b/Src/FmHelpAware.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). * * Implements a base class for forms that can access topics in the help file. } diff --git a/Src/FmMain.dfm b/Src/FmMain.dfm index 2da24ce52..5b2eab657 100644 --- a/Src/FmMain.dfm +++ b/Src/FmMain.dfm @@ -8,8 +8,8 @@ inherited MainForm: TMainForm Constraints.MinWidth = 480 Menu = mnuMain OnResize = FormResize - ExplicitWidth = 613 - ExplicitHeight = 495 + ExplicitWidth = 621 + ExplicitHeight = 503 PixelsPerInch = 96 TextHeight = 13 object sbStatusBar: TStatusBar @@ -166,21 +166,8 @@ inherited MainForm: TMainForm ImageIndex = 12 Style = tbsSeparator end - object tbUpdateDbase: TToolButton - Left = 423 - Top = 0 - Action = actUpdateDbase - end - object tbSpacer4: TToolButton - Left = 446 - Top = 0 - Width = 8 - Caption = 'tbSpacer4' - ImageIndex = 12 - Style = tbsSeparator - end object tbHelpContents: TToolButton - Left = 454 + Left = 423 Top = 0 Action = actHelpContents end @@ -323,7 +310,7 @@ inherited MainForm: TMainForm Caption = 'Save Annotated Source...' Hint = 'Save annotated source code|Save the annotated source code of the' + - ' selected snippet or category to a file' + ' selected routine or category to a file' ImageIndex = 18 ShortCut = 24654 OnExecute = actSaveSnippetExecute @@ -354,8 +341,8 @@ inherited MainForm: TMainForm Category = 'File' Caption = 'Save Unit...' Hint = - 'Save unit|Generate and save a Pascal unit containing the snippet' + - 's in the current selection' + 'Save unit|Generate and save a Pascal unit containing valid snipp' + + 'ets in the current selection' ImageIndex = 14 ShortCut = 16469 OnExecute = actSaveUnitExecute @@ -450,7 +437,7 @@ inherited MainForm: TMainForm Caption = 'Copy Annotated Source' Hint = 'Copy annotated source code|Copy the annotated source code of the' + - ' selected snippet or category to the clipboard' + ' selected routine or category to the clipboard' ShortCut = 16462 OnExecute = actCopySnippetExecute OnUpdate = actCopySnippetUpdate @@ -492,18 +479,10 @@ inherited MainForm: TMainForm OnExecute = ActOverviewTabExecute OnUpdate = ActOverviewTabUpdate end - object actWebSite: TBrowseURL - Category = 'Help' - Caption = 'DelphiDabbler Website' - Hint = - 'Visit DelphiDabbler website|Display the DelphiDabbler website in' + - ' the default web browser' - ImageIndex = 6 - end object actHelpQuickStart: TAction Category = 'Help' Caption = 'QuickStart Guide' - Hint = 'QuickStart guide|Display the QuickStart help page' + Hint = 'QuickStart guide|Display the QuickStart help topic' OnExecute = actHelpQuickStartExecute end object actCompilers: TAction @@ -536,26 +515,19 @@ inherited MainForm: TMainForm ImageIndex = 16 OnExecute = actPreferencesExecute end - object actRegister: TAction - Category = 'Tools' - Caption = '&Register CodeSnip...' - Hint = 'Register CodeSnip|Register CodeSnip online' - OnExecute = actRegisterExecute - OnUpdate = actRegisterUpdate - end object actLicense: TAction Category = 'Help' Caption = 'License' - Hint = 'View license|View the end user license agreement' + Hint = 'View license|View a summary of the end user license agreement' ImageIndex = 35 OnExecute = actLicenseExecute end - object actHomePage: TBrowseURL + object actGitHubHome: TBrowseURL Category = 'Help' - Caption = 'CodeSnip Home Page' + Caption = 'CodeSnip On GitHub' Hint = - 'Codesnip home page|Display CodeSnip'#39's web page in the default we' + - 'b browser' + 'Codesnip GitHub project|Display CodeSnip'#39's GitHub web page in th' + + 'e default web browser' ImageIndex = 6 end object actFindXRefs: TAction @@ -641,18 +613,13 @@ inherited MainForm: TMainForm OnExecute = actPrintExecute OnUpdate = actPrintUpdate end - object actPrivacy: TAction - Category = 'Help' - Caption = 'Privacy Statement' - Hint = 'View privacy statement|View the program'#39's privacy statement' - OnExecute = actPrivacyExecute - end object actBackupDatabase: TAction Category = 'Database' Caption = 'Backup User Database...' Hint = 'Backup user database|Backup the user-defined snippet database' ImageIndex = 33 OnExecute = actBackupDatabaseExecute + OnUpdate = ActNonEmptyUserDBUpdate end object actRestoreDatabase: TAction Category = 'Database' @@ -676,22 +643,12 @@ inherited MainForm: TMainForm end object actUpdateDbase: TAction Category = 'Database' - Caption = 'Update From Web...' + Caption = 'Install or Update DelphiDabbler Snippets Database...' Hint = - 'Update database from web|Update the main snippet database from t' + - 'he internet' - ImageIndex = 7 + 'Install or update DelphiDabbler Code Snippets database|Install o' + + 'r update the main DelphiDabbler Code Snippets database' OnExecute = actUpdateDbaseExecute end - object actSubmit: TAction - Category = 'Snippets' - Caption = 'Submit Snippets...' - Hint = - 'Submit|Submit one or more snippets for possible inclusion in the' + - ' main database' - OnExecute = actSubmitExecute - OnUpdate = ActSubmitOrExportUpdate - end object actExportCode: TAction Category = 'Snippets' Caption = 'Export Snippets...' @@ -715,19 +672,12 @@ inherited MainForm: TMainForm OnExecute = actCopyInfoExecute OnUpdate = actCopyInfoUpdate end - object actDonate: TAction - Category = 'Help' - Caption = 'Donate...' - Hint = 'Donate|Make a PayPal donation to support CodeSnip development' - ImageIndex = 23 - OnExecute = actDonateExecute - end object actViewDependencies: TAction Category = 'View' Caption = 'Dependencies...' Hint = 'View dependencies|Display the names of snippets that depend on, ' + - 'or are required, by the selected snippet' + 'or are required by, the selected snippet' ImageIndex = 31 ShortCut = 16452 OnExecute = actViewDependenciesExecute @@ -777,14 +727,6 @@ inherited MainForm: TMainForm OnExecute = actCopySourceExecute OnUpdate = actCopySourceUpdate end - object actProxyServer: TAction - Category = 'Tools' - Caption = 'Proxy Server...' - Hint = - 'Configure proxy server|Configure any proxy server to be used by ' + - 'CodeSnip when accessing web services' - OnExecute = actProxyServerExecute - end object actAddCategory: TAction Category = 'Categories' Caption = 'New Category...' @@ -810,15 +752,6 @@ inherited MainForm: TMainForm OnExecute = actDeleteCategoryExecute OnUpdate = actDeleteCategoryUpdate end - object actNews: TAction - Category = 'Help' - Caption = 'CodeSnip News...' - Hint = - 'CodeSnip news|Display news about CodeSnip and the online databas' + - 'e' - ImageIndex = 36 - OnExecute = actNewsExecute - end object actNewDetailsTab: TAction Category = 'View' Caption = 'New Tab' @@ -843,12 +776,12 @@ inherited MainForm: TMainForm Caption = 'FAQs' Hint = 'FAQs|Display CodeSnip'#39's online Frequently Asked Questions in the' + - ' default browser' + ' default web browser' ImageIndex = 6 end object actDuplicateSnippet: TAction Category = 'Snippets' - Caption = 'Duplicate Snippet' + Caption = 'Duplicate Snippet...' Hint = 'Duplicate snippet|Duplicate the selected snippet and add it to t' + 'he user-defined database' @@ -861,8 +794,8 @@ inherited MainForm: TMainForm Category = 'File' Caption = 'Save Selection...' Hint = - 'Save current selection|Save information about the current snippe' + - 't selection to a file' + 'Save current selection|Save information about the currently sele' + + 'cted snippets to a file' ShortCut = 41043 OnExecute = actSaveSelectionExecute OnUpdate = actSaveSelectionUpdate @@ -877,20 +810,12 @@ inherited MainForm: TMainForm OnExecute = actLoadSelectionExecute OnUpdate = ActNonEmptyDBUpdate end - object actProgramUpdates: TAction - Category = 'Tools' - Caption = 'Check For Program Updates...' - Hint = - 'Check for program updates|Check online for newer versions of Cod' + - 'eSnip' - OnExecute = actProgramUpdatesExecute - end object actCloseUnselectedDetailsTabs: TAction Category = 'View' Caption = 'Close All Other Tabs' Hint = 'Close all other tabs|Close all tabs in the Details pane except f' + - 'or the current tab' + 'or the current one' ShortCut = 24691 OnExecute = actCloseUnselectedDetailsTabsExecute OnUpdate = actCloseDetailsTabsUpdate @@ -925,15 +850,43 @@ inherited MainForm: TMainForm 'Move user database|Move the user-defined snippet database to a n' + 'ew directory' OnExecute = actMoveUserDatabaseExecute + OnUpdate = ActNonEmptyUserDBUpdate end object actSWAGImport: TAction Category = 'Snippets' Caption = 'Import Snippets From SWAG...' Hint = 'Import snippets from SWAG|Import one or more snippets into the u' + - 'ser database from the online SWAG database' + 'ser database from the SWAG database' OnExecute = actSWAGImportExecute end + object actBlog: TBrowseURL + Category = 'Help' + Caption = 'CodeSnip News On DelphiDabbler Blog' + Hint = + 'Display CodeSnip news|Display the DelphiDabbler blog, containing' + + ' CodeSnip news, in the default web browser' + ImageIndex = 6 + end + object actDeleteUserDatabase: TAction + Category = 'Database' + Caption = 'Delete User Database...' + Hint = + 'Delete User Database|Deletes the user'#39's snippets database - USE ' + + 'WITH CAUTION' + OnExecute = actDeleteUserDatabaseExecute + OnUpdate = ActNonEmptyUserDBUpdate + end + object actSaveInfo: TAction + Category = 'File' + Caption = 'Save Snippet Information...' + Hint = + 'Save snippet information|Save information about the selected sni' + + 'ppet to file' + ShortCut = 24649 + OnExecute = actSaveInfoExecute + OnUpdate = actSaveInfoUpdate + end end object mnuMain: TMainMenu Images = ilMain @@ -944,6 +897,9 @@ inherited MainForm: TMainForm object miSaveSnippet: TMenuItem Action = actSaveSnippet end + object miSaveInfo: TMenuItem + Action = actSaveInfo + end object miSaveUnit: TMenuItem Action = actSaveUnit end @@ -1116,12 +1072,6 @@ inherited MainForm: TMainForm object miSpacer14: TMenuItem Caption = '-' end - object miSubmit: TMenuItem - Action = actSubmit - end - object miSpacer19: TMenuItem - Caption = '-' - end object miAddFavourite: TMenuItem Action = actAddFavourite end @@ -1167,6 +1117,12 @@ inherited MainForm: TMainForm object miMoveUserDatabase: TMenuItem Action = actMoveUserDatabase end + object miSpacer21: TMenuItem + Caption = '-' + end + object miDeleteUserDatabase: TMenuItem + Action = actDeleteUserDatabase + end end object miCompile: TMenuItem Caption = 'Compile' @@ -1189,28 +1145,12 @@ inherited MainForm: TMainForm object miCompilers: TMenuItem Action = actCompilers end - object miProxyServer: TMenuItem - Action = actProxyServer - end object miSpacer18: TMenuItem Caption = '-' end - object miCheckUpdates: TMenuItem - Action = actProgramUpdates - end - object miSpacer7: TMenuItem - Caption = '-' - end object miReportBug: TMenuItem Action = actBugReport end - object miSpacer9: TMenuItem - Caption = '-' - end - object miRegister: TMenuItem - Action = actRegister - Default = True - end end object miHelp: TMenuItem Caption = 'Help' @@ -1223,39 +1163,31 @@ inherited MainForm: TMainForm object miHelpCompChecks: TMenuItem Action = actHelpCompChecks end - object miPrivacy: TMenuItem - Action = actPrivacy - end object miLicense: TMenuItem Action = actLicense end object miSpacer3: TMenuItem Caption = '-' end - object miFAQs: TMenuItem - Action = actFAQs - end object miHomePage: TMenuItem - Action = actHomePage + Action = actGitHubHome + end + object miBlog: TMenuItem + Action = actBlog end - object miWebSite: TMenuItem - Action = actWebSite + object miFAQs: TMenuItem + Action = actFAQs end object miSpacer5: TMenuItem Caption = '-' end - object miNews: TMenuItem - Action = actNews - end - object miDonate: TMenuItem - Action = actDonate - end object miAbout: TMenuItem Action = actAbout end end end object appEvents: TApplicationEvents + OnHelp = appEventsHelp OnHint = appEventsHint Left = 291 Top = 96 diff --git a/Src/FmMain.pas b/Src/FmMain.pas index 6ddbae3a3..6fc09ef54 100644 --- a/Src/FmMain.pas +++ b/Src/FmMain.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2016, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2025, Peter Johnson (gravatar.com/delphidabbler). * * Application's main form. Handles the program's main window display and user * interaction. @@ -21,12 +18,34 @@ interface uses // Delphi - SysUtils, Menus, ExtActns, StdActns, Classes, ActnList, ImgList, Controls, - Forms, ExtCtrls, ComCtrls, ToolWin, Messages, AppEvnts, + SysUtils, + Menus, + ExtActns, + StdActns, + Classes, + ActnList, + ImgList, + Controls, + Forms, + ExtCtrls, + ComCtrls, + ToolWin, + Messages, + AppEvnts, // Project - Favourites.UManager, FmHelpAware, FrDetail, FrOverview, FrTitled, - IntfNotifier, UCompileMgr, UDialogMgr, UHistory, UMainDisplayMgr, USearch, - UStatusBarMgr, UUpdateCheckers, UWindowSettings; + Favourites.UManager, + FmHelpAware, + FrDetail, + FrOverview, + FrTitled, + IntfNotifier, + UCompileMgr, + UDialogMgr, + UHistory, + UMainDisplayMgr, + USearch, + UStatusBarMgr, + UWindowSettings; type @@ -37,6 +56,7 @@ TMainForm = class(THelpAwareForm) actAddFavourite: TAction; actAddSnippet: TAction; actBackupDatabase: TAction; + actBlog: TBrowseURL; actBugReport: TAction; actCloseAllDetailsTabs: TAction; actCloseDetailsTab: TAction; @@ -50,7 +70,7 @@ TMainForm = class(THelpAwareForm) actCopySource: TAction; actDeleteCategory: TAction; actDeleteSnippet: TAction; - actDonate: TAction; + actDeleteUserDatabase: TAction; actDuplicateSnippet: TAction; actEditSnippet: TAction; actExit: TFileExit; @@ -68,21 +88,16 @@ TMainForm = class(THelpAwareForm) actHelpCompChecks: TAction; actHelpContents: TAction; actHelpQuickStart: TAction; - actHomePage: TBrowseURL; + actGitHubHome: TBrowseURL; actImportCode: TAction; actLicense: TAction; actLoadSelection: TAction; actMoveUserDatabase: TAction; actNextTab: TAction; actNewDetailsTab: TAction; - actNews: TAction; actPreferences: TAction; actPreviousTab: TAction; actPrint: TAction; - actPrivacy: TAction; - actProgramUpdates: TAction; - actProxyServer: TAction; - actRegister: TAction; actRenameCategory: TAction; actRestoreDatabase: TAction; actSaveDatabase: TAction; @@ -91,7 +106,6 @@ TMainForm = class(THelpAwareForm) actSaveUnit: TAction; actSelectAll: TAction; actSelectSnippets: TAction; - actSubmit: TAction; actSWAGImport: TAction; actTestBug: TAction; actTestCompile: TAction; @@ -102,7 +116,6 @@ TMainForm = class(THelpAwareForm) actViewDependencies: TAction; actViewSnippetKinds: TAction; actViewTestUnit: TAction; - actWebSite: TBrowseURL; actWelcome: TAction; alMain: TActionList; appEvents: TApplicationEvents; @@ -114,8 +127,8 @@ TMainForm = class(THelpAwareForm) miAddFavourite: TMenuItem; miAddSnippet: TMenuItem; miBackupDatabase: TMenuItem; + miBlog: TMenuItem; miCategories: TMenuItem; - miCheckUpdates: TMenuItem; miCloseAllDetailsTabs: TMenuItem; miCloseDetailsTab: TMenuItem; miCollapseNode: TMenuItem; @@ -128,7 +141,7 @@ TMainForm = class(THelpAwareForm) miDatabase: TMenuItem; miDeleteCategory: TMenuItem; miDeleteSnippet: TMenuItem; - miDonate: TMenuItem; + miDeleteUserDatabase: TMenuItem; miDuplicateSnippet: TMenuItem; miEdit: TMenuItem; miEditSnippet: TMenuItem; @@ -155,12 +168,8 @@ TMainForm = class(THelpAwareForm) miLoadSelection: TMenuItem; miMoveUserDatabase: TMenuItem; miNewDetailsTab: TMenuItem; - miNews: TMenuItem; miPreferences: TMenuItem; miPrint: TMenuItem; - miPrivacy: TMenuItem; - miProxyServer: TMenuItem; - miRegister: TMenuItem; miRenameCategory: TMenuItem; miReportBug: TMenuItem; miRestoreDatabase: TMenuItem; @@ -179,9 +188,7 @@ TMainForm = class(THelpAwareForm) miSpacer4: TMenuItem; miSpacer5: TMenuItem; miSpacer6: TMenuItem; - miSpacer7: TMenuItem; miSpacer8: TMenuItem; - miSpacer9: TMenuItem; miSpacer10: TMenuItem; miSpacer11: TMenuItem; miSpacer12: TMenuItem; @@ -191,9 +198,8 @@ TMainForm = class(THelpAwareForm) miSpacer16: TMenuItem; miSpacer17: TMenuItem; miSpacer18: TMenuItem; - miSpacer19: TMenuItem; miSpacer20: TMenuItem; - miSubmit: TMenuItem; + miSpacer21: TMenuItem; miSWAGImport: TMenuItem; miTestCompile: TMenuItem; miTools: TMenuItem; @@ -205,7 +211,6 @@ TMainForm = class(THelpAwareForm) miViewSnippetKinds: TMenuItem; miViewTestUnit: TMenuItem; miViewAlphabetical: TMenuItem; - miWebSite: TMenuItem; miWelcome: TMenuItem; mnuMain: TMainMenu; pnlBody: TPanel; @@ -231,13 +236,13 @@ TMainForm = class(THelpAwareForm) tbSelectSnippets: TToolButton; tbSpacer1: TToolButton; tbSpacer3: TToolButton; - tbSpacer4: TToolButton; tbSpacer5: TToolButton; tbSpacer6: TToolButton; tbSpacer7: TToolButton; tbSpacer8: TToolButton; tbTestCompile: TToolButton; - tbUpdateDbase: TToolButton; + miSaveInfo: TMenuItem; + actSaveInfo: TAction; /// <summary>Displays About Box.</summary> procedure actAboutExecute(Sender: TObject); /// <summary>Gets a new category from user and adds to database.</summary> @@ -294,8 +299,9 @@ TMainForm = class(THelpAwareForm) /// <summary>Attempts to delete the current user defined snippet from the /// database.</summary> procedure actDeleteSnippetExecute(Sender: TObject); - /// <summary>Displays Donate dialogue box.</summary> - procedure actDonateExecute(Sender: TObject); + /// <summary>Requests permission then attempts to delete the user defined + /// snippets database.</summary> + procedure actDeleteUserDatabaseExecute(Sender: TObject); /// <summary>Displays a dialogue box that can be used to duplicate the /// selected snippet.</summary> procedure actDuplicateSnippetExecute(Sender: TObject); @@ -358,9 +364,6 @@ TMainForm = class(THelpAwareForm) procedure actMoveUserDatabaseExecute(Sender: TObject); /// <summary>Creates a new empty tab in details pane.</summary> procedure actNewDetailsTabExecute(Sender: TObject); - /// <summary>Displays a dialogue box containing latest news from CodeSnip's - /// RSS feed.</summary> - procedure actNewsExecute(Sender: TObject); /// <summary>Displays next tab in either overview or details pane depending /// which pane is active.</summary> procedure actNextTabExecute(Sender: TObject); @@ -386,21 +389,6 @@ TMainForm = class(THelpAwareForm) procedure actPrintExecute(Sender: TObject); /// <summary>Determines whether the Print action can be enabled.</summary> procedure actPrintUpdate(Sender: TObject); - /// <summary>Displays the Privacy Statement help topic.</summary> - procedure actPrivacyExecute(Sender: TObject); - /// <summary>Displays the Check For Program Updates dialogue box that - /// displays the availability of any program updates.</summary> - procedure actProgramUpdatesExecute(Sender: TObject); - /// <summary>Displays the Proxy Server Configuration dialogue box that can - /// be used to specify a proxy server to use for internet access.</summary> - procedure actProxyServerExecute(Sender: TObject); - /// <summary>Displays the Registration Wizard that can be used to register - /// CodeSnip.</summary> - procedure actRegisterExecute(Sender: TObject); - /// <summary>Determines whether the Register action is visible.</summary> - /// <remarks>The action is visible iff the program is not already - /// registered.</remarks> - procedure actRegisterUpdate(Sender: TObject); /// <summary>Displays a dialogue box that can be used to rename a user /// defined category.</summary> procedure actRenameCategoryExecute(Sender: TObject); @@ -440,9 +428,6 @@ TMainForm = class(THelpAwareForm) /// <summary>Displays the Select Snippets dialogue box where the snippets /// to be displayed can be chosen.</summary> procedure actSelectSnippetsExecute(Sender: TObject); - /// <summary>Displays the Code Submission Wizard that enables user defined - /// snippets to be submitted for inclusion in the main database.</summary> - procedure actSubmitExecute(Sender: TObject); /// <summary>Determines whether the Submit or ExportCode actions can be /// enabled.</summary> procedure ActSubmitOrExportUpdate(Sender: TObject); @@ -495,6 +480,11 @@ TMainForm = class(THelpAwareForm) procedure actViewTestUnitUpdate(Sender: TObject); /// <summary>Displays the Welcome page in the details pane.</summary> procedure actWelcomeExecute(Sender: TObject); + /// <summary>Handles events triggered when help system is invoked. Prevents + /// exception being raised when F1 key is pressed when a menu is dropped + /// down.</summary> + function appEventsHelp(Command: Word; Data: Integer; + var CallHelp: Boolean): Boolean; /// <summary>Handles events triggered when a control issues a hint. The /// hint is displayed in the form's status bar.</summary> procedure appEventsHint(Sender: TObject); @@ -512,10 +502,11 @@ TMainForm = class(THelpAwareForm) /// position is permitted and blocks the move if not.</summary> procedure splitVertCanResize(Sender: TObject; var NewSize: Integer; var Accept: Boolean); + procedure ActNonEmptyUserDBUpdate(Sender: TObject); + procedure actSaveInfoUpdate(Sender: TObject); + procedure actSaveInfoExecute(Sender: TObject); strict private var - /// <summary>Flag denoting if application is registered.</summary> - fIsAppRegistered: Boolean; /// <summary>Object that notifies user-initiated events by triggering /// actions.</summary> fNotifier: INotifier; @@ -534,9 +525,6 @@ TMainForm = class(THelpAwareForm) fCompileMgr: TMainCompileMgr; /// <summary>Object that manages favourites.</summary> fFavouritesMgr: TFavouritesManager; - /// <summary>Object that checks for program and database updates in a - /// background thread.</summary> - fUpdateChecker: TUpdateCheckerMgr; /// <summary>Displays view item given by TViewItemAction instance /// referenced by Sender and adds to history list.</summary> @@ -551,10 +539,6 @@ TMainForm = class(THelpAwareForm) /// <summary>Selects a tab in the details pane where the tab is provided by /// the TDetailTabAction instance referenced by Sender.</summary> procedure ActSelectDetailTabExecute(Sender: TObject); - /// <summary>Displays Preferences dialogue box containing the single page - /// specified by the TShowPrefsPageAction instance referenced by Sender. - /// </summary> - procedure ActShowPrefsPageExecute(Sender: TObject); /// <summary>Handles events that inform of changes to the database. The /// EvtInfo object provides information about the change.</summary> procedure DBChangeHandler(Sender: TObject; const EvtInfo: IInterface); @@ -603,14 +587,16 @@ implementation // Delphi Windows, Graphics, // Project + ClassHelpers.UControls, + ClassHelpers.UGraphics, DB.UCategory, DB.UMain, DB.USnippet, FmSplash, FmTrappedBugReportDlg, - FmWaitDlg, IntfFrameMgrs, Notifications.UDisplayMgr, UActionFactory, UAppInfo, - UClassHelpers, UCodeShareMgr, UCommandBars, UConsts, UCopyInfoMgr, + FmWaitDlg, IntfFrameMgrs, UActionFactory, UAppInfo, + UCodeShareMgr, UCommandBars, UConsts, UCopyInfoMgr, UCopySourceMgr, UDatabaseLoader, UDatabaseLoaderUI, UDetailTabAction, UEditSnippetAction, UExceptions, UHelpMgr, UHistoryMenus, UKeysHelper, - UMessageBox, UNotifier, UNulDropTarget, UPrintMgr, UQuery, USaveSnippetMgr, - USaveUnitMgr, USelectionIOMgr, UShowPrefsPageAction, UUserDBMgr, UView, - UViewItemAction, UWBExternal, Web.UInfo; + UMessageBox, UNotifier, UNulDropTarget, UPrintMgr, UQuery, USaveInfoMgr, + USaveSnippetMgr, USaveUnitMgr, USelectionIOMgr, UUrl, UUserDBMgr, UView, + UViewItemAction, UWBExternal; {$R *.dfm} @@ -621,8 +607,6 @@ implementation procedure TMainForm.actAboutExecute(Sender: TObject); begin fDialogMgr.ShowAboutDlg; - if not fIsAppRegistered then - fIsAppRegistered := TAppInfo.IsRegistered; end; procedure TMainForm.actAddCategoryExecute(Sender: TObject); @@ -649,7 +633,10 @@ procedure TMainForm.actAddSnippetExecute(Sender: TObject); procedure TMainForm.actBackupDatabaseExecute(Sender: TObject); begin + if (Database as IDatabaseEdit).Updated then + TUserDBMgr.Save(Self); TUserDBMgr.BackupDatabase(Self); + fStatusBarMgr.Update; end; procedure TMainForm.actBugReportExecute(Sender: TObject); @@ -747,9 +734,15 @@ procedure TMainForm.actDeleteSnippetExecute(Sender: TObject); // display update is handled by snippets change event handler end; -procedure TMainForm.actDonateExecute(Sender: TObject); +procedure TMainForm.actDeleteUserDatabaseExecute(Sender: TObject); begin - fDialogMgr.ShowDonateDlg + if (Database as IDatabaseEdit).Updated then + TUserDBMgr.Save(Self); + if TUserDBMgr.DeleteDatabase then + begin + ReloadDatabase; + fStatusBarMgr.Update; + end; end; procedure TMainForm.actDuplicateSnippetExecute(Sender: TObject); @@ -929,6 +922,8 @@ procedure TMainForm.actLoadSelectionExecute(Sender: TObject); procedure TMainForm.actMoveUserDatabaseExecute(Sender: TObject); begin + if (Database as IDatabaseEdit).Updated then + TUserDBMgr.Save(Self); TUserDBMgr.MoveDatabase; end; @@ -937,11 +932,6 @@ procedure TMainForm.actNewDetailsTabExecute(Sender: TObject); fMainDisplayMgr.CreateNewDetailsTab; end; -procedure TMainForm.actNewsExecute(Sender: TObject); -begin - fDialogMgr.ShowNewsDlg; -end; - procedure TMainForm.actNextTabExecute(Sender: TObject); begin fMainDisplayMgr.SelectNextActiveTab; @@ -952,6 +942,11 @@ procedure TMainForm.ActNonEmptyDBUpdate(Sender: TObject); (Sender as TAction).Enabled := not Database.Snippets.IsEmpty; end; +procedure TMainForm.ActNonEmptyUserDBUpdate(Sender: TObject); +begin + (Sender as TAction).Enabled := not Database.Snippets.IsEmpty(True); +end; + procedure TMainForm.ActOverviewTabExecute(Sender: TObject); begin // Action's Tag property specifies index of tab being selected @@ -959,13 +954,13 @@ procedure TMainForm.ActOverviewTabExecute(Sender: TObject); end; procedure TMainForm.ActOverviewTabUpdate(Sender: TObject); +var + Action: TAction; begin // Action's Tag property specifies index of tab being updated - with Sender as TAction do - begin - Checked := fMainDisplayMgr.SelectedOverviewTab = Tag; - Enabled := True; - end; + Action := (Sender as TAction); + Action.Checked := fMainDisplayMgr.SelectedOverviewTab = Tag; + Action.Enabled := True; end; procedure TMainForm.actPreferencesExecute(Sender: TObject); @@ -998,36 +993,6 @@ procedure TMainForm.actPrintUpdate(Sender: TObject); TPrintMgr.CanPrint(fMainDisplayMgr.CurrentView); end; -procedure TMainForm.actPrivacyExecute(Sender: TObject); -begin - DisplayHelp('PrivacyStatement'); -end; - -procedure TMainForm.actProgramUpdatesExecute(Sender: TObject); -begin - fDialogMgr.ShowProgramUpdatesDlg; -end; - -procedure TMainForm.actProxyServerExecute(Sender: TObject); -begin - fDialogMgr.ExecProxyServerDlg; -end; - -procedure TMainForm.actRegisterExecute(Sender: TObject); -begin - if fDialogMgr.ExecRegistrationDlg then - fIsAppRegistered := True; -end; - -procedure TMainForm.actRegisterUpdate(Sender: TObject); -begin - with Sender as TAction do - begin - Visible := not fIsAppRegistered; - Enabled := True; - end; -end; - procedure TMainForm.actRenameCategoryExecute(Sender: TObject); begin TUserDBMgr.RenameACategory; @@ -1041,7 +1006,10 @@ procedure TMainForm.actRenameCategoryUpdate(Sender: TObject); procedure TMainForm.actRestoreDatabaseExecute(Sender: TObject); begin if TUserDBMgr.RestoreDatabase(Self) then + begin ReloadDatabase; + fStatusBarMgr.Update; + end; end; procedure TMainForm.actSaveDatabaseExecute(Sender: TObject); @@ -1055,6 +1023,17 @@ procedure TMainForm.actSaveDatabaseUpdate(Sender: TObject); (Sender as TAction).Enabled := TUserDBMgr.CanSave; end; +procedure TMainForm.actSaveInfoExecute(Sender: TObject); +begin + TSaveInfoMgr.Execute(fMainDisplayMgr.CurrentView); +end; + +procedure TMainForm.actSaveInfoUpdate(Sender: TObject); +begin + (Sender as TAction).Enabled := + TSaveInfoMgr.CanHandleView(fMainDisplayMgr.CurrentView); +end; + procedure TMainForm.actSaveSelectionExecute(Sender: TObject); begin TSelectionIOMgr.SaveCurrentSelection; @@ -1107,22 +1086,6 @@ procedure TMainForm.actSelectSnippetsExecute(Sender: TObject); DoSearchFilter(Search); end; -procedure TMainForm.ActShowPrefsPageExecute(Sender: TObject); -var - UpdateUI: Boolean; // flag true if preference changes affect main window UI -begin - fDialogMgr.ExecPreferencesDlg( - (Sender as TShowPrefsPageAction).FrameClassName, UpdateUI - ); - if UpdateUI then - fMainDisplayMgr.CompleteRefresh; -end; - -procedure TMainForm.actSubmitExecute(Sender: TObject); -begin - TCodeShareMgr.Submit(fMainDisplayMgr.CurrentView); -end; - procedure TMainForm.ActSubmitOrExportUpdate(Sender: TObject); begin (Sender as TAction).Enabled := TCodeShareMgr.CanShare; @@ -1272,6 +1235,19 @@ procedure TMainForm.AfterShowForm; // initialise display fMainDisplayMgr.Initialise(fWindowSettings.OverviewTab); fMainDisplayMgr.ShowWelcomePage; + // check for registerable Delphi compiler installations + if fCompileMgr.CheckForNewCompilerInstalls then + fMainDisplayMgr.Refresh; +end; + +function TMainForm.appEventsHelp(Command: Word; Data: Integer; + var CallHelp: Boolean): Boolean; +begin + // Prevent Delphi Help system from interfering! + // This prevents exception being raised when F1 is pressed over menu items + // while still allowing our custom help manager to operate. + CallHelp := False; + Result := True; end; procedure TMainForm.appEventsHint(Sender: TObject); @@ -1340,21 +1316,15 @@ procedure TMainForm.FormCreate(Sender: TObject); end; procedure TMainForm.FormDestroy(Sender: TObject); +var + EditableDB: IDatabaseEdit; begin inherited; - // Stop update checking threads - fUpdateChecker.StopThreads; - fUpdateChecker.Free; - - // Stop notification display sub-system - TNotificationDisplayMgr.Stop; - // Save any changes to user database - with Database as IDatabaseEdit do - begin - if Updated then - Save; - end; + EditableDB := Database as IDatabaseEdit; + if EditableDB.Updated then + EditableDB.Save; + // Unhook snippets event handler Database.RemoveChangeEventHandler(DBChangeHandler); // Save window state @@ -1369,7 +1339,6 @@ procedure TMainForm.FormDestroy(Sender: TObject); // fStatusBarMgr MUST be nilled: otherwise it can be called after status bar // control has been freed and so cause AV when trying to use the control FreeAndNil(fStatusBarMgr); - end; procedure TMainForm.FormResize(Sender: TObject); @@ -1392,6 +1361,8 @@ procedure TMainForm.HandleExceptions(Sender: TObject; E: Exception); procedure TMainForm.InitForm; var WBExternal: IDispatch; // external object of browser control + ActionSetter: ISetActions; + DetailCmdBarCfg, OverviewCmdBarCfg: ICommandBarConfig; begin try inherited; @@ -1402,8 +1373,6 @@ procedure TMainForm.InitForm; // Set window caption Application.Title := TAppInfo.ProgramCaption; Caption := TAppInfo.ProgramCaption; - if TWebInfo.UsingTestServer then - Caption := Caption + ' [' + TWebInfo.TestServerHost + ']'; // Restore window settings fWindowSettings := TMainWindowSettings.CreateStandAlone(Self); // auto-freed @@ -1412,9 +1381,9 @@ procedure TMainForm.InitForm; // Initialise actions // Browse actions have to have URLs set dynamically - actHomePage.URL := TWebInfo.ProgramHomeURL; - actWebSite.URL := TWebInfo.DelphiDabblerHomeURL; - actFAQs.URL := TWebInfo.FAQsURL; + actGitHubHome.URL := TURL.CodeSnipRepo; + actFAQs.URL := TURL.CodeSnipFAQReadMe; + actBlog.URL := TURL.CodeSnipBlog; // Tree control actions need shortcuts adding dynamically, and state stored // in Tag property actExpandNode.ShortCut := ShortCut(VK_ADD, [ssCtrl]); @@ -1437,43 +1406,37 @@ procedure TMainForm.InitForm; // Create notifier object and assign actions triggered by its methods // note that actions created on fly are automatically freed fNotifier := TNotifier.Create; - with fNotifier as ISetActions do - begin - SetUpdateDbaseAction(actUpdateDbase); - SetDisplaySnippetAction(TActionFactory.CreateSnippetAction(Self)); - SetDisplayCategoryAction(TActionFactory.CreateCategoryAction(Self)); - SetConfigCompilersAction(actCompilers); - SetShowViewItemAction( - TActionFactory.CreateViewItemAction(Self, ActViewItemExecute) - ); - SetOverviewStyleChangeActions( - [actViewCategorised, actViewAlphabetical, actViewSnippetKinds] - ); - SetDetailPaneChangeAction( - TActionFactory.CreateDetailTabAction(Self, ActSelectDetailTabExecute) - ); - SetEditSnippetAction( - TActionFactory.CreateEditSnippetAction( - Self, ActEditSnippetByNameExecute - ) - ); - SetDonateAction(actDonate); - SetNewSnippetAction(actAddSnippet); - SetNewsAction(actNews); - SetCheckForUpdatesAction(actProgramUpdates); - SetAboutBoxAction(actAbout); - SetShowPrefsPageAction( - TActionFactory.CreateShowPrefsPageAction(Self, ActShowPrefsPageExecute) - ); - end; + ActionSetter := fNotifier as ISetActions; + ActionSetter.SetUpdateDbaseAction(actUpdateDbase); + ActionSetter.SetDisplaySnippetAction( + TActionFactory.CreateSnippetAction(Self) + ); + ActionSetter.SetDisplayCategoryAction( + TActionFactory.CreateCategoryAction(Self) + ); + ActionSetter.SetConfigCompilersAction(actCompilers); + ActionSetter.SetShowViewItemAction( + TActionFactory.CreateViewItemAction(Self, ActViewItemExecute) + ); + ActionSetter.SetOverviewStyleChangeActions( + [actViewCategorised, actViewAlphabetical, actViewSnippetKinds] + ); + ActionSetter.SetDetailPaneChangeAction( + TActionFactory.CreateDetailTabAction(Self, ActSelectDetailTabExecute) + ); + ActionSetter.SetEditSnippetAction( + TActionFactory.CreateEditSnippetAction( + Self, ActEditSnippetByNameExecute + ) + ); + ActionSetter.SetNewSnippetAction(actAddSnippet); + ActionSetter.SetNewsAction(actBlog); + ActionSetter.SetAboutBoxAction(actAbout); // Customise web browser controls in Details pane WBExternal := TWBExternal.Create; - with frmDetail as IWBCustomiser do - begin - SetExternalObj(WBExternal); - SetDragDropHandler(TNulDropTarget.Create); - end; + (frmDetail as IWBCustomiser).SetExternalObj(WBExternal); + (frmDetail as IWBCustomiser).SetDragDropHandler(TNulDropTarget.Create); // Set notifier for objects that trigger notifications (WBExternal as ISetNotifier).SetNotifier(fNotifier); @@ -1511,65 +1474,60 @@ procedure TMainForm.InitForm; ); // Set up detail pane's popup menus - with frmDetail as ICommandBarConfig do - begin + DetailCmdBarCfg := frmDetail as ICommandBarConfig; // set images to use - SetImages(ilMain); + DetailCmdBarCfg.SetImages(ilMain); // detail view menus - AddAction( - TActionFactory.CreateLinkAction(Self), - [cDetailPopupMenuAnchor, cDetailPopupMenuImage] - ); - AddSpacer([cDetailPopupMenuAnchor, cDetailPopupMenuImage]); - AddAction(actViewDependencies, cDetailPopupMenuIDs); - AddSpacer(cDetailPopupMenuIDs); - AddAction(actCopyInfo, cDetailPopupMenuIDs); - AddAction(actCopySnippet, cDetailPopupMenuIDs); - AddAction(actCopySource, cDetailPopupMenuIDs); - AddSpacer(cDetailPopupMenuIDs); - AddAction(actTestCompile, cDetailPopupMenuIDs); - AddSpacer(cDetailPopupMenuIDs); - AddAction(actSaveSnippet, cDetailPopupMenuIDs); - AddAction(actPrint, cDetailPopupMenuIDs); - AddSpacer(cDetailPopupMenuIDs); - AddAction(actCopy, cDetailPopupMenuTextSelect); - AddAction(actSelectAll, cDetailPopupMenuIDs); - AddSpacer(cDetailPopupMenuIDs); - AddAction(actCloseDetailsTab, cDetailPopupMenuIDs); - // tab set menu - AddAction(actCloseDetailsTab, cDetailTabSetPopupMenu); - AddAction(actCloseUnselectedDetailsTabs, cDetailTabSetPopupMenu); - end; + DetailCmdBarCfg.AddAction( + TActionFactory.CreateLinkAction(Self), + [cDetailPopupMenuAnchor, cDetailPopupMenuImage] + ); + DetailCmdBarCfg.AddSpacer([cDetailPopupMenuAnchor, cDetailPopupMenuImage]); + DetailCmdBarCfg.AddAction(actViewDependencies, cDetailPopupMenuIDs); + DetailCmdBarCfg.AddSpacer(cDetailPopupMenuIDs); + DetailCmdBarCfg.AddAction(actCopyInfo, cDetailPopupMenuIDs); + DetailCmdBarCfg.AddAction(actCopySnippet, cDetailPopupMenuIDs); + DetailCmdBarCfg.AddAction(actCopySource, cDetailPopupMenuIDs); + DetailCmdBarCfg.AddSpacer(cDetailPopupMenuIDs); + DetailCmdBarCfg.AddAction(actTestCompile, cDetailPopupMenuIDs); + DetailCmdBarCfg.AddSpacer(cDetailPopupMenuIDs); + DetailCmdBarCfg.AddAction(actSaveSnippet, cDetailPopupMenuIDs); + DetailCmdBarCfg.AddAction(actPrint, cDetailPopupMenuIDs); + DetailCmdBarCfg.AddSpacer(cDetailPopupMenuIDs); + DetailCmdBarCfg.AddAction(actCopy, cDetailPopupMenuTextSelect); + DetailCmdBarCfg.AddAction(actSelectAll, cDetailPopupMenuIDs); + DetailCmdBarCfg.AddSpacer(cDetailPopupMenuIDs); + DetailCmdBarCfg.AddAction(actCloseDetailsTab, cDetailPopupMenuIDs); + // tab set menu + DetailCmdBarCfg.AddAction(actCloseDetailsTab, cDetailTabSetPopupMenu); + DetailCmdBarCfg.AddAction( + actCloseUnselectedDetailsTabs, cDetailTabSetPopupMenu + ); // Set up overview pane's toolbar and popup menu - with frmOverview as ICommandBarConfig do - begin - SetImages(ilMain); - // add toolbar actions (in reverse order we want them!) - AddAction(actCollapseTree, cOverviewToolBar); - AddAction(actExpandTree, cOverviewToolBar); - // add popup menu actions - AddAction(actViewDependencies, cOverviewPopupMenu); - AddSpacer(cOverviewPopupMenu); - AddAction(actCopyInfo, cOverviewPopupMenu); - AddAction(actCopySnippet, cOverviewPopupMenu); - AddAction(actCopySource, cOverviewPopupMenu); - AddSpacer(cOverviewPopupMenu); - AddAction(actSaveSnippet, cOverviewPopupMenu); - AddAction(actPrint, cOverviewPopupMenu); - AddSpacer(cOverviewPopupMenu); - AddAction(actEditSnippet, cOverviewPopupMenu); - AddSpacer(cOverviewPopupMenu); - AddAction(actCollapseNode, cOverviewPopupMenu); - AddAction(actExpandNode, cOverviewPopupMenu); - end; + OverviewCmdBarCfg := frmOverview as ICommandBarConfig; + OverviewCmdBarCfg.SetImages(ilMain); + // add toolbar actions (in reverse order we want them!) + OverviewCmdBarCfg.AddAction(actCollapseTree, cOverviewToolBar); + OverviewCmdBarCfg.AddAction(actExpandTree, cOverviewToolBar); + // add popup menu actions + OverviewCmdBarCfg.AddAction(actViewDependencies, cOverviewPopupMenu); + OverviewCmdBarCfg.AddSpacer(cOverviewPopupMenu); + OverviewCmdBarCfg.AddAction(actCopyInfo, cOverviewPopupMenu); + OverviewCmdBarCfg.AddAction(actCopySnippet, cOverviewPopupMenu); + OverviewCmdBarCfg.AddAction(actCopySource, cOverviewPopupMenu); + OverviewCmdBarCfg.AddSpacer(cOverviewPopupMenu); + OverviewCmdBarCfg.AddAction(actSaveSnippet, cOverviewPopupMenu); + OverviewCmdBarCfg.AddAction(actPrint, cOverviewPopupMenu); + OverviewCmdBarCfg.AddSpacer(cOverviewPopupMenu); + OverviewCmdBarCfg.AddAction(actEditSnippet, cOverviewPopupMenu); + OverviewCmdBarCfg.AddSpacer(cOverviewPopupMenu); + OverviewCmdBarCfg.AddAction(actCollapseNode, cOverviewPopupMenu); + OverviewCmdBarCfg.AddAction(actExpandNode, cOverviewPopupMenu); // Create object to handle compilation and assoicated UI and dialogues fCompileMgr := TMainCompileMgr.Create(Self); // auto-freed - // Record if app is registered - fIsAppRegistered := TAppInfo.IsRegistered; - // Set event handler for snippets database Database.AddChangeEventHandler(DBChangeHandler); @@ -1590,13 +1548,6 @@ procedure TMainForm.InitForm; // *** Must be done AFTER database has loaded *** fFavouritesMgr := TFavouritesManager.Create(fNotifier); - // Start notification display sub-system - TNotificationDisplayMgr.Start(Self); - - // Start update checking manager - // *** Should be done after notification window listener starts - fUpdateChecker := TUpdateCheckerMgr.Create; - fUpdateChecker.StartThreads; finally // Ready to start using app: request splash form closes and enable form SplashForm.RequestClose; diff --git a/Src/FmNewHiliterNameDlg.pas b/Src/FmNewHiliterNameDlg.pas index 792517ff5..a7fcca8e5 100644 --- a/Src/FmNewHiliterNameDlg.pas +++ b/Src/FmNewHiliterNameDlg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2013-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a dialogue box that enables the user to enter a syntax highlighter * name. @@ -125,17 +122,19 @@ procedure TNewHiliterNameDlg.cbNamesChange(Sender: TObject); class function TNewHiliterNameDlg.Execute(Owner: TComponent; const Names: array of string; out NewName: string): Boolean; +var + Dlg: TNewHiliterNameDlg; begin - with InternalCreate(Owner) do - try - fNames := TIStringList.Create(Names); - fNames.CaseSensitive := False; - Result := ShowModal = mrOK; - if Result then - NewName := fNewName; - finally - Free; - end; + Dlg := InternalCreate(Owner); + try + Dlg.fNames := TIStringList.Create(Names); + Dlg.fNames.CaseSensitive := False; + Result := Dlg.ShowModal = mrOK; + if Result then + NewName := Dlg.fNewName; + finally + Dlg.Free; + end; end; procedure TNewHiliterNameDlg.InitForm; diff --git a/Src/FmNewsDlg.dfm b/Src/FmNewsDlg.dfm deleted file mode 100644 index ee0f377de..000000000 --- a/Src/FmNewsDlg.dfm +++ /dev/null @@ -1,117 +0,0 @@ -inherited NewsDlg: TNewsDlg - Left = 1267 - Top = 793 - Caption = 'CodeSnip News' - ClientHeight = 387 - Position = poDesigned - ExplicitWidth = 474 - ExplicitHeight = 415 - PixelsPerInch = 96 - TextHeight = 13 - inherited pnlBody: TPanel - Width = 441 - Height = 353 - ExplicitWidth = 441 - ExplicitHeight = 353 - object pnlTop: TPanel - Left = 0 - Top = 0 - Width = 441 - Height = 41 - Align = alTop - BevelOuter = bvNone - TabOrder = 0 - DesignSize = ( - 441 - 41) - object lblDays: TLabel - Left = 0 - Top = 0 - Width = 34 - Height = 13 - Anchors = [akLeft] - Caption = 'lblDays' - end - object btnConfig: TButton - Left = 366 - Top = 10 - Width = 75 - Height = 25 - Action = actConfig - Anchors = [akRight] - ParentShowHint = False - ShowHint = True - TabOrder = 0 - end - end - inline frmHTML: TRSSNewsFrame - Left = 0 - Top = 41 - Width = 441 - Height = 312 - Align = alClient - TabOrder = 1 - TabStop = True - ExplicitTop = 41 - ExplicitWidth = 441 - ExplicitHeight = 312 - inherited pnlBrowser: TPanel - Width = 441 - Height = 312 - ExplicitWidth = 441 - ExplicitHeight = 312 - inherited wbBrowser: TWebBrowser - Width = 439 - Height = 310 - ExplicitLeft = 0 - ExplicitTop = 0 - ExplicitWidth = 439 - ExplicitHeight = 310 - ControlData = { - 4C0000005F2D00000A2000000000000000000000000000000000000000000000 - 000000004C000000000000000000000001000000E0D057007335CF11AE690800 - 2B2E126208000000000000004C0000000114020000000000C000000000000046 - 8000000000000000000000000000000000000000000000000000000000000000 - 00000000000000000100000000000000000000000000000000000000} - end - end - end - end - inherited btnHelp: TButton - TabOrder = 3 - end - inherited btnClose: TButton - TabOrder = 2 - end - object btnRSSFeed: TBitBtn - Left = 8 - Top = 304 - Width = 89 - Height = 25 - Action = actRSSFeed - Caption = '&RSS Feed' - DoubleBuffered = True - ParentDoubleBuffered = False - ParentShowHint = False - ShowHint = True - TabOrder = 1 - end - object ilActions: TImageList - Left = 224 - Top = 192 - end - object alMain: TActionList - Images = ilActions - Left = 168 - Top = 192 - object actRSSFeed: TAction - Caption = '&RSS Feed' - ImageIndex = 45 - OnExecute = actRSSFeedExecute - end - object actConfig: TAction - Caption = '&Change...' - OnExecute = actConfigExecute - end - end -end diff --git a/Src/FmNewsDlg.pas b/Src/FmNewsDlg.pas deleted file mode 100644 index 378f2fb0e..000000000 --- a/Src/FmNewsDlg.pas +++ /dev/null @@ -1,348 +0,0 @@ -{ - * This Source Code Form is subject to the terms of the Mozilla Public License, - * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ - * - * Copyright (C) 2010-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ - * - * Implements a dialogue box that displays news items from CodeSnip's RSS news - * feed. -} - - -unit FmNewsDlg; - - -interface - - -uses - // Delphi - Buttons, StdCtrls, Forms, Controls, ExtCtrls, Classes, - // Project - FmGenericViewDlg, FrBrowserBase, FrRSSNews, UBaseObjects, UExceptions, URSS20, - UXMLDocumentEx, ActnList, ImgList; - - -type - - { - TNewsDlg: - Dialog box that displays news items from CodeSnip's RSS news feed. - } - TNewsDlg = class(TGenericViewDlg, INoPublicConstruct) - actConfig: TAction; - actRSSFeed: TAction; - alMain: TActionList; - btnConfig: TButton; - btnRSSFeed: TBitBtn; - frmHTML: TRSSNewsFrame; - ilActions: TImageList; - lblDays: TLabel; - pnlTop: TPanel; - /// <summary>Configure action handler. Displays news prefs page of - /// Preferences dialog box to enable number of days to display in news - procedure actConfigExecute(Sender: TObject); - /// <summary>Action handler that displays RSS feed in default web browser. - /// </summary> - procedure actRSSFeedExecute(Sender: TObject); - /// <summary>Form creation handler. Loads image list from resources. - /// </summary> - procedure FormCreate(Sender: TObject); - strict private - /// <summary>Loads news from RSS feed, converts to HTML and displays in - /// browser control.</summary> - procedure LoadNews; - /// <summary>Displays brief message Msg as an HTML paragraph in browser - /// control.</summary> - procedure DisplayMessage(const Msg: string); - /// <summary>Displays a message in browser that informs that content is - /// loading.</summary> - procedure DisplayLoadingMsg; - /// <summary>Renders RSS news feed in HTML and displays in browser control. - /// </summary> - procedure DisplayNews(const RSS: TRSS20); - /// <summary>Displays a message in browser control informing that there are - /// no news items in RSS feed.</summary> - procedure DisplayNoNewsMsg; - /// <summary>Renders HTML describing exception E and displays it in browser - /// control.</summary> - procedure DisplayErrorMsg(E: ECodeSnip); - /// <summary>Gets and returns interface to XML document containing details - /// of RSS news feed from web.</summary> - function GetRSSDocument: IXMLDocumentEx; - /// <summary>Gets maximum number of days of news to be displayed.</summary> - function GetMaxNewsAge: Integer; - /// <summary>Updates label with current maximum news age.</summary> - procedure UpdateNewsAgeLbl; - strict protected - /// <summary>Arranges controls on form.</summary> - /// <remarks>Called from ancestor class.</remarks> - procedure ArrangeForm; override; - /// <summary>Initialises HTML frame.</summary> - /// <remarks>Called from ancestor class.</remarks> - procedure ConfigForm; override; - /// <summary>Initialises form's controls.</summary> - /// <remarks>Called from ancestor class.</remarks> - procedure InitForm; override; - /// <summary>Loads news from RSS feed after form is displayed.</summary> - /// <remarks>Called from ancestor class.</remarks> - procedure AfterShowForm; override; - public - /// <summary>Displays dialog box.</summary> - /// <param name="AOwner">TComponent [in] Control that owns dialog box. - /// </param> - class procedure Execute(AOwner: TComponent); - end; - - -implementation - - -uses - // Delphi - SysUtils, ExtActns, Windows, Graphics, - // Project - FmPreferencesDlg, FrNewsPrefs, UClassHelpers, UCtrlArranger, UHTMLUtils, - UIStringList, UPreferences, UStrUtils, Web.UInfo, Web.UXMLRequestor; - -{$R *.dfm} - - -{ TNewsDlg } - -procedure TNewsDlg.actConfigExecute(Sender: TObject); -var - CurrentNewsAge: Integer; -begin - CurrentNewsAge := GetMaxNewsAge; - if TPreferencesDlg.Execute(Self, [TNewsPrefsFrame]) then - begin - if CurrentNewsAge <> GetMaxNewsAge then - LoadNews; - end; -end; - -procedure TNewsDlg.actRSSFeedExecute(Sender: TObject); -var - BrowseAction: TBrowseURL; // action that displays RSS feed URL in browser -begin - BrowseAction := TBrowseURL.Create(nil); - try - BrowseAction.URL := TWebInfo.NewsFeedURL(GetMaxNewsAge); - BrowseAction.Execute; - finally - BrowseAction.Free; - end; -end; - -procedure TNewsDlg.AfterShowForm; -begin - LoadNews; -end; - -procedure TNewsDlg.ArrangeForm; -begin - inherited; - TCtrlArranger.AlignVCentres(2, [lblDays, btnConfig]); - lblDays.Left := 0; - pnlTop.ClientHeight := TCtrlArranger.TotalControlHeight(pnlTop) + 8; - btnRSSFeed.Top := btnClose.Top; -end; - -procedure TNewsDlg.ConfigForm; -begin - inherited; - frmHTML.Initialise; -end; - -procedure TNewsDlg.DisplayErrorMsg(E: ECodeSnip); -resourcestring - sErrorHeading = 'Error loading news:'; // fixed heading text -var - ErrHeadingAttrs: IHTMLAttributes; // HTML attributes of heading - ErrMessageAttrs: IHTMLAttributes; // HTML attributes of error message -begin - ErrHeadingAttrs := THTMLAttributes.Create('class', 'error-heading'); - ErrMessageAttrs := THTMLAttributes.Create('class', 'error-message'); - frmHTML.DisplayContent( - THTML.CompoundTag('p', ErrHeadingAttrs, THTML.Entities(sErrorHeading)) + - THTML.CompoundTag('p', ErrMessageAttrs, THTML.Entities(E.Message)) - ); -end; - -procedure TNewsDlg.DisplayLoadingMsg; -resourcestring - sLoadingMsg = 'Loading...'; // message text -begin - DisplayMessage(sLoadingMsg); -end; - -procedure TNewsDlg.DisplayMessage(const Msg: string); -var - HTMLAttrs: IHTMLAttributes; // HTML attributes -begin - HTMLAttrs := THTMLAttributes.Create('class', 'message'); - frmHTML.DisplayContent( - THTML.CompoundTag('p', HTMLAttrs, THTML.Entities(Msg)) - ); -end; - -procedure TNewsDlg.DisplayNews(const RSS: TRSS20); - - /// Renders the given RSS news item title as HTML. Rendered as a link if item - /// specifies a URL. - function TitleHTML(const Item: TRSS20Item): string; - resourcestring - sNoTitle = 'Untitled'; // text used when no title - var - TitleHTML: string; // title text - URL: string; // item's URL used for link - begin - TitleHTML := THTML.Entities(StrTrim(Item.Title)); - if TitleHTML = '' then - TitleHTML := THTML.Entities(sNoTitle); - URL := StrTrim(Item.Link); - if URL = '' then - Result := TitleHTML - else - Result := THTML.CompoundTag( - 'a', - THTMLAttributes.Create([ - THTMLAttribute.Create('href', URL), - THTMLAttribute.Create('class', 'external-link') - ]), - TitleHTML - ); - Result := THTML.CompoundTag('strong', Result); - end; - - /// Renders given RSS new item's description as HTML. - function DescriptionHTML(const Item: TRSS20Item): string; - resourcestring - sNoDescription = 'No description.'; // text used when no description - var - Description: string; // description text - begin - Description := StrTrim(Item.Description); - if Description = '' then - Description := sNoDescription; - Result := THTML.Entities(Description); - end; - -var - SB: TStringBuilder; // object used to construct HTML - Item: TRSS20Item; // references each RSS item in feed -begin - SB := TStringBuilder.Create; - try - SB.AppendLine(THTML.OpeningTag('dl')); - for Item in RSS do - begin - SB.AppendLine(THTML.OpeningTag('dt')); - SB.AppendLine(THTML.CompoundTag('div', TitleHTML(Item))); - if Item.PubDateAsText <> '' then - SB.AppendLine( - THTML.CompoundTag('div', THTML.Entities(DateTimeToStr(Item.PubDate))) - ); - SB.AppendLine(THTML.ClosingTag('dt')); - SB.AppendLine(THTML.CompoundTag('dd', DescriptionHTML(Item))); - end; - SB.AppendLine(THTML.ClosingTag('dl')); - frmHTML.DisplayContent(SB.ToString); - finally - SB.Free; - end; -end; - -procedure TNewsDlg.DisplayNoNewsMsg; -resourcestring - sNoNews = 'There are no news items to display.'; // message text -begin - DisplayMessage(sNoNews); -end; - -class procedure TNewsDlg.Execute(AOwner: TComponent); -begin - with InternalCreate(AOwner) do - try - ShowModal; - finally - Free; - end; -end; - -procedure TNewsDlg.FormCreate(Sender: TObject); -begin - inherited; - ilActions.LoadFromResource(RT_RCDATA, 'ACTIONIMAGES', 16, clFuchsia); - RefreshActions; // ensure control glyphs are updated with loaded images -end; - -function TNewsDlg.GetMaxNewsAge: Integer; -begin - Result := Preferences.NewsAge; -end; - -function TNewsDlg.GetRSSDocument: IXMLDocumentEx; -var - Requestor: TXMLRequestor; // object that makes XML request -begin - Requestor := TXMLRequestor.Create; - try - Result := Requestor.GetDocument(TWebInfo.NewsFeedURL(GetMaxNewsAge)); - finally - Requestor.Free; - end; -end; - -procedure TNewsDlg.InitForm; -begin - inherited; - lblDays.Caption := ''; -end; - -procedure TNewsDlg.LoadNews; -var - RSSFeed: TRSS20; // object used to interpret RSS feed XML -begin - Screen.Cursor := crHourGlass; - try - DisplayLoadingMsg; - try - RSSFeed := TRSS20.Create; - try - RSSFeed.Load(GetRSSDocument); - if RSSFeed.Count > 0 then - DisplayNews(RSSFeed) - else - DisplayNoNewsMsg; - UpdateNewsAgeLbl; - finally - RSSFeed.Free; - end; - except - on E: ECodeSnip do - begin - lblDays.Caption := ''; - DisplayErrorMsg(E); - end; - end; - finally - Screen.Cursor := crDefault; - end; -end; - -procedure TNewsDlg.UpdateNewsAgeLbl; -resourcestring - // message displayed in label at - sNewsDays = 'CodeSnip news from the last %d days.'; -begin - lblDays.Caption := Format(sNewsDays, [GetMaxNewsAge]); -end; - -end. - diff --git a/Src/FmPreferencesDlg.dfm b/Src/FmPreferencesDlg.dfm index cf38aa3cf..d39f3b146 100644 --- a/Src/FmPreferencesDlg.dfm +++ b/Src/FmPreferencesDlg.dfm @@ -3,27 +3,36 @@ inherited PreferencesDlg: TPreferencesDlg Top = 138 Caption = 'Preferences' ClientHeight = 421 - ClientWidth = 462 - ExplicitWidth = 468 - ExplicitHeight = 447 + ClientWidth = 722 + ExplicitWidth = 728 + ExplicitHeight = 450 PixelsPerInch = 96 TextHeight = 13 inherited pnlBody: TPanel - Width = 446 - Height = 377 - ExplicitWidth = 446 - ExplicitHeight = 377 + Width = 609 + Height = 353 + ExplicitWidth = 609 + ExplicitHeight = 353 object pcMain: TPageControl - Left = 0 + Left = 163 Top = 0 Width = 446 - Height = 377 - Align = alClient + Height = 353 + Align = alRight MultiLine = True + TabOrder = 1 + ExplicitHeight = 329 + end + object lbPages: TListBox + Left = 0 + Top = 0 + Width = 153 + Height = 353 + Align = alLeft + ItemHeight = 13 TabOrder = 0 - OnChange = pcMainChange - OnChanging = pcMainChanging - OnMouseDown = pcMainMouseDown + OnClick = lbPagesClick + ExplicitHeight = 329 end end inherited btnOK: TButton diff --git a/Src/FmPreferencesDlg.pas b/Src/FmPreferencesDlg.pas index cdc2635f2..cceb5b6ce 100644 --- a/Src/FmPreferencesDlg.pas +++ b/Src/FmPreferencesDlg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2006-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2006-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a dialogue box that is used to set user preferences. } @@ -30,31 +27,19 @@ interface /// Dialog box that sets user preferences. /// </summary> /// <remarks> - /// This dialog box displays tabs for preferences frames registered with the + /// This dialog box displays pages for preferences frames registered with the /// dialog box. /// </remarks> TPreferencesDlg = class(TGenericOKDlg, INoPublicConstruct) pcMain: TPageControl; + lbPages: TListBox; /// <summary>OK button click event handler. Writes preference data to /// persistent storage.</summary> procedure btnOKClick(Sender: TObject); - /// <param>Called when current tab sheet has changed. Gets newly selected - /// page to re-initialise its controls from local preferences.</param> - /// <remarks>This enables any pages that depend on preferences that may - /// have been changed in other pages to update appropriately.</remarks> - procedure pcMainChange(Sender: TObject); - /// <summary>Called just before active tab sheet is changed. Causes page - /// about to be deselected to update local preferences with any changes. - /// </summary> - /// <remarks>We do this in case another page needs to update due to changes - /// made on current page.</remarks> - procedure pcMainChanging(Sender: TObject; var AllowChange: Boolean); - /// <summary>Handles event triggered when user clicks on one of page - /// control tabs. Ensures page control has focus.</summary> - /// <remarks>Without this fix, page control does not always get focus when - /// a tab is clicked.</remarks> - procedure pcMainMouseDown(Sender: TObject; Button: TMouseButton; - Shift: TShiftState; X, Y: Integer); + /// <summary>Handles event triggered when list box is clicked or changed + /// via keyboard.</summary> + procedure lbPagesClick(Sender: TObject); + procedure FormDestroy(Sender: TObject); strict private class var /// <summary>List of registered page frames</summary> @@ -65,6 +50,10 @@ TPreferencesDlg = class(TGenericOKDlg, INoPublicConstruct) /// <summary>Records if main UI needs to be updated to reflect changed /// preferences.</summary> fUpdateUI: Boolean; + /// <summary>Records flags to be passed to frames.</summary> + fFrameFlags: UInt64; + /// <summary>Records index of currently select tab/list item.</summary> + fCurrentPageIdx: Integer; /// <summary>Creates the required frames and displays each in a tab sheet /// within the page control.</summary> /// <param name="FrameClasses">array of TPrefsFrameClass [in] Class @@ -86,6 +75,13 @@ TPreferencesDlg = class(TGenericOKDlg, INoPublicConstruct) /// <summary>Gets reference to preferences frame on currently selected tab. /// </summary> function GetSelectedPage: TPrefsBaseFrame; + /// <summary>Selects given tab.</summary> + /// <remarks>Stores state of tab being closed and restores state of tab + /// being opened.</remarks> + procedure SelectTab(TS: TTabSheet); + /// <summary>Returns index of tab selected when dialogue box was last + /// closed or -1 if no tab recorded or tab doesn't exist.</summary> + function GetLastTabIdx: Integer; strict protected /// <summary>Gets the help A-link keyword to be used when help button /// clicked.</summary> @@ -114,7 +110,8 @@ TPreferencesDlg = class(TGenericOKDlg, INoPublicConstruct) /// <returns>Boolean. True if user clicks OK to accept changes or False if /// user cancels and no changes made.</returns> class function Execute(AOwner: TComponent; - const Pages: array of TPrefsFrameClass; out UpdateUI: Boolean): Boolean; + const Pages: array of TPrefsFrameClass; out UpdateUI: Boolean; + const Flags: UInt64 = 0): Boolean; overload; /// <summary>Displays dialog with pages for each specified preferences /// frame.</summary> @@ -125,7 +122,8 @@ TPreferencesDlg = class(TGenericOKDlg, INoPublicConstruct) /// <returns>Boolean. True if user clicks OK to accept changes or False if /// user cancels and no changes made.</returns> class function Execute(AOwner: TComponent; - const Pages: array of TPrefsFrameClass): Boolean; overload; + const Pages: array of TPrefsFrameClass; const Flags: UInt64 = 0): + Boolean; overload; /// <summary>Displays preferences dialog with all registered preference /// frames.</summary> /// <param name="AOwner">TComponent [in] Component that owns dialog. @@ -134,8 +132,8 @@ TPreferencesDlg = class(TGenericOKDlg, INoPublicConstruct) /// be updated as a result of preference changes.</param> /// <returns>Boolean. True if user clicks OK to accept changes or False if /// user cancels and no changes made.</returns> - class function Execute(AOwner: TComponent; out UpdateUI: Boolean): Boolean; - overload; + class function Execute(AOwner: TComponent; out UpdateUI: Boolean; + const Flags: UInt64 = 0): Boolean; overload; /// <summary>Displays dialogue with showing a single frame, specified by /// its class name.</summary> /// <param name="AOwner">TComponent [in] Component that owns dialog. @@ -147,7 +145,7 @@ TPreferencesDlg = class(TGenericOKDlg, INoPublicConstruct) /// <returns>Boolean. True if user clicks OK to accept changes or False if /// user cancels and no changes made.</returns> class function Execute(AOwner: TComponent; const PageClsName: string; - out UpdateUI: Boolean): Boolean; overload; + out UpdateUI: Boolean; const Flags: UInt64 = 0): Boolean; overload; /// <summary>Registers given preferences frame class for inclusion in the /// preferences dialog box.</summary> /// <remarks>Registered frames are created when the dialog box is displayed @@ -163,16 +161,17 @@ implementation Design notes ------------ - This dialog box is a multi-page preferences dialog that provides access to - each page via a tab. The dialog box does not provide an implementation of each - page of the dialog. This representation must be provided by a frame descended - from TPrefsBaseFrame. Such frames must: - (a) register themselves with the dialog box by passing their class to the + This dialogue box is a multi-page preferences dialog that provides access to + each page via a list of page names. The dialogue box does not provide an + implementation of each page. This representation must be provided by a frame + descended from TPrefsBaseFrame. Such frames must: + (a) register themselves with the dialogue box by passing their class to the TPreferencesDlg.RegisterPage class method. (b) implement all the abstract methods of TPrefsBaseFrame. - The dialog box will create registered frames when needed and host them within - a tab sheet in the main page control. + The dialogue box will create registered frames when needed and host them + within tab sheet in the main page control. It will also add the name of the + frame to a list control that is used to select the required "page". There is no need to modify this unit when a new frame is to be addded to it. } @@ -180,7 +179,8 @@ implementation uses // Project - IntfCommon; + IntfCommon, + UStrUtils; {$R *.dfm} @@ -189,7 +189,7 @@ implementation procedure TPreferencesDlg.ArrangeForm; var - Idx: Integer; // loops through all displayed tab sheets + Idx: Integer; // loops through all displayed page Frame: TPrefsBaseFrame; // references each preference frame TabSheet: TTabSheet; // references each tab sheet begin @@ -244,6 +244,10 @@ procedure TPreferencesDlg.CreatePages( Frame.Top := 4; // set tab sheet caption to frame's display name TS.Caption := Frame.DisplayName; + TS.TabVisible := False; + + // Create list box item for page + lbPages.Items.AddObject(Frame.DisplayName, TS); end; end; @@ -259,37 +263,42 @@ function TPreferencesDlg.CustomHelpKeyword: string; end; class function TPreferencesDlg.Execute(AOwner: TComponent; - const Pages: array of TPrefsFrameClass; out UpdateUI: Boolean): Boolean; + const Pages: array of TPrefsFrameClass; out UpdateUI: Boolean; + const Flags: UInt64): Boolean; +var + Dlg: TPreferencesDlg; begin - with InternalCreate(AOwner) do - try - CreatePages(Pages); - Result := ShowModal = mrOK; - if Result then - UpdateUI := fUpdateUI - else - UpdateUI := False; - finally - Free; - end; + Dlg := InternalCreate(AOwner); + try + Dlg.fFrameFlags := Flags; + Dlg.CreatePages(Pages); + Result := Dlg.ShowModal = mrOK; + if Result then + UpdateUI := Dlg.fUpdateUI + else + UpdateUI := False; + finally + Dlg.Free; + end; end; class function TPreferencesDlg.Execute(AOwner: TComponent; - out UpdateUI: Boolean): Boolean; + out UpdateUI: Boolean; const Flags: UInt64): Boolean; begin - Result := Execute(AOwner, fPages.ToArray, UpdateUI); + Result := Execute(AOwner, fPages.ToArray, UpdateUI, Flags); end; class function TPreferencesDlg.Execute(AOwner: TComponent; - const Pages: array of TPrefsFrameClass): Boolean; + const Pages: array of TPrefsFrameClass; const Flags: UInt64): Boolean; var Dummy: Boolean; // unused UpdateUI parameters begin - Result := Execute(AOwner, Pages, Dummy); + Result := Execute(AOwner, Pages, Dummy, Flags); end; class function TPreferencesDlg.Execute(AOwner: TComponent; - const PageClsName: string; out UpdateUI: Boolean): Boolean; + const PageClsName: string; out UpdateUI: Boolean; const Flags: UInt64): + Boolean; var FrameClass: TPrefsFrameClass; begin @@ -298,6 +307,28 @@ class function TPreferencesDlg.Execute(AOwner: TComponent; Result := Execute(AOwner, [FrameClass], UpdateUI); end; +procedure TPreferencesDlg.FormDestroy(Sender: TObject); +begin + // Save current page + if Assigned(pcMain.ActivePage) then + Preferences.LastTab := MapTabSheetToPage(pcMain.ActivePage).DisplayName; + inherited; +end; + +function TPreferencesDlg.GetLastTabIdx: Integer; +var + TabName: string; + ListIdx: Integer; +begin + TabName := Preferences.LastTab; + if TabName = '' then + Exit(-1); + for ListIdx := 0 to Pred(lbPages.Count) do + if StrSameText(TabName, lbPages.Items[ListIdx]) then + Exit(ListIdx); + Result := -1; +end; + function TPreferencesDlg.GetSelectedPage: TPrefsBaseFrame; begin Result := MapTabSheetToPage(pcMain.ActivePage); @@ -305,7 +336,7 @@ function TPreferencesDlg.GetSelectedPage: TPrefsBaseFrame; procedure TPreferencesDlg.InitForm; var - TabIdx: Integer; // loops thru tabs in page control + TabIdx: Integer; // loops thru tabs in page control begin inherited; // Take local copy of global preferences. This local copy will be updated as @@ -314,9 +345,22 @@ procedure TPreferencesDlg.InitForm; fLocalPrefs := (Preferences as IClonable).Clone as IPreferences; // Display and initialise required pages for TabIdx := 0 to Pred(pcMain.PageCount) do - MapTabSheetToPage(TabIdx).LoadPrefs(fLocalPrefs); - // Select first TabSheet - pcMain.ActivePageIndex := 0; + MapTabSheetToPage(TabIdx).LoadPrefs(fLocalPrefs, fFrameFlags); + // Select last used tab sheet (or 1st if last not known) + fCurrentPageIdx := GetLastTabIdx; + if fCurrentPageIdx < 0 then + fCurrentPageIdx := 0; + pcMain.ActivePageIndex := fCurrentPageIdx; + lbPages.ItemIndex := fCurrentPageIdx; +end; + +procedure TPreferencesDlg.lbPagesClick(Sender: TObject); +begin + if lbPages.ItemIndex < 0 then + Exit; + if lbPages.ItemIndex = fCurrentPageIdx then + Exit; + SelectTab(lbPages.Items.Objects[lbPages.ItemIndex] as TTabSheet) end; class function TPreferencesDlg.MapClassNameToPageClass(const ClsName: string): @@ -354,24 +398,6 @@ function TPreferencesDlg.MapTabSheetToPage( Assert(Assigned(Result), ClassName + '.MapTabSheetToPage: Frame not found'); end; -procedure TPreferencesDlg.pcMainChange(Sender: TObject); -begin - GetSelectedPage.Activate(fLocalPrefs); -end; - -procedure TPreferencesDlg.pcMainChanging(Sender: TObject; - var AllowChange: Boolean); -begin - GetSelectedPage.Deactivate(fLocalPrefs); -end; - -procedure TPreferencesDlg.pcMainMouseDown(Sender: TObject; Button: TMouseButton; - Shift: TShiftState; X, Y: Integer); -begin - if htOnItem in pcMain.GetHitTestInfoAt(X, Y) then - pcMain.SetFocus; -end; - class procedure TPreferencesDlg.RegisterPage(const FrameCls: TPrefsFrameClass); var PageIdx: Integer; // loops through all registered frames @@ -392,5 +418,14 @@ class procedure TPreferencesDlg.RegisterPage(const FrameCls: TPrefsFrameClass); fPages.Insert(InsIdx, FrameCls); end; +procedure TPreferencesDlg.SelectTab(TS: TTabSheet); +begin + Assert(Assigned(TS), ClassName + '.SelectTab: TS is nil'); + GetSelectedPage.Deactivate(fLocalPrefs); + pcMain.ActivePage := TS; + GetSelectedPage.Activate(fLocalPrefs, fFrameFlags); + fCurrentPageIdx := pcMain.ActivePageIndex; +end; + end. diff --git a/Src/FmPreviewDlg.pas b/Src/FmPreviewDlg.pas index e6c50485e..e9f924046 100644 --- a/Src/FmPreviewDlg.pas +++ b/Src/FmPreviewDlg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a dialogue box that is used to preview or display plain text, HTML * and Rich text documents. @@ -175,16 +172,18 @@ procedure TPreviewDlg.CopyToClipboard; class procedure TPreviewDlg.Execute(AOwner: TComponent; const ADocContent: TEncodedData; const ADocType: TPreviewDocType; const ADlgTitle: string); +var + Dlg: TPreviewDlg; begin - with InternalCreate(AOwner) do - try - fDlgTitle := ADlgTitle; - fDocContent := TEncodedData.Create(ADocContent); - fDocType := ADocType; - ShowModal; - finally - Free; - end; + Dlg := InternalCreate(AOwner); + try + Dlg.fDlgTitle := ADlgTitle; + Dlg.fDocContent := TEncodedData.Create(ADocContent); + Dlg.fDocType := ADocType; + Dlg.ShowModal; + finally + Dlg.Free; + end; end; class function TPreviewDlg.FindParentTabSheet(const Frame: TFrame): TTabSheet; diff --git a/Src/FmPrintDlg.pas b/Src/FmPrintDlg.pas index 770129bd9..b34cdcab0 100644 --- a/Src/FmPrintDlg.pas +++ b/Src/FmPrintDlg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2007-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2007-2024, Peter Johnson (gravatar.com/delphidabbler). * * Implements a print dialogue box. } @@ -79,7 +76,8 @@ implementation // Delphi Printers, Graphics, // Project - FmPreferencesDlg, FrPrintingPrefs, UClassHelpers, UConsts, UMessageBox, + ClassHelpers.UGraphics, + FmPreferencesDlg, FrPrintingPrefs, UConsts, UMessageBox, UPageSetupDlgMgr, UPrintInfo, UStructs, UStrUtils; @@ -133,7 +131,11 @@ procedure TPrintDlg.btnPrefencesClick(Sender: TObject); sMessage = 'Your preferences will take effect the next time you start the ' + 'application'; begin - if TPreferencesDlg.Execute(Self, [TPrintingPrefsFrame]) then + if TPreferencesDlg.Execute( + Self, + [TPrintingPrefsFrame], + TPrintingPrefsFrame.MakeFrameFlag(TPrintingPrefsFrame.HideRestartMessage) + ) then begin if TMessageBox.Confirm(Self, sQuery) then begin @@ -263,13 +265,15 @@ class function TPrintDlg.Execute(const AOwner: TComponent): Boolean; @param AOwner [in] Owner of dialog box. @return True if user OKs dialog box and False if user cancels. } +var + Dlg: TPrintDlg; begin - with Create(AOwner) do - try - Result := ShowModal = mrOK; - finally - Free; - end; + Dlg := Create(AOwner); + try + Result := Dlg.ShowModal = mrOK; + finally + Dlg.Free; + end; end; procedure TPrintDlg.FormCreate(Sender: TObject); diff --git a/Src/FmProgramUpdatesDlg.dfm b/Src/FmProgramUpdatesDlg.dfm deleted file mode 100644 index 105705105..000000000 --- a/Src/FmProgramUpdatesDlg.dfm +++ /dev/null @@ -1,38 +0,0 @@ -inherited ProgramUpdatesDlg: TProgramUpdatesDlg - Caption = 'Check For Program Updates' - ExplicitWidth = 474 - ExplicitHeight = 374 - PixelsPerInch = 96 - TextHeight = 13 - inherited pnlBody: TPanel - object lblProgram: TLabel - Left = 0 - Top = 0 - Width = 54 - Height = 13 - Caption = 'Please wait' - end - object lblPreReleaseMsg: TLabel - Left = 0 - Top = 50 - Width = 345 - Height = 33 - AutoSize = False - Caption = - 'New preview and beta releases are not reported here - they are a' + - 'nnounced in the CodeSnip news feed.' - Visible = False - WordWrap = True - end - object btnProgUpdate: TButton - Left = 0 - Top = 19 - Width = 129 - Height = 25 - Caption = '&Download Now' - TabOrder = 0 - Visible = False - OnClick = btnProgUpdateClick - end - end -end diff --git a/Src/FmProgramUpdatesDlg.pas b/Src/FmProgramUpdatesDlg.pas deleted file mode 100644 index 52da4a5f4..000000000 --- a/Src/FmProgramUpdatesDlg.pas +++ /dev/null @@ -1,178 +0,0 @@ -{ - * This Source Code Form is subject to the terms of the Mozilla Public License, - * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ - * - * Copyright (C) 2012-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ - * - * Implements a dialogue box that accesses program update web service and - * reports if a new version of CodeSnip is available. -} - - -unit FmProgramUpdatesDlg; - - -interface - - -uses - // Delphi - StdCtrls, Controls, ExtCtrls, Classes, - // Project - FmGenericViewDlg, UBaseObjects, UProgramUpdateChecker; - - -type - /// <summary>Dialogue box that checks if a program update is available and - /// reports the result, providing a link to download any available update. - /// </summary> - /// <remarks>This dialogus box indirectly accesses the CodeSnip program - /// update we service to get the update information.</remarks> - TProgramUpdatesDlg = class(TGenericViewDlg, INoPublicConstruct) - lblProgram: TLabel; - btnProgUpdate: TButton; - lblPreReleaseMsg: TLabel; - /// <summary>Handles form creation event. Creates owned objects.</summary> - procedure FormCreate(Sender: TObject); - /// <summary>Handles form destruction event. Frees owned objects.</summary> - procedure FormDestroy(Sender: TObject); - /// <summary>Handles clicks on "Download Now" button by displaying, in the - /// default browser, the web page from where the latest version of CodeSnip - /// can be downloaded.</summary> - procedure btnProgUpdateClick(Sender: TObject); - strict private - var - /// <summary>Object that checks whether an update is available and - /// provides information about it if so.</summary> - fUpdateChecker: TProgramUpdateChecker; - /// <summary>Checks if a program update is available and updates the UI - /// according to the result.</summary> - procedure CheckProgramUpdates; - strict protected - /// <summary>Triggers checks for updates after the forma is displayed. - /// </summary> - /// <remarks>Called from ancestor class.</remarks> - procedure AfterShowForm; override; - /// <summary>Arranges the controls and sizes the dialogue box.</summary> - /// <remarks>Called from ancestor class.</remarks> - procedure ArrangeForm; override; - /// <summary>Initialises form's controls.</summary> - /// <remarks>Called from ancestor class.</remarks> - procedure InitForm; override; - public - /// <summary>Displays the dialogue box, aligned over the given owner - /// control.</summary> - /// <remarks>The check for program updates begins as soon as the dialogue - /// box appears on-screen, without any user interaction being required to - /// start it.</remarks> - class procedure Execute(AOwner: TComponent); - end; - - -implementation - - -uses - // Delphi - SysUtils, Forms, ExtActns, Graphics, - // Project - UCtrlArranger; - -{$R *.dfm} - - -{ TCheckUpdatesDlg } - -resourcestring - sChecking = 'Checking...'; - sProgNeedsUpdating = 'CodeSnip version %s is available.'; - sProgUpToDate = 'CodeSnip is up to date.'; - -procedure TProgramUpdatesDlg.AfterShowForm; -begin - Screen.Cursor := crHourGlass; - try - CheckProgramUpdates; - finally - Screen.Cursor := crDefault; - end; -end; - -procedure TProgramUpdatesDlg.ArrangeForm; -begin - TCtrlArranger.SetLabelHeight(lblPreReleaseMsg); - TCtrlArranger.MoveBelow(lblProgram, btnProgUpdate, 12); - TCtrlArranger.MoveBelow(lblProgram, lblPreReleaseMsg, 8); - pnlBody.ClientHeight := TCtrlArranger.TotalControlHeight(pnlBody) + 8; - TCtrlArranger.AlignLefts([lblProgram, btnProgUpdate, lblPreReleaseMsg], 0); - pnlBody.Width := TCtrlArranger.TotalControlWidth(pnlBody); - inherited; -end; - -procedure TProgramUpdatesDlg.btnProgUpdateClick(Sender: TObject); -var - BrowseAction: TBrowseURL; // action that displays RSS feed URL in browser -begin - BrowseAction := TBrowseURL.Create(nil); - try - BrowseAction.URL := fUpdateChecker.DownloadURL; - BrowseAction.Execute; - finally - BrowseAction.Free; - end; -end; - -procedure TProgramUpdatesDlg.CheckProgramUpdates; -begin - btnProgUpdate.Visible := False; - lblProgram.Caption := sChecking; - Application.ProcessMessages; - fUpdateChecker.Execute; - if fUpdateChecker.IsUpdateAvailable then - begin - lblProgram.Caption := Format( - sProgNeedsUpdating, [string(fUpdateChecker.LatestVersion)] - ); - btnProgUpdate.Visible := True; - end - else - begin - lblProgram.Caption := sProgUpToDate; - lblPreReleaseMsg.Visible := True; - end; -end; - -class procedure TProgramUpdatesDlg.Execute(AOwner: TComponent); -begin - with InternalCreate(AOwner) do - try - ShowModal; - finally - Free; - end; -end; - -procedure TProgramUpdatesDlg.FormCreate(Sender: TObject); -begin - inherited; - fUpdateChecker := TProgramUpdateChecker.Create('Manual'); -end; - -procedure TProgramUpdatesDlg.FormDestroy(Sender: TObject); -begin - fUpdateChecker.Free; - inherited; -end; - -procedure TProgramUpdatesDlg.InitForm; -begin - inherited; - lblPreReleaseMsg.Font.Color := clGrayText; -end; - -end. - diff --git a/Src/FmProxyServerDlg.dfm b/Src/FmProxyServerDlg.dfm deleted file mode 100644 index 41c600225..000000000 --- a/Src/FmProxyServerDlg.dfm +++ /dev/null @@ -1,191 +0,0 @@ -inherited ProxyServerDlg: TProxyServerDlg - Caption = 'Proxy Server Configuration' - ExplicitWidth = 474 - ExplicitHeight = 356 - PixelsPerInch = 96 - TextHeight = 13 - inherited pnlBody: TPanel - Width = 299 - Height = 245 - ExplicitWidth = 299 - ExplicitHeight = 245 - object cbUseProxy: TCheckBox - Left = 0 - Top = 0 - Width = 169 - Height = 17 - Caption = '&Use proxy server' - TabOrder = 0 - OnClick = cbUseProxyClick - end - object gbProxy: TGroupBox - Left = 0 - Top = 27 - Width = 299 - Height = 218 - Caption = 'Proxy Server Details' - TabOrder = 1 - object lblIPAddress: TLabel - Left = 11 - Top = 28 - Width = 91 - Height = 13 - Caption = 'Server IP &Address:' - FocusControl = edIPAddress - end - object lblPort: TLabel - Left = 11 - Top = 59 - Width = 79 - Height = 13 - Caption = '&Port (0..65535):' - FocusControl = edPort - end - object lblUserName: TLabel - Left = 11 - Top = 89 - Width = 56 - Height = 13 - Caption = 'User &Name:' - FocusControl = edUserName - end - object lblPassword1: TLabel - Left = 11 - Top = 119 - Width = 50 - Height = 13 - Caption = 'Pass&word:' - FocusControl = edPassword1 - end - object lblPassword2: TLabel - Left = 11 - Top = 150 - Width = 96 - Height = 13 - Caption = '&Re-enter Password:' - FocusControl = edPassword2 - end - object lblIPAddressReq: TLabel - Left = 242 - Top = 29 - Width = 8 - Height = 16 - Caption = '*' - Font.Charset = DEFAULT_CHARSET - Font.Color = clRed - Font.Height = -13 - Font.Name = 'Tahoma' - Font.Style = [] - ParentFont = False - end - object lblPortReq: TLabel - Left = 180 - Top = 58 - Width = 8 - Height = 16 - Caption = '*' - Font.Charset = DEFAULT_CHARSET - Font.Color = clRed - Font.Height = -13 - Font.Name = 'Tahoma' - Font.Style = [] - ParentFont = False - end - object lblReqSymbol: TLabel - Left = 11 - Top = 185 - Width = 8 - Height = 16 - Caption = '*' - Font.Charset = DEFAULT_CHARSET - Font.Color = clRed - Font.Height = -13 - Font.Name = 'Tahoma' - Font.Style = [] - ParentFont = False - end - object lblReqExplain: TLabel - Left = 22 - Top = 187 - Width = 78 - Height = 13 - Caption = '= required field.' - end - object edIPAddress: TEdit - Left = 131 - Top = 29 - Width = 105 - Height = 21 - MaxLength = 15 - TabOrder = 0 - OnKeyPress = edIPAddressKeyPress - end - object edPort: TEdit - Left = 131 - Top = 56 - Width = 43 - Height = 21 - MaxLength = 5 - TabOrder = 1 - OnKeyPress = edPortKeyPress - end - object edUserName: TEdit - Left = 131 - Top = 85 - Width = 153 - Height = 21 - TabOrder = 2 - end - object edPassword1: TEdit - Left = 131 - Top = 115 - Width = 153 - Height = 21 - PasswordChar = '*' - TabOrder = 3 - end - object edPassword2: TEdit - Left = 131 - Top = 146 - Width = 153 - Height = 21 - PasswordChar = '*' - TabOrder = 4 - end - end - end - inherited btnOK: TButton - OnClick = btnOKClick - end - object alMain: TActionList - Left = 320 - Top = 32 - object actCut: TEditCut - Category = 'Edit' - Caption = 'Cu&t' - Hint = 'Cut|Cuts the selection and puts it on the Clipboard' - ImageIndex = 0 - ShortCut = 16472 - end - object actCopy: TEditCopy - Category = 'Edit' - Caption = '&Copy' - Hint = 'Copy|Copies the selection and puts it on the Clipboard' - ImageIndex = 1 - ShortCut = 16451 - end - object actPaste: TEditPaste - Category = 'Edit' - Caption = '&Paste' - Hint = 'Paste|Inserts Clipboard contents' - ImageIndex = 2 - ShortCut = 16470 - end - object actSelectAll: TEditSelectAll - Category = 'Edit' - Caption = 'Select &All' - Hint = 'Select All|Selects the entire document' - ShortCut = 16449 - end - end -end diff --git a/Src/FmProxyServerDlg.pas b/Src/FmProxyServerDlg.pas deleted file mode 100644 index 8bcda6d5a..000000000 --- a/Src/FmProxyServerDlg.pas +++ /dev/null @@ -1,333 +0,0 @@ -{ - * This Source Code Form is subject to the terms of the Mozilla Public License, - * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ - * - * Copyright (C) 2009-2014, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ - * - * Implements a dialogue box that enables users to configure a proxy server for - * use by CodeSnip's web services. -} - - -unit FmProxyServerDlg; - - -interface - - -uses - // Delphi - StdCtrls, Controls, ExtCtrls, Classes, StdActns, ActnList, - // Project - FmGenericOKDlg; - - -type - - { - TProxyServerDlg: - Ddialog box that enables users to specify (or remove) a proxy server for use - by CodeSnip's web services. - } - TProxyServerDlg = class(TGenericOKDlg) - cbUseProxy: TCheckBox; - gbProxy: TGroupBox; - lblIPAddress: TLabel; - lblIPAddressReq: TLabel; - lblPassword1: TLabel; - lblPassword2: TLabel; - lblPort: TLabel; - lblPortReq: TLabel; - lblReqExplain: TLabel; - lblReqSymbol: TLabel; - lblUserName: TLabel; - edIPAddress: TEdit; - edPassword1: TEdit; - edPassword2: TEdit; - edPort: TEdit; - edUserName: TEdit; - alMain: TActionList; - actCut: TEditCut; - actCopy: TEditCopy; - actPaste: TEditPaste; - actSelectAll: TEditSelectAll; - procedure btnOKClick(Sender: TObject); - procedure edIPAddressKeyPress(Sender: TObject; var Key: Char); - procedure edPortKeyPress(Sender: TObject; var Key: Char); - procedure cbUseProxyClick(Sender: TObject); - strict private - procedure Validate; - {Validates data entered into controls. - } - procedure SaveData; - {Stores data entered in controls in settings. - } - procedure SetProxyCtrlState(const Flag: Boolean); - {Sets enabled state of all controls in "proxy server details" group box. - @param Flag [in] True if controls are to be enabled, False if to be - disabled. - } - strict protected - procedure ConfigForm; override; - {Configures form. Ensures correct font is used and sets password - character. - } - procedure InitForm; override; - {Initialises controls on form from values read from settings. - } - public - class function Execute(const AOwner: TComponent): Boolean; - {Creates and displays the proxy server dialog box. - @param AOwner [in] Component that owns the dialog box. - } - end; - - -implementation - - -uses - // Delphi - SysUtils, Windows, Character, - // Project - UConsts, UExceptions, UFontHelper, UMessageBox, USettings, UStructs, - USystemInfo, UStrUtils, UUtils; - - -{$R *.dfm} - -{ TProxyServerDlg } - -procedure TProxyServerDlg.btnOKClick(Sender: TObject); - {Handles OK button click. Validates entered data and saves proxy information - in settings. Handles any errors. - @param Sender [in] Not used. - } -begin - try - ModalResult := mrNone; - Validate; - SaveData; - ModalResult := mrOK; - except - on E: EDataEntry do - begin - if Assigned(E.Ctrl) then - begin - E.Ctrl.SetFocus; - TMessageBox.Error(Self, E.Message); - end; - end; - end; -end; - -procedure TProxyServerDlg.cbUseProxyClick(Sender: TObject); - {Handles click on "Use proxy server" check box. Updates state of other - controls depending on whether check box is checked. - @param Sender [in] Not used. - } -begin - inherited; - SetProxyCtrlState(cbUseProxy.Checked); -end; - -procedure TProxyServerDlg.ConfigForm; - {Configures form. Ensures correct font is used and sets password character. - } -begin - inherited; - TFontHelper.SetDefaultBaseFont(lblIPAddressReq.Font); - TFontHelper.SetDefaultBaseFont(lblPortReq.Font); - TFontHelper.SetDefaultBaseFont(lblReqSymbol.Font); - if TOSInfo.IsReallyWindowsVistaOrGreater then - begin - edPassword1.PasswordChar := '�'; - edPassword2.PasswordChar := '�'; - end - else - begin - edPassword1.PasswordChar := '*'; - edPassword2.PasswordChar := '*'; - end; -end; - -procedure TProxyServerDlg.edIPAddressKeyPress(Sender: TObject; var Key: Char); - {Filters keypresses in IP address edit box. Permits only numbers or dots to be - entered. - @param Sender [in] Not used. - @param Key [in/out] Key pressed. Set to #0 if key is not permitted. - } -const - cDot = '.'; // dot separator (not decimal point) -begin - if not TCharacter.IsDigit(Key) and (Key <> cDot) and (Key <> BACKSPACE) then - Key := #0 - else if (Key = cDot) and ( - (edIPAddress.SelStart = 0) or (StrCountDelims(cDot, edIPAddress.Text) = 3) - ) then - Key := #0; - if Key = #0 then - KeyErrorBeep; -end; - -procedure TProxyServerDlg.edPortKeyPress(Sender: TObject; var Key: Char); - {Filters keypresses in Port edit box. Permits only numbers to be entered. - @param Sender [in] Not used. - @param Key [in/out] Key pressed. Set to #0 if key is not permitted. - } -begin - if not TCharacter.IsDigit(Key) and (Key <> BACKSPACE) then - Key := #0; - if Key = #0 then - KeyErrorBeep; -end; - -class function TProxyServerDlg.Execute(const AOwner: TComponent): Boolean; - {Creates and displays the proxy server dialog box. - @param AOwner [in] Component that owns the dialog box. - } -begin - with Create(AOwner) do - try - Result := ShowModal = mrOK; - finally - Free; - end; -end; - -procedure TProxyServerDlg.InitForm; - {Initialises controls on form from values read from settings. - } -var - Section: ISettingsSection; // settings section containing current settings -begin - inherited; - // init control contents from proxy server settings - Section := Settings.ReadSection(ssProxyServer); - cbUseProxy.Checked := Section.GetBoolean('UseProxy', False); - edIPAddress.Text := Section.GetString('IPAddress'); - edPort.Text := Section.GetString('Port'); - edUserName.Text := Section.GetString('UserName'); - edPassword1.Text := Section.GetEncryptedString('Password'); - edPassword2.Text := edPassword1.Text; - // init control state - SetProxyCtrlState(cbUseProxy.Checked); -end; - -procedure TProxyServerDlg.SaveData; - {Stores data entered in controls in settings. - } -var - Section: ISettingsSection; // settings section to receive data -begin - Section := Settings.EmptySection(ssProxyServer); - Section.SetBoolean('UseProxy', cbUseProxy.Checked); - Section.SetString('IPAddress', edIPAddress.Text); - Section.SetString('Port', edPort.Text); - Section.SetString('UserName', edUserName.Text); - Section.SetEncryptedString('Password', edPassword1.Text); - Section.Save; -end; - -procedure TProxyServerDlg.SetProxyCtrlState(const Flag: Boolean); - {Sets enabled state of all controls in "proxy server details" group box. - @param Flag [in] True if controls are to be enabled, False if to be - disabled. - } -var - Idx: Integer; // loops through all child controls of group box -begin - for Idx := 0 to Pred(gbProxy.ControlCount) do - gbProxy.Controls[Idx].Enabled := Flag; - gbProxy.Enabled := Flag; -end; - -procedure TProxyServerDlg.Validate; - {Validates data entered into controls. - } - - // --------------------------------------------------------------------------- - function IsValidIPAddress(const Addr: string): Boolean; - {Checks if an IP address has valid format. - @param Addr [in] IP address to check. - @return True if IP address has valid format, False otherwise. - } - var - Quads: TStringList; // quads of IP address - Quad: string; // a quad - IQuad: Integer; // integer value of a quad - const - IPQuadRange: TRange = (Min: 0; Max: 255); // range of valid IP quads - begin - Result := False; - Quads := TStringList.Create; - try - // split IP address into quads (they are separated by dots) - StrExplode(Addr, '.', Quads); - if Quads.Count <> 4 then - Exit; // must be 4 quads - for Quad in Quads do - begin - if not TryStrToInt(Quad, IQuad) then - Exit; // each quad must be an integer - if not IPQuadRange.Contains(IQuad) then - Exit; // each quad must be storable in a byte - end; - finally - FreeAndNil(Quads); - end; - Result := True; - end; - - function IsValidPort(const Port: string): Boolean; - {Checks if a port is valid. - @param Port [in] String representation of port to be checked. - @return True if port is valid, False otherwise. - } - var - IPort: Integer; // port nunber as integer - const - PortNumbers: TRange = (Min: 0; Max: 65535); // range of valid port numbers - begin - Result := False; - // check port is a valid number - if not TryStrToInt(Port, IPort) then - Exit; - // check port in range (see http://www.iana.org/assignments/port-numbers) - if not PortNumbers.Contains(IPort) then - Exit; - Result := True; - end; - // --------------------------------------------------------------------------- - -resourcestring - // Error messages - sMissingIPAddress = 'IP address must be specified'; - sInvalidIPAddress = 'Invalid IP address'; - sMissingPort = 'Port must be specified'; - sInvalidPort = 'Port must be in range 0..65535'; - sMissingUserName = 'User name must be specified'; - sPasswordMismatch = 'Passwords are not the same. Please re-enter.'; -begin - if not cbUseProxy.Checked then - Exit; - if edIPAddress.Text = '' then - raise EDataEntry.Create(sMissingIPAddress, edIPAddress); - if not IsValidIPAddress(edIPAddress.Text) then - raise EDataEntry.Create(sInvalidIPAddress, edIPAddress); - if edPort.Text = '' then - raise EDataEntry.Create(sMissingPort, edPort); - if not IsValidPort(edPort.Text) then - raise EDataEntry.Create(sInvalidPort, edPort); - if (edPassword1.Text <> '') and (edUserName.Text = '') then - raise EDataEntry.Create(sMissingUserName, edUserName); - if edPassword1.Text <> edPassword2.Text then - raise EDataEntry.Create(sPasswordMismatch, edPassword1); -end; - -end. - diff --git a/Src/FmRegisterCompilersDlg.dfm b/Src/FmRegisterCompilersDlg.dfm new file mode 100644 index 000000000..c9db2d7e4 --- /dev/null +++ b/Src/FmRegisterCompilersDlg.dfm @@ -0,0 +1,91 @@ +inherited RegisterCompilersDlg: TRegisterCompilersDlg + Caption = 'RegisterCompilersDlg' + ClientHeight = 453 + ExplicitWidth = 474 + ExplicitHeight = 482 + PixelsPerInch = 96 + TextHeight = 13 + inherited bvlBottom: TBevel + Top = 403 + ExplicitTop = 403 + end + inherited pnlBody: TPanel + Width = 433 + Height = 389 + ExplicitWidth = 433 + ExplicitHeight = 389 + object lblDesc: TLabel + Left = 0 + Top = 0 + Width = 33 + Height = 13 + Caption = 'lblDesc' + FocusControl = clbCompilers + end + object clbCompilers: TCheckListBox + Left = 0 + Top = 32 + Width = 361 + Height = 56 + IntegralHeight = True + ItemHeight = 13 + TabOrder = 0 + end + inline frmNotes: TFixedHTMLDlgFrame + Left = 0 + Top = 159 + Width = 361 + Height = 199 + TabOrder = 1 + TabStop = True + ExplicitTop = 159 + ExplicitWidth = 361 + ExplicitHeight = 199 + inherited pnlBrowser: TPanel + Width = 361 + Height = 199 + ExplicitWidth = 361 + ExplicitHeight = 199 + inherited wbBrowser: TWebBrowser + Width = 361 + Height = 199 + ExplicitWidth = 353 + ExplicitHeight = 199 + ControlData = { + 4C0000004F250000911400000000000000000000000000000000000000000000 + 000000004C000000000000000000000001000000E0D057007335CF11AE690800 + 2B2E126208000000000000004C0000000114020000000000C000000000000046 + 8000000000000000000000000000000000000000000000000000000000000000 + 00000000000000000100000000000000000000000000000000000000} + end + end + end + object chkDontShowAgain: TCheckBox + Left = 0 + Top = 372 + Width = 145 + Height = 17 + Caption = '&Don'#39't show this again' + TabOrder = 2 + end + end + inherited btnHelp: TButton + Left = 362 + Top = 420 + ExplicitLeft = 362 + ExplicitTop = 420 + end + inherited btnCancel: TButton + Left = 282 + Top = 420 + ExplicitLeft = 282 + ExplicitTop = 420 + end + inherited btnOK: TButton + Left = 201 + Top = 420 + Default = False + ExplicitLeft = 201 + ExplicitTop = 420 + end +end diff --git a/Src/FmRegisterCompilersDlg.pas b/Src/FmRegisterCompilersDlg.pas new file mode 100644 index 000000000..05c897c49 --- /dev/null +++ b/Src/FmRegisterCompilersDlg.pas @@ -0,0 +1,250 @@ +{ + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/ + * + * Copyright (C) 2022, Peter Johnson (gravatar.com/delphidabbler). + * + * Implements a dialogue box that is optionally displayed at start-up when + * Delphi compiler(s) are installed but not registered with CodeSnip. Allows the + * user to specify which, if any, compiler(s) are to be registered. +} + + +unit FmRegisterCompilersDlg; + +interface + +uses + // Delphi + Forms, + Classes, + StdCtrls, + Controls, + CheckLst, + ExtCtrls, + SysUtils, + // Project + FmGenericOKDlg, + FrBrowserBase, + FrFixedHTMLDlg, + FrHTMLDlg, + Compilers.UCompilers, + Compilers.UGlobals, + UBaseObjects, + UCSSBuilder; + +type + TRegisterCompilersDlg = class(TGenericOKDlg, INoPublicConstruct) + lblDesc: TLabel; + clbCompilers: TCheckListBox; + frmNotes: TFixedHTMLDlgFrame; + chkDontShowAgain: TCheckBox; + procedure FormDestroy(Sender: TObject); + procedure alMainUpdate(Action: TBasicAction; var Handled: Boolean); + strict private + var + fCandidateCompilers: TCompilerList; + fSelectedCompilers: TCompilerList; + function CountTickedCompilers: Integer; + procedure StoreSelectedCompilers; + procedure SetCaptions; + procedure PopulateList; + strict protected + procedure ConfigForm; override; + procedure InitForm; override; + procedure ArrangeForm; override; + /// <summary>Handles HTML frame's OnBuildCSS event. Adds additional CSS + /// required by HTML in this form.</summary> + /// <param name="Sender">TObject [in] Reference to object triggering event. + /// </param> + /// <param name="CSSBuilder">TCSSBuilder [in] Object used to construct the + /// CSS.</param> + procedure BuildCSS(Sender: TObject; const CSSBuilder: TCSSBuilder); + public + class function Execute(const AOwner: TComponent; + const CandidateCompilers: TCompilerList; + const SelectedCompilers: TCompilerList): Boolean; + end; + +implementation + +uses + Compilers.USettings, + UBox, + UCSSUtils, + UCtrlArranger; + +{$R *.dfm} + +{ TRegisterCompilersDlg } + +procedure TRegisterCompilersDlg.alMainUpdate(Action: TBasicAction; + var Handled: Boolean); +begin + inherited; + btnOK.Enabled := chkDontShowAgain.Checked or (CountTickedCompilers > 0); +end; + +procedure TRegisterCompilersDlg.ArrangeForm; +begin + TCtrlArranger.SetLabelHeight(lblDesc); + clbCompilers.Top := TCtrlArranger.BottomOf(lblDesc, 8); + frmNotes.Top := TCtrlArranger.BottomOf(clbCompilers, 0); + frmNotes.Height := frmNotes.DocHeight; + chkDontShowAgain.Top := TCtrlArranger.BottomOf(frmNotes, 8); + TCtrlArranger.AlignLefts( + [lblDesc, clbCompilers, frmNotes, chkDontShowAgain], 0 + ); + pnlBody.ClientHeight := TCtrlArranger.TotalControlHeight(pnlBody) + 8; + pnlBody.ClientWidth := TCtrlArranger.TotalControlWidth(pnlBody); + inherited; +end; + +procedure TRegisterCompilersDlg.BuildCSS(Sender: TObject; + const CSSBuilder: TCSSBuilder); +var + Selector: TCSSSelector; +begin + Selector := CSSBuilder.AddSelector('ol'); + Selector.AddProperty(TCSS.MarginProp(cssLeft, 24)); + Selector.AddProperty(TCSS.MarginProp(cssTop, 6)); + Selector.AddProperty(TCSS.MarginProp(cssBottom, 0)); + Selector.AddProperty(TCSS.PaddingProp(0)); + + Selector := CSSBuilder.AddSelector('ol li'); + Selector.AddProperty(TCSS.MarginProp(cssTop, 6)); + + Selector := CSSBuilder.AddSelector('ol li ul'); + Selector.AddProperty(TCSS.MarginProp(cssLeft, 16)); + Selector.AddProperty(TCSS.MarginProp(cssTop, 6)); + Selector.AddProperty(TCSS.MarginProp(cssBottom, 0)); + Selector.AddProperty(TCSS.PaddingProp(0)); + + Selector := CSSBuilder.AddSelector('ol li ul li'); + Selector.AddProperty(TCSS.MarginProp(cssTop, 0)); +end; + +procedure TRegisterCompilersDlg.ConfigForm; +begin + inherited; + frmNotes.OnBuildCSS := BuildCSS; + frmNotes.Initialise('dlg-register-compilers.html'); +end; + +function TRegisterCompilersDlg.CountTickedCompilers: Integer; +var + I: Integer; +begin + Result := 0; + for I := 0 to Pred(clbCompilers.Count) do + if clbCompilers.Checked[I] then + Inc(Result); +end; + +class function TRegisterCompilersDlg.Execute(const AOwner: TComponent; + const CandidateCompilers: TCompilerList; + const SelectedCompilers: TCompilerList): Boolean; +var + Dlg: TRegisterCompilersDlg; +begin + Assert(Assigned(CandidateCompilers), + ClassName + '.Execute: AvailCompilers list is nil'); + Assert(Assigned(SelectedCompilers), + ClassName + '.Execute: CompilersToReg list is nil'); + Assert(CandidateCompilers.Count > 0, + ClassName + '.Execute: AvailCompilers list must not be empty'); + + Dlg := InternalCreate(AOwner); + try + Dlg.fCandidateCompilers := CandidateCompilers; // reference to list + Dlg.fSelectedCompilers := SelectedCompilers; // reference to list + Result := Dlg.ShowModal = mrOK; + if Result then + begin + Dlg.StoreSelectedCompilers; + TCompilerSettings.PermitStartupDetection := + not Dlg.chkDontShowAgain.Checked; + end; + finally + Dlg.Free; + end; +end; + +procedure TRegisterCompilersDlg.FormDestroy(Sender: TObject); +var + Idx: Integer; +begin + // Free TBox<> objects associated with compiler list items + for Idx := Pred(clbCompilers.Count) downto 0 do + clbCompilers.Items.Objects[Idx].Free; + inherited; +end; + +procedure TRegisterCompilersDlg.InitForm; +begin + inherited; + SetCaptions; + PopulateList; +end; + +procedure TRegisterCompilersDlg.PopulateList; +var + Compiler: ICompiler; +begin + clbCompilers.Items.BeginUpdate; + try + clbCompilers.Clear; + for Compiler in fCandidateCompilers do + begin + clbCompilers.Items.AddObject( + Compiler.GetName, + TBox<ICompiler>.Create(Compiler) + ); + end; + finally + clbCompilers.Items.EndUpdate; + end; + Assert(clbCompilers.Count > 0, + ClassName + '.PopulateList: compiler list empty'); + clbCompilers.ItemIndex := 0; +end; + +procedure TRegisterCompilersDlg.SetCaptions; +resourcestring + sFormCaptionS = 'Unregistered Delphi Installations Detected'; + sFormCaptionP = 'Unregistered Delphi Installation Detected'; + sDescS = '&Unregistered compiler. Tick to register:'; + sDescP = '&Unregistered compilers. Tick the ones to be registered:'; +begin + // Set form and description captions + if fCandidateCompilers.Count = 1 then + begin + Caption := sFormCaptionS; + lblDesc.Caption := sDescS; + end + else + begin + // fCandidateCompilers.Count > 1 because fCandidateCompilers.Count > 0 assured by + // assertions in Execute method + Caption := sFormCaptionP; + lblDesc.Caption := sDescP; + end; +end; + +procedure TRegisterCompilersDlg.StoreSelectedCompilers; +var + I: Integer; +begin + fSelectedCompilers.Clear; + for I := 0 to Pred(clbCompilers.Count) do + if clbCompilers.Checked[I] then + begin + fSelectedCompilers.Add( + TBox<ICompiler>(clbCompilers.Items.Objects[I]).Value + ); + end; +end; + +end. + diff --git a/Src/FmRegistrationDlg.dfm b/Src/FmRegistrationDlg.dfm deleted file mode 100644 index a2907c6b8..000000000 --- a/Src/FmRegistrationDlg.dfm +++ /dev/null @@ -1,151 +0,0 @@ -inherited RegistrationDlg: TRegistrationDlg - Caption = 'Registration Wizard' - ExplicitWidth = 565 - ExplicitHeight = 433 - PixelsPerInch = 96 - TextHeight = 13 - inherited pnlBody: TPanel - inherited pcWizard: TPageControl - ActivePage = tsAboutUser - object tsIntro: TTabSheet - Caption = 'tsIntro' - TabVisible = False - object lblIntro: TLabel - Left = 0 - Top = 8 - Width = 369 - Height = 13 - AutoSize = False - Caption = 'Thanks for deciding to register CodeSnip.' - end - object lblIntroExplain: TLabel - Left = 0 - Top = 33 - Width = 369 - Height = 40 - AutoSize = False - Caption = - 'This wizard collects some information from you and the operating' + - ' system then contacts the DelphiDabbler.com website to register ' + - 'the program.' - WordWrap = True - end - object lblInstructions: TLabel - Left = 0 - Top = 72 - Width = 369 - Height = 13 - AutoSize = False - Caption = 'Please click the Next button below to begin.' - end - end - object tsAboutUser: TTabSheet - Caption = 'tsAboutUser' - ImageIndex = 1 - TabVisible = False - object lblName: TLabel - Left = 0 - Top = 8 - Width = 369 - Height = 41 - AutoSize = False - Caption = - 'The only information about you that is required to complete regs' + - 'tration is your &name. You can use a nickname if your prefer. Pl' + - 'ease enter it below (maximum 48 characters):' - FocusControl = edName - WordWrap = True - end - object edName: TEdit - Left = 0 - Top = 55 - Width = 369 - Height = 21 - BevelInner = bvSpace - BevelKind = bkFlat - BevelOuter = bvRaised - BorderStyle = bsNone - MaxLength = 48 - TabOrder = 0 - end - end - object tsSubmit: TTabSheet - Caption = 'tsSubmit' - ImageIndex = 3 - TabVisible = False - object lblReport: TLabel - Left = 0 - Top = 8 - Width = 369 - Height = 26 - AutoSize = False - Caption = - 'You are now ready to submit the registration. Here is the inform' + - 'ation that will be sent:' - WordWrap = True - end - object lblSubmit: TLabel - Left = 0 - Top = 162 - Width = 369 - Height = 26 - AutoSize = False - Caption = - 'Please ensure you are connected to the internet and then click t' + - 'he Submit button to send the registration.' - WordWrap = True - end - object edReport: TMemo - Left = 0 - Top = 40 - Width = 369 - Height = 113 - TabStop = False - BevelInner = bvSpace - BevelKind = bkFlat - BevelOuter = bvRaised - BorderStyle = bsNone - ParentColor = True - ReadOnly = True - ScrollBars = ssVertical - TabOrder = 0 - end - end - object tsFinish: TTabSheet - Caption = 'tsFinish' - ImageIndex = 4 - TabVisible = False - object lblThanks: TLabel - Left = 0 - Top = 8 - Width = 369 - Height = 13 - AutoSize = False - Caption = 'Your registration has been completed successfully - thankyou.' - WordWrap = True - end - object lblRegCode: TLabel - Left = 0 - Top = 40 - Width = 119 - Height = 13 - Caption = 'Your registration code is: ' - end - object edRegCode: TEdit - Left = 0 - Top = 59 - Width = 249 - Height = 21 - TabStop = False - BevelInner = bvSpace - BevelKind = bkFlat - BevelOuter = bvRaised - BorderStyle = bsNone - ParentColor = True - ReadOnly = True - TabOrder = 0 - end - end - end - end -end diff --git a/Src/FmRegistrationDlg.pas b/Src/FmRegistrationDlg.pas deleted file mode 100644 index 061e278aa..000000000 --- a/Src/FmRegistrationDlg.pas +++ /dev/null @@ -1,344 +0,0 @@ -{ - * This Source Code Form is subject to the terms of the Mozilla Public License, - * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ - * - * Copyright (C) 2006-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ - * - * Implements a wizard style dialogue box that registers the program online. -} - - -unit FmRegistrationDlg; - - -interface - - -uses - // Delphi - StdCtrls, ComCtrls, Controls, ExtCtrls, Classes, - // Project - FmWizardDlg, UBaseObjects; - - -type - - { - TRegistrationDlg: - Wizard style dialog box that collects information and sends application - registration to web server. - } - TRegistrationDlg = class(TWizardDlg, INoPublicConstruct) - edName: TEdit; - edRegCode: TEdit; - edReport: TMemo; - lblInstructions: TLabel; - lblIntro: TLabel; - lblIntroExplain: TLabel; - lblName: TLabel; - lblRegCode: TLabel; - lblReport: TLabel; - lblSubmit: TLabel; - lblThanks: TLabel; - tsAboutUser: TTabSheet; - tsFinish: TTabSheet; - tsIntro: TTabSheet; - tsSubmit: TTabSheet; - strict private - fRegistered: Boolean; // Flag indicating whether program was registered - procedure BuildSubmission(const Report: TStrings); - {Builds registration submission as list of values in name=value format. - @param Report [in] Stores submission on completion. - } - procedure DoRegistration; - {Registers program and displays registration code. - } - function RegisterWithWebServer: string; - {Gathers required registration data, sends to web server and gets - registration code from it. - @return Registration code. - @except EWebError exception raised if there is a failure in interaction - with web service. - } - function ValidateUserInfo: Boolean; - {Validates data entered by user. Displays any error messages. - @return True if data valid, false if not. - } - strict protected - procedure ArrangeForm; override; - {Vertically arranges controls as required and sizes the tab sheets to be - able to display the longest page. - } - procedure ConfigForm; override; - {Sets font styles where necessary and initialises HTML frames. - } - procedure InitForm; override; - {Initialises controls. - } - procedure BeginPage(const PageIdx: Integer); override; - {Performs required initialisation when a page is displayed. - @param PageIdx [in] Index page to be initialised. - } - function HeadingText(const PageIdx: Integer): string; override; - {Gets text of heading of a wizard page. - @param PageIdx [in] Index of page for which heading is required. - @return Heading text. - } - procedure MoveForward(const PageIdx: Integer; - var CanMove: Boolean); override; - {Performs required processing on moving forward from pages. - @param PageIdx [in] Index of page we are leaving. - @param CanMove [in/out] Flag set to indicate whether can leave page. - Defaults to true. - } - procedure UpdateButtons(const PageIdx: Integer); override; - {Updates wizard buttons depending on page and state. - @param PageIdx [in] Index of current page. - } - public - class function Execute(const Owner: TComponent): Boolean; - {Displays dialog box. - @param Owner [in] Component that owns this dialog. - @return True if program was registered, false otherwise. - } - end; - - -implementation - - -uses - // Delphi - SysUtils, Forms, - // Project - UAppInfo, UFontHelper, UCtrlArranger, UMessageBox, UStrUtils, USystemInfo, - UUserDetails, UUserDetailsPersist, Web.URegistrar; - - -{$R *.dfm} - -const - // Index of wizard pages - cIntroPageIdx = 0; - cAboutUserPageIdx = 1; - cSubmitPageIdx = 2; - cFinishPageIdx = 3; - -resourcestring - // Pages headings - sIntroHeading = 'Register CodeSnip'; - sAboutUserHeading = 'About you'; - sSubmitHeading = 'Submit registration'; - sFinishHeading = 'Registration complete'; - // Submit button caption - sSubmitBtnCaption = '&Submit'; - // Error messages - sErrNameRequired = 'You need to provide your name or a nickname.'; - - -{ TRegistrationDlg } - -procedure TRegistrationDlg.ArrangeForm; - {Vertically arranges controls as required and sizes the tab sheets to be able - to display the longest page. - } -begin - // set heights of all labels with AutoSize = False - TCtrlArranger.SetLabelHeights(Self); - - // tsIntro tabsheet - lblIntroExplain.Top := TCtrlArranger.BottomOf(lblIntro, 8); - lblInstructions.Top := TCtrlArranger.BottomOf(lblIntroExplain, 8); - - // tsAboutUser tabsheet - edName.Top := TCtrlArranger.BottomOf(lblName, 8); - - // tsSubmit tabsheet - edReport.Top := TCtrlArranger.BottomOf(lblReport, 8); - lblSubmit.Top := TCtrlArranger.BottomOf(edReport, 8); - - // tsFinish tabsheet - lblRegCode.Top := TCtrlArranger.BottomOf(lblThanks, 8); - edRegCode.Top := TCtrlArranger.BottomOf(lblRegCode, 4); - - // set required height - pnlBody.ClientHeight := TCtrlArranger.MaxContainerHeight( - [tsAboutUser, tsFinish, tsIntro, tsSubmit] - ) + pnlBody.ClientHeight - tsAboutUser.Height; - - // Arrange inherited controls and size the form - inherited; -end; - -procedure TRegistrationDlg.BeginPage(const PageIdx: Integer); - {Performs required initialisation when a page is displayed. - @param PageIdx [in] Index page to be initialised. - } -begin - inherited; - case PageIdx of - cSubmitPageIdx: - // Create data to be sent to website ready for submission - BuildSubmission(edReport.Lines); - cAboutUserPageIdx: - // Focus first control on page - edName.SetFocus; - end; -end; - -procedure TRegistrationDlg.BuildSubmission(const Report: TStrings); - {Builds registration submission as list of values in name=value format. - @param Report [in] Stores submission on completion. - } -begin - Report.Clear; - Report.Values['ProgId'] := TAppInfo.ProgramID; - Report.Values['ProgName'] := TAppInfo.ProgramName; - Report.Values['ProgVer'] := TAppInfo.ProgramReleaseVersion; - Report.Values['ProgKey'] := TAppInfo.ProgramKey; - Report.Values['UserName'] := StrTrim(edName.Text); - Report.Values['OSDesc'] := - Format( - '%0:s. IE Version %1:d.', [TOSInfo.Description, TIEInfo.MajorVersion] - ); -end; - -procedure TRegistrationDlg.ConfigForm; - {Sets font styles where necessary and initialises HTML frames. - } -begin - inherited; - TFontHelper.SetDefaultMonoFont(edRegCode.Font); -end; - -procedure TRegistrationDlg.DoRegistration; - {Registers program and displays registration code. - } -var - UserDetails: TUserDetails; // information about user -begin - Screen.Cursor := crHourglass; - try - // register with server - edRegCode.Text := RegisterWithWebServer; - // record registration & user details - UserDetails := TUserDetails.Create(StrTrim(edName.Text), ''); - TAppInfo.RegisterProgram(edRegCode.Text, UserDetails.Name); - TUserDetailsPersist.Update(UserDetails); - fRegistered := True; - finally - Screen.Cursor := crDefault; - end; -end; - -class function TRegistrationDlg.Execute(const Owner: TComponent): Boolean; - {Displays dialog box. - @param Owner [in] Component that owns this dialog. - @return True if program was registered, false otherwise. - } -begin - with InternalCreate(Owner) do - try - ShowModal; - Result := fRegistered; - finally - Free; - end; -end; - -function TRegistrationDlg.HeadingText(const PageIdx: Integer): string; - {Gets text of heading of a wizard page. - @param PageIdx [in] Index of page for which heading is required. - @return Heading text. - } -const - // Map of page indexes to page heading - cPageHeadings: array[0..3] of string = ( - sIntroHeading, sAboutUserHeading, sSubmitHeading, sFinishHeading - ); -begin - Result := cPageHeadings[PageIdx]; -end; - -procedure TRegistrationDlg.InitForm; - {Initialises controls. - } -var - UserDetails: TUserDetails; // any known information about user -begin - inherited; - // Use user name if known - UserDetails := TUserDetailsPersist.Load; - edName.Text := UserDetails.Name; -end; - -procedure TRegistrationDlg.MoveForward(const PageIdx: Integer; - var CanMove: Boolean); - {Performs required processing on moving forward from pages. - @param PageIdx [in] Index of page we are leaving. - @param CanMove [in/out] Flag set to indicate whether can leave page. - Defaults to true. - } -begin - inherited; - case PageIdx of - cAboutUserPageIdx: - CanMove := ValidateUserInfo; - cSubmitPageIdx: - DoRegistration; - end; -end; - -function TRegistrationDlg.RegisterWithWebServer: string; - {Gathers required registration data, sends to web server and gets registration - code from it. - @return Registration code. - @except EWebError exception raised if there is a failure in interaction - with web service. - } -var - Reg: TRegistrar; // object that communicates with web server - PostData: TStringList; // list of data items to be sent to web server -begin - PostData := nil; - Reg := TRegistrar.Create; - try - PostData := TStringList.Create; - BuildSubmission(PostData); - Result := Reg.Submit(PostData); // raises exception on fail result - finally - FreeAndNil(PostData); - FreeAndNil(Reg); - end; -end; - -procedure TRegistrationDlg.UpdateButtons(const PageIdx: Integer); - {Updates wizard buttons depending on page and state. - @param PageIdx [in] Index of current page. - } -begin - inherited; - if PageIdx = cSubmitPageIdx then - btnNext.Caption := sSubmitBtnCaption; - btnCancel.Enabled := PageIdx <> cFinishPageIdx; -end; - -function TRegistrationDlg.ValidateUserInfo: Boolean; - {Validates data entered by user. Displays any error messages. - @return True if data valid, false if not. - } -begin - Result := True; - if StrTrim(edName.Text) = '' then - begin - Result := False; - TMessageBox.Error(Self, sErrNameRequired); - end; -end; - -end. - diff --git a/Src/FmRenameCategoryDlg.pas b/Src/FmRenameCategoryDlg.pas index 9bf1230fa..039d6905a 100644 --- a/Src/FmRenameCategoryDlg.pas +++ b/Src/FmRenameCategoryDlg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2009-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2009-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a dialogue box that permits user to select and rename a user * defined category. @@ -185,14 +182,16 @@ class function TRenameCategoryDlg.Execute(AOwner: TComponent; @param AOwner [in] Component that owns dialog box. @param CatList [in] List of categories available for renaming. } +var + Dlg: TRenameCategoryDlg; begin - with InternalCreate(AOwner) do - try - fCategories := CatList; - Result := ShowModal = mrOK; - finally - Free; - end; + Dlg := InternalCreate(AOwner); + try + Dlg.fCategories := CatList; + Result := Dlg.ShowModal = mrOK; + finally + Dlg.Free; + end; end; procedure TRenameCategoryDlg.RenameCategory(const Category: TCategory; diff --git a/Src/FmSWAGImportDlg.dfm b/Src/FmSWAGImportDlg.dfm index 552a1be68..e93311a78 100644 --- a/Src/FmSWAGImportDlg.dfm +++ b/Src/FmSWAGImportDlg.dfm @@ -4,7 +4,7 @@ inherited SWAGImportDlg: TSWAGImportDlg ClientHeight = 505 ClientWidth = 687 ExplicitWidth = 693 - ExplicitHeight = 533 + ExplicitHeight = 534 PixelsPerInch = 96 TextHeight = 13 inherited pnlBody: TPanel @@ -19,34 +19,30 @@ inherited SWAGImportDlg: TSWAGImportDlg inherited pcWizard: TPageControl Width = 671 Height = 456 - ActivePage = tsIntro + ActivePage = tsFinish ExplicitWidth = 671 ExplicitHeight = 456 object tsIntro: TTabSheet Caption = 'tsIntro' TabVisible = False - inline frmIntro: TFixedHTMLDlgFrame + inline frmIntro: THTMLTpltDlgFrame Left = 0 Top = 0 Width = 663 - Height = 446 + Height = 236 Align = alTop TabOrder = 0 TabStop = True ExplicitWidth = 663 - ExplicitHeight = 446 inherited pnlBrowser: TPanel Width = 663 - Height = 446 ExplicitWidth = 663 - ExplicitHeight = 446 inherited wbBrowser: TWebBrowser Width = 663 - Height = 446 - ExplicitWidth = 663 - ExplicitHeight = 446 + ExplicitWidth = 320 + ExplicitHeight = 240 ControlData = { - 4C00000086440000182E00000000000000000000000000000000000000000000 + 4C00000086440000641800000000000000000000000000000000000000000000 000000004C000000000000000000000001000000E0D057007335CF11AE690800 2B2E126208000000000000004C0000000114020000000000C000000000000046 8000000000000000000000000000000000000000000000000000000000000000 @@ -55,6 +51,57 @@ inherited SWAGImportDlg: TSWAGImportDlg end end end + object tsFolder: TTabSheet + Caption = 'tsFolder' + ImageIndex = 4 + TabVisible = False + object lblFolder: TLabel + Left = 0 + Top = 8 + Width = 383 + Height = 13 + Caption = + 'Enter the SWAG database download &folder - you need the '#39'swag'#39' s' + + 'ub-directory:' + FocusControl = edPath + end + object lblFolderPageInfo2: TLabel + Left = 0 + Top = 72 + Width = 267 + Height = 13 + Caption = 'Click the Next button to choose which packets to import' + end + object lblFolderPageInfo1: TLabel + Left = 0 + Top = 26 + Width = 208 + Height = 13 + Caption = 'Click the ... button to browse for the folder' + FocusControl = edPath + end + object edPath: TEdit + Left = 3 + Top = 45 + Width = 484 + Height = 21 + TabOrder = 0 + end + object btnBrowse: TButton + Left = 493 + Top = 45 + Width = 27 + Height = 21 + Action = actBrowse + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Tahoma' + Font.Style = [fsBold] + ParentFont = False + TabOrder = 1 + end + end object tsCategories: TTabSheet Caption = 'tsCategories' ImageIndex = 1 @@ -74,20 +121,26 @@ inherited SWAGImportDlg: TSWAGImportDlg Height = 36 AutoSize = False Caption = - 'Select a category from the list on the left and click "Show Snip' + - 'pets In Category" (or double click the category) to display a li' + - 'st of its snippets in the right hand list. Tick the snippets you' + - ' want to import. Repeat with as many categories as you wish. Whe' + - 'n you are ready to import click "Next".' + 'Select a category from the list on the left and click "Show Pack' + + 'ets In Category" (or double click the category) to display a lis' + + 't of its packets in the right hand list. Tick the packet(s) you ' + + 'want to import. Repeat with as many categories as you wish. When' + + ' you are ready to import click "Next".' WordWrap = True end - object lblSelectSnippets: TLabel + object lblSelectPackets: TLabel Left = 256 Top = 45 - Width = 119 + Width = 116 + Height = 13 + Caption = '&Select required packets:' + FocusControl = clbSelectPackets + end + object lblVersionNumber: TLabel + Left = 2 + Top = 427 + Width = 3 Height = 13 - Caption = '&Select required snippets:' - FocusControl = clbSelectSnippets end object lbCategories: TListBox Left = 0 @@ -99,16 +152,16 @@ inherited SWAGImportDlg: TSWAGImportDlg OnDblClick = lbCategoriesDblClick OnKeyDown = lbCategoriesKeyDown end - object clbSelectSnippets: TCheckListBox - Left = 256 + object clbSelectPackets: TCheckListBox + Left = 240 Top = 64 Width = 404 Height = 321 - OnClickCheck = clbSelectSnippetsClickCheck + OnClickCheck = clbSelectPacketsClickCheck ItemHeight = 13 TabOrder = 2 - OnDblClick = clbSelectSnippetsDblClick - OnKeyDown = clbSelectSnippetsKeyDown + OnDblClick = clbSelectPacketsDblClick + OnKeyDown = clbSelectPacketsKeyDown end object btnDisplayCategory: TButton Left = 30 @@ -118,12 +171,12 @@ inherited SWAGImportDlg: TSWAGImportDlg Action = actDisplayCategory TabOrder = 1 end - object btnDisplaySnippet: TButton + object btnDisplayPacket: TButton Left = 280 Top = 391 Width = 185 Height = 25 - Action = actDisplaySnippet + Action = actDisplayPacket TabOrder = 3 end end @@ -138,11 +191,12 @@ inherited SWAGImportDlg: TSWAGImportDlg Height = 36 AutoSize = False Caption = - 'You have chosen to import the following SWAG snippets. They will' + - ' be imported with the given Display Names and Unique IDs. You ca' + - 'n change these if you wish using the Snippets Editor. To make ch' + - 'anges go back to the previous page. When you are ready to import' + - ' the snippets click "Import". This step can'#39't be undone.' + 'You have chosen to import the following SWAG packets as CodeSnip' + + ' snippets. They will be imported with the given Display Names an' + + 'd Packet IDs. You can change these if you wish using the Snippet' + + 's Editor. To make changes go back to the previous page. When you' + + ' are ready to import the packets click "Import". This step can'#39't' + + ' be undone.' WordWrap = True end object lvImports: TListView @@ -152,11 +206,11 @@ inherited SWAGImportDlg: TSWAGImportDlg Height = 317 Columns = < item - Caption = 'Snippet Title (Display Name)' + Caption = 'Packet Title '#8594' Snippet Display Name' Width = 400 end item - Caption = 'Snippet ID (Name)' + Caption = 'Snippet Name from SWAG ID' Width = 200 end> ColumnClick = False @@ -179,6 +233,8 @@ inherited SWAGImportDlg: TSWAGImportDlg Width = 663 Height = 446 Align = alTop + DoubleBuffered = False + ParentDoubleBuffered = False TabOrder = 0 TabStop = True ExplicitWidth = 663 @@ -209,14 +265,18 @@ inherited SWAGImportDlg: TSWAGImportDlg Left = 336 Top = 256 object actDisplayCategory: TAction - Caption = 'S&how Snippets In Category' + Caption = 'S&how Packets In Category' OnExecute = actDisplayCategoryExecute OnUpdate = actDisplayCategoryUpdate end - object actDisplaySnippet: TAction - Caption = '&Preview Selected Snippet...' - OnExecute = actDisplaySnippetExecute - OnUpdate = actDisplaySnippetUpdate + object actDisplayPacket: TAction + Caption = '&Preview Selected Packet...' + OnExecute = actDisplayPacketExecute + OnUpdate = actDisplayPacketUpdate + end + object actBrowse: TAction + Caption = '...' + OnExecute = actBrowseExecute end end end diff --git a/Src/FmSWAGImportDlg.pas b/Src/FmSWAGImportDlg.pas index 92ff527c8..ffe252edc 100644 --- a/Src/FmSWAGImportDlg.pas +++ b/Src/FmSWAGImportDlg.pas @@ -1,15 +1,13 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2013-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a wizard dialogue box that lets the user select and import - * snippets from the DelphiDabbler implementation of the SWAG Pascal archive. + * packets from the DelphiDabbler implementation of the SWAG Pascal archive as + * new user-defined CodeSnip snippets. } @@ -36,6 +34,7 @@ interface FrHTMLTpltDlg, UBaseObjects, UContainers, + UCSSBuilder, SWAG.UCommon, SWAG.UImporter, SWAG.UReader, ActnList; @@ -43,17 +42,16 @@ interface type /// <summary>Class that implements a wizard dialogue box that lets the user - /// select and import snippets from the DelphiDabbler implementation of the - /// SWAG Pascal archive.</summary> + /// select and import packets from the DelphiDabbler implementation of the + /// SWAG Pascal archive as new user-defined CodeSnip snippets.</summary> TSWAGImportDlg = class(TWizardDlg, INoPublicConstruct) tsIntro: TTabSheet; tsCategories: TTabSheet; lblCategories: TLabel; - frmIntro: TFixedHTMLDlgFrame; lbCategories: TListBox; lblCategoriesDesc: TLabel; - lblSelectSnippets: TLabel; - clbSelectSnippets: TCheckListBox; + lblSelectPackets: TLabel; + clbSelectPackets: TCheckListBox; tsUpdate: TTabSheet; lvImports: TListView; lblUpdateDesc: TLabel; @@ -62,95 +60,131 @@ TSWAGImportDlg = class(TWizardDlg, INoPublicConstruct) btnDisplayCategory: TButton; alWizard: TActionList; actDisplayCategory: TAction; - actDisplaySnippet: TAction; - btnDisplaySnippet: TButton; - /// <summary>Handles clicks on the check boxes next to snippets in the - /// snippet selection list box by selecting and deselecting snippets for + actDisplayPacket: TAction; + btnDisplayPacket: TButton; + tsFolder: TTabSheet; + lblFolder: TLabel; + edPath: TEdit; + lblFolderPageInfo2: TLabel; + btnBrowse: TButton; + actBrowse: TAction; + frmIntro: THTMLTpltDlgFrame; + lblVersionNumber: TLabel; + lblFolderPageInfo1: TLabel; + /// <summary>Handles clicks on the check boxes next to packets in the + /// packet selection list box by selecting and deselecting packets for /// inclusion in the import.</summary> - procedure clbSelectSnippetsClickCheck(Sender: TObject); - /// <summary>Handles double clicks on snippets in the snippet selection - /// list box by causing the selected snippet to be previewed.</summary> - procedure clbSelectSnippetsDblClick(Sender: TObject); - /// <summary>Handles key down events on the snippet selection list box by - /// causing the selected snippet to be previewed when the user presses + procedure clbSelectPacketsClickCheck(Sender: TObject); + /// <summary>Handles double clicks on packets in the packet selection + /// list box by causing the selected packet to be previewed.</summary> + procedure clbSelectPacketsDblClick(Sender: TObject); + /// <summary>Handles key down events on the packet selection list box by + /// causing the selected packet to be previewed when the user presses /// Enter.</summary> - procedure clbSelectSnippetsKeyDown(Sender: TObject; var Key: Word; + procedure clbSelectPacketsKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); /// <summary>Handles double clicks on categories in the SWAG categories - /// list box by displaying the category's snippets in the snippet selection + /// list box by displaying the category's packets in the packet selection /// list box.</summary> procedure lbCategoriesDblClick(Sender: TObject); /// <summary>Handles key down events on categories in the SWAG categories - /// list box by displaying the category's snippets in the snippet selection + /// list box by displaying the category's packets in the packet selection /// list box when the user presses enter.</summary> procedure lbCategoriesKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); - procedure actDisplayCategoryUpdate(Sender: TObject); + /// <summary>Executes action to display a Browse for Folders dialogue box + /// and store the chosen folder in an edit box.</summary> + procedure actBrowseExecute(Sender: TObject); + /// <summary>Executes action to display the packets in the selected + /// category.</summary> procedure actDisplayCategoryExecute(Sender: TObject); - procedure actDisplaySnippetExecute(Sender: TObject); - procedure actDisplaySnippetUpdate(Sender: TObject); + /// <summary>Updates enabled state of display category action.</summary> + procedure actDisplayCategoryUpdate(Sender: TObject); + /// <summary>Executes action to preview the selected packet.</summary> + procedure actDisplayPacketExecute(Sender: TObject); + /// <summary>Updates enabled state of display packet category.</summary> + procedure actDisplayPacketUpdate(Sender: TObject); strict private const /// <summary>Index of introductory page in wizard.</summary> cIntroPage = 0; - /// <summary>Index of snippet selection page in wizard.</summary> - cSelectionPage = 1; + /// <summary>Index of SWAG database folder selection page in wizard. + /// </summary> + cChooseFolderPage = 1; + /// <summary>Index of packet selection page in wizard.</summary> + cSelectionPage = 2; /// <summary>Index of import page in wizard.</summary> - cUpdatePage = 2; + cUpdatePage = 3; /// <summary>Index of finish page in wizard.</summary> - cFinishPage = 3; + cFinishPage = 4; var + fPrevSWAGDir: string; /// <summary>Object that provides cached access to the SWAG database. /// </summary> fSWAGReader: TSWAGReader; /// <summary>List of all categories in SWAG database, sorted by title. /// </summary> fSortedCategories: TSortedList<TSWAGCategory>; - /// <summary>List of snippets in the current category, sorted by title. + /// <summary>List of packets in the current category, sorted by title. /// </summary> - fCurrentCatSnippets: TSortedList<TSWAGSnippet>; - /// <summary>List of snippets selected for import, sorted by ID. + fCurrentCatPackets: TSortedList<TSWAGPacket>; + /// <summary>List of packets selected for import, sorted by ID. /// </summary> - fSelectedSnippets: TSortedList<TSWAGSnippet>; - /// <summary>Object that imports selected SWAG snippets into CodeSnip's + fSelectedPackets: TSortedList<TSWAGPacket>; + /// <summary>Object that imports selected SWAG packets into CodeSnip's /// user database.</summary> fImporter: TSWAGImporter; /// <summary>ID of currently selected category.</summary> - /// <remarks>Set to empty string if no category is selected.</remarks> - fCurrentCatID: string; + /// <remarks>Set to zero if no category is selected.</remarks> + fCurrentCatID: Cardinal; + /// <summary>Retrieves import directory name from edit control where it is + /// entered.</summary> + function GetDirNameFromEditCtrl: string; /// <summary>Validates entries on the wizard page identified by the given /// page index.</summary> procedure ValidatePage(const PageIdx: Integer); - /// <summary>Displays snippets selected for import in list view on Update + /// <summary>Handles HTML template frame's OnBuildCSS event. Adds + /// additional CSS required by HTML in this form.</summary> + /// <param name="Sender">TObject [in] Reference to object triggering event. + /// </param> + /// <param name="CSSBuilder">TCSSBuilder [in] Object used to construct the + /// CSS.</param> + procedure BuildCSS(Sender: TObject; const CSSBuilder: TCSSBuilder); + /// <summary>Displays packets selected for import in list view on Update /// page.</summary> procedure PopulateImportsLV; + /// <summary>Initialises SWAG database XML file for reading before + /// Selection page.</summary> + /// <remarks>May display a wait dialogue box while initialising the + /// database.</remarks> + procedure BeforeSelectionPage; /// <summary>Initialises Selection page by populating its list of SWAG /// categories, if necessary.</summary> - /// <remarks>May display a wait dialogue box if the categories have to be - /// downloaded from the SWAG database.</remarks> + /// <remarks>May display a wait dialogue box while loading the categories. + /// </remarks> procedure InitSelectionPage; /// <summary>Initialises Update page by retrieving all the selected - /// snippets, preparing them for import and displaying them in the page's + /// packets, preparing them for import and displaying them in the page's /// list view.</summary> - /// <remarks>May display a wait dialogue box if any of the snippets to be - /// imported have to be downloaded from the SWAG database.</remarks> + /// <remarks>May display a wait dialogue box while loading the packets. + /// </remarks> procedure InitUpdatePage; - /// <summary>Gets the snippets contained is any selected category and - /// displays them in the snippet selection list box on the Selection page. + /// <summary>Gets the packets contained in any selected category and + /// displays them in the packet selection list box on the Selection page. /// </summary> - /// <remarks>May display a wait dialogue box if the snippets have to be - /// downloaded from the SWAG database.</remarks> - procedure DisplaySnippetsForCategory; + /// <remarks>May display a wait dialogue box while the packets are being + /// retrieved.</remarks> + procedure DisplayPacketsForCategory; /// <summary>Creates and displays a preview of the currently selected - /// snippet in the Selection page's snippet selection list box.</summary> - /// <remarks>May display a wait dialogue box if the selected snippet has to - /// be downloaded from the SWAG database.</remarks> - procedure PreviewSelectedSnippet; - /// <summary>Gets the complete information for each snippet selected for + /// packet in the Selection page's packet selection list box.</summary> + /// <remarks>May display a wait dialogue box while the selected packet is + /// retrieved.</remarks> + procedure PreviewSelectedPacket; + /// <summary>Gets the complete information for each packet selected for /// import and stores in the given list.</summary> - procedure GetImportSnippets(const SnipList: TList<TSWAGSnippet>); - /// <summary>Performs the import of the selected snippets into CodeSnip's - /// user database.</summary> + procedure GetImportPackets(const PacketList: TList<TSWAGPacket>); + /// <summary>Performs the import of the selected packets as into CodeSnip's + /// user database as new user-defined snippets.</summary> /// <remarks>Displays a wait dialogue box while the import is proceeding. /// </remarks> procedure UpdateDatabase; @@ -207,16 +241,23 @@ implementation // Delphi Generics.Defaults, Windows, + IOUtils, // Project FmPreviewDlg, FmWaitDlg, + SWAG.UVersion, + UBrowseForFolderDlg, + UColours, UConsts, + UCSSUtils, UCtrlArranger, UEncodings, UExceptions, UHTMLTemplate, UMessageBox, UStrUtils, + UUrl, + UVersionInfo, UWaitForThreadUI; {$R *.dfm} @@ -224,9 +265,28 @@ implementation { TSWAGImportDlg } +procedure TSWAGImportDlg.actBrowseExecute(Sender: TObject); +var + Dlg: TBrowseForFolderDlg; // browse for folder standard dialogue box +resourcestring + sDlgTitle = 'Choose SWAG database download directory'; + sDlgHeading = 'Choose an empty directory or create a new one'; +begin + Dlg := TBrowseForFolderDlg.Create(nil); + try + Dlg.Title := sDlgTitle; + Dlg.Headline := sDlgHeading; + Dlg.MakeFolderBtnVisible := True; + if Dlg.Execute then + edPath.Text := Dlg.FolderName; + finally + Dlg.Free; + end; +end; + procedure TSWAGImportDlg.actDisplayCategoryExecute(Sender: TObject); begin - DisplaySnippetsForCategory; + DisplayPacketsForCategory; end; procedure TSWAGImportDlg.actDisplayCategoryUpdate(Sender: TObject); @@ -234,14 +294,14 @@ procedure TSWAGImportDlg.actDisplayCategoryUpdate(Sender: TObject); actDisplayCategory.Enabled := lbCategories.ItemIndex >= 0; end; -procedure TSWAGImportDlg.actDisplaySnippetExecute(Sender: TObject); +procedure TSWAGImportDlg.actDisplayPacketExecute(Sender: TObject); begin - PreviewSelectedSnippet; + PreviewSelectedPacket; end; -procedure TSWAGImportDlg.actDisplaySnippetUpdate(Sender: TObject); +procedure TSWAGImportDlg.actDisplayPacketUpdate(Sender: TObject); begin - actDisplaySnippet.Enabled := clbSelectSnippets.ItemIndex >= 0; + actDisplayPacket.Enabled := clbSelectPackets.ItemIndex >= 0; end; procedure TSWAGImportDlg.ArrangeForm; @@ -252,26 +312,42 @@ procedure TSWAGImportDlg.ArrangeForm; // tsIntro frmIntro.Height := frmIntro.DocHeight; + // tsFolder + TCtrlArranger.AlignVCentres( + TCtrlArranger.BottomOf(lblFolder, 6), [edPath, btnBrowse] + ); + TCtrlArranger.AlignLefts( + [lblFolder, edPath, lblFolderPageInfo1, lblFolderPageInfo2], 0 + ); + TCtrlArranger.MoveToRightOf(edPath, btnBrowse, 8); + lblFolderPageInfo1.Top := TCtrlArranger.BottomOf([edPath, btnBrowse], 12); + lblFolderPageInfo2.Top := TCtrlArranger.BottomOf(lblFolderPageInfo1, 8); + // tsCategories lblCategoriesDesc.Width := tsCategories.ClientWidth; lblCategoriesDesc.Top := 3; TCtrlArranger.AlignLefts( - [lblCategoriesDesc, lblCategories, lbCategories], 0 + [lblCategoriesDesc, lblCategories, lbCategories, lblVersionNumber], 0 ); TCtrlArranger.AlignTops( - [lblCategories, lblSelectSnippets], + [lblCategories, lblSelectPackets], TCtrlArranger.BottomOf(lblCategoriesDesc, 12) ); TCtrlArranger.AlignTops( - [lbCategories, clbSelectSnippets], - TCtrlArranger.BottomOf([lblCategories, lblSelectSnippets], 6) + [lbCategories, clbSelectPackets], + TCtrlArranger.BottomOf([lblCategories, lblSelectPackets], 6) ); TCtrlArranger.AlignTops( - [btnDisplayCategory, btnDisplaySnippet], - TCtrlArranger.BottomOf([lbCategories, clbSelectSnippets], 8) + [btnDisplayCategory, btnDisplayPacket], + TCtrlArranger.BottomOf([lbCategories, clbSelectPackets], 8) + ); + TCtrlArranger.MoveBelow( + [btnDisplayCategory, btnDisplayPacket], + lblVersionNumber, + 8 ); TCtrlArranger.AlignHCentresTo([lbCategories], [btnDisplayCategory]); - TCtrlArranger.AlignHCentresTo([clbSelectSnippets], [btnDisplaySnippet]); + TCtrlArranger.AlignHCentresTo([clbSelectPackets], [btnDisplayPacket]); // tsUpdate lblUpdateDesc.Width := tsUpdate.ClientWidth; @@ -285,8 +361,11 @@ procedure TSWAGImportDlg.ArrangeForm; // set required height pnlBody.ClientHeight := TCtrlArranger.MaxContainerHeight( - [tsIntro, tsCategories, tsUpdate, tsFinish] + [tsIntro, tsFolder, tsCategories, tsUpdate, tsFinish] ) + pnlBody.ClientHeight - tsFinish.Height; + pnlBody.ClientWidth := TCtrlArranger.MaxContainerWidth( + [tsIntro, tsFolder, tsCategories, tsUpdate, tsFinish] + ) + pnlBody.ClientWidth - tsIntro.Width; // re-size controls to fit height lvImports.Height := tsUpdate.ClientHeight - lvImports.Top; @@ -294,6 +373,42 @@ procedure TSWAGImportDlg.ArrangeForm; inherited; end; +procedure TSWAGImportDlg.BeforeSelectionPage; +resourcestring + sDefaultWaitMsg = 'Accessing database...'; + sWaitMsg = 'Initialising SWAG database...'; +var + WaitProc: TProc; +begin + if StrSameText(fPrevSWAGDir, GetDirNameFromEditCtrl) then + Exit; + + lbCategories.Clear; + clbSelectPackets.Clear; + + WaitProc := procedure + begin + Application.ProcessMessages; + fPrevSWAGDir := GetDirNameFromEditCtrl; + FreeAndNil(fSWAGReader); + fSWAGReader := TSWAGReader.Create( + GetDirNameFromEditCtrl, + procedure (CallProc: TProc) + begin + WaitWrapper(Self, CallProc, sDefaultWaitMsg); + end + ); + end; + + TWaitForThreadUI.Run( // this blocks until thread completes + WaitProc, + False, + TWaitDlg.CreateAutoFree(Self, sWaitMsg), + 0, + 500 + ); +end; + procedure TSWAGImportDlg.BeginPage(const PageIdx: Integer); begin case PageIdx of @@ -302,43 +417,60 @@ procedure TSWAGImportDlg.BeginPage(const PageIdx: Integer); end; end; -procedure TSWAGImportDlg.clbSelectSnippetsClickCheck(Sender: TObject); +procedure TSWAGImportDlg.BuildCSS(Sender: TObject; + const CSSBuilder: TCSSBuilder); +begin + inherited; + // Set body text spacing + CSSBuilder.Selectors['body'] + .AddProperty(TCSS.LineHeightProp(120)); + // Create .framed border style + CSSBuilder.AddSelector('.framed') + .AddProperty(TCSS.BorderProp(cssAll, 1, cbsSolid, clBorder)) + .AddProperty(TCSS.PaddingProp(0, 4, 4, 4)) + .AddProperty(TCSS.MarginProp(cssTop, 4)); +end; + +procedure TSWAGImportDlg.clbSelectPacketsClickCheck(Sender: TObject); var SelIdx: Integer; DelIdx: Integer; begin - SelIdx := clbSelectSnippets.ItemIndex; + SelIdx := clbSelectPackets.ItemIndex; if SelIdx = -1 then Exit; - if clbSelectSnippets.Checked[SelIdx] then + if clbSelectPackets.Checked[SelIdx] then begin - if not fSelectedSnippets.Contains(fCurrentCatSnippets[SelIdx]) then - fSelectedSnippets.Add(fCurrentCatSnippets[SelIdx]); + if not fSelectedPackets.Contains(fCurrentCatPackets[SelIdx]) then + fSelectedPackets.Add(fCurrentCatPackets[SelIdx]); end else begin - DelIdx := fSelectedSnippets.IndexOf(fCurrentCatSnippets[SelIdx]); + DelIdx := fSelectedPackets.IndexOf(fCurrentCatPackets[SelIdx]); if DelIdx >= 0 then - fSelectedSnippets.Delete(DelIdx); + fSelectedPackets.Delete(DelIdx); end; end; -procedure TSWAGImportDlg.clbSelectSnippetsDblClick(Sender: TObject); +procedure TSWAGImportDlg.clbSelectPacketsDblClick(Sender: TObject); begin - PreviewSelectedSnippet; + PreviewSelectedPacket; end; -procedure TSWAGImportDlg.clbSelectSnippetsKeyDown(Sender: TObject; +procedure TSWAGImportDlg.clbSelectPacketsKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); begin if Key = VK_RETURN then - PreviewSelectedSnippet; + PreviewSelectedPacket; end; procedure TSWAGImportDlg.ConfigForm; +resourcestring + sVersions = 'v%0:s to v%1:s'; begin inherited; pcWizard.ActivePage := tsFinish; + frmOutro.OnBuildCSS := BuildCSS; frmOutro.Initialise( 'dlg-swag-import-outro-tplt.html', procedure (Tplt: THTMLTemplate) @@ -350,112 +482,147 @@ procedure TSWAGImportDlg.ConfigForm; end ); pcWizard.ActivePage := tsIntro; - frmIntro.Initialise('dlg-swag-import-intro.html'); + frmIntro.OnBuildCSS := BuildCSS; + frmIntro.Initialise( + 'dlg-swag-import-intro-tplt.html', + procedure (Tplt: THTMLTemplate) + begin + Tplt.ResolvePlaceholderText( + 'SWAGReleaseURL', + TURL.SWAGReleases + ); + Tplt.ResolvePlaceholderText( + 'SupportedSWAGVersions', + Format( + sVersions, + [ + string(TSWAGVersion.LowestSupportedVersion), + string(TSWAGVersion.LowestUnSupportedVersion) + ] + ) + ); + end + ); end; destructor TSWAGImportDlg.Destroy; begin fSWAGReader.Free; fImporter.Free; - fSelectedSnippets.Free; - fCurrentCatSnippets.Free; + fSelectedPackets.Free; + fCurrentCatPackets.Free; fSortedCategories.Free; inherited; end; -procedure TSWAGImportDlg.DisplaySnippetsForCategory; +procedure TSWAGImportDlg.DisplayPacketsForCategory; resourcestring - sSnippetListCaption = '&Select snippets from "%s"'; + sPacketListCaption = '&Select packets from "%s"'; var CatIdx: Integer; Idx: Integer; N: Integer; - Snippets: TList<TSWAGSnippet>; + Packets: TList<TSWAGPacket>; begin CatIdx := lbCategories.ItemIndex; if CatIdx = -1 then begin - fCurrentCatID := ''; + fCurrentCatID := 0; Exit; end; if fCurrentCatID = fSortedCategories[CatIdx].ID then // nothing to do if current category selected again Exit; fCurrentCatID := fSortedCategories[CatIdx].ID; - lblSelectSnippets.Caption := Format( - sSnippetListCaption, [fSortedCategories[CatIdx].Title] + lblSelectPackets.Caption := Format( + sPacketListCaption, + // double up ampersands to avoid being treated as accelerator characters + [StrReplace(fSortedCategories[CatIdx].Title, '&', '&&')] ); - Snippets := TList<TSWAGSnippet>.Create; + Packets := TList<TSWAGPacket>.Create; try - fSWAGReader.GetPartialSnippets(fCurrentCatID, Snippets); - clbSelectSnippets.Items.BeginUpdate; + fSWAGReader.GetPartialPackets(fCurrentCatID, Packets); + clbSelectPackets.Items.BeginUpdate; try - fCurrentCatSnippets.Clear; - clbSelectSnippets.Clear; - // We set fCurrentCatSnippets first because it is a sorted list which + fCurrentCatPackets.Clear; + clbSelectPackets.Clear; + // We set fCurrentCatPackets first because it is a sorted list which // means indices of new items added are not sequential, and we must have - // displayed title at same index in clbSelectSnippets as its snippet is in - // fCurrentCatSnippets. - fCurrentCatSnippets.AddRange(Snippets); - for Idx := 0 to Pred(fCurrentCatSnippets.Count) do + // displayed title at same index in clbSelectPackets as its packet is in + // fCurrentCatPackets. + fCurrentCatPackets.AddRange(Packets); + for Idx := 0 to Pred(fCurrentCatPackets.Count) do begin - N := clbSelectSnippets.Items.Add(fCurrentCatSnippets[Idx].Title); + N := clbSelectPackets.Items.Add(fCurrentCatPackets[Idx].Title); Assert(Idx = N, 'Idx <> N'); - clbSelectSnippets.Checked[Idx] := fSelectedSnippets.Contains( - fCurrentCatSnippets[Idx] + clbSelectPackets.Checked[Idx] := fSelectedPackets.Contains( + fCurrentCatPackets[Idx] ); end; finally - clbSelectSnippets.Items.EndUpdate; + clbSelectPackets.Items.EndUpdate; end; finally - Snippets.Free; + Packets.Free; end; end; class function TSWAGImportDlg.Execute(const AOwner: TComponent): Boolean; +var + Dlg: TSWAGImportDlg; begin - with InternalCreate(AOwner) do - try - Result := ShowModal = mrOK; - finally - Free; - end; + Dlg := InternalCreate(AOwner); + try + Result := Dlg.ShowModal = mrOK; + finally + Dlg.Free; + end; +end; + +function TSWAGImportDlg.GetDirNameFromEditCtrl: string; +begin + Result := StrTrim(edPath.Text); end; -procedure TSWAGImportDlg.GetImportSnippets(const SnipList: TList<TSWAGSnippet>); +procedure TSWAGImportDlg.GetImportPackets(const PacketList: TList<TSWAGPacket>); var - SnipIDs: TList<Cardinal>; - PartialSnippet: TSWAGSnippet; + PacketIDs: TList<Cardinal>; + PartialPacket: TSWAGPacket; resourcestring - sWaitMsg = 'Downloading Snippets From SWAG...'; + sWaitMsg = 'Retrieving packets...'; begin - SnipIDs := TList<Cardinal>.Create; + PacketIDs := TList<Cardinal>.Create; try - for PartialSnippet in fSelectedSnippets do - SnipIDs.Add(PartialSnippet.ID); - fSWAGReader.GetCompleteSnippets( - SnipIDs, - SnipList, + for PartialPacket in fSelectedPackets do + PacketIDs.Add(PartialPacket.ID); + fSWAGReader.GetCompletePackets( + PacketIDs, + PacketList, + procedure + begin + Application.ProcessMessages; + end, procedure (CallProc: TProc) begin WaitWrapper(Self, CallProc, sWaitMsg); end ); finally - SnipIDs.Free; + PacketIDs.Free; end; end; function TSWAGImportDlg.HeadingText(const PageIdx: Integer): string; resourcestring - sIntroPageHeading = 'Import snippets from SWAG'; - sSelectionPageHeading = 'Select required snippets'; + sIntroPageHeading = 'Import packets from SWAG as new snippets'; + sFolderPage = 'Select SWAG database download folder'; + sSelectionPageHeading = 'Select required packets'; sUpdatePage = 'Ready to import'; sFinishPage = 'Import complete'; begin case PageIdx of cIntroPage: Result := sIntroPageHeading; + cChooseFolderPage: Result := sFolderPage; cSelectionPage: Result := sSelectionPageHeading; cUpdatePage: Result := sUpdatePage; cFinishPage: Result := sFinishPage; @@ -466,9 +633,17 @@ procedure TSWAGImportDlg.InitSelectionPage; var Cats: TList<TSWAGCategory>; Idx: Integer; +resourcestring + sLblVersionNumberCaption = 'SWAG version %s'; begin + lblVersionNumber.Caption := Format( + sLblVersionNumberCaption, + [string(TSWAGVersion.GetVersion(GetDirNameFromEditCtrl))] + ); + Application.ProcessMessages; - if lbCategories.Count > 0 then + + if (lbCategories.Count > 0) then Exit; Cats := TList<TSWAGCategory>.Create; @@ -494,20 +669,20 @@ procedure TSWAGImportDlg.InitSelectionPage; procedure TSWAGImportDlg.InitUpdatePage; var - FullSnippets: TList<TSWAGSnippet>; - Snippet: TSWAGSnippet; + FullPackets: TList<TSWAGPacket>; + Packet: TSWAGPacket; resourcestring - sWaitMsg = 'Downloading Snippets From SWAG...'; + sWaitMsg = 'Retrieving packets...'; begin Application.ProcessMessages; - FullSnippets := TList<TSWAGSnippet>.Create; + FullPackets := TList<TSWAGPacket>.Create; try - GetImportSnippets(FullSnippets); + GetImportPackets(FullPackets); fImporter.Reset; - for Snippet in FullSnippets do - fImporter.IncludeSnippet(Snippet); + for Packet in FullPackets do + fImporter.IncludePacket(Packet); finally - FullSnippets.Free; + FullPackets.Free; end; PopulateImportsLV; end; @@ -527,46 +702,40 @@ constructor TSWAGImportDlg.InternalCreate(AOwner: TComponent); ); fSortedCategories.PermitDuplicates := True; - fCurrentCatSnippets := TSortedList<TSWAGSnippet>.Create( - TDelegatedComparer<TSWAGSnippet>.Create( - function (const Left, Right: TSWAGSnippet): Integer + fCurrentCatPackets := TSortedList<TSWAGPacket>.Create( + TDelegatedComparer<TSWAGPacket>.Create( + function (const Left, Right: TSWAGPacket): Integer begin Result := StrCompareStr(Left.Title, Right.Title); end ) ); - fCurrentCatSnippets.PermitDuplicates := True; + fCurrentCatPackets.PermitDuplicates := True; - fSelectedSnippets := TSortedList<TSWAGSnippet>.Create( - TDelegatedComparer<TSWAGSnippet>.Create( - function (const Left, Right: TSWAGSnippet): Integer + fSelectedPackets := TSortedList<TSWAGPacket>.Create( + TDelegatedComparer<TSWAGPacket>.Create( + function (const Left, Right: TSWAGPacket): Integer begin Result := Left.ID - Right.ID; end ) ); - fSelectedSnippets.PermitDuplicates := False; + fSelectedPackets.PermitDuplicates := False; fImporter := TSWAGImporter.Create; - fSWAGReader := TSWAGReader.Create( - procedure (CallProc: TProc) - begin - WaitWrapper(Self, CallProc, sDefaultWaitMsg); - end - ); end; procedure TSWAGImportDlg.lbCategoriesDblClick(Sender: TObject); begin - DisplaySnippetsForCategory; + DisplayPacketsForCategory; end; procedure TSWAGImportDlg.lbCategoriesKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); begin if Key = VK_RETURN then - DisplaySnippetsForCategory; + DisplayPacketsForCategory; end; procedure TSWAGImportDlg.MoveForward(const PageIdx: Integer; @@ -576,6 +745,7 @@ procedure TSWAGImportDlg.MoveForward(const PageIdx: Integer; try ValidatePage(PageIdx); case PageIdx of + cChooseFolderPage: BeforeSelectionPage; cUpdatePage: UpdateDatabase; end; CanMove := True; @@ -591,44 +761,44 @@ procedure TSWAGImportDlg.MoveForward(const PageIdx: Integer; procedure TSWAGImportDlg.PopulateImportsLV; var - Snippet: TSWAGSnippet; + Packet: TSWAGPacket; LI: TListItem; begin lvImports.Items.BeginUpdate; try lvImports.Clear; - for Snippet in fSelectedSnippets do + for Packet in fSelectedPackets do begin LI := lvImports.Items.Add; - LI.Caption := Snippet.Title; - LI.SubItems.Add(TSWAGImporter.MakeValidSnippetName(Snippet.ID)); + LI.Caption := Packet.Title; + LI.SubItems.Add(TSWAGImporter.MakeValidSnippetName(Packet.ID)); end; finally lvImports.Items.EndUpdate; end; end; -procedure TSWAGImportDlg.PreviewSelectedSnippet; +procedure TSWAGImportDlg.PreviewSelectedPacket; var - PartialSnippet: TSWAGSnippet; - FullSnippet: TSWAGSnippet; + PartialPacket: TSWAGPacket; + FullPacket: TSWAGPacket; SelIdx: Integer; Content: string; resourcestring - sWaitMsg = 'Downloading Snippet From SWAG...'; + sWaitMsg = 'Retrieving packet...'; sContentTplt = 'ID: %0:d' + EOL + - 'Category: "%1:s"' + EOL + + 'Category ID: %1:d' + EOL + 'File Name: "%2:s"' + EOL + 'Title: "%3:s"' + EOL + 'Author: "%4:s"' + EOL2 + 'Source Code:' + EOL + '%5:s' + EOL + '%6:s'; begin - SelIdx := clbSelectSnippets.ItemIndex; + SelIdx := clbSelectPackets.ItemIndex; if SelIdx = -1 then Exit; - PartialSnippet := fCurrentCatSnippets[SelIdx]; - FullSnippet := fSWAGReader.GetCompleteSnippet( - PartialSnippet.ID, + PartialPacket := fCurrentCatPackets[SelIdx]; + FullPacket := fSWAGReader.GetCompletePacket( + PartialPacket.ID, procedure (CallProc: TProc) begin WaitWrapper(Self, CallProc, sWaitMsg); @@ -637,13 +807,13 @@ procedure TSWAGImportDlg.PreviewSelectedSnippet; Content := Format( sContentTplt, [ - FullSnippet.ID, - FullSnippet.Category, - FullSnippet.FileName, - FullSnippet.Title, - FullSnippet.Author, - StringOfChar('-', 80), - StrWindowsLineBreaks(FullSnippet.SourceCode) + FullPacket.ID, + FullPacket.Category, + FullPacket.FileName, + FullPacket.Title, + FullPacket.Author, + StrOfChar('-', 80), + StrWindowsLineBreaks(FullPacket.SourceCode) ] ); TPreviewDlg.Execute( @@ -676,6 +846,8 @@ procedure TSWAGImportDlg.UpdateDatabase; Application.ProcessMessages; end; +resourcestring + sWaitMsg = 'Importing packets into database as new snippets...'; begin SetBtnVisibility(False); try @@ -684,13 +856,13 @@ procedure TSWAGImportDlg.UpdateDatabase; procedure begin fImporter.Import( - procedure (const Snippet: TSWAGSnippet) + procedure (const Packet: TSWAGPacket) begin Application.ProcessMessages; end ); end, - 'Importing Snippets Into Database...' + sWaitMsg ); finally SetBtnVisibility(True); @@ -698,15 +870,45 @@ procedure TSWAGImportDlg.UpdateDatabase; end; procedure TSWAGImportDlg.ValidatePage(const PageIdx: Integer); -resourcestring - sEmptySelection = 'You must select one or more snippets to import.'; + + procedure ValidateChooseFolderPage; + resourcestring + sNoFolder = 'Please enter the directory where you downloaded the SWAG ' + + 'database.'; + sBadFolder = 'Directory "%s" does not exist. Please specify a valid one.'; + sBadVersion = '%s.' + EOL2 + + 'Please specify a directory containing a supported version.'; + sCorrupt = 'Not a valid SWAG database (%s). ' + EOL2 + + 'Please specify a different directory.'; + begin + if GetDirNameFromEditCtrl = '' then + raise EDataEntry.Create(sNoFolder, edPath); + if not TDirectory.Exists(GetDirNameFromEditCtrl, False) then + raise EDataEntry.CreateFmt(sBadFolder, [GetDirNameFromEditCtrl], edPath); + try + TSWAGVersion.ValidateVersionFile(GetDirNameFromEditCtrl); + except + on E: ECorruptSWAGVersion do + raise EDataEntry.CreateFmt(sCorrupt, [E.Message], edPath); + on E: EUnsupportedSWAGVersion do + raise EDataEntry.CreateFmt(sBadVersion, [E.Message], edPath); + end; + end; + + procedure ValidateSelectionPage; + resourcestring + sEmptySelection = 'You must select one or more packets to import.'; + begin + if fSelectedPackets.Count = 0 then + raise EDataEntry.Create(sEmptySelection); + end; + begin case PageIdx of + cChooseFolderPage: + ValidateChooseFolderPage; cSelectionPage: - begin - if fSelectedSnippets.Count = 0 then - raise EDataEntry.Create(sEmptySelection); - end; + ValidateSelectionPage; end; end; diff --git a/Src/FmSelectionSearchDlg.pas b/Src/FmSelectionSearchDlg.pas index 3e7312a7d..a1dcf49bc 100644 --- a/Src/FmSelectionSearchDlg.pas +++ b/Src/FmSelectionSearchDlg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2006-2016, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2006-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a dialogue box that enables the user to the select the snippets * that are to be displayed. @@ -197,15 +194,17 @@ class function TSelectionSearchDlg.Execute(const AOwner: TComponent; if user cancels. @return True if user OKs and false if user cancels. } +var + Dlg: TSelectionSearchDlg; begin - with InternalCreate(AOwner) do - try - SetSelectedSnippets(SelectedSnippets); - Result := (ShowModal = mrOK); - ASearch := fSearch; - finally - Free; - end; + Dlg := InternalCreate(AOwner); + try + Dlg.SetSelectedSnippets(SelectedSnippets); + Result := (Dlg.ShowModal = mrOK); + ASearch := Dlg.fSearch; + finally + Dlg.Free; + end; end; procedure TSelectionSearchDlg.FormCreate(Sender: TObject); diff --git a/Src/FmSnippetsEditorDlg.FrActiveTextEditor.pas b/Src/FmSnippetsEditorDlg.FrActiveTextEditor.pas index c3dcac798..65e3f116f 100644 --- a/Src/FmSnippetsEditorDlg.FrActiveTextEditor.pas +++ b/Src/FmSnippetsEditorDlg.FrActiveTextEditor.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2012-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2012-2023, Peter Johnson (gravatar.com/delphidabbler). * * A frame that provides an editor for entering and ammending active text, * either as plain text or in markup. @@ -132,7 +129,7 @@ function TSnippetsActiveTextEdFrame.ActiveTextToPlainText( begin // NOTE: we use IActiveText.ToString here, because there may be text not in // blocks and we want to see that: usual renderer will ignore that text. - // However all lines are trimmed and empty blanks are ingored. + // However all lines are trimmed and empty blanks are ignored. Lines := TIStringList.Create(ActiveText.ToString, EOL, False, True); Result := Lines.GetText(EOL2, False); // insert blank line between paras end; @@ -218,13 +215,14 @@ function TSnippetsActiveTextEdFrame.PlainTextToActiveText(Text: string): Paragraph: string; // each paragraph in paragraphs begin // NOTE: TSnippetExtraHelper.PlainTextToActiveText is not sufficient for use - // here since it ignores newlines and we want double newlines to separated + // here since it ignores newlines and we want double newlines to separate // paragraphs. Result := TActiveTextFactory.CreateActiveText; Text := StrTrim(Text); if Text = '' then Exit; Paragraphs := TIStringList.Create(Text, EOL2, False, True); + Result.AddElem(TActiveTextFactory.CreateActionElem(ekDocument, fsOpen)); for Paragraph in Paragraphs do begin Result.AddElem(TActiveTextFactory.CreateActionElem(ekPara, fsOpen)); @@ -233,6 +231,7 @@ function TSnippetsActiveTextEdFrame.PlainTextToActiveText(Text: string): ); Result.AddElem(TActiveTextFactory.CreateActionElem(ekPara, fsClose)); end; + Result.AddElem(TActiveTextFactory.CreateActionElem(ekDocument, fsClose)); end; procedure TSnippetsActiveTextEdFrame.Preview; @@ -258,7 +257,7 @@ procedure TSnippetsActiveTextEdFrame.SetActiveText(Value: IActiveText); SetEditMode(emREML) else SetEditMode(fDefaultEditMode); - if not Value.IsEmpty then + if Value.HasContent then begin case fEditMode of emPlainText: diff --git a/Src/FmSnippetsEditorDlg.dfm b/Src/FmSnippetsEditorDlg.dfm index c503bb258..365274c63 100644 --- a/Src/FmSnippetsEditorDlg.dfm +++ b/Src/FmSnippetsEditorDlg.dfm @@ -6,7 +6,7 @@ inherited SnippetsEditorDlg: TSnippetsEditorDlg ClientWidth = 738 Position = poDesigned ExplicitWidth = 744 - ExplicitHeight = 606 + ExplicitHeight = 607 PixelsPerInch = 96 TextHeight = 13 inherited pnlBody: TPanel @@ -19,7 +19,7 @@ inherited SnippetsEditorDlg: TSnippetsEditorDlg Top = 0 Width = 662 Height = 504 - ActivePage = tsReferences + ActivePage = tsCompileResults Align = alClient TabOrder = 0 OnChange = pcMainChange @@ -193,10 +193,6 @@ inherited SnippetsEditorDlg: TSnippetsEditorDlg object tsReferences: TTabSheet Caption = 'References' ImageIndex = 1 - ExplicitLeft = 0 - ExplicitTop = 0 - ExplicitWidth = 0 - ExplicitHeight = 0 object lblXRefs: TLabel Left = 3 Top = 3 @@ -326,10 +322,6 @@ inherited SnippetsEditorDlg: TSnippetsEditorDlg object tsCompileResults: TTabSheet Caption = 'Compile Results' ImageIndex = 3 - ExplicitLeft = 0 - ExplicitTop = 0 - ExplicitWidth = 0 - ExplicitHeight = 0 object lblCompilers: TLabel Left = 3 Top = 3 @@ -347,8 +339,8 @@ inherited SnippetsEditorDlg: TSnippetsEditorDlg end object lblCompResDesc: TLabel Left = 3 - Top = 405 - Width = 166 + Top = 401 + Width = 186 Height = 60 AutoSize = False Caption = @@ -359,7 +351,7 @@ inherited SnippetsEditorDlg: TSnippetsEditorDlg object lbCompilers: TListBox Left = 3 Top = 22 - Width = 166 + Width = 186 Height = 373 Style = lbOwnerDrawFixed ItemHeight = 28 diff --git a/Src/FmSnippetsEditorDlg.pas b/Src/FmSnippetsEditorDlg.pas index 0ba307edc..45e9bc7d7 100644 --- a/Src/FmSnippetsEditorDlg.pas +++ b/Src/FmSnippetsEditorDlg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2008-2014, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2008-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a dialogue box that enables the user to create or edit user * defined snippets. @@ -534,17 +531,19 @@ class function TSnippetsEditorDlg.AddNewSnippet(AOwner: TComponent): Boolean; is aligned. May be nil. @return True if user OKs, False if cancels. } +var + Dlg: TSnippetsEditorDlg; resourcestring sCaption = 'Add a Snippet'; // dialog box caption begin - with InternalCreate(AOwner) do - try - Caption := sCaption; - fSnippet := nil; - Result := ShowModal = mrOK; - finally - Free; - end; + Dlg := InternalCreate(AOwner); + try + Dlg.Caption := sCaption; + Dlg.fSnippet := nil; + Result := Dlg.ShowModal = mrOK; + finally + Dlg.Free; + end; end; procedure TSnippetsEditorDlg.ArrangeForm; @@ -713,17 +712,19 @@ class function TSnippetsEditorDlg.EditSnippet(AOwner: TComponent; @param Snippet [in] Reference to snippet to be edited. @return True if user OKs, False if cancels. } +var + Instance: TSnippetsEditorDlg; resourcestring sCaption = 'Edit Snippet'; // dialogue box caption begin - with InternalCreate(AOwner) do - try - Caption := sCaption; - fSnippet := Snippet; - Result := ShowModal = mrOK; - finally - Free; - end; + Instance := InternalCreate(AOwner); + try + Instance.Caption := sCaption; + Instance.fSnippet := Snippet; + Result := Instance.ShowModal = mrOK; + finally + Instance.Free; + end; end; procedure TSnippetsEditorDlg.FocusCtrl(const Ctrl: TWinControl); @@ -947,23 +948,20 @@ function TSnippetsEditorDlg.UpdateData: TSnippetEditData; } begin Result.Init; - with Result do - begin - if StrTrim(edName.Text) <> StrTrim(edDisplayName.Text) then - Props.DisplayName := StrTrim(edDisplayName.Text) - else - Props.DisplayName := ''; - Props.Cat := fCatList.CatID(cbCategories.ItemIndex); - Props.Kind := fSnipKindList.SnippetKind(cbKind.ItemIndex); - (Props.Desc as IAssignable).Assign(frmDescription.ActiveText); - Props.SourceCode := StrTrimRight(edSourceCode.Text); - Props.HiliteSource := chkUseHiliter.Checked; - (Props.Extra as IAssignable).Assign(frmExtra.ActiveText); - Props.CompilerResults := fCompilersLBMgr.GetCompileResults; - Refs.Units := fUnitsCLBMgr.GetCheckedUnits; - Refs.Depends := fDependsCLBMgr.GetCheckedSnippets; - Refs.XRef := fXRefsCLBMgr.GetCheckedSnippets; - end; + if StrTrim(edName.Text) <> StrTrim(edDisplayName.Text) then + Result.Props.DisplayName := StrTrim(edDisplayName.Text) + else + Result.Props.DisplayName := ''; + Result.Props.Cat := fCatList.CatID(cbCategories.ItemIndex); + Result.Props.Kind := fSnipKindList.SnippetKind(cbKind.ItemIndex); + (Result.Props.Desc as IAssignable).Assign(frmDescription.ActiveText); + Result.Props.SourceCode := StrTrimRight(edSourceCode.Text); + Result.Props.HiliteSource := chkUseHiliter.Checked; + (Result.Props.Extra as IAssignable).Assign(frmExtra.ActiveText); + Result.Props.CompilerResults := fCompilersLBMgr.GetCompileResults; + Result.Refs.Units := fUnitsCLBMgr.GetCheckedUnits; + Result.Refs.Depends := fDependsCLBMgr.GetCheckedSnippets; + Result.Refs.XRef := fXRefsCLBMgr.GetCheckedSnippets; end; procedure TSnippetsEditorDlg.UpdateReferences; diff --git a/Src/FmSplash.pas b/Src/FmSplash.pas index 26fdf5b0e..eb2e1c034 100644 --- a/Src/FmSplash.pas +++ b/Src/FmSplash.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2007-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2007-2024, Peter Johnson (gravatar.com/delphidabbler). * * Implements the program's splash screen. } @@ -69,10 +66,9 @@ implementation uses // Delphi - Windows, Graphics, - // 3rd party - GIFImage, + Windows, Graphics, GIFImg, // Project + ClassHelpers.UGraphics, UAppInfo, UColours, UStructs, UWindowSettings; @@ -152,7 +148,7 @@ procedure TSplashForm.pbMainPaint(Sender: TObject); // Load and display splash screen image GIF := TGIFImage.Create; try - GIF.LoadFromResourceName(HInstance, 'SPLASHIMAGE'); + GIF.LoadFromResource(HInstance, 'SPLASHIMAGE', RT_RCDATA); Canvas.Draw(0, 0, GIF); finally GIF.Free; @@ -230,19 +226,20 @@ function TSplashAligner.GetMainFormBounds(const AForm: TCustomForm): TRectEx; } var State: TWindowState; // window state read from storage + Settings: TOwnerWindowSettings; begin // We get main form's bounds from persistent storage: we have to do this since // the splash form may be displayed before main form is aligned. // If we can't read from persistent storage or form is maximized we centre // splash form in work area. This works because main form is also centred when // storage can't be read, and maximized form takes all of work area. - with TOwnerWindowSettings.Create(AForm) do - try - if not GetWdwState(Result, State) or (State = wsMaximized) then - Result := Screen.WorkAreaRect; // we use workarea of primary monitor - finally - Free; - end; + Settings := TOwnerWindowSettings.Create(AForm); + try + if not Settings.GetWdwState(Result, State) or (State = wsMaximized) then + Result := Screen.WorkAreaRect; // we use workarea of primary monitor + finally + Settings.Free; + end; end; { TOwnerWindowSettings } diff --git a/Src/FmTestCompileDlg.pas b/Src/FmTestCompileDlg.pas index f2068e1e5..5066e2bff 100644 --- a/Src/FmTestCompileDlg.pas +++ b/Src/FmTestCompileDlg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2011-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2011-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a dialogue box which test compiles a snippet and displays the * results. @@ -317,17 +314,19 @@ procedure TTestCompileDlg.DisplayCompileResults(const Compilers: ICompilers); class procedure TTestCompileDlg.Execute(const AOwner: TComponent; const ACompileMgr: TCompileMgr; const ASnippet: TSnippet); +var + Dlg: TTestCompileDlg; begin Assert(Assigned(ACompileMgr), ClassName + '.Execute: ACompileMgr is nil'); Assert(Assigned(ASnippet), ClassName + '.Execute: ASnippet is nil'); - with InternalCreate(AOwner) do - try - fCompileMgr := ACompileMgr; - fSnippet := ASnippet; - ShowModal; - finally - Free; - end; + Dlg := InternalCreate(AOwner); + try + Dlg.fCompileMgr := ACompileMgr; + Dlg.fSnippet := ASnippet; + Dlg.ShowModal; + finally + Dlg.Free; + end; end; procedure TTestCompileDlg.FormCreate(Sender: TObject); diff --git a/Src/FmTrappedBugReportDlg.dfm b/Src/FmTrappedBugReportDlg.dfm index 50f08005b..1451ff039 100644 --- a/Src/FmTrappedBugReportDlg.dfm +++ b/Src/FmTrappedBugReportDlg.dfm @@ -1,6 +1,6 @@ inherited TrappedBugReportDlg: TTrappedBugReportDlg Caption = 'Unexpected Error' - ExplicitHeight = 356 + ExplicitHeight = 375 PixelsPerInch = 96 TextHeight = 13 inherited pnlBody: TPanel @@ -46,7 +46,8 @@ inherited TrappedBugReportDlg: TTrappedBugReportDlg AutoSize = False Caption = 'It will be helpful if you can take the time to report this bug u' + - 'sing the online CodeSnip bug tracker.' + 'sing the online CodeSnip bug tracker on GitHub (GitHub account r' + + 'equired).' WordWrap = True end object lblInstruct2: TLabel [4] @@ -69,9 +70,11 @@ inherited TrappedBugReportDlg: TTrappedBugReportDlg Height = 38 AutoSize = False Caption = - 'CodeSnip has gathered some information about the bug that will b' + - 'e placed on the clipboard. When you are reporting the bug please' + - ' paste the information after your description of the problem.' + 'CodeSnip has gathered some data about the bug that will be place' + + 'd on the clipboard. When you are reporting the bug please paste ' + + 'the information after your description of the problem. Please do' + + ' write a description of what you were doing. The data on the cli' + + 'pboard is not sufficient on its own.' WordWrap = True end inherited lblBugTrackerKey: TLabel diff --git a/Src/FmTrappedBugReportDlg.pas b/Src/FmTrappedBugReportDlg.pas index 2201826f6..6c1412954 100644 --- a/Src/FmTrappedBugReportDlg.pas +++ b/Src/FmTrappedBugReportDlg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2009-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2009-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a bug report dialogue box that is displayed when unexpected * exceptions are detected. @@ -169,15 +166,17 @@ class procedure TTrappedBugReportDlg.Execute(Owner: TComponent; dialog is aligned over the active form. @param ErrorObj [in] Exception that caused dialog box to be displayed. } +var + Dlg: TTrappedBugReportDlg; begin Assert(Assigned(ErrorObj), ClassName + '.Execute: ErrorObj is nil'); - with InternalCreate(Owner) do - try - fErrorObj := ErrorObj; - ShowModal; - finally - Free; - end; + Dlg := InternalCreate(Owner); + try + Dlg.fErrorObj := ErrorObj; + Dlg.ShowModal; + finally + Dlg.Free; + end; end; procedure TTrappedBugReportDlg.GoToTracker; diff --git a/Src/FmUserBugReportDlg.dfm b/Src/FmUserBugReportDlg.dfm index 265c6b9ab..10cf1e19a 100644 --- a/Src/FmUserBugReportDlg.dfm +++ b/Src/FmUserBugReportDlg.dfm @@ -1,5 +1,6 @@ inherited UserBugReportDlg: TUserBugReportDlg Caption = 'Report Bug Online' + ExplicitHeight = 375 PixelsPerInch = 96 TextHeight = 13 inherited pnlBody: TPanel @@ -22,8 +23,9 @@ inherited UserBugReportDlg: TUserBugReportDlg AutoSize = False Caption = 'Please use the link below to display CodeSnip'#39's online bug track' + - 'er then review the existing bug reports to see if anything simil' + - 'ar has been reported already, or maybe even fixed.' + 'er on GitHub (GitHub account required). Please review the existi' + + 'ng bug reports to see if anything similar has been reported alre' + + 'ady, or maybe even fixed.' WordWrap = True end object lblInstruct3: TLabel diff --git a/Src/FmUserBugReportDlg.pas b/Src/FmUserBugReportDlg.pas index 9006b77d0..bf180386d 100644 --- a/Src/FmUserBugReportDlg.pas +++ b/Src/FmUserBugReportDlg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2009-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2009-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a dialogue box that informs users how to report bugs. } @@ -78,13 +75,15 @@ class procedure TUserBugReportDlg.Execute(AOwner: TComponent); this component if it is a form. If Owner it is nil or not a form the dialog is aligned over the active form. } +var + Dlg: TUserBugReportDlg; begin - with Create(AOwner) do - try - ShowModal; - finally - Free; - end; + Dlg := Create(AOwner); + try + Dlg.ShowModal; + finally + Dlg.Free; + end; end; end. diff --git a/Src/FmUserDataPathDlg.dfm b/Src/FmUserDataPathDlg.dfm index 17a66e41f..014df9466 100644 --- a/Src/FmUserDataPathDlg.dfm +++ b/Src/FmUserDataPathDlg.dfm @@ -1,7 +1,7 @@ inherited UserDataPathDlg: TUserDataPathDlg Caption = 'Move User Database' ExplicitWidth = 474 - ExplicitHeight = 374 + ExplicitHeight = 375 PixelsPerInch = 96 TextHeight = 13 inherited pnlBody: TPanel @@ -170,14 +170,19 @@ inherited UserDataPathDlg: TUserDataPathDlg TabOrder = 0 end end - inline frmProgress: TUserDataPathDlgProgressFrame + inline frmProgress: TProgressFrame Left = 57 Top = 0 Width = 320 Height = 82 + ParentBackground = False TabOrder = 2 Visible = False ExplicitLeft = 57 + ExplicitHeight = 82 + inherited pnlBody: TPanel + Height = 82 + end end end object alDlg: TActionList diff --git a/Src/FmUserDataPathDlg.pas b/Src/FmUserDataPathDlg.pas index 03c1f9cf3..9c6bbbbbe 100644 --- a/Src/FmUserDataPathDlg.pas +++ b/Src/FmUserDataPathDlg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2013-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a dialogue box that can be used to move the user database to a * different directory. @@ -23,7 +20,7 @@ interface // Delphi SysUtils, Forms, Classes, ActnList, StdCtrls, Controls, ExtCtrls, // Project - FmGenericViewDlg, FmUserDataPathDlg.FrProgress, UBaseObjects, + FmGenericViewDlg, FrProgress, UBaseObjects, UControlStateMgr, UUserDBMove; type @@ -49,7 +46,7 @@ TUserDataPathDlg = class(TGenericViewDlg, INoPublicConstruct) lblPath: TLabel; lblWarning: TLabel; edPath: TEdit; - frmProgress: TUserDataPathDlgProgressFrame; + frmProgress: TProgressFrame; /// <summary>Dispays Browse For Folder dialogue box and copies any chosen /// folder to the edPath edit control.</summary> procedure actBrowseExecute(Sender: TObject); @@ -275,16 +272,21 @@ procedure TUserDataPathDlg.DoMove(const NewDir: string; end; class procedure TUserDataPathDlg.Execute(AOwner: TComponent); +{$IFNDEF PORTABLE} +var + Dlg: TUserDataPathDlg; +{$ENDIF} begin {$IFDEF PORTABLE} raise EBug.Create(ClassName + '.Execute: Call forbidden in portable edition'); + {$ELSE} + Dlg := InternalCreate(AOwner); + try + Dlg.ShowModal + finally + Dlg.Free; + end; {$ENDIF} - with InternalCreate(AOwner) do - try - ShowModal - finally - Free; - end; end; procedure TUserDataPathDlg.FormCreate(Sender: TObject); diff --git a/Src/FmUserHiliterMgrDlg.pas b/Src/FmUserHiliterMgrDlg.pas index 54a0c78fa..b71130b03 100644 --- a/Src/FmUserHiliterMgrDlg.pas +++ b/Src/FmUserHiliterMgrDlg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2013-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a dialogue box that manages named user defined syntax * highlighters. It lists available named highlighters which can be selected for @@ -155,16 +152,18 @@ procedure TUserHiliterMgrDlg.ArrangeForm; class function TUserHiliterMgrDlg.Execute(AOwner: TComponent; ANamedAttrs: INamedHiliteAttrs; out ASelected: IHiliteAttrs): Boolean; +var + Dlg: TUserHiliterMgrDlg; begin - with InternalCreate(AOwner) do - try - fNamedAttrs := ANamedAttrs; - Result := ShowModal = mrOK; - if Result then - ASelected := fSelected; - finally - Free; - end; + Dlg := InternalCreate(AOwner); + try + Dlg.fNamedAttrs := ANamedAttrs; + Result := Dlg.ShowModal = mrOK; + if Result then + ASelected := Dlg.fSelected; + finally + Dlg.Free; + end; end; procedure TUserHiliterMgrDlg.InitForm; diff --git a/Src/FmWaitDlg.pas b/Src/FmWaitDlg.pas index eecbee0b8..1d55ef3d1 100644 --- a/Src/FmWaitDlg.pas +++ b/Src/FmWaitDlg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2006-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2006-2021, Peter Johnson (gravatar.com/delphidabbler). * * Implements a borderless dialogue box that displays a message. * diff --git a/Src/FmWizardDlg.pas b/Src/FmWizardDlg.pas index ef3879adf..a52605a9e 100644 --- a/Src/FmWizardDlg.pas +++ b/Src/FmWizardDlg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2006-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2006-2021, Peter Johnson (gravatar.com/delphidabbler). * * Base class for multi-page modal "wizard" dialogue boxes. } diff --git a/Src/FrBrowserBase.pas b/Src/FrBrowserBase.pas index 4eeaa22d3..479b1c828 100644 --- a/Src/FrBrowserBase.pas +++ b/Src/FrBrowserBase.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2022, Peter Johnson (gravatar.com/delphidabbler). * * Implements a base class for all frames that contain a web browser control. } @@ -196,76 +193,56 @@ procedure TBrowserBaseFrame.BuildCSS(const CSSBuilder: TCSSBuilder); CSSFont: TFont; begin // <img> tag style: no borders - with CSSBuilder.AddSelector('img') do - AddProperty(TCSS.HideBorderProp(cssAll)); + CSSBuilder.AddSelector('img') + .AddProperty(TCSS.HideBorderProp(cssAll)); // Default <a> tag style: fall back links for unknown link classes. // Each link type is expected to define own colour as a minimum. - with CSSBuilder.AddSelector('a') do - begin - AddProperty(TCSS.ColorProp(clDefaultLink)); - AddProperty(TCSS.TextDecorationProp([ctdUnderline])); - end; + CSSBuilder.AddSelector('a') + .AddProperty(TCSS.ColorProp(clDefaultLink)) + .AddProperty(TCSS.TextDecorationProp([ctdUnderline])); // <a class="help-link"> override - with CSSBuilder.AddSelector('a.help-link') do - begin - AddProperty(TCSS.ColorProp(clHelpLink)); - end; + CSSBuilder.AddSelector('a.help-link') + .AddProperty(TCSS.ColorProp(clHelpLink)); // <a class="snippet-link"> and <a class="category-link"> overrides - with CSSBuilder.AddSelector('a.snippet-link, a.category-link') do - begin - AddProperty(TCSS.ColorProp(clDBLink)); - AddProperty(TCSS.FontStyleProp(cfsItalic)); - AddProperty(TCSS.TextDecorationProp([ctdNone])); - end; - with CSSBuilder.AddSelector('a:hover.snippet-link, a:hover.category-link') do - begin - AddProperty(TCSS.BorderProp(cssBottom, 1, cbsDotted, clDBLink)); - end; + CSSBuilder.AddSelector('a.snippet-link, a.category-link') + .AddProperty(TCSS.ColorProp(clDBLink)) + .AddProperty(TCSS.FontStyleProp(cfsItalic)) + .AddProperty(TCSS.TextDecorationProp([ctdNone])); + CSSBuilder.AddSelector('a:hover.snippet-link, a:hover.category-link') + .AddProperty(TCSS.BorderProp(cssBottom, 1, cbsDotted, clDBLink)); // <a class="command-link"> override - with CSSBuilder.AddSelector('a.command-link') do - begin - AddProperty(TCSS.ColorProp(clCommandLink)); - AddProperty(TCSS.FontStyleProp(cfsItalic)); - AddProperty(TCSS.TextDecorationProp([ctdNone])); - end; - with CSSBuilder.AddSelector('a:hover.command-link') do - begin - AddProperty(TCSS.BorderProp(cssBottom, 1, cbsDotted, clCommandLink)); - end; - with CSSBuilder.AddSelector('.no-link-decoration a:hover') do - AddProperty(TCSS.HideBorderProp(cssBottom)); + CSSBuilder.AddSelector('a.command-link') + .AddProperty(TCSS.ColorProp(clCommandLink)) + .AddProperty(TCSS.FontStyleProp(cfsItalic)) + .AddProperty(TCSS.TextDecorationProp([ctdNone])); + CSSBuilder.AddSelector('a:hover.command-link') + .AddProperty(TCSS.BorderProp(cssBottom, 1, cbsDotted, clCommandLink)); + CSSBuilder.AddSelector('.no-link-decoration a:hover') + .AddProperty(TCSS.HideBorderProp(cssBottom)); // <a class="external-link"> override - with CSSBuilder.AddSelector('a.external-link') do - begin - AddProperty(TCSS.ColorProp(clExternalLink)); - end; + CSSBuilder.AddSelector('a.external-link') + .AddProperty(TCSS.ColorProp(clExternalLink)); // <var> tag style - with CSSBuilder.AddSelector('var') do - begin - AddProperty(TCSS.ColorProp(clVarText)); - AddProperty(TCSS.FontStyleProp(cfsItalic)); - end; + CSSBuilder.AddSelector('var') + .AddProperty(TCSS.ColorProp(clVarText)) + .AddProperty(TCSS.FontStyleProp(cfsItalic)); // <code> tag style - with CSSBuilder.AddSelector('code') do - begin - CSSFont := TFont.Create; - try - TFontHelper.SetDefaultMonoFont(CSSFont); - AddProperty(TCSS.FontProps(CSSFont)); - finally - CSSFont.Free; - end; + CSSFont := TFont.Create; + try + TFontHelper.SetDefaultMonoFont(CSSFont); + CSSBuilder.AddSelector('code') + .AddProperty(TCSS.FontProps(CSSFont)); + finally + CSSFont.Free; end; // .warning class style: mainly for use inline - with CSSBuilder.AddSelector('.warning') do - begin - AddProperty(TCSS.ColorProp(clWarningText)); - AddProperty(TCSS.FontWeightProp(cfwBold)); - end; + CSSBuilder.AddSelector('.warning') + .AddProperty(TCSS.ColorProp(clWarningText)) + .AddProperty(TCSS.FontWeightProp(cfwBold)); end; function TBrowserBaseFrame.CanCopy: Boolean; diff --git a/Src/FrCategoryDescEdit.pas b/Src/FrCategoryDescEdit.pas index c845fccfd..b5dd958ea 100644 --- a/Src/FrCategoryDescEdit.pas +++ b/Src/FrCategoryDescEdit.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2009-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2009-2021, Peter Johnson (gravatar.com/delphidabbler). * * Implements a frame that accepts and validates a description for a snippet * category. diff --git a/Src/FrCategoryList.pas b/Src/FrCategoryList.pas index fbabae8f5..b1b225c97 100644 --- a/Src/FrCategoryList.pas +++ b/Src/FrCategoryList.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2009-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2009-2021, Peter Johnson (gravatar.com/delphidabbler). * * Implements a frame that displays a list of categories and provides access to * the selected category. diff --git a/Src/FrCheckedTV.pas b/Src/FrCheckedTV.pas index 5444b889d..24526d94e 100644 --- a/Src/FrCheckedTV.pas +++ b/Src/FrCheckedTV.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2006-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2006-2021, Peter Johnson (gravatar.com/delphidabbler). * * Implements a frame containing a treeview that has check boxes associated with * tree nodes and updates parent check boxes according to state of child nodes. diff --git a/Src/FrCodeGenPrefs.pas b/Src/FrCodeGenPrefs.pas index 7f07a64e8..cff57d325 100644 --- a/Src/FrCodeGenPrefs.pas +++ b/Src/FrCodeGenPrefs.pas @@ -1,16 +1,14 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2010-2015, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2010-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a frame that allows user to set source code generation * preferences. - * Designed for use as one of the tabs in the Preferences dialogue box. + * + * Designed for use as one of the pages in the Preferences dialogue box. } @@ -179,7 +177,8 @@ TCodeGenPrefsFrame = class(TPrefsBaseFrame) /// <summary>Records details of warnings from given preferences object and /// updates controls accordingly.</summary> /// <remarks>Called when page is activated.</remarks> - procedure Activate(const Prefs: IPreferences); override; + procedure Activate(const Prefs: IPreferences; const Flags: UInt64); + override; /// <summary>Updates given preferences object with details of warnings as /// modified by user.</summary> /// <remarks>Called when page is deactivated.</remarks> @@ -325,7 +324,8 @@ procedure TCodeGenPrefsFrame.actDeleteUpdate(Sender: TObject); actDelete.Enabled := Assigned(fLVWarnings.Selected); end; -procedure TCodeGenPrefsFrame.Activate(const Prefs: IPreferences); +procedure TCodeGenPrefsFrame.Activate(const Prefs: IPreferences; + const Flags: UInt64); begin (fWarnings as IAssignable).Assign(Prefs.Warnings); chkWARNEnabled.Checked := fWarnings.Enabled; @@ -480,6 +480,16 @@ constructor TCodeGenPrefsFrame.Create(AOwner: TComponent); end; procedure TCodeGenPrefsFrame.CreateLV; + + procedure AddColumn(const ACaption: string; const AWidth: Integer); + var + Col: TListColumn; + begin + Col := fLVWarnings.Columns.Add; + Col.Caption := ACaption; + Col.Width := AWidth; + end; + resourcestring // column header captions sSymbolColCaption = 'Symbol'; @@ -487,36 +497,21 @@ procedure TCodeGenPrefsFrame.CreateLV; sStateColCaption = 'State'; begin fLVWarnings := TListViewEx.Create(Self); - with fLVWarnings do - begin - Parent := Self; - Height := 150; - Left := 0; - HideSelection := False; - ReadOnly := True; - RowSelect := True; - TabOrder := 2; - ViewStyle := vsReport; - SortImmediately := False; - with Columns.Add do - begin - Caption := sSymbolColCaption; - Width := 240; - end; - with Columns.Add do - begin - Caption := sMinCompilerColCaption; - Width := 100; - end; - with Columns.Add do - begin - Caption := sStateColCaption; - Width := 50; - end; - OnSelectItem := LVWarningsSelected; - OnCompare := LVWarningsCompare; - OnCreateItemClass := LVWarningsCreateItemClass; - end; + fLVWarnings.Parent := Self; + fLVWarnings.Height := 150; + fLVWarnings.Left := 0; + fLVWarnings.HideSelection := False; + fLVWarnings.ReadOnly := True; + fLVWarnings.RowSelect := True; + fLVWarnings.TabOrder := 2; + fLVWarnings.ViewStyle := vsReport; + fLVWarnings.SortImmediately := False; + AddColumn(sSymbolColCaption, 240); + AddColumn(sMinCompilerColCaption, 100); + AddColumn(sStateColCaption, 50); + fLVWarnings.OnSelectItem := LVWarningsSelected; + fLVWarnings.OnCompare := LVWarningsCompare; + fLVWarnings.OnCreateItemClass := LVWarningsCreateItemClass; end; procedure TCodeGenPrefsFrame.Deactivate(const Prefs: IPreferences); @@ -681,6 +676,12 @@ procedure TCodeGenPrefsFrame.PopulatePreDefCompilerMenu; AddMenuItem('Delphi XE7', 28.0); AddMenuItem('Delphi XE8', 29.0); AddMenuItem('Delphi 10 Seattle', 30.0); + AddMenuItem('Delphi 10.1 Berlin', 31.0); + AddMenuItem('Delphi 10.2 Tokyo', 32.0); + AddMenuItem('Delphi 10.3 Rio', 33.0); + AddMenuItem('Delphi 10.4 Sydney', 34.0); + AddMenuItem('Delphi 11.x Alexandria', 35.0); + AddMenuItem('Delphi 12 Athens', 36.0); end; procedure TCodeGenPrefsFrame.PreDefCompilerMenuClick(Sender: TObject); diff --git a/Src/FrDetail.pas b/Src/FrDetail.pas index a0bee1621..b090041e2 100644 --- a/Src/FrDetail.pas +++ b/Src/FrDetail.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). * * Implements a title frame that displays, and manages user interaction with, * the detail pane tabset. diff --git a/Src/FrDetailView.pas b/Src/FrDetailView.pas index 6bfeac0e5..8aa41a592 100644 --- a/Src/FrDetailView.pas +++ b/Src/FrDetailView.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a frame that can display detailed views. } @@ -110,7 +107,7 @@ implementation uses // Delphi - SysUtils, Graphics, Menus, + SysUtils, Graphics, Menus, Math, // Project ActiveText.UHTMLRenderer, Browser.UHighlighter, Hiliter.UAttrs, Hiliter.UCSS, Hiliter.UGlobals, UColours, UCSSUtils, UFontHelper, UPreferences, UQuery, @@ -122,105 +119,183 @@ implementation procedure TDetailViewFrame.BuildCSS(const CSSBuilder: TCSSBuilder); var - HiliteAttrs: IHiliteAttrs; // syntax highlighter used to build CSS - CSSFont: TFont; // font used to set CSS properties + HiliteAttrs: IHiliteAttrs; // syntax highlighter used to build CSS + ContentFont: TFont; // default content font sized per preferences + MonoFont: TFont; // default mono font sized per preferences + CSSFont: TFont; // font used to set CSS properties + ContentFontScaleFactor: Single; // amount to increase font size by to get + // proportionally same increase as adding 1 to + // default content font size + MonoToContentFontRatio: Single; // ratio of size of mono font to content font + DefContentFontSize: Integer; // default size of content font + DefMonoFontSize: Integer; // default size of mono font + HiliterCSS: THiliterCSS; begin // NOTE: // We only set CSS properties that may need to use system colours or fonts // that may be changed by user or changing program defaults. CSS that controls // layout remains in a CSS file embedded in resources. inherited; + ContentFont := nil; + MonoFont := nil; CSSFont := TFont.Create; try + MonoFont := TFont.Create; + ContentFont := TFont.Create; + TFontHelper.SetDefaultMonoFont(MonoFont); + TFontHelper.SetContentFont(ContentFont); + + // Must do next two lines before changing content & mono font sizes + DefContentFontSize := ContentFont.Size; + DefMonoFontSize := MonoFont.Size; + ContentFontScaleFactor := 1.0 / DefContentFontSize; + MonoToContentFontRatio := DefMonoFontSize / DefContentFontSize; + ContentFont.Size := Preferences.DetailFontSize; + MonoFont.Size := Round(ContentFont.Size * MonoToContentFontRatio); + // Set body style to use program's font and window colour - with CSSBuilder.AddSelector('body') do - begin - TFontHelper.SetContentFont(CSSFont); - AddProperty(TCSS.FontProps(CSSFont)); - AddProperty(TCSS.BackgroundColorProp(clWindow)); - end; + CSSFont.Assign(ContentFont); + CSSBuilder.AddSelector('body') + .AddProperty(TCSS.FontProps(CSSFont)) + .AddProperty(TCSS.BackgroundColorProp(clWindow)); + + // Set mono spaced font for <code> tags + CSSFont.Assign(MonoFont); + CSSBuilder.Selectors['code'] + .AddProperty(TCSS.FontProps(CSSFont)); + // Set table to use required font - with CSSBuilder.AddSelector('table') do - begin - TFontHelper.SetContentFont(CSSFont); - AddProperty(TCSS.FontProps(CSSFont)); - AddProperty(TCSS.BackgroundColorProp(clBorder)); - end; + CSSFont.Assign(ContentFont); + CSSBuilder.AddSelector('table') + .AddProperty(TCSS.FontProps(CSSFont)) + .AddProperty(TCSS.BackgroundColorProp(clBorder)); + // Set default table cell colour (must be different to table to get border) - with CSSBuilder.AddSelector('td') do - AddProperty(TCSS.BackgroundColorProp(clWindow)); + CSSBuilder.AddSelector('td') + .AddProperty(TCSS.BackgroundColorProp(clWindow)); + // Sets H1 heading font size and border - with CSSBuilder.AddSelector('h1') do - begin - TFontHelper.SetContentFont(CSSFont); - CSSFont.Size := CSSFont.Size + 2; - CSSFont.Style := [fsBold]; - AddProperty(TCSS.FontProps(CSSFont)); - AddProperty(TCSS.BorderProp(cssBottom, 1, cbsSolid, clBorder)); - end; + CSSFont.Assign(ContentFont); + CSSFont.Size := CSSFont.Size + Max( + Round(2 * ContentFontScaleFactor * CSSFont.Size), 2 + ); + CSSFont.Style := [fsBold]; + CSSBuilder.AddSelector('h1') + .AddProperty(TCSS.FontProps(CSSFont)) + .AddProperty(TCSS.BorderProp(cssBottom, 1, cbsSolid, clBorder)); + // Sets H2 heading font size and border - with CSSBuilder.AddSelector('h2') do - begin - TFontHelper.SetContentFont(CSSFont); - CSSFont.Style := [fsBold]; - AddProperty(TCSS.FontProps(CSSFont)); - end; + CSSFont.Assign(ContentFont); + CSSFont.Style := [fsBold]; + CSSBuilder.AddSelector('h2') + .AddProperty(TCSS.FontProps(CSSFont)); + // Set H2 heading font for use in rendered active text - with CSSBuilder.AddSelector('.active-text h2') do - begin - TFontHelper.SetContentFont(CSSFont); - CSSFont.Style := [fsBold]; - CSSFont.Size := CSSFont.Size + 1; - AddProperty(TCSS.FontProps(CSSFont)); - end; + CSSFont.Assign(ContentFont); + CSSFont.Style := [fsBold]; + CSSFont.Size := CSSFont.Size + Max( + Round(ContentFontScaleFactor * CSSFont.Size), 1 + ); + CSSBuilder.AddSelector('.active-text h2') + .AddProperty(TCSS.FontProps(CSSFont)); + + // Set CODE tag within H2 heading for use in rendered active text + CSSFont.Assign(MonoFont); + CSSFont.Style := [fsBold]; + CSSFont.Size := CSSFont.Size + Max( + Round(ContentFontScaleFactor * CSSFont.Size), 1 + ); + CSSBuilder.AddSelector('.active-text h2 code') + .AddProperty(TCSS.FontProps(CSSFont)); + // Set H2 heading font for use in rendered active text in snippet list table - with CSSBuilder.AddSelector('.snippet-list .active-text h2') do - begin - TFontHelper.SetContentFont(CSSFont); - CSSFont.Style := [fsBold]; - AddProperty(TCSS.FontProps(CSSFont)); - end; + CSSFont.Assign(ContentFont); + CSSFont.Style := [fsBold]; + CSSBuilder.AddSelector('.snippet-list .active-text h2') + .AddProperty(TCSS.FontProps(CSSFont)); + + // Set <code> within H2 heading font for use in rendered active text in + // snippet list table + CSSFont.Assign(MonoFont); + CSSFont.Style := [fsBold]; + CSSBuilder.AddSelector('.snippet-list .active-text h2 code') + .AddProperty(TCSS.FontProps(CSSFont)); + // Style of box that appears around clickable options (or actions) - with CSSBuilder.AddSelector('.optionbox') do - AddProperty(TCSS.BorderProp(cssAll, 1, cbsSolid, clBorder)); - with CSSBuilder.AddSelector('.userdb') do - AddProperty(TCSS.ColorProp(Preferences.DBHeadingColours[True])); - with CSSBuilder.AddSelector('.maindb') do - AddProperty(TCSS.ColorProp(Preferences.DBHeadingColours[False])); + CSSBuilder.AddSelector('.optionbox') + .AddProperty(TCSS.BorderProp(cssAll, 1, cbsSolid, clBorder)); + + // Heading colours for user & main databases + CSSBuilder.AddSelector('.userdb') + .AddProperty(TCSS.ColorProp(Preferences.DBHeadingColours[True])); + CSSBuilder.AddSelector('.maindb') + .AddProperty(TCSS.ColorProp(Preferences.DBHeadingColours[False])); + // Sets CSS for style of New Tab text - with CSSBuilder.AddSelector('#newtab') do - begin - TFontHelper.SetContentFont(CSSFont); - CSSFont.Size := 36; - CSSFont.Color := clNewTabText; - AddProperty(TCSS.FontProps(CSSFont)); - end; + CSSFont.Assign(ContentFont); + CSSFont.Size := 36 + Round(36 * ContentFontScaleFactor); + CSSFont.Color := clNewTabText; + CSSBuilder.AddSelector('#newtab') + .AddProperty(TCSS.FontProps(CSSFont)); + // Sets text styles and colours used by syntax highlighter HiliteAttrs := THiliteAttrsFactory.CreateUserAttrs; - with THiliterCSS.Create(HiliteAttrs) do - try - BuildCSS(CSSBuilder); - finally - Free; - end; - // Adjust .pas-source class to use required background colour - with CSSBuilder.Selectors['.' + THiliterCSS.GetMainCSSClassName] do - begin - AddProperty(TCSS.BackgroundColorProp(Preferences.SourceCodeBGcolour)); - if TIEInfo.SupportsCSSOverflowX then - AddProperty(TCSS.OverflowProp(covAuto, codX)); + HiliterCSS := THiliterCSS.Create(HiliteAttrs); + try + HiliterCSS.BuildCSS(CSSBuilder); + finally + HiliterCSS.Free; end; + + // Adjust .pas-source class to use required background colour + CSSBuilder.Selectors['.' + THiliterCSS.GetMainCSSClassName] + .AddProperty(TCSS.BackgroundColorProp(Preferences.SourceCodeBGcolour)) + .AddPropertyIf( + TIEInfo.SupportsCSSOverflowX, TCSS.OverflowProp(covAuto, codX) + ); + + // Address IE peculiarities if TIEInfo.SupportsCSSOverflowX then - begin - with CSSBuilder.AddSelector('#compile-results') do - AddProperty(TCSS.OverflowProp(covAuto, codX)); - end; - with CSSBuilder.AddSelector('.comptable th') do - begin - AddProperty(TCSS.BackgroundColorProp(clCompTblHeadBg)); - AddProperty(TCSS.FontWeightProp(cfwNormal)); - end; + CSSBuilder.AddSelector('#compile-results') + .AddProperty(TCSS.OverflowProp(covAuto, codX)); + + // Compiler table heading + CSSBuilder.AddSelector('.comptable th') + .AddProperty(TCSS.BackgroundColorProp(clCompTblHeadBg)) + .AddProperty(TCSS.FontWeightProp(cfwNormal)); + + // Set active text list classes + CSSBuilder.EnsureSelector('.active-text ul') + .AddProperty(TCSS.MarginProp(cssAll, 0)) + .AddProperty(TCSS.MarginProp(cssTop, 4)) + .AddProperty(TCSS.PaddingProp(cssAll, 0)) + .AddProperty(TCSS.PaddingProp(cssLeft, 24)) + .AddProperty(TCSS.ListStylePositionProp(clspOutside)) + .AddProperty(TCSS.ListStyleTypeProp(clstDisc)); + + // Active list styling + CSSBuilder.EnsureSelector('.active-text ol') + .AddProperty(TCSS.MarginProp(cssAll, 0)) + .AddProperty(TCSS.MarginProp(cssTop, 4)) + .AddProperty(TCSS.PaddingProp(cssAll, 0)) + .AddProperty(TCSS.PaddingProp(cssLeft, 32)) + .AddProperty(TCSS.ListStylePositionProp(clspOutside)) + .AddProperty(TCSS.ListStyleTypeProp(clstDecimal)); + CSSBuilder.EnsureSelector('.active-text li') + .AddProperty(TCSS.PaddingProp(cssAll, 0)) + .AddProperty(TCSS.MarginProp(cssAll, 0)); + CSSBuilder.EnsureSelector('.active-text li ol') + .AddProperty(TCSS.MarginProp(cssTop, 0)); + CSSBuilder.EnsureSelector('.active-text li ul') + .AddProperty(TCSS.MarginProp(cssTop, 0)); + CSSBuilder.EnsureSelector('.active-text ul li') + .AddProperty(TCSS.PaddingProp(cssLeft, 8)); + CSSBuilder.EnsureSelector('.active-text ul li ol li') + .AddProperty(TCSS.PaddingProp(cssLeft, 0)); + finally + ContentFont.Free; + MonoFont.Free; CSSFont.Free; end; end; diff --git a/Src/FrDisplayPrefs.dfm b/Src/FrDisplayPrefs.dfm index 71572dce7..80a7370ef 100644 --- a/Src/FrDisplayPrefs.dfm +++ b/Src/FrDisplayPrefs.dfm @@ -32,6 +32,35 @@ inherited DisplayPrefsFrame: TDisplayPrefsFrame Height = 13 Caption = 'Background colour for &source code:' end + object lblOverviewFontSize: TLabel + Left = 16 + Top = 200 + Width = 145 + Height = 13 + Caption = 'Overview tree view &font size: ' + FocusControl = cbOverviewFontSize + end + object lblDetailFontSize: TLabel + Left = 16 + Top = 232 + Width = 105 + Height = 13 + Caption = 'Detail pane font si&ze: ' + FocusControl = cbDetailFontSize + end + object lblHiliterInfo: TLabel + Left = 16 + Top = 256 + Width = 370 + Height = 36 + Caption = + 'To change the size of the source code font use the the Syntax Hi' + + 'ghlighter options page.' + Color = clBtnFace + ParentColor = False + Transparent = True + WordWrap = True + end object cbOverviewTree: TComboBox Left = 192 Top = 2 @@ -66,4 +95,20 @@ inherited DisplayPrefsFrame: TDisplayPrefsFrame TabOrder = 3 OnClick = btnDefColoursClick end + object cbOverviewFontSize: TComboBox + Left = 192 + Top = 197 + Width = 57 + Height = 21 + TabOrder = 4 + OnChange = FontSizeChange + end + object cbDetailFontSize: TComboBox + Left = 192 + Top = 229 + Width = 57 + Height = 21 + TabOrder = 5 + OnChange = FontSizeChange + end end diff --git a/Src/FrDisplayPrefs.pas b/Src/FrDisplayPrefs.pas index 9277f8973..015d1ea02 100644 --- a/Src/FrDisplayPrefs.pas +++ b/Src/FrDisplayPrefs.pas @@ -1,15 +1,13 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2012-2014, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2012-2022, Peter Johnson (gravatar.com/delphidabbler). * * Implements a frame that allows user to set application display preferences. - * Designed for use as one of the tabs in the preferences dialog box. + + * Designed for use as one of the pages in the preferences dialogue box. } @@ -37,8 +35,14 @@ TDisplayPrefsFrame = class(TPrefsBaseFrame) lblUserColour: TLabel; btnDefColours: TButton; lblSourceBGColour: TLabel; + lblOverviewFontSize: TLabel; + cbOverviewFontSize: TComboBox; + lblDetailFontSize: TLabel; + cbDetailFontSize: TComboBox; + lblHiliterInfo: TLabel; procedure chkHideEmptySectionsClick(Sender: TObject); procedure btnDefColoursClick(Sender: TObject); + procedure FontSizeChange(Sender: TObject); strict private var /// <summary>Flag indicating if changes affect UI.</summary> @@ -61,12 +65,14 @@ TDisplayPrefsFrame = class(TPrefsBaseFrame) function CreateCustomColourBox(const ColourDlg: TColorDialogEx): TColorBoxEx; procedure ColourBoxChangeHandler(Sender: TObject); + procedure PopulateFontSizeCombos; public constructor Create(AOwner: TComponent); override; {Class constructor. Sets up frame and populates controls. @param AOwner [in] Component that owns frame. } - procedure Activate(const Prefs: IPreferences); override; + procedure Activate(const Prefs: IPreferences; const Flags: UInt64); + override; {Called when page activated. Updates controls. @param Prefs [in] Object that provides info used to update controls. } @@ -99,17 +105,25 @@ implementation uses // Delphi - Math, Graphics, ExtCtrls, + SysUtils, Math, Graphics, ExtCtrls, // Project - FmPreferencesDlg, UColours, UCtrlArranger, UGraphicUtils; + FmPreferencesDlg, UColours, UCtrlArranger, UFontHelper, UGraphicUtils, + UMessageBox; {$R *.dfm} +resourcestring + // Error messages + sErrBadOverviewFontSize = 'Invalid font size'; + sErrBadOverviewFontRange = 'Font size out of range. ' + + 'Enter a value between %0:d and %1:d'; + { TDisplayPrefsFrame } -procedure TDisplayPrefsFrame.Activate(const Prefs: IPreferences); +procedure TDisplayPrefsFrame.Activate(const Prefs: IPreferences; + const Flags: UInt64); {Called when page activated. Updates controls. @param Prefs [in] Object that provides info used to update controls. } @@ -125,6 +139,10 @@ procedure TDisplayPrefsFrame.Activate(const Prefs: IPreferences); Prefs.DBHeadingCustomColours[False].CopyTo(fMainColourDlg.CustomColors, True); Prefs.DBHeadingCustomColours[True].CopyTo(fUserColourDlg.CustomColors, True); Prefs.SourceCodeBGCustomColours.CopyTo(fSourceBGColourDlg.CustomColors, True); + cbOverviewFontSize.Tag := Prefs.OverviewFontSize; // store font size in .Tag + cbOverviewFontSize.Text := IntToStr(Prefs.OverviewFontSize); + cbDetailFontSize.Tag := Prefs.DetailFontSize; // store font size in .Tag + cbDetailFontSize.Text := IntToStr(Prefs.DetailFontSize); end; procedure TDisplayPrefsFrame.ArrangeControls; @@ -134,12 +152,16 @@ procedure TDisplayPrefsFrame.ArrangeControls; TCtrlArranger.AlignLefts( [ lblOverviewTree, chkHideEmptySections, chkSnippetsInNewTab, - lblMainColour, lblUserColour, lblSourceBGColour, btnDefColours + lblMainColour, lblUserColour, lblSourceBGColour, btnDefColours, + lblOverviewFontSize, lblDetailFontSize, lblHiliterInfo ], 0 ); TCtrlArranger.AlignLefts( - [cbOverviewTree, fMainColourBox, fUserColourBox, fSourceBGColourBox], + [ + cbOverviewTree, fMainColourBox, fUserColourBox, fSourceBGColourBox, + cbOverviewFontSize, cbDetailFontSize + ], TCtrlArranger.RightOf( [lblOverviewTree, lblMainColour, lblUserColour, lblSourceBGColour], 8 @@ -147,24 +169,37 @@ procedure TDisplayPrefsFrame.ArrangeControls; ); TCtrlArranger.AlignVCentres(3, [lblOverviewTree, cbOverviewTree]); TCtrlArranger.MoveBelow( - [lblOverviewTree, cbOverviewTree], chkSnippetsInNewTab, 24 + [lblOverviewTree, cbOverviewTree], chkSnippetsInNewTab, 12 ); TCtrlArranger.MoveBelow(chkSnippetsInNewTab, chkHideEmptySections, 8); TCtrlArranger.AlignVCentres( - TCtrlArranger.BottomOf(chkHideEmptySections, 24), + TCtrlArranger.BottomOf(chkHideEmptySections, 12), [lblMainColour, fMainColourBox] ); TCtrlArranger.AlignVCentres( - TCtrlArranger.BottomOf([lblMainColour, fMainColourBox], 8), + TCtrlArranger.BottomOf([lblMainColour, fMainColourBox], 6), [lblUserColour, fUserColourBox] ); TCtrlArranger.AlignVCentres( - TCtrlArranger.BottomOf([lblUserColour, fUserColourBox], 8), + TCtrlArranger.BottomOf([lblUserColour, fUserColourBox], 6), [lblSourceBGColour, fSourceBGColourBox] ); TCtrlArranger.MoveBelow( - [lblSourceBGColour, fSourceBGColourBox], btnDefColours, 12 + [lblSourceBGColour, fSourceBGColourBox], btnDefColours, 6 + ); + TCtrlArranger.AlignVCentres( + TCtrlArranger.BottomOf(btnDefColours, 12), + [lblOverviewFontSize, cbOverviewFontSize] + ); + TCtrlArranger.AlignVCentres( + TCtrlArranger.BottomOf(cbOverviewFontSize, 8), + [lblDetailFontSize, cbDetailFontSize] + ); + TCtrlArranger.MoveBelow( + [lblDetailFontSize, cbDetailFontSize], lblHiliterInfo, 12 ); + lblHiliterInfo.Width := Self.ClientWidth; + TCtrlArranger.SetLabelHeight(lblHiliterInfo); chkHideEmptySections.Width := Self.Width - 16; chkSnippetsInNewTab.Width := Self.Width - 16; end; @@ -227,6 +262,8 @@ constructor TDisplayPrefsFrame.Create(AOwner: TComponent); fSourceBGColourBox := CreateCustomColourBox(fSourceBGColourDlg); fSourceBGColourBox.TabOrder := 5; lblSourceBGColour.FocusControl := fSourceBGColourBox; + + PopulateFontSizeCombos; end; function TDisplayPrefsFrame.CreateCustomColourBox( @@ -269,6 +306,10 @@ procedure TDisplayPrefsFrame.Deactivate(const Prefs: IPreferences); Prefs.SourceCodeBGCustomColours.CopyFrom( fSourceBGColourDlg.CustomColors, True ); + // Setting following properties to -1 causes preferences object to use their + // default font size + Prefs.OverviewFontSize := StrToIntDef(cbOverviewFontSize.Text, -1); + Prefs.DetailFontSize := StrToIntDef(cbDetailFontSize.Text, -1); end; function TDisplayPrefsFrame.DisplayName: string; @@ -282,6 +323,47 @@ function TDisplayPrefsFrame.DisplayName: string; Result := sDisplayName; end; +procedure TDisplayPrefsFrame.FontSizeChange(Sender: TObject); +var + Size: Integer; // font size entered by user + CB: TComboBox; // combo box that triggered event +begin + inherited; + Assert(Sender is TComboBox, + ClassName + '.FontSizeChange: Sender not TComboBox'); + CB := Sender as TComboBox; + // Do nothing if combo box text field cleared + if CB.Text = '' then + Exit; + if TryStrToInt(CB.Text, Size) then + begin + if TFontHelper.IsInCommonFontSizeRange(Size) then + begin + // Combo has valid value entered: update + CB.Tag := Size; + fUIChanged := True; + end + else + begin + // Font size out of range + TMessageBox.Error( + ParentForm, + Format( + sErrBadOverviewFontRange, + [TFontHelper.CommonFontSizes.Min, TFontHelper.CommonFontSizes.Max] + ) + ); + CB.Text := IntToStr(CB.Tag); + end; + end + else + begin + // Combo has invalid value: say so + TMessageBox.Error(ParentForm, sErrBadOverviewFontSize); + CB.Text := IntToStr(CB.Tag); + end; +end; + class function TDisplayPrefsFrame.Index: Byte; {Index number that determines the location of the tab containing this frame when displayed in the preferences dialog box. @@ -310,6 +392,14 @@ function TDisplayPrefsFrame.OverviewTreeStateDesc( Result := cOTSStartStates[State]; end; +procedure TDisplayPrefsFrame.PopulateFontSizeCombos; +begin + cbOverviewFontSize.Clear; + TFontHelper.ListCommonFontSizes(cbOverviewFontSize.Items); + cbDetailFontSize.Clear; + TFontHelper.ListCommonFontSizes(cbDetailFontSize.Items); +end; + procedure TDisplayPrefsFrame.SelectOverviewTreeState( const State: TOverviewStartState); {Selects combo box item associated with a overview treeview startup state. diff --git a/Src/FrEasterEgg.pas b/Src/FrEasterEgg.pas index c48d93e90..f993747b7 100644 --- a/Src/FrEasterEgg.pas +++ b/Src/FrEasterEgg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2009-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2009-2022, Peter Johnson (gravatar.com/delphidabbler). * * Implements a frame that hosts the HTML, CSS and JavaScript used to display * the program's animated easter egg. @@ -77,8 +74,8 @@ procedure TEasterEggFrame.BuildCSS(const CSSBuilder: TCSSBuilder); CSSFont := TFont.Create; try TFontHelper.SetContentFont(CSSFont); - with CSSBuilder.AddSelector('body') do - AddProperty(TCSS.FontProps(CSSFont)); + CSSBuilder.AddSelector('body') + .AddProperty(TCSS.FontProps(CSSFont)); finally FreeAndNil(CSSFont); end; diff --git a/Src/FrFixedHTMLDlg.pas b/Src/FrFixedHTMLDlg.pas index 74fcb1177..89cf575b0 100644 --- a/Src/FrFixedHTMLDlg.pas +++ b/Src/FrFixedHTMLDlg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2008-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2008-2021, Peter Johnson (gravatar.com/delphidabbler). * * Implements a frame containing a web browser control that displays content * loaded from an HTML resource. diff --git a/Src/FrGeneralPrefs.pas b/Src/FrGeneralPrefs.pas index 8fcaf36d6..cfc8e7758 100644 --- a/Src/FrGeneralPrefs.pas +++ b/Src/FrGeneralPrefs.pas @@ -1,15 +1,13 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2007-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2007-2021, Peter Johnson (gravatar.com/delphidabbler). * * Implements a frame that allows user to set general application preferences. - * Designed for use as one of the tabs in the Preferences dialogue box. + * + * Designed for use as one of the pages in the Preferences dialogue box. } @@ -48,7 +46,8 @@ TGeneralPrefsFrame = class(TPrefsBaseFrame) {Class constructor. Sets up frame and populates controls. @param AOwner [in] Component that owns frame. } - procedure Activate(const Prefs: IPreferences); override; + procedure Activate(const Prefs: IPreferences; const Flags: UInt64); + override; {Called when page activated. Updates controls. @param Prefs [in] Object that provides info used to update controls. } @@ -91,7 +90,8 @@ implementation { TGeneralPrefsFrame } -procedure TGeneralPrefsFrame.Activate(const Prefs: IPreferences); +procedure TGeneralPrefsFrame.Activate(const Prefs: IPreferences; + const Flags: UInt64); {Called when page activated. Updates controls. @param Prefs [in] Object that provides info used to update controls. } diff --git a/Src/FrHTMLDlg.pas b/Src/FrHTMLDlg.pas index 160078e8b..b9901068a 100644 --- a/Src/FrHTMLDlg.pas +++ b/Src/FrHTMLDlg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2022, Peter Johnson (gravatar.com/delphidabbler). * * Implements a frame containing a web browser control that displays HTML * content that takes on the appearance of a dialogue box. @@ -21,9 +18,16 @@ interface uses // Delphi - OleCtrls, SHDocVw, Classes, Controls, ExtCtrls, Forms, + OleCtrls, + SHDocVw, + Classes, + Controls, + ExtCtrls, + Forms, // Project - FrBrowserBase, UCSSBuilder; + Browser.UUIMgr, + FrBrowserBase, + UCSSBuilder; type @@ -38,8 +42,11 @@ THTMLDlgFrame = class(TBrowserBaseFrame) fOwner: TForm; {Form that owns the frame. Used to set font and colours to display in frame's HTML } + function GetScrollStyle: TWBScrollbarStyle; + procedure SetScrollStyle(const AValue: TWBScrollbarStyle); strict protected procedure BuildCSS(const CSSBuilder: TCSSBuilder); override; + published {Generates CSS classes specific to HTML dialog boxes. This CSS is added to that provided by parent class. @param CSSBuilder [in] Object used to build the CSS code. @@ -53,6 +60,7 @@ THTMLDlgFrame = class(TBrowserBaseFrame) {Calculates height of document displayed in browser. @return Document height in pixels. } + property ScrollStyle: TWBScrollbarStyle read GetScrollStyle write SetScrollStyle default sbsHide; end; @@ -61,9 +69,11 @@ implementation uses // Delphi - SysUtils, Graphics, + SysUtils, + Graphics, // Project - Browser.UUIMgr, UCSSUtils, UNulDropTarget; + UCSSUtils, + UNulDropTarget; {$R *.dfm} @@ -83,32 +93,30 @@ procedure THTMLDlgFrame.BuildCSS(const CSSBuilder: TCSSBuilder); // Set body style to use dialog box colour and font with no margin CSSFont := TFont.Create; try - with CSSBuilder.AddSelector('body') do - begin - AddProperty(TCSS.BackgroundColorProp(fOwner.Color)); - AddProperty(TCSS.FontProps(fOwner.Font)); - AddProperty(TCSS.MarginProp(0)); - end; + CSSBuilder.AddSelector('body') + .AddProperty(TCSS.BackgroundColorProp(fOwner.Color)) + .AddProperty(TCSS.FontProps(fOwner.Font)) + .AddProperty(TCSS.MarginProp(0)); // Sets heading margins, padding and font size - with CSSBuilder.AddSelector('h1') do - begin - CSSFont.Assign(Self.Font); - CSSFont.Size := CSSFont.Size + 2; - CSSFont.Style := [fsBold]; - AddProperty(TCSS.FontProps(CSSFont)); - AddProperty(TCSS.MarginProp(0)); - AddProperty(TCSS.PaddingProp(0)); - end; + CSSFont.Assign(Self.Font); + CSSFont.Size := CSSFont.Size + 2; + CSSFont.Style := [fsBold]; + CSSBuilder.AddSelector('h1') + .AddProperty(TCSS.FontProps(CSSFont)) + .AddProperty(TCSS.MarginProp(0)) + .AddProperty(TCSS.PaddingProp(0)); // Sets paragraph margins and padding - with CSSBuilder.AddSelector('p') do - begin - AddProperty(TCSS.MarginProp(cssTop, 6)); - AddProperty(TCSS.MarginProp(cssBottom, 0)); - AddProperty(TCSS.PaddingProp(0)); - end; + CSSBuilder.AddSelector('p') + .AddProperty(TCSS.MarginProp(cssTop, 6)) + .AddProperty(TCSS.MarginProp(cssBottom, 0)) + .AddProperty(TCSS.PaddingProp(0)); + CSSBuilder.AddSelector('ul') + .AddProperty(TCSS.MarginProp(cssTop, 6)) + .AddProperty(TCSS.MarginProp(cssBottom, 0)) + .AddProperty(TCSS.PaddingProp(0)); // Sets table font info - with CSSBuilder.AddSelector('table') do - AddProperty(TCSS.FontProps(fOwner.Font)); + CSSBuilder.AddSelector('table') + .AddProperty(TCSS.FontProps(fOwner.Font)); finally FreeAndNil(CSSFont); end; @@ -137,5 +145,16 @@ function THTMLDlgFrame.DocHeight: Integer; Result := WBController.UIMgr.DocHeight; end; +function THTMLDlgFrame.GetScrollStyle: TWBScrollbarStyle; +begin + Result := WBController.UIMgr.ScrollbarStyle; +end; + +procedure THTMLDlgFrame.SetScrollStyle(const AValue: TWBScrollbarStyle); +begin + if AValue <> GetScrollStyle then + WBController.UIMgr.ScrollbarStyle := AValue; +end; + end. diff --git a/Src/FrHTMLPreview.pas b/Src/FrHTMLPreview.pas index 0b581ce10..e45cc0641 100644 --- a/Src/FrHTMLPreview.pas +++ b/Src/FrHTMLPreview.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2022, Peter Johnson (gravatar.com/delphidabbler). * * Implements a frame used to display previews of HTML documents. } @@ -73,8 +70,8 @@ procedure THTMLPreviewFrame.BuildCSS(const CSSBuilder: TCSSBuilder); } begin inherited; - with CSSBuilder.AddSelector('body') do - AddProperty(TCSS.MarginProp(cPreviewMargin)); + CSSBuilder.AddSelector('body') + .AddProperty(TCSS.MarginProp(cPreviewMargin)); end; procedure THTMLPreviewFrame.Display(const DocContent: TEncodedData); diff --git a/Src/FrHTMLTpltDlg.pas b/Src/FrHTMLTpltDlg.pas index 435ebe244..af15a0328 100644 --- a/Src/FrHTMLTpltDlg.pas +++ b/Src/FrHTMLTpltDlg.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). * * Implements a frame containing a web browser control that displays HTML * content generated from a template that takes on the appearance of a dialogue diff --git a/Src/FrHiliterPrefs.pas b/Src/FrHiliterPrefs.pas index c21fa1002..61f6816f2 100644 --- a/Src/FrHiliterPrefs.pas +++ b/Src/FrHiliterPrefs.pas @@ -1,16 +1,14 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2006-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2006-2025, Peter Johnson (gravatar.com/delphidabbler). * * Implements a frame that allows the user to set syntax highlighter - * preferences. Designed for use as one of the tabs in the Preferences dialogue - * box. + * preferences. + * + * Designed for use as one of the pages in the Preferences dialogue box. } @@ -133,9 +131,7 @@ THiliterPrefsFrame = class(TPrefsBaseFrame) /// <summary>Generates and returns RTF representation of currently selected /// highlighter element.</summary> /// <remarks>This RTF is used to display elememt in preview pane.</remarks> - function GenerateRTF: TRTF; - /// <summary>Returns reference to form that hosts the frame.</summary> - function ParentForm: TForm; + function GenerateRTF: TRTFMarkup; public /// <summary>Constructs frame instance and initialises controls.</summary> /// <param name="AOwner">TComponent [in] Component that owns the frame. @@ -146,7 +142,8 @@ THiliterPrefsFrame = class(TPrefsBaseFrame) /// <summary>Updates controls from given preferences object.</summary> /// <remarks>Called when the dialogue page containing the frame is /// activated.</remarks> - procedure Activate(const Prefs: IPreferences); override; + procedure Activate(const Prefs: IPreferences; const Flags: UInt64); + override; /// <summary>Updates given preferences object with data entered in controls. /// </summary> /// <remarks>Called when the dialogue page containing the frame is @@ -181,6 +178,7 @@ implementation // Delphi SysUtils, ExtCtrls, Windows, Graphics, Dialogs, // Project + ClassHelpers.RichEdit, FmPreferencesDlg, FmNewHiliterNameDlg, FmUserHiliterMgrDlg, Hiliter.UAttrs, IntfCommon, UCtrlArranger, UFontHelper, UIStringList, UMessageBox, URTFBuilder, URTFStyles, UUtils; @@ -219,6 +217,8 @@ implementation // Error messages sErrBadFontSize = 'Invalid font size'; + sErrBadFontRange = 'Font size out of range. ' + + 'Enter a value between %0:d and %1:d'; const /// <summary>Map of highlighter elements to descriptions.</summary> @@ -257,7 +257,8 @@ implementation { THiliterPrefsFrame } -procedure THiliterPrefsFrame.Activate(const Prefs: IPreferences); +procedure THiliterPrefsFrame.Activate(const Prefs: IPreferences; + const Flags: UInt64); begin (fAttrs as IAssignable).Assign(Prefs.HiliteAttrs); (fNamedAttrs as IAssignable).Assign(Prefs.NamedHiliteAttrs); @@ -345,10 +346,24 @@ procedure THiliterPrefsFrame.cbFontSizeChange(Sender: TObject); Exit; if TryStrToInt(cbFontSize.Text, Size) then begin - // Combo has valid value entered: update - fAttrs.FontSize := Size; - UpdatePreview; - fChanged := True; + if TFontHelper.IsInCommonFontSizeRange(Size) then + begin + // Combo has valid value entered: update + fAttrs.FontSize := Size; + UpdatePreview; + fChanged := True; + end + else + begin + TMessageBox.Error( + ParentForm, + Format( + sErrBadFontRange, + [TFontHelper.CommonFontSizes.Min, TFontHelper.CommonFontSizes.Max] + ) + ); + cbFontSize.Text := IntToStr(fAttrs.FontSize); + end; end else begin @@ -464,7 +479,7 @@ function THiliterPrefsFrame.DisplayName: string; Result := sDisplayName; end; -function THiliterPrefsFrame.GenerateRTF: TRTF; +function THiliterPrefsFrame.GenerateRTF: TRTFMarkup; var RTFBuilder: TRTFBuilder; // object used to create and render RTFBuilder EgLines: IStringList; // list of lines in the example @@ -524,20 +539,6 @@ procedure THiliterPrefsFrame.miNamedStylesClick(Sender: TObject); UpdatePopupMenu; end; -function THiliterPrefsFrame.ParentForm: TForm; -var - ParentCtrl: TWinControl; // reference to parent controls -begin - // Loop through parent controls until form found or top level parent reached - ParentCtrl := Self.Parent; - while Assigned(ParentCtrl) and not (ParentCtrl is TForm) do - ParentCtrl := ParentCtrl.Parent; - if ParentCtrl is TForm then - Result := ParentCtrl as TForm - else - Result := nil; -end; - procedure THiliterPrefsFrame.PopulateElementsList; var ElemId: THiliteElement; // loops thru all highlighter elements @@ -614,7 +615,7 @@ procedure THiliterPrefsFrame.UpdatePopupMenu; procedure THiliterPrefsFrame.UpdatePreview; begin - TRichEditHelper.Load(frmExample.RichEdit, GenerateRTF); + frmExample.RichEdit.Load(GenerateRTF); end; initialization diff --git a/Src/FrMemoPreview.pas b/Src/FrMemoPreview.pas index 0e91c54ca..6a8864b1d 100644 --- a/Src/FrMemoPreview.pas +++ b/Src/FrMemoPreview.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2007-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2007-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements an abstract base class for frames used to display previews of * documents using controls that descend from TCustomMemo. @@ -144,13 +141,15 @@ procedure TMemoPreviewFrame.SelectAll; procedure TMemoPreviewFrame.SetMargin; {Sets fixed size margin around control. } +var + MemoHelper: TMemoHelper; begin - with TMemoHelper.Create(GetMemoCtrl) do - try - SetMargin(cPreviewMargin); - finally - Free; - end; + MemoHelper := TMemoHelper.Create(GetMemoCtrl); + try + MemoHelper.SetMargin(cPreviewMargin); + finally + MemoHelper.Free; + end; end; end. diff --git a/Src/FrNewsPrefs.dfm b/Src/FrNewsPrefs.dfm deleted file mode 100644 index d8d334354..000000000 --- a/Src/FrNewsPrefs.dfm +++ /dev/null @@ -1,29 +0,0 @@ -inherited NewsPrefsFrame: TNewsPrefsFrame - object lblAgePrefix: TLabel - Left = 0 - Top = 16 - Width = 116 - Height = 13 - Caption = '&Display news going back' - FocusControl = seAge - end - object lblAgeSuffix: TLabel - Left = 184 - Top = 16 - Width = 121 - Height = 13 - Caption = 'days. (min 14, max 365).' - end - object seAge: TSpinEdit - Left = 128 - Top = 13 - Width = 50 - Height = 22 - MaxValue = 365 - MinValue = 14 - ParentShowHint = False - ShowHint = False - TabOrder = 0 - Value = 14 - end -end diff --git a/Src/FrNewsPrefs.pas b/Src/FrNewsPrefs.pas deleted file mode 100644 index b02e85f8d..000000000 --- a/Src/FrNewsPrefs.pas +++ /dev/null @@ -1,152 +0,0 @@ -{ - * This Source Code Form is subject to the terms of the Mozilla Public License, - * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ - * - * Copyright (C) 2011-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ - * - * Implements a frame that allows user to set preferences that relate to news - * items. - * Designed for use as one of the tabs in the Preferences dialogue box. -} - - -unit FrNewsPrefs; - - -interface - - -uses - // Delphi - Controls, StdCtrls, Spin, Classes, - // Project - FrPrefsBase, UPreferences; - - -type - { - TNewsPrefsFrame: - Frame that allows user to set preferences that relate to news items. Can - persist preferences entered by user. Note: Designed for use in preferences - dialog box. - } - TNewsPrefsFrame = class(TPrefsBaseFrame) - lblAgePrefix: TLabel; - lblAgeSuffix: TLabel; - seAge: TSpinEdit; - public - constructor Create(AOwner: TComponent); override; - {Object constructor. Sets up frame. - @param AOwner [in] Component that owns frame. - } - procedure Activate(const Prefs: IPreferences); override; - {Called when page activated. Updates controls. - @param Prefs [in] Object that provides info used to update controls. - } - procedure Deactivate(const Prefs: IPreferences); override; - {Called when page is deactivated. Stores information entered by user. - @param Prefs [in] Object used to store information. - } - /// <summary>Checks if preference changes require that main window UI is - /// updated.</summary> - /// <remarks>Called when dialog box containing frame is closing. Always - /// returns False because these preferences never affect UI.</remarks> - function UIUpdated: Boolean; override; - procedure ArrangeControls; override; - {Arranges controls on frame. Called after frame has been sized. - } - function DisplayName: string; override; - {Caption that is displayed in the tab sheet that contains this frame when - displayed in the preference dialog box. - @return Required display name. - } - class function Index: Byte; override; - {Index number that determines the location of the tab containing this - frame when displayed in the preferences dialog box. - @return Required index number. - } - end; - - -implementation - - -uses - // Project - FmPreferencesDlg, UCtrlArranger; - - -{$R *.dfm} - -{ TNewPrefsFrame } - -procedure TNewsPrefsFrame.Activate(const Prefs: IPreferences); - {Called when page activated. Updates controls. - @param Prefs [in] Object that provides info used to update controls. - } -begin - seAge.Value := Prefs.NewsAge; -end; - -procedure TNewsPrefsFrame.ArrangeControls; - {Arranges controls on frame. Called after frame has been sized. - } -begin - lblAgePrefix.Left := 0; - TCtrlArranger.MoveToRightOf(lblAgePrefix, seAge, 6); - TCtrlArranger.MoveToRightOf(seAge, lblAgeSuffix, 6); - TCtrlArranger.AlignVCentres(8, [lblAgePrefix, seAge, lblAgeSuffix]); -end; - -constructor TNewsPrefsFrame.Create(AOwner: TComponent); - {Object constructor. Sets up frame. - @param AOwner [in] Component that owns frame. - } -begin - inherited; - HelpKeyword := 'NewsPrefs'; -end; - -procedure TNewsPrefsFrame.Deactivate(const Prefs: IPreferences); - {Called when page is deactivated. Stores information entered by user. - @param Prefs [in] Object used to store information. - } -begin - Prefs.NewsAge := seAge.Value; -end; - -function TNewsPrefsFrame.DisplayName: string; - {Caption that is displayed in the tab sheet that contains this frame when - displayed in the preference dialog box. - @return Required display name. - } -resourcestring - sDisplayName = 'News'; // display name -begin - Result := sDisplayName; -end; - -class function TNewsPrefsFrame.Index: Byte; - {Index number that determines the location of the tab containing this frame - when displayed in the preferences dialog box. - @return Required index number. - } -begin - Result := 50; -end; - -function TNewsPrefsFrame.UIUpdated: Boolean; -begin - Result := False; -end; - -initialization - -// Register frame with preferences dialog box -TPreferencesDlg.RegisterPage(TNewsPrefsFrame); - -end. diff --git a/Src/FrOverview.pas b/Src/FrOverview.pas index 274ade2ac..17f912f81 100644 --- a/Src/FrOverview.pas +++ b/Src/FrOverview.pas @@ -1,15 +1,15 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2025, Peter Johnson (gravatar.com/delphidabbler). * * Implements a titled frame that displays lists of snippets, arranged in * different ways, and manages user interaction with the displayed items. + * + * ACKNOWLEDGEMENT: fViewStore view list implemented by @SirRufo (GitHub PR + * #160 & Issue #158). } @@ -21,6 +21,7 @@ interface uses // Delphi + Generics.Collections, ComCtrls, Controls, Classes, Windows, ExtCtrls, StdCtrls, ToolWin, Menus, // Project DB.USnippet, FrTitled, IntfFrameMgrs, IntfNotifier, UCommandBars, @@ -89,7 +90,10 @@ TTVDraw = class(TSnippetsTVDraw) @return True if node is a section header, False if not. } end; + var + fViewStore : TList<IView>; // Stores references to IView instances that + // have weak references in tree nodes fTVDraw: TTVDraw; // Object that renders tree view nodes fNotifier: INotifier; // Notifies app of user initiated events fCanChange: Boolean; // Whether selected node allowed to change @@ -286,6 +290,7 @@ constructor TOverviewFrame.Create(AOwner: TComponent); TabIdx: Integer; // loops through tabs begin inherited; + fViewStore := TList<IView>.Create; // Create delegated (contained) command bar manager for toolbar and popup menu fCommandBars := TCommandBarMgr.Create(Self); fCommandBars.AddCommandBar( @@ -321,6 +326,7 @@ destructor TOverviewFrame.Destroy; fSelectedItem := nil; fSnippetList.Free; // does not free referenced snippets fCommandBars.Free; + fViewStore.Free; inherited; end; @@ -522,7 +528,7 @@ procedure TOverviewFrame.Redisplay; Exit; // Build new treeview using grouping determined by selected tab Builder := BuilderClasses[tcDisplayStyle.TabIndex].Create( - tvSnippets, fSnippetList + tvSnippets, fSnippetList, fViewStore ); Builder.Build; // Restore state of treeview based on last time it was displayed @@ -969,7 +975,12 @@ function TOverviewFrame.TTVDraw.IsSectionHeadNode( ViewItem: IView; // view item represented by node begin ViewItem := (Node as TViewItemTreeNode).ViewItem; - Result := ViewItem.IsGrouping; + // Workaround for possibility that ViewItem might be nil when restarting after + // hibernation. + if Assigned(ViewItem) then + Result := ViewItem.IsGrouping + else + Result := False; end; function TOverviewFrame.TTVDraw.IsUserDefinedNode( @@ -982,7 +993,12 @@ function TOverviewFrame.TTVDraw.IsUserDefinedNode( ViewItem: IView; // view item represented by node begin ViewItem := (Node as TViewItemTreeNode).ViewItem; - Result := ViewItem.IsUserDefined; + // Workaround for possibility that ViewItem might be nil when restarting after + // hibernation. + if Assigned(ViewItem) then + Result := ViewItem.IsUserDefined + else + Result := False; end; end. diff --git a/Src/FrPrefsBase.pas b/Src/FrPrefsBase.pas index 718b3bac2..6ec25b042 100644 --- a/Src/FrPrefsBase.pas +++ b/Src/FrPrefsBase.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2006-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2006-2021, Peter Johnson (gravatar.com/delphidabbler). * * Implements a frame that forms an abstract base class for all frames displayed * in the Preferences dialogue box. @@ -41,6 +38,11 @@ TPrefsFrameClass = class of TPrefsBaseFrame; preferences dialog box. } TPrefsBaseFrame = class(TFrame) + strict protected + class function IsFlagSupported(const Flag: UInt64): Boolean; inline; + class function ExtractFrameFlag(const Flag: UInt64): UInt32; inline; + /// <summary>Returns reference to form that hosts the frame.</summary> + function ParentForm: TForm; public procedure SavePrefs(const Prefs: IPreferences); virtual; {Saves information user entered in frame. By default the method simply @@ -48,12 +50,14 @@ TPrefsBaseFrame = class(TFrame) use Prefs object. @param Prefs [in] Object used to store information. } - procedure LoadPrefs(const Prefs: IPreferences); virtual; + procedure LoadPrefs(const Prefs: IPreferences; const Flags: UInt64); + virtual; {Initialises controls. By default the method simply calls Activate. May be overridden to load any custom data that doesn't use Prefs object. @param Prefs [in] Object that provides info used to update controls. } - procedure Activate(const Prefs: IPreferences); virtual; abstract; + procedure Activate(const Prefs: IPreferences; const Flags: UInt64); + virtual; abstract; {Called when page activated. Must update controls from preferences. @param Prefs [in] Object that provides info used to update controls. } @@ -80,23 +84,64 @@ TPrefsBaseFrame = class(TFrame) new entries at a later date. @return Required index number. } + class function MakeFrameFlag(const Flag: UInt32): UInt64; inline; end; implementation +uses + // Delphi + SysUtils, + Controls; + {$R *.dfm} { TPrefsBaseFrame } -procedure TPrefsBaseFrame.LoadPrefs(const Prefs: IPreferences); +class function TPrefsBaseFrame.ExtractFrameFlag(const Flag: UInt64): UInt32; +begin + if not IsFlagSupported(Flag) then + Exit(0); + Result := Int64Rec(Flag).Lo; +end; + +class function TPrefsBaseFrame.IsFlagSupported(const Flag: UInt64): Boolean; +begin + Result := Int64Rec(Flag).Hi = UInt32(Index); +end; + +procedure TPrefsBaseFrame.LoadPrefs(const Prefs: IPreferences; + const Flags: UInt64); {Initialises controls. By default the method simply calls Activate. May be overridden to load any custom data that doesn't use Prefs object. @param Prefs [in] Object that provides info used to update controls. } begin - Activate(Prefs); + Activate(Prefs, Flags); +end; + +class function TPrefsBaseFrame.MakeFrameFlag(const Flag: UInt32): UInt64; +begin + // Frame flag is in form $IIIIIIIIFFFFFFFF where $IIIIIIII is the frame's + // index number and $FFFFFFFF is the 32 bit flag or bitmask of flags + Int64Rec(Result).Hi := UInt32(Index); + Int64Rec(Result).Lo := Flag; +end; + +function TPrefsBaseFrame.ParentForm: TForm; +var + ParentCtrl: TWinControl; // reference to parent controls +begin + // Loop through parent controls until form found or top level parent reached + ParentCtrl := Self.Parent; + while Assigned(ParentCtrl) and not (ParentCtrl is TForm) do + ParentCtrl := ParentCtrl.Parent; + if ParentCtrl is TForm then + Result := ParentCtrl as TForm + else + Result := nil; end; procedure TPrefsBaseFrame.SavePrefs(const Prefs: IPreferences); diff --git a/Src/FrPrintingPrefs.dfm b/Src/FrPrintingPrefs.dfm index 90226b3f2..8f82fa663 100644 --- a/Src/FrPrintingPrefs.dfm +++ b/Src/FrPrintingPrefs.dfm @@ -6,6 +6,17 @@ inherited PrintingPrefsFrame: TPrintingPrefsFrame DesignSize = ( 396 311) + object lblInfo: TLabel + Left = 0 + Top = 264 + Width = 329 + Height = 13 + Alignment = taCenter + Anchors = [akLeft, akTop, akRight] + Caption = + 'Your changes will take effect the next time you start the applic' + + 'ation.' + end object gpOutputOptions: TGroupBox Left = 0 Top = 2 @@ -126,17 +137,4 @@ inherited PrintingPrefsFrame: TPrintingPrefsFrame OnKeyPress = NumEditKeyPress end end - object stInfo: TStaticText - Left = 0 - Top = 256 - Width = 393 - Height = 25 - Alignment = taCenter - Anchors = [akLeft, akTop, akRight] - AutoSize = False - Caption = - 'Your changes will take effect the next time you start the applic' + - 'ation.' - TabOrder = 2 - end end diff --git a/Src/FrPrintingPrefs.pas b/Src/FrPrintingPrefs.pas index 73ec956b0..d5a2b3034 100644 --- a/Src/FrPrintingPrefs.pas +++ b/Src/FrPrintingPrefs.pas @@ -1,15 +1,13 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2007-2012, Peter Johnson (www.delphidabbler.com). + * Copyright (C) 2007-2025, Peter Johnson (gravatar.com/delphidabbler). * - * $Rev$ - * $Date$ + * Implements a frame that allows user to set printing preferences. * - * Implements a frame that allows user to set printing preferences. Designed for - * use as one of the tabs in the Preferences dialogue box. + * Designed for use as one of the pages in the Preferences dialogue box. } @@ -47,7 +45,7 @@ TPrintingPrefsFrame = class(TPrefsBaseFrame) edLeft: TEdit; edRight: TEdit; edTop: TEdit; - stInfo: TStaticText; + lblInfo: TLabel; procedure CheckboxClick(Sender: TObject); procedure NumEditKeyPress(Sender: TObject; var Key: Char); strict private @@ -59,11 +57,15 @@ TPrintingPrefsFrame = class(TPrefsBaseFrame) {Displays preview of appearance of document according to current state of controls. } + public + const + HideRestartMessage = 1; public constructor Create(AOwner: TComponent); override; {Class constructor. Sets up frame object. } - procedure Activate(const Prefs: IPreferences); override; + procedure Activate(const Prefs: IPreferences; const Flags: UInt64); + override; {Called when page activated. Updates controls. @param Prefs [in] Object that provides info used to update controls. } @@ -99,9 +101,10 @@ implementation // Delphi SysUtils, Windows, Graphics, Math, ComCtrls, // Project - FmPreferencesDlg, Hiliter.UAttrs, Hiliter.UHiliters, IntfCommon, UConsts, - UEncodings, UKeysHelper, UPrintInfo, URTFBuilder, URTFStyles, URTFUtils, - UStrUtils, UUtils; + ClassHelpers.RichEdit, + FmPreferencesDlg, Hiliter.UAttrs, Hiliter.UHiliters, IntfCommon, UColours, + UConsts, UEncodings, UFontHelper, UKeysHelper, UPrintInfo, URTFBuilder, + URTFStyles, UStrUtils, UUtils; {$R *.dfm} @@ -144,7 +147,8 @@ TPrintingPrefsPreview = class(TObject) { TPrintingPrefsFrame } -procedure TPrintingPrefsFrame.Activate(const Prefs: IPreferences); +procedure TPrintingPrefsFrame.Activate(const Prefs: IPreferences; + const Flags: UInt64); {Called when page activated. Updates controls. @param Prefs [in] Object that provides info used to update controls. } @@ -196,13 +200,25 @@ procedure TPrintingPrefsFrame.Activate(const Prefs: IPreferences); // Record current user highlighting choices and display initial preview (fHiliteAttrs as IAssignable).Assign(Prefs.HiliteAttrs); DisplayPreview; + + // Show or hide info label depending on custom flag + if IsFlagSupported(Flags) then + lblInfo.Visible := not ( + ExtractFrameFlag(Flags) and HideRestartMessage = HideRestartMessage + ) + else + lblInfo.Visible := True; end; procedure TPrintingPrefsFrame.ArrangeControls; {Arranges controls on frame. Called after frame has been sized. } begin - // Do nothing: all controls arrange themselves using Anchors property + // No alignment needed: + // all controls use Anchors property to arrange themselves + // Set warning font for info label + TFontHelper.SetDefaultFont(lblInfo.Font); + lblInfo.Font.Color := clWarningText; end; procedure TPrintingPrefsFrame.CheckboxClick(Sender: TObject); @@ -364,7 +380,7 @@ procedure TPrintingPrefsPreview.Generate(const UseColor, SyntaxPrint: Boolean); HiliteSource(UseColor, SyntaxPrint, Builder); Builder.EndPara; // Load document into rich edit - TRichEditHelper.Load(fRe, Builder.Render); + fRe.Load(Builder.Render); finally FreeAndNil(Builder); end; diff --git a/Src/FmUserDataPathDlg.FrProgress.dfm b/Src/FrProgress.dfm similarity index 78% rename from Src/FmUserDataPathDlg.FrProgress.dfm rename to Src/FrProgress.dfm index f84a2afaa..40b7f078d 100644 --- a/Src/FmUserDataPathDlg.FrProgress.dfm +++ b/Src/FrProgress.dfm @@ -1,4 +1,4 @@ -object UserDataPathDlgProgressFrame: TUserDataPathDlgProgressFrame +object ProgressFrame: TProgressFrame Left = 0 Top = 0 Width = 320 @@ -14,10 +14,6 @@ object UserDataPathDlgProgressFrame: TUserDataPathDlgProgressFrame BevelKind = bkFlat BevelOuter = bvNone TabOrder = 0 - ExplicitLeft = 16 - ExplicitTop = 16 - ExplicitWidth = 288 - ExplicitHeight = 73 object lblDescription: TLabel Left = 8 Top = 8 diff --git a/Src/FmUserDataPathDlg.FrProgress.pas b/Src/FrProgress.pas similarity index 84% rename from Src/FmUserDataPathDlg.FrProgress.pas rename to Src/FrProgress.pas index 61ed33ee7..d4ae51ab1 100644 --- a/Src/FmUserDataPathDlg.FrProgress.pas +++ b/Src/FrProgress.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2013-2021, Peter Johnson (gravatar.com/delphidabbler). * * Implements a frame that displays a message and progress bar. Designed for use * with the TUserDataPathDlg dialogue box to indicate progress when moving the @@ -14,7 +11,7 @@ } -unit FmUserDataPathDlg.FrProgress; +unit FrProgress; interface @@ -30,7 +27,7 @@ interface /// <summary>Frame that displays a message and a progress bar.</summary> /// <remarks>For use from TUserDataPathDlg to indicate progress when moving /// the user database.</remarks> - TUserDataPathDlgProgressFrame = class(TFrame) + TProgressFrame = class(TFrame) pnlBody: TPanel; prgProgress: TProgressBar; lblDescription: TLabel; @@ -80,39 +77,39 @@ implementation { TUserDataPathDlgProgressFrame } -function TUserDataPathDlgProgressFrame.GetDescription: string; +function TProgressFrame.GetDescription: string; begin Result := lblDescription.Caption; end; -function TUserDataPathDlgProgressFrame.GetProgress: Integer; +function TProgressFrame.GetProgress: Integer; begin Result := prgProgress.Position; end; -function TUserDataPathDlgProgressFrame.GetRange: TRange; +function TProgressFrame.GetRange: TRange; begin Result := TRange.Create(prgProgress.Min, prgProgress.Max); end; -procedure TUserDataPathDlgProgressFrame.SetDescription(const Value: string); +procedure TProgressFrame.SetDescription(const Value: string); begin lblDescription.Caption := Value; end; -procedure TUserDataPathDlgProgressFrame.SetProgress(const Value: Integer); +procedure TProgressFrame.SetProgress(const Value: Integer); begin prgProgress.Position := GetRange.Constrain(Value); end; -procedure TUserDataPathDlgProgressFrame.SetRange(const Value: TRange); +procedure TProgressFrame.SetRange(const Value: TRange); begin prgProgress.Min := Value.Min; prgProgress.Max := Value.Max; prgProgress.Position := Value.Constrain(prgProgress.Position); end; -procedure TUserDataPathDlgProgressFrame.Show(const AlignToCtrl: TWinControl); +procedure TProgressFrame.Show(const AlignToCtrl: TWinControl); begin TCtrlArranger.SetLabelHeight(lblDescription); lblDescription.Top := 8; diff --git a/Src/FrRSSNews.pas b/Src/FrRSSNews.pas deleted file mode 100644 index 66ea17222..000000000 --- a/Src/FrRSSNews.pas +++ /dev/null @@ -1,155 +0,0 @@ -{ - * This Source Code Form is subject to the terms of the Mozilla Public License, - * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ - * - * Copyright (C) 2010-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ - * - * Implements a frame containing a web browser control that is used to display - * the program's RSS news feed. -} - - -unit FrRSSNews; - - -interface - - -uses - // Delphi - OleCtrls, SHDocVw, Classes, Controls, ExtCtrls, - // Project - FrBrowserBase, UCSSBuilder; - -type - - { - TRSSNewsFrame: - Frame containing a web browser control that is used to display the program's - RSS news feed. - } - TRSSNewsFrame = class(TBrowserBaseFrame) - strict protected - procedure BuildCSS(const CSSBuilder: TCSSBuilder); override; - {Generates CSS classes specific to this frame. This CSS is added to that - provided by parent class. - @param CSSBuilder [in] Object used to build the CSS code. - } - public - constructor Create(AOwner: TComponent); override; - {Object constructor. Sets up object and configures web browser control. - @param AOwner [in] Component that owns frame. - } - procedure Initialise; - {Initialises browser control. Loads container HTML document. - } - procedure DisplayContent(const HTML: string); - {Displays HTML content as body of document currently loaded in the browser - control. - @param HTML [in] HTML ode to be displayed. - } - end; - - -implementation - - -uses - // Delphi - Graphics, - // Project - Browser.UUIMgr, UColours, UCSSUtils, UFontHelper, UNulDropTarget; - -{$R *.dfm} - - -{ TRSSNewsFrame } - -procedure TRSSNewsFrame.BuildCSS(const CSSBuilder: TCSSBuilder); - {Generates CSS classes specific to this frame. This CSS is added to that - provided by parent class. - @param CSSBuilder [in] Object used to build the CSS code. - } -var - ContentFont: TFont; // content font used to set CSS properties -begin - inherited; - ContentFont := TFont.Create; - try - TFontHelper.SetContentFont(ContentFont); - with CSSBuilder.AddSelector('body') do - begin - AddProperty(TCSS.BackgroundColorProp(clWindow)); - AddProperty(TCSS.FontProps(ContentFont)); - AddProperty(TCSS.MarginProp(3)); - end; - with CSSBuilder.AddSelector('p') do - begin - AddProperty(TCSS.MarginProp(4, 0, 0, 0)); - end; - with CSSBuilder.AddSelector('dt') do - begin - AddProperty(TCSS.MarginProp(0, 0, 4, 0)); - AddProperty(TCSS.PaddingProp(4)); - AddProperty(TCSS.BackgroundColorProp(clBtnFace)); - AddProperty(TCSS.ColorProp(clBtnText)); - end; - with CSSBuilder.AddSelector('dd') do - begin - AddProperty(TCSS.MarginProp(0, 0, 8, 20)); - end; - with CSSBuilder.AddSelector('.message') do - begin - AddProperty(TCSS.TextAlignProp(ctaCenter)); - AddProperty(TCSS.MarginProp(cssTop, 30)); - AddProperty(TCSS.FontWeightProp(cfwBold)); - end; - with CSSBuilder.AddSelector('.error-heading') do - begin - AddProperty(TCSS.ColorProp(clWarningText)); - AddProperty(TCSS.FontWeightProp(cfwBold)); - end; - with CSSBuilder.AddSelector('.error-message') do - begin - AddProperty(TCSS.MarginProp(cssLeft, 20)); - end; - finally - ContentFont.Free; - end; -end; - -constructor TRSSNewsFrame.Create(AOwner: TComponent); - {Object constructor. Sets up object and configures web browser control. - @param AOwner [in] Component that owns frame. - } -begin - inherited; - // Set up browser control - WBController.UIMgr.ScrollbarStyle := sbsNormal; // normal scroll bars - WBController.UIMgr.Show3dBorder := False; // we'll handle border - WBController.UIMgr.AllowTextSelection := False; // can't select text - WBController.UIMgr.DropTarget := // inhibit drag drop in dialog - TNulDropTarget.Create; -end; - -procedure TRSSNewsFrame.DisplayContent(const HTML: string); - {Displays HTML content as body of document currently loaded in the browser - control. - @param HTML [in] HTML ode to be displayed. - } -begin - WBController.IOMgr.ReplaceExistingBodyHTML(HTML); -end; - -procedure TRSSNewsFrame.Initialise; - {Initialises browser control. Loads container HTML document. - } -begin - WBController.IOMgr.NavigateToResource(HInstance, 'dlg-rssnews.html'); -end; - -end. diff --git a/Src/FrRTFPreview.pas b/Src/FrRTFPreview.pas index f994b30cf..294602dab 100644 --- a/Src/FrRTFPreview.pas +++ b/Src/FrRTFPreview.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2025, Peter Johnson (gravatar.com/delphidabbler). * * Implements a frame used to display previews of RTF documents. } @@ -62,6 +59,7 @@ implementation uses // Project + ClassHelpers.RichEdit, URTFUtils; @@ -83,7 +81,7 @@ procedure TRTFPreviewFrame.LoadContent(const DocContent: TEncodedData); @param DocContent [in] Valid RTF document to be displayed. } begin - TRichEditHelper.Load(reView, TRTF.Create(DocContent)); + reView.Load(TRTFMarkup.Create(DocContent)); end; procedure TRTFPreviewFrame.SetPopupMenu(const Menu: TPopupMenu); diff --git a/Src/FrRTFShowCase.pas b/Src/FrRTFShowCase.pas index a883992b2..1ff93751d 100644 --- a/Src/FrRTFShowCase.pas +++ b/Src/FrRTFShowCase.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2008-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2008-2021, Peter Johnson (gravatar.com/delphidabbler). * * Implements a frame containing a rich edit control that is placed in a "show * case", i.e. behind a transparent control that prevents the RTF control and diff --git a/Src/FrSelectSnippets.pas b/Src/FrSelectSnippets.pas index cb6ee5c7c..6552d8ead 100644 --- a/Src/FrSelectSnippets.pas +++ b/Src/FrSelectSnippets.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2006-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2006-2021, Peter Johnson (gravatar.com/delphidabbler). * * Implements a frame that enables one or more snippets from both the user and * main databases to be selected via a tree view. diff --git a/Src/FrSelectSnippetsBase.pas b/Src/FrSelectSnippetsBase.pas index aa45fefc5..4952a2ace 100644 --- a/Src/FrSelectSnippetsBase.pas +++ b/Src/FrSelectSnippetsBase.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2009-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2009-2021, Peter Johnson (gravatar.com/delphidabbler). * * Provides an abstract base class for frames that enable categorised snippets * to be selected by means of a tree view displaying check boxes. diff --git a/Src/FrSelectUserSnippets.pas b/Src/FrSelectUserSnippets.pas index 813fb1b09..28c684c67 100644 --- a/Src/FrSelectUserSnippets.pas +++ b/Src/FrSelectUserSnippets.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2008-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2008-2021, Peter Johnson (gravatar.com/delphidabbler). * * Implements a frame that enables one or more snippets in the user-defined * database to be selected via a tree view. diff --git a/Src/FrSnippetLayoutPrefs.pas b/Src/FrSnippetLayoutPrefs.pas index 4765f75da..a16cc1da4 100644 --- a/Src/FrSnippetLayoutPrefs.pas +++ b/Src/FrSnippetLayoutPrefs.pas @@ -1,16 +1,14 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2012-2024, Peter Johnson (gravatar.com/delphidabbler). * * Implements a frame that allows user to customise appearance of different * kinds of snippets in main display. - * Designed for use as one of the tabs in the preferences dialog box. + * + * Designed for use as one of the pages in the Preferences dialogue box. } @@ -67,7 +65,8 @@ TSnippetLayoutPrefsFrame = class(TPrefsBaseFrame) public constructor Create(AOwner: TComponent); override; destructor Destroy; override; - procedure Activate(const Prefs: IPreferences); override; + procedure Activate(const Prefs: IPreferences; const Flags: UInt64); + override; procedure Deactivate(const Prefs: IPreferences); override; /// <summary>Checks if preference changes require that main window UI is /// updated.</summary> @@ -85,7 +84,9 @@ implementation // Delphi Windows, Graphics, // Project - FmPreferencesDlg, UClassHelpers, UCtrlArranger; + ClassHelpers.UControls, + ClassHelpers.UGraphics, + FmPreferencesDlg, UCtrlArranger; {$R *.dfm} @@ -149,7 +150,8 @@ procedure TSnippetLayoutPrefsFrame.actIncludeFragmentUpdate(Sender: TObject); and (lbAvailableFragments.ItemIndex >= 0); end; -procedure TSnippetLayoutPrefsFrame.Activate(const Prefs: IPreferences); +procedure TSnippetLayoutPrefsFrame.Activate(const Prefs: IPreferences; + const Flags: UInt64); begin fPageStructs.Assign(Prefs.PageStructures); UpdateFragmentInfo; diff --git a/Src/FrSourcePrefs.dfm b/Src/FrSourcePrefs.dfm index f59527039..4900f194a 100644 --- a/Src/FrSourcePrefs.dfm +++ b/Src/FrSourcePrefs.dfm @@ -1,16 +1,16 @@ inherited SourcePrefsFrame: TSourcePrefsFrame Width = 393 - Height = 327 + Height = 323 ExplicitWidth = 393 - ExplicitHeight = 327 + ExplicitHeight = 323 DesignSize = ( 393 - 327) + 323) object gbSourceCode: TGroupBox Left = 0 Top = 0 Width = 393 - Height = 201 + Height = 219 Anchors = [akLeft, akTop, akRight] Caption = ' Source code formatting ' TabOrder = 0 @@ -56,10 +56,18 @@ inherited SourcePrefsFrame: TSourcePrefsFrame Caption = '&Truncate comments to one paragraph' TabOrder = 2 end + object chkUnitImplComments: TCheckBox + Left = 8 + Top = 195 + Width = 345 + Height = 17 + Caption = 'Repeat comments in &unit implemenation section' + TabOrder = 3 + end end object gbFileFormat: TGroupBox Left = 0 - Top = 207 + Top = 229 Width = 393 Height = 81 Anchors = [akLeft, akTop, akRight] diff --git a/Src/FrSourcePrefs.pas b/Src/FrSourcePrefs.pas index c5af98062..c27caf5fa 100644 --- a/Src/FrSourcePrefs.pas +++ b/Src/FrSourcePrefs.pas @@ -1,15 +1,13 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2006-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2006-2025, Peter Johnson (gravatar.com/delphidabbler). * * Implements a frame that allows user to set source code preferences. - * Designed for use as one of the tabs in the Preferences dialogue box. + * + * Designed for use as one of the pages in the Preferences dialogue box. } @@ -45,6 +43,7 @@ TSourcePrefsFrame = class(TPrefsBaseFrame) lblCommentStyle: TLabel; lblSnippetFileType: TLabel; chkTruncateComments: TCheckBox; + chkUnitImplComments: TCheckBox; procedure cbCommentStyleChange(Sender: TObject); procedure cbSnippetFileTypeChange(Sender: TObject); strict private @@ -77,7 +76,8 @@ TSourcePrefsFrame = class(TPrefsBaseFrame) constructor Create(AOwner: TComponent); override; {Class constructor. Initialises controls. } - procedure Activate(const Prefs: IPreferences); override; + procedure Activate(const Prefs: IPreferences; const Flags: UInt64); + override; {Called when page activated. Updates controls. @param Prefs [in] Object that provides info used to update controls. } @@ -113,6 +113,7 @@ implementation // Delphi SysUtils, Math, // Project + ClassHelpers.RichEdit, FmPreferencesDlg, Hiliter.UAttrs, Hiliter.UFileHiliter, Hiliter.UHiliters, IntfCommon, UConsts, UCtrlArranger, URTFUtils; @@ -122,16 +123,19 @@ implementation resourcestring // File type descriptions - sHTMLFileDesc = 'HTML'; + sHTML5FileDesc = 'HTML 5'; + sXHTMLFileDesc = 'XHTML'; sRTFFileDesc = 'Rich text'; sPascalFileDesc = 'Pascal'; sTextFileDesc = 'Plain text'; + sMarkdownFileDesc = 'Markdown'; const // Maps source code file types to descriptions cFileDescs: array[TSourceFileType] of string = ( - sTextFileDesc, sPascalFileDesc, sHTMLFileDesc, sRTFFileDesc + sTextFileDesc, sPascalFileDesc, sHTML5FileDesc, sXHTMLFileDesc, + sRTFFileDesc, sMarkdownFileDesc ); @@ -159,7 +163,7 @@ TSourcePrefsPreview = class(TObject) @param HiliteAttrs [in] Attributes of highlighter used to render preview. } - function Generate: TRTF; + function Generate: TRTFMarkup; {Generate RTF code used to render preview. @return Required RTF code. } @@ -168,7 +172,8 @@ TSourcePrefsPreview = class(TObject) { TSourcePrefsFrame } -procedure TSourcePrefsFrame.Activate(const Prefs: IPreferences); +procedure TSourcePrefsFrame.Activate(const Prefs: IPreferences; + const Flags: UInt64); {Called when page activated. Updates controls. @param Prefs [in] Object that provides info used to update controls. } @@ -177,6 +182,7 @@ procedure TSourcePrefsFrame.Activate(const Prefs: IPreferences); SelectSourceFileType(Prefs.SourceDefaultFileType); SelectCommentStyle(Prefs.SourceCommentStyle); chkTruncateComments.Checked := Prefs.TruncateSourceComments; + chkUnitImplComments.Checked := Prefs.CommentsInUnitImpl; chkSyntaxHighlighting.Checked := Prefs.SourceSyntaxHilited; (fHiliteAttrs as IAssignable).Assign(Prefs.HiliteAttrs); fHiliteAttrs.ResetDefaultFont; @@ -194,13 +200,15 @@ procedure TSourcePrefsFrame.ArrangeControls; TCtrlArranger.AlignVCentres(20, [lblCommentStyle, cbCommentStyle]); TCtrlArranger.MoveBelow([lblCommentStyle, cbCommentStyle], frmPreview, 8); TCtrlArranger.MoveBelow(frmPreview, chkTruncateComments, 8); - gbSourceCode.ClientHeight := TCtrlArranger.TotalControlHeight(gbSourceCode) - + 10; TCtrlArranger.AlignVCentres(20, [lblSnippetFileType, cbSnippetFileType]); TCtrlArranger.MoveBelow( [lblSnippetFileType, cbSnippetFileType], chkSyntaxHighlighting, 8 ); + TCtrlArranger.MoveBelow(chkTruncateComments, chkUnitImplComments, 8); + + gbSourceCode.ClientHeight := TCtrlArranger.TotalControlHeight(gbSourceCode) + + 10; gbFileFormat.ClientHeight := TCtrlArranger.TotalControlHeight(gbFileFormat) + 10; @@ -214,7 +222,7 @@ procedure TSourcePrefsFrame.ArrangeControls; TCtrlArranger.AlignLefts( [ cbCommentStyle, frmPreview, cbSnippetFileType, chkSyntaxHighlighting, - chkTruncateComments + chkTruncateComments, chkUnitImplComments ], Col2Left ); @@ -267,6 +275,7 @@ procedure TSourcePrefsFrame.Deactivate(const Prefs: IPreferences); begin Prefs.SourceCommentStyle := GetCommentStyle; Prefs.TruncateSourceComments := chkTruncateComments.Checked; + Prefs.CommentsInUnitImpl := chkUnitImplComments.Checked; Prefs.SourceDefaultFileType := GetSourceFileType; Prefs.SourceSyntaxHilited := chkSyntaxHighlighting.Checked; end; @@ -344,6 +353,7 @@ procedure TSourcePrefsFrame.UpdateControlState; chkSyntaxHighlighting.Enabled := TFileHiliter.IsHilitingSupported(GetSourceFileType); chkTruncateComments.Enabled := GetCommentStyle <> csNone; + chkUnitImplComments.Enabled := GetCommentStyle <> csNone; end; procedure TSourcePrefsFrame.UpdatePreview; @@ -357,8 +367,7 @@ procedure TSourcePrefsFrame.UpdatePreview; // Generate and display preview with required comment style Preview := TSourcePrefsPreview.Create(GetCommentStyle, fHiliteAttrs); try - // Display preview - TRichEditHelper.Load(frmPreview.RichEdit, Preview.Generate); + frmPreview.RichEdit.Load(Preview.Generate); finally Preview.Free; end; @@ -399,12 +408,14 @@ constructor TSourcePrefsPreview.Create(const CommentStyle: TCommentStyle; fHiliteAttrs := HiliteAttrs; end; -function TSourcePrefsPreview.Generate: TRTF; +function TSourcePrefsPreview.Generate: TRTFMarkup; {Generate RTF code used to render preview. @return Required RTF code. } begin - Result := TRTF.Create(TRTFDocumentHiliter.Hilite(SourceCode, fHiliteAttrs)); + Result := TRTFMarkup.Create( + TRTFDocumentHiliter.Hilite(SourceCode, fHiliteAttrs) + ); end; function TSourcePrefsPreview.SourceCode: string; diff --git a/Src/FrTextPreview.pas b/Src/FrTextPreview.pas index 7efce8cf8..a0b91a59a 100644 --- a/Src/FrTextPreview.pas +++ b/Src/FrTextPreview.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). * * Implements a frame used to display previews of plain text documents. } diff --git a/Src/FrTitled.pas b/Src/FrTitled.pas index 12cf6d368..c38712d9a 100644 --- a/Src/FrTitled.pas +++ b/Src/FrTitled.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). * * Implements a base class for frames that display a title bar. } diff --git a/Src/FrUpdatePrefs.dfm b/Src/FrUpdatePrefs.dfm deleted file mode 100644 index 59ec75a94..000000000 --- a/Src/FrUpdatePrefs.dfm +++ /dev/null @@ -1,38 +0,0 @@ -inherited UpdatePrefsFrame: TUpdatePrefsFrame - HelpType = htKeyword - HelpKeyword = 'UpdatePrefs' - object lblProgAutoCheckFreq: TLabel - Left = 3 - Top = 12 - Width = 135 - Height = 13 - Caption = 'Check for &program updates:' - FocusControl = cbProgAutoCheckFreq - end - object lblDBAutoCheckFreq: TLabel - Left = 3 - Top = 52 - Width = 140 - Height = 13 - Caption = 'Check for &database updates:' - FocusControl = cbDBAutoCheckFreq - end - object cbProgAutoCheckFreq: TComboBox - Left = 172 - Top = 9 - Width = 124 - Height = 21 - Style = csDropDownList - TabOrder = 0 - OnChange = CBAutoCheckFreqChanged - end - object cbDBAutoCheckFreq: TComboBox - Left = 172 - Top = 49 - Width = 124 - Height = 21 - Style = csDropDownList - TabOrder = 1 - OnChange = CBAutoCheckFreqChanged - end -end diff --git a/Src/FrUpdatePrefs.pas b/Src/FrUpdatePrefs.pas deleted file mode 100644 index 0bfc2e8bd..000000000 --- a/Src/FrUpdatePrefs.pas +++ /dev/null @@ -1,218 +0,0 @@ -{ - * This Source Code Form is subject to the terms of the Mozilla Public License, - * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ - * - * Copyright (C) 2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ - * - * Implements a frame to be hosted in the preferences diaogue box that permits - * the user to set their application and database update preferences. -} - - -unit FrUpdatePrefs; - - -interface - - -uses - // Delphi - Classes, Controls, StdCtrls, - // Project - FrPrefsBase, UPreferences; - - -type - /// <summary>Frame that allows the user to set their application and database - /// update preferences.</summary> - /// <remarks>Designed for use within the preferences dialog box.</remarks> - TUpdatePrefsFrame = class(TPrefsBaseFrame) - lblProgAutoCheckFreq: TLabel; - lblDBAutoCheckFreq: TLabel; - cbProgAutoCheckFreq: TComboBox; - cbDBAutoCheckFreq: TComboBox; - /// <summary>Handles OnChange events on auto-update frequency check boxes. - /// Sets changed flag since display may need to be updated.</summary> - procedure CBAutoCheckFreqChanged(Sender: TObject); - strict private - var - /// <summary>Flag indicating if user has changed any update frequencies. - /// </summary> - fChanged: Boolean; - /// <summary>Populates the given combo box with values for each available - /// auto update check frequency.</summary> - procedure PopulateFrequencyCB(const CB: TComboBox); - /// <summary>Gets the currently selected auto-update frequency from the - /// given combo box.</summary> - function GetFrequencyFromCB(const CB: TComboBox): Word; - /// <summary>Selects the given combo box item that relates to the given - /// auto-update check frequency.</summary> - procedure SelectFrequencyInCB(const CB: TComboBox; const Freq: Word); - public - /// <summary>Constructs new frame instance and initialises controls. - /// </summary> - constructor Create(AOwner: TComponent); override; - /// <summary>Updates controls to reflect values recorded in given - /// preferences object.</summary> - /// <remarks>Called when the page is activated.</remarks> - procedure Activate(const Prefs: IPreferences); override; - /// <summary>Updates given preferences object from values entered in - /// controls.</summary> - /// <remarks>Called when the page is deactivated.</remarks> - procedure Deactivate(const Prefs: IPreferences); override; - /// <summary>Checks if preference changes require that main window UI is - /// updated.</summary> - /// <returns>Boolean. Always False.</returns> - /// <remarks>Called when dialog box containing frame is closing.</remarks> - function UIUpdated: Boolean; override; - /// <summary>Arranges the frame's controls.</summary> - /// <remarks>Called after frame has been (re)sized.</remarks> - procedure ArrangeControls; override; - /// <summary>Returns the caption that is to be displayed to identify this - /// to the user.</summary> - function DisplayName: string; override; - /// <summary>Returns a number that determines the position of this frame - /// relative to other frames in host control.</summary> - class function Index: Byte; override; - end; - - -implementation - - -uses - // Delphi - Math, - // Project - FmPreferencesDlg, UCtrlArranger, UGraphicUtils; - - -{$R *.dfm} - - -{ TUpdatePrefsFrame } - -procedure TUpdatePrefsFrame.Activate(const Prefs: IPreferences); -begin - SelectFrequencyInCB(cbProgAutoCheckFreq, Prefs.AutoCheckProgramFrequency); - SelectFrequencyInCB(cbDBAutoCheckFreq, Prefs.AutoCheckDatabaseFrequency); -end; - -procedure TUpdatePrefsFrame.ArrangeControls; -begin - TCtrlArranger.AlignLefts( - [lblProgAutoCheckFreq, lblDBAutoCheckFreq], 0 - ); - TCtrlArranger.AlignLefts( - [cbProgAutoCheckFreq, cbDBAutoCheckFreq], - Max( - StringExtent(lblProgAutoCheckFreq.Caption, lblProgAutoCheckFreq.Font).cx, - StringExtent(lblDBAutoCheckFreq.Caption, lblDBAutoCheckFreq.Font).cx - ) + 12 - ); - TCtrlArranger.AlignVCentres(0, [lblProgAutoCheckFreq, cbProgAutoCheckFreq]); - TCtrlArranger.AlignVCentres( - TCtrlArranger.BottomOf([lblProgAutoCheckFreq, cbProgAutoCheckFreq], 12), - [lblDBAutoCheckFreq, cbDBAutoCheckFreq] - ); -end; - -procedure TUpdatePrefsFrame.CBAutoCheckFreqChanged(Sender: TObject); -begin - fChanged := True; -end; - -constructor TUpdatePrefsFrame.Create(AOwner: TComponent); -begin - inherited; - PopulateFrequencyCB(cbProgAutoCheckFreq); - PopulateFrequencyCB(cbDBAutoCheckFreq); -end; - -procedure TUpdatePrefsFrame.Deactivate(const Prefs: IPreferences); -begin - if cbProgAutoCheckFreq.ItemIndex >= 0 then - Prefs.AutoCheckProgramFrequency := GetFrequencyFromCB(cbProgAutoCheckFreq); - if cbDBAutoCheckFreq.ItemIndex >= 0 then - Prefs.AutoCheckDatabaseFrequency := GetFrequencyFromCB(cbDBAutoCheckFreq); -end; - -function TUpdatePrefsFrame.DisplayName: string; -resourcestring - sDisplayName = 'Updates'; -begin - Result := sDisplayName; -end; - -function TUpdatePrefsFrame.GetFrequencyFromCB(const CB: TComboBox): Word; -begin - if CB.ItemIndex = -1 then - Exit(0); - Result := Word(CB.Items.Objects[CB.ItemIndex]); -end; - -class function TUpdatePrefsFrame.Index: Byte; -begin - Result := 60; -end; - -procedure TUpdatePrefsFrame.PopulateFrequencyCB(const CB: TComboBox); -resourcestring - s0 = 'Never'; - s1 = 'Daily'; - s3 = 'Every 3 days'; - s7 = 'Weekly'; - s14 = 'Fortnightly'; - s28 = 'Monthly'; -const - FreqMap: array[0..5] of record - Text: string; - Value: Word; - end = ( - (Text: s0; Value: 0), - (Text: s1; Value: 1), - (Text: s3; Value: 3), - (Text: s7; Value: 7), - (Text: s14; Value: 14), - (Text: s28; Value: 28) - ); -var - I: Integer; -begin - CB.Clear; - for I := Low(FreqMap) to High(FreqMap) do - CB.Items.AddObject(FreqMap[I].Text, TObject(FreqMap[I].Value)); -end; - -procedure TUpdatePrefsFrame.SelectFrequencyInCB(const CB: TComboBox; - const Freq: Word); -var - I: Integer; -begin - for I := 0 to Pred(CB.Items.Count) do - begin - if Word(CB.Items.Objects[I]) = Freq then - begin - CB.ItemIndex := I; - Exit; - end; - end; - CB.ItemIndex := -1; -end; - -function TUpdatePrefsFrame.UIUpdated: Boolean; -begin - Result := fChanged; -end; - -initialization - -// Register frame with preferences dialog box -TPreferencesDlg.RegisterPage(TUpdatePrefsFrame); - -end. - diff --git a/Src/HTML.hrc b/Src/HTML.hrc index 222a08d7e..600d279b8 100644 --- a/Src/HTML.hrc +++ b/Src/HTML.hrc @@ -1,11 +1,8 @@ # This Source Code Form is subject to the terms of the Mozilla Public License, # v. 2.0. If a copy of the MPL was not distributed with this file, You can -# obtain one at http://mozilla.org/MPL/2.0/ +# obtain one at https://mozilla.org/MPL/2.0/ # -# Copyright (C) 2005-2016, Peter Johnson (www.delphidabbler.com). -# -# $Rev$ -# $Date$ +# Copyright (C) 2005-2022, Peter Johnson (gravatar.com/delphidabbler). # # Manifest file used to generate HTML.res resource file. @@ -22,33 +19,33 @@ Res\HTML\dlg-about-database-tplt.html Res\HTML\dlg-easter-egg.html Res\CSS\easteregg.css Res\Scripts\easteregg.js -Res\Scripts\3rdParty\jquery-1.8.0.min.js +Res\Scripts\3rdParty\jquery-1.12.4.min.js Res\Scripts\3rdParty\jquery.cycle.lite.js # compiler error log Res\HTML\dlg-comperror-tplt.html -# donate dialogue -Res\HTML\dlg-donate.html - -# email privacy information for display in frames -Res\HTML\frm-emailprivacy.html - -# active text preview dialogueue +# active text preview dialogue Res\HTML\dlg-activetext-preview-tplt.html -# news dialogue -Res\HTML\dlg-rssnews.html - # SWAG import dialogue -Res\HTML\dlg-swag-import-intro.html +Res\HTML\dlg-swag-import-intro-tplt.html Res\HTML\dlg-swag-import-outro-tplt.html -# code submission wizard -Res\HTML\dlg-codesubmit-intro.html -Res\HTML\dlg-codesubmit-license.html -Res\HTML\dlg-codesubmit-submit.html -Res\HTML\dlg-codesubmit-finished.html +# get delphidabbler snippets database wizard +Res\HTML\dlg-dbupdate-intro-tplt.html +Res\HTML\dlg-dbupdate-load.html +Res\HTML\dlg-dbupdate-finish.html + +# what's new dialogue +Res\HTML\dlg-whatsnew.html + +# delete database dialogue +Res\HTML\dlg-dbdelete.html + +# auto-register new compilers dialogue +Res\HTML\dlg-register-compilers.html + # Detail pane pages, scripts and CSS # ================================== @@ -137,7 +134,3 @@ Res\Img\Egg\play-btn.png Res\Img\Egg\play-btn-hover.png Res\Img\Egg\doggy-pics.png Res\Img\Egg\blue-paper.png - -# donate dialogue - � sign image -Res\Img\pound-sign.png - diff --git a/Src/Help/CSS/codesnip.css b/Src/Help/CSS/codesnip.css index f769228c1..333f831cf 100644 --- a/Src/Help/CSS/codesnip.css +++ b/Src/Help/CSS/codesnip.css @@ -1,12 +1,9 @@ /* * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). * * CSS for the CodeSnip HTML help file. */ @@ -115,7 +112,7 @@ tr { margin: 0; } -td { +td, th { padding: 4px; margin: 1px; } @@ -125,6 +122,18 @@ table.menu { margin-left: 1em; } +table.bordered { + padding: 0; +} + +table.bordered { + background-color: #ccc; +} + +table.bordered th { + background-color: white; +} + table.menu td.img { width: 24px; height: 24px; @@ -144,10 +153,20 @@ table.menu td.mainitem { background-color: #eee; } -table.menu td.desc { +table.menu td.desc, +table.bordered td { background-color: white; } +table.bordered td.centre { + text-align: center; +} + +pre.reml, +code.reml { + color: purple; +} + .indent { margin-left: 2em; } diff --git a/Src/Help/CodeSnip.hhp b/Src/Help/CodeSnip.hhp index fdb87ef30..c7bb1a367 100644 --- a/Src/Help/CodeSnip.hhp +++ b/Src/Help/CodeSnip.hhp @@ -1,18 +1,14 @@ ; This Source Code Form is subject to the terms of the Mozilla Public License, ; v. 2.0. If a copy of the MPL was not distributed with this file, You can -; obtain one at http://mozilla.org/MPL/2.0/ +; obtain one at https://mozilla.org/MPL/2.0/ ; -; Copyright (C) 2005-2014, Peter Johnson (www.delphidabbler.com). -; -; $Rev$ -; $Date$ +; Copyright (C) 2005-2025, Peter Johnson (gravatar.com/delphidabbler). ; ; CodeSnip help project file. - [OPTIONS] Compatibility=1.1 -Compiled file=..\..\Exe\CodeSnip.chm +Compiled file=..\..\_build\exe\CodeSnip.chm Contents file=TOC.hhc Default topic=HTML\welcome.htm Display compile progress=No @@ -20,50 +16,53 @@ Index file=Index.hhk Language=0x809 English (United Kingdom) Title=CodeSnip Help - [FILES] HTML\about_compiler_checks.htm +HTML\about_swag.htm HTML\detail_pane.htm HTML\dlg_about.htm HTML\dlg_addcategory.htm HTML\dlg_backup.htm HTML\dlg_configcompilers.htm HTML\dlg_deletecategory.htm +HTML\dlg_deleteuserdb.htm HTML\dlg_dependencies.htm +HTML\dlg_dependencies_edit.htm HTML\dlg_duplicatesnippet.htm HTML\dlg_editsnippet.htm HTML\dlg_elementcolour.htm HTML\dlg_export.htm HTML\dlg_exportfile.htm +HTML\dlg_favourites.htm HTML\dlg_findcompiler.htm HTML\dlg_findtext.htm HTML\dlg_findxrefs.htm HTML\dlg_firstrun.htm +HTML\dlg_hilitemgr.htm HTML\dlg_import.htm HTML\dlg_importfile.htm HTML\dlg_loadselection.htm -HTML\dlg_news.htm +HTML\dlg_moveuserdb.htm HTML\dlg_pagesetup.htm HTML\dlg_preferences.htm HTML\dlg_prefs_codegen.htm HTML\dlg_prefs_display.htm HTML\dlg_prefs_general.htm HTML\dlg_prefs_hiliter.htm -HTML\dlg_prefs_news.htm HTML\dlg_prefs_printing.htm HTML\dlg_prefs_snippetlayout.htm HTML\dlg_prefs_sourcecode.htm HTML\dlg_print.htm -HTML\dlg_programupdates.htm -HTML\dlg_proxyserver.htm -HTML\dlg_register.htm +HTML\dlg_registercompilers.htm HTML\dlg_renamecategory.htm HTML\dlg_restore.htm +HTML\dlg_savehiliter.htm +HTML\dlg_saveinfo.htm HTML\dlg_saveselection.htm HTML\dlg_savesnippet.htm HTML\dlg_selectcompiler.htm HTML\dlg_selectroutines.htm -HTML\dlg_submit.htm +HTML\dlg_swagimport.htm HTML\dlg_testcompile.htm HTML\dlg_trappedbugreport.htm HTML\dlg_update.htm @@ -87,7 +86,6 @@ HTML\menu_view.htm HTML\navigation.htm HTML\new.htm HTML\overview_pane.htm -HTML\privacy_statement.htm HTML\quickstart.htm HTML\reml.htm HTML\snippet_class.htm @@ -97,27 +95,18 @@ HTML\snippet_kinds.htm HTML\snippet_routine.htm HTML\snippet_type.htm HTML\snippet_unit.htm -HTML\tasks.htm HTML\task_addsnippets.htm +HTML\task_backup.htm HTML\task_copysnippet.htm HTML\task_customise.htm +HTML\task_deleteuserdb.htm HTML\task_export.htm HTML\task_generateunit.htm HTML\task_printroutine.htm +HTML\task_registercompilers.htm HTML\task_savesnippet.htm HTML\task_search.htm -HTML\task_submit.htm HTML\task_testcompile.htm HTML\task_update.htm +HTML\tasks.htm HTML\welcome.htm -HTML\dlg_favourites.htm -HTML\dlg_moveuserdb.htm -HTML\dlg_savehiliter.htm -HTML\dlg_hilitemgr.htm -HTML\dlg_prefs_updates.htm -HTML\notifiy_program_update.htm -HTML\notifiy_database_update.htm -HTML\dlg_dependencies_edit.htm -HTML\about_swag.htm -HTML\dlg_swagimport.htm -HTML\submission-rights.htm diff --git a/Src/Help/HTML/about_compiler_checks.htm b/Src/Help/HTML/about_compiler_checks.htm index 29844b5e9..ab4a5784f 100644 --- a/Src/Help/HTML/about_compiler_checks.htm +++ b/Src/Help/HTML/about_compiler_checks.htm @@ -2,12 +2,9 @@ <!-- * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2015, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2023, Peter Johnson (gravatar.com/delphidabbler). * * Help topic explaining compiler checks. --> @@ -37,14 +34,15 @@ <h1> </p> <p> The supported compilers are the Win32 Delphi compilers from Delphi 2 to - Delphi 10 Seattle and Free Pascal. + Delphi 12 Athens and Free Pascal. </p> <h2> Configuring CodeSnip </h2> <p> Before <em>CodeSnip</em> can use the compilers it needs to be told about - them. This is done using the <a + them. Compilers are registered for use by <em>CodeSnip</em> This is done + using the <a href="dlg_configcompilers.htm">Configure Compilers</a> dialogue box that is accessed from the <em>Tools</em> menu. </p> @@ -55,11 +53,34 @@ <h2> <em>Configure Compilers</em> dialogue box. </p> <p> - Compiler configuration is stored on a per user basis, i.e. each user - account maintains separate compiler configuration information. Therefore - each user must configure compilers separately. The reason for this is that - each user may wish to use a different set of available compilers and / or - may wish to use different compiler switches and search paths. + When using the standard version of CodeSnip, compiler configuration is + stored on a per user basis, i.e. each user account maintains separate + compiler configuration information. Therefore each user must configure + compilers separately. The reason for this is that each user may wish to + use a different set of available compilers and / or may wish to use + different compiler switches and search paths. + </p> + <p> + The portable version of CodeSnip only maintains one set of compiler + configuration information per installation. + </p> + <h3> + Automatic detection of Delphi compilers + </h3> + <p> + By default, when <em>CodeSnip</em> starts up, it will check if there are + any versions of Delphi installed on the user's system that + <em>CodeSnip</em> could use, but that are not registered for use. Any such + compilers are displayed in the <a + href="dlg_registercompilers.htm" + >Unregistered Delphi Installation(s) Detected</a> dialogue box where you + can choose whether or not to register them. + </p> + <p> + It is possible to switch off automatic detection on either a global or a + per compiler basis. For more information see the <a + href="task_registercompilers.htm" + >Register Compilers with <em>CodeSnip</em></a> topic. </p> <h2> Running the tests @@ -95,7 +116,7 @@ <h2> warnings following a test compile, you may be able to inhibit them by configuring <em>CodeSnip</em>'s code generator to emit suitable compiler directives. This is done using the <a href="dlg_prefs_codegen.htm">Code - Generation</a> tab of the <a href="dlg_preferences.htm">Preferences</a> + Generation</a> page of the <a href="dlg_preferences.htm">Preferences</a> dialogue box. Display the dialogue box using the <em>Tools | Preferences</em> menu option. </p> diff --git a/Src/Help/HTML/about_swag.htm b/Src/Help/HTML/about_swag.htm index 3f39dc08c..8ce1698fa 100644 --- a/Src/Help/HTML/about_swag.htm +++ b/Src/Help/HTML/about_swag.htm @@ -2,12 +2,9 @@ <!-- * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2013-2021, Peter Johnson (gravatar.com/delphidabbler). * * Help topic explaining the SWAG database. --> @@ -31,24 +28,34 @@ <h1> </h1> <p> SWAG is a collection of source code and program examples for the Pascal - programming language that was maintained up to the end of 1997. + programming language that was maintained up to the end of 1997. Each chunk + of source code or example is known as a <em>packet</em>. </p> <p> - Although the SWAG database is old it is extensive and some of the code is + Although the SWAG database is old, it is extensive and some of the code is still relevant for today's programs. </p> <p> <em>CodeSnip</em> provides the <a href="dlg_swagimport.htm">SWAG Import Wizard</a> that enables you to - browse the SWAG database and import any of its snippets into your own - snippets database. + browse a local copy of the SWAG database and convert any of its packets + into snippets that can be imported into your local snippets database </p> <p> - You can browse the archive online using the <a - href="http://swag.delphidabbler.com/" + You can download the database from the <a + href="https://github.com/delphidabbler/swag" class="weblink" target="_blank" - >DelphiDabbler SWAG Viewer</a>. + ><code>delphidabbler/swag</code></a> project on GitHub. Go to the + <em>releases</em> tab and download the latest supported version†. + Unpack the zip file into an empty folder then run the SWAG Import Wizard + and navigate to the <code>swag</code> sub-directory of the folder where + you extracted the zip file . The wizard reads the database and guides you + through the import process. + </p> + <p> + † The first page of the SWAG Import Wizard specifies which versions + of SWAG are supported by CodeSnip. </p> </body> </html> diff --git a/Src/Help/HTML/detail_pane.htm b/Src/Help/HTML/detail_pane.htm index e1dce3cad..cd27e930f 100644 --- a/Src/Help/HTML/detail_pane.htm +++ b/Src/Help/HTML/detail_pane.htm @@ -2,12 +2,9 @@ <!-- * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). * * Help topic for Details pane. --> @@ -35,7 +32,7 @@ <h2> Tabs </h2> <p> - Information is be displayed in one or more tabs. Items selected in the + Information is displayed in one or more tabs. Items selected in the <a href="overview_pane.htm">Overview pane</a> are normally displayed in the currently selected tab, overwriting previous contents. This behaviour can be changed as follows: @@ -60,13 +57,13 @@ <h2> Newly created snippets and categories may be displayed in new tabs. This happens only if the feature is selected in the <a href="dlg_prefs_display.htm" - >Display</a> tab of the <a + >Display</a> page of the <a href="dlg_preferences.htm" >Preferences</a> dialogue box. </li> </ul> <p> - When more than one tab is displayed you can quickly cycle through them + When multiple tabs are displayed you can quickly cycle through them from left to right by pressing <span class="smallcaps">Ctrl+Tab</span> or from right to left using <span class="smallcaps">Shift+Ctrl+Tab</span>. </p> @@ -77,7 +74,7 @@ <h2> <li> Close the selected tab using <em>Close Tab</em> menu option displayed on the <a href="menu_view.htm">View</a> menu, the detail pane's context - menu and any tab's context menu. Alternatively press + menu or any tab's context menu. Alternatively press <span class="smallcaps">Ctrl+F4</span>. </li> <li> @@ -153,8 +150,8 @@ <h3> </li> </ul> <div> - This information is not displayed for user-defined snippets. In the - place of the images is a link that can be clicked to edit the snippet. + This information is not displayed for user-defined snippets. Instead + there is a link that can be clicked to edit the snippet. </div> </li> <li> @@ -208,7 +205,7 @@ <h3> >snippet kind</a>. The program's default settings for these views can be customised using the <a href="dlg_prefs_snippetlayout.htm" - >Snippet Layout</a> tab of the <a + >Snippet Layout</a> page of the <a href="dlg_preferences.htm" >Preferences</a> dialogue box. </p> @@ -218,15 +215,15 @@ <h3> <p> Selecting a section header (a category, an intial letter or a <a href="snippet_kinds.htm" - >snippet kind</a> in the <a + >snippet kind</a>) in the <a href="overview_pane.htm" >Overview Pane</a> causes a table to be displayed in the detail pane that - lists the name of the snippets contained in the section, along with their + lists the names of the snippets contained in the section, along with their descriptions. Following a search, the listed snippets are just those contained in the current database selection, if any. </p> <p> - Snippet names are links that, when clicked, displays the chosen snippet + Snippet names are links that, when clicked, display the chosen snippet in the detail pane. </p> <p> @@ -245,9 +242,9 @@ <h3> Database Update View </h3> <p> - When the database has been updated, all open tabs are closed and a new tab - created that simply informs that the database has been updated - successfully. + When the main DelphiDabbler Snippets database has been installed or + updated, all open tabs are closed and a new tab is created that simply + informs that the database has been updated successfully. </p> <h3> Empty Tab View @@ -255,8 +252,8 @@ <h3> <p> When a new tab is created (<em>View | New Tab</em> or <span class="smallcaps" - >Ctrl+T</span>, nothing is displayed except "Empty Tab" in light - grey. + >Ctrl+T</span>), nothing is displayed except "Empty Tab" in + light grey. </p> <h2> View History @@ -280,7 +277,7 @@ <h2> >Alt+Right</span>. </p> <p> - You can also jump directly to some recent views by selecting its name from + You can also jump directly to a recent view by selecting its name from the drop-down menus adjacent to the <img alt="Previous button" src="../Images/GoBack.png" @@ -295,22 +292,6 @@ <h2> Deleting or editing an existing snippet or category will clear the history. </p> - <h2> - Snippet and Category Links - </h2> - <p> - Links to snippets or categories may be displayed as part of snippet or - section contents views. - </p> - <p> - Clicking a snippet link displays a snippet view for that snippet in the - current tab of the details pane, Clicking a category link causes a - section contents view to be displayed for the contents of the category, - again in the current tab. - </p> - <p> - Views displayed in this way are added to the history list. - </p> </body> </html> diff --git a/Src/Help/HTML/dlg_about.htm b/Src/Help/HTML/dlg_about.htm index 91cff10e0..f1eaf8480 100644 --- a/Src/Help/HTML/dlg_about.htm +++ b/Src/Help/HTML/dlg_about.htm @@ -2,12 +2,9 @@ <!-- * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2016, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). * * Help topic for About dialogue box. --> @@ -30,18 +27,12 @@ <h1> </h1> <p> This dialogue box provides various pieces of information about - <em>CodeSnip</em> and the <em>Code Snippets Database</em>. It is accessed - from the <em>Help | About</em> menu option. - </p> - <p> - The program's version number is displayed in the heading of the dialogue - box. + <em>CodeSnip</em> and the <em>DelphiDabbler Code Snippets Database</em>. + It is accessed from the <em>Help | About</em> menu option. </p> <p> - If <em>CodeSnip</em> is unregistered then a <em>Register CodeSnip</em> - button will be displayed at the bottom left of the dialogue box. Clicking - this button displays the <a href="dlg_register.htm">Registration - Wizard</a> that can be used to register the program. + The program's name and version number is displayed in the heading of the + dialogue box. </p> <p> In addition the dialogue has three tabs, each of which is described below. @@ -59,9 +50,6 @@ <h2> <li> Credits and Attributions. </li> - <li> - If the program has been registered. - </li> </ul> <h2> About The Database @@ -71,7 +59,8 @@ <h2> </p> <ul class="unspaced"> <li> - Where to find the <em>Code Snippets Database</em> online. + A brief description of the database with a link to its <em>GitHub</em> + project page. </li> <li> Licensing information. @@ -101,8 +90,8 @@ <h3> </li> <li> <em>Main Database Directory</em> – the directory containing a - local copy of the DelphiDabbler Code Snippets Database database. This - directory may be empty if the user has not downloaded the database. + copy of the DelphiDabbler Code Snippets Database. This directory may be + empty if the database has not been installed. </li> <li> <em>User Database Directory</em> – the directory containing the @@ -125,16 +114,16 @@ <h3> </h3> <p> The content of the program's application wide and per-user config files - can be displayed by clicking the <em>View Application Config File</em> and - <em>View Per-User Config File</em> buttons respectively. + can be displayed by clicking the <em>View Application Config File</em> + and <em>View Per-User Config File</em> buttons respectively. </p> <p> - You can find documentation of the meaning of the config file entries in - <em>CodeSnip<em>'s <a + You can find documentation of the config file entries in + <em>CodeSnip</em>'s <a href="https://github.com/delphidabbler/codesnip" class="weblink" target="_blank" - >Git repository</a>. + >GitHub repository</a>. </p> </body> </html> diff --git a/Src/Help/HTML/dlg_addcategory.htm b/Src/Help/HTML/dlg_addcategory.htm index d492d4cd3..be63ebacb 100644 --- a/Src/Help/HTML/dlg_addcategory.htm +++ b/Src/Help/HTML/dlg_addcategory.htm @@ -2,12 +2,9 @@ <!-- * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2009-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2009-2021, Peter Johnson (gravatar.com/delphidabbler). * * Help topic for Add Category dialogue box. --> @@ -41,20 +38,20 @@ <h1> accessed from the <em>Categories | New Category</em> menu option. </p> <p> - Enter a description of the category in the edit box and click OK to add it - to the database. The description you provided will be used to identify the - category in program, so keep it brief. + Enter a description of the category in the edit box and click <em>OK</em> + to add it to the database. The description you provided will be used to + identify the category in the user interface, so keep it brief. </p> <p> - Leading and trailing spaces are ignored. If you enter an existing + Leading and trailing spaces are ignored. If you enter a pre-existing description a warning to that effect is displayed below the edit box. The - <em>OK</em> button will be disabled if you haven't entered any text or if - the entry is not valid. + <em>OK</em> button will be disabled if you haven't entered any text, if + the entry is not valid or if it is a duplicate. </p> <p> - Note that the new category may not appear in the - <a href="overview_pane.htm">overview pane</a> because it is empty. It will - however be available to use when editing snippets. + Depending on your settings, the new category may not appear in the + <a href="overview_pane.htm">overview pane</a>, because the category is + empty. It will however be available to use when editing snippets. </p> </body> </html> diff --git a/Src/Help/HTML/dlg_backup.htm b/Src/Help/HTML/dlg_backup.htm index 96e822f57..0f2d363cb 100644 --- a/Src/Help/HTML/dlg_backup.htm +++ b/Src/Help/HTML/dlg_backup.htm @@ -2,12 +2,9 @@ <!-- * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2008-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2008-2021, Peter Johnson (gravatar.com/delphidabbler). * * Help topic for Save Backup dialogue box. --> @@ -38,7 +35,10 @@ <h1> <p> This Windows standard dialogue box is displayed when the <em>Database | Backup User Database</em> menu option is selected. Use the dialogue to - specify the name and location of the file the will store the backup. + specify the name and location of the file that will store the backup. + </p> + <p> + No extension is added to the specified file name. </p> <p> Note that the entire database is stored in a single file. diff --git a/Src/Help/HTML/dlg_configcompilers.htm b/Src/Help/HTML/dlg_configcompilers.htm index 4244fbafa..787f3bec5 100644 --- a/Src/Help/HTML/dlg_configcompilers.htm +++ b/Src/Help/HTML/dlg_configcompilers.htm @@ -2,12 +2,9 @@ <!-- * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2016, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2023, Peter Johnson (gravatar.com/delphidabbler). * * Help topic for Configure Compilers dialogue box. --> @@ -40,7 +37,13 @@ <h1> Compilers that are installed on the system. It is displayed using the <em>Tools | Configure Compilers</em> menu option. <em>CodeSnip</em> uses these compilers when <a href="about_compiler_checks.htm">test - compiling</a> routines. + compiling</a> snippets. + </p> + <p class="bordered"> + When using the standard version of CodeSnip each logged on user has their + own compiler configuration, so any changes you make will not affect other + users. The portable edition of CodeSnip only supports one set of + configuration data per installation. </p> <p> Supported compilers are listed on the left hand side of the dialogue. If a @@ -48,17 +51,17 @@ <h1> compilers (or compilers that are not installed on the local system) appear in plain text. </p> - <p> - Any changes you make do not affect other users. - </p> <p> Selecting a compiler on the left displays information about it on the right hand side of the dialogue. Here you enter or edit the required - information on three tabs, as follows. + information on four or five tabs, as follows. </p> <h2> Compiler Tab </h2> + <h3> + Compiler file name + </h3> <p> Enter the the full path to the compiler's executable file in the <em>Enter compiler executable file name</em> edit box. This must be file name of @@ -67,7 +70,7 @@ <h2> by clicking the ellipsis button to the right of the edit box. The chosen file name is entered in the edit box. As soon as the cursor leaves the edit box the file name is checked for validity – it must exist and - be an executable file. If it valid it is used and compiler name will be + be an executable file. If it is valid the compiler name will be emboldened. </p> <p> @@ -75,8 +78,11 @@ <h2> path to its executable file. The <em>Enter compiler executable file name</em> edit box is cleared. </p> + <h3> + Display compiler results + </h3> <p> - Finally, you can choose whether the compiler is included in the table of + You can choose whether the compiler is included in the table of compile results displayed in the <a href="detail_pane.htm">Details Pane</a>. This is done using the <em>Display results for this compiler in details pane</em> check box. Tick the box to display the compiler's @@ -93,12 +99,37 @@ <h2> doing that will cause a warning message to be displayed in place of the compiler results table. To switch off compiler results completely you should use the <a href="dlg_prefs_snippetlayout.htm">Snippet Layout</a> - tab of the <a href="dlg_preferences.htm">Preferences</a> dialogue box to + page of the <a href="dlg_preferences.htm">Preferences</a> dialogue box to remove the <em>Compiler Results Table</em> component from the different kinds of snippet pages. </p> + <h3> + Automatic compiler detection & registration + </h3> + <p> + By default <em>CodeSnip </em> will automatically detect and register + installed Delphi compilers for use with the program. This can happen in + two ways: + </p> + <ol> + <li> + When the <em>Detect Delphi Compilers</em> button is clicked (see below). + </li> + <li> + Optionally, when the program starts up. + </li> + </ol> + <p> + You can prevent a specific Delphi compiler from being automatically + registered by clearing the <em>Permit auto-detection & registration of + this compiler</em> check box. + </p> + <p class="bordered"> + <strong>Note:</strong> This check box does not appear when the Free Pascal + compiler is selected. + </p> <h2> - Switches + Switches Tab </h2> <p> <em>CodeSnip</em> passes certain switches to the command line of each @@ -157,7 +188,7 @@ <h2> correctly. </p> <h2> - Namespaces + Namespaces Tab </h2> <p class="bordered"> <strong>Note:</strong> This tab appears only if the selected compiler is @@ -204,7 +235,7 @@ <h2> see your compiler's documentation. </p> <h2> - Search Paths + Search Paths Tab </h2> <p> If you have your own or third party units you want your user-defined @@ -281,15 +312,25 @@ <h2> </h2> <p> <em>CodeSnip</em> can automatically detect the presence of Win 32 Delphi - compilers from Delphi 2 to Delphi 10 Seattle. Click the <em>Detect Delphi - Compilers</em> button to do this. Any supported installed version of - Delphi will be recorded. This can save considerable time and avoid - errors. + compilers from Delphi 2 to Delphi 12 Athens. Click the <em>Detect + Delphi Compilers</em> button to do this. Any supported installed version + of Delphi will be recorded<sup>†</sup>. This can save considerable + time and avoid errors. </p> <p> Free Pascal cannot be detected automatically and must be configured manually. </p> + <p> + If the <em>Automatically register newly installed Delphi compilers at + program startup</em> check box is ticked then, when it starts up, + <em>CodeSnip</em> will attempt to register any known Delphi compiler that + is not yet registered with Delphi<sup>†</sup>. + </p> + <p class="bordered"> + † Any version of Delphi for which the <em>Permit auto-detection + & registration of this compiler</em> check box is cleared will not be + recorded. + </p> </body> </html> - diff --git a/Src/Help/HTML/dlg_deletecategory.htm b/Src/Help/HTML/dlg_deletecategory.htm index f50806446..0f8833faf 100644 --- a/Src/Help/HTML/dlg_deletecategory.htm +++ b/Src/Help/HTML/dlg_deletecategory.htm @@ -2,12 +2,9 @@ <!-- * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2009-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2009-2021, Peter Johnson (gravatar.com/delphidabbler). * * Help topic for Delete Category dialogue box. --> @@ -42,19 +39,18 @@ <h1> </p> <p> Select the category to be deleted from the list box. If the selected - category is non-empty, i.e. it contains snippets then it can't be deleted - and a message wil be displayed to that effect. Once a suitable category is - selected simply click the <em>Delete</em> button to delete it. + category is non-empty, i.e. it contains snippets, then it can't be deleted + and a message will be displayed to that effect. Once a suitable category + is selected simply click the <em>Delete</em> button to delete it. </p> <p> The <em>Delete</em> button will be disabled if you haven't selected a - category from the list box or if the category can't be deleted. + category from the list box or if the selected category can't be deleted. </p> <p> You may have noticed that some user defined categories do not appear in - the list of categories to be deleted. This is because some categories are - required by <em>CodeSnip</em> and cannot be deleted, although they can be - renamed. + the list. This is because those categories are reserved for use by + <em>CodeSnip</em> and cannot be deleted, although they can be renamed. </p> </body> </html> diff --git a/Src/Help/HTML/dlg_deleteuserdb.htm b/Src/Help/HTML/dlg_deleteuserdb.htm new file mode 100644 index 000000000..86cd27a83 --- /dev/null +++ b/Src/Help/HTML/dlg_deleteuserdb.htm @@ -0,0 +1,82 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> +<!-- + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/ + * + * Copyright (C) 2022, Peter Johnson (gravatar.com/delphidabbler). + * + * Help topic for Delete User Database dialogue box. +--> +<html> + <head> + <meta + http-equiv="Content-Type" + content="text-html; charset=Windows-1252"> + <title> + Delete User Database Dialogue Box + + + + + + + +

    + Delete User Database Dialogue Box +

    +

    + This dialogue box is displayed when you choose the Database | Delete + User Database menu option. +

    +

    + Proceed with caution. If you give + permission here, all your user-defined snippets will be + deleted without further confirmation. This action + can't be undone. +

    +

    + Click Cancel to back out of this dialogue box. +

    +

    + If you decide you really want to do this you must enter the upper case + text DELETE MY SNIPPETS in the edit + box, then press OK. You will get an error message, and the + snippets won't be deleted, if the text you entered is incorrect. +

    +

    + Once you enter the required text and click OK all user-defined + snippets will be deleted straight away and the main display will be + re-loaded. +

    +

    + You are strongly advised to take a backup of your + snippets before deleting them, in case you change you mind. To do this + first cancel this dialogue box then use the Database | Backup User + Database menu option. +

    +

    + Why would you do this? +

    +

    + It's very unlikely you will need to delete all your snippets, but there + is one use case where this facility is useful. That is when you are moving + snippets from the standard version of CodeSnip to a portable version, or + vice versa. +

    +

    + For further details, see this FAQ. +

    + + \ No newline at end of file diff --git a/Src/Help/HTML/dlg_dependencies.htm b/Src/Help/HTML/dlg_dependencies.htm index 5cf9290db..a165e0879 100644 --- a/Src/Help/HTML/dlg_dependencies.htm +++ b/Src/Help/HTML/dlg_dependencies.htm @@ -2,12 +2,9 @@ @@ -31,14 +28,21 @@

    This dialogue box is displayed by choosing the View | - Dependencies main menu item. It has two tabs: Depends Upon - and Required By. + Dependencies menu item or by pressing + Ctrl+D.

    - Depends Upon Tab + Tabs

    - If the selected snippet has dependenciess (i.e. snippets it requires in + The dialogue box has two tabs: Depends Upon and Required + By. +

    +

    + Depends Upon Tab +

    +

    + If the selected snippet has dependencies (i.e. snippets it depends upon in order to compile) a tree of snippets is displayed that shows the direct and indirect dependencies. When there are no dependencies a message is displayed to that effect. @@ -49,13 +53,27 @@

    error message is displayed. Such a dependency indicates there is an error in the database.

    -

    +

    Required By Tab -

    +

    This tab displays a simple list of snippets that immediately depend on the - selected snippet. Again, if there are no snippets to list a message is + selected snippet. Again, if there are no snippets to list, a message is displayed to that effect.

    +

    + Select & Close button +

    +

    + This button appears to the left of the Close and Help + buttons. It's purpose is to select the snippets listed on the currently + active tab and to display that selection when the dialogue box is closed. + If the active tab lists no snippets then the button is disabled. +

    +

    + Warning: Any selection made using this + button will replace any search results that were previously displayed. No + warning will be displayed. +

    diff --git a/Src/Help/HTML/dlg_dependencies_edit.htm b/Src/Help/HTML/dlg_dependencies_edit.htm index 2d0ac6794..763162c42 100644 --- a/Src/Help/HTML/dlg_dependencies_edit.htm +++ b/Src/Help/HTML/dlg_dependencies_edit.htm @@ -2,12 +2,9 @@ @@ -36,9 +33,9 @@

    Snippets Editor.

    - If the snippet being edited has dependenciess selected in the + If the snippet being edited has dependencies selected in the Dependencies list box then a tree of those snippets is displayed - that shows both the direct and indirect dependencies. When there are no + that shows both direct and indirect dependencies. When there are no dependencies a message is displayed to that effect.

    diff --git a/Src/Help/HTML/dlg_duplicatesnippet.htm b/Src/Help/HTML/dlg_duplicatesnippet.htm index 67edabf68..4f42da611 100644 --- a/Src/Help/HTML/dlg_duplicatesnippet.htm +++ b/Src/Help/HTML/dlg_duplicatesnippet.htm @@ -2,12 +2,9 @@ @@ -37,9 +34,9 @@

    name="dlg_duplicatesnippet">Duplicate Snippet Dialogue Box

    - This dialogue box is used to duplicate the currently selected snippet, - creating a new user defined snippet. The dialogue box is accessed from the - Snippet | Duplicate Snippet menu option. + This dialogue box is used to create a new, user-defined, snippet that is + a duplicate the currently selected snippet. The dialogue box is accessed + from the Snippet | Duplicate Snippet menu option.

    Enter a unique name for the duplicated snippet in the Unique name for @@ -47,7 +44,7 @@

    When duplicating a snippet from the main database it may be possible to use the same name, providing a snippet of that name does not already exist in the user database. If this is the case the suggested name will be - the same as the original snippet. + the same as that of the original snippet.

    The display name of the duplicated snippet can be edited or entered in the @@ -58,25 +55,23 @@

    edit box as the display name.

    - The new snippet's category can be changed using the Choose categegory - for duplicate snippet drop-down list. The category of the original - snippet will already be selected, making it easy to create the new snippet - in the same category. + The new snippet's category can be changed using the Category + drop-down list. The category of the original snippet will already be + selected, making it easy to create the new snippet in the same category.

    - Click OK to go ahead and create the new snippet. If the snippet - name is invalid or is not unique an error message will be displayed. + Click OK to create the new snippet. If the snippet name is + invalid or is not unique then an error message will be displayed.

    - The newly duplicated snippet can be edited in the Snippets Editor immediately after clicking the OK button. To do this tick the Edit in Snippets Editor check box. If you don't want to display the Snippets Editor leave the check box clear. - Note: The duplicated snippet is added to the database - when this dialogue box closes, before the Snippets Editor is - displayed. Therefore cancelling the Snippets Editor will not - remove the duplicated snippet from the database. + Note that when automatically opening the Snippets Editor the + duplicated snippet is added to the database before the + Snippets Editor is displayed.

    diff --git a/Src/Help/HTML/dlg_editsnippet.htm b/Src/Help/HTML/dlg_editsnippet.htm index ab0a78571..8af0dfad3 100644 --- a/Src/Help/HTML/dlg_editsnippet.htm +++ b/Src/Help/HTML/dlg_editsnippet.htm @@ -2,12 +2,9 @@ @@ -33,7 +30,7 @@

    This dialogue box is displayed when either the Snippets | New Snippet or Snippets | Edit Snippet menu options are - selected. It is used to enter details of a new user defined code snippet + selected. It is used to enter details of a new user-defined code snippet or to edit the details of an existing user-defined snippet.

    @@ -45,10 +42,10 @@

    Editing the snippet

    - The dialogue has multiple tabs. All but the Code tab are optional - – others can be used to provide additional information about the - snippet. However, if you plan to test compile the snippet you must ensure - that the snippet's dependencies and units are properly set up on the + The dialogue has multiple tabs. All but the Code tab are + optional: they are used to enter additional information that can be useful + but is not required. However, if you plan to test compile a Pascal snippet + you must ensure that the snippet's dependencies are properly set up on the References tab.

    @@ -56,116 +53,116 @@

    This tab gathers all the information needed to define a snippet. All the - fields are required. They are: + fields except Display Name are required. They are:

    • -
      +

      Name -

      -
      +

      +

      Enter a name for the snippet in the edit box. The name must be a valid Unicode Pascal identifier. It must also be unique within the user - database, it may duplicate names in the main database. -

      -
      + database, although it may duplicate names in the main database. +

      +

      It is customary to name functions or procedures using the routine's name. Overloaded routines are often named by suffixing the routine name with '_A', '_B', '_C' and so on. -

      +

    • -
      +

      Display Name -

      -
      +

      +

      Enter a display name for the snippet. When present this name will be - displayed in the main display instead of the snippet's name entered - above. This name may contain any characters and does not have to be + displayed in the main display instead of the name entered above. The + display name may contain any Unicode character and does not need to be unique. -

      -
      +

      +

      This is an optional entry. If it is not supplied the value of the Name control is used as the display name. -

      -
      - The display name is useful where the entry in Name field does - not display well. For example overloaded snippets named +

      +

      + The display name is useful where the entry in the Name field + does not display well. For example overloaded snippets named "Foo" may have names "Foo_A", "Foo_B", "Foo_C" etc., which are not too friendly. To improve this you could use display names like "Foo (Integer overload)", "Foo (string overload)", "Foo (Integer,string overload)" etc. -

      +

    • -
      +

      Description -

      -
      +

      +

      Enter a description of the code snippet in the - markup editor. You can use more than - one paragraph of text. You have the choice of styling the text and - using hyperlinks. -

      -
      + markup editor. More than one paragraph + of text can be used. You can enter either plain text or text formatted + using CodeSnip's REML markup language. +

      +

      The description field should not contain "}" characters - since the field's text is used in comments in exported Pascal code and - the "}" character will terminate the comment early. -

      -
      + since the field's text may be enclosed in comments in exported Pascal + code and the "}" character will terminate the comment early. +

      +

      The description can be previewed using the Preview button. Clicking this button also validates markup if you have used it. -

      +

    • -
      +

      Kind -

      -
      +

      +

      Select the kind of snippet from the drop down list. -

      +

    • -
      +

      Category -

      -
      +

      +

      Select the category to which the snippet belongs from the drop down list. -

      +

    • -
      +

      Source code -

      -
      +

      +

      Enter the snippet's source code in this text box. The format of the source code is important unless you are using the freeform snippet kind. Learn more. -

      +

    • -
      +

      Syntax highlight this snippet as Pascal code -

      -
      +

      +

      Tick this box if your snippet is written in Pascal and you would like the source to be syntax highlighted. Otherwise clear the box. Note that the built in highlighter only understands Pascal code and will not render code in other languages correctly. -

      -
      +

      +

      If you prefer to turn off syntax highlighting completely go to the Syntax Highlighter tab of the Syntax Highlighter page of the Preferences dialogue box, click the Use Predefined Styles button and select the No Highliter option. -

      +

    References Tab @@ -174,132 +171,124 @@

    Use this tab to specify the snippets and units that are associated with the snippet being edited. If it is intended to test compile the snippet then the Dependencies and Units list boxes must be - completed if the snippet depends on a Delphi unit or if it uses another - snippet from the database. + completed correctly whenever the snippet depends on a Delphi unit or if it + relies upon another snippet from the database.

    The available controls are:

    • -
      +

      Cross-references list box -

      -
      +

      +

      Use this list box to specify any other snippets to be cross-referenced - from this snippet. Check the boxes next to desired snippets. Cross + from this snippet. Check the boxes next to the desired snippets. Cross references are for information only. All snippets are listed, except for the one being edited. -

      -
      +

      +

      To deselect all cross-references right click the list box and select the Deselect All Cross-References option from the context menu. -

      -
      - User defined snippets are displayed in blue. -
      +

    • -
      +

      Dependencies list box -

      -
      +

      +

      Specify any other snippets that this snippet depends upon by checking the boxes next to the desired snippets in this list box. If this snippet is to be test compiled then any other snippets it requires must be specified here or the test compile will fail. The exception is that unit snippets never have any dependencies. -

      -
      +

      +

      To deselect all dependencies right click the list box and select the Deselect All Dependencies option from the context menu. -

      -
      - You can use the context menu to to see all dependencies for the - snippet being edited. This displayed the +

      +

      + You can use the context menu to see all dependencies for the + snippet being edited. This information is displayed in the Dependencies dialogue box. All entries checked in the Dependencies list box are included, along with all their own dependencies, and so on. You must not create a circular dependency; the Dependencies dialogue box will help you detect them. -

      -
      - The available entries in this list box will vary according to the - snippet kind and the kind of snippets - it can depend on. For example, constant - snippets can't depend on routine - snippets and unit snippets can't depend - on anything, so the list is empty. - Freeform snippets are never - included. -
      -
      - User defined snippets are displayed in blue. -
      +

      +

      + The available snippets in this list box will vary according to the + snippet kind. For example, + constant snippets can't depend on + routine snippets and unit snippets can't depend on anything, so the + list will be empty. Freeform + snippets are never included in the list. +

    • -
      +

      Units list box -

      -
      - Select any units that this snippet requires in order to compile using - this list box. Required units must be specified if test compilation is - to be attempted. A selection of common units is provided, along with - any units already associated with the snippet. -
      -
      - If the unit you need is not in the list box simply enter its name in +

      +

      + Use this list box to select any units that this snippet requires in + order to compile. Required units must be specified if test compilation + is to be attempted. A selection of common units is provided, along + with any units already associated with the snippet. +

      +

      + If the unit you need is not in the list box, simply enter its name in the edit box next to the Add Unit button and click the button. -

      -
      +

      +

      Each unit you use is remembered and will appear in the list box in future. If the list box is getting too full you can remove most units by right clicking them and selecting Delete Selected Unit from the context menu. Note that the SysUtils, Classes, Windows and Graphics units cannot be deleted. -

      -
      +

      +

      The list box's context menu also gives options to restore the unit list to it's "factory" default and to deselect all the units in the list. -

      -
      +

      +

      Units are "used" in the order they appear in the list. If - this order causes namespace clashes you should prefix ambiguous + this ordering causes namespace clashes you should prefix ambiguous identifiers in the source code with the unit name, e.g. Graphics.TBitmap. -

      -
      - If you intend to test compile your snippet with Delphi XE2 or later - you will need to make sure the appropriate compiler is configured to - search the namespaces containing the units you list here. Do this in - the Configure Compilers dialogue - box. Use the Namespaces tab to enter the required namespaces. - Click the Defaults button to use a set of namespaces that - works with many of the commonly used units. You can see a list of - namespaces with the units they contain in the Unit Scope Names topic of the Embarcadero RADStudio - documentation. -
      -
      - If any unit is not part of the Delphi RTL or VCL you will need to let - the compiler know where to find the unit. Use the Search - Paths tab of the Configure - Compilers dialogue box to specify the required path. You need to - do this for each compiler separately. -
      -
      - Notes: Unit snippets +

      +

      + If you intend to test compile your snippet with Delphi XE2 or later + you will need to make sure the appropriate compiler is configured to + search the namespaces containing the units you list here. Do this + using the Namespaces tab of the + Configure Compilers dialogue + box. You need to do this for each compiler separately. You can + find a list of namespaces and the units they contain in the + Unit Scope Names topic of the Embarcadero RADStudio + documentation. +

      +

      + If any unit is not part of the Delphi RTL or VCL you will need to let + the compiler know where to find the unit. Use the Search + Paths tab of the Configure + Compilers dialogue box to specify the required path. You need to + do this for each compiler separately. +

      +

      + Notes: Unit snippets must declare their used units in the unit source code, so this list box is disabled when the snippet kind is unit. -

      +

    @@ -313,7 +302,7 @@

    Enter the required information in the Extra information markup editor. You can enter either plain - text of formatted text and hyperlinks using the custom + text or text formatted using CodeSnip's REML markup language.

    @@ -324,19 +313,23 @@

    Compile Results Tab

    - Use this tab to specify any known compiler results for the snippet. You - can specify the required compiler in several ways: + Use this tab to specify any known compilation results for the snippet. You + can specify such results in several ways:

    • - Manually, one compiler at a time using the Set a compile - results list box. Click the drop down button next to the required - compiler then choose the required compile result from the drop down - menu, i.e. one of Success, Warning, Error and - Unknown. To use the keyboard to modify compile results proceed - as follows: +

      + Manually, one compiler at a time using the Set compile + results list box. Click the drop down button next to the required + compiler then choose a compilation result from the drop down + menu, i.e. one of Success, Warning, Error + and Unknown. +

      +

      + To use the keyboard to modify compile results proceed as follows: +

        -
      1. +
      2. Move the keyboard focus to the list box using the Tab key.
      3. @@ -368,22 +361,22 @@

        result "query" (grey LED).
      4. -
        +

        Use the Test Compile button to compile the snippet and set - the compile results accordingly. This button is disabled if no + the compile results automatically. This button is disabled if no compilers are configured or if the snippet is freeform. -

        -
        +

        +

        If one or more compilers report warnings or errors, a link appears below the Test Compile button that can be clicked to display details of the warnings or errors. -

        -
        +

        +

        You can display the unit used to perform the test compilation by clicking the View Test Unit button. This can be useful when tracking down errors. -

        +

    diff --git a/Src/Help/HTML/dlg_elementcolour.htm b/Src/Help/HTML/dlg_elementcolour.htm index c7f28a097..dbf126d84 100644 --- a/Src/Help/HTML/dlg_elementcolour.htm +++ b/Src/Help/HTML/dlg_elementcolour.htm @@ -2,12 +2,9 @@ diff --git a/Src/Help/HTML/dlg_export.htm b/Src/Help/HTML/dlg_export.htm index 1916be9ab..b8e1db3ff 100644 --- a/Src/Help/HTML/dlg_export.htm +++ b/Src/Help/HTML/dlg_export.htm @@ -2,12 +2,9 @@ @@ -42,10 +39,10 @@

    You specify the snippets to be exported by checking the required snippets in the Select snippets to be exported check list box. Only user - defined snippets are listed and any user defined snippet that is selected - in the main display when the dialogue box is displayed is pre-selected. - Checking or unchecking a category selects or deselects all the snippets in - that category. At least one snippet must be selected. + defined snippets are listed. Any user defined snippet that is selected in + the main display is pre-selected in the list. Checking or unchecking a + category selects or deselects all the snippets in that category. At least + one snippet must be selected.

    Next you specify the name of the export file by entering it in the @@ -57,9 +54,13 @@

    Click the OK button to export the snippets. If no snippet is selected or no file name is specified then an error message is displayed - and the dialogue box is not closed. The export can be aborted by clicking + and the dialogue box remains open. The export can be aborted by clicking the Cancel button.

    +

    + Note: Snippet categories and cross references are not + included in the export file. +

    diff --git a/Src/Help/HTML/dlg_exportfile.htm b/Src/Help/HTML/dlg_exportfile.htm index 2182856b9..de3545c5a 100644 --- a/Src/Help/HTML/dlg_exportfile.htm +++ b/Src/Help/HTML/dlg_exportfile.htm @@ -2,12 +2,9 @@ diff --git a/Src/Help/HTML/dlg_favourites.htm b/Src/Help/HTML/dlg_favourites.htm index 1d53471aa..2df231576 100644 --- a/Src/Help/HTML/dlg_favourites.htm +++ b/Src/Help/HTML/dlg_favourites.htm @@ -2,12 +2,9 @@ @@ -43,13 +40,13 @@

    adjusted using the Transparency slider (see below).

    - Favourite snippets are listed in the main list view control. To the right - of each snippet is the date and time it was created or last accessed. As - usual snippets from the main and user database are displayed in different - colours (black and blue by default). + Favourite snippets are listed in the dialogue's list view control. To the + right of each snippet is the date and time it was created or last + accessed. As usual snippets from the main and user database are displayed + in different colours (black and blue by default).

    - Snippets can be sorted in either alphabetic order by clicking the + Snippets can be sorted in alphabetic order by clicking the "Snippet" header. You can also sort by accessed date if you click the "Last used" header.

    @@ -62,9 +59,9 @@

    By default snippets are displayed in a new tab in CodeSnip's detail pane. You change this to display all snippets in the same tab - by clearing the tick mark from the Open favourites in new tabs - check box. This setting is persistent. + >detail pane. You can change this to display all snippets in the same + tab by clearing the tick mark from the Open favourites in new + tabs check box. This setting is persistent.

    You can remove a snippet from favourites by selecting it and clicking the @@ -91,7 +88,7 @@

    fades down to the selected transparency when the Transparency slider is used and the transparency changes as the slider is moved. When the mouse or arrow key is released the dialogue's opacity is restored. To - view the current transparency without changing it press the + view the current transparency without changing it press and hold the Space key when the slider has focus.

    diff --git a/Src/Help/HTML/dlg_findcompiler.htm b/Src/Help/HTML/dlg_findcompiler.htm index 3c5060416..d7aeda2df 100644 --- a/Src/Help/HTML/dlg_findcompiler.htm +++ b/Src/Help/HTML/dlg_findcompiler.htm @@ -2,12 +2,9 @@ @@ -122,7 +119,7 @@

    The dialogue box will have remembered details of your previous search, - so deselect Delphi 7, 2006 2010 and select Delphi XE and XE2 in the + so deselect Delphi 7, 2006 & 2010 and select Delphi XE and XE2 in the Compilers list. Now choose Does not compile in the Criteria drop down list and change the search logic to Find all compilers. Ensure that Refine existing search scope is diff --git a/Src/Help/HTML/dlg_findtext.htm b/Src/Help/HTML/dlg_findtext.htm index 37c3610d9..fc919b00d 100644 --- a/Src/Help/HTML/dlg_findtext.htm +++ b/Src/Help/HTML/dlg_findtext.htm @@ -2,12 +2,9 @@ @@ -44,17 +41,18 @@

    Enter the text you want to search for in the Text to find combo - box. + box. Recent search text can be accessed from the drop down list.

    When more than one search word is entered you have a choice of whether to search for snippets containing all the words, or any of them. You specify which of these options you want by choosing the relevant radio button in the Search logic group box. For example, choosing the Find - all words option and searching for "Foo Bar" would match - the text "Foo and Bar" but would not match "Foo" or - "Bar". Choosing Find and word instead and again - searching for "Foo Bar" would match all the previous examples. + all words option and searching for "Foo Bar" + would match the text "Foo and Bar" but would not match + "Foo" or "Bar". Choosing Find any word + instead and again searching for "Foo Bar" would + match all the previous examples.

    There are two more options for further refining the search: @@ -62,18 +60,20 @@

    1. Use the Whole words only check box to ensure that the search - only looks up words that exactly match those specified. If the check box + only matches words that exactly match those specified. If the check box is cleared then words containing the specified text will also be found. - For example when the check box is ticked search for "Foo" will - not match "FooBar", but when the check box is cleared, the two - words will match. + For example when the check box is ticked a search for + "Foo" will match "Foo" in + "Foo Bar" but will not match "FooBar". When the + check box is cleared, "Foo" will match both + examples.
    2. Make the search case sensitive by checking the Case sensitive - check box. In this case searching for "Foo" would only match - the exact text and would not match "foo" or "FOO". - If the check box is cleared then case is ignored. Now searching for - "Foo;" will match both "foo" and "FOO". + check box. In this case searching for "Foo" will + match "Foo" but not "foo" or "FOO". If the + check box is cleared then case is ignored and searching for + "Foo" again will match all three examples.

    @@ -91,10 +91,9 @@

    remains unchanged.

    - The Search scope group box is disabled if there was no existing - search when this dialogue box was opened, and the search operates on the - entire database. + The Search scope group box is disabled if there was no previous + search active when this dialogue box was opened. The search will operate + on the entire database.

    - diff --git a/Src/Help/HTML/dlg_findxrefs.htm b/Src/Help/HTML/dlg_findxrefs.htm index 308051b00..92304ddc3 100644 --- a/Src/Help/HTML/dlg_findxrefs.htm +++ b/Src/Help/HTML/dlg_findxrefs.htm @@ -2,12 +2,9 @@ @@ -27,49 +24,90 @@

    - Find Cross References Dialogue Box + Find Cross References Dialogue Box

    Use this dialogue box to find snippets that are referenced by the - currently selected snippet and / or those snippets reference that snippet. - The dialogue is displayed from the Search | Find Cross Refs menu - option. + currently selected snippet and / or those snippets that reference the + snippet. The dialogue is displayed from the Search | Find Cross + Refs menu option. +

    +

    + The dialogue enables two kinds of cross-references to be found:

    +
      +
    1. + Snippet dependencies, which are snippets that the selected snippet + depends upon in order to compile. Alternatively the search can find + snippets that depend on the selected one. +
    2. +
    3. + "See also" references, which are snippets that are related to + the selected snippet conceptually. Such snippets are not necessarily + needed to compile the selected snippet. Alternatively the search can + find snippets that refer to the selected one as a "see also" + snippet. +
    4. +

    - The search can be customised by checking or clearing the following check + The search is customised by checking or clearing the following check boxes:

    • - Search for required snippets. Selecting this option causes - all snippets that are needed to compile the selected snippet to be - included in the search result. If the related Search - recursively box is checked the search continues through the found - snippets. +

      + Search for required snippets +

      +

      + Selecting this option includes all snippets that the selected snippet + depends upon in the search result. +

      +

      + If the related Search recursively box is checked the search + is applied recursively to all the found snippets. +

    • - Search for snippets that depend on this one. Choose this option - to include in the search results all snippets that depend upon (i.e. - require) this one. +

      + Search for snippets that depend on this one +

      +

      + Choose this option to include in the search results all snippets that + depend upon this one. +

    • - Search for "see also" cross references. This option - includes snippets referenced as "see also" to be included in - the search result. These snippets are not necessarily needed to compile - the selected snippet, but are related to it conceptually. Again, - checking the related Search recursively check box causes the - search to recurse through the found "see also" cross - references. +

      + Search for "see also" cross references +

      +

      + This option includes all snippets referenced as "see also" + in the search result. +

      +

      + Checking the related Search recursively check box causes the + search to recurse through all the found snippets' "see also" + cross references. +

    • - Search for snippets that cross-reference this one. Choose this - option to include in the search results all snippets that - cross-reference this one. +

      + Search for snippets that cross-reference this one +

      +

      + Choose this option to include all snippets that cross-reference this + one in the search results. +

    • - Include "xxxx" in search (where xxxx is the name of - the selected snippet). Checking this box simply includes the selected - snippet in the search results. +

      + Include "xxxx" in search (where xxxx is the name of + the selected snippet). +

      +

      + Checking this box simply includes the selected snippet in the search + results. +

    @@ -77,10 +115,10 @@

    or Search for "see also" cross references options.

    - NOTE: Any cross references selected using this dialogue - box will replace any search results that were previously displayed. A + Note: Snippets selected by the use of this dialogue box + will replace any search results that were previously displayed. A message to this effect will be displayed in the dialogue box if it is - displayed when search results are being displayed. + opened while a previous search is active.

    diff --git a/Src/Help/HTML/dlg_firstrun.htm b/Src/Help/HTML/dlg_firstrun.htm index a436b40f3..fed435c80 100644 --- a/Src/Help/HTML/dlg_firstrun.htm +++ b/Src/Help/HTML/dlg_firstrun.htm @@ -2,12 +2,9 @@ @@ -24,7 +21,7 @@ type="application/x-oleobject" classid="clsid:1e2a7bd0-dab9-11d0-b93a-00c04fc99f9e" > - +

    @@ -36,12 +33,13 @@

    Its purpose is to enable you to preserve preferences from the earlier - version of CodeSnip and to import any user snippets database you - may have created. + version of CodeSnip and to import any user defined snippets + database you may have created.

    - Notes: If you run CodeSnip 4 as a different user you may see this - dialogue box again since it performs configuration on a per-user basis. + Note: If you run CodeSnip 4 as a different user + you may see this dialogue box again since it performs configuration on a + per-user basis.

    The dialogue box takes the form of a multi-page wizard. You can't cancel @@ -87,24 +85,11 @@

    You will need to redo any customisation using the Syntax Highlighter tab of the Syntax Highlighter page of the Preferences dialogue box.
    -
  7. -
    - If you are updating from v3.6.0 or earlier and have set up a password - protected proxy server for internet access you will need to re-enter - the password since it will have been lost. This is because format for - storing passwords changed at v3.6.1. -
    -
    - To re-enter your proxy password use the Proxy Server Configuration dialogue box. -
    -
  8. Your source code formatting preferences will have been lost if you @@ -113,28 +98,14 @@

    You will need to reconfigure them using the Code Formatting tab of the Code Formatting page of the Preferences dialogue box.

  9. -
  10. -
    - If you are updating from v1.8.11 or earlier and have registered - CodeSnip you registration information will have been lost. -
    -
    - You can check this by displaying the About dialogue box and checking the About The Program - tab. If it displays a Register CodeSnip button the program - is not registered. You can (re)register if you wish by clicking the - button. -
    -
  11. - Once you have decided whether to preserve preferences or not, click the + Once you have decided whether to preserve preferences, click the Next button. This will take you to the Snippets database page or the Summary page, depending on whether a user defined snippets database has been found. @@ -149,18 +120,19 @@

    you prefer to abandon the database, clear the check box.

    - If you do create a copy of the database any changes you make to it will - not be reflected in the earlier copy, or vice versa. This is because a - snippets database updated by CodeSnip 4 ceases to be readable by - earlier versions. + If you do create a copy of the database any changes you make to it in + CodeSnip 4 will not be reflected in the earlier copy, and vice + versa. This is because a snippets database updated by CodeSnip 4 + ceases to be readable by earlier versions.

    Note: If you don't install the old database you can still - export snippets from it later that can be imported into CodeSnip - 4 at a later date, provided you don't uninstall the older version of - CodeSnip. See CodeSnip + and then import them with CodeSnip 4. If you think you may need + to do this do not uninstall the older version of CodeSnip. See Exporting and Importing Snippets for further information. + >Exporting and Importing Snippets for further information about + importing and exporting.

    "Summary" page @@ -184,14 +156,14 @@

    This page is displayed after the configuration is complete.

    - If you preversed your preference and any loss of information has been - detected, or is possible, the information is listed here. See the + If you preserved your preferences and any loss of information has been + detected, or is possible, that information is listed here. See the "Preferences" page section above for details. Otherwise the page simply reports that the process is complete.

    Click Finish to close the wizard. CodeSnip 4 will now - load. + load normally.

    diff --git a/Src/Help/HTML/dlg_hilitemgr.htm b/Src/Help/HTML/dlg_hilitemgr.htm index 1883fc27b..31761ef8d 100644 --- a/Src/Help/HTML/dlg_hilitemgr.htm +++ b/Src/Help/HTML/dlg_hilitemgr.htm @@ -2,12 +2,9 @@ @@ -27,22 +24,23 @@

    - User Defined Highlighters Dialogue Box + User Defined Highlighters Dialogue Box

    This dialogue box is displayed from the User Defined Styles option of the drop-down menu displayed by the Use Named Style button located on the Syntax - Highlighter tab of the Preferences + Highlighter page of the Preferences dialogue box.

    - The dialogue box is used to manage and display saved user-defined syntax - highlighter styles. The available styles are listed in the Available - highlighter styles list box. + The dialogue box is used to manage saved user-defined syntax highlighter + styles. The available styles are listed in the Available highlighter + styles list box.

    - To display a style either select the required style and press the Use + To display a style in the Syntax + Highlighter tab either select the required style and press the Use & Close button or, alternatively, double click the style in the list box. The dialogue box will be closed immediately and the style will be displayed in the main dialogue box ready for editing. diff --git a/Src/Help/HTML/dlg_import.htm b/Src/Help/HTML/dlg_import.htm index 3ca2c13de..e9e71a5b1 100644 --- a/Src/Help/HTML/dlg_import.htm +++ b/Src/Help/HTML/dlg_import.htm @@ -2,12 +2,9 @@ @@ -66,43 +63,11 @@

    Files exported from CodeSnip usually have a .csexp extension.

    - Once a suitable file has be specified click the Next button to + Once a suitable file has been specified click the Next button to load the file and move to the next page of the wizard. The database is not updated at this stage, so if you made a mistake you can return to this page and choose another file.

    -

    - "User information" page -

    -

    - Note: This page is not shown if the selected import file - does not contain any user information. -

    -

    - The User information page simply displays any information that - the person who created the file provided. There are three items of - information that may be present: -

    -
      -
    1. - Name - the name or nickname of the person who created the file. -
    2. -
    3. - Email - the email address of the person who created the file. -
    4. -
    5. - Comments - any comments about the content of the file. -
    6. -
    -

    - Any of these items of information can be empty. Comments are for - information only but the name and email will be added to the end of the - imported snippet's Extra information field. This will be in the - form "Contributed by Name <Email>". -

    -

    - Click Next to go to the next page. -

    "Edit import and update database" page

    @@ -122,7 +87,7 @@

  12. Import Using Name
    - This is that name that will be used for the snippet when it is imported. + This is the name that will be used for the snippet when it is imported. If there is already a snippet in your database with the same name as that given in the Snippet Name column, the entry in this column will have been altered to make it unique in your database. Any names in @@ -139,11 +104,11 @@

    Selecting one of the rows in the grid copies the snippet name from the Import Using Name column into the Selected Snippet edit - control. You can rename the snippet by entering a new name here then - clicking the Rename button. The new name is validated before - being displayed in the grid. You can't use the name of a snippet that is - already in your user database or is the name one of the other snippets in - the import list. + control. You can rename the snippet by entering a new name in the edit + control then clicking the Rename button. The new name is + validated before being displayed in the grid. You can't use the name of a + snippet that is already in your user database or is the name of another + snippet in the import list.

    When you are ready to perform the import click the Update button diff --git a/Src/Help/HTML/dlg_importfile.htm b/Src/Help/HTML/dlg_importfile.htm index 09c05e274..c6fb74b3a 100644 --- a/Src/Help/HTML/dlg_importfile.htm +++ b/Src/Help/HTML/dlg_importfile.htm @@ -2,12 +2,9 @@ diff --git a/Src/Help/HTML/dlg_loadselection.htm b/Src/Help/HTML/dlg_loadselection.htm index 889b36813..b8601123e 100644 --- a/Src/Help/HTML/dlg_loadselection.htm +++ b/Src/Help/HTML/dlg_loadselection.htm @@ -2,12 +2,9 @@ @@ -27,22 +24,21 @@

    - Load Selection Dialogue Box + Load Selection Dialogue Box

    This Windows standard dialogue box is displayed when the File | Load Selection menu option is chosen.

    - Use this dialogue box to specify the name of the file containing the - details of database selection to be loaded. By default only files with the + Use this dialogue box to specify the name of the file containing details + of the database selection to be loaded. By default only files with the ".cssel" extension are displayed. This can be changed using the Files of type drop-down list and chosing "All files".

    - Click OK to confirm the selection. If the file aleady exists you - will be prompted for permission to overwrite it. Choosing an invalid file - will result in an error message. + Click OK to load the selection. Choosing an invalid file will + result in an error message.

    diff --git a/Src/Help/HTML/dlg_moveuserdb.htm b/Src/Help/HTML/dlg_moveuserdb.htm index 4929933f6..1ec724995 100644 --- a/Src/Help/HTML/dlg_moveuserdb.htm +++ b/Src/Help/HTML/dlg_moveuserdb.htm @@ -2,12 +2,9 @@ @@ -36,19 +33,19 @@

    Move User Database Dialogue Box

    - Note: This dialogue box is not available in the portable - edition of CodeSnip. + Note: This dialogue box is not available + in the portable edition of CodeSnip.

    The purpose of this dialogue box is to enable the user to move the user defined snippets database from its default location to a new directory. You may wish to do this to enable the database to be easily backed up. For example you could move the database to a sub-directory of a - Dropbox or GoogleDrive directory. + Dropbox or Google Drive directory.

    - You have the option to restore a previously moved database to its default - location. Both options are described below. + You also have the option to restore a previously moved database to its + default location. Both options are described below.

    Important: You a strongly advised to make a backup of the @@ -57,7 +54,7 @@

    Database menu option to do this.

    - Move the database to a new directory + Move database to new directory group box

    To move the database enter the full path of the new @@ -70,10 +67,10 @@

    • - the directory must be empty; + it must be empty;
    • - it must not be a sub-directory of the current database location; + it must not be a sub-directory of the current database location.

    @@ -85,22 +82,22 @@

    The new location of the database will be recorded in the per user config - file and it is important that this file is not modified or deleted + file. It is important that this config file is not modified or deleted otherwise the database will not be found.

    - Restore database to default directory + Restore database to default directory group box

    The database can be moved back to its default directory simply by clicking - the Restore Default Path button. If the default directory is not - empty this action will fail and an error message will be displayed. As - above, a progress bar gives an indication of how long the process may + the large Restore Default Path button. If the default directory + is not empty this action will fail and an error message will be displayed. + As above, a progress bar gives an indication of how long the process may take.

    - The Restore Default Path button will be disabled if database is - already in its default location. + The Restore Default Path button will be disabled if the database + is already in its default location.

    diff --git a/Src/Help/HTML/dlg_news.htm b/Src/Help/HTML/dlg_news.htm deleted file mode 100644 index a5fd8e99c..000000000 --- a/Src/Help/HTML/dlg_news.htm +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - CodeSnip News Dialogue Box - - - - - - - -

    - CodeSnip News Dialogue Box -

    -

    - This dialogue box is displayed from the Help | CodeSnip News menu - item. -

    -

    - It shows the latest news about CodeSnip and the online database. - The news is obtained from CodeSnip's online RSS news feed. The - titles of most items are clickable and will display a web page relevant - to the news item when clicked. -

    -

    - The maximum age of the news items is shown at the top of the window. By - default this is 92 days. You can change this by clicking the - Change button, which will display the News tab of the Preferences dialogue box. Enter a new value there and click - OK. If the value has changed the news will be re-loaded, showing - items from the date range specified. -

    -

    - If the news feed is not available or there is an error, a message to that - effect is displayed. -

    -

    - You can display the RSS feed in your default web browser by clicking the - RSS Feed button. Depending on your browser, you may be able to - subscribe to the feed from there. -

    - - diff --git a/Src/Help/HTML/dlg_pagesetup.htm b/Src/Help/HTML/dlg_pagesetup.htm index 783987cea..cad0ec963 100644 --- a/Src/Help/HTML/dlg_pagesetup.htm +++ b/Src/Help/HTML/dlg_pagesetup.htm @@ -2,12 +2,9 @@ @@ -75,10 +72,15 @@

    Select the size of each margin by entering a value in the appropriate edit box.

    +

    + The default margin sizes can be specified using the + Printing page of the + Preferences dialogue box. +

    The unit of measurement used to set margins is displayed in the group box label. This can be changed on the - Misc. tab of the + Misc. page of the Preferences dialogue box.

    diff --git a/Src/Help/HTML/dlg_preferences.htm b/Src/Help/HTML/dlg_preferences.htm index b0e3e7ee5..0f80b146d 100644 --- a/Src/Help/HTML/dlg_preferences.htm +++ b/Src/Help/HTML/dlg_preferences.htm @@ -2,12 +2,9 @@ @@ -34,8 +31,10 @@

    dialogue box is accessed via the Tools | Preferences menu option.

    - The dialogue box is in several sections, accessed by tabs across the top - of the page. These are: + The dialogue box is divided into different "pages". Each page is listed by name in the pane on the left hand side of the dialogue box. Selecting one of these names displays the related page of options on the right. +

    +

    + The sections are:

    • @@ -43,6 +42,7 @@

    • Snippet Layout +
    • Code Generation
    • @@ -55,12 +55,6 @@

    • Printing
    • -
    • - News -
    • -
    • - Updates -
    • Misc.
    • @@ -69,12 +63,15 @@

      Click OK to accept all the changes or Cancel to close the dialogue without making changes.

      +

      + The last page you selected is remembered and is displayed again the next time you open the dialogue box. This happens regardless of whether you close the dialogue box with OK or Cancel. +

      Note: On some occasions this dialogue box may be - displayed with just one tab visible. An example of this is when the + displayed with just one page visible. An example of this is when the Default Options button of the Print dialogue box is clicked – only the - Printing tab is displayed. + Printing page is displayed.

      diff --git a/Src/Help/HTML/dlg_prefs_codegen.htm b/Src/Help/HTML/dlg_prefs_codegen.htm index db7e18758..bf045b7ff 100644 --- a/Src/Help/HTML/dlg_prefs_codegen.htm +++ b/Src/Help/HTML/dlg_prefs_codegen.htm @@ -2,14 +2,11 @@ @@ -27,23 +24,28 @@

      - Code Generation Preferences + Code Generation Preferences Page

      - This tab of the Preferences dialogue box - is used to customise the source code emitted by the code generator for - test compilations and generated units. + This page of the Preferences dialogue + box is used to customise the source code emitted by the code generator + when creating test compilations and when outputting units. +

      +

      + The page is accessed by selecting the Tools | Preferences menu + item then clicking on Code Generation in the list on the left of + the window. +

      - The tab is accessed by selecting the Tools | Preferences menu - item then clicking the Code Generation tab. - Note: This tab is not used to modify the style + Note: This page is not used to modify the style of the source code: use the Code - Formatting tab for that. + Formatting page for that.

      - The Code Generator can be configured to generate $WARN compiler directives - to switch specified warning messages on or off. These directives are - supported by Delphi 6 and later. + The Code Generator can be configured to generate $WARN + compiler directives to switch specified warning messages on or off. These + directives are supported by Delphi 6 and later. Free Pascal is not + supported.

      To enable this facility you must check the Emit $WARN directives @@ -53,14 +55,12 @@

      The warnings for which compiler directives are to be generated are those listed in the list view control situated below the check box. Warnings are identified by the text in the Symbol column. These symbols - must be valid parameters for Delphi's $WARN directive. Since new symbols - have been introduced in different versions of Delphi, the minimum version - of the compiler that supports the warning must also be specified. This is - shown in the Min. Compiler column of the list view. The version - number is that specified by the Delphi's CompilerVersion constant.. Finally, whether a warning is + must be valid parameters for Delphi's $WARN directive. Since + new symbols have been introduced in different versions of Delphi, the + minimum version of the compiler that supports the warning must also be + specified. This is shown in the Min. Compiler column of the list + view. The version number is that specified by Delphi's + CompilerVersion constant. Finally, whether a warning is switched on or off is shown in the State column.

      @@ -78,12 +78,12 @@

      Add a symbol
      Enter the symbol name in the Symbol edit control, specify the earliest compiler version that supports it in the Min. Compiler - edit control and specify whether the $WARN directive will switch the - warning on or off by selecting the appropriate Warning State - radio button. Click the Add button to add the symbol to the - list. If the Add button remains disabled check that the + edit control and specify whether the $WARN directive will + switch the warning on or off by selecting the appropriate Warning + State radio button. Click the Add button to add the symbol + to the list. If the Add button remains disabled check that the compiler version is valid and that the symbol name does not already - exist. + exist in the list view.
    • Delete a symbol
      @@ -96,9 +96,9 @@

      copied into the Symbol and Min. Compiler edit controls and the appropriate Warning State radio button will be selected. Change the details as required then click the Update - button to modify the symbol. The changes will be reflected in the list - view. If the entered information is not valid then the Update - button will be disabled. + button. The changes will be reflected in the list view. If the entered + information is not valid then the Update button will be + disabled.

    @@ -110,12 +110,12 @@

    supported compiler version is 14.0.

    - The Preview button at the top right of the tab displays the + The Preview button at the top right of the page displays the source code that will be emitted by the code generator.

    - The Restore Defaults button discards the current list of $WARN - directives and replaces it with a default set. + The Restore Defaults button discards the current list of + $WARN directives and replaces it with a default set.

    diff --git a/Src/Help/HTML/dlg_prefs_display.htm b/Src/Help/HTML/dlg_prefs_display.htm index 302994439..50ed4fcfd 100644 --- a/Src/Help/HTML/dlg_prefs_display.htm +++ b/Src/Help/HTML/dlg_prefs_display.htm @@ -2,14 +2,11 @@ @@ -27,15 +24,15 @@

    - Display Preferences + Display Preferences Page

    - Using this tab of the Preferences dialogue box you can configure various aspects of the - program's display. The tab is accessed by selecting the - Tools | Preferences menu item then clicking the Display - tab. + program's display. The page is accessed by selecting the + Tools | Preferences menu item then clicking on Display + in the list on the left of the window.

    The options are: @@ -46,7 +43,7 @@

    href="overview_pane.htm" >overview pane appears when the program starts. Do this using the Start overview pane treeview as drop down list to choose either - Fully expanded to make the treeview display all entries) or + Fully expanded to make the treeview display all entries or Fully collapsed to display only section headers.

  13. @@ -54,21 +51,22 @@

    tab or in a new tab in the detail pane. Check the Display newly added snippets & - categories in new tabs check box to add a new tab for each item - created or clear the check box to overwrite the content of the currently - selected tab. + categories in new tabs check box to add a new tab for each item, + or clear the check box to overwrite the content of the currently + selected tab. Note that Ctrl clicking an + item always displays the item in a new tab, regardless of this setting.

  14. Choose whether empty section headings are hidden in the overview pane. Check the Hide empty section headings in - overiew check box to hide empty section headings or clear the check - box to display them. + overview check box to hide empty section headings or clear the + check box to display them.
  15. -
    +

    Specify the colours used for the following interface elements: -

    +

    • Snippet and category headings in the main database. @@ -82,14 +80,37 @@

      >detail pane.

    -
    +

    Choose the required colours from the appropriate drop-down colour - lists. Custom colours can be created: just select the - Custom... option from the drop down list and choose or create - the colour in the displayed colour dialogue box. Default colours for - any of the above interface elements can be restored by clicking the - Use Default Colours button. -

    + lists. Custom colours can be created: just select the + Custom... option from the top of the drop down list and + choose or create the colour in the displayed colour dialogue box. +

    +

    + Default colours for all the above interface elements can be restored + by clicking the Use Default Colours button. +

    +
  16. +
  17. + Choose the size of font used in the overview pane's tree view using the Overview tree font + size combo box. +
  18. +
  19. +

    + Similarly, the size of font used in the details pane is set using the Detail pane font size + combo box. +

    +

    + Note: this combo box does + not affect the size of the font used to display source code + – this can be changed on the Syntax Highlighter Preferences page. +

  20. diff --git a/Src/Help/HTML/dlg_prefs_general.htm b/Src/Help/HTML/dlg_prefs_general.htm index 8c89f25a1..aac2be5da 100644 --- a/Src/Help/HTML/dlg_prefs_general.htm +++ b/Src/Help/HTML/dlg_prefs_general.htm @@ -2,14 +2,11 @@ @@ -27,18 +24,18 @@

    - Miscellaneous Preferences + Miscellaneous Preferences Page

    - This tab of the Preferences dialogue box is used for miscellaneous preferences that - don't merit a tab of their own. The tab is accessed by selecting the - Tools | Preferences menu item then clicking the Misc. - tab. + don't merit a page of their own. The page is accessed by selecting the + Tools | Preferences menu item then clicking on Misc. in + the list on the left of the window.

    - There is only a single option that can be set on this tab. + There is only a single option that can be set on this page.

    Measurement @@ -47,7 +44,7 @@

    Select the measurement units to be used by the application from the Preferred units of measurement drop down list. Options are Inches and Millimeters. If you have not set this option - before, the measurement units will default to your locale settings. + before, the measurement units will be appropriate to your locale.

    diff --git a/Src/Help/HTML/dlg_prefs_hiliter.htm b/Src/Help/HTML/dlg_prefs_hiliter.htm index 5e436496c..aacd45b21 100644 --- a/Src/Help/HTML/dlg_prefs_hiliter.htm +++ b/Src/Help/HTML/dlg_prefs_hiliter.htm @@ -2,14 +2,11 @@ @@ -28,13 +25,14 @@

    - Syntax Highlighter Preferences + Syntax Highlighter Preferences Page

    The appearance of syntax highlighted source files can be customised using - this tab of the Preferences dialogue - box. The tab is accessed by selecting the Tools | Preferences - menu item then clicking the Syntax Highlighter tab. + this page of the Preferences dialogue + box. The page is accessed by selecting the Tools | Preferences + menu item then clicking on Syntax Highlighter in the list on the + left of the window.

    Note @@ -43,11 +41,12 @@

    Syntax highlighter customisation applies to the Detail Pane, exported files and source code copied to the clipboard. Highlighting used in printed documents is - configured from the Printing tab. + configured from the Printing + preferences page.

    Different syntax highlighter styles can be saved and given identifying - names for later user. + names for later use.

    Highlighter Font @@ -59,7 +58,7 @@

    Note: The highlighter font is ignored in code previews in - other tabs in the Preferences dialogue + other pages of the Preferences dialogue box to ensure that previews fit the available space.

    @@ -68,14 +67,14 @@

    The colour and font style used for various syntactic elements of the source code can be changed. Select an element in the Elements - list box then using the Font style check boxes and - Colour drop down list. A preview of the element is displayed. + list box then edit its style using the Font style check boxes and + the Colour drop down list. A preview of the element is displayed.

    Named Styles

    - CodeSnip provides five pre-defined styles that are applied by + CodeSnip provides some pre-defined styles that are applied by clicking the Use Predefined Style button and selecting the style from the drop-down menu that appears.

    @@ -84,7 +83,7 @@

    1. - CodeSnip Classic – style of highlighting used in main + CodeSnip Classic – style of highlighting used in the main display of CodeSnip v2 and earlier.
    2. @@ -105,9 +104,7 @@

      highlighters can also be deleted from this dialogue box.

    3. - No Highlighter – no syntax highlighting used. You might - want to use this style if you are storing code other than Pascal in - CodeSnip. + No Highlighter – no syntax highlighting is used.

    @@ -137,7 +134,7 @@

    (see above).

    - Any number of custom highlighter style can be saved. + Any number of custom highlighter styles can be saved.

    diff --git a/Src/Help/HTML/dlg_prefs_news.htm b/Src/Help/HTML/dlg_prefs_news.htm deleted file mode 100644 index 808c8fd2b..000000000 --- a/Src/Help/HTML/dlg_prefs_news.htm +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - News Preferences - - - - - - - -

    - News Preferences -

    -

    - Preferences concerning the CodeSnip new feed can be modified by - using this tab of the - Preferences dialogue box. The tab is accessed by selecting the - Tools | Preferences menu item then clicking the News - tab. You can also display this tab from the CodeSnip News dialogue box. -

    -

    - There is only one setting that can be changed, and that is the maximum - number of days old a news item may be to be displayed in the CodeSnip News dialogue box. -

    -

    - Simply enter the required number of days in the spin edit control. The - minimum number of days is 14 and the maximum is 365. If you enter values - outside this range a valid value will be substituted. -

    - - diff --git a/Src/Help/HTML/dlg_prefs_printing.htm b/Src/Help/HTML/dlg_prefs_printing.htm index 0d4f27155..ca9f06182 100644 --- a/Src/Help/HTML/dlg_prefs_printing.htm +++ b/Src/Help/HTML/dlg_prefs_printing.htm @@ -2,14 +2,11 @@ @@ -27,22 +24,24 @@

    - Printing Preferences + Printing Preferences Page

    - This tab of the Preferences dialogue box - is used customise the default appearance of printed output. -

    -

    - The tab is accessed by selecting the Tools | Preferences menu - item then clicking the Printing tab. You can also display this - tab from the Print dialogue box. + This page of the Preferences dialogue + box is used customise the default appearance of printed output. +

    +

    + The page is accessed by selecting the Tools | Preferences menu + item then clicking on Printing in the list on the left of the + window. You can also display this page from the + Print dialogue box.

    - Note: Options set in this tab usually take effect the - next time the application is started. The exception is when the tab is - displayed from the Print dialogue box, when you can choose to - apply the new options straight away. + Warning: When this page is displayed via + the Tools | Preferences menu option options usually only take + effect the next time the application is started. But when the page is + displayed from the Print dialogue box, the changed options take + effect straight away.

    Document Formatting Options @@ -50,7 +49,7 @@

    There are two options that change the way printed documents are formatted:

    -
      +
      • The Syntax highlight source code check box specifies whether source code included in the output is syntax highlighted by default. @@ -65,6 +64,12 @@

        A preview of the effect of the options is displayed to the right of the check boxes.

        +

        + When syntax highlighting is enabled the style used is as specified in the + Syntax Highlighter peferences page. + When black and white printing is specified the highlighter's font styles + are used but colours are ignored. +

        Page Margins

        @@ -75,8 +80,9 @@

        Margins are displayed and entered either in inches or millimeters. The units being used are displayed in the group box caption. - Note: measurement units can be specified on the - Misc. tab, if it is visible. + Note: measurement units can be changed on the + Misc. preferences page, if it is + visible.

        diff --git a/Src/Help/HTML/dlg_prefs_snippetlayout.htm b/Src/Help/HTML/dlg_prefs_snippetlayout.htm index d6b77ff5a..da48fc1f4 100644 --- a/Src/Help/HTML/dlg_prefs_snippetlayout.htm +++ b/Src/Help/HTML/dlg_prefs_snippetlayout.htm @@ -2,14 +2,11 @@ @@ -27,21 +24,23 @@

        - Snippet Layout Preferences + Snippet Layout Preferences Page

        - This tab of the Preferences dialogue box is used to customise the layout of pages - that display the various kinds of snippet in the kinds of snippet + in the Detail Pane.

        - The tab is accessed by selecting the Tools | Preferences menu - item then clicking the Display tab in the Preferences dialogue box. + The page is accessed by selecting the Tools | Preferences menu + item then clicking on Snippet Layout in the list on the left of + the window.

        Overview @@ -80,8 +79,8 @@

        Type
        - Snippet's type or kind, e.g. "Routine" or "Type - Definition" + Snippet's type or kind, e.g. + "Routine" or "Type Definition"

      • @@ -89,7 +88,7 @@

        Category
        - Category that snippet belongs to. Renders as a link. + Category that a snippet belongs to. Renders as a link.

      • @@ -97,7 +96,7 @@

        Required Units List
        - Always empty for Unit snippets.
        @@ -107,7 +106,8 @@

        Required Snippets List
        - Contains links to any required snippets. Always empty for Unit snippets.
        @@ -117,7 +117,7 @@

        Cross Reference List
        - Contains links to any cross referenced snippets. + List of links to any cross referenced snippets.

      • @@ -160,10 +160,10 @@

        The current page components will be displayed in the right hand Displayed page components list box. Any unused page components - are listed in the Unused page components list box. + are listed in the Unused page components list box on the left.

        - You customise the page using this list boxes. Here are the available + You customise the page using these list boxes. Here are the available actions:

          diff --git a/Src/Help/HTML/dlg_prefs_sourcecode.htm b/Src/Help/HTML/dlg_prefs_sourcecode.htm index a4ad33f61..b0ed0fef4 100644 --- a/Src/Help/HTML/dlg_prefs_sourcecode.htm +++ b/Src/Help/HTML/dlg_prefs_sourcecode.htm @@ -2,14 +2,11 @@ @@ -20,76 +17,87 @@ - - - -

          - - Code Formatting Preferences -

          -

          - This tab of the Preferences dialogue box - is used configure the format of source code that CodeSnip writes - to file or copies to the clipboard. The tab is accessed by selecting the - Tools | Preferences menu item then clicking the Code - Formatting tab. -

          -

          - Source code formatting -

          -

          - Descriptions of snippets can be included as comments in generated source - code. The placing of such comments can be customised using the - Commenting style drop down list. Options are: -

          -
            -
          • - No descriptive comments – descriptions are not included. -
          • -
          • - Comments after snippet header – comments appear between - a snippet's header and the body of the snippet. For functions and - procedures the header is the function or procedure prototype. For - types and constants the comment is placed after the - type or const keyword and before any - other code. -
          • -
          • - Comments before snippet – comments are placed before the - snippet. The snippet's name is included in the comment. -
          • -
          -

          - A preview of the comment style is displayed below the drop down list. -

          -

          - Some snippets have multi-paragraph descriptions, all of which would normally - appear in the snippet's comment in generated code. You can limit the comment - to use just the first paragraph of the snippet's description by ticking the - Truncate comments to one paragraph check box. -

          -

          - Note: Descriptive comments are not applicable to - freeform snippets. -

          -

          - File formatting -

          -

          - The default format of output files is configurable. The available output - types are HTML, rich text and two types of unformatted text, i.e. Pascal - files and plain text files. You choose the default output type using the - Output file type drop down list. -

          -

          - HTML and rich text file types can be syntax highlighted. To enable this - option check the Enable syntax highlighting check box. The - highlighting can be customised using the - Syntax Highlighter tab. -

          + + + +

          + + Code Formatting Preferences Page +

          +

          + This page of the Preferences dialogue + box is used to configure the format of source code that CodeSnip + writes to file or copies to the clipboard. The page is accessed by + selecting the Tools | Preferences menu item then clicking on + Code Formatting in the list on the left of the window. +

          +

          + Source code formatting +

          +

          + Descriptions of snippets can be included as comments in generated source + code. The placing of such comments can be customised using the + Commenting style drop down list. Options are: +

          +
            +
          • + No descriptive comments – descriptions are not included. +
          • +
          • + Comments after snippet header – comments appear between + a snippet's header and the body of the snippet. For functions and + procedures the header is the function or procedure prototype. For + types and constants the comment is placed after the + type or const keyword and before any + other code. +
          • +
          • + Comments before snippet – comments are placed before the + snippet. The snippet's name is included in the comment. +
          • +
          +

          + A preview of the commenting style is displayed below the drop down list. +

          +

          + Some snippets have multi-paragraph descriptions, all of which normally + appear in the snippet's comment in generated code. You can limit the + comment to use just the first paragraph of the snippet's description by + ticking the Truncate comments to one paragraph check box. +

          +

          + When descriptive comments are enabled, they are included in the interface + section of generated units. You can choose whether or not such comments + are repeated in the unit's implementation section using the Repeat + comments in unit implementation section check box. +

          +

          + Note: Descriptive comments are not applicable to + freeform or + unit snippets. +

          +

          + File formatting +

          +

          + The default format of output files is configurable. The available output + types are HTML, rich text and two types of unformatted text, i.e. Pascal + files and plain text files. You choose the default output type using the + Output file type drop down list. +

          +

          + HTML and rich text file types can be syntax highlighted. To enable this + option check the Enable syntax highlighting check box. The + highlighting can be customised using the + Syntax Highlighter preferences page. +

          +

          + You can override the default format when saving source code to file by + choosing a different format in the save dialogue box. +

          diff --git a/Src/Help/HTML/dlg_prefs_updates.htm b/Src/Help/HTML/dlg_prefs_updates.htm deleted file mode 100644 index 37935e15f..000000000 --- a/Src/Help/HTML/dlg_prefs_updates.htm +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - Updates Preferences - - - - - - - -

          - Updates Preferences -

          -

          - Use this tab of the Preferences dialogue box to determine if and when CodeSnip - checks for program and database updates. The tab is accessed by selecting - the Tools | Preferences menu item then clicking the - Updates tab. -

          -

          - There are two combo boxes, Check for program updates and - Check for database updates. They allow you to determine if, and - how often, CodeSnip checks to see if updates to the program and - the online Code Snippets Database are available, respectively. -

          -

          - The available choices are: -

          -
            -
          • - Never -
          • -
          • - Daily -
          • -
          • - Every 3 Days -
          • -
          • - Weekly -
          • -
          • - Fornightly -
          • -
          • - Monthly -
          • -
          -

          - The default value is Weekly. -

          -

          - Select Never if you don't want CodeSnip to check for - updates automatically. You can still check manually as follows: -

          -
            -
          • - For program updates use the Tools | Check For Program Updates - menu option. -
          • -
          • - For database updates use the Database | Update From Web menu - option. -
          • -
          -

          - CodeSnip performs update checking in a background thread shortly - after starting up. If there are no updates available you will see nothing. - If any updates are available a small notification window is displayed in - the bottom right hand side of the main window. The notification window - will be displayed for about 20 seconds or until you close it. -

          -

          - The notification window will describe the available update and provide a - button to click to enable it to be downloaded. -

          - - diff --git a/Src/Help/HTML/dlg_print.htm b/Src/Help/HTML/dlg_print.htm index 7c466aa78..7d398aedf 100644 --- a/Src/Help/HTML/dlg_print.htm +++ b/Src/Help/HTML/dlg_print.htm @@ -2,12 +2,9 @@ diff --git a/Src/Help/HTML/dlg_programupdates.htm b/Src/Help/HTML/dlg_programupdates.htm deleted file mode 100644 index b32f8ea43..000000000 --- a/Src/Help/HTML/dlg_programupdates.htm +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - Check For Program Updates Dialogue Box - - - - - - - - -

          - - Check For Program Updates Dialogue Box -

          -

          - This dialogue box is displayed when the Tools | Check For Program - Updates menu option is selected. -

          -

          - As soon as the dialogue box appears the DelphiDabbler web site is - contacted to find out if any new versions of CodeSnip are - available. "Checking..." is displayed while this is in progress. -

          -

          - If a new version is available then a button is displayed that, when - clicked, uses your default web browser to download a zip file containing - the latest version of CodeSnip. You can then - close CodeSnip and install the updated program from the installer - contained in the zip file. -

          -

          - If there is no new version, a message is displayed to - that effect. -

          -

          - Notes: -

          -

          - † Should a download fail you can also get the latest version from - CodeSnip's download web page. -

          -

          - ‡ Only stable releases of CodeSnip are made available via - this dialogue box. If you are interested trying any pre-release or beta - versions, please check the CodeSnip News Feed which carries announcements of all such - releases. -

          -

          - By default CodeSnip performs a check for program updates - automatically from time to time. A pop-up notification window is displayed - in the main window if an update is available. The notification includes - a button that causes the update to be downloaded via the default browser, - in an exactly similar way to this dialogue box. You can switch this - automatic checking feature off, or change the frequency with which checks - are made, by using the Updates tab of the Preferences dialogue box. -

          - - diff --git a/Src/Help/HTML/dlg_proxyserver.htm b/Src/Help/HTML/dlg_proxyserver.htm deleted file mode 100644 index 08b5078b7..000000000 --- a/Src/Help/HTML/dlg_proxyserver.htm +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - - Proxy Server Dialogue Box - - - - - - - -

          - Proxy Server Configuration Dialogue Box -

          -

          - Use this dialogue to specify any proxy server that you want - CodeSnip to use when it accesses the internet. The dialogue is - accessed from the Tools | Proxy Server menu option. -

          -

          - To use a proxy server tick the User Proxy Server check box then - provide the required information in the rest of the dialogue box. -

          -
            -
          • - Server IP address – Enter the IP address of your server - in dotted quad format, i.e. xxx.xxx.xxx.xxx where xxx is a number - between 0 and 255. -
          • -
          • - Port – Enter the number of the port used by the proxy - server. -
          • -
          • - User Name – Provide any user name you need to access the - proxy server. This may be left blank if your server does not require a - user name. -
          • -
          • - Password – Enter any required password here. May be left - blank if no password is required. -
          • -
          • - Re-enter Password – If you entered a password above you - should type it again here. -
          • -
          -

          - If you wish to stop using a proxy server you should clear the Use - proxy server check box. -

          -

          - Click OK once you have entered the correct details. If a proxy - server has been specified CodeSnip will use it whenever accessing - the internet. -

          -

          - Note: CodeSnip does not use this proxy when it - displays web links in your default browser: you must configure your - browser to use the required proxy server. -

          - - - diff --git a/Src/Help/HTML/dlg_register.htm b/Src/Help/HTML/dlg_register.htm deleted file mode 100644 index fa55b5b8e..000000000 --- a/Src/Help/HTML/dlg_register.htm +++ /dev/null @@ -1,105 +0,0 @@ - - - - - - - Registration Wizard - - - - - - - -

          - Registration Wizard -

          -

          - This dialogue box is used to register CodeSnip. -

          -

          - Note You do not have to register CodeSnip to - use it. However, the author hopes you will register so that he knows that - CodeSnip is being used and is worth maintaining. -

          -

          - The dialogue takes the form of a multi-page wizard that gathers - registration information and submits it online. You move through the - pages by clicking the Next button and can return to a previous - page using the Back button. The Cancel button closes - the wizard without registering the application. -

          -

          - First page -

          -

          - This page simply displays some introductory text. -

          -

          - Second page -

          -

          - You use this page to provide information about yourself. All you need to - provide is your name or a nickname. -

          -

          - Third page -

          -

          - Along with the information you have entered CodeSnip will have - collected some information about itself and your operating system that it - will send to the web server. All the information that will be sent is - displayed on this page. -

          -

          - If you are happy to send the information please make sure you are - connected to the internet and click the Submit button. -

          -

          - The program will now attempt to connect to delphidabbler.com and send the registration information. If the - report is sent successfully the wizard's final page will be displayed. - Should an error be detected an error message describing the problem will - be displayed. -

          -

          - If you have concerns about privacy, please read the privacy statement before submitting. -

          -

          - Fourth page -

          -

          - This page is shown when the registration has been successful. The - program's registration code will be displayed in a box. This value - is automatically stored by CodeSnip in its configuration file. -

          -

          - You can now close the dialogue box by clicking the Finish button. -

          -

          - Please note CodeSnip should be registered only once per computer, - regardless of the number of users. If other users wish to join the mailing - list they can do so using the Tools | Join Mailing List menu - option. -

          - - diff --git a/Src/Help/HTML/dlg_registercompilers.htm b/Src/Help/HTML/dlg_registercompilers.htm new file mode 100644 index 000000000..3dc52b335 --- /dev/null +++ b/Src/Help/HTML/dlg_registercompilers.htm @@ -0,0 +1,66 @@ + + + + + + + Unregistered Delphi Installation(s) Detected Dialogue Box + + + + + + + +

          + Unregistered Delphi Installation(s) + Detected Dialogue Box +

          +

          + This dialogue box is displayed at program start-up if CodeSnip + detects that Delphi compilers are installed on the user's system that have + not yet been registered for use by CodeSnip. +

          +

          + The available compilers are listed at the top of the dialogue box. Each + compiler has a check box to its left. You should tick this check box to + permit CodeSnip to register the compiler or leave it clear if you + don't want to register the compiler with CodeSnip. +

          +

          + Underneath the list of compilers are some instructions about how to + disable auto-registration of some or all compilers. +

          +

          + Once you have made your selection click the OK button to register + the selected compilers with CodeSnip. A message box will pop up + to confirm that the compilers have been registered. +

          +

          + Preventing automatic detection +

          +

          + You can prevent CodeSnip from checking for any + new compilers on startup in future by ticking the Don't show this + again check box before clicking OK. +

          +

          + If you change your mind startup checks can be re-enabled. Alternatively + you can exclude individual Delphi compilers from automatic detection. + For information about how to do this see Register Compilers with CodeSnip. +

          + + diff --git a/Src/Help/HTML/dlg_renamecategory.htm b/Src/Help/HTML/dlg_renamecategory.htm index 96e8c23f9..5157e6e8f 100644 --- a/Src/Help/HTML/dlg_renamecategory.htm +++ b/Src/Help/HTML/dlg_renamecategory.htm @@ -2,12 +2,9 @@ diff --git a/Src/Help/HTML/dlg_restore.htm b/Src/Help/HTML/dlg_restore.htm index fdc437eb9..a1d1f5b41 100644 --- a/Src/Help/HTML/dlg_restore.htm +++ b/Src/Help/HTML/dlg_restore.htm @@ -2,12 +2,9 @@ @@ -33,17 +30,21 @@ value="RestoreBackupDlg">

          - Open Backup Dialogue Box + Open Backup File Dialogue Box

          This Windows standard dialogue box is displayed when the Database | Restore User Database menu option is selected. Use the dialogue to - specify the name and location of the previously created backup file that - contains the database to be restored. + specify the name and location of the backup file that contains the + database to be restored. +

          +

          + An error message will be displayed if the selected file is not a valid + backup file.

          - Click OK restore the database. The existing user database will be - overwritten. + Click OK to restore the backup. The existing user database will + be overwritten.

          diff --git a/Src/Help/HTML/dlg_savehiliter.htm b/Src/Help/HTML/dlg_savehiliter.htm index 6413d49a5..0dd906a30 100644 --- a/Src/Help/HTML/dlg_savehiliter.htm +++ b/Src/Help/HTML/dlg_savehiliter.htm @@ -2,12 +2,9 @@ @@ -27,17 +24,17 @@

          - Save Highlighter Dialogue Box + Save Highlighter Dialogue Box

          This dialogue box is displayed by clicking the Save Style button - in the Syntax Highlighter tab of the + in the Syntax Highlighter page of the Preferences dialogue box.

          The dialogue box is used to save a custom syntax highlighter style. Provide a name for the style in the Save current highlighter as - combo box. You can overwrite an existing style by selecting its name from + combo box. You can overwrite any existing style by selecting its name from the combo box's drop down list.

          @@ -47,7 +44,7 @@

          You can use a named style by selecting it in the User Defined Highlighters dialogue box, accessed from the Syntax Highlighter - preferences tab. + preferences page's Use Named Style drop-down button.

          diff --git a/Src/Help/HTML/dlg_saveinfo.htm b/Src/Help/HTML/dlg_saveinfo.htm new file mode 100644 index 000000000..e35745cdb --- /dev/null +++ b/Src/Help/HTML/dlg_saveinfo.htm @@ -0,0 +1,119 @@ + + + + + + + + + Save Snippet Information Dialogue Box + + + + + + + + +

          + Save Snippet Information Dialogue Box +

          +

          + This dialogue box is displayed when the File | Save Snippet + Information menu option is clicked. It is used to specify the file + name, file type and encoding information for the snippet information + that is to be saved. +

          +

          + The dialogue is a standard Windows save dialogue box with a few added + options. +

          +

          + You specify the name and folder for the file where the snippet information + is to be written in in the usual way. +

          +

          + Use the Save as type drop down list to specify the type of file + to be saved. Options are: +

          +
            +
          • Plain text.
          • +
          • HTML
          • +
          • XHTML
          • +
          • Rich text format
          • +
          • Markdown
          • +
          +

          + The HTML 5 and XHTML options are very similar and differ only in the + type of HTML that is written. For either type an embedded CSS style + sheet is used to style the document. +

          +

          + When any of the HTML 5, XHTML or rich text file types are selected source + code embedded in the snippet information will be syntax highlighted if + the Use syntax highlighting check box is checked. +

          +

          + The output file encoding can be be specified in the File Encoding + drop down list. Options vary depending on the file type. Some file types + support only a single encoding. The encodings are: +

          +
            +
          • + ANSI Code Page nnn – ANSI encoding for the system default code page, + where nnn is the code page for the user's locale. + Available as an option for plain text and Markdown file formats. +
          • +
          • + UTF-8 – UTF-8 encoding, with BOM. + Available as an option for plain text and Markdown file formats and + as the only encoding available for HTML 5 and XHTML file formats. +
          • +
          • + UTF-16 Little Endian – UTF-16 LE encoding, with + BOM. Available as an option for plain text and Markdown file formats. +
          • +
          • + UTF-18 Big Endian – UTF-16 BE encoding, with + BOM. Available as an option for plain text and Markdown file formats. +
          • +
          • + ASCII – The only encoding available for the rich text file. +
          • +
          +

          + The output can be previewed by clicking the Preview button. This + displays the snippet information in a dialogue box, formatted according to your + selections. Text in the preview can be selected and copied to the + clipboard if required. +

          +

          + Use the Save button to write the snippet information to disk or choose + Cancel to abort. +

          +

          + Warning: When plain text or Markdown formatted + snippet information is written in ANSI format it is possibe that the information + contains characters that can't be represented in the system default ANSI encoding. + If this happens a warning + dialogue box is displayed whenever the snippet information is written to file + or is previewed. +

          +

          + Footnote +

          +

          + † BOM = Byte Order Mark or Preamble: a sequence of bytes at the + start of a text file that identifies its encoding. +

          + + + \ No newline at end of file diff --git a/Src/Help/HTML/dlg_saveselection.htm b/Src/Help/HTML/dlg_saveselection.htm index c3cd33d86..8d3ee9153 100644 --- a/Src/Help/HTML/dlg_saveselection.htm +++ b/Src/Help/HTML/dlg_saveselection.htm @@ -2,12 +2,9 @@ @@ -27,7 +24,7 @@

          - Save Selection Dialogue Box + Save Selection Dialogue Box

          This Windows standard dialogue box is displayed when the File | Save @@ -40,8 +37,8 @@

          Save as type drop-down list and chosing "All files".

          - Click OK to confirm the selection. If the file aleady exists you - will be prompted for permission to overwrite it. Note that if the + Click OK to confirm the save. If the chosen file aleady exists + you will be prompted for permission to overwrite it. Note that if the "CodeSnip selection files" file type is selected and a file name with no extension is entered then ".cssel" will be appended to it. diff --git a/Src/Help/HTML/dlg_savesnippet.htm b/Src/Help/HTML/dlg_savesnippet.htm index dff0a2de8..3e8eba30a 100644 --- a/Src/Help/HTML/dlg_savesnippet.htm +++ b/Src/Help/HTML/dlg_savesnippet.htm @@ -2,12 +2,9 @@ @@ -68,21 +65,29 @@

          • - A Pascal include file (.inc) - The code is written to a file with - extension *.inc that can be included in any Pascal source file. + A Pascal include file (.inc) – The code is written to a file with + extension .inc that can be included in any Pascal source file. The file + should be valid for inclusion in a Pascal unit using the + $INCLUDE (or $I) directive. +
          • +
          • + A plain text file (.txt) – This is the same as the Pascal include + file except that the extension is .txt rather than .inc.
          • - A plain text file (.txt) - This is the same as the Pascal include file - except that the extension is .txt rather than .inc. + A HTML 5 file (.html) – This option writes the source code out as a + valid HTML 5 document that uses embedded CSS to format the code. The + source code will be syntax highlighted if the Use syntax + highlighting check box is checked.
          • - An HTML file (.html) - This option writes the source code out as a - valid XHTML document that uses CSS to format the code. The document can - be syntax highlighted if the Use syntax highlighting - check box is checked. + An XHTML file (.html) – This option writes the source code out as a + valid XHTML document that uses embedded CSS to format the code. The + source code will be syntax highlighted if the Use syntax + highlighting check box is checked.
          • - A rich text file (.rtf) - The source code is written out as an RTF + A rich text file (.rtf) – The source code is written out as an RTF document. Again syntax highlighting can be used if the Use syntax highlighting check box is checked.
          • @@ -90,12 +95,44 @@

            Snippet descriptions can be written as a Pascal comment to the output file. Use the Comment style drop down list to specify the style - of commenting to be used. Comments can be inhibited using by selecting the - No comments option from the list. If the Truncate comments to - 1st paragraph check box is ticked then only the first paragraph of - the snippet's description is used. Clearing this check box means that all - of the description is used. + of commenting to be used. Such comments can be inhibited by selecting the + No descriptive comments option from the list. If the Truncate + comments to 1st paragraph check box is ticked then only the first + paragraph of the snippet's description is used. Clearing this check box + means that the full text of the description is used.

            +

            + The output file encoding can be be specified in the File Encoding + drop down list. Options vary depending on the file type. Some file types + support only a single encoding, in which case the drop down list will be + disabled. The encodings are: +

            +
              +
            • + ANSI Code Page nnn – ANSI encoding for the system default code page, + where nnn is the code page for the user's locale. + Available for both plain text and Pascal include files. +
            • +
            • + UTF-8 – UTF-8 encoding, with BOM. + Available for both plain text and Pascal include files and as the only + option for HTML5 and XHTML files. If used for Pascal include files be warned that + the files will only compile with compilers that support Unicode source + files. +
            • +
            • + UTF-16 Little Endian – UTF-16 LE encoding, with + BOM. Available for plain text files only. +
            • +
            • + UTF-18 Big Endian – UTF-16 BE encoding, with + BOM. Available for plain text files only. +
            • +
            • + ASCII – ASCII encoding. Available as the only option for + rich text files. +
            • +

            The output can be previewed by clicking the Preview button. This displays the source code in a dialogue box, formatted according to your @@ -106,6 +143,13 @@

            Use the Save button to write the file to disk or press Cancel to abort.

            +

            + Footnote +

            +

            + † BOM = Byte Order Mark or Preamble: a sequence of bytes at the + start of a text file that identifies its encoding. +

            diff --git a/Src/Help/HTML/dlg_saveunit.htm b/Src/Help/HTML/dlg_saveunit.htm index c04684b24..22c3c7253 100644 --- a/Src/Help/HTML/dlg_saveunit.htm +++ b/Src/Help/HTML/dlg_saveunit.htm @@ -2,12 +2,9 @@ @@ -32,7 +29,7 @@

            This dialogue box is displayed when the File | Save Unit menu option is clicked. It is used to specify the file name, file type and - formatting information for the source code unit that is to be saved. + formatting information for the unit that is to be saved.

            All currently selected snippets, with the exception of any @@ -46,7 +43,7 @@

            You specify the name and folder for the file where the unit will be saved - in in the usual way. Note that the chosen file name, minus its extension + in in the usual way. Warning: the chosen file name, minus its extension must be a valid Pascal unit name.

            @@ -55,21 +52,27 @@

            • - A Pascal unit file (.pas) - The code is written to a file with + A Pascal unit file (.pas) – The code is written to a file with extension .pas. The unit is given a name based on the file name.
            • - A plain text file (.txt) - This is the same as the Pascal unit file - except that the extension is .txt rather than .pas. + A plain text file (.txt) – This is the same as the Pascal unit + file except that the extension is .txt rather than .pas. +
            • +
            • + A HTML 5 file (.html) – This option writes the source code out as a + valid HTML 5 document that uses embedded CSS to format the code. The + source code will be syntax highlighted if the Use syntax + highlighting check box is checked.
            • - An HTML file (.html) - This option writes the source code out as a - valid XHTML document that uses CSS to format the code. The document can - contain syntax highlighting if the Use syntax highlighting - check box is checked. + An XHTML file (.html) – This option writes the source code out as a + valid XHTML document that uses embedded CSS to format the code. The + source code will be syntax highlighted if the Use syntax + highlighting check box is checked.
            • - A rich text file (.rtf) - The source code is written out as an RTF + A rich text file (.rtf) – The source code is written out as an RTF document. Again syntax highlighting can be used if the Use syntax highlighting check box is checked.
            • @@ -77,12 +80,44 @@

              Each snippet's description can be written as a Pascal comment to the output file. Use the Comment style drop down list to specify the - style of commenting to be used. Comments can be inhibited using by - selecting the No comments option from the list. If the - Truncate comments to 1st paragraph check box is ticked then only - the first paragraph of the snippet's description is used. Clearing this - check box means that all of the description is used. + style of commenting to be used. Such comments can be inhibited by + selecting the No descriptive comments option from the list. If + the Truncate comments to 1st paragraph check box is ticked then + only the first paragraph of each snippet's description is used. Clearing + this check box means that the full text of snippet descriptions are used.

              +

              + The output file encoding can be be specified in the File Encoding + drop down list. Options vary depending on the file type. Some file types + support only a single encoding, in which case the drop down list will be + disabled. The encodings are: +

              +
                +
              • + ANSI Code Page nnn – ANSI encoding for the system default code page, + where nnn is the code page for the user's locale. + Available for both plain text and Pascal unit files. +
              • +
              • + UTF-8 – UTF-8 encoding, with BOM. + Available for both plain text and Pascal unit files and as the only + option for HTML 5 and XHTML files. If used for Pascal units be warned that the + unit will only compile with compilers that support Unicode source + files. +
              • +
              • + UTF-16 Little Endian – UTF-16 LE encoding, with + BOM. Available for plain text files only. +
              • +
              • + UTF-18 Big Endian – UTF-16 BE encoding, with + BOM. Available for plain text files only. +
              • +
              • + ASCII – ASCII encoding. Available as the only option for + rich text files. +
              • +

              The output can be previewed by clicking the Preview button. This displays the source code in a dialogue box, formatted according to your @@ -90,8 +125,15 @@

              clipboard if required.

              - Use the Save button to write the source code to disk or - Cancel to abort. + Use the Save button to write the unit to disk or Cancel + to abort. +

              +

              + Footnote +

              +

              + † BOM = Byte Order Mark or Preamble: a sequence of bytes at the + start of a text file that identifies its encoding.

              diff --git a/Src/Help/HTML/dlg_selectcompiler.htm b/Src/Help/HTML/dlg_selectcompiler.htm index 6822bcdba..0acd280f0 100644 --- a/Src/Help/HTML/dlg_selectcompiler.htm +++ b/Src/Help/HTML/dlg_selectcompiler.htm @@ -2,12 +2,9 @@ @@ -38,12 +35,12 @@

              Click OK to confirm the selection. This will close the dialogue - box and enter the selected file name into the edit box next the the + box and enter the selected file name into the edit box next to the ellipsis button. If the selected file does not exist an error message will be displayed and the dialogue box will not close.

              - Click the Cancel to cancel the selection. + Click the Cancel button to cancel the selection.

              diff --git a/Src/Help/HTML/dlg_selectroutines.htm b/Src/Help/HTML/dlg_selectroutines.htm index 8c07fc65b..c239e5ad3 100644 --- a/Src/Help/HTML/dlg_selectroutines.htm +++ b/Src/Help/HTML/dlg_selectroutines.htm @@ -2,12 +2,9 @@ @@ -44,18 +41,18 @@

              Snippets are displayed sorted under categories. When the dialogue box opens only the categories are shown. They can be expanded and collapsed in the usual way: by clicking the arrow symbols (or plus/minus symbols) - that appear to the left of the categories; by double clicking the - category; by using the Left, + that appear to the left of the categories, by double clicking the + category or by using the Left, Right and Enter keys. Alternatively use the Expand All and Collapse All buttons on the right of the dialogue box.

              - Snippets are selected simply by placing a check mark next to the name of - the required snippets. Checking or unchecking a category will select or + Snippets are selected by placing a check mark next to the name of the + required snippets. Checking or unchecking a category will select or de-select all the snippets in the category. When the dialogue box appears - all snippets currently displayed by the program are selected. + all snippets currently displayed by the program are pre-selected.

              There are also four buttons that can be used to select or deselect groups @@ -63,29 +60,32 @@

              • - Select All selects all the snippets in the database. + Select All
                + Selects all the snippets in the database.
              • - Clear All deselects all the snippets in the database. + Clear All
                + Deselects all the snippets in the database.
              • - User Defined selects all the user defined snippets in the - database. The button will be disabled if there are no user defined - snippets. + User Defined
                + Selects all the user defined snippets in the database. The button will + be disabled if there are no user defined snippets.
              • - Main selects all the snippets in the main database. + Main
                + Selects all the snippets in the main database.

              - When the selection has been made, click the OK to accept the - changes. At least one snippet must be selected. Click Cancel to - abort your selection. + When the selection has been made, click the OK button to accept + the changes. At least one snippet must be selected. Click Cancel + to abort your selection.

              - NOTE: Any selection made using this dialogue box will + Note: Any selection made using this dialogue box will replace any search results that were previously displayed. A message to - this effect will be displayed in the dialogue box if it is displayed when + this effect will be displayed in the dialogue box if it is opened while search results are being displayed.

              diff --git a/Src/Help/HTML/dlg_submit.htm b/Src/Help/HTML/dlg_submit.htm deleted file mode 100644 index b9e12b81a..000000000 --- a/Src/Help/HTML/dlg_submit.htm +++ /dev/null @@ -1,158 +0,0 @@ - - - - - - - Code Submission Wizard - - - - - - - -

              - Code Submission Wizard -

              -

              - This wizard is used to submit user defined snippets for inclusion in the - online Code Snippets database. It is accessed Snippets | Submit - Snippets menu item. The menu item is only available if there are user - defined snippets in the database. -

              -

              - The various pages of the wizard are discussed below. You can cancel your - submission at any time the Cancel button is enabled. -

              -

              - First page -

              -

              - This is simply an introductory page that describes the wizard. Click the - Next button to display the Select Snippets page and to - begin using the wizard. -

              -

              - "Select Snippets" page -

              -

              - This page displays a check list of all the user-defined snippets in the - database. It is used to select the snippets you wish to submit. -

              -

              - Check the names of the required snippets. Checking a category selects all - the snippets in the category. At least one snippet must be checked before - moving on to the next page. If a user-defined snippet was displayed in the - main window when the wizard was started, that snippet will be pre-selected - in the check list. -

              -

              - Click the Next button to move the next page. If no snippets are - selected an error message is displayed. -

              -

              - "About You" page -

              -

              - In order to credit you, and to contact you about any problems, your name - and email address are needed. They must be entered on this page in the - Name and Email Address text boxes. You can - also enter any comments or a message on the page using the optional - Your Comments text box. -

              -

              - If you have successfully submitted code before CodeSnip will - remember the name and email address you last used and will have entered - them in the appropriate text boxes. Please check these are correct and - alter as necessary. -

              -

              - You cannot move to the next page until your name and a valid email address - are provided. Once the data has been entered click the Next - button to move to the next page. -

              -

              - You should provide a valid, working, email address in case there is a - need to contact you with any licensing queries. In the case where a dummy - email address is provided your submission may be rejected. -

              -

              - Note: your personal data will not be abused and your - email address will not be published. See the - privacy statement for more - information. -

              -

              - "License agreement" page -

              -

              - All the code in the Code Snippets Database is released under the MIT License and is copyright by the Code Snippets Database contributors. -

              -

              - In order to submit snippets to the database you must confirm that you have - the right to submit the snippets and - that you agree for it to be released under the MIT license. -

              -

              - "Ready to Submit" page -

              -

              - You have now provided all the required information. The only additional - information that gets sent is the program's version number. If you - want to see the data that will be submitted (in XML format) click the - Preview Data button. -

              -

              - Now ensure you are connected to the internet and click the Submit - button to send the data to the DelphiDabbler website. -

              -

              - If the data is sent successfully the final page of the wizard will be - displayed. If there was an error in sending the data an error message will - be displayed and the current page will remain. -

              -

              - "Submission Complete" page -

              -

              - This page is displayed when the subscription succeeds – i.e. the - information has been sent to the DelphiDabbler website. The code - does not immediately appear on the database – it will be reviewed, - tested and revised as necessary before publication. You will be emailed if - there are any queries. -

              -

              - Click the Finish button to close the wizard. -

              - - - diff --git a/Src/Help/HTML/dlg_swagimport.htm b/Src/Help/HTML/dlg_swagimport.htm index 283296137..a70ef6f8a 100644 --- a/Src/Help/HTML/dlg_swagimport.htm +++ b/Src/Help/HTML/dlg_swagimport.htm @@ -2,12 +2,9 @@ @@ -30,88 +27,129 @@

              SWAG Import Wizard

              - This wizard is used to enable you import snippets from the + This wizard is used to enable you to import packets from the SWAG Database into your code snippets - database. The wizard is accessed from the Snippets | Import + database. (A SWAG packet is roughly the equivalent of a + CodeSnip snippet: packets are converted into snippets before + being imported.) +

              +

              + The wizard is accessed from the Snippets | Import Snippets From SWAG menu option.

              - First page + Page 1: Import packets from SWAG as new snippets

              - This page simply gives an overview of how to use the wizard. Click the - Next button to display the Select required snippets - page. + This page gives an overview of how to use the wizard. +

              +

              + The wizard requires that you have a suitable copy of SWAG stored on your + computer. If you haven't yet got it then you need to download it before + continuing. The instructions shown in the box on this page of the wizard + explain how to do this. +

              +

              + When you are ready, click the Next button.

              - "Select required snippets" page + Page 2: Select SWAG database download folder

              - When this page is first displayed it downloads information about the - available categories from the SWAG database. A list of categories is then - displayed in the left hand, SWAG categories, list box. + On this page you need to provide the full path to the SWAG database on + your system. By default the required folder is the swag + sub-directory of the folder where you extracted the SWAG zip file. +

              +

              + Enter the path in the edit box. You can click the ellipsis button to the + right of the edit box to display a standard browse for folder dialogue box + and choose the folder from there. The chosen folder will be entered into + the edit box for you.

              - Choose a category in the list and click the Show Snippets In - Category button to display a list of its snippets in the right - hand list box. The first time you choose a category the wizard will - download the snippet list from the SWAG database. Note that - double-clicking a category has the same effect as clicking the Show - Snippets In Category button. + Once you have entered the path click Next. The data in the folder + will then be read and, if valid, the next page of the wizard will be + displayed. A progress bar may be displayed if the process of reading the + data takes some time. If the data is invalid an error message will be + displayed and the page will not change.

              +

              + Page 3: Select required packets +

              - Select any snippets you want to import by ticking the check box next to - the snippet's name. If you want to preview the snippet before deciding - whether to import it highlight the snippet and click the Preview - Selected Snippet button. The snippet will then be downloaded (if - necessary) and displayed in a dialogue box. Note that double-clicking a - snippet has the same effect as clicking the Preview Selected - Snippet button. + When this page is first displayed it displays all the categories of + packets available in the SWAG database in the SWAG categories + list box. +

              +

              + Choose a category in the list and click the Show Packets In + Category button to display a list of its packets in the right + hand list box. Alternatively just double-click the category name. The + first time you choose a category the wizard will read the list of packets + from the database and a progress bar may be displayed while this is done. +

              +

              + Select any packets you want to import by ticking the check box next to + the packet's name. If you want to preview the packet before deciding + whether to import it, highlight it and click the Preview Selected + Packet button. Alternatively just double click the packet name. The + packet will then be read from the database (if necessary) and displayed + in a dialogue box.

              Repeat this process for further categories if you wish. When you are - ready to import your chosen snippets click the Next button to - display the Ready to import page. + ready to import your chosen packets click the Next button to + display the next page. Note that you must select at least one packet in + order to move on. +

              +

              + Warning: Do not select too many packets + to be imported at one time because the import process is not very + efficient and can take a long time.

              - "Ready to import" page + Page 4: Ready to import

              - This page lists all the snippets you have selected for import along with - the unique ID (i.e. snippet name) that will be assigned to it after - import. Note that snippets may be downloaded from the SWAG database when - the page is displayed. + This page lists all the packets you have selected for import along with + the unique ID (i.e. snippet name) that will be assigned to the + CodeSnip snippet after import. Note that some packets may need to + be retrieved from the SWAG database when the page is displayed and a + progress bar may be displayed while this happens.

              You should review your selection. If you want to make any changes use the - Back button to go back Select required snippets page. + Back button to go back to the previous page and de-select any + unwanted packets or select any you may have missed.

              - When you are ready to import click the Import button. Once - Import has been clicked the wizard will perform the import. A - dialogue box will be displayed while the import proceeds. When the import - has finished the Import complete page. + When you are ready to proceed, click the Import button. Once + Import has been clicked the wizard will perform the import + – there is no going back. A dialogue box will be displayed while the + import proceeds. When the import has finished the final page of the wizard + will be displayed.

              - Import complete + Page 5: Import complete

              - This page simply confirms the import has completed. You should note that - the snippets will have been imported into a special category named - "SWAG Imports" by default. + This page simply confirms the import has completed. All the selected + packets will have been converted to snippets and imported into the same + special category, the name of which will be displayed.

              - Click the Finish to close the wizard. + Click the Finish button to close the wizard.

              The imported snippets are fully editable like any other user defined snippet.

              - Note: CodeSnip does not track snippets imported - from SWAG, so if you import same snippet more than once it will be - duplicated in your local database – it will not overwrite the - original. + Warning: CodeSnip does not track + packets imported from SWAG, so if you import the same packet more than + once a duplicate snippet will be created for it in your local database + – the original will not be overwritten. You will not be warned about + such duplications.

              diff --git a/Src/Help/HTML/dlg_testcompile.htm b/Src/Help/HTML/dlg_testcompile.htm index 287e82ea9..ffa5e947e 100644 --- a/Src/Help/HTML/dlg_testcompile.htm +++ b/Src/Help/HTML/dlg_testcompile.htm @@ -2,12 +2,9 @@ diff --git a/Src/Help/HTML/dlg_trappedbugreport.htm b/Src/Help/HTML/dlg_trappedbugreport.htm index 429606e74..e520d49ca 100644 --- a/Src/Help/HTML/dlg_trappedbugreport.htm +++ b/Src/Help/HTML/dlg_trappedbugreport.htm @@ -2,12 +2,9 @@ - Update From Web Dialogue Box + Install or Update DelphiDabbler Snippets Database Wizard @@ -24,90 +21,99 @@ type="application/x-oleobject" classid="clsid:1e2a7bd0-dab9-11d0-b93a-00c04fc99f9e" > - +

              - Update From Web Dialogue Box + Install or Update DelphiDabbler Snippets Database + Wizard

              - This dialogue box is used to update the CodeSnip database from - the internet. It is displayed using the Database | Update From - Web menu option. + This wizard dialogue box is used to install or update CodeSnip's + main snippets database. It is displayed using the Database | Install + or Update DelphiDabbler Snippets Database menu option.

              - The Update from Web button should be clicked to initiate the - update. Note: you must ensure you are connected to the - internet before clicking this button otherwise a "Connection - Error" message will be displayed. + The wizard navigates you through the installation process, with each page + appearing in a fixed sequence.

              - Once the Update from Web button has been clicked it will be - replaced by a progress report pane. The update will then begin. + Warning: if you are using the standard + version of CodeSnip updating this database will affect all users + of this computer because each user shares the same copy of the database.

              +

              + Page 1: Download the database +

              - CodeSnip first logs on to the web server and then determines if - your copy of the database is out of date. If so the latest version is - downloaded. A progress bar is displayed while the database is being - downloaded. A confirmatory message is displayed once the process is - complete. + Before the database can be installed it must first be downloaded from the + internet and stored in a folder on your local computer. This page explains + how.

              - Note: The database will be updated for all users, since - each user accesses the same copy of the database. + Follow the instructions on how to download and unpack the database into a + folder then come back to the wizard and press the Next button to + go to the next page.

              - Cancelling the update + Page 2: Select database download folder

              - The Close button is replaced by a larger Cancel Update - button while the update is in progress. This button can be clicked at any - time to cancel the update. CodeSnip will restore the previous - database. Note: database downloads can't be - interupted. + On this page you need to provide the full path to the folder where you + downloaded the DelphiDabbler Code Snippets Database. Enter the path in the + edit box. You can click the ellipsis button to the right of the edit box + to display a standard browse for folder dialogue box and choose the folder + from there. The chosen folder will be entered into the edit box for you. +

              +

              + Once you have entered the path click Next. The data in the folder + will then be checked and, if valid, the next page of the wizard will be + displayed. If the data is invalid an error message will be displayed and + the page will not change.

              - Update errors + Page 3: Install the database

              - If CodeSnip detects an error while updating it aborts the - update, restores the old database and displays a brief error message at - the top of the the dialogue box. More details of the error are displayed - in the progress report pane. + You are now ready to install the latest version of the database.

              - If you see a "Connection Error" check your internet connection - and try again. If you receive web server or time out errors this may be - due to the server being down or busy so it is worth trying later. + Simply click the Load button when you are ready to proceed. + Note: this is the last chance to change your mind – + you can't go back once the Load button has been pressed.

              - If a server or timeout error persists please inform the author via the - DelphiDabbler website's - contact page. + On pressing Load a progress bar will be displayed while the + database is installed. +

              +

              + Once the installation is complete the final page of the wizard will be + displayed.

              - Latest CodeSnip News + Page 4: Database installed

              - Click the Latest News button at the bottom left of the dialogue - box to see the latest news about CodeSnip and the online - database. If this button is clicked while an update is in progress the - update is paused until the dialogue box is closed. -

              -

              - Note: By default CodeSnip checks online - from time to time to check if any database updates are available. A pop-up - notification window is displayed in the main window if an update is - available. This dialogue box can then be used to download the update. You - can switch this feature off, or change the frequency with which checks are - made, by using the Updates tab of the Preferences dialogue box. + This page confirms that the installation is complete. +

              +

              + If you were updating an older version of the database it will have been + overwritten. +

              +

              + Click the Finish button to close the wizard. +

              +

              + Finishing up +

              +

              + Once the wizard has closed CodeSnip will close all open tabs and + then reload the database with the changes made by the wizard. If you have + unsaved changes in your user database CodeSnip will offer to save + them before reloading the database. +

              +

              + Once the database has been reloaded a single tab will be opened that + confirms the database was updated.

              diff --git a/Src/Help/HTML/dlg_userbugreport.htm b/Src/Help/HTML/dlg_userbugreport.htm index 091257f45..3130ed660 100644 --- a/Src/Help/HTML/dlg_userbugreport.htm +++ b/Src/Help/HTML/dlg_userbugreport.htm @@ -2,12 +2,9 @@ @@ -35,8 +32,8 @@

              Bugs are not reported directly from the dialogue box. They are reported - using the CodeSnip online bug tracker. At the time of writing the - bug tracker is located on SourceForge.net. + using CodeSnip's online bug tracker on GitHub. A GitHub + account is required.

              The bug tracker is accessed by clicking the Go to the Bug Tracker @@ -58,9 +55,9 @@

              If the bug has not been reported, please create a new bug report and - provide as much information as you can. You should give steps to reproduce - the bug if possible. You can also upload a screenshot or other file that - helps to explain the bug. + provide as much information as you can. You should describe the steps to + take to reproduce the bug if possible. You can also upload a screenshot or + other file that helps to explain the bug.

              diff --git a/Src/Help/HTML/explain_all_compilers_hidden.htm b/Src/Help/HTML/explain_all_compilers_hidden.htm index be3f20528..efd5bd882 100644 --- a/Src/Help/HTML/explain_all_compilers_hidden.htm +++ b/Src/Help/HTML/explain_all_compilers_hidden.htm @@ -2,12 +2,9 @@ @@ -34,7 +31,7 @@

              » Go to the FAQs diff --git a/Src/Help/HTML/license.htm b/Src/Help/HTML/license.htm index 052a0b73e..5f57a6c32 100644 --- a/Src/Help/HTML/license.htm +++ b/Src/Help/HTML/license.htm @@ -2,12 +2,9 @@ @@ -30,12 +27,12 @@

              Summary of End User License Agreement

              - DelphiDabbler CodeSnip is copyright © 2005-2016 by Peter D + DelphiDabbler CodeSnip is copyright © 2005-2025 by Peter D Johnson, http://delphidabbler.com. + >https://gravatar.com/delphidabbler.

              The executable version of the program is made available under the terms of @@ -50,7 +47,7 @@

              You may also modify CodeSnip as you wish and you may distribute copies of your modified version under the terms of the Mozilla Public License. The only exception is that you may not use the CodeSnip - name or branding (e.g. the program icon) any any modification you + name or branding (e.g. the program icon) in any modification you distribute unless you have the explicit permission of the copyright holder.

              diff --git a/Src/Help/HTML/main_display.htm b/Src/Help/HTML/main_display.htm index 5764ff860..c12270f78 100644 --- a/Src/Help/HTML/main_display.htm +++ b/Src/Help/HTML/main_display.htm @@ -2,12 +2,9 @@ @@ -31,7 +28,7 @@

              Menu menu + >Main menu
              The main menu appears at the top of the window. @@ -78,9 +75,9 @@

              The status bar lies across the bottom of the window. It displays - information about the database and any actives searches. When a main - or context menu item is highlighted brief help text describing the - menu item is also displayed here. + information about the database and any active searches. When a main or + context menu item is highlighted brief help text describing the menu + item is also displayed here.

diff --git a/Src/Help/HTML/main_menu.htm b/Src/Help/HTML/main_menu.htm index ccd875d70..1b0a6fdc0 100644 --- a/Src/Help/HTML/main_menu.htm +++ b/Src/Help/HTML/main_menu.htm @@ -2,12 +2,9 @@ @@ -33,7 +30,7 @@

File - Save snippets and units, print snippets and exit the program. + Save snippets, units & selections, print snippets and exit the program. @@ -67,8 +64,8 @@

Snippets - Create, edit and delete user defined snippet. Also allows snippets to - be imported or exported and submitted for use in the on-line database. + Create, edit and delete user defined snippets. Also allows snippets to + be imported, exported and favourited. @@ -84,8 +81,7 @@

Database - Save, backup and restore the user database and update the main - database from the web. + Various database save, restore and update operations. @@ -102,7 +98,7 @@

Tools - Configruation, bug reporting and registration. + Configuration and bug reporting. @@ -110,8 +106,7 @@

Help - Help file, web links, license, copyright, donations, news and - acknowledgements. + Help file, license, web links and about box. diff --git a/Src/Help/HTML/markup_editor.htm b/Src/Help/HTML/markup_editor.htm index 75a84ed64..b4154bc18 100644 --- a/Src/Help/HTML/markup_editor.htm +++ b/Src/Help/HTML/markup_editor.htm @@ -2,12 +2,9 @@ @@ -54,14 +51,14 @@

edit control to be interpreted as plain text.

- All characters you enter are treated literally. You cannot style the text. - Any URLs you enter are treated as plain text and are not interpreted as - hyplerlinks. + All characters you enter are treated literally. You cannot format the + text, other than to separate it into paragraphs. Any URLs you enter are + treated as plain text and are not interpreted as hyperlinks.

- You can enter multiple paragraphs of text by separating each paragraph - with a blank line. Any text on consequtive lines is otherwise treated as - a single line. For example: + You enter multiple paragraphs of text by separating each paragraph with a + blank line. Any text on consecutive lines is treated as a single line. For + example:

Lorem ipsum dolor sit amet
@@ -97,11 +94,11 @@

Markup

- Choosing the Markup tab causes the entered text to be treated as - markup language, which is a very simple, HTML-like, language that allows - for a limited amount of text formatting and for the inclusion of - hyperlinks. For full details see the REML help - topic. + Choosing the Markup tab causes the entered text to be interpreted + as CodeSnip's own markup language, which is a very simple, + HTML-like, language that allows for a limited amount of text formatting + and for the inclusion of hyperlinks. For full details see the + REML help topic.

A fixed width font is used to indicate you are editing @@ -121,44 +118,44 @@

  • -
    +

    Convert To Plain Text -

    -
    +

    +

    This option is only available when the editor is in Markup mode. It replaces the REML markup in the edit control with the plain text equivalent. -

    -
    +

    +

    All in-line styling and hyperlinks are lost. Paragraphs and headings are converted to plain text paragraphs, separated by blank lines. -

    -
    +

    +

    The editor is switched into Plain Text mode. -

    -
    +

    +

    If there is an error in the markup an error message will be displayed, no conversion will take place and the mode will not be changed. -

    +

  • -
    +

    Convert To Markup -

    -
    +

    +

    This option is only available when the editor is in Plain Text mode. It replaces the text entered in the edit control with the equivalent REML code. -

    -
    +

    +

    Illegal REML characters are converted to character attributes. Paragraphs (i.e. blocks of text separated by blanks lines) are converted to REML paragraphs - using the <p> tag. -

    -
    + using the <p> tag. +

    +

    The editor is switched into Markup mode. -

    +

diff --git a/Src/Help/HTML/menu_categories.htm b/Src/Help/HTML/menu_categories.htm index dbb54e17b..657ea6561 100644 --- a/Src/Help/HTML/menu_categories.htm +++ b/Src/Help/HTML/menu_categories.htm @@ -2,12 +2,9 @@ diff --git a/Src/Help/HTML/menu_compile.htm b/Src/Help/HTML/menu_compile.htm index 0ecf569e4..95b3620af 100644 --- a/Src/Help/HTML/menu_compile.htm +++ b/Src/Help/HTML/menu_compile.htm @@ -2,12 +2,9 @@ @@ -21,7 +18,7 @@

- Compile Menu + Compile Menu

The Compile menu contains the following options: @@ -41,7 +38,8 @@

Test Compile Results dialogue box. See About Compiler Checks for more - information. + information. This option is disabled if the snippet is + freeform. @@ -53,6 +51,8 @@

Displays any test compilation errors and warnings in a dialogue box. + This option is disabled if the snippet is + freeform. @@ -64,7 +64,7 @@

Displays a dialogue box that shows source code of the unit used to - test compile the selected snippet. This option is not available if the + test compile the selected snippet. This option is disabled if the snippet is freeform. diff --git a/Src/Help/HTML/menu_database.htm b/Src/Help/HTML/menu_database.htm index 1e4a1770b..303a0d8c4 100644 --- a/Src/Help/HTML/menu_database.htm +++ b/Src/Help/HTML/menu_database.htm @@ -2,12 +2,9 @@ @@ -72,14 +69,16 @@

- Menu icon +   - Update From Web + Install or Update DelphiDabbler Snippets Database - Displays the Update from Web dialogue box - that enables the main code snippets database to be updated. + Displays the Install or Update DelphiDabbler + Snippets Database dialogue box that enables the main code snippets + database to be updated from a local copy of the DelphiDabbler Snippets + Database. @@ -90,13 +89,25 @@

Move User Database† - Displays the Move User Database + Displays the Move User Database dialogue box from where the user defined database files can be moved to a diffent folder. The main reason for this option is so that the - user can locate the database in a suitable location for backing up, + user can locate the database in a suitable location for backing up, such as a DropBox or Google Drive sub-directory. + + +   + + + Delete User Database + + + Displays the Delete User Database dialogue box that asks for permission to delete ALL the snippets created by the user. If permission is granted then all snippets user defined snippets will be deleted without any further warning. +
Use with caution – this action can't be undone. + +

† The Move User Database option is not available in the diff --git a/Src/Help/HTML/menu_edit.htm b/Src/Help/HTML/menu_edit.htm index 054588662..09d81d017 100644 --- a/Src/Help/HTML/menu_edit.htm +++ b/Src/Help/HTML/menu_edit.htm @@ -2,12 +2,9 @@ @@ -84,7 +81,7 @@

Copy Source Code
- [Shift+U] + [Shift+Ctrl+C] Copies only the source code of the selected snippet to the clipboard @@ -101,8 +98,6 @@

Selects all the text in the Detail Pane. - This menu option is disabled if the Detail Pane does not have the - input focus. diff --git a/Src/Help/HTML/menu_file.htm b/Src/Help/HTML/menu_file.htm index c1c06c6e3..a7beabcd4 100644 --- a/Src/Help/HTML/menu_file.htm +++ b/Src/Help/HTML/menu_file.htm @@ -2,12 +2,9 @@ @@ -40,13 +37,29 @@

to a file. The file contains an annotated fragment of Pascal code. The Save Annotated Source dialogue box is displayed and is used to determine the format of the file being - saved. This can be plain text, a Pascal include file, HTML or RTF. The - latter two options can be syntax highlighted. This option is available + saved. This can be plain text, a Pascal include file, HTML 5, XHTML or RTF. The + latter three options can be syntax highlighted. This option is available only for routine snippets or categories containing routines. Any snippets in a category that are not routines are ignored. + + +   + + + Save Snippet Information
+ [Shift+Ctrl+I] + + + Saves information about the currently selected snippet to file, in + rich text format. The information saved is that displayed in the + Detail Pane. + The Save Snippet Information dialogue + box is displayed where the required file name is entered. + + snippets and saves it to file. The Save Unit dialogue box is displayed and is used to determine the format of the file being saved. The format can be plain text, a Pascal unit - file, HTML or RTF. The latter two options can be syntax highlighted. + file, HTML 5, XHTML or RTF. The latter three options can be syntax highlighted. Freeform snippets are not included in the unit. @@ -75,7 +88,7 @@

[Shift+Alt+S] - Saves a list snippets selected by the most recent search. This + Saves a list of snippets selected by the most recent search. This selection can then be reloaded later. The Save Selection dialogue box is displayed where the required file name is entered. Note that this diff --git a/Src/Help/HTML/menu_help.htm b/Src/Help/HTML/menu_help.htm index 980f685f1..8ecd783f0 100644 --- a/Src/Help/HTML/menu_help.htm +++ b/Src/Help/HTML/menu_help.htm @@ -2,12 +2,9 @@ @@ -66,19 +63,6 @@

CodeSnip's ability to test compile snippets. - - -   - - - Privacy Statement - - - Selecting this option displays CodeSnip's - privacy statement in the help - file. - - Menu icon @@ -96,15 +80,16 @@

Menu icon - FAQs + CodeSnip On GitHub - Displays the + Displays the default page of CodeSnip FAQs in the default web browser. + >CodeSnip's GitHub Project in the default web browser. The latest + release of CodeSnip is always published here. @@ -112,16 +97,14 @@

Menu icon - CodeSnip Home Page + CodeSnip News On DelphiDabbler Blog - Displays the - CodeSnip Home Page in the default web browser. The latest release - of CodeSnip is always published here first. + >DelphiDabbler Blog in the default web browser. The latest news about CodeSnip is posted in this blog. @@ -129,46 +112,15 @@

Menu icon - DelphiDabbler Website + FAQs - Displays the home page of the + Displays the DelphiDabbler web site in the default web browser. The site has - more open source and free software, open source Delphi units, - components and IDE extensions. Several articles on various programming - subjects are also available. - - - - - Menu icon - - - CodeSnip News - - - Displays the CodeSnip News dialogue box that shows the latest news about - CodeSnip and the online database. - - - - - Menu icon - - - Donate - - - Choose this option to make a donation to help support development of - CodeSnip and the online database. Displays a dialogue box - containing a button to click to make a payment online using - PayPal™. + href="https://github.com/delphidabbler/codesnip-faq/blob/master/README.md" + target="_blank" + >CodeSnip FAQs in the default web browser. @@ -179,11 +131,7 @@

About - Displays the about box that shows the - program's version number, copyright information and credits. It - also provides links to the full license for the program and shows if - the program has been registered. See if you can find the Easter - Egg! + Displays the about box that shows the program's version number, copyright information and credits. It also provides links to the full license for the program. See if you can find the Easter Egg! diff --git a/Src/Help/HTML/menu_search.htm b/Src/Help/HTML/menu_search.htm index e529f21ee..1de316feb 100644 --- a/Src/Help/HTML/menu_search.htm +++ b/Src/Help/HTML/menu_search.htm @@ -2,12 +2,9 @@ @@ -50,7 +47,7 @@

Find Compiler(s)
- [Ctrl+Shift+F] + [Shift+Ctrl+F] Finds snippets that either compile, fail to compile or result in @@ -87,8 +84,8 @@

[Shift+Ctrl+S] - Allows manual selection of the snippets to be displayed. Displays the - Select Snippets dialogue box + Allows a manual selection of the snippets to be displayed. Displays + the Select Snippets dialogue box where the required snippets are selected. On completion the Overview Pane shows only the selected snippets. @@ -100,7 +97,7 @@

Show All
- [Ctrl+Shift+A] + [Shift+Ctrl+A] Restores the whole database after a find operation. All snippets in diff --git a/Src/Help/HTML/menu_snippets.htm b/Src/Help/HTML/menu_snippets.htm index 2fc42216c..9fc121355 100644 --- a/Src/Help/HTML/menu_snippets.htm +++ b/Src/Help/HTML/menu_snippets.htm @@ -2,12 +2,9 @@ @@ -116,23 +113,10 @@

Import Snippets From SWAG - Imports selected snippets from the SWAG - Database into the local user-defined snippets database. The - SWAG Import Wizard is displayed which - is used to select the required snippets. - - - - -   - - - Submit Snippets - - - Opens the Code Submission Wizard that is - used to submit user defined snippets for inclusion in the main - database. + Imports selected snippets from a local copy of the + SWAG Database into the local user-defined + snippets database. The SWAG Import + Wizard is displayed which is used to select the required snippets. @@ -145,7 +129,7 @@

Makes the currently display snippet a "Favourite". If the - snippet is already a favourite this option has no effect. + snippet is already a favourite this option is disabled. diff --git a/Src/Help/HTML/menu_tools.htm b/Src/Help/HTML/menu_tools.htm index a523e695d..698672acd 100644 --- a/Src/Help/HTML/menu_tools.htm +++ b/Src/Help/HTML/menu_tools.htm @@ -2,12 +2,9 @@ @@ -65,35 +62,6 @@

where the compilers used in test compilations are specified. - - -   - - - Proxy Server - - - Enables CodeSnip to be configured to use a proxy server when - accessing the internet. Details of the proxy are entered in the Proxy Server Configuration dialogue box that is displayed. - - - - -   - - - Check For Program Updates - - - Checks if an updated version of CodeSnip is available. - The Check For Program Updates dialogue box is displayed which checks - online for any updates and reports if one is available. - - Report Bug Online - Displays the Bug Report Dialogue - Box. This is used to access the CodeSnip bug tracker. - - - - -   - - - Register CodeSnip - - - Displays the Registration Wizard that enables the - program to be registered online. Registration is not compulsory, but - is appreciated.
- Note: This menu item only appears if - CodeSnip has not been registered. + Displays the Bug Report Dialogue Box. This is used to access the CodeSnip online bug tracker. diff --git a/Src/Help/HTML/menu_view.htm b/Src/Help/HTML/menu_view.htm index 80938a99e..ad496b15a 100644 --- a/Src/Help/HTML/menu_view.htm +++ b/Src/Help/HTML/menu_view.htm @@ -2,12 +2,9 @@ @@ -99,8 +96,9 @@

Displays snippets grouped by kind in the - Overview Pane: i.e. routine, type, - constant or freeform. + Overview Pane i.e. class/advanced + record, constant, freeform, + routine, type definition, or unit. @@ -202,7 +200,7 @@

Collapses all sections in the Overview Pane so that only the section - headings a visible. + headings are visible. diff --git a/Src/Help/HTML/navigation.htm b/Src/Help/HTML/navigation.htm index 334bc88a3..cefdd212a 100644 --- a/Src/Help/HTML/navigation.htm +++ b/Src/Help/HTML/navigation.htm @@ -2,12 +2,9 @@ @@ -54,12 +51,12 @@

any item displays it in a new tab.

- Details Pane + Detail Pane

The Details Pane is often displaying either details of a snippet or a + >Detail Pane is often displaying either details of a snippet or a group's table of contents.

@@ -71,7 +68,7 @@

the group.

- Clicking any link displays target of the link in the current + Clicking any link displays the target of the link in the current tab, overwriting the current display. Once again Ctrl clicking the link displays the target item in a new tab. @@ -80,7 +77,7 @@

You can use the Tab key to cycle through any snippet and category links displayed in the Details Pane and then press Return or + >Detail Pane and then press Return or Ctrl+Return to display the links in current or a new tab, respectively.

@@ -91,7 +88,7 @@

re-displayed using the navigation history.

- You can move backwards and forwards in the history Previous button

You can narrow down the range of displayed snippets by performing a - ssearch. + search.

Footnote diff --git a/Src/Help/HTML/new.htm b/Src/Help/HTML/new.htm index d850c4285..336bcd45c 100644 --- a/Src/Help/HTML/new.htm +++ b/Src/Help/HTML/new.htm @@ -2,12 +2,9 @@ @@ -40,26 +37,24 @@

also be included in generated units.
  • -
    +

    Snippets from both the main and user databases can now be duplicated - using the Snippets | Duplicate Snippet menu option or - tool button. -

    -
    + using the Snippets | Duplicate Snippet menu option, by + pressing Shift+Ctrl+D or by clicking + the tool + button. +

    +

    This is very useful if you have created a snippet and want to create another one that shares a lot of the source code, dependencies etc. Just duplicate the first one with a new name and edit it as required. It saves a lot of time. -

    +

    +

    There's a second use where you can duplicate a snippet from the main database under the same name and make any tweaks you like to the new version: duplicate snippets are always editable. -

    -
    - Use the Snippets | Duplicate Snippet menu option, press - Shift+Ctrl+D or click the - tool button. -
    +

  • The Database now supports Unicode text for snippet names, descriptions @@ -68,9 +63,9 @@

    don't understand Unicode source code or unit file names.

  • - There is now finer control over the control of warnings in generated - code via the $WARN directive. This is controlled on the - Code Generation tab of the + There is now finer control over the warnings in generated code via the + $WARN directive. This is controlled on the + Code Generation page of the Preferences dialogue box.
  • @@ -78,7 +73,7 @@

    written to exported snippets and units, in that snippet comments can be restricted to display only the first paragraph of multi-paragraph snippet descriptions. Select this option from the - Code Formatting tab of the + Code Formatting page of the Preferences dialogue box.

  • @@ -86,11 +81,18 @@

    namespaces can be used.

  • - [v4.3] The user defined snippets database can now be + [v4.3/v4.5.1] As of v4.3 the user defined snippets database can now be moved to a user specified directory. This useful for ensuring the database is backed up, for example by placing it in a Dropbox or GoogleDrive sub-folder. (This option is not available in the - portable edition.) + portable edition.) From v4.5.1 the database can also be relocated to a network drive. +
  • +
  • + [v4.7] Snippets can now be imported from the SWAG + (SourceWare Archive Group) collection of snippets. +
  • +
  • + [v4.20] The user defined database can now be deleted.
  • @@ -112,7 +114,7 @@

    The structure of snippet pages in the details pane is now customisable: various page elements can be omitted and the order of elements can be changed. Each snippet type has its own page customisation. Use the - Snippet Layout tab of the + Snippet Layout page of the Preferences dialogue box to do the customisation. @@ -130,21 +132,21 @@

  • The colours used for Snippet and other headings can now be customised - using the Display tab of the + using the Display page of the Preferences dialogue box.
  • -
    +

    Snippets can now have an optional "display name" that, unlike the snippet's name, does not need to be unique. This is useful for giving meaningful names to snippets such as overloaded functions. -

    -
    +

    +

    For example snippets ResizeRect_A and ResizeRect_B in the online database now have display names ResizeRect (TSize overload) and ResizeRect (Longint overload) which are much more meaningful. -

    +

  • Snippet descriptions can now be formatted and can contain multiple @@ -158,11 +160,11 @@

    for notes or snippets in languages other than Pascal.

  • -
    +

    Information about how a snippet from the online Code Snippets Database was tested is now displayed by means of a glyph at the top right of the detail pane, as follows: -

    +

    • care.
    +
  • The Welcome page has been completely redesigned to be cleaner and to provide more useful information about the databases and program.
  • +
  • + [v4.19.0/v4.20.0] From v4.19.0 the font size used in + the overview pane can be customised. The ability to change the font size + in the detail pane was added in v4.20.0. +
  • Favourites [v4.2]

    For other features of v4 please read the change log for this release and all preceding v4 releases, including diff --git a/Src/Help/HTML/notifiy_database_update.htm b/Src/Help/HTML/notifiy_database_update.htm deleted file mode 100644 index 022ee8add..000000000 --- a/Src/Help/HTML/notifiy_database_update.htm +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - Database Update Notification Window - - - - - - - -

    - Database Update Notification Window -

    -

    - This notification window appears when CodeSnip has detected that - the online Code Snippets Database has been updated and can be downloaded. -

    -

    - You can click the Update Now button to open the - Update From Web dialogue box from where the - database can be downloaded. -

    -

    - Clicking the down facing arrow at the top right of the window closes the - notification. If you don't use the button, the window disappear roughly - 20 seconds after it appears. -

    - - diff --git a/Src/Help/HTML/notifiy_program_update.htm b/Src/Help/HTML/notifiy_program_update.htm deleted file mode 100644 index e525ccf49..000000000 --- a/Src/Help/HTML/notifiy_program_update.htm +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - Program Update Notification Window - - - - - - - -

    - Program Update Notification Window -

    -

    - This notification window appears when CodeSnip has detected that - an update to the program is available. -

    -

    - You can click the Download Now button to open a web page in your - default browser from where you can download the program update. -

    -

    - Clicking the down facing arrow at the top right of the window closes the - notification. If you don't use the button, the window disappear roughly - 20 seconds after it appears. -

    - - diff --git a/Src/Help/HTML/overview_pane.htm b/Src/Help/HTML/overview_pane.htm index 9cde31aad..1726affac 100644 --- a/Src/Help/HTML/overview_pane.htm +++ b/Src/Help/HTML/overview_pane.htm @@ -2,12 +2,9 @@ @@ -25,10 +22,10 @@

    This pane appears on the left hand side of the main window. It lists all - the snippets in the database, grouped under section headers, which appear + the snippets in the database grouped under section headers, which appear in bold. Snippets can be grouped in three ways:

    -
      +
      1. by category, using the Categorised tab;
      2. @@ -49,7 +46,7 @@

        Section headers for sections that contain no snippets are hidden by default. This can be changed using the Display tab of the Display page of the Preferences dialogue box.

        @@ -99,9 +96,9 @@

        You can configure whether the overview pane tree view appears fully - expanded or fully collapsed by using the Display tab of the Display page of the Preferences dialogue box.

        @@ -158,7 +155,7 @@

        Ctrl+Up - Scrolls up the tree, without changing the selected item. + Scrolls the tree up, without changing the selected item. @@ -166,7 +163,7 @@

        Ctrl+Down - Scrolls down the tree, without changing the selected item. + Scrolls the tree down, without changing the selected item. @@ -280,16 +277,20 @@

        - - Ctrl+Num Sub + + Num Sub
        +   or
        + Ctrl+Num Sub Collapses an expanded section and selects the section header. - - Ctrl+Num Plus + + Num Plus
        +   or
        + Ctrl+Num Plus Expands a collapsed section. diff --git a/Src/Help/HTML/privacy_statement.htm b/Src/Help/HTML/privacy_statement.htm deleted file mode 100644 index 9cc01d9ad..000000000 --- a/Src/Help/HTML/privacy_statement.htm +++ /dev/null @@ -1,423 +0,0 @@ - - - - - - - Privacy Statement - - - - - - - -

        - Privacy Statement - for DelphiDabbler Codesnip -

        -

        - Offline Privacy -

        -

        - CodeSnip stores some personal information in two configuration - files. The location of the files differs depending on whether you are - using the standard or portable edition. -

        -

        - Common.config -

        -

        - Common.config is located in the in the - %ProgramData%\DelphiDabbler\CodeSnip.4 folder for the - standard edition or in the AppData sub folder of the install - directory for the portable edition. -

        -

        - The data that is stored in Common.config includes: -

        -
          -
        • - A unique 32 digit key based on attributes of your computer. -
        • -
        • - A 32 digit registration key (registered programs only). -
        • -
        • - The registered user name or nickname (registered programs only). -
        • -
        -

        - The data that is used to create the hexidecimal keys cannot be recovered - from those keys. -

        -

        - User.config -

        -

        - User.config is found in the current user's - %AppData%\DelphiDabbler\CodeSnip.4 folder for the standard - edition or in the AppData sub folder of the install directory - for the portable edition. -

        -

        - The file stores several user-specific application data - settings such as your preferences. The only personal information is as - follows: -

        -
          -
        • - Any user name or email address you provided if you ever entered the - information in a dialogue box. This is used simply to automatically - enter the data in dialogue boxes where needed, to save you re-typing it. - See below for details of activities that require either or both a name - or email address to be submitted. -
        • -
        • - Your user name and password if you configured CodeSnip to - access the internet using a proxy server. The information is used only - to access the proxy. Passwords are encrypted. -
        • -
        -

        - Spyware -

        -

        - No spyware or other software that threatens your privacy has been - deliberately or knowingly included with CodeSnip or its - installer. If the program is found to contain spyware it will have been - added by third parties without permission. Please inform the author of any - such occurrence or any suspicious behaviour via http://delphidabbler.com/contact. -

        -

        - CodeSnip never tries to "phone home" of its own - accord. It only goes online in response to user requests. Should you - notice any unauthorised activity please inform the author since it is - likely that your copy of the program has been hacked by some third party. -

        -

        - CodeSnip Online Activity -

        -

        - CodeSnip may go online for several reasons. They are: -

        -
          -
        1. - Updating the database. -
        2. -
        3. - Registering the program. -
        4. -
        5. - Submitting code for inclusion in the main database. -
        6. -
        7. - Displaying the latest news in the "CodeSnip News" dialogue - box. -
        8. -
        9. - Checking for program updates. -
        10. -
        11. - Importing snippets from the SWAG database. -
        12. -
        13. - Accessing various websites via hyperlinks and menu options. -
        14. -
        15. - Accessing the internet using a proxy server. -
        16. -
        -

        - The first six actions are performed directly by CodeSnip, - which communicates with web services located on delphidabbler.com - using HTTP on port 80. The seventh action simply displays web pages in a - browser. The eighth passes user information to the proxy server. -

        -

        - Personal data will neither be published on the DelphiDabbler website nor - knowingly passed to third parties. Furthermore your data will only be - used by the author for the purposes described below. -

        -

        - Updating the Database -

        -

        - This activity is started by displaying the "Update From Web" - dialogue box and clicking its "Update from Web" button. -

        -

        - CodeSnip may also check automatically, behind the scenes, to see - if database updates are available. This usually happens just after the - program has started. You can disable this automatic checking by using the - "Updates" tab of the "Preferences" dialogue box which - is displayed via the "Tools | Preferences" menu option. -

        -

        - When CodeSnip contacts the update web service it sends the - following information: -

        -
          -
        • - The unique 32 digit program key (see Common.config above for details). -
        • -
        • - Program version number. -
        • -
        • - Operating system description. -
        • -
        • - Internet Explorer version. -
        • -
        -

        - This information is recorded in two places: -

        -
          -
        1. - Each transaction is recorded in a log file on the web server. This log - is used for tracking any database errors. -
        2. -
        3. -
          - A database of the program keys is updated with the following data: -
          -
            -
          • - the number of times that program has updated the database; -
          • -
          • - the first and latest version of CodeSnip having that program key; -
          • -
          • - the date of the first and latest updates; -
          • -
          • - the current operating system being used; -
          • -
          • - the current Internet Explorer version being used. -
          • -
          -
          - This database is used to provide usage information and to track the - operating systems and browser control versions currently in use. This - information informs development of CodeSnip. -
          -
        4. -
        -

        - This information may also be used to update the registered program - database. -

        -

        - None of these records store any information that can be tracked back to an - individual. -

        -

        - Registering the Program -

        -

        - This activity is started when the user chooses to register - CodeSnip. A wizard appears that guides the user through the - registration process. The wizard displays the data that is to be sent to - the registration web service. The data is only sent if the user clicks - the wizard's "Submit" button. The data is: -

        -
          -
        • - The program's id, name and version information. -
        • -
        • - The unique 32 digit program key (see Common.config above for details). -
        • -
        • - The user name or nickname. -
        • -
        • - A description of the operating system and version of Internet Explorer - being used. -
        • -
        -

        - The registration data is recorded in a database on the DelphiDabbler web - server. The data is only used to keep track of the number of people using - the different versions of the program and what operating systems are being - used. The registration web service returns a registration code to the - program. CodeSnip records this code, with the user name, in - Common.config. -

        -

        - Registration information may be updated when the user updates the database - (see "Updating the Database" above). -

        -

        - Submitting code to the main database -

        -

        - This activity is started from the Snippets | Submit Snippets menu - option and data is collected using the resulting wizard. Clicking the - Submit button in the wizard sends the following information to - the DelphiDabbler code submission web service: -

        -
          -
        • - The CodeSnip program version number. -
        • -
        • - Your name, email address any comments entered in the wizard. -
        • -
        • - All the snippets you chose to send. -
        • -
        -

        - You can preview the data to be sent (in XML format) from the wizard. The - XML will be stored in a file on the DelphiDabbler server until - processed. The data is also emailed to the database maintainer (currently - the author of CodeSnip). -

        -

        - The snippets may be edited and published on the Code Snippets online - database and distributed to CodeSnip users. Your name may be used - to credit the snippets. Your email address will not be published and is - used only to contact you with any queries about the submitted code or its - licensing. -

        -

        - Displaying the latest news -

        -

        - This action is performed by selecting the Help | CodeSnip News - menu option or by clicking the "Latest News" button in the - "Update from Web" dialogue box. -

        -

        - CodeSnip simply requests an XML document containing an RSS 2.0 - news feed from the DelphiDabbler website. No personal information is - included in the request. -

        -

        - Checking for program updates -

        -

        - This action can be started manually by clicking the "Tools | Check - For Program Updates" menu option. The resulting dialogue box connects - to the DelphiDabbler program update web service. -

        -

        - CodeSnip may also check for updates automatically, behind the - scenes. The same DelphiDabbler program update web is used in this case. - The process usually happens just after the program has started. You can - disable this automatic checking by using the "Updates" tab of - the "Preferences" dialogue box which is displayed via the - "Tools | Preferences" menu option. -

        -

        - Whichever way the the web service is used the same information is sent, - which is: -

        -
          -
        • - An API key used permit access to the web service. -
        • -
        • - The program's version information. -
        • -
        • - The unique 32 digit program key (see Common.config above for details). -
        • -
        • - A description of the operating system and version of Internet Explorer - being used. -
        • -
        • - An indication of whether the service is being called automatically or - manually. -
        • -
        -

        - This information is recorded in a log file on the DelphiDabbler web - server. The log file is used to gather information about the operating - systems and program versions currently in use and to monitor any web - service errors. -

        -

        - Regardless of the method used to contact the web service, if an update is - available you are given the option to download it. To do this CodeSnip - uses your default web browser to download a zip file containing the update - from the SourceForge website. SourceForge may set cookies on your computer - that are outside CodeSnip's control. -

        -

        - Importing snippets from the SWAG database -

        -

        - Snippets are imported from the SWAG database (http://swag.delphidabbler.com) using the SWAG Import Wizard - that is accessed from the Snippets | Import Snippets From SWAG - menu item. -

        -

        - The wizard gets the required snippets from the REST API at - swag.delphidabbler.com. This API is documented at http://swag.delphidabbler.com/docs/api/. No personal data is sent. -

        -

        - Accessing Websites via Links and Menu Options -

        -

        - CodeSnip's Help menu contains several items that cause various - web pages from DelphiDabbler.com to be displayed in your default web - browser. -

        -

        - In addition, various links in the program's main display, some dialogue - boxes and the help file access remote websites which are displayed in your - default browser. You should refer to the relevant website's privacy - policies to learn how they use any data you supply when visiting their - pages. -

        -

        - DelphiDabbler has no control over the content of linked third party - websites and you follow such links at your own risk. -

        -

        - Accessing the internet using a proxy server -

        -

        - CodeSnip can be configured to use a proxy server to access the - internet. In this case the program sends the appropriate user name and - password to the proxy server if required. -

        -

        - DelphiDabbler has no control over the proxy server and you should satisfy - yourself that the proxy you use is trustworthy. You use this feature at - your own risk. -

        - - diff --git a/Src/Help/HTML/quickstart.htm b/Src/Help/HTML/quickstart.htm index 5119b5a2f..44ea064b2 100644 --- a/Src/Help/HTML/quickstart.htm +++ b/Src/Help/HTML/quickstart.htm @@ -2,12 +2,9 @@ @@ -34,21 +31,13 @@

        This is a guide to getting up and running quickly with CodeSnip.

        - Download the database + Install the DelphiDabbler Snippets database

        - If you have not already done so, you need to get an up to date copy of - the CodeSnip database. You will know if this is the case because - the program will display a message to that effect when it first starts. - Use the Update From Web dialogue box, reached from the - Database | Update From Web menu option. -

        -

        - If you need CodeSnip to access the internet via a Proxy Server - you should use the Tools | Proxy Server menu option and enter - information and the proxy in the resulting Proxy Server Configuration dialogue box. + If you have not already done so, you can + install the main DelphiDabbler Code + Snippets database. You may be prompted to download the database the + first time you run the program.

        Customise the Database @@ -56,13 +45,11 @@

        You can add your own snippets to the database. These snippets are held separately to the main database and are not affected by database updates. - If you wish you can submit user defined code to be considered for - inclusion in the main database.

        Add, edit and delete snippets using the appropriate menu items on the - Database menu. You can also save, backup and restore the user - database from this menu. + Database menu. You can also save, backup + and restore the user database from this menu.

        Snippets can be categorised using any of the available categories or you @@ -76,12 +63,14 @@

        Overview Pane, grouped in different ways depending on which tab is selected. Select the name of a snippet in the Overview Pane to display its source code, description and other - information in the Detail Pane. Any snippets required to compile - the displayed snippet are listed in the "Required snippets" - section. Clicking a snippet's name displays its details. Similar - snippets are accessible in the "See also" section. The - compilers the snippet is known to compile with are indicated at the bottom - of the display. + information in the Detail Pane. +

        +

        + For Pascal snippets any snippets required to compile the displayed snippet + are listed in the "Required snippets" section. Clicking a + snippet's name displays its details. Similar snippets are accessible in + the "See also" section. The compilers the snippet is known to + compile with are indicated at the bottom of the display.

        Clicking a section heading in the Overview @@ -100,7 +89,7 @@

        Searching the database

        - You can search the database for specific words, for snippets that + You can search the database for specific words, for Pascal snippets that compile (or don't compile) under certain compilers or for cross-referenced snippets. Use the Find Text, Find Compiler(s) or @@ -137,8 +126,8 @@

        accessed from the Snippets | Show Favourites menu option or by pressing F4. Display the snippet you want by double clicking it. The snippet will then be displayed in the main - window's detail pane as normal. You can also - use the dialogue box to remove any unwanted favourites. + window's detail pane in a new tab. You can + also use the dialogue box to remove any unwanted favourites.

        The Favourites dialogue box is non-modal @@ -151,8 +140,8 @@

        Test Compiling Snippets

        - When any snippet is selected you can test it with any supported compilers - currently installed on your computer, provided it is not a + When any Pascal snippet is selected you can test it with any supported + compilers currently installed on your computer, provided it is not a freeform snippet. To do this you must first have configured CodeSnip to work with your compilers by providing the required information in the @@ -160,11 +149,11 @@

        To perform a test compilation select the required snippet in the Overview - pane and choose Compile | Test Compile Snippet menu option. The - program will then compile the snippet and display the result in the - Test Compile Results dialogue box. Full - details of any compiler errors or warnings can be displayed from this - dialogue box. + pane and choose Compile | Test Compile Snippet menu option or + press F9. The program will then compile the + snippet and display the result in the Test + Compile Results dialogue box. Full details of any compiler errors or + warnings can be displayed from this dialogue box.

        Exporting Snippets @@ -174,24 +163,36 @@

        • - Any selected routine can be exported to a file using the + Any selected routine or routines in + a selected category can be exported to a file using the Save Annotated Source dialogue box accessed from the File | Save Annotated Source menu option.
        • Plain text and syntax highlighted rich text copies of selected snippets can be copied to the clipboard via the Edit | Copy Annotated - Source or Edit | Copy Source Code menu options. + Source (routines only) or + Edit | Copy Source Code menu options.
        • - Complete Pascal units can be generated and written to file. The unit - will contain all the currently selected snippets (i.e. those displayed - in the Overview Pane). You can - explicitly specify the selected snippets by using the - Select Snippets dialogue box - available via Search | Select Snippets. The unit is configured - and saved from the Save Unit dialogue - box, displayed from the File | Save Unit menu option. +

          + Complete Pascal units can be generated and written to file. The unit + will contain all the currently selected snippets (i.e. those displayed + in the Overview Pane). You can + explicitly specify the selected snippets by using the + Select Snippets dialogue box + available via the Search | Select Snippets menu option. +

          +

          + The unit is configured and saved from the + Save Unit dialogue box, displayed from + the File | Save Unit menu option. +

          +

          + Note that unit and + freeform snippets cannot be + included in a generated unit. +

        • User-defined snippets can be exported to a file that can be imported by diff --git a/Src/Help/HTML/reml.htm b/Src/Help/HTML/reml.htm index 9d5fb8450..0783817a5 100644 --- a/Src/Help/HTML/reml.htm +++ b/Src/Help/HTML/reml.htm @@ -2,12 +2,9 @@ @@ -18,24 +15,6 @@ About REML - About the REML markup language

          - REML is CodeSnip's own little markup language that can - be used to style the text of a snippet's description and / or extra - information. -

          -

          - Language Details -

          -

          - The REML language is a SGML language similar to a greatly - simplified XHTML. The are a small number of tags you can use. Firstly - there are two block-level tags that render text in paragraphs, while the - other tags format text inline or embed hyplerlinks. -

          -

          - Block level tags -

          -
          -
          <p>...</p>
          -
          - Renders the enclosed markup as a simple paragraph. -
          -
          <heading>...</heading>
          -
          - Renders the enclosed markup as a heading. -
          -
          -

          - The following rules apply to the use of - <p> and - <heading> -

          -
            -
          1. - The tags must not be nested. -
          2. -
          3. - The tags must be matched, e.g. - <p> must have a matching - </p>. -
          4. -
          5. - All text should be embedded within block level tags, e.g. <heading>heading</heading> <p>text</p> - or simply <p>text</p>. -
          6. -
          -

          - Here are some valid examples: -

          -
            -
          1. - <p>Hello World</p> -
          2. -
          3. - <heading>Hello</heading>
            - <p>Hello World</p>
            -
          4. -
          -

          - Srictly speaking, the following example is invalid code – the - highlighted sections are in error, because they are not contained within - block tags. -

          -

          - blah - <heading>blah</heading> - blah - <p>blah</p> - blah + REML is a little markup language that can be used to style text. It is a SGML language similar to HTML, albeit much smaller. A small number of tags and character entities are supported.

          - However, CodeSnip is quite permissive and, in many cases, - automatically adds - <p>...</p> - tags for text that is not enclosed in block level tags. The above code is - interpreted as: -

          -

          - <p>blah </p>
          - <heading>blah</heading>
          - <p>blah </p>
          - <p>blah</p>
          - <p>blah</p> + CodeSnip currently supports REML v6. See the REML v6 language definition for full details.

          -

          - Inline tags -

          - Here are the available inline tags: -

          -
          -
          <strong>...</strong>
          -
          - Renders the enclosed markup with strong emphasis.
          - Example: <p>Make stuff - <strong>stand out</strong>.</p> -
          -
          <em>...</em>
          -
          - Emphasises the enclosed markup.
          - Example: <p>Draw - <em>attention</em> to something.</p> -
          -
          <var>...</var>
          -
          - Used to indicate the enclosed markup is a variable.
          - Example: <p>Refer to a function - <var>parameter</var>.</p> -
          -
          <warning>...</warning>
          -
          - Used for warning text.
          - Example: - <p><warning>Warning:</warning> - Don't do it!</p> -
          -
          <mono>...</mono>
          -
          - Renders markup in a mono-spaced font.
          - Example: <p>Use the: - <mono>Windows</mono> unit.</p> -
          -
          <a href="url">...</a>
          -
          - Creates a hyperlink. The href attribute must - specify the required URL, which must use one of the http:, - https: or file: protocols; others are not permitted. - If you use the file: protocol it must reference a valid local - or network file. Be aware that if you submit or export a snippet - containing a hyperlink that uses the file: protocol it will - only work on the recipient's system if the specified file exists in the - same location.
          - Example: <p><a - href="http://www.delphidabbler.com">Visit - DelphiDabbler.com</a></p>.. -
          -
          -

          - Character Entities -

          -

          - The "<" and "&" characters are special within - the markup and must not be used directly, even when you are just entering - plain text. You must use the &lt; character - entity in place of "<" and - &amp; instead of "&". -

          -

          - A few other character entities are supported for convenience. Here is the - complete list: -

          -
            -
          • - &lt; for < -
          • -
          • - &gt; for > -
          • -
          • - &quot; for " -
          • -
          • - &amp; for & -
          • -
          • - &copy; for © -
          • -
          -

          - By way of an example, if you want to display - x < y, use: -

          -

          - x &lt; y + The following whimsical example demonstrates every supported REML tag along with a couple of character entities: +

          <heading>
          +  Wombat converter
          +</heading>
          +<p>
          + Transforms <strong>wombats</strong> into <em>dongles</em>.
          + <warning><em>W</em>arning:</warning> The <var>Foo</var>
          + variable stores &lt;=<mono>12</mono> accumulated <mono>dongles</mono>.
          +</p>
          +<p>
          + All 3 species of wombat are supported:
          +</p>
          +<ol>
          + <li>
          +   <p>
          +     <a href="https://en.wikipedia.org/wiki/Common_wombat">Common
          +     wombat</a>. The following sub-species are supported:
          +   </p>
          +   <ul>
          +     <li>
          +       Bass Strait wombat
          +     </li>
          +     <li>
          +       Hirsute wombat
          +     </li>
          +     <li>
          +       Tasmanian wombat
          +     </li>
          +   </ul>
          + </li>
          + <li>
          +   Northen hairy-nosed wombat
          + </li>
          + <li>
          +   Southern hairy-nosed wombat
          + </li>
          +</ol>
          +<p>
          + Copyright &copy; wombaterama, 2024.
          +</p>

          - No other symbolic character entities are supported. - However, numeric character entities can be used to insert other characters - by specifying its code. For example &#64; is - equivalent to "@". + All this silliness renders something like this:

          - Numeric entities should be used with caution. Using a code that is - specific to an ANSI character set may cause unexpected results because - CodeSnip uses Unicode internally and the specified character code - may not represent the same character in ANSI and Unicode. +

          - diff --git a/Src/Help/HTML/snippet_class.htm b/Src/Help/HTML/snippet_class.htm index c500fce52..08ef29d2f 100644 --- a/Src/Help/HTML/snippet_class.htm +++ b/Src/Help/HTML/snippet_class.htm @@ -2,12 +2,9 @@ @@ -30,8 +27,8 @@

          Class & Advanced Record Snippets

          - A class (or advanced record) snippet, defines a single Pascal - class or advanced record (i.e. record with methods). + A snippet of the class & advanced record kind defines one or more + Pascal classes or advanced records (i.e. records with methods).

          The source code must conform to the following rules: @@ -39,13 +36,14 @@

          • Each snippet must begin with the type keyword as the - first non-white-space text in the source code. Comments should not - preceed it. Ideally the type keyword should be on a - line on its own. + first non-white-space text in the source code. Comments must not + precede it. The type keyword must be followed by at + least one space or a new line.
          • - Next follow all the class declarations. Addition type - keywords may be used. + Next follows the class or advance record declaration(s). Additional + type keywords may be used between types, but are not + required.
          • Method implementations follow once all the class / record types @@ -53,17 +51,16 @@

            classes and record types.

          • - The implementation keyword must not be used to separate - the declaration and the implementation: CodeSnip works out - where the implementation starts. + Unlike in a Pascal unit, the implementation keyword + must not be used to separate the declaration and the implementation: CodeSnip works out where the implementation + starts.

          Warning: It is an error to use this - snippet kind for records or classes that have no methods – use simple type definition instead. It is also an error to define more - than one class per snippet. + >simple type definition snippet instead.

          Here are examples valid class and advanced record snippets: @@ -101,25 +98,34 @@

          constructor Create(AX, AY: Integer); end; +type // optional type keyword + TRectEx = record + Top, Left, Width, Height: Integer; + constructor Create(ATop, ALeft, AWidth, AHeight: Integer); + end; + constructor TPointEx.Create(AX, AY: Integer); begin X := AX; Y := AY; +end; + +constructor TRectEx.Create(ATop, ALeft, AWidth, AHeight: Integer); +begin + Top := ATop; + Left := ALeft; + Width := AWidth; + Height := AHeight; end;

          - Class and Advanced Record snippets may refer to other classes or advanced - records, routines, - simple type definitions or + Class & Advanced Record snippets may refer to other class & + advanced record snippets, routines, + simple type definitions and constants, providing they are defined - in Delphi units or elsewhere in the database. The snippet's units and - dependency references should indicate where to find referenced types, - constants and routines. Freeform + in Delphi units or elsewhere in the database. The snippet's units + and dependency references should indicate where to find the + referenced snippets. Freeform and unit snippets may not be referenced.

          -

          - It is important that CodeSnip can understand the code because it - must be able to separate the declaration and implementation parts when - generating a unit containing the snippet. -

          diff --git a/Src/Help/HTML/snippet_constant.htm b/Src/Help/HTML/snippet_constant.htm index 52846a2e9..480f620d7 100644 --- a/Src/Help/HTML/snippet_constant.htm +++ b/Src/Help/HTML/snippet_constant.htm @@ -2,12 +2,9 @@ @@ -35,9 +32,9 @@

          Each constant snippet begins with the const keyword as - the first non-space text in the source code. Comments may not preceed it. - The const keyword is followed by at least one space or - newline and then one or more constant definitions. + the first non-space text in the source code. Comments may not precede it. + The const keyword is followed by at least one space or a + new line and then one or more constant definitions.

          Here's an example of a valid constant snippet: @@ -47,11 +44,11 @@

          cRangeMin = 1; cIntArray: array[cRangeMin..cRangeMax] of Integer = (42, 56, 99);

          - Constant snippets may refer to other - simple type definitions, - class & advanced record definitions - or constants, providing they are defined in Delphi units or elsewhere in - the database. The snippet's units and dependencies references should + Constant snippets may refer to other constant definitions, + simple type definitions or + class & advanced record definitions, + providing they are defined in Delphi units or elsewhere in the database. + The snippet's units and dependencies references should indicate where to find the other types and constants. Constants may not reference routine, unit or diff --git a/Src/Help/HTML/snippet_freeform.htm b/Src/Help/HTML/snippet_freeform.htm index 5bb945048..f5f7f09b5 100644 --- a/Src/Help/HTML/snippet_freeform.htm +++ b/Src/Help/HTML/snippet_freeform.htm @@ -2,12 +2,9 @@ @@ -32,12 +29,12 @@

          Unlike the other snippet kinds, freeform code snippets can contain any kind of source code. The - code does not need to compilable, and nor does its layout need to conform - to any rules. + code can be in any language, does not need to be compilable and does not + have to conform to any layout rules.

          Because of the nature of freeform snippets, CodeSnip can make no - assumptions about them, and therefore cannot include the code in a + assumptions about them and therefore cannot include the code in a compilable unit. This has two major implications:

            @@ -46,18 +43,13 @@

          1. The snippets cannot be test-compiled (because CodeSnip cannot - generate a test unit. + generate a test unit).

          If you can live without these features and just want to use CodeSnip as a simple code repository, freeform snippets are by - far the easiest way to create your own snippets as there are no rules - to conform to. -

          -

          - Note: You will never find freeform code in the online - database. + far the easiest way to create your own snippets.

          When should freeform be used? @@ -75,8 +67,8 @@

          other units and routines, although you can if you wish.
        • - The code doesn't conform with one of CodeSnip's other snippet - kinds, for example if the code is a class or a complete unit. + The code doesn't conform with the requirements of one of + CodeSnip's other snippet kinds.
        • @@ -85,17 +77,19 @@

          Select the snippet in the Overview Pane and look in the Detail Pane. Look for the - "Kind" section below the source code. + "Kind" section. (This section appears just below the source code + unless the display layout has been + customised.)

          - How do I change the snippet to another kind? + How do I change a freeform snippet to another kind?

          Select the snippet in the Overview Pane then select the Snippets | Edit Snippet menu item to open the Edit Snippet dialogue box. On the - Code tab select a value in the Kind drop down list and - press OK. + Code tab select a suitable value in the Kind drop down + list and press OK.

          Warning: Don't change the kind unless you diff --git a/Src/Help/HTML/snippet_kinds.htm b/Src/Help/HTML/snippet_kinds.htm index a33ab112d..ee1ce5f4c 100644 --- a/Src/Help/HTML/snippet_kinds.htm +++ b/Src/Help/HTML/snippet_kinds.htm @@ -2,12 +2,9 @@ @@ -31,11 +28,12 @@

          CodeSnip is more than just a simple repository for your code - snippets, it can do some other things like create compilable units and - test-compile your code that mean it needs to know something about your - code. For that reason each snippet must be identified as one of six - kinds, and there are rules governing the layout of the source code for - each kind. The six kinds are: + snippets. If you're using Pascal it can do some other things like + generate compilable units and test-compile your code. To be able to do + this CodeSnip needs to know something about your code. For that + reason each snippet must be identified as one of six kinds, and there are + rules governing the layout of the source code for each kind. The six kinds + are:

          1. @@ -72,21 +70,23 @@

            Freeform code
            A catch all for any snippet that is not one of the above types. Can contain anything. Freeform code can't be included in a - generated unit and nether can it be test complied.
            + generated unit and neither can it be test complied. This kind is also + used for non-Pascal code.
            Learn more.

          If you want to be able use all of CodeSnip's functionality you - must designate each of your snippets as one of the the first five kinds. - Use the "learn more" links to find out what you need to do to - comply with the requirements. Another way to learn is to examine the code - from the main database, since it all conforms. + must designate each of your Pascal snippets as one of the the first five + kinds. Use the "learn more" links to find out what you need to + do to comply with the requirements. Another way to learn is to examine the + code from the main DelphiDabbler Code Snippets database, since it all + conforms.

          - On the other hand if you only want to use CodeSnip as a - repository for your code snippets, and don't want test compiling, just - designate everything as freeform. Or mix and match. + On the other hand if you don't use Pascal or don't need + CodeSnip's advanced support for Pascal code, just designate + everything as freeform. Or mix and match. It's up to you.

          diff --git a/Src/Help/HTML/snippet_routine.htm b/Src/Help/HTML/snippet_routine.htm index 088bc524d..c0f9ef706 100644 --- a/Src/Help/HTML/snippet_routine.htm +++ b/Src/Help/HTML/snippet_routine.htm @@ -2,12 +2,9 @@ @@ -31,37 +28,38 @@

          A routine snippet, defines exactly one Pascal procedure or function. - Methods are not permitted. + Methods are not permitted (see the class & + advanced record kind for how to deal with methods).

          Each routine snippet begins with either the function or procedure keyword followed by a valid Pascal routine - prototype (see below). No source code or comments may not preceed the + prototype (see below). No source code or comments may precede the function or procedure keywords. The - following directives may be included in the prototype. + following directives may be included in the prototype:

          • - register + register
          • - pascal + pascal
          • - cdecl + cdecl
          • - stdcall + stdcall
          • - safecall + safecall
          • - overload + overload

          - After the routine prototype there must be at least one space or newline + After the routine prototype there must be at least one space or a new line before the routine's implementation. Private functions, procedures, type definitions, variables and constants are permitted.

          @@ -83,10 +81,10 @@

          simple type definitions, class & advanced record definitions or constants, providing they are defined - in Delphi units or elsewhere in the database. The snippet's units and - dependency references should indicate where to find referenced types, - constants and routines. Routine snippets may not reference - freeform or + in Delphi units or elsewhere in the database. The snippet's units + and dependency references should indicate where to find + referenced types, constants and routines. Routine snippets may not + reference freeform or unit snippets.

          @@ -100,9 +98,15 @@

          You may use overloaded routines by creating a separate routine snippet for each overloaded version of the routine. The overload - directive must be used. While the name of the routines used in the source - code must be the same for each overloaded routine, each snippet must be - given a unique name for use by the database. + directive must be used for each snippet. +

          +

          + While the name of the routines in source code must obviously be the same, + each snippet must be given a unique name for use by the database. For + example if there are two overloaded routines named MyFunc you + could use MyFunc_A and MyFunc_B as the snippet's name in + the database. The main DelphiDabbler Snippets Database has plenty of + examples.

          diff --git a/Src/Help/HTML/snippet_type.htm b/Src/Help/HTML/snippet_type.htm index 43b68e59c..b6457381b 100644 --- a/Src/Help/HTML/snippet_type.htm +++ b/Src/Help/HTML/snippet_type.htm @@ -2,12 +2,9 @@ @@ -32,7 +29,7 @@

          A simple type definition snippet, unsurprisingly, defines one or more Pascal types. Only simple types are supported. Classes, objects and - records that contain methods are not: use + records that contain methods are not supported: use class & advanced record snippet kinds for those. If you're not sure, the only types supported are those that can be completely defined in the interface section of a unit. @@ -41,7 +38,7 @@

          Each type definition snippet must begin with the type keyword as the first non-space text in the source code. Comments may not preceed it. The type keyword is followed by at least one - space or newline and then one or more type definitions. + space or a new line and then one or more type definitions.

          Here's an example of a valid type definition: @@ -54,8 +51,9 @@

          class & advanced record type definitions or constants, providing they are defined in Delphi units or elsewhere in the database. The - snippet's units and dependencies references should indicate where to find - the other types and constants. Type definitions must not reference + snippet's units and dependencies references should + indicate where to find the other types and constants. Type definitions + must not reference routine, unit or freeform snippets. diff --git a/Src/Help/HTML/snippet_unit.htm b/Src/Help/HTML/snippet_unit.htm index c6bb1be30..1de02d01e 100644 --- a/Src/Help/HTML/snippet_unit.htm +++ b/Src/Help/HTML/snippet_unit.htm @@ -2,12 +2,9 @@ @@ -36,7 +33,7 @@

          Unit snippets are compilable just like all the other - snippet kinds except + snippet kinds, except freeform. However, that is where the similarities end. Specifically, unit snippets:

          @@ -62,8 +59,8 @@

          path.)
        • - Do not have comments containg the snippet description inserted when - saved. + Do not have comments that contain the snippet description inserted into + the source code when copied or printed.
        • @@ -102,9 +99,9 @@

          end.

          - In this unit, the Windows and Controls must be - available to the available compilers. All is well in this case because the - units are in the VCL. + In this unit, the Windows and Controls units must be + accessible to the available compilers. All is well in this case because + the units are in the VCL.

          Notes diff --git a/Src/Help/HTML/submission-rights.htm b/Src/Help/HTML/submission-rights.htm deleted file mode 100644 index baf7f7412..000000000 --- a/Src/Help/HTML/submission-rights.htm +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - Submission rights - - - - - - - -

          - Have I The Right To Submit a Snippet? -

          -

          - This topic is for guidance only and is not authoritative. To be - sure you have the right to submit a snippet you didn't write you may need - to contact the initial author or read any license that accompanied the - snippet. -

          -
            -
          • - If you wrote the snippet you have the right to do as you like with it. -
          • -
          • - If you acquired the snippet from a blog post or similar, check the - posting to see if the author has placed any restrictions on use. It is - unusual for the author to assert any rights over code snippets, but they - may do. -
          • -
          • - If a snippet is in the public domain you can submit it and it is OK to - license it under the MIT License. -
          • -
          • - If you extracted a snippet from anyone's licensed source code then you - need to check the license to see if it allows the code to be distributed - and, if necessary, re-licensed. -
          • -
          • - If the author simply requires credit for his or her work then include - their name in the comments when you submit the snippet and make it clear - they need to be credited. However, if there is any requirement to - reproduce a copyright statement this cannot be met because all - snippets in the Code Snippets database are currenty released under the - copyright of the Code Snippets Database Contributors. -
          • -
          - - - diff --git a/Src/Help/HTML/task_addsnippets.htm b/Src/Help/HTML/task_addsnippets.htm index 514962a1c..deceed9c6 100644 --- a/Src/Help/HTML/task_addsnippets.htm +++ b/Src/Help/HTML/task_addsnippets.htm @@ -2,12 +2,9 @@ @@ -91,9 +88,9 @@

          menu.

          - Snippets from the main database cannot be edited or deleted. User defined - snippets are displayed in a different colour to the snippets from the main - database. + Snippets from the main database cannot be edited or deleted. The names of + user defined snippets are displayed in a different colour to the snippets + from the main database.

          diff --git a/Src/Help/HTML/task_backup.htm b/Src/Help/HTML/task_backup.htm new file mode 100644 index 000000000..7772193ac --- /dev/null +++ b/Src/Help/HTML/task_backup.htm @@ -0,0 +1,84 @@ + + + + + + + Backup and Restore User Database + + + + + + + +

          + Backup and Restore the User Database +

          +

          + To protect against unexpected errors like hard disk failure it is wise to + periodically backup your user database. CodeSnip has a tool to + help with this: it can write your complete user database to a single file + that you can archive safely. CodeSnip can recreate the user + database from such backup files if necessary. +

          +

          + Note that backups do not include the main DelphiDabbler + Snippets Database. This is because you can just download a fresh copy of + it from the internet if you need to replace it. Use the Database | + Install or Update DelphiDabbler Snippets Database menu option to do + that. +

          +

          + How to create a backup +

          +

          + To perform the backup just select the Database | Backup User + Database menu option. This displays a + file dialogue box where you specify a file to + use for the backup. It is up to you how to name the file. Including the + date of the backup may be a good idea. +

          +

          + You can save the file to a USB drive or portable hard drive or put it + something like a Dropbox or Google Drive folder. +

          +

          + How to restore a backup +

          +

          + Should the worst happen and you loose your user defined database you can + recreate it from your latest backup. +

          +

          + To do this use the Database | Restore Backup menu option. This + displays a file dialogue box where you can + navigate to your chosen backup file. Select the file and click OK + to restore the backup. +

          +

          + If the backup file is valid it will replace any existing user database and + then the whole database will be reloaded. All existing tabs in the user + interface will be closed. When the database has finished reloading a + new tab will open with a message informing that the database has been + updated. +

          + + diff --git a/Src/Help/HTML/task_copysnippet.htm b/Src/Help/HTML/task_copysnippet.htm index 803e32274..3814c25f4 100644 --- a/Src/Help/HTML/task_copysnippet.htm +++ b/Src/Help/HTML/task_copysnippet.htm @@ -2,12 +2,9 @@ @@ -24,16 +21,15 @@

          Copy Snippet to Clipboard

          - There are three options ways for copying a snippet of code to the - clipboard. + There are three ways to copy a code snippet to the clipboard.

          Copy Information

          Copies all the information about the selected snippet to the clipboard in - both plain text and rich text format. The rich text format version syntax - highlights the source code. + both plain text and rich text format. The rich text format version is + syntax highlighted.

          This option applies to any kind of snippet. Simply select a snippet in the @@ -54,7 +50,7 @@

          A snippets description may be included as a comment, depending on the commenting style specified on the - Code Formatting tab of the + Code Formatting page of the Preferences dialogue box.

          @@ -68,21 +64,15 @@

          Copy Source Code

          - Copies only the source code of the selected snippet in plain text and in - rich text format. Again the rich text copy is syntax highlighted. The + Copies the source code of the selected snippet in plain text and in + rich text format, with the rich text copy being syntax highlighted. The snippet's description is not included as comments.

          This option applies to any kind of snippet. Select a snippet in the Overview Pane and then use the Edit | Copy Source Code menu option or press - Ctrl+U. -

          -

          - You can copy either a single routine or a whole category of routines to - the clipboard. The code is copied as a valid Pascal fragment. Notes of - any required units or other routines are included as comments. To copy a - routine proceed as follows: + Shift+Ctrl+C.

          diff --git a/Src/Help/HTML/task_customise.htm b/Src/Help/HTML/task_customise.htm index 12c3495e5..bef129e7d 100644 --- a/Src/Help/HTML/task_customise.htm +++ b/Src/Help/HTML/task_customise.htm @@ -2,12 +2,9 @@ @@ -28,12 +25,5 @@

          Preferences dialogue box accessed via the Tools | Preferences menu option.

          -

          - By default CodeSnip accesses the internet directly but it can be - made to use a Proxy Server. Use the Tools | Proxy Server menu - option to display the Proxy Server - Configuration dialogue box to provide details of the required proxy - server. -

          diff --git a/Src/Help/HTML/task_deleteuserdb.htm b/Src/Help/HTML/task_deleteuserdb.htm new file mode 100644 index 000000000..a6264cef9 --- /dev/null +++ b/Src/Help/HTML/task_deleteuserdb.htm @@ -0,0 +1,34 @@ + + + + + + + Delete User Defined Database + + + + +

          + Delete the User-Defined Snippets Database +

          +

          + In the unlikely event you need to delete all of the user defined snippets from CodeSnip you can use the Database | Delete User Database menu option, which will display the Delete User Database dialogue box. +

          +

          + There are very few use cases where you will want to delete the whole database, but one such case is where you want to move your snippets from a portable version of CodeSnip to a standard version, or vice-versa. For more information about this, see this FAQ. +

          + + diff --git a/Src/Help/HTML/task_export.htm b/Src/Help/HTML/task_export.htm index c99b174ce..a8d303fcb 100644 --- a/Src/Help/HTML/task_export.htm +++ b/Src/Help/HTML/task_export.htm @@ -2,12 +2,9 @@ diff --git a/Src/Help/HTML/task_generateunit.htm b/Src/Help/HTML/task_generateunit.htm index 7e24b9cb2..1e043b3b6 100644 --- a/Src/Help/HTML/task_generateunit.htm +++ b/Src/Help/HTML/task_generateunit.htm @@ -2,12 +2,9 @@ @@ -21,7 +18,7 @@

          - Generate a Pascal Unit + Generate a Pascal Unit

          You can generate source code for a Pascal unit and save it to disk as @@ -57,8 +54,9 @@

        • Choose the file type, the commenting style and whether you want the - file syntax highlighted using the controls in the dialogue box. You can - preview the file by using the dialogue box's Preview button. + file to be syntax highlighted by using the controls in the dialogue box. + You can use the Preview button to preview the unit. Note that + syntax highlighting is only available for HTML and RTF file types.
        • Click OK to generate the unit. @@ -66,12 +64,12 @@

          See the Save Unit dialogue box help - topic for detailed information about the available options. + topic for detailed information about all available options.

          Whether certain warnings are inihibited in the generated code can be specified using the Code Generation - tab of the Preferences dialogue + page of the Preferences dialogue box. The default commenting style, syntax highlighting and file type can be also be configured using the Code Formatting tab. diff --git a/Src/Help/HTML/task_printroutine.htm b/Src/Help/HTML/task_printroutine.htm index dee8a7ca2..7ccbe4f91 100644 --- a/Src/Help/HTML/task_printroutine.htm +++ b/Src/Help/HTML/task_printroutine.htm @@ -2,12 +2,9 @@ @@ -48,11 +45,11 @@

          The printed document contains the same information that is presented in - the information pane. + the detail pane.

          Note that the default output options and margin size can be configured on - the Printing tab of the + the Printing page of the Preferences dialogue box.

          diff --git a/Src/Help/HTML/task_registercompilers.htm b/Src/Help/HTML/task_registercompilers.htm new file mode 100644 index 000000000..21d556623 --- /dev/null +++ b/Src/Help/HTML/task_registercompilers.htm @@ -0,0 +1,120 @@ + + + + + + + Register Compilers + + + + + + + +

          + Register Compilers with + CodeSnip +

          +

          + Before you can test compile a snippet you must first register one or more installed + compilers with CodeSnip. +

          +

          + To do this you must tell CodeSnip how to find the compiler and + configure the required options for it. There are three ways to do this: +

          +
            +
          1. +

            + Manually configure the compiler using the Configure Compilers dialogue box, accessed from the Tools | + Configure Compilers dialogue box. +

            +

            + Note: This is the only option available for Free + Pascal. +

            +
          2. +
          3. +

            + Use the Detect Delphi Compilers button in the Configure Compilers dialogue box. This searches for any installed + Delphi compilers that are not registered and registers them. Any + compilers for which automatic detection has been disabled is ignored + (see below). +

            +
          4. +
          5. +

            + By default CodeSnip checks for any un-registered installed + version of Delphi at start-up, and offers to register them by popping + up the Unregistered Delphi Installation(s) Detected Dialogue Box. +

            +
          6. +
          +

          + Inhibiting Auto-Detection +

          +

          + If you don't want CodeSnip to detect any new, unregistered, + Delphi compilers at startup you can switch the feature off entirely from + the Unregistered Delphi Installation(s) Detected Dialogue Box, by ticking + the Don't show this again check box. You can do the same thing + from the Configure Compilers dialogue box by clearing the Automatically + register newly installed Delphi compilers at startup check box. +

          +

          + Alternatively, if you don't want CodeSnip to automatically detect + one or more specific Delphi compilers you can do that in the Configure Compilers dialogue box. You just need to clear the + Permit auto-detection & registration of this compiler check + box on a selected compiler's Compiler tab. If you do this then + CodeSnip will not suggest registering that compiler at startup. + The Configure Compilers dialogue box's Detect Delphi Compilers + feature will ignore them too. +

          +

          + (Re-)enabling Auto-Detection +

          +

          + Global automatic detection can be enabled from the Configure Compilers dialogue box by ticking the Automatically + register newly installed Delphi compilers at startup check box. +

          +

          + Individually excluded compilers can be included in automatic detection + again by ticking the Permit auto-detection & registration of this + compiler check box on a selected compiler's Compiler tab in + the Configure Compilers dialogue box. +

          + + + diff --git a/Src/Help/HTML/task_savesnippet.htm b/Src/Help/HTML/task_savesnippet.htm index e4e2189fa..dee53867d 100644 --- a/Src/Help/HTML/task_savesnippet.htm +++ b/Src/Help/HTML/task_savesnippet.htm @@ -2,12 +2,9 @@ diff --git a/Src/Help/HTML/task_search.htm b/Src/Help/HTML/task_search.htm index 8a6a85d6e..bae9ac523 100644 --- a/Src/Help/HTML/task_search.htm +++ b/Src/Help/HTML/task_search.htm @@ -2,12 +2,9 @@ @@ -33,7 +30,7 @@

          criteria are displayed.

          - Text and compiler searches can be configured to either operate on any + Text and compiler searches can be configured either to operate on any previous search results, effectively refining that search or to operate on the whole database, discarding any previous search results.

          @@ -42,7 +39,7 @@

          database with any existing search results discarded.

          - Serach results can be discarded by clicking the + Search results can be discarded by clicking the Show All button Show All toolbar button or by selecting Search | Show All. Following this all snippets are displayed. @@ -68,7 +65,7 @@

          src="../images/FindCompiler.gif" class="glyph" > Find Compiler(s) toolbar button (or select Search | Find - Compiler) and enter the search criteria in the + Compiler(s)) and enter the search criteria in the Find Compiler(s) Dialogue Box.

          @@ -76,10 +73,10 @@

          This search is provided to enable you to find snippets that are related to - one another. The search finds snippets referenced by the currently - selected snippet. Cross references via the "see also" or - "required snippets" fields can be searched, and searches can be - recursive. + one another. The search finds snippets that are referenced by the + currently selected snippet. Cross references via the "see also" + or "required snippets" fields can be searched, and searches can + be recursive.

          To run the search select a snippet in the @@ -100,7 +97,7 @@

          Select Snippets) you can explicitly choose which snippets are to be displayed in the Overview Pane. This is done in the Select Snippets dialogue - box by simply placing a check mark next to the required snippets. + box by simply placing check marks next to the required snippets.

          The main purpose of this option is to select snippets for inclusion in a @@ -112,15 +109,15 @@

          The results of a search can be saved to disk using the File | Save - Selection menu option. These search results can be loaded from disk - and any matching snippets selected. Doing this replaces any existing - search results. + Selection menu option. To restore the saved selection later use the + File | Load Selection menu option and select the required search + file. Loading a search always replaces any existing search results.

          Note that the search criteria are not saved, just the details of the snippets that were included in the search results. If any snippets from a - saved search result have been deleted when a result set is loaded, any - references to deleted snippets are ignored. + saved search result have been deleted by the time a result set is loaded + then those snippets are quietly ignored.

          diff --git a/Src/Help/HTML/task_submit.htm b/Src/Help/HTML/task_submit.htm deleted file mode 100644 index 99268c029..000000000 --- a/Src/Help/HTML/task_submit.htm +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - Submit Code - - - - -

          - Submit Code to the Database -

          -

          - You can (and are encouraged to) submit your user defined snippets for - inclusion in the Code Snippets database. -

          -

          - The Code Submission Wizard is used to send - code to DelphiDabbler. This is accessed from the Snippets | - Submit Snippets menu item. -

          -

          - Your code may be edited to conform with the format of the database and - must be compilable in at least one Delphi or Free Pascal compiler. It is - helpful if you can ensure your code compiles before submitting it. -

          -

          - Once included in the Code Snippets Database the code will be licensed - under the terms of the MIT License and copyright will be held jointly by the database contributors. -

          - - diff --git a/Src/Help/HTML/task_testcompile.htm b/Src/Help/HTML/task_testcompile.htm index b5a064b4e..ecac2cf6e 100644 --- a/Src/Help/HTML/task_testcompile.htm +++ b/Src/Help/HTML/task_testcompile.htm @@ -2,12 +2,9 @@ @@ -24,8 +21,9 @@

          Test Compile a Snippet

          - You can check any snippet in the database with all the supported compilers - installed on the local computer, providing the snippet is not + You can check whether any snippet in the database compiles with all + supported compilers that are installed on the local computer, providing + the snippet is not freeform. Proceed as follows:

            diff --git a/Src/Help/HTML/task_update.htm b/Src/Help/HTML/task_update.htm index 2db98f0c8..1ac96af4f 100644 --- a/Src/Help/HTML/task_update.htm +++ b/Src/Help/HTML/task_update.htm @@ -2,12 +2,9 @@ @@ -15,52 +12,91 @@ - Update Database + Install or Update the DelphiDabbler Code Snippets Database + + +

            - Update Database + Install or Update the DelphiDabbler Code Snippets Database

            - Updates to the code snippets database are made available from time to time - on the web. You can download the latest version of the database easily by - connecting to the internet then clicking the Update Database button Update Database from Web toolbar button or by choosing the - Database | Update From Web menu option. -

            -

            - The Update From Web dialogue box will be displayed. Make sure you are - connected to the internet then click the Update from Web button - to begin the update. + You can install the DelphiDabbler Code Snippets Database into + CodeSnip as follows.

            +
              +
            1. + Download the latest release of the database from the + delphidabbler/code-snippets project on GitHub. You can find + the latest release at + https://github.com/delphidabbler/code-snippets/releases. +
            2. +
            3. + Unpack the downloaded zip file into a new or empty + folder on your computer. The folder must not be a sub + folder of CodeSnip's application or user database directories. +
            4. +
            5. + Display the Install or Update DelphiDabbler Snippets Database wizard from the + Database | Install or Update DelphiDabbler Snippets + Database menu option. +
            6. +
            7. + Skip the first page of the wizard by clicking Next. +
            8. +
            9. + On the Select database download folder page of the wizard enter + the full path of the folder where you unpacked the downloaded zip file. + Click Next. +
            10. +
            11. + The Install the database page will now be displayed. Click + Load to load the database. +
            12. +
            13. + Once the database has been copied the Database installed page + will be displayed and you can close the wizard using the Finish + button. +
            14. +
            15. + The database is now installed. You can now delete both the downloaded + zip file and the folder where you unpacked it. +
            16. +

            - By default CodeSnip checks periodically to find if any database - updates are available. It displays a pop-up notification in the main - window if an update is available. The notification includes a button that - displays the Update From Web dialogue box mentioned above. You can switch this - feature off, or change the frequency with which checks are made, by using - the Updates tab of the Preferences dialogue box. + You can periodically check the delphidabbler/code-snippets + GitHub account to see if any new releases have been made. To update to the + latest release simply repeat the process above.

            +

            + Notes +

            - Database updates apply to all users. This is because only one copy of the - database exists on the computer. + A single copy of DelphiDabbler Code Snippets Database is shared by all + users of the same computer.

            Updating the database has no effect on any user-defined snippets that have been created – they are not overwritten by the update process. + However, it does no harm to take a backup of your snippets before + performing the update! Use the Database | Backup User Database + menu option to do this. +

            +

            + You cannot edit snippets from the Code Snippets Database. You can identify + them easily because, by default, the snippet name is displayed in a + different colour to user-defined snippets.

            diff --git a/Src/Help/HTML/tasks.htm b/Src/Help/HTML/tasks.htm index 1a82a6ccb..196b56c9c 100644 --- a/Src/Help/HTML/tasks.htm +++ b/Src/Help/HTML/tasks.htm @@ -2,12 +2,9 @@ @@ -45,6 +42,7 @@

          1. Print Information about a Snippet +
          2. Generate a Pascal Unit
          3. @@ -55,17 +53,22 @@

            Test Compile a Snippet
          4. - Update the Database + Register a Compiler for Test + Compilation
          5. - Customise CodeSnip + Install or Update the DelphiDabbler Code + Snippets Database
          6. - Submit Code to the Database + Customise CodeSnip
          7. Export and Import Snippets
          8. +
          9. + Backup and Restore the User Database +
          10. diff --git a/Src/Help/HTML/welcome.htm b/Src/Help/HTML/welcome.htm index 516c9c6f4..524d46074 100644 --- a/Src/Help/HTML/welcome.htm +++ b/Src/Help/HTML/welcome.htm @@ -2,12 +2,9 @@ @@ -30,10 +27,10 @@

            In addition to managing your own code snippets, CodeSnip can also - download and display source code from the DelphiDabbler Code Snippets Database of reusable Pascal source code.

            @@ -48,27 +45,23 @@

            sharing the same copy of the Code Snippets Database. The Standard Edition's program file name is CodeSnip.exe.
          11. The Portable edition which can be run from removable storage, such a memory sticks. It keeps its settings and data files within the - directory in which it is installed. This make the program suitable for + directory in which it is installed. This makes the program suitable for running on more than one computer, taking its data with it. This edition has program file name CodeSnip-p.exe.
          12. -

            - You can tell which edition you are using from the program's About - Box. -

            What makes CodeSnip different from other code snippets managers - is that it can test-compile snippets that are in the correct format. It - can also generate a compilable unit containing suitable selected snippets, - taking care of all dependencies. + is that it can test-compile Pascal code snippets. It can also generate a + compilable unit containing suitable selected snippets, taking care of all + dependencies.

            CodeSnip understands several different diff --git a/Src/Help/Images/LICENSE b/Src/Help/Images/LICENSE deleted file mode 100644 index 9e15be494..000000000 --- a/Src/Help/Images/LICENSE +++ /dev/null @@ -1,5 +0,0 @@ -All image files in the Src/Help/Images directory are licensed under the Creative -Commons Attribution Share Alike 3.0 License -(http://creativecommons.org/licenses/by-sa/3.0/). - -A full copy of this license is available in Docs/License.html#CC-BY-SA-3.0. diff --git a/Src/Help/Images/News.png b/Src/Help/Images/News.png deleted file mode 100644 index 843737a16..000000000 Binary files a/Src/Help/Images/News.png and /dev/null differ diff --git a/Src/Help/Images/REMLExample.png b/Src/Help/Images/REMLExample.png new file mode 100644 index 000000000..6e7a49f5b Binary files /dev/null and b/Src/Help/Images/REMLExample.png differ diff --git a/Src/Help/Index.hhk b/Src/Help/Index.hhk index a78b140c2..dbc1a4d8c 100644 --- a/Src/Help/Index.hhk +++ b/Src/Help/Index.hhk @@ -2,12 +2,9 @@ @@ -41,10 +38,6 @@

        • -
        • - - -
        • @@ -61,10 +54,6 @@ -
        • - - -
        • @@ -74,6 +63,10 @@ +
        • + + +
        • @@ -82,6 +75,10 @@ +
        • + + +
        • @@ -103,12 +100,12 @@
        • - - + +
        • - - + +
        • @@ -123,10 +120,6 @@ -
        • - - -
        • @@ -157,6 +150,10 @@ +
        • + + +
        • @@ -283,14 +280,6 @@ -
        • - - - -
        • - - -
        • @@ -319,26 +308,18 @@ -
        • - - - -
        • - - -
        • -
        • - - -
        • +
        • + + +
        • @@ -379,10 +360,6 @@ -
        • - - -
        • @@ -415,10 +392,6 @@ -
        • - - -
        • @@ -426,6 +399,9 @@
        • +
        • + + diff --git a/Src/Help/TOC.hhc b/Src/Help/TOC.hhc index e59624218..ac2c63139 100644 --- a/Src/Help/TOC.hhc +++ b/Src/Help/TOC.hhc @@ -2,12 +2,9 @@ @@ -136,17 +133,25 @@
        • - - + +
        • - - + +
        • +
        • + + + +
        • + + +
        • @@ -182,10 +187,6 @@ -
        • - - -
        • diff --git a/Src/Hiliter.UAttrs.pas b/Src/Hiliter.UAttrs.pas index b4f4f840c..67a970123 100644 --- a/Src/Hiliter.UAttrs.pas +++ b/Src/Hiliter.UAttrs.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements classes that define syntax highlighter attributes along with an * object that provides a list of named highlighter attributes. @@ -110,7 +107,11 @@ THiliteAttrs = class(TInterfacedObject, /// Method of IHiliteAttrs. function GetFontSize: Integer; /// Sets size of highlighter font. - /// Method of IHiliteAttrs. + /// + /// If font size is out of range of supported sizes then the + /// highlighter font is reset to its default value. + /// Method of IHiliteAttrs. + /// procedure SetFontSize(const AFontSize: Integer); /// Resets highlighter font name and size to default values. /// @@ -231,6 +232,7 @@ TNamedHiliterAttrs = class(TInterfacedObject, procedure THiliteAttrs.Assign(const Src: IInterface); var Elem: THiliteElement; // loops thru all highlight elements + Attrs: IHiliteAttrs; begin if Assigned(Src) then begin @@ -239,13 +241,11 @@ procedure THiliteAttrs.Assign(const Src: IInterface); ClassName + '.Assign: Src does not support IHiliteAttrs' ); // Src is assigned: copy its properties - with Src as IHiliteAttrs do - begin - Self.SetFontName(FontName); - Self.SetFontSize(FontSize); - for Elem := Low(THiliteElement) to High(THiliteElement) do - (Self.GetElement(Elem) as IAssignable).Assign(Elements[Elem]); - end; + Attrs := Src as IHiliteAttrs; + Self.SetFontName(Attrs.FontName); + Self.SetFontSize(Attrs.FontSize); + for Elem := Low(THiliteElement) to High(THiliteElement) do + (Self.GetElement(Elem) as IAssignable).Assign(Attrs.Elements[Elem]); end else begin @@ -310,12 +310,17 @@ procedure THiliteAttrs.SetFontName(const AFontName: string); procedure THiliteAttrs.SetFontSize(const AFontSize: Integer); begin - fFontSize := AFontSize; + if TFontHelper.IsInCommonFontSizeRange(AFontSize) then + fFontSize := AFontSize + else + fFontSize := cDefFontSize; end; { THiliteElemAttrs } procedure THiliteElemAttrs.Assign(const Src: IInterface); +var + ElemAttrs: IHiliteElemAttrs; begin if Assigned(Src) then begin @@ -324,11 +329,9 @@ procedure THiliteElemAttrs.Assign(const Src: IInterface); ClassName + '.Assign: Src does not support IHiliteElemAttrs' ); // Src is assigned: copy its properties - with Src as IHiliteElemAttrs do - begin - Self.SetForeColor(ForeColor); - Self.SetFontStyle(FontStyle); - end; + ElemAttrs := Src as IHiliteElemAttrs; + Self.SetForeColor(ElemAttrs.ForeColor); + Self.SetFontStyle(ElemAttrs.FontStyle); end else begin diff --git a/Src/Hiliter.UCSS.pas b/Src/Hiliter.UCSS.pas index adca61ca7..2a605527e 100644 --- a/Src/Hiliter.UCSS.pas +++ b/Src/Hiliter.UCSS.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2006-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2006-2022, Peter Johnson (gravatar.com/delphidabbler). * * Defines a class that generates CSS code to enable syntax highlighted source * to be displayed in HTML. CSS code uses a highlighter's attributes. Access to @@ -85,11 +82,9 @@ procedure THiliterCSS.BuildCSS(const CSSBuilder: TCSSBuilder); Elem: THiliteElement; // loops thru highlighter elements begin // Add font definition in main class - with CSSBuilder.AddSelector('.' + GetMainCSSClassName) do - begin - AddProperty(TCSS.FontFamilyProp(fHiliteAttrs.FontName, cfgMonoSpace)); - AddProperty(TCSS.FontSizeProp(fHiliteAttrs.FontSize)); - end; + CSSBuilder.AddSelector('.' + GetMainCSSClassName) + .AddProperty(TCSS.FontFamilyProp(fHiliteAttrs.FontName, cfgMonoSpace)) + .AddProperty(TCSS.FontSizeProp(fHiliteAttrs.FontSize)); // Add font style and colour definitions for each element for Elem := Low(THiliteElement) to High(THiliteElement) do BuildElemCSS(Elem, CSSBuilder); @@ -108,14 +103,13 @@ procedure THiliterCSS.BuildElemCSS(const Elem: THiliteElement; // We only create CSS class if element attributes are non-nul if not ElemAttr.IsNul then begin - with CSSBuilder.AddSelector('.' + GetElemCSSClassName(Elem)) do - begin - if ElemAttr.ForeColor <> clNone then - AddProperty(TCSS.ColorProp(ElemAttr.ForeColor)); - AddProperty(TCSS.FontWeightProp(ElemAttr.FontStyle)); - AddProperty(TCSS.FontStyleProp(ElemAttr.FontStyle)); - AddProperty(TCSS.TextDecorationProp(ElemAttr.FontStyle)); - end; + CSSBuilder.AddSelector('.' + GetElemCSSClassName(Elem)) + .AddProperty(TCSS.FontWeightProp(ElemAttr.FontStyle)) + .AddProperty(TCSS.FontStyleProp(ElemAttr.FontStyle)) + .AddProperty(TCSS.TextDecorationProp(ElemAttr.FontStyle)) + .AddPropertyIf( + ElemAttr.ForeColor <> clNone, TCSS.ColorProp(ElemAttr.ForeColor) + ); end; end; diff --git a/Src/Hiliter.UFileHiliter.pas b/Src/Hiliter.UFileHiliter.pas index 89ef276c1..609a4cd74 100644 --- a/Src/Hiliter.UFileHiliter.pas +++ b/Src/Hiliter.UFileHiliter.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2006-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2006-2025, Peter Johnson (gravatar.com/delphidabbler). * * Implements a class that generates hilighted and formatted source code for a * specified file type. @@ -102,7 +99,8 @@ function TFileHiliter.Hilite(const SourceCode, DocTitle: string): TEncodedData; begin case fFileType of sfRTF: HilitedDocCls := TRTFDocumentHiliter; - sfHTML: HilitedDocCls := TXHTMLDocumentHiliter; + sfXHTML: HilitedDocCls := TXHTMLDocumentHiliter; + sfHTML5: HilitedDocCls := THTML5DocumentHiliter; else HilitedDocCls := TNulDocumentHiliter; end; if fWantHiliting and IsHilitingSupported(fFileType) then @@ -119,7 +117,7 @@ class function TFileHiliter.IsHilitingSupported( @return True if file type supports highlighting, false if not. } begin - Result := FileType in [sfHTML, sfRTF]; + Result := FileType in [sfHTML5, sfXHTML, sfRTF]; end; end. diff --git a/Src/Hiliter.UGlobals.pas b/Src/Hiliter.UGlobals.pas index 1177a4966..27623f7ee 100644 --- a/Src/Hiliter.UGlobals.pas +++ b/Src/Hiliter.UGlobals.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). * * Declares various types that describe syntax hilighters and and defines * interfaces to various syntax highlighters and highlighter attributes. diff --git a/Src/Hiliter.UHiliters.pas b/Src/Hiliter.UHiliters.pas index e789ac74e..45267ca81 100644 --- a/Src/Hiliter.UHiliters.pas +++ b/Src/Hiliter.UHiliters.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2025, Peter Johnson (gravatar.com/delphidabbler). * * Provides highlighter classes used to format and highlight source code in * various file formats. Contains a factory object and implementation of various @@ -135,7 +132,7 @@ TNulDocumentHiliter = class sealed(TDocumentHiliter) /// /// Creates a highlighted source code document in XHTML format. /// - TXHTMLDocumentHiliter = class sealed(TDocumentHiliter) + THTMLDocumentHiliter = class abstract(TDocumentHiliter) strict private /// Generates the CSS rules to be used in the document. /// IHiliteAttrs [in] Highlighting styles used in @@ -143,6 +140,8 @@ TXHTMLDocumentHiliter = class sealed(TDocumentHiliter) /// string. CSS rules that apply styles specified in Attrs. /// class function GenerateCSSRules(Attrs: IHiliteAttrs): string; + strict protected + class function BuilderClass: THTMLBuilderClass; virtual; abstract; public /// Creates XHTML document containing highlighted source code. /// @@ -157,6 +156,20 @@ TXHTMLDocumentHiliter = class sealed(TDocumentHiliter) override; end; + /// Creates a highlighted source code document in XHTML format. + /// + TXHTMLDocumentHiliter = class sealed(THTMLDocumentHiliter) + strict protected + class function BuilderClass: THTMLBuilderClass; override; + end; + + /// Creates a highlighted source code document in HTML5 format. + /// + THTML5DocumentHiliter = class sealed(THTMLDocumentHiliter) + strict protected + class function BuilderClass: THTMLBuilderClass; override; + end; + type /// /// Creates a highlighted source code document in rich text format. @@ -245,55 +258,56 @@ TRTFHiliteRenderer = class(THiliteRenderer, IHiliteRenderer) end; type - /// - /// Renders highlighted source code in XHTML format. Generated code is - /// recorded in a given HTML code builder object. + /// Renders highlighted source code in any supported HTML format. /// - /// - /// Designed for use with TSyntaxHiliter objects. - /// + /// Designed for use with TSyntaxHiliter objects. THTMLHiliteRenderer = class(THiliteRenderer, IHiliteRenderer) strict private var - /// Object used to record generated XHTML code. + /// Object used to build up the generated HTML. fBuilder: THTMLBuilder; - /// Flag indicating if writing first line of output. + /// Flag indicating if writing the first line of output. + /// fIsFirstLine: Boolean; public - /// Object constructor. Sets up object to render documents. - /// - /// THTMLBuilder [in] Object that receives generated - /// XHTML code. - /// IHiliteAttrs [in] Specifies required highlighting - /// style. If nil document is not highlighted. + /// Object constructor. Sets up the object to render HTML + /// documents. + /// THTMLBuilder [in] Object used to build the + /// required HTML. Builder must be an instance of a concreate + /// descendant class of THTMLBuilder, which is abstract. The type of + /// Builder determines the type of HTML that is generated. + /// IHiliteAttrs [in] Specifies required + /// highlighting style. If nil the document is not highlighted. + /// constructor Create(const Builder: THTMLBuilder; const Attrs: IHiliteAttrs = nil); - /// Initialises XHTML ready to receive highlighted code. - /// Method of IHiliteRenderer. + /// Initialises the HTML ready to receive highlighted code. + /// + /// Method of IHiliteRenderer. procedure Initialise; - /// Tidies up XHTML after all highlighted code processed. + /// Tidies up the HTML after all highlighted code is processed. /// - /// Method of IHiliteRenderer. + /// Method of IHiliteRenderer. procedure Finalise; - /// Emits new line if necessary. - /// Method of IHiliteRenderer. + /// Emits a new line if necessary. + /// Method of IHiliteRenderer. procedure BeginLine; /// Does nothing. /// - /// Handling of new lines is all done by BeginLine. - /// Method of IHiliteRenderer. + /// Handling of new lines is all done by BeginLine. + /// Method of IHiliteRenderer. /// procedure EndLine; - /// Emits any span tag required to style following source code - /// element as specified by Elem. - /// Method of IHiliteRenderer. + /// Emits any <span> tag required to style the following + /// source code element, specified by Elem. + /// Method of IHiliteRenderer. procedure BeforeElem(Elem: THiliteElement); - /// Writes given source code element text. - /// Method of IHiliteRenderer. + /// Writes the given source code element text. + /// Method of IHiliteRenderer. procedure WriteElemText(const Text: string); - /// Closes any span tag used to style source code element - /// specified by Elem. - /// Method of IHiliteRenderer. + /// Closes any <span> tag used to style the source code + /// element specified by Elem. + /// Method of IHiliteRenderer. procedure AfterElem(Elem: THiliteElement); end; @@ -339,14 +353,16 @@ procedure TSyntaxHiliter.ElementHandler(Parser: THilitePasParser; class procedure TSyntaxHiliter.Hilite(const RawCode: string; Renderer: IHiliteRenderer); +var + Instance: TSyntaxHiliter; begin Assert(Assigned(Renderer), ClassName + '.Create: Renderer is nil'); - with InternalCreate(Renderer) do - try - DoHilite(RawCode); - finally - Free; - end; + Instance := InternalCreate(Renderer); + try + Instance.DoHilite(RawCode); + finally + Instance.Free; + end; end; procedure TSyntaxHiliter.LineBeginHandler(Parser: THilitePasParser); @@ -373,9 +389,9 @@ class function TNulDocumentHiliter.Hilite(const RawCode: string; Result := TEncodedData.Create(RawCode, etUnicode); end; -{ TXHTMLDocumentHiliter } +{ THTMLDocumentHiliter } -class function TXHTMLDocumentHiliter.GenerateCSSRules(Attrs: IHiliteAttrs): +class function THTMLDocumentHiliter.GenerateCSSRules(Attrs: IHiliteAttrs): string; var CSSBuilder: TCSSBuilder; // builds CSS code @@ -397,7 +413,7 @@ class function TXHTMLDocumentHiliter.GenerateCSSRules(Attrs: IHiliteAttrs): end; end; -class function TXHTMLDocumentHiliter.Hilite(const RawCode: string; +class function THTMLDocumentHiliter.Hilite(const RawCode: string; Attrs: IHiliteAttrs; const Title: string): TEncodedData; resourcestring // Default document title @@ -406,7 +422,7 @@ class function TXHTMLDocumentHiliter.Hilite(const RawCode: string; Renderer: IHiliteRenderer; // XHTML renderer object Builder: THTMLBuilder; // object used to construct XHTML document begin - Builder := THTMLBuilder.Create; + Builder := BuilderClass.Create; try if Title <> '' then Builder.Title := Title @@ -421,6 +437,20 @@ class function TXHTMLDocumentHiliter.Hilite(const RawCode: string; end; end; +{ TXHTMLDocumentHiliter } + +class function TXHTMLDocumentHiliter.BuilderClass: THTMLBuilderClass; +begin + Result := TXHTMLBuilder; +end; + +{ THTML5DocumentHiliter } + +class function THTML5DocumentHiliter.BuilderClass: THTMLBuilderClass; +begin + Result := THTML5Builder; +end; + { TRTFDocumentHiliter } class function TRTFDocumentHiliter.Hilite(const RawCode: string; diff --git a/Src/Hiliter.UPasLexer.pas b/Src/Hiliter.UPasLexer.pas index 88d632fbe..4a39be357 100644 --- a/Src/Hiliter.UPasLexer.pas +++ b/Src/Hiliter.UPasLexer.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). * * Defines a class that analyses and tokenises Pascal source code. } diff --git a/Src/Hiliter.UPasParser.pas b/Src/Hiliter.UPasParser.pas index 55403c8ee..da631da15 100644 --- a/Src/Hiliter.UPasParser.pas +++ b/Src/Hiliter.UPasParser.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). * * Defines a class that parses Pascal source files and splits into different * highlighting elements. diff --git a/Src/Hiliter.UPersist.pas b/Src/Hiliter.UPersist.pas index a121744a9..d4c271a24 100644 --- a/Src/Hiliter.UPersist.pas +++ b/Src/Hiliter.UPersist.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2006-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2006-2021, Peter Johnson (gravatar.com/delphidabbler). * * Implements a static class that can persist syntax highlighter attributes * to/from a given storage. diff --git a/Src/Install/Assets/AutoUpdateChecks.rtf b/Src/Install/Assets/AutoUpdateChecks.rtf deleted file mode 100644 index 61112043e..000000000 Binary files a/Src/Install/Assets/AutoUpdateChecks.rtf and /dev/null differ diff --git a/Src/Install/Assets/LICENSE b/Src/Install/Assets/LICENSE deleted file mode 100644 index f9915218c..000000000 --- a/Src/Install/Assets/LICENSE +++ /dev/null @@ -1,8 +0,0 @@ -All the files in the Src/Install/Assets directory are governed by the following -license. - -This Source Code Form is subject to the terms of the Mozilla Public -License, v. 2.0. If a copy of the MPL was not distributed with this -file, You can obtain one at http://mozilla.org/MPL/2.0/. - -All files are copyright (C) 2012-2016, Peter Johnson (www.delphidabbler.com). \ No newline at end of file diff --git a/Src/Install/Assets/License.rtf b/Src/Install/Assets/License.rtf index 7df5a967a..74ec0e7d1 100644 Binary files a/Src/Install/Assets/License.rtf and b/Src/Install/Assets/License.rtf differ diff --git a/Src/Install/CodeSnip.iss b/Src/Install/CodeSnip.iss index 7d0df17f3..229db969a 100644 --- a/Src/Install/CodeSnip.iss +++ b/Src/Install/CodeSnip.iss @@ -1,11 +1,8 @@ ; This Source Code Form is subject to the terms of the Mozilla Public License, ; v. 2.0. If a copy of the MPL was not distributed with this file, You can -; obtain one at http://mozilla.org/MPL/2.0/ +; obtain one at https://mozilla.org/MPL/2.0/ ; -; Copyright (C) 2006-2013, Peter Johnson (www.delphidabbler.com). -; -; $Rev$ -; $Date$ +; Copyright (C) 2006-2024, Peter Johnson (gravatar.com/delphidabbler). ; ; Install file generation script for use with Inno Setup. @@ -20,7 +17,7 @@ /* assumes S begins with "Release " followed by version as x.x.x */ \ Local[0] = Copy(S, Len("Release ") + 1, 99), \ Local[0] - + #define AppPublisher "DelphiDabbler" #define AppName "CodeSnip" #define ExeFile AppName + ".exe" @@ -28,19 +25,19 @@ #define ReadMeFile "ReadMe.txt" #define LicenseFile "License.rtf" #define LicenseTextFile "License.html" -#define PrivacyFile "Privacy.txt" -#define OutDir SourcePath + "..\..\Exe" +#define OutDir SourcePath + "..\..\_build\exe" #define SrcDocsPath SourcePath + "..\..\Docs\" -#define SrcAssetsPath SourcePath + '\Assets\" -#define SrcExePath SourcePath + "..\..\Exe\" +#define SrcAssetsPath SourcePath + "\Assets\" +#define SrcExePath SourcePath + "..\..\_build\exe\" +#define TmpPath SourcePath + "..\..\_build\release\~tmp~\" #define ProgDataSubDir AppName + ".4" #define ExeProg SrcExePath + ExeFile #define AppVersion DeleteToVerStart(GetFileProductVersion(ExeProg)) #define Copyright GetStringFileInfo(ExeProg, LEGAL_COPYRIGHT) -#define Company "DelphiDabbler.com" -#define WebAddress "www.delphidabbler.com" -#define WebURL "https://" + WebAddress + "/" -#define AppURL WebURL + "software/codesnip" +#define Company "DelphiDabbler" +#define WebAddress "github.com/delphidabbler" +#define WebURL "https://" + WebAddress + "/" +#define AppURL WebURL + "/codesnip" #define InstUninstDir "Uninst" ; Creates name of setup file from app name, version and any special build string @@ -93,10 +90,8 @@ Name: {commonappdata}\{#AppPublisher}\{#ProgDataSubDir}\Database; permissions: e Source: {#SrcExePath}{#ExeFile}; DestDir: {app} Source: {#SrcExePath}{#HelpFile}; DestDir: {app}; Flags: ignoreversion Source: {#SrcDocsPath}{#LicenseTextFile}; DestDir: {app}; Flags: ignoreversion -Source: {#SrcDocsPath}{#ReadMeFile}; DestDir: {app}; Flags: ignoreversion -Source: {#SrcDocsPath}{#PrivacyFile}; DestDir: {app}; Flags: ignoreversion +Source: {#TmpPath}{#ReadMeFile}; DestDir: {app}; Flags: ignoreversion Source: {#SrcAssetsPath}UpdatingPreview.rtf; Flags: dontcopy -Source: {#SrcAssetsPath}AutoUpdateChecks.rtf; Flags: dontcopy [Icons] Name: {group}\{#AppPublisher} {#AppName}; Filename: {app}\{#ExeFile} @@ -116,7 +111,7 @@ Type: filesandordirs; Name: "{commonappdata}\{#AppPublisher}\{#ProgDataSubDir}" [Code] // DataLocations.ps must be declared first #include "DataLocations.ps" -#include "VersionInfo.ps" +#include "VersionInfo.ps" #include "Unicode.ps" #include "UpdateIni.ps" #include "UpdateDBase.ps" diff --git a/Src/Install/DataLocations.ps b/Src/Install/DataLocations.ps index 0461036c1..40ff8d225 100644 Binary files a/Src/Install/DataLocations.ps and b/Src/Install/DataLocations.ps differ diff --git a/Src/Install/EventHandlers.ps b/Src/Install/EventHandlers.ps index 82ebcf401..a4cde1953 100644 Binary files a/Src/Install/EventHandlers.ps and b/Src/Install/EventHandlers.ps differ diff --git a/Src/Install/Unicode.ps b/Src/Install/Unicode.ps index c9bf7ff41..27ebf16d3 100644 Binary files a/Src/Install/Unicode.ps and b/Src/Install/Unicode.ps differ diff --git a/Src/Install/UpdateDBase.ps b/Src/Install/UpdateDBase.ps index 5cc3f6839..aeaf937a0 100644 Binary files a/Src/Install/UpdateDBase.ps and b/Src/Install/UpdateDBase.ps differ diff --git a/Src/Install/UpdateIni.ps b/Src/Install/UpdateIni.ps index f3bf50c6f..3957559d8 100644 Binary files a/Src/Install/UpdateIni.ps and b/Src/Install/UpdateIni.ps differ diff --git a/Src/Install/VersionInfo.ps b/Src/Install/VersionInfo.ps index 41c609ef0..cdfc33ab6 100644 Binary files a/Src/Install/VersionInfo.ps and b/Src/Install/VersionInfo.ps differ diff --git a/Src/IntfAligner.pas b/Src/IntfAligner.pas index b0e075747..7bf9e8280 100644 --- a/Src/IntfAligner.pas +++ b/Src/IntfAligner.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2007-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2007-2021, Peter Johnson (gravatar.com/delphidabbler). * * Declares an interface supported by objects that can align a form on screen. } diff --git a/Src/IntfCommon.pas b/Src/IntfCommon.pas index 0bde0ad78..976611d1f 100644 --- a/Src/IntfCommon.pas +++ b/Src/IntfCommon.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). * * Common general purpose interfaces. } diff --git a/Src/IntfFrameMgrs.pas b/Src/IntfFrameMgrs.pas index 2434bab7e..813d320ad 100644 --- a/Src/IntfFrameMgrs.pas +++ b/Src/IntfFrameMgrs.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2025, Peter Johnson (gravatar.com/delphidabbler). * * Declares interfaces, constants and enumerations required to manage various * parts of CodeSnip's UI. diff --git a/Src/IntfNotifier.pas b/Src/IntfNotifier.pas index aac9bca5b..cb0e460c9 100644 --- a/Src/IntfNotifier.pas +++ b/Src/IntfNotifier.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). * * Defines interfaces to set up and use the notifier object that triggers * actions in response to user initiated events in the GUI. @@ -74,26 +71,15 @@ interface /// Snippet must be user defined. procedure EditSnippet(const SnippetName: WideString); - /// Displays Donate dialogue box. - procedure Donate; - /// Opens Snippets Editor ready to create a new snippet. procedure NewSnippet; /// Displays news items from the CodeSnip news feed. procedure ShowNews; - /// Checks for program updates. - procedure CheckForUpdates; - /// Displays the program's About Box. procedure ShowAboutBox; - /// Displays the Preferences dialogue box containing the specified - /// page. - /// string [in] Class name of the frame that - /// implements the required preferences page. - procedure ShowPrefsPage(const ClsName: string); end; type @@ -140,10 +126,6 @@ interface /// TBasicAction [in] Required action. procedure SetEditSnippetAction(const Action: TBasicAction); - /// Sets action used to display Donate dialogue box. - /// TBasicAction [in] Required action. - procedure SetDonateAction(const Action: TBasicAction); - /// Sets action used to display a category. /// TBasicAction [in] Required action. procedure SetDisplayCategoryAction(const Action: TBasicAction); @@ -158,18 +140,10 @@ interface /// TBasicAction [in] Required action. procedure SetNewsAction(const Action: TBasicAction); - /// Sets action used to check for program updates. - /// TBasicAction [in] Required action. - procedure SetCheckForUpdatesAction(const Action: TBasicAction); - /// Sets action used to display the program's About Box. /// TBasicAction [in] Required action. procedure SetAboutBoxAction(const Action: TBasicAction); - /// Sets action used to display a given page of the Preferences - /// dialogue box. - /// TBasicAction [in] Required action. - procedure SetShowPrefsPageAction(const Action: TBasicAction); end; type diff --git a/Src/IntfPreview.pas b/Src/IntfPreview.pas index ba6742425..bf5475836 100644 --- a/Src/IntfPreview.pas +++ b/Src/IntfPreview.pas @@ -1,12 +1,9 @@ { * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). * * Defines the interface implemented by objects that can display a document of a * certain type in a preview dialogue box. diff --git a/Src/LICENSE b/Src/LICENSE deleted file mode 100644 index 2b62ec515..000000000 --- a/Src/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -This file describes the licenses that apply to source code files in the Src -directory and its sub-directories, unless the sub-directory or its closest -parent directory also contains a LICENSE file, in which case that file takes -precedence. - -Most files contain a comment that provides license information. - -Exceptions are: - -* All .dfm files are licensed under the same license as the related .pas file. - For example, if Foo.pas is licensed under the Mozilla Public License v2.0 then - the same license also applies to Foo.dfm. - -* The following files have any copyright dedicated to the Public Domain - http://creativecommons.org/publicdomain/zero/1.0/ - - - Src/CodeSnip.cfg.tplt - - Src/CodeSnip.dproj - - Src/CodeSnip.groupproj - - Src/CodeSnip.todo \ No newline at end of file diff --git a/Src/Makefile b/Src/Makefile index 26d7d7c6b..b8b69e3b5 100644 --- a/Src/Makefile +++ b/Src/Makefile @@ -1,19 +1,26 @@ # This Source Code Form is subject to the terms of the Mozilla Public License, # v. 2.0. If a copy of the MPL was not distributed with this file, You can -# obtain one at http://mozilla.org/MPL/2.0/ +# obtain one at https://mozilla.org/MPL/2.0/ # -# Copyright (C) 2009-2015, Peter Johnson (www.delphidabbler.com). -# -# $Rev$ -# $Date$ +# Copyright (C) 2009-2024, Peter Johnson (gravatar.com/delphidabbler). # # Makefile for the CodeSnip project. -# Define macros giving relative paths to other directories from location of -# makefile -BIN = ..\Bin -EXE = ..\Exe +# Define macros relative paths to various directories relative to the repo root +BUILD_ROOT = _build +BIN_ROOT = $(BUILD_ROOT)\bin +EXE_ROOT = $(BUILD_ROOT)\exe +RELEASE_ROOT = $(BUILD_ROOT)\release +RELEASE_TMP_ROOT = $(RELEASE_ROOT)\~tmp~ +DOCS_ROOT = Docs +SRC_ROOT = Src + +# Defines macros giving directories relative to location of the Makefile +BIN_REL = ..\$(BIN_ROOT) +EXE_REL = ..\$(EXE_ROOT) +DOCS_REL = ..\$(DOCS_ROOT) +RELEASE_TMP_REL = ..\$(RELEASE_TMP_ROOT) # Check for required environment variables @@ -24,10 +31,6 @@ DELPHIROOT = $(DELPHIXE) !error DELPHIROOT or DELPHIXE environment variable required. !endif -!ifndef INDY10 -!error INDY10 environment variable required. -!endif - # Define macros for required tools TLIBIMP = "$(DELPHIROOT)\Bin\TLibImp.exe" @@ -82,11 +85,10 @@ DELPHIDEFINES = # Implicit rules -# Resource files are compiled to the directory specified by BIN macro, which -# must have been set by the caller. +# Resource files are compiled to the directory specified by BIN_REL macro. .rc.res: @echo +++ Compiling Resource file $< +++ - @$(BRCC32) $< -fo$(BIN)\$(@F) + @$(BRCC32) $< -fo$(BIN_REL)\$(@F) # Version info files are compiled by VIEd. A temporary .rc file is left behind .vi.rc: @@ -111,11 +113,13 @@ config: @copy /Y CodeSnip.cfg.tplt CodeSnip.cfg # Create build folders @cd .. - @if exist Bin rmdir /S /Q Bin - @mkdir Bin - @if not exist Exe mkdir Exe - @if not exist Release mkdir Release - @cd Src + @if not exist $(BUILD_ROOT) mkdir $(BUILD_ROOT) + @if exist $(BIN_ROOT) rmdir /S /Q $(BIN_ROOT) + @mkdir $(BIN_ROOT) + @if not exist $(EXE_ROOT) mkdir $(EXE_ROOT) + @if not exist $(RELEASE_ROOT) mkdir $(RELEASE_ROOT) + @if not exist $(RELEASE_TMP_ROOT) mkdir $(RELEASE_TMP_ROOT) + @cd $(SRC_ROOT) # Builds CodeSnip pascal files and links program pascal: CodeSnip.exe @@ -125,14 +129,14 @@ pascal: CodeSnip.exe CodeSnip.exe: @echo +++ Compiling Pascal +++ !ifdef PORTABLE - @if exist $(EXE)\$(@F) copy $(EXE)\$(@F) $(EXE)\$(@F).bak + @if exist $(EXE_REL)\$(@F) copy $(EXE_REL)\$(@F) $(EXE_REL)\$(@F).bak !endif - @$(DCC32) $(@B).dpr -B -U"$(INDY10)" $(DELPHIDEFINES) + @$(DCC32) $(@B).dpr -B $(DELPHIDEFINES) !ifdef PORTABLE - @copy $(EXE)\$(@F) $(EXE)\$(@B)-p.exe /Y - @del $(EXE)\$(@F) - @if exist $(EXE)\$(@F).bak copy $(EXE)\$(@F).bak $(EXE)\$(@F) - @if exist $(EXE)\$(@F).bak del $(EXE)\$(@F).bak + @copy $(EXE_REL)\$(@F) $(EXE_REL)\$(@B)-p.exe /Y + @del $(EXE_REL)\$(@F) + @if exist $(EXE_REL)\$(@F).bak copy $(EXE_REL)\$(@F).bak $(EXE_REL)\$(@F) + @if exist $(EXE_REL)\$(@F).bak del $(EXE_REL)\$(@F).bak !endif # Builds help file @@ -151,34 +155,45 @@ resources: $(VERINFOFILEBASE).res Resources.res HTML.res # Compiles HTMLres from .hrc file HTML.res: HTML.hrc @echo +++ Compiling HTML Resource manifest file +++ - @$(HTMLRES) -mHTML.hrc -o$(BIN)\HTML.res -r -q + @$(HTMLRES) -mHTML.hrc -o$(BIN_REL)\HTML.res -r -q # Compiles type library from IDL typelib: - @$(GENTLB) .\ExternalObj.ridl -D$(BIN) -TExternalObj.tlb + @$(GENTLB) .\ExternalObj.ridl -D$(BIN_REL) -TExternalObj.tlb # Builds setup program setup: !ifndef PORTABLE - @del ..\Exe\CodeSnip-Setup-* + copy $(DOCS_REL)\ReadMe-standard.txt $(RELEASE_TMP_REL)\ReadMe.txt + del $(EXE_REL)\CodeSnip-Setup-* @$(ISCC) Install\CodeSnip.iss + del $(RELEASE_TMP_REL)\ReadMe.txt !else @echo **** Portable build - no setup file created **** !endif # Creates auto generated files autogen: - @$(TLIBIMP) -P+ -Ps+ -D.\AutoGen -FtIntfExternalObj $(BIN)\ExternalObj.tlb + @$(TLIBIMP) -P+ -Ps+ -D.\AutoGen -FtIntfExternalObj $(BIN_REL)\ExternalObj.tlb @if exist .\AutoGen\IntfExternalObj.dcr del .\AutoGen\IntfExternalObj.dcr # Build release files (.zip) +# If RELEASEFILENAME is defined by caller then it is used as name of zip file +# otherwise default zip file name is used, which depends on whether PORTABLE +# is defined. +# If VERSION is defined by caller then it is appended to RELEASEFILENAME, +# separated by a dash. !ifndef RELEASEFILENAME -RELEASEFILENAME = dd-codesnip -!ifdef PORTABLE -RELEASEFILENAME = $(RELEASEFILENAME)-portable +!ifndef PORTABLE +RELEASEFILENAME = codesnip-exe +!else +RELEASEFILENAME = codesnip-portable-exe +!endif !endif +!ifdef VERSION +RELEASEFILENAME = $(RELEASEFILENAME)-$(VERSION) !endif -OUTFILE = Release\$(RELEASEFILENAME).zip +OUTFILE = $(RELEASE_ROOT)\$(RELEASEFILENAME).zip release: @echo --------------------- @echo Creating Release File @@ -186,15 +201,18 @@ release: @cd .. -@if exist $(OUTFILE) del $(OUTFILE) !ifndef PORTABLE - @$(ZIP) -j -9 $(OUTFILE) Exe\CodeSnip-Setup-*.exe Docs\ReadMe.txt + copy $(DOCS_ROOT)\ReadMe-standard.txt $(RELEASE_TMP_ROOT)\ReadMe.txt + @$(ZIP) -j -9 $(OUTFILE) $(EXE_ROOT)\CodeSnip-Setup-*.exe $(RELEASE_TMP_ROOT)\ReadMe.txt + del $(RELEASE_TMP_ROOT)\ReadMe.txt !else - @$(ZIP) -j -9 $(OUTFILE) Exe\CodeSnip-p.exe - @$(ZIP) -j -9 $(OUTFILE) Exe\CodeSnip.chm - @$(ZIP) -j -9 $(OUTFILE) Docs\ReadMe.txt - @$(ZIP) -j -9 $(OUTFILE) Docs\Privacy.txt - @$(ZIP) -j -9 $(OUTFILE) Docs\License.html + copy $(DOCS_ROOT)\ReadMe-portable.txt $(RELEASE_TMP_ROOT)\ReadMe.txt + @$(ZIP) -j -9 $(OUTFILE) $(EXE_ROOT)\CodeSnip-p.exe + @$(ZIP) -j -9 $(OUTFILE) $(EXE_ROOT)\CodeSnip.chm + @$(ZIP) -j -9 $(OUTFILE) $(RELEASE_TMP_ROOT)\ReadMe.txt + @$(ZIP) -j -9 $(OUTFILE) $(DOCS_ROOT)\License.html + del $(RELEASE_TMP_ROOT)\ReadMe.txt !endif - @cd Src + @cd $(SRC_ROOT) # Clean up unwanted files clean: @@ -208,4 +226,4 @@ clean: -@del /S *.tvsconfig 2>nul # remove __history folders -@for /F "usebackq" %i in (`dir /S /B /A:D ..\__history`) do @rmdir /S /Q %i - @cd Src + @cd $(SRC_ROOT) diff --git a/Src/Notifications.UData.pas b/Src/Notifications.UData.pas deleted file mode 100644 index 6e343d00e..000000000 --- a/Src/Notifications.UData.pas +++ /dev/null @@ -1,117 +0,0 @@ -{ - * This Source Code Form is subject to the terms of the Mozilla Public License, - * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ - * - * Copyright (C) 2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ - * - * Defines a record that stores information to be displayed in a notification - * window along with a related task that can be performed from the window. -} - - -unit Notifications.UData; - - -interface - - -uses - // Delphi - SysUtils; - - -type - /// Stores information to be displayed in a notification window - /// along with a related task that can be performed from the window. - /// - /// - /// A notification always has a title and at least one paragraph of - /// content text. - /// An associated task and help keyword are optional. - /// - TNotificationData = record - strict private - var - /// Value of Title property. - fTitle: string; - /// Value of Content property. - fContent: TArray; - /// Value of HelpKeyword property. - fHelpKeyword: string; - /// Valure of TaskCallback property. - fTaskCallback: TProc; - /// Value of TaskPrompt property. - fTaskPrompt: string; - public - /// Title of notification. - property Title: string read fTitle; - - /// Notification content. - /// Each array element is a paragraph of text. - property Content: TArray read fContent; - - /// Keyword of any related help topic. - /// Set to the empty string to inhibit display of help. - property HelpKeyword: string read fHelpKeyword; - - /// Callback procedure called from notification window when user - /// clicks the task button. - /// Note that no task button will appear if this property is nil. - /// - property TaskCallback: TProc read fTaskCallback; - - /// Prompt text that appears in notification window's task button. - /// - /// This property is ignored if TaskCallback is nil. - property TaskPrompt: string read fTaskPrompt; - - /// Creates an initialised notification data record. - /// string [in] Notification title. Must be empty - /// string or contain only white space. - /// array of string [in] Paragraphs of text forming - /// content of notification. Must contain at least one element that - /// contains non-whitespace text. Empty elements or elements containing - /// only white space are ignored. - /// string [in] A-link keyword of associated - /// help topic or empty string if there is no such help topic. - /// TProc [in] Procedure to be called from - /// notification window if user clicks the task button, or nil if no - /// such button is required. - /// string [in] Prompt or caption to appear on - /// any task button. May be empty string if ATaskCallback is nil. - /// TProc [in] Procedure to be called from - /// notification window if user chooses to inhibit similar messages in - /// future, or nil if message can't be inhibited. - constructor Create(const ATitle: string; const AContent: array of string; - const AHelpKeyord: string; const ATaskCallback: TProc; - const ATaskPrompt: string); - end; - -implementation - -{ TNotificationData } - -constructor TNotificationData.Create(const ATitle: string; - const AContent: array of string; const AHelpKeyord: string; - const ATaskCallback: TProc; const ATaskPrompt: string); -var - I: Integer; -begin - Assert(ATitle <> '', 'TNotificationData.Create: ATitle = empty string'); - Assert((Length(AContent) > 0), - 'TNotificationData.Create: AContent has no elements'); - fTitle := ATitle; - SetLength(fContent, Length(AContent)); - for I := 0 to Pred(Length(AContent)) do - fContent[I] := AContent[I]; - fHelpKeyword := AHelpKeyord; - fTaskCallback := ATaskCallback; - fTaskPrompt := ATaskPrompt; -end; - -end. - diff --git a/Src/Notifications.UDisplayMgr.pas b/Src/Notifications.UDisplayMgr.pas deleted file mode 100644 index 28e4c7a4e..000000000 --- a/Src/Notifications.UDisplayMgr.pas +++ /dev/null @@ -1,114 +0,0 @@ -{ - * This Source Code Form is subject to the terms of the Mozilla Public License, - * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ - * - * Copyright (C) 2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ - * - * Implements a static object that manages the notification display sub-system. -} - - -unit Notifications.UDisplayMgr; - - -interface - - -uses - // Delphi - Controls, - // Project - Notifications.UDisplayThread, Notifications.UWindow, UBaseObjects; - - -type - /// Static class that manages the notification display sub-system. - /// - /// This class manages the background thread that reads the - /// notification queue and displays the notifications sequentially in a - /// suitable pop-up window. - TNotificationDisplayMgr = class(TNoConstructObject) - strict private - const - /// Amount of time the notification window is displayed on - /// screen before hiding itself. - WindowDisplayTime = 20 * 1000; // 20 seconds - class var - /// Notification window object. - fWindow: TNotificationWindow; - /// Thread that reads the notification queue and displays the - /// notifications sequentially in the notification window. - /// - fDisplayThread: TNotificationDisplayThread; - public - /// Class destructor. Ensures the noification window and display - /// thread are destroyed when the program closes. - class destructor Destroy; - /// Starts the notification display sub-system. - /// TWinControl [in] Window control that is to - /// be the parent of the notification window. Must not be nil. - /// Creates a new notification window object and a linked display - /// thread. - class procedure Start(const WindowParent: TWinControl); - /// Stops the notification display sub-system. - /// The display thread and notification window are destroyed. - /// - class procedure Stop; - end; - - -implementation - - -uses - SysUtils, Classes, - // Project - Notifications.UData, Notifications.UQueue; - - -{ TNotificationDisplayMgr } - -class destructor TNotificationDisplayMgr.Destroy; -begin - Stop; -end; - -class procedure TNotificationDisplayMgr.Start(const WindowParent: TWinControl); -begin - Assert(Assigned(WindowParent), ClassName + '.Start: WindowParent is nil'); - - if Assigned(fWindow) or Assigned(fDisplayThread) then - Stop; - - fWindow := TNotificationWindow.Create(nil); - fWindow.Parent := WindowParent; - fWindow.DisplayTime := WindowDisplayTime; - - fDisplayThread := TNotificationDisplayThread.Create( - fWindow.DisplayLock, - procedure(N: TNotificationData) - begin - Assert(fWindow.State = nwsClosed, - ClassName + '.Create:AnonCallback: Window not closed'); - fWindow.SlideIn(N); - end - ); - fDisplayThread.FreeOnTerminate := False; - fDisplayThread.Priority := tpLowest; - fDisplayThread.Start; -end; - -class procedure TNotificationDisplayMgr.Stop; -begin - // FreeAndNil is necessary here - TNotificationQueue.ReleaseLocks; - FreeAndNil(fDisplayThread); - FreeAndNil(fWindow); -end; - -end. - diff --git a/Src/Notifications.UDisplayThread.pas b/Src/Notifications.UDisplayThread.pas deleted file mode 100644 index bf0dd665c..000000000 --- a/Src/Notifications.UDisplayThread.pas +++ /dev/null @@ -1,141 +0,0 @@ -{ - * This Source Code Form is subject to the terms of the Mozilla Public License, - * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ - * - * Copyright (C) 2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ - * - * Implements a thread that pops any waiting items from notification queue and - * causes them to be displayed. -} - - -unit Notifications.UDisplayThread; - - -interface - - -uses - // Delphi - SysUtils, Classes, SyncObjs, - // Project - Notifications.UData, Notifications.UQueue; - - -type - /// Thread that pops pending notifications from the notification - /// queue and causes them to be displayed. - /// - /// This class is display agnostic. It has no knowledge of the object - /// that actually displays the notification and simply a provided callback - /// procedure to request display of a notification. - /// The thread waits for any currently displayed notification to clear - /// before displaying another notification. - /// NOTE: There should only be one instance of this thread running at - /// any time. - /// - TNotificationDisplayThread = class(TThread) - strict private - const - /// Timeout, in ms, when waiting for a notification display to - /// become available. - DisplayLockTimeout = 20000; // 20 seconds - /// Timeout, in ms, when waiting for notification queue's - /// 'empty' lock. - EmptyLockTimeout = 10000; // 10 seconds - var - /// Object that provides access to notification queue. - fQueue: TNotificationQueue; - /// Caller-provided closure used to display a notification. - /// - /// This closure is called in the context of the main thread. - /// - fDisplayProc: TProc; - /// Event object that signals when the notification display - /// object is available for use. - fDisplayLock: TSimpleEvent; - strict protected - /// Executes the main thread logic. Waits to pop a notification - /// item from the queue then causes it to be displayed. - procedure Execute; override; - public - /// Constructs a suspended thread instance. - /// TSimpleEvent [in] Lock used by notification - /// window while it is being displayed. - /// TProc<TNotificationData> Anonymous - /// method that the thread calls when it wants to display a notification. - /// - constructor Create(const DisplayLock: TSimpleEvent; - const DisplayProc: TProc); - /// Destroys the thread instance, freeing owned objects. - destructor Destroy; override; - end; - - -implementation - - -{ TNotificationDisplayThread } - -constructor TNotificationDisplayThread.Create(const DisplayLock: TSimpleEvent; - const DisplayProc: TProc); -begin - Assert(Assigned(DisplayLock), ClassName + '.Create: DisplayLock is nil'); - Assert(Assigned(DisplayProc), ClassName + '.Create: DisplayProc is nil'); - inherited Create(True); - fDisplayLock := DisplayLock; - fDisplayProc := DisplayProc; - fQueue := TNotificationQueue.Create; -end; - -destructor TNotificationDisplayThread.Destroy; -begin - fQueue.Free; - inherited; -end; - -procedure TNotificationDisplayThread.Execute; -var - NotificationData: TNotificationData; - EmptyQueueWaitResult: TWaitResult; -begin - while not Terminated do - begin - EmptyQueueWaitResult := fQueue.WaitForEmptyLock(EmptyLockTimeout); - if not (EmptyQueueWaitResult in [wrSignaled, wrTimeout]) then - Terminate; - if Terminated then - Exit; - if not fQueue.Pop(NotificationData) then - Continue; - if Terminated then - Exit; - - while not Terminated do - begin - case fDisplayLock.WaitFor(DisplayLockTimeout) of - wrSignaled: - begin - Synchronize( - procedure - begin - fDisplayProc(NotificationData); - end - ); - Break; - end; - wrTimeout: - Continue; - else - Terminate; - end; - end; - end; -end; - -end. - diff --git a/Src/Notifications.UQueue.pas b/Src/Notifications.UQueue.pas deleted file mode 100644 index 1e3d2be71..000000000 --- a/Src/Notifications.UQueue.pas +++ /dev/null @@ -1,178 +0,0 @@ -{ - * This Source Code Form is subject to the terms of the Mozilla Public License, - * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ - * - * Copyright (C) 2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ - * - * Defines a class that manages a thread safe queue of notifications that are - * awaiting display. -} - - -unit Notifications.UQueue; - - -interface - - -uses - // Delphi - SyncObjs, Generics.Collections, - // Project - Notifications.UData; - - -type - /// Manages a thread safe queue of notifications that are awaiting - /// display. - /// Notifications can be placed in the queue and removed from it by - /// different threads. - TNotificationQueue = class(TObject) - strict private - - const - /// Maximum size of queue. - MaxQueueSize = 20; - - class var - /// Queue of notifications. - fQueue: TQueue; - /// Critical section that ensures queue is accessed by only one - /// thread at a time. - fCriticalSection: TCriticalSection; - /// Event object that enables threads reading the queue to block - /// while the queue is empty. - /// NOTE: This lock is designed for use with a single reading - /// thread and is not guaranteed to work properly should more than one - /// task block on the empty queue. - fEmptyLock: TSimpleEvent; - - strict private - - /// Checks if the queue is full. - function IsFull: Boolean; inline; - - /// Checks if the queue is empty. - function IsEmpty: Boolean; inline; - - public - - /// Creates objects shared amongst all instances. - class constructor Create; - - /// Frees objects shared amongst all instances. - class destructor Destroy; - - /// Attempts to add a new notification data item onto the end of - /// the queue. - /// TNotificationData [in] Notification data to be - /// added to queue. - /// Boolean. True if data was added to the queue or False if the - /// queue was full and the data was not added. - function Push(const Value: TNotificationData): Boolean; - - /// Attempts to read and remove a notification data item from the - /// front of the queue. - /// TNotificationData [out] Receives any notification - /// data added to queue. - /// Boolean. True if data was read from the queue or False if the - /// queue was empty and no data could be read. - /// If False is returned, Value is undefined. - function Pop(out Value: TNotificationData): Boolean; - - /// Waits for the empty lock event to enter a singalled state. - /// - /// Cardinal [in] Maximum amount of time to wait. - /// - /// TWaitResult. Indicates how the wait operation finished. - /// - class function WaitForEmptyLock(Timeout: Cardinal): TWaitResult; - - /// Releases any locks on the queue. - class procedure ReleaseLocks; - end; - - -implementation - - -{ TNotificationQueue } - -class constructor TNotificationQueue.Create; -begin - fCriticalSection := TCriticalSection.Create; - fQueue := TQueue.Create; - fEmptyLock := TSimpleEvent.Create; - fEmptyLock.ResetEvent; -end; - -class destructor TNotificationQueue.Destroy; -begin - fEmptyLock.Free; - fQueue.Free; - fCriticalSection.Free; -end; - -function TNotificationQueue.IsEmpty: Boolean; -begin - Result := fQueue.Count = 0; -end; - -function TNotificationQueue.IsFull: Boolean; -begin - Result := fQueue.Count = MaxQueueSize; -end; - -function TNotificationQueue.Pop(out Value: TNotificationData): Boolean; -begin - fCriticalSection.Acquire; - try - Result := not IsEmpty; - if Result then - begin - Value := fQueue.Dequeue; - if IsEmpty then - fEmptyLock.ResetEvent; - end; - finally - fCriticalSection.Release; - end; -end; - -function TNotificationQueue.Push(const Value: TNotificationData): Boolean; -var - WasEmpty: Boolean; -begin - fCriticalSection.Acquire; - try - WasEmpty := IsEmpty; - Result := not IsFull; - if Result then - begin - fQueue.Enqueue(Value); - if WasEmpty then - fEmptyLock.SetEvent; - end; - finally - fCriticalSection.Release; - end; -end; - -class procedure TNotificationQueue.ReleaseLocks; -begin - // only the one lock! - fEmptyLock.SetEvent; -end; - -class function TNotificationQueue.WaitForEmptyLock(Timeout: Cardinal): - TWaitResult; -begin - Result := fEmptyLock.WaitFor(Timeout); -end; - -end. - diff --git a/Src/Notifications.URecorderThread.pas b/Src/Notifications.URecorderThread.pas deleted file mode 100644 index ce6625e07..000000000 --- a/Src/Notifications.URecorderThread.pas +++ /dev/null @@ -1,137 +0,0 @@ -{ - * This Source Code Form is subject to the terms of the Mozilla Public License, - * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ - * - * Copyright (C) 2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ - * - * Defines an abstract base class for threads that generate and record - * notification data in the notification queue. -} - - -unit Notifications.URecorderThread; - - -interface - - -uses - // Delphi - Classes, - // Project - Notifications.UData; - - -type - /// Abstract base class for threads that generate or receive a - /// notification that is to be recorded in the notification queue. - /// Sub-classes must override GetNotification to provide any - /// notification data record to be queued. - TNotificationRecorderThread = class abstract(TThread) - strict private - var - /// Number of milliseconds to delay start of thread's execution. - /// - fStartDelay: Cardinal; - /// Performs a busy wait for the time specified by fStartDelay. - /// - procedure DoDelay; - strict protected - /// Gets notification data to be queued. - /// TNotificationData [out] Set to required notification - /// data item. - /// Boolean. True on success or False if no notification data is - /// available. - /// N can be undefined if False is returned. - function GetNotification(out N: TNotificationData): Boolean; virtual; - abstract; - /// Stores the given notification data item in the notification - /// queue. - /// If notification can't be queued it is discarded. - procedure RecordNotification(const N: TNotificationData); - /// Gets and stores a notification in the notification queue. - /// - /// Called when thread is started. - procedure Execute; override; - public - /// Creates the and initialises the thread instance. - /// Cardinal [in] Number of milliseconds to delay - /// the start of the thread's execution. Passing 0 indicates no delay. - /// Non-zero delays are rounded up to the nearest 1/5th second. - constructor Create(StartDelay: Cardinal = 0); - end; - - -implementation - - -uses - // Delphi - Windows, - // Project - Notifications.UQueue; - - -{ TNotificationRecorderThread } - -constructor TNotificationRecorderThread.Create(StartDelay: Cardinal); -begin - inherited Create(True); - fStartDelay := StartDelay; -end; - -procedure TNotificationRecorderThread.DoDelay; -var - StartTC: Cardinal; - CurrentTC: Int64; -begin - StartTC := GetTickCount; - repeat - if Terminated then - Exit; - CurrentTC := Windows.GetTickCount; - if CurrentTC < StartTC then - CurrentTC := CurrentTC + High(Cardinal); - Sleep(200); // 1/5 second - until CurrentTC - StartTC >= fStartDelay; -end; - -procedure TNotificationRecorderThread.Execute; -var - N: TNotificationData; -begin - if fStartDelay > 0 then - DoDelay; - if Terminated then - Exit; - if not GetNotification(N) or Terminated then - Exit; - RecordNotification(N); -end; - -procedure TNotificationRecorderThread.RecordNotification( - const N: TNotificationData); -var - Queue: TNotificationQueue; -begin - Queue := TNotificationQueue.Create; - try - if Terminated then - Exit; - // Attempt to push notification data onto queue. This may fail if queue is - // full, in which case we ignore the problem and ditch the notification. - // If in future some other action needs to be taken when the queue is - // full, just test the return value of the Push method - False => queue - // full. - Queue.Push(N); // call this may block waiting for critical section - finally - Queue.Free; - end; -end; - - -end. diff --git a/Src/Notifications.UWindow.pas b/Src/Notifications.UWindow.pas deleted file mode 100644 index c5548f71b..000000000 --- a/Src/Notifications.UWindow.pas +++ /dev/null @@ -1,559 +0,0 @@ -{ - * This Source Code Form is subject to the terms of the Mozilla Public License, - * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ - * - * Copyright (C) 2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ - * - * Implements a slide-in notification window that appears at the bottom right - * of its parent window. -} - - -unit Notifications.UWindow; - - -interface - - -uses - // Delphi - Classes, StdCtrls, ExtCtrls, Controls, Buttons, Generics.Collections, - Windows {must come before Graphics}, Graphics, Messages, SyncObjs, - // Project - Notifications.UData, UStructs; - - -type - /// VCL control that encapsulates notification window that appears - /// at the bottom right of its parent window and can be animated to slide on - /// and off screen. - TNotificationWindow = class(TCustomControl) - public - type - /// - /// Possible states of notification window. - /// - nwsOpen - window is fully open. - /// - nwsClosed - window is fully closed (hidden). - /// - nwsOpening - window is transitioning from closed to open. - /// - /// - nwsClosing - window is transitioning from open to closed. - /// - /// - TState = (nwsOpen, nwsClosed, nwsOpening, nwsClosing); - strict private - const - /// Custom window message used to indicate window has been - /// concealed (i.e. slide-out has completed). - WM_CONCEALED = WM_USER + 234; - /// Colour that defines starting point (top) of window's - /// gradient fill. - GradColour1 = clWhite; - /// Colour that defines end point (bottom) of window's gradient - /// fill. - GradColour2 = $edbc96; - /// Colour of window border. - BorderColour = $cc8888; - /// Step size used when slliding window in or out. - /// A complete move is taken to range from 0.0 to 1.0. - SlideStep = 0.02; - /// Spacing between paragraphs of text from Contents property. - /// - ParaSpacing = 4; - /// Character used for close button. - /// Character must be rendered in Marlett font. - CloseButtonChar = #$75; - /// Character used for help button. - /// Character must be rendered in Marlett font. - HelpButtonChar = #$73; - strict private - var - /// Value of DisplayLock property. - fDisplayLock: TSimpleEvent; - /// Value of State property. - fState: TState; - /// Stores the properties and tasks associated with the last - /// window displayed. - /// This record is updated by every call to the SlideIn method. - /// It is undefined until SlideIn is first called. - fNotificationData: TNotificationData; - /// Button used to close (hide) an open window. - fCloseBtn: TSpeedButton; - /// Button used to display context sensitive help. - fHelpBtn: TSpeedButton; - /// Button used to perform any task associated with current - /// notification. - fTaskBtn: TButton; - /// Displays window's title text. - fTitleLbl: TLabel; - /// Timer used to automatically close (hide) an open window - /// after a specified time. - fHideTimer: TTimer; - /// Bounding rectangle of 'light bulb' glyph in window. - /// - fGlyphBounds: TRectEx; - /// Maintains a list of labels used to display the paragraphs - /// of text from the Contents property. - fContentLblList: TObjectList; - /// 'Light bulb' glyph displayed in window. - fLightBulb: TBitmap; - - strict private - - /// Handles the custom message sent when the window has been - /// concealed after sliding off screen. - /// The window display lock event is signalled. - procedure WMConcealed(var Msg: TMessage); message WM_CONCEALED; - - /// OnClick event handler for task button. Calls any callback - /// procedure registered for the current task. - procedure TaskBtnClick(Sender: TObject); - - /// Creates labels required to display given content. - /// One label is created for each element of the given array. - /// - procedure CreateContentLabels(const AContent: TArray); - - /// Getter for DisplayTime property. - function GetDisplayTime: Cardinal; - - /// Setter for DisplayTime property. - procedure SetDisplayTime(const MS: Cardinal); - - /// Sets window property values for current notification and - /// rearranges and sizes window accordingly. - procedure UpdateWindow; - - /// Creates and initialises all the window's controls. - procedure CreateCtrls; - - /// Handles clicks on Close button by hiding (closing) window. - /// - /// Does nothing if window is not fully open. - procedure CloseBtnClickHandler(Sender: TObject); - - /// Handles clicks on Help button by displaying any help topic - /// specified by the HelpKeyword field of the current notification data - /// record. - /// Does nothing if the help keyword is the empty string. - /// - procedure HelpBtnClickHandler(Sender: TObject); - - /// Closes (hides) the window when the timer's event fires. - /// - /// The event is ignored if the window is not fully open. - /// - procedure TimerTickHandler(Sender: TObject); - - strict protected - - /// Sets this control's parent to the given control. Ensures that - /// this window is aligned at the bottom right of the parent window. - /// - procedure SetParent(AParent: TWinControl); override; - - /// Paints window background and glyph. - procedure Paint; override; - - public - - /// Creates and initialises a new component instance. - constructor Create(AOwner: TComponent); override; - - /// Destroys the current component instance. - destructor Destroy; override; - - /// Sizes and locates the window. ALeft is ignored and is always - /// set so that the window appears at the right edge of the parent window. - /// - procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer); override; - - /// Opens (displays) the window with the content and tasks - /// specified by the given notification data records. The window slides - /// on-screen. - /// - /// Does nothing unless State = nwsClosed. - /// State = nwsOpening while window is sliding and changes to - /// nwsOpen when it is fully hidden. - /// The window display lock is closed iff State = mwsClose when the - /// method is called. - /// - procedure SlideIn(const N: TNotificationData); - - /// Closes (hides) the window by sliding it off-screen. - /// - /// Does nothing unless State = nwsOpen. - /// State = nwsClosing while window is sliding and changes to - /// nwsClosed when it is fully hidden. - /// Posts a WM_CONCEALED message when the window is fully closed. - /// - /// - procedure SlideOut; - - /// Event object used as lock when window is displayed. - /// - /// This lock is used by calling code to prevent them from - /// trying to display the window until it is closed. - property DisplayLock: TSimpleEvent read fDisplayLock; - - /// Current state of window. - /// State is always nwsClosed when the window is created, i.e. it - /// is hidden by default. - property State: TState read fState; - - /// Amount of time, in milliseconds, the notification is to be - /// displayed before being hidden automatically. - /// - /// Defaults to 10000 (10 seconds). - /// If DisplayTime is zero the notification is not hidden - /// automatically. - /// - property DisplayTime: Cardinal read GetDisplayTime write SetDisplayTime; - end; - - -implementation - - -uses - // Delphi - GraphUtil, Math, - // Project - UCtrlArranger, UFontHelper, UGraphicUtils, UHelpMgr, UUtils; - - -{ TNotificationWindow } - -procedure TNotificationWindow.CloseBtnClickHandler(Sender: TObject); -begin - SlideOut; // does nothing if State <> nwsOpen -end; - -constructor TNotificationWindow.Create(AOwner: TComponent); -resourcestring - sDefaultTitle = 'Notification'; -begin - inherited Create(AOwner); - fContentLblList := TObjectList.Create(True); - Width := 300; - Height := 150; - ControlStyle := ControlStyle - [csOpaque]; - Anchors := [akBottom, akRight]; - Color := $FFFFFF; - Visible := False; - fState := nwsClosed; - fLightBulb := TBitmap.Create; - fLightBulb.LoadFromResourceName(HInstance, 'NOTIFICATION'); - CreateCtrls; - fTitleLbl.Caption := sDefaultTitle; - fHideTimer.Interval := 10000; - fDisplayLock := TSimpleEvent.Create; - fDisplayLock.SetEvent; - UpdateWindow; -end; - -procedure TNotificationWindow.CreateContentLabels( - const AContent: TArray); -var - Para: string; - Lbl: TLabel; -begin - fContentLblList.Clear; // frees any existing labels - for Para in AContent do - begin - Lbl := TLabel.Create(nil); - Lbl.Parent := Self; - TFontHelper.SetDefaultFont(Lbl.Font); - Lbl.Caption := Para; - Lbl.AutoSize := False; - Lbl.WordWrap := True; - fContentLblList.Add(Lbl); - end; -end; - -procedure TNotificationWindow.CreateCtrls; -resourcestring - sDontShowText = 'Don''t show this notification again.'; - sHelpBtnHint = 'Help'; - sCloseBtnHint = 'Hide this window'; - - // Creates a new speed button with a character from the Marlett font as - // caption and with given OnClick event handler. - function CreateSymbolButton(Symbol: Char; ClickHandler: TNotifyEvent; - const Hint: string): - TSpeedButton; - begin - Result := TSpeedButton.Create(Self); - Result.Parent := Self; - Result.Width := 16; - Result.Height := 20; - Result.Flat := True; - Result.OnClick := ClickHandler; - Result.Font.Name := 'Marlett'; - Result.Font.Size := 9; - Result.Caption := Symbol; - Result.ShowHint := Hint <> ''; - Result.Hint := Hint; - end; - -begin - // create title label - fTitleLbl := TLabel.Create(Self); - fTitleLbl.Parent := Self; - fTitleLbl.Font.Style := [fsBold]; - fTitleLbl.Font.Size := fTitleLbl.Font.Size + 1; - fTitleLbl.AutoSize := False; - fTitleLbl.EllipsisPosition := epEndEllipsis; - TFontHelper.SetDefaultBaseFont(fTitleLbl.Font); - - // create task button with default size (width changed to suit text) - fTaskBtn := TButton.Create(Self); - fTaskBtn.Parent := Self; - fTaskBtn.Visible := False; - fTaskBtn.Enabled := False; // enabled only when window displayed - fTaskBtn.OnClick := TaskBtnClick; - - // create close button with down arrow symbol - fCloseBtn := CreateSymbolButton( - CloseButtonChar, CloseBtnClickHandler, sCloseBtnHint - ); - - // create help button with "?" symbol - fHelpBtn := CreateSymbolButton( - HelpButtonChar, HelpBtnClickHandler, sHelpBtnHint - ); - fHelpBtn.Visible := False; - - // create timer used to automatically hide window - fHideTimer := TTimer.Create(Self); - fHideTimer.Enabled := False; - fHideTimer.OnTimer := TimerTickHandler; -end; - -destructor TNotificationWindow.Destroy; -begin - fLightBulb.Free; - fContentLblList.Free; - fDisplayLock.SetEvent; - fDisplayLock.Free; - inherited; -end; - -function TNotificationWindow.GetDisplayTime: Cardinal; -begin - Result := fHideTimer.Interval; -end; - -procedure TNotificationWindow.HelpBtnClickHandler(Sender: TObject); -begin - if fNotificationData.HelpKeyword <> '' then - HelpMgr.ShowHelp(fNotificationData.HelpKeyword); -end; - -procedure TNotificationWindow.Paint; -begin - GradientFillCanvas(Canvas, GradColour1, GradColour2, ClientRect, gdVertical); - Canvas.Pen.Color := BorderColour; - Canvas.Brush.Style := bsClear; - Canvas.Rectangle(ClientRect); - Canvas.BrushCopy( - fGlyphBounds, - fLightBulb, - TRectEx.Create(0, 0, fLightBulb.Width, fLightBulb.Height), - clFuchsia - ); -end; - -procedure TNotificationWindow.SetBounds(ALeft, ATop, AWidth, AHeight: Integer); -var - ParentBounds: TRectEx; -begin - if Assigned(Parent) then - begin - // align at bottom right of parent - ParentBounds := Parent.ClientRect; - ALeft := ParentBounds.Right - AWidth - 6; - end; - inherited SetBounds(ALeft, ATop, AWidth, AHeight); -end; - -procedure TNotificationWindow.SetDisplayTime(const MS: Cardinal); -begin - fHideTimer.Interval := MS; -end; - -procedure TNotificationWindow.SetParent(AParent: TWinControl); -begin - inherited; - SetBounds(0, 0, Width, Height); -end; - -procedure TNotificationWindow.SlideIn(const N: TNotificationData); -var - ClosedTop: Integer; - OpenedTop: Integer; - Range: Integer; - Scale: Double; -begin - if State <> nwsClosed then - Exit; - fNotificationData := N; - UpdateWindow; - fDisplayLock.ResetEvent; - fState := nwsOpening; - ClosedTop := Parent.ClientRect.Bottom + 1; - OpenedTop := Parent.ClientRect.Bottom - Height; - Range := ClosedTop - OpenedTop; - Top := ClosedTop; - Visible := True; - Scale := SlideStep; - while Scale < 1.0 - SlideStep do - begin - Top := ClosedTop - Round(Range * Scale); - Scale := Scale + SlideStep; - Pause(2); - end; - Top := OpenedTop; - fState := nwsOpen; - fTaskBtn.Enabled := True; - fHideTimer.Enabled := DisplayTime > 0; -end; - -procedure TNotificationWindow.SlideOut; -var - ClosedTop: Integer; - OpenedTop: Integer; - Range: Integer; - Scale: Double; -begin - if State <> nwsOpen then - Exit; - fHideTimer.Enabled := False; - fTaskBtn.Enabled := False; - fState := nwsClosing; - ClosedTop := Parent.ClientRect.Bottom + 1; - OpenedTop := Parent.ClientRect.Bottom - Height; - Range := ClosedTop - OpenedTop; - Top := OpenedTop; - Scale := SlideStep; - while Scale < 1.0 - SlideStep do - begin - Top := OpenedTop + Round(Range * Scale); - Scale := Scale + SlideStep; - Pause(2); - end; - Top := ClosedTop; - Visible := False; - fState := nwsClosed; - PostMessage(Handle, WM_CONCEALED, 0, 0); -end; - -procedure TNotificationWindow.TaskBtnClick(Sender: TObject); -begin - if Assigned(fNotificationData.TaskCallback) then - fNotificationData.TaskCallback(); -end; - -procedure TNotificationWindow.TimerTickHandler(Sender: TObject); -begin - fHideTimer.Enabled := False; - SlideOut; // does nothing if State <> nwsOpen -end; - -procedure TNotificationWindow.UpdateWindow; -const - MarginLR = 8; - MarginTB = 8; - CtrlVSpacing = 8; - GlyphSize = 24; - TextLeftOffset = MarginLR + GlyphSize + 8; -var - Lbl: TLabel; - NextTop: Integer; - ParaTop: Integer; - TextWidth: Integer; - TopRowHeight: Integer; -begin - // Set control content and state for current notification data - fTitleLbl.Caption := fNotificationData.Title; - CreateContentLabels(fNotificationData.Content); - if Assigned(fNotificationData.TaskCallback) then - begin - fTaskBtn.Caption := fNotificationData.TaskPrompt; - fTaskBtn.Visible := True; - end - else - begin - fTaskBtn.Caption := ''; - fTaskBtn.Visible := False; - end; - fHelpBtn.Visible := fNotificationData.HelpKeyword <> ''; - - // Find height of "top row" of controls - title and close button plus help - // button if visible: they are all centred in this row. - TCtrlArranger.SetLabelHeight(fTitleLbl); - TopRowHeight := Max(fTitleLbl.Height, fCloseBtn.Height); - if fHelpBtn.Visible then - TopRowHeight := Max(TopRowHeight, fHelpBtn.Height); - - // Locate left hand "light bulb" glyph - fGlyphBounds := TRectEx.CreateBounds( - MarginLR, MarginTB, GlyphSize, GlyphSize - ); - - // Set "close" and "help" button locations - if fHelpBtn.Visible then - begin - fHelpBtn.Left := Width - MarginLR - fHelpBtn.Width; - fHelpBtn.Top := MarginTB + (TopRowHeight - fHelpBtn.Height) div 2; - fCloseBtn.Left := fHelpBtn.Left - fCloseBtn.Width; - end - else - fCloseBtn.Left := Width - MarginLR - fCloseBtn.Width; - fCloseBtn.Top := MarginTB + (TopRowHeight - fCloseBtn.Height) div 2; - - // Set title size and location - fTitleLbl.Width := fCloseBtn.Left - 4 - TextLeftOffset; - fTitleLbl.Left := TextLeftOffset; - fTitleLbl.Top := MarginTB + (TopRowHeight - fTitleLbl.Height) div 2; - - // Get width available for text / check box in main body - TextWidth := Width - MarginLR - TextLeftOffset; - - // Create labels to display content paragraphs and locate them one below the - // other. - ParaTop := MarginTB + TopRowHeight + CtrlVSpacing; - for Lbl in fContentLblList do - begin - Lbl.Top := ParaTop; - Lbl.Left := TextLeftOffset; - Lbl.Width := TextWidth; - TCtrlArranger.SetLabelHeight(Lbl); - Inc(ParaTop, Lbl.Height + ParaSpacing); - end; - NextTop := ParaTop - ParaSpacing + CtrlVSpacing; - - // Set location and size of task button - if fTaskBtn.Visible then - begin - fTaskBtn.Left := TextLeftOffset; - fTaskBtn.Width := StringExtent(fTaskBtn.Caption, fTaskBtn.Font).cx - + 28; - fTaskBtn.Top := NextTop; - NextTop := TCtrlArranger.BottomOf(fTaskBtn, CtrlVSpacing); - end; - - // Set required window height - Height := NextTop - CtrlVSpacing + MarginTB; -end; - -procedure TNotificationWindow.WMConcealed(var Msg: TMessage); -begin - fDisplayLock.SetEvent; -end; - -end. - diff --git a/Src/Res/CSS/detail.css b/Src/Res/CSS/detail.css index 2232cf903..bea2dc732 100644 --- a/Src/Res/CSS/detail.css +++ b/Src/Res/CSS/detail.css @@ -1,12 +1,9 @@ /* * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2005-2013, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). * * Cascading style sheet used by HTML displayed in detail panes. */ @@ -60,6 +57,41 @@ p { margin: 4px 0 0 0; } +ul, ol { + margin: 4px 0 0 0; + padding: 0; + list-style-position: outside; +} + +ul { + list-style-type: disc; + padding-left: 24px; +} + +ol { + list-style-type: decimal; + padding-left: 32px; +} + +li ol, +li ul { + margin-top: 0; +} + +li { + padding: 0; + margin: 0; +} + +ul li { + padding-left: 8px; +} + +ul li ol li { + padding-left: 0px; +} + + pre { margin: 4px 0; padding: 4px; @@ -110,14 +142,6 @@ pre { background-color: #FCD9F3; } -#auto-updates .caption { - background-color: #FFDFAF; -} - -#donate .caption { - background-color: #FFF1A4; -} - #help .caption { background-color: #D3FFB6; } diff --git a/Src/Res/CSS/easteregg.css b/Src/Res/CSS/easteregg.css index f6959a61f..0a150ee98 100644 --- a/Src/Res/CSS/easteregg.css +++ b/Src/Res/CSS/easteregg.css @@ -1,12 +1,9 @@ /* * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can - * obtain one at http://mozilla.org/MPL/2.0/ + * obtain one at https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2009-2012, Peter Johnson (www.delphidabbler.com). - * - * $Rev$ - * $Date$ + * Copyright (C) 2009-2021, Peter Johnson (gravatar.com/delphidabbler). * * Cascading style sheet used to format HTML displayed in program's easter egg. */ diff --git a/Src/Res/HTML/dlg-about-database-tplt.html b/Src/Res/HTML/dlg-about-database-tplt.html index 1d72230ad..ccc4f9797 100644 --- a/Src/Res/HTML/dlg-about-database-tplt.html +++ b/Src/Res/HTML/dlg-about-database-tplt.html @@ -2,22 +2,19 @@ + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> - + dlg-about-database-tplt.html @@ -48,44 +45,67 @@ The DelphiDabbler Code Snippets Database is an online resource containing numerous - fragments of useful Object Pascal code. + href="https://github.com/delphidabbler/code-snippets" + >Code Snippets Database is a resource containing numerous fragments of useful Object Pascal code. You are using version <%Version%>.

          -

          - The source code in the database is copyright © <%Year%> by the - database contributors (see below). It is made available under the terms of - the MIT License. The code is - used entirely at your own risk. -

          +
          -

          - Credits -

          +
          -

          - The following people have contributed code to the database, or have helped - to test it: -

          +

          + The source code in the database is copyright © <%CopyrightYear%> by <%CopyrightHolders%>. It is made available under the terms of the <%DBLicense%>. +

          + +

          + No copyright or licensing information is available. +

          + +

          + The code is used entirely at your own risk. +

          + +

          + Credits +

          + +

          + The following people have contributed code to the database, or have helped to test it: +

          + +
          +
          + Contributors +
          +
          + <%ContribList%> +
          +
          + Testers +
          +
          + <%TesterList%> +
          +
          -
          -
          - Contributors -
          -
          - <%ContribList%> -
          -
          - Testers -
          -
          - <%TesterList%>
          + +

          + Credits, License and Copyright information are not available. The main database may be corrupt. Please use the Database | Install or Update DelphiDabbler Snippets Database menu option to display the Install or Update DelphiDabbler Snippets Database dialogue box and follow the instructions there to get the latest version of the database. +

          + +
          + +
          + +

          + The database is not installed. +

          + +

          + You can install it from the Database | Install or Update DelphiDabbler Snippets Database menu option. Learn more. +

          +
          diff --git a/Src/Res/HTML/dlg-about-head-tplt.html b/Src/Res/HTML/dlg-about-head-tplt.html index 1403b5779..90c0e127d 100644 --- a/Src/Res/HTML/dlg-about-head-tplt.html +++ b/Src/Res/HTML/dlg-about-head-tplt.html @@ -2,22 +2,19 @@ + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> - + dlg-about-head-tplt diff --git a/Src/Res/HTML/dlg-about-program-tplt.html b/Src/Res/HTML/dlg-about-program-tplt.html index 19a9aef2a..a337e8b80 100644 --- a/Src/Res/HTML/dlg-about-program-tplt.html +++ b/Src/Res/HTML/dlg-about-program-tplt.html @@ -2,29 +2,26 @@ + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> - + dlg-about-program-tplt - - - - -

          - Before proceeding please confirm your agreement to the following - statements by ticking the check box below. If you can't agree with the - statements please cancel your submission. -

          - -
            -
          1. - I have the right to donate this code for publication in the Code - Snippets Database. More information -
          2. -
          3. - I agree that the code should be released under the terms of the MIT license. -
          4. -
          - - - - diff --git a/Src/Res/HTML/dlg-codesubmit-submit.html b/Src/Res/HTML/dlg-codesubmit-submit.html deleted file mode 100644 index 945215933..000000000 --- a/Src/Res/HTML/dlg-codesubmit-submit.html +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - dlg-codesubmit-submit.html - - - - -

          - Ready to submit the code. -

          - -

          - The data that will be sent contains the selected snippet(s), along with - other data you provided. -

          - -

          - If you want to see the data before sending it, click the Preview Data - button. -

          - -

          - To proceed please ensure you are connected to the internet then click the - Submit button. -

          - - - - diff --git a/Src/Res/HTML/dlg-comperror-tplt.html b/Src/Res/HTML/dlg-comperror-tplt.html index 61e1a41c1..46980958a 100644 --- a/Src/Res/HTML/dlg-comperror-tplt.html +++ b/Src/Res/HTML/dlg-comperror-tplt.html @@ -2,22 +2,19 @@ + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> - + dlg-comperror-tplt diff --git a/Src/Res/HTML/dlg-dbdelete.html b/Src/Res/HTML/dlg-dbdelete.html new file mode 100644 index 000000000..b21cfb4c7 --- /dev/null +++ b/Src/Res/HTML/dlg-dbdelete.html @@ -0,0 +1,48 @@ + + + + + + + + + + dlg-dbdelete.html + + + + +

          + ARE YOU SURE? +

          + +

          + Before going any further you are strongly advised to take a backup of your snippets database. Use the Database | Backup User Database menu option to do this. +

          + +

          + This action cannot be undone: you will loose all your user-defined snippets. +

          + +

          + To confirm enter DELETE MY SNIPPETS (in capital letters) in the box below, then click OK. +

          + +

          + There will be no further chances to change your mind. +

          + + + + diff --git a/Src/Res/HTML/dlg-dbupdate-finish.html b/Src/Res/HTML/dlg-dbupdate-finish.html new file mode 100644 index 000000000..14795acd8 --- /dev/null +++ b/Src/Res/HTML/dlg-dbupdate-finish.html @@ -0,0 +1,40 @@ + + + + + + + + + + dlg-dbupdate-finish.html + + + + +

          + The database was installed successfully. +

          + +

          + Immediately this dialogue box is closed the database will be reloaded and the newly installed Code Snippets database will replace any earlier version. +

          + +

          + Click the Finish button to close the dialogue box. +

          + + + + diff --git a/Src/Res/HTML/dlg-dbupdate-intro-tplt.html b/Src/Res/HTML/dlg-dbupdate-intro-tplt.html new file mode 100644 index 000000000..e20e9c423 --- /dev/null +++ b/Src/Res/HTML/dlg-dbupdate-intro-tplt.html @@ -0,0 +1,64 @@ + + + + + + + + + + dlg-dbupdate-intro-tplt.html + + + + +

          + Warning: you are strongly advised to back up your user database before going ahead with this installation. To do this cancel this dialogue box then use the Database | Backup User Database menu option to create a backup. When you have done that please come back here. +

          + +

          + Before you can install a new version of the DelphiDabbler Code Snippets Database you need to download it from GitHub. +

          + +

          + First go to <%CSDBReleaseURL%> and find the latest version 2 release of the database. Look for a zip file whose name has the form csdb-v2.x.x-data.zip where 2.x.x is the database version number. +

          + +

          + Download the file and unzip it into a folder on your computer. Make a note of the folder's location - it will be needed in the next step. +

          + +

          + Note: Your chosen download folder: +

          + +
            +
          • + must be empty before downloading the database. +
          • +
          • + must not be a sub-directory of CodeSnip's application or user data directories. +
          • +
          + +

          + Once the file has been downloaded and unzipped, click the Next button. +

          + + + + diff --git a/Src/Res/HTML/dlg-dbupdate-load.html b/Src/Res/HTML/dlg-dbupdate-load.html new file mode 100644 index 000000000..fd59050c7 --- /dev/null +++ b/Src/Res/HTML/dlg-dbupdate-load.html @@ -0,0 +1,40 @@ + + + + + + + + + + dlg-dbupdate-load.html + + + + +

          + Ready to install the new version of the database. +

          + +

          + Click the Load button to begin the installation. +

          + +

          + Note: Once you have clicked the Load button you cannot cancel the installation process. +

          + + + + diff --git a/Src/Res/HTML/dlg-donate.html b/Src/Res/HTML/dlg-donate.html deleted file mode 100644 index 6afc0f589..000000000 --- a/Src/Res/HTML/dlg-donate.html +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - - dlg-donate.html - - - - -
          - -
          - -

          - CodeSnip is free to use and there is no requirement to pay anything for - it. You get a fully working version of the program whether you make a - donation or not. -

          - -

          - Having said that, it takes time and money to maintain both - CodeSnip and the online database. So if you wish to make a contribution it - will be most welcome. -

          - -

          - Payment, in pounds sterling, can be made by - PayPal™. -

          - -

          - Make sure you are connected to the internet then click the - Make Donation button. This will display the - DelphiDabbler PayPal™ donation page in your web browser. -

          - -

          - Many thanks. -

          - - - - diff --git a/Src/Res/HTML/dlg-easter-egg.html b/Src/Res/HTML/dlg-easter-egg.html index e7ffd2857..ad02ea31c 100644 --- a/Src/Res/HTML/dlg-easter-egg.html +++ b/Src/Res/HTML/dlg-easter-egg.html @@ -2,23 +2,20 @@ + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> - + @@ -27,7 +24,7 @@ <link rel="stylesheet" href="easteregg.css" /> - <script type="text/javascript" src="jquery-1.8.0.min.js"></script> + <script type="text/javascript" src="jquery-1.12.4.min.js"></script> <script type="text/javascript" src="jquery.cycle.lite.js"></script> <script type="text/javascript" src="easteregg.js"></script> @@ -95,13 +92,13 @@ <h1> <div id="slide-5"> <a class="external-link" - href="http://www.flickr.com/photos/delphidabbler/sets/72157631306758128/" + href="https://www.flickr.com/photos/delphidabbler/sets/72157631306758128/" ><img src="dogs-4.jpg" /></a> <div class="caption"> More pics on <a class="external-link" - href="http://www.flickr.com/photos/delphidabbler/sets/72157631306758128/" + href="https://www.flickr.com/photos/delphidabbler/sets/72157631306758128/" >Flickr</a>... </div> </div> diff --git a/Src/Res/HTML/dlg-register-compilers.html b/Src/Res/HTML/dlg-register-compilers.html new file mode 100644 index 000000000..773bf8734 --- /dev/null +++ b/Src/Res/HTML/dlg-register-compilers.html @@ -0,0 +1,59 @@ +<?xml version="1.0"?> + +<!DOCTYPE html + PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> + +<!-- + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at http://mozilla.org/MPL/2.0/ + * + * Copyright (C) 2022, Peter Johnson (gravatar.com/delphidabbler). + * + * Instructions diplayed on Register Compilers dialogue box. +--> + +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> + + <head> + <title>Register Compilers Configration + + + +

          + If you don't wish to receive notifications like this again you have two + options: +

          +
            +
          1. +

            + Turn off all checks for new compiler installations. + Do this by ticking the Don't show this again check box below. +

            +

            + If you change your mind later you can re-enable the checks from the + Configure Compilers dialogue box. +

            +
          2. +
          3. +

            + Turn notifications on or off on a per-compiler basis. + You need to do this after you have closed this dialogue box, again by + using the Configure Compilers dialogue box. +

            +
          4. +
          +

          + For information about how to configure automatic detection of Delphi + compilers see the Register Compilers with CodeSnip help topic. +

          +

          + Note: only Delphi compilers can be automatically + registered with CodeSnip: this doesn't work with Free Pascal. +

          + + diff --git a/Src/Res/HTML/dlg-rssnews.html b/Src/Res/HTML/dlg-rssnews.html deleted file mode 100644 index 186032eda..000000000 --- a/Src/Res/HTML/dlg-rssnews.html +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - dlg-rssnews - - - - - - - - - diff --git a/Src/Res/HTML/dlg-swag-import-intro-tplt.html b/Src/Res/HTML/dlg-swag-import-intro-tplt.html new file mode 100644 index 000000000..e8b2f4967 --- /dev/null +++ b/Src/Res/HTML/dlg-swag-import-intro-tplt.html @@ -0,0 +1,70 @@ + + + + + + + + + + dlg-swag-import-intro.html + + + + +

          + This wizard enables you to import "packets" (SWAG's equivalent of CodeSnip's snippets) from the SWAG database into your local user database. +

          + +
          +

          + You must have a copy of a specially prepared version of SWAG on your local system before continuing. If you haven't already got it then you need to download it from the delphidabbler/swag repository on GitHub. +

          +

          + Go to <%SWAGReleaseURL%> and find the latest supported release†. The file name will be in the form dd-swag-vX.Y.Z where X.Y.Z is the version number. Download the file and unzip it into a folder on your computer. Make a note of the folder's location - it will be needed in the next step. +

          +

          + † Currently CodeSnip supports SWAG versions <%SupportedSWAGVersions%>. +

          +
          + +

          + Warning: Although importing snippets should be safe, you may wish to take a backup of your user database before going any further. You can do this from the Database | Backup User Database menu option. +

          + +

          + On the next page of the wizard you will be asked for the location of the folder containing the SWAG database. Note that you will need to specify the swag sub-directory of the download. CodeSnip will then open the database and display all SWAG's categories on the following page. From there you can browse and display the packets in each category before selecting those you want to import. You get a chance to review these before importing. +

          + +

          + All packets are imported as freeform snippets. +

          + +

          + CodeSnip does not keep a track of packets imported from SWAG – it treats them just like any other user defined snippet. This means it is possible to import the same packet more than once. It's up to you to keep track of this. +

          + +

          + Note: It can take quite some time to import packets from SWAG. You are advised not to import more than about 20 packets in one go. If you want to import a lot of packets you should run this wizard more than once. +

          + + + + diff --git a/Src/Res/HTML/dlg-swag-import-intro.html b/Src/Res/HTML/dlg-swag-import-intro.html deleted file mode 100644 index 0bb082cdf..000000000 --- a/Src/Res/HTML/dlg-swag-import-intro.html +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - dlg-swag-import-intro.html - - - - -

          - This wizard enables you to import one or more snippets from the online SWAG database into your local user database. -

          - -

          - When you click the "Next" button, CodeSnip will - download a list of categories from the SWAG database. Double-click on a - category to see a list of the snippets it contains. You can then select - the snippet(s) you want to import using the check boxes that appear next - to each snippet. Double-clicking a snippet lets you view it. -

          - -

          - To help you to decide what to import you can - view the SWAG database in your browser. Make a note of the names of - the snippet(s) you are interested in, along with their categories. That - will help you find them in this wizard. -

          - -

          - All snippets are imported as freeform. -

          - -

          - CodeSnip does not keep a track of snippets imported from the SWAG - database – it treats them just like any other user defined snippet. - This means that you will be able to import the same snippet more than once - and it's up to you to keep track of this. -

          - -

          - NOTE: - It can take quite some time to import SWAG snippets. You are advised not - to import more than about 20 snippets in one go. If you want to import a - lot of snippets you should run this wizard more than once. -

          - - - - diff --git a/Src/Res/HTML/dlg-swag-import-outro-tplt.html b/Src/Res/HTML/dlg-swag-import-outro-tplt.html index 06128126e..a827ab94c 100644 --- a/Src/Res/HTML/dlg-swag-import-outro-tplt.html +++ b/Src/Res/HTML/dlg-swag-import-outro-tplt.html @@ -2,22 +2,19 @@ + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> - + dlg-swag-import-outro-tplt.html @@ -26,12 +23,12 @@

          - All the selected snippets have now been imported into the - <%SWAGCategory%> category. + All the selected SWAG packets have now been converted to CodeSnip snippets + and imported into the <%SWAGCategory%> category.

          - The imported snippets can be edited as required. + The snippets can be edited as required.

          diff --git a/Src/Res/HTML/dlg-whatsnew.html b/Src/Res/HTML/dlg-whatsnew.html new file mode 100644 index 000000000..3667ce009 --- /dev/null +++ b/Src/Res/HTML/dlg-whatsnew.html @@ -0,0 +1,78 @@ + + + + + + + + + + What's New + + + +

          + You seem to be updating from a version of CodeSnip prior to v4.16.0. +

          +

          + There are several important differences from your old version of CodeSnip that you need to know about. +

          +

          + Please read carefully because this dialogue box will not appear again. +

          +

          + Before v4.16.0, CodeSnip depended for some of its functionality on web services that have now been closed down. +

          +

          + The main purpose of CodeSnip v4.16.0 was to remove the program's dependency on the defunct web services. This was done by removing some features and providing alternatives to others. +

          +

          + The main changes are: +

          +
            +
          • + You can no longer check for new releases of CodeSnip or the main DelphiDabbler Code Snippets Database from within the program. The automatic update checker has been removed along with the menu option to check for program updates manually. +
          • +
          • + The DelphiDabbler Code Snippets database can no longer be updated directly from the web. You now have to download the database youself and install it from disk. + More info. +
          • +
          • + Similarly the SWAG collection must be downloaded manually before you can import snippets from it. More Info. +
          • +
          • + CodeSnip can no longer be registered. +
          • +
          • + You can no longer submit snippets for inclusion in the DelphiDabbler Code Snippets Database. +
          • +
          • + The news feed has gone away. News will now be posted to the CodeSnip blog + DelphiDabbler blog. You can display the blog in your web browser from the Help menu. +
          • +
          +

          + There are other changes to this version of CodeSnip that you can read about in the program's change log. However the ones above are the major departures from how CodeSnip used to work. +

          + + + diff --git a/Src/Res/HTML/frm-emailprivacy.html b/Src/Res/HTML/frm-emailprivacy.html deleted file mode 100644 index 3fa8f8e56..000000000 --- a/Src/Res/HTML/frm-emailprivacy.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - frm-emailprivacy.html - - - - -
          - Your email address will not be abused. See the - privacy statement for details. -
          - - - - diff --git a/Src/Res/HTML/info-basic-tplt.html b/Src/Res/HTML/info-basic-tplt.html index 50d99b327..a8bfc2f73 100644 --- a/Src/Res/HTML/info-basic-tplt.html +++ b/Src/Res/HTML/info-basic-tplt.html @@ -2,22 +2,19 @@ + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> - + Basic Detail Page diff --git a/Src/Res/HTML/info-empty-selection-tplt.html b/Src/Res/HTML/info-empty-selection-tplt.html index 48d1e2a18..dd2b9fdd3 100644 --- a/Src/Res/HTML/info-empty-selection-tplt.html +++ b/Src/Res/HTML/info-empty-selection-tplt.html @@ -2,22 +2,19 @@ + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> - + Empty Snippet List @@ -37,5 +34,5 @@

          - + \ No newline at end of file diff --git a/Src/Res/HTML/info-snippet-list-tplt.html b/Src/Res/HTML/info-snippet-list-tplt.html index a6399419a..e762589f9 100644 --- a/Src/Res/HTML/info-snippet-list-tplt.html +++ b/Src/Res/HTML/info-snippet-list-tplt.html @@ -2,23 +2,20 @@ + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> - + Snippet List @@ -42,7 +39,7 @@

          <%SnippetList%>

          - + - + diff --git a/Src/Res/HTML/info-snippet-tplt.html b/Src/Res/HTML/info-snippet-tplt.html index 8dbc29caa..a362b8bdc 100644 --- a/Src/Res/HTML/info-snippet-tplt.html +++ b/Src/Res/HTML/info-snippet-tplt.html @@ -2,23 +2,20 @@ + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> - + detail-info @@ -32,7 +29,7 @@ - +
          @@ -80,5 +77,5 @@

          - + \ No newline at end of file diff --git a/Src/Res/HTML/welcome-tplt.html b/Src/Res/HTML/welcome-tplt.html index f4418f8d0..55a23c116 100644 --- a/Src/Res/HTML/welcome-tplt.html +++ b/Src/Res/HTML/welcome-tplt.html @@ -2,22 +2,19 @@ + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> - + Welcome @@ -67,35 +64,32 @@

          - Code Snippets database + DelphiDabbler Code Snippets database
          - There are <%MainDBCount%> snippets in the Code Snippets - database. -
          -
          - Check for updates + There are <%MainDBCount%> snippets in the DelphiDabbler + Code Snippets database.
          The DelphiDabbler Code Snippets Database has not been installed. + Learn more
          Download it now + >Install it now
          @@ -148,33 +142,6 @@

          -
          -
          - Update Checks -
          -
          - CodeSnip can check automatically for program and Code - Snippets database updates. Your current settings are: -
            -
          • - Program updates: - <%ProgramAutoCheckFrequency%> -
          • -
          • - Code Snippets database updates: - <%DatabaseAutoCheckFrequency%> -
          • -
          -
          - -
          -
          - - - + \ No newline at end of file diff --git a/Src/Res/Img/AltBranding/CodeSnip.ico b/Src/Res/Img/AltBranding/CodeSnip.ico new file mode 100644 index 000000000..a5a72e8e9 Binary files /dev/null and b/Src/Res/Img/AltBranding/CodeSnip.ico differ diff --git a/Src/Res/Img/AltBranding/README.txt b/Src/Res/Img/AltBranding/README.txt new file mode 100644 index 000000000..81c75b353 --- /dev/null +++ b/Src/Res/Img/AltBranding/README.txt @@ -0,0 +1,48 @@ +About the Src/Res/Img/AltBranding directory +=========================================== + +If you are creating a fork of CodeSnip, or a program based on it, then +the CodeSnip license does not permit you to use the images in the +`Src/Res/Img/Branding` directory. But, since CodeSnip expects to find +the files `CodeSnip.ico`, `icon.gif` and `Splash.gif` in that directory, +simply deleting the images will cause CodeSnip to fail to build. + +Alternative versions of the above images are provided in the +`Src/Res/Img/AltBranding` directory. These files are Public Domain, and +therefore can be used and modified in derived programs. + +Simply copy the image files from the `Src/Res/Img/AltBranding` directory +into `Src/Res/Img/Branding`, overwriting the existing files. CodeSnip +can then be built successfully without using the prohibited files. + +It is not expected that the images will be used as-is. They are provided +only as placeholders to enable CodeSnip to build successfully. +Evetually you may want to edit the images to meet your needs. +Alternatively, the CodeSnip source could be changed so that the images +are not required at all. + +Because the files are Public Domain, you may relicense any modified +version as you wish. + + +Information about the files +--------------------------- + +`CodeSnip.ico` + + This is the program's main icon. + + It contains images at four different resolutions: 16×16, 32×32, 48×48 + and 256×256 pixels. + +`icon.gif` + + A 32×32 pixel GIF file that is displayed in the About box. + +`Splash.gif` + + A 325×155 pixel GIF image that is displayed as a splash screen while + the program is loading. + + The program's version number is overlaid in the bottom quarter of the + image. diff --git a/Src/Res/Img/AltBranding/Splash.gif b/Src/Res/Img/AltBranding/Splash.gif new file mode 100644 index 000000000..ea67c501c Binary files /dev/null and b/Src/Res/Img/AltBranding/Splash.gif differ diff --git a/Src/Res/Img/AltBranding/icon.gif b/Src/Res/Img/AltBranding/icon.gif new file mode 100644 index 000000000..47ce6d354 Binary files /dev/null and b/Src/Res/Img/AltBranding/icon.gif differ diff --git a/Src/Res/Img/Branding/LICENSE b/Src/Res/Img/Branding/LICENSE deleted file mode 100644 index 58a2c21ed..000000000 --- a/Src/Res/Img/Branding/LICENSE +++ /dev/null @@ -1,8 +0,0 @@ -All image files in the Src/Res/Img/Branding directory are copyright (c) 2012 by -Peter D Johnson, http://www.delphidabbler.com/. - -The files may not be copied or modified and may not be used in the distribution -of derived programs without explicit permission of the copyright holder. - -This restriction does not apply to modifications of CodeSnip that are for -personal use that are not distributed publicly. \ No newline at end of file diff --git a/Src/Res/Img/Branding/README.txt b/Src/Res/Img/Branding/README.txt new file mode 100644 index 000000000..080124e97 --- /dev/null +++ b/Src/Res/Img/Branding/README.txt @@ -0,0 +1,19 @@ +About the Src/Res/Img/Branding directory +======================================== + +If you are creating a fork of CodeSnip, or a program based on it, the +CodeSnip license does not permit you to use the images in this directory +in such a project. + +To assist with this problem, alternative versions of the above images +are provided in the `Src/Res/Img/AltBranding` directory. + +For more information see: + +* `Src/Res/Img/AltBranding/README.txt` + +* `Build.html`, specifically the "Get the Source Code" sub-section and + the "Conditions For Release of Modified Code" section. + +* `Docs/License.html` for full details of the license restrictions + applying to files in this directory. diff --git a/Src/Res/Img/LICENSE b/Src/Res/Img/LICENSE deleted file mode 100644 index f448f15a7..000000000 --- a/Src/Res/Img/LICENSE +++ /dev/null @@ -1,16 +0,0 @@ -All image files in the Src/Res/Img and Src/Res/Img/Egg directories are made -available under the Creative Commons Attribution Share Alike 3.0 License -(http://creativecommons.org/licenses/by-sa/3.0/). - -A full copy of this license is available in Docs/License.html#CC-BY-SA-3.0. - -Some images have been derived or copied from files covered by the following -licenses, all of which permit relicensing under the Creative Commons Attribution -Share Alike 3.0 License: - - * Creative Commons Attribution 2.5 License - * Creative Commons Attribution Share Alike 3.0 License - * MIT license - -Files in Src/Res/Img/Branding are licensed differently: See -Src/Res/Img/Branding/LICENSE for details. \ No newline at end of file diff --git a/Src/Res/Img/pound-sign.png b/Src/Res/Img/pound-sign.png deleted file mode 100644 index c920f39b1..000000000 Binary files a/Src/Res/Img/pound-sign.png and /dev/null differ diff --git a/Src/Res/Misc/CSDB-v1-License.txt b/Src/Res/Misc/CSDB-v1-License.txt new file mode 100644 index 000000000..fedb839ec --- /dev/null +++ b/Src/Res/Misc/CSDB-v1-License.txt @@ -0,0 +1,21 @@ +All snippets in this collection are licensed under the MIT license (see below). + +MIT License +----------- + +>> Begin license text + +Copyright (c) 2005-2016 Peter Johnson and Contributors. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +<< End license text + +Note +---- + +The contributors mentioned above are listed in the contrib.txt file. diff --git a/Src/Res/Misc/CodeSnip.manifest b/Src/Res/Misc/CodeSnip.manifest index 9b018529c..d9ad97432 100644 --- a/Src/Res/Misc/CodeSnip.manifest +++ b/Src/Res/Misc/CodeSnip.manifest @@ -3,12 +3,9 @@ diff --git a/Src/Res/Scripts/3rdParty/LICENSE b/Src/Res/Scripts/3rdParty/LICENSE deleted file mode 100644 index 717562f2a..000000000 --- a/Src/Res/Scripts/3rdParty/LICENSE +++ /dev/null @@ -1,10 +0,0 @@ -Files in the Src/Res/Scripts/3rdParty directory are licensed as follows. - -jquery.cycle.lite.js --------------------- -Dual licensed under the MIT or GPL license. It is used here under the MIT -license. See Docs/License.html#jquery-cycle for details. - -jquery-1.8.0.min.js ------------------- -MIT license. See Docs/License.html#jquery for details. \ No newline at end of file diff --git a/Src/Res/Scripts/3rdParty/jquery-1.12.4.min.js b/Src/Res/Scripts/3rdParty/jquery-1.12.4.min.js new file mode 100644 index 000000000..b0ecaddee --- /dev/null +++ b/Src/Res/Scripts/3rdParty/jquery-1.12.4.min.js @@ -0,0 +1,5 @@ +/*! jQuery v1.12.4 | (c) jQuery Foundation | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=a.document,e=c.slice,f=c.concat,g=c.push,h=c.indexOf,i={},j=i.toString,k=i.hasOwnProperty,l={},m="1.12.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return e.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:e.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a){return n.each(this,a)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(e.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:g,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(n.isPlainObject(c)||(b=n.isArray(c)))?(b?(b=!1,f=a&&n.isArray(a)?a:[]):f=a&&n.isPlainObject(a)?a:{},g[d]=n.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray||function(a){return"array"===n.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){var b=a&&a.toString();return!n.isArray(a)&&b-parseFloat(b)+1>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;try{if(a.constructor&&!k.call(a,"constructor")&&!k.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(!l.ownFirst)for(b in a)return k.call(a,b);for(b in a);return void 0===b||k.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?i[j.call(a)]||"object":typeof a},globalEval:function(b){b&&n.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b){var c,d=0;if(s(a)){for(c=a.length;c>d;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):g.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(h)return h.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,g=0,h=[];if(s(a))for(d=a.length;d>g;g++)e=b(a[g],g,c),null!=e&&h.push(e);else for(g in a)e=b(a[g],g,c),null!=e&&h.push(e);return f.apply([],h)},guid:1,proxy:function(a,b){var c,d,f;return"string"==typeof b&&(f=a[b],b=a,a=f),n.isFunction(a)?(c=e.call(arguments,2),d=function(){return a.apply(b||this,c.concat(e.call(arguments)))},d.guid=a.guid=a.guid||n.guid++,d):void 0},now:function(){return+new Date},support:l}),"function"==typeof Symbol&&(n.fn[Symbol.iterator]=c[Symbol.iterator]),n.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){i["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=!!a&&"length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ga(),z=ga(),A=ga(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+M+"))|)"+L+"*\\]",O=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+N+")*)|.*)\\)|)",P=new RegExp(L+"+","g"),Q=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),R=new RegExp("^"+L+"*,"+L+"*"),S=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),T=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),U=new RegExp(O),V=new RegExp("^"+M+"$"),W={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M+"|[*])"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},X=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Z=/^[^{]+\{\s*\[native \w/,$=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,_=/[+~]/,aa=/'|\\/g,ba=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),ca=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},da=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(ea){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fa(a,b,d,e){var f,h,j,k,l,o,r,s,w=b&&b.ownerDocument,x=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==x&&9!==x&&11!==x)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==x&&(o=$.exec(a)))if(f=o[1]){if(9===x){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(w&&(j=w.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(o[2])return H.apply(d,b.getElementsByTagName(a)),d;if((f=o[3])&&c.getElementsByClassName&&b.getElementsByClassName)return H.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==x)w=b,s=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(aa,"\\$&"):b.setAttribute("id",k=u),r=g(a),h=r.length,l=V.test(k)?"#"+k:"[id='"+k+"']";while(h--)r[h]=l+" "+qa(r[h]);s=r.join(","),w=_.test(a)&&oa(b.parentNode)||b}if(s)try{return H.apply(d,w.querySelectorAll(s)),d}catch(y){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(Q,"$1"),b,d,e)}function ga(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ha(a){return a[u]=!0,a}function ia(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ja(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function ka(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function la(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function na(a){return ha(function(b){return b=+b,ha(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function oa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=fa.support={},f=fa.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fa.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ia(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ia(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Z.test(n.getElementsByClassName),c.getById=ia(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return"undefined"!=typeof b.getElementsByClassName&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=Z.test(n.querySelectorAll))&&(ia(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ia(function(a){var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Z.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ia(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",O)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Z.test(o.compareDocumentPosition),t=b||Z.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return ka(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?ka(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},fa.matches=function(a,b){return fa(a,null,null,b)},fa.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(T,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fa(b,n,null,[a]).length>0},fa.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fa.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fa.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fa.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fa.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fa.selectors={cacheLength:50,createPseudo:ha,match:W,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ba,ca),a[3]=(a[3]||a[4]||a[5]||"").replace(ba,ca),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fa.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fa.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return W.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&U.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ba,ca).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fa.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(P," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fa.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ha(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ha(function(a){var b=[],c=[],d=h(a.replace(Q,"$1"));return d[u]?ha(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ha(function(a){return function(b){return fa(a,b).length>0}}),contains:ha(function(a){return a=a.replace(ba,ca),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ha(function(a){return V.test(a||"")||fa.error("unsupported lang: "+a),a=a.replace(ba,ca).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Y.test(a.nodeName)},input:function(a){return X.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:na(function(){return[0]}),last:na(function(a,b){return[b-1]}),eq:na(function(a,b,c){return[0>c?c+b:c]}),even:na(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:na(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:na(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:na(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function ra(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j,k=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(j=b[u]||(b[u]={}),i=j[b.uniqueID]||(j[b.uniqueID]={}),(h=i[d])&&h[0]===w&&h[1]===f)return k[2]=h[2];if(i[d]=k,k[2]=a(b,c,g))return!0}}}function sa(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ta(a,b,c){for(var d=0,e=b.length;e>d;d++)fa(a,b[d],c);return c}function ua(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(c&&!c(f,d,e)||(g.push(f),j&&b.push(h)));return g}function va(a,b,c,d,e,f){return d&&!d[u]&&(d=va(d)),e&&!e[u]&&(e=va(e,f)),ha(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ta(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ua(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ua(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ua(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function wa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ra(function(a){return a===b},h,!0),l=ra(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[ra(sa(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return va(i>1&&sa(m),i>1&&qa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(Q,"$1"),c,e>i&&wa(a.slice(i,e)),f>e&&wa(a=a.slice(e)),f>e&&qa(a))}m.push(c)}return sa(m)}function xa(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=F.call(i));u=ua(u)}H.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&fa.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ha(f):f}return h=fa.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xa(e,d)),f.selector=a}return f},i=fa.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ba,ca),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=W.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ba,ca),_.test(j[0].type)&&oa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qa(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,!b||_.test(a)&&oa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ia(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ia(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ja("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ia(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ja("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ia(function(a){return null==a.getAttribute("disabled")})||ja(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fa}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.uniqueSort=n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},v=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},w=n.expr.match.needsContext,x=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,y=/^.[^:#\[\.,]*$/;function z(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(y.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return n.inArray(a,b)>-1!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;e>b;b++)if(n.contains(d[b],this))return!0}));for(b=0;e>b;b++)n.find(a,d[b],c);return c=this.pushStack(e>1?n.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(z(this,a||[],!1))},not:function(a){return this.pushStack(z(this,a||[],!0))},is:function(a){return!!z(this,"string"==typeof a&&w.test(a)?n(a):a||[],!1).length}});var A,B=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=n.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||A,"string"==typeof a){if(e="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:B.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),x.test(e[1])&&n.isPlainObject(b))for(e in b)n.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}if(f=d.getElementById(e[2]),f&&f.parentNode){if(f.id!==e[2])return A.find(a);this.length=1,this[0]=f}return this.context=d,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof c.ready?c.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};C.prototype=n.fn,A=n(d);var D=/^(?:parents|prev(?:Until|All))/,E={children:!0,contents:!0,next:!0,prev:!0};n.fn.extend({has:function(a){var b,c=n(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(n.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=w.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?n.inArray(this[0],n(a)):n.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.uniqueSort(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function F(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return u(a,"parentNode")},parentsUntil:function(a,b,c){return u(a,"parentNode",c)},next:function(a){return F(a,"nextSibling")},prev:function(a){return F(a,"previousSibling")},nextAll:function(a){return u(a,"nextSibling")},prevAll:function(a){return u(a,"previousSibling")},nextUntil:function(a,b,c){return u(a,"nextSibling",c)},prevUntil:function(a,b,c){return u(a,"previousSibling",c)},siblings:function(a){return v((a.parentNode||{}).firstChild,a)},children:function(a){return v(a.firstChild)},contents:function(a){return n.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(E[a]||(e=n.uniqueSort(e)),D.test(a)&&(e=e.reverse())),this.pushStack(e)}});var G=/\S+/g;function H(a){var b={};return n.each(a.match(G)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?H(a):n.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),h>=c&&h--}),this},has:function(a){return a?n.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=!0,c||j.disable(),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().progress(c.notify).done(c.resolve).fail(c.reject):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=e.call(arguments),d=c.length,f=1!==d||a&&n.isFunction(a.promise)?d:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?e.call(arguments):d,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(d>1)for(i=new Array(d),j=new Array(d),k=new Array(d);d>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().progress(h(b,j,i)).done(h(b,k,c)).fail(g.reject):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(d,[n]),n.fn.triggerHandler&&(n(d).triggerHandler("ready"),n(d).off("ready"))))}});function J(){d.addEventListener?(d.removeEventListener("DOMContentLoaded",K),a.removeEventListener("load",K)):(d.detachEvent("onreadystatechange",K),a.detachEvent("onload",K))}function K(){(d.addEventListener||"load"===a.event.type||"complete"===d.readyState)&&(J(),n.ready())}n.ready.promise=function(b){if(!I)if(I=n.Deferred(),"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll)a.setTimeout(n.ready);else if(d.addEventListener)d.addEventListener("DOMContentLoaded",K),a.addEventListener("load",K);else{d.attachEvent("onreadystatechange",K),a.attachEvent("onload",K);var c=!1;try{c=null==a.frameElement&&d.documentElement}catch(e){}c&&c.doScroll&&!function f(){if(!n.isReady){try{c.doScroll("left")}catch(b){return a.setTimeout(f,50)}J(),n.ready()}}()}return I.promise(b)},n.ready.promise();var L;for(L in n(l))break;l.ownFirst="0"===L,l.inlineBlockNeedsLayout=!1,n(function(){var a,b,c,e;c=d.getElementsByTagName("body")[0],c&&c.style&&(b=d.createElement("div"),e=d.createElement("div"),e.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(e).appendChild(b),"undefined"!=typeof b.style.zoom&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",l.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(e))}),function(){var a=d.createElement("div");l.deleteExpando=!0;try{delete a.test}catch(b){l.deleteExpando=!1}a=null}();var M=function(a){var b=n.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b},N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(O,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}n.data(a,b,c)}else c=void 0; +}return c}function Q(a){var b;for(b in a)if(("data"!==b||!n.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function R(a,b,d,e){if(M(a)){var f,g,h=n.expando,i=a.nodeType,j=i?n.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||n.guid++:h),j[k]||(j[k]=i?{}:{toJSON:n.noop}),"object"!=typeof b&&"function"!=typeof b||(e?j[k]=n.extend(j[k],b):j[k].data=n.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[n.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[n.camelCase(b)])):f=g,f}}function S(a,b,c){if(M(a)){var d,e,f=a.nodeType,g=f?n.cache:a,h=f?a[n.expando]:n.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){n.isArray(b)?b=b.concat(n.map(b,n.camelCase)):b in d?b=[b]:(b=n.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!Q(d):!n.isEmptyObject(d))return}(c||(delete g[h].data,Q(g[h])))&&(f?n.cleanData([a],!0):l.deleteExpando||g!=g.window?delete g[h]:g[h]=void 0)}}}n.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?n.cache[a[n.expando]]:a[n.expando],!!a&&!Q(a)},data:function(a,b,c){return R(a,b,c)},removeData:function(a,b){return S(a,b)},_data:function(a,b,c){return R(a,b,c,!0)},_removeData:function(a,b){return S(a,b,!0)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=n.data(f),1===f.nodeType&&!n._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));n._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){n.data(this,a)}):arguments.length>1?this.each(function(){n.data(this,a,b)}):f?P(f,a,n.data(f,a)):void 0},removeData:function(a){return this.each(function(){n.removeData(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=n._data(a,b),c&&(!d||n.isArray(c)?d=n._data(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return n._data(a,c)||n._data(a,c,{empty:n.Callbacks("once memory").add(function(){n._removeData(a,b+"queue"),n._removeData(a,c)})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},Z=/^(?:checkbox|radio)$/i,$=/<([\w:-]+)/,_=/^$|\/(?:java|ecma)script/i,aa=/^\s+/,ba="abbr|article|aside|audio|bdi|canvas|data|datalist|details|dialog|figcaption|figure|footer|header|hgroup|main|mark|meter|nav|output|picture|progress|section|summary|template|time|video";function ca(a){var b=ba.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}!function(){var a=d.createElement("div"),b=d.createDocumentFragment(),c=d.createElement("input");a.innerHTML="
          a",l.leadingWhitespace=3===a.firstChild.nodeType,l.tbody=!a.getElementsByTagName("tbody").length,l.htmlSerialize=!!a.getElementsByTagName("link").length,l.html5Clone="<:nav>"!==d.createElement("nav").cloneNode(!0).outerHTML,c.type="checkbox",c.checked=!0,b.appendChild(c),l.appendChecked=c.checked,a.innerHTML="",l.noCloneChecked=!!a.cloneNode(!0).lastChild.defaultValue,b.appendChild(a),c=d.createElement("input"),c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),a.appendChild(c),l.checkClone=a.cloneNode(!0).cloneNode(!0).lastChild.checked,l.noCloneEvent=!!a.addEventListener,a[n.expando]=1,l.attributes=!a.getAttribute(n.expando)}();var da={option:[1,""],legend:[1,"
          ","
          "],area:[1,"",""],param:[1,"",""],thead:[1,"","
          "],tr:[2,"","
          "],col:[2,"","
          "],td:[3,"","
          "],_default:l.htmlSerialize?[0,"",""]:[1,"X
          ","
          "]};da.optgroup=da.option,da.tbody=da.tfoot=da.colgroup=da.caption=da.thead,da.th=da.td;function ea(a,b){var c,d,e=0,f="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||n.nodeName(d,b)?f.push(d):n.merge(f,ea(d,b));return void 0===b||b&&n.nodeName(a,b)?n.merge([a],f):f}function fa(a,b){for(var c,d=0;null!=(c=a[d]);d++)n._data(c,"globalEval",!b||n._data(b[d],"globalEval"))}var ga=/<|&#?\w+;/,ha=/r;r++)if(g=a[r],g||0===g)if("object"===n.type(g))n.merge(q,g.nodeType?[g]:g);else if(ga.test(g)){i=i||p.appendChild(b.createElement("div")),j=($.exec(g)||["",""])[1].toLowerCase(),m=da[j]||da._default,i.innerHTML=m[1]+n.htmlPrefilter(g)+m[2],f=m[0];while(f--)i=i.lastChild;if(!l.leadingWhitespace&&aa.test(g)&&q.push(b.createTextNode(aa.exec(g)[0])),!l.tbody){g="table"!==j||ha.test(g)?""!==m[1]||ha.test(g)?0:i:i.firstChild,f=g&&g.childNodes.length;while(f--)n.nodeName(k=g.childNodes[f],"tbody")&&!k.childNodes.length&&g.removeChild(k)}n.merge(q,i.childNodes),i.textContent="";while(i.firstChild)i.removeChild(i.firstChild);i=p.lastChild}else q.push(b.createTextNode(g));i&&p.removeChild(i),l.appendChecked||n.grep(ea(q,"input"),ia),r=0;while(g=q[r++])if(d&&n.inArray(g,d)>-1)e&&e.push(g);else if(h=n.contains(g.ownerDocument,g),i=ea(p.appendChild(g),"script"),h&&fa(i),c){f=0;while(g=i[f++])_.test(g.type||"")&&c.push(g)}return i=null,p}!function(){var b,c,e=d.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(l[b]=c in a)||(e.setAttribute(c,"t"),l[b]=e.attributes[c].expando===!1);e=null}();var ka=/^(?:input|select|textarea)$/i,la=/^key/,ma=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,na=/^(?:focusinfocus|focusoutblur)$/,oa=/^([^.]*)(?:\.(.+)|)/;function pa(){return!0}function qa(){return!1}function ra(){try{return d.activeElement}catch(a){}}function sa(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)sa(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=qa;else if(!e)return a;return 1===f&&(g=e,e=function(a){return n().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=n.guid++)),a.each(function(){n.event.add(this,b,e,d,c)})}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=n.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return"undefined"==typeof n||a&&n.event.triggered===a.type?void 0:n.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(G)||[""],h=b.length;while(h--)f=oa.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=n.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=n.event.special[o]||{},l=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},i),(m=g[o])||(m=g[o]=[],m.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,l):m.push(l),n.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n.hasData(a)&&n._data(a);if(r&&(k=r.events)){b=(b||"").match(G)||[""],j=b.length;while(j--)if(h=oa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=m.length;while(f--)g=m[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(m.splice(f,1),g.selector&&m.delegateCount--,l.remove&&l.remove.call(a,g));i&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(k)&&(delete r.handle,n._removeData(a,"events"))}},trigger:function(b,c,e,f){var g,h,i,j,l,m,o,p=[e||d],q=k.call(b,"type")?b.type:b,r=k.call(b,"namespace")?b.namespace.split("."):[];if(i=m=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!na.test(q+n.event.triggered)&&(q.indexOf(".")>-1&&(r=q.split("."),q=r.shift(),r.sort()),h=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=r.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:n.makeArray(c,[b]),l=n.event.special[q]||{},f||!l.trigger||l.trigger.apply(e,c)!==!1)){if(!f&&!l.noBubble&&!n.isWindow(e)){for(j=l.delegateType||q,na.test(j+q)||(i=i.parentNode);i;i=i.parentNode)p.push(i),m=i;m===(e.ownerDocument||d)&&p.push(m.defaultView||m.parentWindow||a)}o=0;while((i=p[o++])&&!b.isPropagationStopped())b.type=o>1?j:l.bindType||q,g=(n._data(i,"events")||{})[b.type]&&n._data(i,"handle"),g&&g.apply(i,c),g=h&&i[h],g&&g.apply&&M(i)&&(b.result=g.apply(i,c),b.result===!1&&b.preventDefault());if(b.type=q,!f&&!b.isDefaultPrevented()&&(!l._default||l._default.apply(p.pop(),c)===!1)&&M(e)&&h&&e[q]&&!n.isWindow(e)){m=e[h],m&&(e[h]=null),n.event.triggered=q;try{e[q]()}catch(s){}n.event.triggered=void 0,m&&(e[h]=m)}return b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,d,f,g,h=[],i=e.call(arguments),j=(n._data(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())a.rnamespace&&!a.rnamespace.test(g.namespace)||(a.handleObj=g,a.data=g.data,d=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==d&&(a.result=d)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&("click"!==a.type||isNaN(a.button)||a.button<1))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>-1:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]","i"),va=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,wa=/\s*$/g,Aa=ca(d),Ba=Aa.appendChild(d.createElement("div"));function Ca(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function Da(a){return a.type=(null!==n.find.attr(a,"type"))+"/"+a.type,a}function Ea(a){var b=ya.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Fa(a,b){if(1===b.nodeType&&n.hasData(a)){var c,d,e,f=n._data(a),g=n._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)n.event.add(b,c,h[c][d])}g.data&&(g.data=n.extend({},g.data))}}function Ga(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!l.noCloneEvent&&b[n.expando]){e=n._data(b);for(d in e.events)n.removeEvent(b,d,e.handle);b.removeAttribute(n.expando)}"script"===c&&b.text!==a.text?(Da(b).text=a.text,Ea(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),l.html5Clone&&a.innerHTML&&!n.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&Z.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:"input"!==c&&"textarea"!==c||(b.defaultValue=a.defaultValue)}}function Ha(a,b,c,d){b=f.apply([],b);var e,g,h,i,j,k,m=0,o=a.length,p=o-1,q=b[0],r=n.isFunction(q);if(r||o>1&&"string"==typeof q&&!l.checkClone&&xa.test(q))return a.each(function(e){var f=a.eq(e);r&&(b[0]=q.call(this,e,f.html())),Ha(f,b,c,d)});if(o&&(k=ja(b,a[0].ownerDocument,!1,a,d),e=k.firstChild,1===k.childNodes.length&&(k=e),e||d)){for(i=n.map(ea(k,"script"),Da),h=i.length;o>m;m++)g=k,m!==p&&(g=n.clone(g,!0,!0),h&&n.merge(i,ea(g,"script"))),c.call(a[m],g,m);if(h)for(j=i[i.length-1].ownerDocument,n.map(i,Ea),m=0;h>m;m++)g=i[m],_.test(g.type||"")&&!n._data(g,"globalEval")&&n.contains(j,g)&&(g.src?n._evalUrl&&n._evalUrl(g.src):n.globalEval((g.text||g.textContent||g.innerHTML||"").replace(za,"")));k=e=null}return a}function Ia(a,b,c){for(var d,e=b?n.filter(b,a):a,f=0;null!=(d=e[f]);f++)c||1!==d.nodeType||n.cleanData(ea(d)),d.parentNode&&(c&&n.contains(d.ownerDocument,d)&&fa(ea(d,"script")),d.parentNode.removeChild(d));return a}n.extend({htmlPrefilter:function(a){return a.replace(va,"<$1>")},clone:function(a,b,c){var d,e,f,g,h,i=n.contains(a.ownerDocument,a);if(l.html5Clone||n.isXMLDoc(a)||!ua.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(Ba.innerHTML=a.outerHTML,Ba.removeChild(f=Ba.firstChild)),!(l.noCloneEvent&&l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(d=ea(f),h=ea(a),g=0;null!=(e=h[g]);++g)d[g]&&Ga(e,d[g]);if(b)if(c)for(h=h||ea(a),d=d||ea(f),g=0;null!=(e=h[g]);g++)Fa(e,d[g]);else Fa(a,f);return d=ea(f,"script"),d.length>0&&fa(d,!i&&ea(a,"script")),d=h=e=null,f},cleanData:function(a,b){for(var d,e,f,g,h=0,i=n.expando,j=n.cache,k=l.attributes,m=n.event.special;null!=(d=a[h]);h++)if((b||M(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)m[e]?n.event.remove(d,e):n.removeEvent(d,e,g.handle);j[f]&&(delete j[f],k||"undefined"==typeof d.removeAttribute?d[i]=void 0:d.removeAttribute(i),c.push(f))}}}),n.fn.extend({domManip:Ha,detach:function(a){return Ia(this,a,!0)},remove:function(a){return Ia(this,a)},text:function(a){return Y(this,function(a){return void 0===a?n.text(this):this.empty().append((this[0]&&this[0].ownerDocument||d).createTextNode(a))},null,a,arguments.length)},append:function(){return Ha(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ca(this,a);b.appendChild(a)}})},prepend:function(){return Ha(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ca(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ha(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ha(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&n.cleanData(ea(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&n.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return Y(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(ta,""):void 0;if("string"==typeof a&&!wa.test(a)&&(l.htmlSerialize||!ua.test(a))&&(l.leadingWhitespace||!aa.test(a))&&!da[($.exec(a)||["",""])[1].toLowerCase()]){a=n.htmlPrefilter(a);try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(ea(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=[];return Ha(this,arguments,function(b){var c=this.parentNode;n.inArray(this,a)<0&&(n.cleanData(ea(this)),c&&c.replaceChild(b,this))},a)}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=0,e=[],f=n(a),h=f.length-1;h>=d;d++)c=d===h?this:this.clone(!0),n(f[d])[b](c),g.apply(e,c.get());return this.pushStack(e)}});var Ja,Ka={HTML:"block",BODY:"block"};function La(a,b){var c=n(b.createElement(a)).appendTo(b.body),d=n.css(c[0],"display");return c.detach(),d}function Ma(a){var b=d,c=Ka[a];return c||(c=La(a,b),"none"!==c&&c||(Ja=(Ja||n("