From 9a70194774569301b76788accab60c6599609a48 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Tue, 14 Sep 2021 16:20:21 +0100 Subject: [PATCH 001/330] Update 3rd party PJSysInfo.pas to v5.6.1 --- Src/3rdParty/PJSysInfo.pas | 296 +++++++++++++++++++++++++++++-------- 1 file changed, 234 insertions(+), 62 deletions(-) diff --git a/Src/3rdParty/PJSysInfo.pas b/Src/3rdParty/PJSysInfo.pas index 23280db53..5a9890ac2 100644 --- a/Src/3rdParty/PJSysInfo.pas +++ b/Src/3rdParty/PJSysInfo.pas @@ -5,8 +5,8 @@ * * Copyright (C) 2001-2020, Peter Johnson (@delphidabbler). * - * $Rev: 2029 $ - * $Date: 2020-10-31 15:24:58 +0000 (Sat, 31 Oct 2020) $ + * $Rev: 2066 $ + * $Date: 2021-09-12 20:29:38 +0100 (Sun, 12 Sep 2021) $ * * This unit contains various static classes, constants, type definitions and * global variables for use in providing information about the host computer and @@ -29,34 +29,7 @@ * * 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) - * - * - Rich Habedank (bug fix in r228 and testing of bug fixes reported as - * issues #31 (https://sourceforge.net/p/ddablib/tickets/31/) and #33 - * (https://sourceforge.net/p/ddablib/tickets/33/) - * - * The project also draws on the work of: - * - * - Achim Kalwa 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. - * - * ***** END LICENSE BLOCK ***** + * See Docs/Acknowledgements.md } @@ -444,7 +417,9 @@ interface osWinSvr2012R2, // Windows Server 2012 R2 osWin10, // Windows 10 osWin10Svr, // Windows 2016 Server - osWinSvr2019 // Windows 2019 Server + osWinSvr2019, // Windows 2019 Server + osWin11, // Windows 11 + osWinSvr2022 // Windows 2022 Server ); type @@ -522,14 +497,17 @@ TPJOSInfo = class(TObject) /// Checks if the OS is on the Windows 9x platform. class function IsWin9x: Boolean; + {$IFDEF INLINEMETHODS}inline;{$ENDIF} /// Checks if the OS is on the Windows NT platform. class function IsWinNT: Boolean; + {$IFDEF INLINEMETHODS}inline;{$ENDIF} /// Checks if the program is hosted on Win32s. /// This is unlikely to ever return True since Delphi does not run /// on Win32s. class function IsWin32s: Boolean; + {$IFDEF INLINEMETHODS}inline;{$ENDIF} /// Checks if a 32 bit program is running under WOW64 on a 64 bit /// operating system. @@ -594,11 +572,13 @@ TPJOSInfo = class(TObject) /// 0 is returned in no service pack is installed, if the host OS /// is not on the NT platform. class function ServicePackMajor: Integer; + {$IFDEF INLINEMETHODS}inline;{$ENDIF} /// Returns the minor version number of any NT platform service /// pack. - /// Invalid is ServicePackMinor returns 0. + /// Invalid if ServicePackMajor returns 0. class function ServicePackMinor: Integer; + {$IFDEF INLINEMETHODS}inline;{$ENDIF} /// Returns the product edition for an NT platform OS. /// The empty string is returned if the OS is not on the NT @@ -755,6 +735,15 @@ TPJOSInfo = class(TObject) /// WARNING: For Windows 10 this method is likely to succeed only if /// the application is correctly manifested. class function IsWindowsServer: Boolean; + + /// Returns any revision number for the OS. + /// + /// If the OS does not provide any revision information then zero is + /// returned. + /// This value is read fromt he registry therefore it is possible + /// that this value could be spoofed. + /// + class function RevisionNumber: Integer; end; type @@ -803,6 +792,7 @@ TPJComputerInfo = class(TObject) /// Checks if the host computer has a 64 bit processor. class function Is64Bit: Boolean; + {$IFDEF INLINEMETHODS}inline;{$ENDIF} /// Checks if a network is present on host computer. class function IsNetworkPresent: Boolean; @@ -935,6 +925,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 @@ -1183,26 +1175,37 @@ implementation ); const - // Known windows build numbers. - // Sources: - // https://en.wikipedia.org/wiki/Windows_NT - // https://en.wikipedia.org/wiki/Windows_10_version_history - // https://en.wikipedia.org/wiki/Windows_Server_2019 - // https://en.wikipedia.org/wiki/Windows_Server_2016 - // https://en.wikipedia.org/wiki/Windows_Server_2016 - // https://tinyurl.com/y8tfadm2 - - // for Vista and Win 7 we have to add service pack number to these values to - // get actual build number - + { + 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://en.wikipedia.org/wiki/Windows_Server + https://en.wikipedia.org/wiki/Windows_Server_2019 + https://en.wikipedia.org/wiki/Windows_Server_2016 + https://tinyurl.com/y8tfadm2 + https://tinyurl.com/usupsz4a + https://docs.microsoft.com/en-us/lifecycle/products/windows-server-2022 + + 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. + } + + // Windows Vista ------------------------------------------------------------- WinVistaBaseBuild = 6000; - Win7BaseBuild = 7600; - // for Win 8 onwards we just use the build numbers as is + // Windows 7 ----------------------------------------------------------------- + Win7BaseBuild = 7600; + // Windows 8 ----------------------------------------------------------------- Win8Build = 9200; // Build number used for all Win 8/Svr 2012 Win8Point1Build = 9600; // Build number used for all Win 8.1/Svr 2012 R2 + // Windows 10 ---------------------------------------------------------------- Win10TH1Build = 10240; // Windows 10 TH1 - version 1507 (1st release) Win10TH2Build = 10586; // Windows 10 TH2 - version 1511 Win10RS1Build = 14393; // Windows 10 RS1 - version 1607 @@ -1214,7 +1217,35 @@ implementation Win1019H2Build = 18363; // Windows 10 19H2 - version 1909 Win1020H1Build = 19041; // Windows 10 20H1 - version 2004 Win1020H2Build = 19042; // Windows 10 20H2 - version 20H2 - + Win1021H1Build = 19043; // Windows 10 21H1 - version 21H1 + { TODO: 2021-09-11 + - Win 21H2 due late 2021 + - Update following var name once Win21H2 released} + _Win1021H2Build = 19044; // Windows 10 21H2 - version 21H2 + + // Windows 11 ---------------------------------------------------------------- + { TODO: 2021-09-11 + - Add more Win11 versions as discovered. } + // NOTE: Preview and beta versions of Windows 11 report version 10.0 + Win11DevBuild = 21996; // Windows 11 version Dev + // – 10.0.21996.1 (Insider version) + Win11v21H2Build = 22000; // Version depends on revision # [Rev#]: + // Revision # 51..168: + // Windows 11 version 21H2 + // – 10.0.22000.[Rev#] (Insider version) + // Revision # 184 + // Windows 11 version 21H2 + // – 10.0.22000.184 (Beta Version) + // Revision # >=185 + // Windows 11 (unknown version) + Win11c21H2PreRel1Build = 22449; // Windows 11 version 21H2 + // – 10.0.22449.000 (RSPRERELEASE) + Win11c21H2PreRel2Build = 22454; // Windows 11 version 21H2 + // – 10.0.22454.1000 (RSPRERELEASE) + + Win11FirstBuild = Win11DevBuild; // First build number of Windows 11 + + // Windows 2016 Server ------------------------------------------------------- Win2016TP1Build = 9841; // Win 2016 Server Technical Preview 1 Win2016TP2Build = 10074; // Win 2016 Server Technical Preview 2 Win2016TP3Build = 10514; // Win 2016 Server Technical Preview 3 @@ -1223,7 +1254,10 @@ implementation Win2016RTMBuild = 14393; // Win 2016 Server Release To Manufacturing Win2016v1709Build = 16299; // Win Server 2016 version 1709 Win2016v1803Build = 17134; // Win Server 2016 version 1803 + Win2016LastBuild = Win2016v1803Build; // Last build number of Win 2016 Server + // After this it's Win 2019 Server + // Windows 2019 Server ------------------------------------------------------- Win2019IP180320Build = 17623; // Win Server 2019 Insider Preview Build 17623 Win2019IP180324Build = 17627; // Win Server 2019 Insider Preview Build 17627 Win2019IP180515Build = 17666; // Win Server 2019 Insider Preview Build 17666 @@ -1237,10 +1271,13 @@ implementation Win2019v1809Build = 17763; // Win Server 2019 version 1809 Win2019v1903Build = 18362; // Win Server 2019 version 1903 Win2019v1909Build = 18363; // Win Server 2019 version 1909 + Win2019v2004Build = 19041; // Win Server 2019 version 2004 + Win2019v20H2Build = 19042; // Win Server 2019 version 20H2 + Win2019LastBuild = Win2019v20H2Build; // Last build number of Win 2019 Server + // After this it's Win 2022 Server - // Last build number of Windows 2016 Server - after this build number are - // Windows 2019 Server - Win2016LastBuild = Win2016v1803Build; + // Windows 2022 Server ------------------------------------------------------- + Win2022v21H2Build = 20348; // Win Server 2022 version 21H2 type // Function type of the GetNativeSystemInfo and GetSystemInfo functions @@ -1277,6 +1314,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. @@ -1595,6 +1633,14 @@ procedure InitPlatformIdEx; Result := Format('Insider Preview Build %d', [Build]); end; + // Get OS's revision number from registry. + function GetOSRevisionNumber(const IsNT: Boolean): Integer; + begin + Result := GetRegistryInt( + HKEY_LOCAL_MACHINE, CurrentVersionRegKeys[IsNT], 'UBR' + ); + end; + begin // Load version query functions used externally to this routine VerSetConditionMask := LoadKernelFunc('VerSetConditionMask'); @@ -1619,6 +1665,7 @@ procedure InitPlatformIdEx; // 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, @@ -1737,6 +1784,82 @@ procedure InitPlatformIdEx; // not '2010' which some had expected it to be InternalExtraUpdateInfo := 'Version 20H2: October 2020 Update'; end + else if IsBuildNumber(Win1021H1Build) then + begin + InternalBuildNumber := Win1021H1Build; + InternalExtraUpdateInfo := 'Version 21H1'; + end + else if IsBuildNumber(_Win1021H2Build) then + begin + { TODO: Added 2021/09/11 + - Release expected late 2021 + - Fix build number if necessary + - Remove underscore prefix from const name + - Fix value of InternalExtraUpdateInfo as required } + InternalBuildNumber := _Win1021H2Build; + InternalExtraUpdateInfo := 'Version 21H2'; + end + // As of 2021-09-11, Win 11 pre-releases are reporting v10.0 + // Details taken from: https://tinyurl.com/usupsz4a + // Correct according to above web oage as of 2021-09-11 + { TODO: Added 2021-09-11 + - Revisit URL to check for change following official release + of Windows 11 + - Add any further pre-release versions + - Check if final release has major version 11 + } + else if IsBuildNumber(Win11DevBuild) then + begin + InternalBuildNumber := Win11DevBuild; + InternalExtraUpdateInfo := Format( + 'Dev [Insider v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ) + end + else if IsBuildNumber(Win11v21H2Build) then + begin + // There are several Win 11 releases with this build number + // Which release we're talking about depends on the revision + // number. + InternalBuildNumber := Win11v21H2Build; + if InternalRevisionNumber in [51, 65, 71, 100, 120, 132, 168] then + begin + InternalExtraUpdateInfo := Format( + 'Version 21H2 [Insider v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + end + else if InternalRevisionNumber = 184 then + begin + InternalExtraUpdateInfo := Format( + 'Version 21H2 [Beta v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + end + else + begin + InternalExtraUpdateInfo := Format( + 'Unknown release v10.0.%d.%d', + [InternalBuildNumber, InternalRevisionNumber] + ); + end; + end + else if IsBuildNumber(Win11c21H2PreRel1Build) then + begin + InternalBuildNumber := Win11c21H2PreRel1Build; + InternalExtraUpdateInfo := Format( + 'Version 21H2 [RSPRERELEASE v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + end + else if IsBuildNumber(Win11c21H2PreRel2Build) then + begin + InternalBuildNumber := Win11c21H2PreRel2Build; + InternalExtraUpdateInfo := Format( + 'Version 21H2 [RSPRERELEASE v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + end; end else begin @@ -1848,6 +1971,21 @@ procedure InitPlatformIdEx; InternalBuildNumber := Win2019v1909Build; InternalExtraUpdateInfo := 'Version 1909'; end + else if IsBuildNumber(Win2019v2004Build) then + begin + InternalBuildNumber := Win2019v2004Build; + InternalExtraUpdateInfo := 'Version 2004'; + end + else if IsBuildNumber(Win2019v20H2Build) then + begin + InternalBuildNumber := Win2019v20H2Build; + InternalExtraUpdateInfo := 'Version 20H2'; + end + else if IsBuildNumber(Win2022v21H2Build) then + begin + InternalBuildNumber := Win2022v21H2Build; + InternalExtraUpdateInfo := 'Version 21H2'; + end; end; end; end; @@ -1875,6 +2013,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'); @@ -1923,6 +2064,8 @@ procedure InitPlatformIdEx; GetSystemInfoFn(SI); // Get processor architecture InternalProcessorArchitecture := SI.wProcessorArchitecture; + // Store revision number + Win32RevisionNumber := InternalRevisionNumber; end; { TPJOSInfo } @@ -1944,11 +2087,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 @@ -1964,15 +2111,22 @@ class function TPJOSInfo.Description: string; // For NT3/4 append version number after product AppendToResult(Format('%d.%d', [MajorVersion, MinorVersion])); AppendToResult(Edition); - AppendToResult(ServicePackEx); // does nothing if no service pack etc + 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(ServicePackEx); // 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: @@ -1990,7 +2144,7 @@ class function TPJOSInfo.Edition: string; osWin7, osWinSvr2008R2, osWin8, osWinSvr2012, osWin8Point1, osWinSvr2012R2, - osWin10, osWin10Svr, osWinSvr2019: + osWin10, osWin11, osWin10Svr, osWinSvr2019, osWinSvr2022: begin // For v6.0 and later we ignore the suite mask and use the new // PRODUCT_ flags from the GetProductInfo() function to determine the @@ -2262,8 +2416,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 ) @@ -2527,12 +2681,23 @@ class function TPJOSInfo.Product: TPJOSProduct; case InternalMinorVersion of 0: if not IsServer then - Result := osWin10 + begin + if InternalBuildNumber < Win11FirstBuild then + Result := osWin10 + else + // As of 2021-09-11 Win 11 is reporting version 10.0 + Result := osWin11; + end else + begin if InternalBuildNumber <= Win2016LastBuild then Result := osWin10Svr + else if InternalBuildNumber <= Win2019LastBuild then + Result := osWinSvr2019 else - Result := osWinSvr2019; + // + Result := osWinSvr2022; + end; end; end; else @@ -2556,7 +2721,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'; @@ -2579,6 +2744,8 @@ class function TPJOSInfo.ProductName: string; osWin10: Result := 'Windows 10'; osWin10Svr: Result := 'Windows Server 2016'; osWinSvr2019: Result := 'Windows Server 2019'; + osWin11: Result := 'Windows 11'; + osWinSvr2022: Result := 'Windows Server 2022'; else raise EPJSysInfo.Create(sUnknownProduct); end; @@ -2607,6 +2774,11 @@ class function TPJOSInfo.RegisteredOwner: string; ); end; +class function TPJOSInfo.RevisionNumber: Integer; +begin + Result := InternalRevisionNumber; +end; + class function TPJOSInfo.ServicePack: string; begin // Assume no service pack From 620b6fabd31618ba6bf47afe798813b9134b6503 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Tue, 14 Sep 2021 16:22:08 +0100 Subject: [PATCH 002/330] Update 3rd Party PJSysInfo.pas to (again!) v5.6.2 Fixes "spurious characters in source code" bug introduced in 5.6.1 --- Src/3rdParty/PJSysInfo.pas | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Src/3rdParty/PJSysInfo.pas b/Src/3rdParty/PJSysInfo.pas index 5a9890ac2..a2d276da5 100644 --- a/Src/3rdParty/PJSysInfo.pas +++ b/Src/3rdParty/PJSysInfo.pas @@ -5,8 +5,8 @@ * * Copyright (C) 2001-2020, Peter Johnson (@delphidabbler). * - * $Rev: 2066 $ - * $Date: 2021-09-12 20:29:38 +0100 (Sun, 12 Sep 2021) $ + * $Rev: 2069 $ + * $Date: 2021-09-14 16:00:48 +0100 (Tue, 14 Sep 2021) $ * * This unit contains various static classes, constants, type definitions and * global variables for use in providing information about the host computer and @@ -1228,20 +1228,20 @@ implementation - Add more Win11 versions as discovered. } // NOTE: Preview and beta versions of Windows 11 report version 10.0 Win11DevBuild = 21996; // Windows 11 version Dev - // – 10.0.21996.1 (Insider version) + // - 10.0.21996.1 (Insider version) Win11v21H2Build = 22000; // Version depends on revision # [Rev#]: // Revision # 51..168: // Windows 11 version 21H2 - // – 10.0.22000.[Rev#] (Insider version) + // - 10.0.22000.[Rev#] (Insider version) // Revision # 184 // Windows 11 version 21H2 - // – 10.0.22000.184 (Beta Version) + // - 10.0.22000.184 (Beta Version) // Revision # >=185 // Windows 11 (unknown version) Win11c21H2PreRel1Build = 22449; // Windows 11 version 21H2 - // – 10.0.22449.000 (RSPRERELEASE) + // - 10.0.22449.000 (RSPRERELEASE) Win11c21H2PreRel2Build = 22454; // Windows 11 version 21H2 - // – 10.0.22454.1000 (RSPRERELEASE) + // - 10.0.22454.1000 (RSPRERELEASE) Win11FirstBuild = Win11DevBuild; // First build number of Windows 11 From b1ae57aa42294ecef3235d515b53657282e72a72 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Tue, 14 Sep 2021 16:58:37 +0100 Subject: [PATCH 003/330] Change Save Annotated Dlg Title for snippets and categories. Fixes issue #11 where Save Annotated Snippet dialogue box was displaying "Snippet" when displaying a category. --- Src/USaveSnippetMgr.pas | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Src/USaveSnippetMgr.pas b/Src/USaveSnippetMgr.pas index 76361dbb7..94ff2fbf7 100644 --- a/Src/USaveSnippetMgr.pas +++ b/Src/USaveSnippetMgr.pas @@ -3,7 +3,7 @@ * 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) 2005-2020, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). * * Defines a class that manages generation, previewing and saving of a code * snippet. @@ -92,7 +92,8 @@ implementation resourcestring // Dialog box title - sSaveDlgTitle = 'Save %0:s Snippet'; + sSaveSnippetDlgTitle = 'Save %0:s Snippet'; + sSaveCategoryDlgTitle = 'Save %0:s Category'; // Output document title for snippets and categories sDocTitle = '"%0:s" %1:s'; sCategory = 'category'; @@ -145,7 +146,12 @@ function TSaveSnippetMgr.GetDlgHelpKeyword: string; function TSaveSnippetMgr.GetDlgTitle: string; begin - Result := Format(sSaveDlgTitle, [fView.Description]); + if Supports(fView, ICategoryView) then + Result := Format(sSaveCategoryDlgTitle, [fView.Description]) + else if Supports(fView, ISnippetView) then + Result := Format(sSaveSnippetDlgTitle, [fView.Description]) + else + Result := ''; end; function TSaveSnippetMgr.GetDocTitle: string; From daf0198e54b6f968c0287e42bade06cc24a529ee Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 28 Nov 2021 20:42:03 +0000 Subject: [PATCH 004/330] Add TGIFImage class helper This class helper was added to replace direct loading of GIFs from resources that was removed from 3rd party TGIFImage when it was ported to the Delphi VCL --- Src/UClassHelpers.pas | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/Src/UClassHelpers.pas b/Src/UClassHelpers.pas index 99d504d02..a7a143f83 100644 --- a/Src/UClassHelpers.pas +++ b/Src/UClassHelpers.pas @@ -11,13 +11,15 @@ unit UClassHelpers; +{ TODO: Separate different helpers into their own units, within a ClassHelpers + scope. E.g. ClassHelpers.Controls, ClassHelper.Graphics } interface uses // Delphi - Controls, Menus, ImgList, Graphics, ActnList; + Controls, Menus, ImgList, Graphics, ActnList, GIFImg; type @@ -70,6 +72,21 @@ TActionListHelper = class helper for TCustomActionList procedure Update; end; +type + /// 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. + TGIFImageHelper = class helper for TGIFImage + public + /// Load a GIF image from given resource. + /// HINSTANCE [in] Module containing resource. + /// string [in] Name of resource to be loaded. + /// + /// PChar [in] Type of resource to be loaded. + procedure LoadFromResource(const Module: HMODULE; const ResName: string; + const ResType: PChar); + end; + implementation @@ -174,5 +191,20 @@ procedure TActionListHelper.Update; Action.Update; end; +{ TGIFImageHelper } + +procedure TGIFImageHelper.LoadFromResource(const Module: HMODULE; + const ResName: string; const ResType: PChar); +var + Stm: TResourceStream; +begin + Stm := TResourceStream.Create(Module, ResName, ResType); + try + LoadFromStream(Stm); + finally + Stm.Free; + end; +end; + end. From 92f3f8ff402a6b9e43e5443f381495704bedce17 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 28 Nov 2021 20:44:22 +0000 Subject: [PATCH 005/330] Modify to use VCL GIF image unit Now use GIFImg unit (from VCL) instead of 3rd party GIFImage unit. FmSplash now uses class helper to load GIF from resources. --- Src/FmSplash.pas | 8 +++----- Src/UGIFImageList.pas | 4 ++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Src/FmSplash.pas b/Src/FmSplash.pas index 6e937dff2..8aeb9b223 100644 --- a/Src/FmSplash.pas +++ b/Src/FmSplash.pas @@ -66,11 +66,9 @@ implementation uses // Delphi - Windows, Graphics, - // 3rd party - GIFImage, + Windows, Graphics, GIFImg, // Project - UAppInfo, UColours, UStructs, UWindowSettings; + UAppInfo, UClassHelpers, UColours, UStructs, UWindowSettings; {$R *.dfm} @@ -149,7 +147,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; diff --git a/Src/UGIFImageList.pas b/Src/UGIFImageList.pas index 6448ff5a2..84c2fb9f3 100644 --- a/Src/UGIFImageList.pas +++ b/Src/UGIFImageList.pas @@ -72,8 +72,8 @@ implementation uses - // 3rd party - GIFImage, + // Delphi + GIFImg, UClassHelpers, // Project UComparers; From e328472528e0fd96f931879bd40ca5e4ae547f2e Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 28 Nov 2021 20:46:09 +0000 Subject: [PATCH 006/330] Refactor to use class helper Code that created a resource stream and then loaded it using TGIFImage.LoadFromStream was refactored to use TGIImage.LoadFromResource method provided by class helper. --- Src/UGIFImageList.pas | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Src/UGIFImageList.pas b/Src/UGIFImageList.pas index 84c2fb9f3..efed8a04e 100644 --- a/Src/UGIFImageList.pas +++ b/Src/UGIFImageList.pas @@ -127,21 +127,17 @@ function TGIFImageList.CreateBMPFromGIFRes(const GIFResName: string): TBitmap; @return Bitmap representation of GIF. } var - GIFStm: TStream; // stream used to access GIF in resources GIF: TGIFImage; // GIF image object begin - GIFStm := nil; GIF := TGIFImage.Create; try // Open stream onto GIF in HTML resources and load into GIF image object - GIFStm := TResourceStream.Create(HInstance, GIFResName, RT_HTML); - GIF.LoadFromStream(GIFStm); + GIF.LoadFromResource(HInstance, GIFResName, RT_HTML); // Make bitmap copy of GIF Result := TBitmap.Create; Result.Assign(GIF); Result.TransparentColor := Result.Canvas.Pixels[0, 0]; finally - GIFStm.Free; GIF.Free; end; end; From bcda322144ab442594572d7dc578891806afbbd6 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 28 Nov 2021 20:46:33 +0000 Subject: [PATCH 007/330] Remove 3rd party GIFImage unit from project --- Src/3rdParty/GIFImage.pas | 12957 ------------------------------------ Src/CodeSnip.dpr | 1 - Src/CodeSnip.dproj | 1 - 3 files changed, 12959 deletions(-) delete mode 100644 Src/3rdParty/GIFImage.pas 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" // -// 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 - 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/CodeSnip.dpr b/Src/CodeSnip.dpr index 64bebc304..0a5a6d02b 100644 --- a/Src/CodeSnip.dpr +++ b/Src/CodeSnip.dpr @@ -33,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', diff --git a/Src/CodeSnip.dproj b/Src/CodeSnip.dproj index b2b923cf6..f1ccf4093 100644 --- a/Src/CodeSnip.dproj +++ b/Src/CodeSnip.dproj @@ -36,7 +36,6 @@ MainSource - From 8a870804cc5c5f84dfacb3cf032435cae2f484ec Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 28 Nov 2021 20:50:24 +0000 Subject: [PATCH 008/330] Remove reference to 3rd part GIFImage unit from docs Removed from License.html, relevant Src/3rdParty/LICENSE file and from program about box. --- Docs/License.html | 123 ----------------------- Src/3rdParty/LICENSE | 4 - Src/Res/HTML/dlg-about-program-tplt.html | 3 - 3 files changed, 130 deletions(-) diff --git a/Docs/License.html b/Docs/License.html index f4daf756d..2dc05c17a 100644 --- a/Docs/License.html +++ b/Docs/License.html @@ -273,14 +273,6 @@

>Mozilla Public License 2.0. -
  • - -
    - Used by Src/3rdParty/GIFImage.pas. -
    -
  • Vadim Crit's TListViewEx License @@ -869,118 +861,6 @@


    -

    - TGIFImage License -

    - -

    TGIFImage is Copyright © 1997-99 Anders Melander. All rights - reserved.

    - -

    Please see copyright.txt for additional copyrights. -

    - -

    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).

    - -

    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!

    - -

    License

    - -

    This software, including documentation, source code, object code and/or - additional materials (TGIFImage) is owned by Anders Melander (the Author).

    - -

    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:

    - -
      -
    • integrate TGIFImage with your Applications, subject to the - redistribution terms below.
    • - -
    • modify or adapt TGIFImage in whole or in part for the development of - Applications based on TGIFImage.
    • - -
    • use portions of the TGIFImage source code or TGIFImage Demo Programs in - your own products.
    • -
    - -

    Redistribution rights

    - -

    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).

    - -

    Restrictions

    - -

    Without the expressed, written consent of the Author, you may NOT:

    - -
      -
    • distribute modified versions of TGIFImage, in whole or in part.
    • - -
    • rent or lease TGIFImage.
    • - -
    • sell any portion of TGIFImage on its own, without integrating it into - your Applications as Executable Code.
    • - -
    • bundle TGIFImage with commercial development libraries.
    • - -
    • charge for the value TGIFImage adds to your Applications.
    • -
    - - - -

    Limited warranty

    - -

    There is no warranty or other guarantee of fitness for this software, it is - provided solely "as is". Bug reports or fixes may be submitted, but there is no guarantee they will be acted upon.

    - -

    LZW license

    - -

    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.

    - -

    To avoid the use of the LZW algorithm for writing GIFs, TGIFImage can write - GIFs using a LZW compatible RLE compression method. See the - TGIFImage.Compression 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.

    - -

    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.

    - -

    The LZW patent expires - expired in 2004.

    - -

    Credit of work

    - -

    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.

    - -
    -

    Vadim Crit's TListViewEx License

    @@ -1783,9 +1663,6 @@

    The MD5 digest code used in this program is based on the RSA Data Security, Inc. MD5 Message-Digest Algorithm.

  • -
  • - This software is based, in part, on the work of Anders Melander. -
  • The TListViewEx component used in this program is copyright © 1999-2009 Vadim Crits. diff --git a/Src/3rdParty/LICENSE b/Src/3rdParty/LICENSE index b733afad0..e93ffd769 100644 --- a/Src/3rdParty/LICENSE +++ b/Src/3rdParty/LICENSE @@ -1,9 +1,5 @@ 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 diff --git a/Src/Res/HTML/dlg-about-program-tplt.html b/Src/Res/HTML/dlg-about-program-tplt.html index c26bc3091..3dabb69d2 100644 --- a/Src/Res/HTML/dlg-about-program-tplt.html +++ b/Src/Res/HTML/dlg-about-program-tplt.html @@ -71,9 +71,6 @@ The MD5 digest code used in this program is based on the RSA Data Security, Inc. MD5 Message-Digest Algorithm.
  • -
  • - This software is based, in part, on the work of Anders Melander. -
  • The TListViewEx component used in this program is copyright © 1999-2009 Vadim Crits. From ee4594cdf9f1afa1defa865a4320fd8ccb762bbf Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 28 Nov 2021 20:50:57 +0000 Subject: [PATCH 009/330] Remove reference to GIFImage.pas from header comments --- Src/UGIFImageList.pas | 3 --- 1 file changed, 3 deletions(-) diff --git a/Src/UGIFImageList.pas b/Src/UGIFImageList.pas index efed8a04e..bd3b3dd43 100644 --- a/Src/UGIFImageList.pas +++ b/Src/UGIFImageList.pas @@ -7,9 +7,6 @@ * * Image list descendant that enables representations of GIF images loaded from * HTML resource to be added. Resource names are mapped to image indices. - * - * Requires TGIFImage from GIFImage.pas by Anders Melander updated by Finn - * Tolderlund. } From de136964f1127d6f942e675173942955585d4aed Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 28 Nov 2021 20:53:24 +0000 Subject: [PATCH 010/330] Bump copyright year in header comments --- Src/CodeSnip.dpr | 2 +- Src/FmSplash.pas | 2 +- Src/Res/HTML/dlg-about-program-tplt.html | 2 +- Src/UClassHelpers.pas | 2 +- Src/UGIFImageList.pas | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Src/CodeSnip.dpr b/Src/CodeSnip.dpr index 0a5a6d02b..13ddad3aa 100644 --- a/Src/CodeSnip.dpr +++ b/Src/CodeSnip.dpr @@ -3,7 +3,7 @@ * 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) 2005-2020, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). * * CodeSnip application project file. } diff --git a/Src/FmSplash.pas b/Src/FmSplash.pas index 8aeb9b223..c83372f20 100644 --- a/Src/FmSplash.pas +++ b/Src/FmSplash.pas @@ -3,7 +3,7 @@ * 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) 2007-2020, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2007-2021, Peter Johnson (gravatar.com/delphidabbler). * * Implements the program's splash screen. } diff --git a/Src/Res/HTML/dlg-about-program-tplt.html b/Src/Res/HTML/dlg-about-program-tplt.html index 3dabb69d2..2b49094d6 100644 --- a/Src/Res/HTML/dlg-about-program-tplt.html +++ b/Src/Res/HTML/dlg-about-program-tplt.html @@ -9,7 +9,7 @@ * 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) 2005-2020, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). * * Template for content displayed in program tab of about dialog box. --> diff --git a/Src/UClassHelpers.pas b/Src/UClassHelpers.pas index a7a143f83..780568a83 100644 --- a/Src/UClassHelpers.pas +++ b/Src/UClassHelpers.pas @@ -3,7 +3,7 @@ * 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-2020, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2012-2021, Peter Johnson (gravatar.com/delphidabbler). * * Provides various class helpers for VCL classes. } diff --git a/Src/UGIFImageList.pas b/Src/UGIFImageList.pas index bd3b3dd43..27b048699 100644 --- a/Src/UGIFImageList.pas +++ b/Src/UGIFImageList.pas @@ -3,7 +3,7 @@ * 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-2020, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2008-2021, Peter Johnson (gravatar.com/delphidabbler). * * Image list descendant that enables representations of GIF images loaded from * HTML resource to be added. Resource names are mapped to image indices. From ebc6997a515ad2f82b670c3ffa9950de2947128b Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Mon, 29 Nov 2021 10:35:06 +0000 Subject: [PATCH 011/330] Update to PJSysInfo unit v5.7.1 Fixes #22 --- Src/3rdParty/PJSysInfo.pas | 144 +++++++++++++++++++++---------------- 1 file changed, 84 insertions(+), 60 deletions(-) diff --git a/Src/3rdParty/PJSysInfo.pas b/Src/3rdParty/PJSysInfo.pas index a2d276da5..96dffe1a7 100644 --- a/Src/3rdParty/PJSysInfo.pas +++ b/Src/3rdParty/PJSysInfo.pas @@ -3,10 +3,10 @@ * 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) 2001-2020, Peter Johnson (@delphidabbler). + * Copyright (C) 2001-2021, Peter Johnson (@delphidabbler). * - * $Rev: 2069 $ - * $Date: 2021-09-14 16:00:48 +0100 (Tue, 14 Sep 2021) $ + * $Rev: 2079 $ + * $Date: 2021-11-27 14:29:47 +0000 (Sat, 27 Nov 2021) $ * * This unit contains various static classes, constants, type definitions and * global variables for use in providing information about the host computer and @@ -1185,9 +1185,12 @@ implementation https://en.wikipedia.org/wiki/Windows_Server https://en.wikipedia.org/wiki/Windows_Server_2019 https://en.wikipedia.org/wiki/Windows_Server_2016 - https://tinyurl.com/y8tfadm2 - https://tinyurl.com/usupsz4a + https://tinyurl.com/y8tfadm2 (MS Windows Server release information) + https://tinyurl.com/usupsz4a (Win 11 Version Numbers & Build Versions) 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 @@ -1218,30 +1221,32 @@ implementation Win1020H1Build = 19041; // Windows 10 20H1 - version 2004 Win1020H2Build = 19042; // Windows 10 20H2 - version 20H2 Win1021H1Build = 19043; // Windows 10 21H1 - version 21H1 - { TODO: 2021-09-11 - - Win 21H2 due late 2021 - - Update following var name once Win21H2 released} - _Win1021H2Build = 19044; // Windows 10 21H2 - version 21H2 + Win1021H2Build = 19044; // Windows 10 21H2 - version 21H2 // Windows 11 ---------------------------------------------------------------- - { TODO: 2021-09-11 - - Add more Win11 versions as discovered. } - // NOTE: Preview and beta versions of Windows 11 report version 10.0 + // NOTE: Preview and beta & release versions of Windows 11 report version 10.0 Win11DevBuild = 21996; // Windows 11 version Dev // - 10.0.21996.1 (Insider version) Win11v21H2Build = 22000; // Version depends on revision # [Rev#]: - // Revision # 51..168: + // Revision # 51,65,71,100,120,132,168: // Windows 11 version 21H2 // - 10.0.22000.[Rev#] (Insider version) // Revision # 184 // Windows 11 version 21H2 // - 10.0.22000.184 (Beta Version) - // Revision # >=185 - // Windows 11 (unknown version) - Win11c21H2PreRel1Build = 22449; // Windows 11 version 21H2 + // Revision # 194 + // Windows 11 version 21H2 + // - ** 1st Public Release ** + Win11v21H2PreRel1Build = 22449; // Windows 11 version 21H2 // - 10.0.22449.000 (RSPRERELEASE) - Win11c21H2PreRel2Build = 22454; // Windows 11 version 21H2 + Win11v21H2PreRel2Build = 22454; // Windows 11 version 21H2 // - 10.0.22454.1000 (RSPRERELEASE) + Win11v21H2PreRel3Build = 22458; // Windows 11 version 21H2 + // - 10.0.22458.1000 (RSPRERELEASE) + Win11v21H2PreRel4Build = 22463; // Windows 11 version 21H2 + // - 10.0.22463.1000 (RSPRERELEASE) + Win11v21H2PreRel5Build = 22468; // Windows 11 version 21H2 + // - 10.0.22468.1000 (RSPRERELEASE) Win11FirstBuild = Win11DevBuild; // First build number of Windows 11 @@ -1724,13 +1729,16 @@ procedure InitPlatformIdEx; begin case InternalMinorVersion of 0: + // ** As of 2021/10/05 all releases of Windows 10 **and** + // Windows 11 report major version 10 and minor version 0 if (Win32ProductType <> VER_NT_DOMAIN_CONTROLLER) and (Win32ProductType <> VER_NT_SERVER) then begin if IsBuildNumber(Win10TH1Build) then begin + // First public release of Window 10 InternalBuildNumber := Win10TH1Build; - InternalExtraUpdateInfo := 'Version 1507'; // 1st Win 10 version + InternalExtraUpdateInfo := 'Version 1507'; end else if IsBuildNumber(Win10TH2Build) then begin @@ -1789,25 +1797,16 @@ procedure InitPlatformIdEx; InternalBuildNumber := Win1021H1Build; InternalExtraUpdateInfo := 'Version 21H1'; end - else if IsBuildNumber(_Win1021H2Build) then + else if IsBuildNumber(Win1021H2Build) then begin - { TODO: Added 2021/09/11 - - Release expected late 2021 - - Fix build number if necessary - - Remove underscore prefix from const name - - Fix value of InternalExtraUpdateInfo as required } - InternalBuildNumber := _Win1021H2Build; + // From 21H2 Windows 10 moves from a 6 monthly update cycle to a + // yearly cycle + InternalBuildNumber := Win1021H2Build; InternalExtraUpdateInfo := 'Version 21H2'; end // As of 2021-09-11, Win 11 pre-releases are reporting v10.0 // Details taken from: https://tinyurl.com/usupsz4a - // Correct according to above web oage as of 2021-09-11 - { TODO: Added 2021-09-11 - - Revisit URL to check for change following official release - of Windows 11 - - Add any further pre-release versions - - Check if final release has major version 11 - } + // Correct according to above web page as of 2021-09-11 else if IsBuildNumber(Win11DevBuild) then begin InternalBuildNumber := Win11DevBuild; @@ -1821,49 +1820,74 @@ procedure InitPlatformIdEx; // 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 := Win11v21H2Build; - if InternalRevisionNumber in [51, 65, 71, 100, 120, 132, 168] then - begin - InternalExtraUpdateInfo := Format( - 'Version 21H2 [Insider v10.0.%d.%d]', - [InternalBuildNumber, InternalRevisionNumber] - ); - end - else if InternalRevisionNumber = 184 then - begin - InternalExtraUpdateInfo := Format( - 'Version 21H2 [Beta v10.0.%d.%d]', - [InternalBuildNumber, InternalRevisionNumber] - ); - end - else - begin - InternalExtraUpdateInfo := Format( - 'Unknown release v10.0.%d.%d', - [InternalBuildNumber, InternalRevisionNumber] - ); + case InternalBuildNumber of + 194: + // First public release of Windows 11 + InternalExtraUpdateInfo := 'Version 21H2'; + 51, 65, 71, 100, 120, 132, 168: + InternalExtraUpdateInfo := Format( + 'Version 21H2 [Insider v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + 184: + InternalExtraUpdateInfo := Format( + 'Version 21H2 [Beta v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + else + InternalExtraUpdateInfo := Format( + 'Unknown release v10.0.%d.%d', + [InternalBuildNumber, InternalRevisionNumber] + ); end; end - else if IsBuildNumber(Win11c21H2PreRel1Build) then + else if IsBuildNumber(Win11v21H2PreRel1Build) then begin - InternalBuildNumber := Win11c21H2PreRel1Build; + InternalBuildNumber := Win11v21H2PreRel1Build; InternalExtraUpdateInfo := Format( 'Version 21H2 [RSPRERELEASE v10.0.%d.%d]', [InternalBuildNumber, InternalRevisionNumber] ); end - else if IsBuildNumber(Win11c21H2PreRel2Build) then + else if IsBuildNumber(Win11v21H2PreRel2Build) then begin - InternalBuildNumber := Win11c21H2PreRel2Build; + InternalBuildNumber := Win11v21H2PreRel2Build; InternalExtraUpdateInfo := Format( 'Version 21H2 [RSPRERELEASE v10.0.%d.%d]', [InternalBuildNumber, InternalRevisionNumber] ); - end; + end + else if IsBuildNumber(Win11v21H2PreRel3Build) then + begin + InternalBuildNumber := Win11v21H2PreRel3Build; + InternalExtraUpdateInfo := Format( + 'Version 21H2 [RSPRERELEASE v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + end + else if IsBuildNumber(Win11v21H2PreRel4Build) then + begin + InternalBuildNumber := Win11v21H2PreRel4Build; + InternalExtraUpdateInfo := Format( + 'Version 21H2 [RSPRERELEASE v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + end + else if IsBuildNumber(Win11v21H2PreRel5Build) then + begin + InternalBuildNumber := Win11v21H2PreRel5Build; + InternalExtraUpdateInfo := Format( + 'Version 21H2 [RSPRERELEASE v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + end end - else + else // Win32ProductType in [VER_NT_DOMAIN_CONTROLLER, VER_NT_SERVER] begin - // Check for Win Server 2016 echnical previews. + // Check for Win Server 2016 technical previews. // We don't check for TP1 // here because that reported version 6.4, // not version 10! if IsBuildNumber(Win2016TP2Build) then @@ -2685,7 +2709,7 @@ class function TPJOSInfo.Product: TPJOSProduct; if InternalBuildNumber < Win11FirstBuild then Result := osWin10 else - // As of 2021-09-11 Win 11 is reporting version 10.0 + // ** As of 2021-10-05 Win 11 is reporting version 10.0! Result := osWin11; end else From ea5acb3237a2f71fca86bea336c17b256f9f3161 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Mon, 29 Nov 2021 11:10:43 +0000 Subject: [PATCH 012/330] Add help about "Save & Close" Dependencies dlg button Fixes #14 --- Src/Help/HTML/dlg_dependencies.htm | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/Src/Help/HTML/dlg_dependencies.htm b/Src/Help/HTML/dlg_dependencies.htm index d438ef605..6f2b7a225 100644 --- a/Src/Help/HTML/dlg_dependencies.htm +++ b/Src/Help/HTML/dlg_dependencies.htm @@ -29,12 +29,18 @@

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

    - Depends Upon Tab + Tabs

    +

    + 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 @@ -47,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 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. +

    From 1ba059f1aee49fa797622094a3d049a76b02afd1 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Mon, 29 Nov 2021 11:24:54 +0000 Subject: [PATCH 013/330] Add new character consts --- Src/UConsts.pas | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Src/UConsts.pas b/Src/UConsts.pas index 7f29d51c8..43edfd156 100644 --- a/Src/UConsts.pas +++ b/Src/UConsts.pas @@ -29,8 +29,12 @@ interface CR = #13; // carriage return character SUB = #26; // ASCII SUB character ESC = #27; // escape character + SINGLEQUOTE = ''''; // single quote character DOUBLEQUOTE = '"'; // double quote character + AMPERSAND = '&'; // ampersand character + GT = '>'; // greater-than / closing angle bracket character + LT = '<'; // less-than / opening angle bracket character CRLF = CR + LF; // carriage return followed by line feed EOL = CRLF; // end of line character sequence for Windows systems From 6fea1b8decf668257958e381dade3222b335bb34 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Mon, 29 Nov 2021 11:27:50 +0000 Subject: [PATCH 014/330] Rewrite THTML.Entities to operate better with Unicode Fixes #17 --- Src/UHTMLUtils.pas | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/Src/UHTMLUtils.pas b/Src/UHTMLUtils.pas index cc7cf2a54..9f8eb1056 100644 --- a/Src/UHTMLUtils.pas +++ b/Src/UHTMLUtils.pas @@ -227,18 +227,12 @@ class function THTML.Entities(const Text: string): string; for Ch in Text do begin case Ch of - '<': - SB.Append('<'); - '>': - SB.Append('>'); - '&': - SB.Append('&'); - DOUBLEQUOTE: - SB.Append('"'); - #0..#9, #11, #12, #14..#31: - SB.Append('&#' + IntToStr(Ord(Ch)) + ';') - else - SB.Append(Ch); + LT: SB.Append('<'); + GT: SB.Append('>'); + SINGLEQUOTE: SB.Append('''); + DOUBLEQUOTE: SB.Append('"'); + AMPERSAND: SB.Append('&'); + else SB.Append(Ch); end; end; Result := SB.ToString; From 9faeb58f7179c6a2b6a1bd3be3e575de78a15b08 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Mon, 29 Nov 2021 11:40:44 +0000 Subject: [PATCH 015/330] Bump copyright date in header comments --- Src/UConsts.pas | 2 +- Src/UHTMLUtils.pas | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Src/UConsts.pas b/Src/UConsts.pas index 43edfd156..8d52f2ca1 100644 --- a/Src/UConsts.pas +++ b/Src/UConsts.pas @@ -3,7 +3,7 @@ * 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-2020, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2009-2021, Peter Johnson (gravatar.com/delphidabbler). * * Defines various character, string and resource id constants. } diff --git a/Src/UHTMLUtils.pas b/Src/UHTMLUtils.pas index 9f8eb1056..e1f8fc61f 100644 --- a/Src/UHTMLUtils.pas +++ b/Src/UHTMLUtils.pas @@ -3,7 +3,7 @@ * 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) 2005-2020, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). * * Helper interfaces and classes used to generate HTML. } From 7b3d822202f2a557e5925902872c1429e0c8238a Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Mon, 29 Nov 2021 11:53:24 +0000 Subject: [PATCH 016/330] Update change log with details of changes in v4.18.1 --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a55ec99ac..128fdd7cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,17 @@ This change log begins with the first ever pre-release version of _CodeSnip_. Re From v4.1.0 the version numbering has attempted to adhere to the principles of [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Release v4.18.1 of 29 November 2021 + +* Improved handling of control and whitespace characters in generated HTML: revised which characters were converted to HTML character attributes / entities. +* Fixed error in title of _Save Annotated Source_ dialogue box. +* Replaced use 3rd party `GIFImage` unit with similar `GIFImg` unit from Delphi XE VCL. +* Corrected help topic for _Dependencies_ dialogue box to describe _Save & Close_ button. +* Operating system detection code was updated to correctly detect Windows 11 and Windows 10 version 21H2. +* Some refactoring. +* Updated license document (`License.html`) following removal of dependency on GIFImage unit. + + ## Release v4.18.0 of 13 September 2021 * Added support for test compilation with, and detection of, Delphi 11 Alexandria. From 2b56b74c231b7235679d21e1b388dd191343109a Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Mon, 29 Nov 2021 11:55:58 +0000 Subject: [PATCH 017/330] Update version numbers to 4.18.1 build 262 --- Src/VCodeSnip.vi | 4 ++-- Src/VCodeSnipPortable.vi | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Src/VCodeSnip.vi b/Src/VCodeSnip.vi index 33f2f229a..6f19f82fa 100644 --- a/Src/VCodeSnip.vi +++ b/Src/VCodeSnip.vi @@ -8,8 +8,8 @@ [Fixed File Info] -File Version #=4, 18, 0, 261 -Product Version #=4, 18, 0, 0 +File Version #=4, 18, 1, 262 +Product Version #=4, 18, 1, 0 File OS=4 File Type=1 File Sub-Type=0 diff --git a/Src/VCodeSnipPortable.vi b/Src/VCodeSnipPortable.vi index cc294d05d..2458ead40 100644 --- a/Src/VCodeSnipPortable.vi +++ b/Src/VCodeSnipPortable.vi @@ -8,8 +8,8 @@ [Fixed File Info] -File Version #=4, 18, 0, 261 -Product Version #=4, 18, 0, 0 +File Version #=4, 18, 1, 262 +Product Version #=4, 18, 1, 0 File OS=4 File Type=1 File Sub-Type=0 From 80bf294a79ebe2c54c77e3e94eb1fca59639f60c Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 17 Dec 2021 10:38:18 +0000 Subject: [PATCH 018/330] Update MPL URL in header to use https:// --- Build.html | 2 +- Docs/Design/FileFormats/backup.html | 2 +- Docs/Design/FileFormats/config.html | 2 +- Docs/Design/FileFormats/export.html | 2 +- Docs/Design/FileFormats/favourites.html | 2 +- Docs/Design/FileFormats/index.html | 2 +- Docs/Design/FileFormats/main-db.html | 2 +- Docs/Design/FileFormats/main.css | 2 +- Docs/Design/FileFormats/saved.html | 2 +- Docs/Design/FileFormats/selection.html | 2 +- Docs/Design/FileFormats/test-unit.html | 2 +- Docs/Design/FileFormats/user-db.html | 2 +- Docs/LICENSE | 2 +- Docs/License.html | 4 ++-- Docs/MPL-2.0-Boilerplate.txt | 10 +++++----- Src/3rdParty/PJMD5.pas | 2 +- Src/3rdParty/PJShellFolders.pas | 2 +- Src/3rdParty/PJStreamWrapper.pas | 2 +- Src/3rdParty/PJSysInfo.pas | 2 +- Src/3rdParty/PJVersionInfo.pas | 2 +- Src/3rdParty/PJWdwState.pas | 2 +- Src/ActiveText.UHTMLRenderer.pas | 2 +- Src/ActiveText.UMain.pas | 2 +- Src/ActiveText.URTFRenderer.pas | 2 +- Src/ActiveText.UTextRenderer.pas | 2 +- Src/ActiveText.UValidator.pas | 2 +- Src/Browser.IntfDocHostUI.pas | 2 +- Src/Browser.UControlHelper.pas | 2 +- Src/Browser.UController.pas | 2 +- Src/Browser.UHTMLEvents.pas | 2 +- Src/Browser.UHighlighter.pas | 2 +- Src/Browser.UIOMgr.pas | 2 +- Src/Browser.UNulUIHandler.pas | 2 +- Src/Browser.UUIMgr.pas | 2 +- Src/CodeSnip.dpr | 2 +- Src/CompilerChecks.inc | 2 +- Src/Compilers.UBDS.pas | 2 +- Src/Compilers.UBorland.pas | 2 +- Src/Compilers.UCompilerBase.pas | 2 +- Src/Compilers.UCompilers.pas | 2 +- Src/Compilers.UDelphi.pas | 2 +- Src/Compilers.UFreePascal.pas | 2 +- Src/Compilers.UGlobals.pas | 2 +- Src/Compilers.URunner.pas | 2 +- Src/Compilers.USearchDirs.pas | 2 +- Src/DB.UCategory.pas | 2 +- Src/DB.UDatabaseIO.pas | 2 +- Src/DB.UMain.pas | 2 +- Src/DB.UMetaData.pas | 2 +- Src/DB.USnippet.pas | 2 +- Src/DB.USnippetKind.pas | 2 +- Src/DBIO.UFileIOIntf.pas | 2 +- Src/DBIO.UIniDataReader.pas | 2 +- Src/DBIO.UNulDataReader.pas | 2 +- Src/DBIO.UXMLDataIO.pas | 2 +- Src/ExternalObj.ridl | 2 +- Src/Favourites.UFavourites.pas | 2 +- Src/Favourites.UManager.pas | 2 +- Src/Favourites.UPersist.pas | 2 +- Src/FirstRun.FmV4ConfigDlg.pas | 2 +- Src/FirstRun.FmWhatsNew.pas | 2 +- Src/FirstRun.UConfigFile.pas | 2 +- Src/FirstRun.UDatabase.pas | 2 +- Src/FirstRun.UIniFile.pas | 2 +- Src/FirstRun.UInstallInfo.pas | 2 +- Src/FirstRun.UMain.pas | 2 +- Src/FmAboutDlg.pas | 2 +- Src/FmActiveTextPreviewDlg.pas | 2 +- Src/FmAddCategoryDlg.pas | 2 +- Src/FmBase.pas | 2 +- Src/FmBugReportBaseDlg.pas | 2 +- Src/FmCategoryEditDlg.pas | 2 +- Src/FmCodeExportDlg.pas | 2 +- Src/FmCodeImportDlg.pas | 4 ++-- Src/FmCompErrorDlg.pas | 2 +- Src/FmCompilersDlg.FrBase.pas | 2 +- Src/FmCompilersDlg.FrCompiler.pas | 2 +- Src/FmCompilersDlg.FrLog.pas | 2 +- Src/FmCompilersDlg.FrNamespaces.pas | 2 +- Src/FmCompilersDlg.FrSearchDirs.pas | 2 +- Src/FmCompilersDlg.FrSwitches.pas | 2 +- Src/FmCompilersDlg.UBannerMgr.pas | 2 +- Src/FmCompilersDlg.UCompilerListMgr.pas | 2 +- Src/FmCompilersDlg.pas | 2 +- Src/FmDBUpdateDlg.pas | 2 +- Src/FmDeleteCategoryDlg.pas | 2 +- Src/FmDependenciesDlg.pas | 2 +- Src/FmDuplicateSnippetDlg.pas | 2 +- Src/FmEasterEgg.pas | 2 +- Src/FmFavouritesDlg.pas | 2 +- Src/FmFindCompilerDlg.pas | 2 +- Src/FmFindTextDlg.pas | 2 +- Src/FmFindXRefsDlg.pas | 2 +- Src/FmGenericDlg.pas | 2 +- Src/FmGenericModalDlg.pas | 2 +- Src/FmGenericNonModalDlg.pas | 2 +- Src/FmGenericOKDlg.pas | 2 +- Src/FmGenericViewDlg.pas | 2 +- Src/FmHelpAware.pas | 2 +- Src/FmMain.pas | 2 +- Src/FmNewHiliterNameDlg.pas | 2 +- Src/FmPreferencesDlg.pas | 2 +- Src/FmPreviewDlg.pas | 2 +- Src/FmPrintDlg.pas | 2 +- Src/FmRenameCategoryDlg.pas | 2 +- Src/FmSWAGImportDlg.pas | 2 +- Src/FmSelectionSearchDlg.pas | 2 +- ...FmSnippetsEditorDlg.FrActiveTextEditor.pas | 2 +- Src/FmSnippetsEditorDlg.pas | 2 +- Src/FmSplash.pas | 2 +- Src/FmTestCompileDlg.pas | 2 +- Src/FmTrappedBugReportDlg.pas | 2 +- Src/FmUserBugReportDlg.pas | 2 +- Src/FmUserDataPathDlg.pas | 2 +- Src/FmUserHiliterMgrDlg.pas | 2 +- Src/FmWaitDlg.pas | 2 +- Src/FmWizardDlg.pas | 2 +- Src/FrBrowserBase.pas | 2 +- Src/FrCategoryDescEdit.pas | 2 +- Src/FrCategoryList.pas | 2 +- Src/FrCheckedTV.pas | 2 +- Src/FrCodeGenPrefs.pas | 2 +- Src/FrDetail.pas | 2 +- Src/FrDetailView.pas | 2 +- Src/FrDisplayPrefs.pas | 2 +- Src/FrEasterEgg.pas | 2 +- Src/FrFixedHTMLDlg.pas | 2 +- Src/FrGeneralPrefs.pas | 2 +- Src/FrHTMLDlg.pas | 2 +- Src/FrHTMLPreview.pas | 2 +- Src/FrHTMLTpltDlg.pas | 2 +- Src/FrHiliterPrefs.pas | 2 +- Src/FrMemoPreview.pas | 2 +- Src/FrNewsPrefs.pas | 2 +- Src/FrOverview.pas | 2 +- Src/FrPrefsBase.pas | 2 +- Src/FrPrintingPrefs.pas | 2 +- Src/FrProgress.pas | 2 +- Src/FrRTFPreview.pas | 2 +- Src/FrRTFShowCase.pas | 2 +- Src/FrSelectSnippets.pas | 2 +- Src/FrSelectSnippetsBase.pas | 2 +- Src/FrSelectUserSnippets.pas | 2 +- Src/FrSnippetLayoutPrefs.pas | 2 +- Src/FrSourcePrefs.pas | 2 +- Src/FrTextPreview.pas | 2 +- Src/FrTitled.pas | 2 +- Src/HTML.hrc | 2 +- Src/Help/CSS/codesnip.css | 2 +- Src/Help/CodeSnip.hhp | 2 +- Src/Help/HTML/about_compiler_checks.htm | 2 +- Src/Help/HTML/about_swag.htm | 2 +- Src/Help/HTML/detail_pane.htm | 2 +- Src/Help/HTML/dlg_about.htm | 2 +- Src/Help/HTML/dlg_addcategory.htm | 2 +- Src/Help/HTML/dlg_backup.htm | 2 +- Src/Help/HTML/dlg_configcompilers.htm | 2 +- Src/Help/HTML/dlg_deletecategory.htm | 2 +- Src/Help/HTML/dlg_dependencies.htm | 2 +- Src/Help/HTML/dlg_dependencies_edit.htm | 2 +- Src/Help/HTML/dlg_duplicatesnippet.htm | 2 +- Src/Help/HTML/dlg_editsnippet.htm | 2 +- Src/Help/HTML/dlg_elementcolour.htm | 2 +- Src/Help/HTML/dlg_export.htm | 2 +- Src/Help/HTML/dlg_exportfile.htm | 2 +- Src/Help/HTML/dlg_favourites.htm | 2 +- Src/Help/HTML/dlg_findcompiler.htm | 2 +- Src/Help/HTML/dlg_findtext.htm | 2 +- Src/Help/HTML/dlg_findxrefs.htm | 2 +- Src/Help/HTML/dlg_firstrun.htm | 2 +- Src/Help/HTML/dlg_hilitemgr.htm | 2 +- Src/Help/HTML/dlg_import.htm | 2 +- Src/Help/HTML/dlg_importfile.htm | 2 +- Src/Help/HTML/dlg_loadselection.htm | 2 +- Src/Help/HTML/dlg_moveuserdb.htm | 2 +- Src/Help/HTML/dlg_pagesetup.htm | 2 +- Src/Help/HTML/dlg_preferences.htm | 2 +- Src/Help/HTML/dlg_prefs_codegen.htm | 2 +- Src/Help/HTML/dlg_prefs_display.htm | 2 +- Src/Help/HTML/dlg_prefs_general.htm | 2 +- Src/Help/HTML/dlg_prefs_hiliter.htm | 2 +- Src/Help/HTML/dlg_prefs_printing.htm | 2 +- Src/Help/HTML/dlg_prefs_snippetlayout.htm | 2 +- Src/Help/HTML/dlg_prefs_sourcecode.htm | 2 +- Src/Help/HTML/dlg_print.htm | 2 +- Src/Help/HTML/dlg_renamecategory.htm | 2 +- Src/Help/HTML/dlg_restore.htm | 2 +- Src/Help/HTML/dlg_savehiliter.htm | 2 +- Src/Help/HTML/dlg_saveselection.htm | 2 +- Src/Help/HTML/dlg_savesnippet.htm | 2 +- Src/Help/HTML/dlg_saveunit.htm | 2 +- Src/Help/HTML/dlg_selectcompiler.htm | 2 +- Src/Help/HTML/dlg_selectroutines.htm | 2 +- Src/Help/HTML/dlg_swagimport.htm | 2 +- Src/Help/HTML/dlg_testcompile.htm | 2 +- Src/Help/HTML/dlg_trappedbugreport.htm | 10 +++++----- Src/Help/HTML/dlg_update.htm | 2 +- Src/Help/HTML/dlg_userbugreport.htm | 2 +- .../HTML/explain_all_compilers_hidden.htm | 2 +- Src/Help/HTML/faqs.htm | 2 +- Src/Help/HTML/license.htm | 2 +- Src/Help/HTML/main_display.htm | 2 +- Src/Help/HTML/main_menu.htm | 2 +- Src/Help/HTML/markup_editor.htm | 2 +- Src/Help/HTML/menu_categories.htm | 2 +- Src/Help/HTML/menu_compile.htm | 2 +- Src/Help/HTML/menu_database.htm | 2 +- Src/Help/HTML/menu_edit.htm | 2 +- Src/Help/HTML/menu_file.htm | 2 +- Src/Help/HTML/menu_help.htm | 2 +- Src/Help/HTML/menu_search.htm | 2 +- Src/Help/HTML/menu_snippets.htm | 2 +- Src/Help/HTML/menu_tools.htm | 2 +- Src/Help/HTML/menu_view.htm | 2 +- Src/Help/HTML/navigation.htm | 2 +- Src/Help/HTML/new.htm | 2 +- Src/Help/HTML/overview_pane.htm | 2 +- Src/Help/HTML/quickstart.htm | 2 +- Src/Help/HTML/reml.htm | 2 +- Src/Help/HTML/snippet_class.htm | 2 +- Src/Help/HTML/snippet_constant.htm | 2 +- Src/Help/HTML/snippet_freeform.htm | 2 +- Src/Help/HTML/snippet_kinds.htm | 2 +- Src/Help/HTML/snippet_routine.htm | 2 +- Src/Help/HTML/snippet_type.htm | 2 +- Src/Help/HTML/snippet_unit.htm | 2 +- Src/Help/HTML/task_addsnippets.htm | 2 +- Src/Help/HTML/task_backup.htm | 2 +- Src/Help/HTML/task_copysnippet.htm | 2 +- Src/Help/HTML/task_customise.htm | 2 +- Src/Help/HTML/task_export.htm | 2 +- Src/Help/HTML/task_generateunit.htm | 2 +- Src/Help/HTML/task_printroutine.htm | 2 +- Src/Help/HTML/task_savesnippet.htm | 2 +- Src/Help/HTML/task_search.htm | 2 +- Src/Help/HTML/task_testcompile.htm | 2 +- Src/Help/HTML/task_update.htm | 2 +- Src/Help/HTML/tasks.htm | 2 +- Src/Help/HTML/welcome.htm | 2 +- Src/Help/Index.hhk | 2 +- Src/Help/TOC.hhc | 2 +- Src/Hiliter.UAttrs.pas | 2 +- Src/Hiliter.UCSS.pas | 2 +- Src/Hiliter.UFileHiliter.pas | 2 +- Src/Hiliter.UGlobals.pas | 2 +- Src/Hiliter.UHiliters.pas | 2 +- Src/Hiliter.UPasLexer.pas | 2 +- Src/Hiliter.UPasParser.pas | 2 +- Src/Hiliter.UPersist.pas | 2 +- Src/Install/Assets/LICENSE | 2 +- Src/Install/CodeSnip.iss | 4 ++-- Src/Install/DataLocations.ps | Bin 5038 -> 5038 bytes Src/Install/EventHandlers.ps | Bin 7085 -> 7086 bytes Src/Install/Unicode.ps | Bin 2723 -> 2724 bytes Src/Install/UpdateDBase.ps | Bin 1986 -> 1987 bytes Src/Install/UpdateIni.ps | Bin 3804 -> 3805 bytes Src/Install/VersionInfo.ps | Bin 3322 -> 3323 bytes Src/IntfAligner.pas | 2 +- Src/IntfCommon.pas | 2 +- Src/IntfFrameMgrs.pas | 2 +- Src/IntfNotifier.pas | 2 +- Src/IntfPreview.pas | 2 +- Src/Makefile | 2 +- Src/Res/CSS/detail.css | 2 +- Src/Res/CSS/easteregg.css | 2 +- Src/Res/HTML/dlg-about-database-tplt.html | 2 +- Src/Res/HTML/dlg-about-head-tplt.html | 2 +- Src/Res/HTML/dlg-about-program-tplt.html | 2 +- Src/Res/HTML/dlg-activetext-preview-tplt.html | 2 +- Src/Res/HTML/dlg-comperror-tplt.html | 2 +- Src/Res/HTML/dlg-dbupdate-finish.html | 2 +- Src/Res/HTML/dlg-dbupdate-intro-tplt.html | 2 +- Src/Res/HTML/dlg-dbupdate-load.html | 4 ++-- Src/Res/HTML/dlg-easter-egg.html | 2 +- Src/Res/HTML/dlg-swag-import-intro-tplt.html | 2 +- Src/Res/HTML/dlg-swag-import-outro-tplt.html | 2 +- Src/Res/HTML/dlg-whatsnew.html | 2 +- Src/Res/HTML/info-basic-tplt.html | 2 +- Src/Res/HTML/info-empty-selection-tplt.html | 4 ++-- Src/Res/HTML/info-snippet-list-tplt.html | 6 +++--- Src/Res/HTML/info-snippet-tplt.html | 6 +++--- Src/Res/HTML/welcome-tplt.html | 4 ++-- Src/Res/Misc/CodeSnip.manifest | 2 +- Src/Res/Scripts/easteregg.js | 2 +- Src/Res/Scripts/external.js | 2 +- Src/Res/Scripts/overflowXFix.js | 6 +++--- Src/Resources.rc | 2 +- Src/SWAG.UCommon.pas | 2 +- Src/SWAG.UImporter.pas | 2 +- Src/SWAG.UPacketCache.pas | 2 +- Src/SWAG.UReader.pas | 2 +- Src/SWAG.UVersion.pas | 2 +- Src/SWAG.UXMLProcessor.pas | 2 +- Src/UActionFactory.pas | 2 +- Src/UAnchors.pas | 2 +- Src/UAppInfo.pas | 2 +- Src/UBaseObjects.pas | 2 +- Src/UBox.pas | 2 +- Src/UBrowseForFolderDlg.pas | 2 +- Src/UBrowseProtocol.pas | 2 +- Src/UCSSBuilder.pas | 2 +- Src/UCSSUtils.pas | 2 +- Src/UCategoryAction.pas | 2 +- Src/UCategoryListAdapter.pas | 2 +- Src/UClassHelpers.pas | 2 +- Src/UClipboardHelper.pas | 2 +- Src/UCodeImportExport.pas | 2 +- Src/UCodeImportMgr.pas | 2 +- Src/UCodeShareMgr.pas | 2 +- Src/UColorBoxEx.pas | 2 +- Src/UColorDialogEx.pas | 6 +++--- Src/UColours.pas | 2 +- Src/UCommandBars.pas | 2 +- Src/UCommonDlg.pas | 2 +- Src/UCompResHTML.pas | 2 +- Src/UComparers.pas | 2 +- Src/UCompileMgr.pas | 2 +- Src/UCompileResultsLBMgr.pas | 2 +- Src/UConsoleApp.pas | 2 +- Src/UConsts.pas | 2 +- Src/UContainers.pas | 2 +- Src/UControlStateMgr.pas | 2 +- Src/UCopyInfoMgr.pas | 2 +- Src/UCopySourceMgr.pas | 2 +- Src/UCopyViewMgr.pas | 2 +- Src/UCtrlArranger.pas | 2 +- Src/UDBUpdateMgr.pas | 2 +- Src/UDOSDateTime.pas | 2 +- Src/UDataBackupMgr.pas | 2 +- Src/UDataStreamIO.pas | 2 +- Src/UDatabaseLoader.pas | 2 +- Src/UDatabaseLoaderUI.pas | 2 +- Src/UDetailPageHTML.pas | 2 +- Src/UDetailPageLoader.pas | 2 +- Src/UDetailTabAction.pas | 2 +- Src/UDialogMgr.pas | 2 +- Src/UDirectoryCopier.pas | 2 +- Src/UDispatchList.pas | 2 +- Src/UDlgHelper.pas | 2 +- Src/UDropDownButtons.pas | 2 +- Src/UEditSnippetAction.pas | 2 +- Src/UEmailHelper.pas | 2 +- Src/UEncodings.pas | 2 +- Src/UExceptions.pas | 2 +- Src/UExeFileType.pas | 2 +- Src/UFileProtocol.pas | 2 +- Src/UFileUpdater.pas | 2 +- Src/UFolderBackup.pas | 2 +- Src/UFontHelper.pas | 2 +- Src/UFormAligner.pas | 2 +- Src/UGIFImageList.pas | 2 +- Src/UGraphicUtils.pas | 2 +- Src/UGroups.pas | 2 +- Src/UHTMLBuilder.pas | 2 +- Src/UHTMLDOMHelper.pas | 2 +- Src/UHTMLHelp.pas | 2 +- Src/UHTMLHelpMgr.pas | 2 +- Src/UHTMLTemplate.pas | 2 +- Src/UHTMLUtils.pas | 2 +- Src/UHTTPProtocol.pas | 2 +- Src/UHelpMgr.pas | 2 +- Src/UHelpProtocol.pas | 2 +- Src/UHexUtils.pas | 2 +- Src/UHiddenRichEdit.pas | 2 +- Src/UHiddenWindow.pas | 2 +- Src/UHistory.pas | 2 +- Src/UHistoryMenus.pas | 2 +- Src/UIOUtils.pas | 2 +- Src/UIStringList.pas | 2 +- Src/UImageTags.pas | 2 +- Src/UIniDataLoader.pas | 2 +- Src/UInitialLetter.pas | 2 +- Src/UJavaScriptUtils.pas | 2 +- Src/UKeysHelper.pas | 2 +- Src/ULEDImageList.pas | 2 +- Src/ULinkAction.pas | 2 +- Src/ULocales.pas | 2 +- Src/UMainDBFileReader.pas | 2 +- Src/UMainDisplayMgr.pas | 2 +- Src/UMarquee.pas | 2 +- Src/UMeasurement.pas | 2 +- Src/UMemoCaretPosDisplayMgr.pas | 2 +- Src/UMemoHelper.pas | 2 +- Src/UMemoProgBarMgr.pas | 2 +- Src/UMenus.pas | 2 +- Src/UMessageBox.pas | 2 +- Src/UMessageWindow.pas | 2 +- Src/UMultiCastEvents.pas | 2 +- Src/UNotifier.pas | 2 +- Src/UNulDropTarget.pas | 2 +- Src/UNulFormAligner.pas | 2 +- Src/UOleClientSite.pas | 2 +- Src/UOpenDialogEx.pas | 6 +++--- Src/UOpenDialogHelper.pas | 2 +- Src/UOverviewTreeBuilder.pas | 2 +- Src/UOverviewTreeState.pas | 2 +- Src/UPageSetupDialogEx.pas | 2 +- Src/UPageSetupDlgMgr.pas | 2 +- Src/UPipe.pas | 2 +- Src/UPreferences.pas | 2 +- Src/UPrintDocuments.pas | 2 +- Src/UPrintEngine.pas | 2 +- Src/UPrintInfo.pas | 2 +- Src/UPrintMgr.pas | 2 +- Src/UProtocols.pas | 2 +- Src/UQuery.pas | 2 +- Src/UREMLDataIO.pas | 6 +++--- Src/URTFBuilder.pas | 2 +- Src/URTFCategoryDoc.pas | 2 +- Src/URTFSnippetDoc.pas | 2 +- Src/URTFStyles.pas | 2 +- Src/URTFUtils.pas | 2 +- Src/UReservedCategories.pas | 2 +- Src/UResourceUtils.pas | 2 +- Src/USaveDialogEx.pas | 6 +++--- Src/USaveSnippetMgr.pas | 2 +- Src/USaveSourceDlg.pas | 2 +- Src/USaveSourceMgr.pas | 2 +- Src/USaveUnitMgr.pas | 4 ++-- Src/USearch.pas | 2 +- Src/USelectionIOMgr.pas | 2 +- Src/USettings.pas | 2 +- Src/UShowCaseCtrl.pas | 2 +- Src/USimpleDispatch.pas | 2 +- Src/USingleton.pas | 2 +- Src/USnipKindListAdapter.pas | 2 +- Src/USnippetAction.pas | 2 +- Src/USnippetCreditsParser.pas | 2 +- Src/USnippetDoc.pas | 2 +- Src/USnippetExtraHelper.pas | 2 +- Src/USnippetHTML.pas | 2 +- Src/USnippetIDListIOHandler.pas | 2 +- Src/USnippetIDs.pas | 2 +- Src/USnippetPageHTML.pas | 2 +- Src/USnippetPageStructure.pas | 2 +- Src/USnippetSourceGen.pas | 4 ++-- Src/USnippetValidator.pas | 2 +- Src/USnippetsChkListMgr.pas | 2 +- Src/USnippetsTVDraw.pas | 2 +- Src/USourceFileInfo.pas | 2 +- Src/USourceGen.pas | 2 +- Src/UStartUp.pas | 2 +- Src/UStatusBarMgr.pas | 2 +- Src/UStrUtils.pas | 4 ++-- Src/UStringReader.pas | 2 +- Src/UStructs.pas | 2 +- Src/USystemInfo.pas | 2 +- Src/UTVCheckBoxes.pas | 2 +- Src/UTaggedTextLexer.pas | 2 +- Src/UTestCompile.pas | 2 +- Src/UTestCompileUI.pas | 2 +- Src/UTestUnit.pas | 2 +- Src/UTestUnitDlgMgr.pas | 2 +- Src/UTextSnippetDoc.pas | 2 +- Src/UThemesEx.pas | 2 +- Src/UToolButtonEx.pas | 2 +- Src/UUIWidgetImages.pas | 2 +- Src/UURIEncode.pas | 2 +- Src/UUniqueID.pas | 2 +- Src/UUnitAnalyser.pas | 2 +- Src/UUnitsChkListMgr.pas | 2 +- Src/UUrl.pas | 2 +- Src/UUrlMonEx.pas | 2 +- Src/UUserDBBackup.pas | 2 +- Src/UUserDBMgr.pas | 2 +- Src/UUserDBMove.pas | 2 +- Src/UUtils.pas | 2 +- Src/UVersionInfo.pas | 2 +- Src/UView.pas | 2 +- Src/UViewItemAction.pas | 2 +- Src/UViewItemTreeNode.pas | 2 +- Src/UWBCommandBars.pas | 2 +- Src/UWBExternal.pas | 2 +- Src/UWBPopupMenus.pas | 2 +- Src/UWaitForThreadUI.pas | 2 +- Src/UWarnings.pas | 2 +- Src/UWindowSettings.pas | 2 +- Src/UXMLDocConsts.pas | 2 +- Src/UXMLDocHelper.pas | 2 +- Src/UXMLDocumentEx.pas | 2 +- Src/VCodeSnip.vi | 4 ++-- Src/VCodeSnipPortable.vi | 4 ++-- 482 files changed, 509 insertions(+), 509 deletions(-) diff --git a/Build.html b/Build.html index 35d54831c..381d77f9f 100644 --- a/Build.html +++ b/Build.html @@ -4,7 +4,7 @@ diff --git a/Src/Res/HTML/dlg-easter-egg.html b/Src/Res/HTML/dlg-easter-egg.html index 51d11a0c8..6a9c7e310 100644 --- a/Src/Res/HTML/dlg-easter-egg.html +++ b/Src/Res/HTML/dlg-easter-egg.html @@ -7,7 +7,7 @@ @@ -1602,8 +1602,8 @@

    available upon request from time to time.

    Creative Commons may be contacted at http://creativecommons.org/.

    + href="https://creativecommons.org" + >https://creativecommons.org/.


    @@ -1679,15 +1679,15 @@

    >http://www.famfamfam.com/lab/icons/silk/.
  • - Silk Companion 1 by Damien Guard: http://www.damieng.com/icons/silkcompanion [link broken] + Silk Companion 1 by Damien Guard: https://www.damieng.com/icons/silkcompanion [link broken]
  • Led Icon Set v1.0: http://led24.de/iconset/ [link broken].
  • 16x16-free-application-icons by Aha-Soft: http://www.aha-soft.com. + href="https://www.aha-soft.com" + >https://www.aha-soft.com.
  • @@ -1706,8 +1706,8 @@

  • CodeSnip's installer was created using Inno Setup: see http://www.jrsoftware.org/isinfo.php. + href="https://www.jrsoftware.org/isinfo.php" + >https://www.jrsoftware.org/isinfo.php.
  • Some program icons are based on the public domain PixelBox icon collection: @@ -1721,7 +1721,7 @@

  • Some images used in the program's Easter Egg are based on public domain images obtained from Clker.com.
  • diff --git a/Docs/ReadMe.txt b/Docs/ReadMe.txt index 28bb20505..19f157d12 100644 --- a/Docs/ReadMe.txt +++ b/Docs/ReadMe.txt @@ -236,7 +236,7 @@ Updates are published on: + SourceForge: https://sourceforge.net/projects/codesnip/files/ News of new updates is published on the CodeSnip Blog: -http://codesnip-app.blogspot.com/. +https://codesnip-app.blogspot.com/. Known Installation and Upgrading Issues @@ -303,7 +303,7 @@ https://github.com/delphidabbler/codesnip-faq/blob/master/SourceCode.md#faq-1 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 +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. diff --git a/README.md b/README.md index 412e0f674..8d3a589e4 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ The following support is available CodeSnip users: * A comprehensive help file. * A [read-me file](https://raw.githubusercontent.com/delphidabbler/codesnip/master/Docs/ReadMe.txt) * that discusses installation, configuration, updating and known issues. * A [Using CodeSnip FAQ](https://github.com/delphidabbler/codesnip-faq/blob/master/UsingCodeSnip.md). -* A [Blog](http://codesnip-app.blogspot.co.uk/). +* A [Blog](https://codesnip-app.blogspot.co.uk/). There's also plenty of info available on how to compile CodeSnip from source - see below. @@ -43,7 +43,7 @@ There's also plenty of info available on how to compile CodeSnip from source - s CodeSnip's source code is maintained in the [`delphidabbler/codesnip`](https://github.com/delphidabbler/codesnip) Git repository on GitHub†. -[Git Flow](http://nvie.com/posts/a-successful-git-branching-model/) methodology has been adopted, with the exception of some branches that have been used in abortive attempts to start work on CodeSnip 5. +[Git Flow](https://nvie.com/posts/a-successful-git-branching-model/) methodology has been adopted, with the exception of some branches that have been used in abortive attempts to start work on CodeSnip 5. The following branches existed at the time when CodeSnip v4.16.0 was released: diff --git a/Src/AutoGen/LICENSE b/Src/AutoGen/LICENSE index 3bf73d21e..db6ff5e35 100644 --- a/Src/AutoGen/LICENSE +++ b/Src/AutoGen/LICENSE @@ -4,4 +4,4 @@ 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 +https://creativecommons.org/publicdomain/zero/1.0/ \ No newline at end of file diff --git a/Src/DB.UMetaData.pas b/Src/DB.UMetaData.pas index 739b643de..88f709517 100644 --- a/Src/DB.UMetaData.pas +++ b/Src/DB.UMetaData.pas @@ -762,7 +762,7 @@ function TV1DBMetaFiles.LicenseInfo: TStringDynArray; 'LicenseURL=https://opensource.org/licenses/MIT', 'CopyrightDate=2005-2016', 'CopyrightHolder=Peter Johnson & Contributors', - 'CopyrightHolderURL=http://gravatar.com/delphidabbler' + 'CopyrightHolderURL=https://gravatar.com/delphidabbler' ); end; diff --git a/Src/Help/HTML/menu_help.htm b/Src/Help/HTML/menu_help.htm index 847869709..4d1bc409d 100644 --- a/Src/Help/HTML/menu_help.htm +++ b/Src/Help/HTML/menu_help.htm @@ -102,7 +102,7 @@

    Displays the CodeSnip Blog in the default web browser. The latest news about CodeSnip is posted in the blog. diff --git a/Src/Help/HTML/reml.htm b/Src/Help/HTML/reml.htm index 744dd0799..5bc60e296 100644 --- a/Src/Help/HTML/reml.htm +++ b/Src/Help/HTML/reml.htm @@ -169,7 +169,7 @@

    only work on the recipient's system if the specified file exists in the same location.
    Example: <p><a - href="http://example.com">Visit + href="https://example.com">Visit example.com</a></p>.. diff --git a/Src/Help/Images/LICENSE b/Src/Help/Images/LICENSE index 9e15be494..731dda904 100644 --- a/Src/Help/Images/LICENSE +++ b/Src/Help/Images/LICENSE @@ -1,5 +1,5 @@ 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/). +(https://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/LICENSE b/Src/LICENSE index 2b62ec515..29f642273 100644 --- a/Src/LICENSE +++ b/Src/LICENSE @@ -12,7 +12,7 @@ Exceptions are: 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/ + https://creativecommons.org/publicdomain/zero/1.0/ - Src/CodeSnip.cfg.tplt - Src/CodeSnip.dproj diff --git a/Src/Res/HTML/dlg-about-program-tplt.html b/Src/Res/HTML/dlg-about-program-tplt.html index 03900e936..d80714362 100644 --- a/Src/Res/HTML/dlg-about-program-tplt.html +++ b/Src/Res/HTML/dlg-about-program-tplt.html @@ -95,7 +95,7 @@

  • 16x16-free-application-icons by Aha-Soft.
  • @@ -110,7 +110,7 @@
  • Public domain images obtained from Clker.com.
  • @@ -123,7 +123,7 @@
  • CodeSnip's installer was created using Inno Setup.
  • diff --git a/Src/Res/HTML/dlg-easter-egg.html b/Src/Res/HTML/dlg-easter-egg.html index 6a9c7e310..1b25fdca1 100644 --- a/Src/Res/HTML/dlg-easter-egg.html +++ b/Src/Res/HTML/dlg-easter-egg.html @@ -92,13 +92,13 @@

    More pics on Flickr...
    diff --git a/Src/Res/Img/LICENSE b/Src/Res/Img/LICENSE index f448f15a7..687e07ab3 100644 --- a/Src/Res/Img/LICENSE +++ b/Src/Res/Img/LICENSE @@ -1,6 +1,6 @@ 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/). +(https://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/Res/Scripts/overflowXFix.js b/Src/Res/Scripts/overflowXFix.js index 65716bb5b..623f41d7c 100644 --- a/Src/Res/Scripts/overflowXFix.js +++ b/Src/Res/Scripts/overflowXFix.js @@ -61,7 +61,7 @@ } // Derived from Remy Sharp's code: - // http://remysharp.com/2008/01/21/fixing-ie-overflow-problem/ + // https://remysharp.com/2008/01/21/fixing-ie-overflow-problem/ function fixOverflow(elems) { var i; for (i = 0; i < elems.length; i += 1) { diff --git a/Src/UComparers.pas b/Src/UComparers.pas index 80e229f60..12f51249d 100644 --- a/Src/UComparers.pas +++ b/Src/UComparers.pas @@ -71,7 +71,7 @@ implementation /// String has function. -/// Sourced from http://www.scalabium.com/faq/dct0136.htm. +/// Sourced from https://www.scalabium.com/faq/dct0136.htm. function ElfHash(const Value: string): Integer; var I: Integer; // loops thru string diff --git a/Src/UConsoleApp.pas b/Src/UConsoleApp.pas index e27bdde64..29d1e650d 100644 --- a/Src/UConsoleApp.pas +++ b/Src/UConsoleApp.pas @@ -366,7 +366,7 @@ function TConsoleApp.StartProcess(const CmdLine, CurrentDir: string; // Without the following code this problem would arise if this method was // called with a constant or string with -1 reference count as the CmdLine // parameter. - // See http://msdn.microsoft.com/en-us/library/ms682425.aspx for an + // See https://msdn.microsoft.com/en-us/library/ms682425.aspx for an // explanation of the problem: look under the lpCommandLine parameter section. // Remy Lebeau suggested the workaround used below in his post to // https://forums.codegear.com/thread.jspa?threadID=12826 diff --git a/Src/UEncodings.pas b/Src/UEncodings.pas index a151d6a84..da956dc31 100644 --- a/Src/UEncodings.pas +++ b/Src/UEncodings.pas @@ -379,7 +379,7 @@ implementation /// /// Caller must ensure that the byte array has the correct format for /// the requested code page. -/// Based on Stack Overflow posting at http://bit.ly/bAvtGd. +/// Based on Stack Overflow posting at https://bit.ly/bAvtGd. /// Any terminating 0 byte included in Bytes is excluded from the /// result because Delphi adds its own terminal #0 character to ANSI strings. /// diff --git a/Src/UFileProtocol.pas b/Src/UFileProtocol.pas index 2819fb6d5..774e8fec9 100644 --- a/Src/UFileProtocol.pas +++ b/Src/UFileProtocol.pas @@ -42,7 +42,7 @@ implementation IE also supports direct specification of the file name, without file:// prefix. UNC file names are also supported, with or without the file:// prefix. - According to Microsoft, http://msdn.microsoft.com/en-us/library/aa767731.aspx, + According to Microsoft, https://msdn.microsoft.com/en-us/library/aa767731.aspx, valid file paths understood by TWebBrowser are: + file:///C|/Dirs/FileName.ext (browses file name) diff --git a/Src/UFontHelper.pas b/Src/UFontHelper.pas index 31b3eda0e..69383f904 100644 --- a/Src/UFontHelper.pas +++ b/Src/UFontHelper.pas @@ -117,7 +117,7 @@ function MonoFontFamilyProc(PLF: PEnumLogFont; PNTM: PNewTextMetric; } begin // check for fixed pitch font and filter out all "vertical" fonts that start - // with "@" (see http://tinyurl.com/6ul6rfo for details of vertical fonts). + // with "@" (see https://tinyurl.com/6ul6rfo for details of vertical fonts). if ((PLF.elfLogFont.lfPitchAndFamily and $F) = FIXED_PITCH) and not StrStartsStr('@', PLF.elfLogFont.lfFaceName) then List.Add(PLF.elfLogFont.lfFaceName); diff --git a/Src/UHTMLBuilder.pas b/Src/UHTMLBuilder.pas index 9a3dc4bdd..c49585d16 100644 --- a/Src/UHTMLBuilder.pas +++ b/Src/UHTMLBuilder.pas @@ -122,7 +122,7 @@ implementation cXMLProcInstruction = ''; // XML document type cDocType = ''; + + '"https://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'; // Various tag names cHTMLTag = 'html'; cHeadTag = 'head'; @@ -214,7 +214,7 @@ function THTMLBuilder.HTMLTag: string; function HTMLAttrs: IHTMLAttributes; begin Result := THTMLAttributes.Create( - [THTMLAttribute.Create('xmlns', 'http://www.w3.org/1999/xhtml'), + [THTMLAttribute.Create('xmlns', 'https://www.w3.org/1999/xhtml'), THTMLAttribute.Create('xml:lang', 'en'), THTMLAttribute.Create('lang', 'en')] ); diff --git a/Src/UREMLDataIO.pas b/Src/UREMLDataIO.pas index 60f81c8e7..54f2101e1 100644 --- a/Src/UREMLDataIO.pas +++ b/Src/UREMLDataIO.pas @@ -175,7 +175,7 @@ implementation Example: Hello

    "Hello" to - you

    + you

    This example specifes a heading "Hello" followed by a single paragraph. In the paragraph, "Hello" will be bold, "to" should be plain text and "you" should diff --git a/Src/USingleton.pas b/Src/USingleton.pas index aef3e1972..1e202c248 100644 --- a/Src/USingleton.pas +++ b/Src/USingleton.pas @@ -12,7 +12,7 @@ * take advantage of modern Delphi features: generics, class vars, class * constructor and destructor etc. Further updated to use class types instead of * class names as dictionary keys following suggestions made in comments on my - * blog post at . + * blog post at . } diff --git a/Src/USystemInfo.pas b/Src/USystemInfo.pas index 5b65ef95c..45cbb2889 100644 --- a/Src/USystemInfo.pas +++ b/Src/USystemInfo.pas @@ -96,7 +96,7 @@ TIEInfo = class(TNoConstructObject) class function GetIEVersionStr: string; /// Calculates major version number of Internet Explorer if it has /// not already been calculated. - /// See http://support.microsoft.com/kb/969393/en-us. + /// See https://support.microsoft.com/kb/969393/en-us. class procedure InitMajorVersion; public const diff --git a/Src/UURIEncode.pas b/Src/UURIEncode.pas index e5a17332b..d24eb1741 100644 --- a/Src/UURIEncode.pas +++ b/Src/UURIEncode.pas @@ -232,7 +232,7 @@ function URIDecodeQueryString(const Str: string): string; ordinal value of less than $80. So we needn't worry about detecting lead and continuation bytes. - For details of the UTF-8 encoding see http://en.wikipedia.org/wiki/UTF-8 + For details of the UTF-8 encoding see https://en.wikipedia.org/wiki/UTF-8 NOTE: URIEncode should be applied to the component parts of the URI before they diff --git a/Src/UUrl.pas b/Src/UUrl.pas index 46cdcd6d1..8ddb77ca6 100644 --- a/Src/UUrl.pas +++ b/Src/UUrl.pas @@ -54,7 +54,7 @@ TURL = record SWAGReleases = SWAGRepo + '/releases'; /// URL of the the CodeSnip blog. - CodeSnipBlog = 'http://codesnip-app.blogspot.com/'; + CodeSnipBlog = 'https://codesnip-app.blogspot.com/'; end; diff --git a/Src/UUrlMonEx.pas b/Src/UUrlMonEx.pas index 4fc021a4b..ab5f70cff 100644 --- a/Src/UUrlMonEx.pas +++ b/Src/UUrlMonEx.pas @@ -23,7 +23,7 @@ interface const // Internet Explorer feature controls. - // For documentation see http://msdn.microsoft.com/en-us/library/ms537169 + // For documentation see https://msdn.microsoft.com/en-us/library/ms537169 FEATURE_OBJECT_CACHING = 0; FEATURE_ZONE_ELEVATION = 1; FEATURE_MIME_HANDLING = 2; @@ -56,7 +56,7 @@ interface const // Flags that specify where to set an Internet Explorer feature control - // For documentation see http://msdn.microsoft.com/en-us/library/ms537168 + // For documentation see https://msdn.microsoft.com/en-us/library/ms537168 SET_FEATURE_ON_THREAD = $00000001; SET_FEATURE_ON_PROCESS = $00000002; SET_FEATURE_IN_REGISTRY = $00000004; diff --git a/Src/UUtils.pas b/Src/UUtils.pas index 5cda5aef4..514244d3e 100644 --- a/Src/UUtils.pas +++ b/Src/UUtils.pas @@ -313,7 +313,7 @@ function NowGMT: TDateTime; ST: TSystemTime; begin // This Windows API function gets system time in UTC/GMT - // see http://msdn.microsoft.com/en-us/library/ms724390 + // see https://msdn.microsoft.com/en-us/library/ms724390 GetSystemTime(ST); Result := SystemTimeToDateTime(ST); end; diff --git a/Src/VCodeSnip.vi b/Src/VCodeSnip.vi index 57fa84259..bc41d494f 100644 --- a/Src/VCodeSnip.vi +++ b/Src/VCodeSnip.vi @@ -21,7 +21,7 @@ Language=2057 Character Set=1252 [String File Info] -Comments=Released under the terms of the Mozilla Public License v2.0 (http://www.mozilla.org/MPL/2.0/) +Comments=Released under the terms of the Mozilla Public License v2.0 (https://www.mozilla.org/MPL/2.0/) Company Name=DelphiDabbler File Description=CodeSnip Database Viewer File Version=<#F1>.<#F2>.<#F3> build <#F4> diff --git a/Src/VCodeSnipPortable.vi b/Src/VCodeSnipPortable.vi index 01b5c43f2..387b2f31a 100644 --- a/Src/VCodeSnipPortable.vi +++ b/Src/VCodeSnipPortable.vi @@ -21,7 +21,7 @@ Language=2057 Character Set=1252 [String File Info] -Comments=Released under the terms of the Mozilla Public License v2.0 (http://www.mozilla.org/MPL/2.0/) +Comments=Released under the terms of the Mozilla Public License v2.0 (https://www.mozilla.org/MPL/2.0/) Company Name=DelphiDabbler File Description=CodeSnip Database Viewer (Portable Edition) File Version=<#F1>.<#F2>.<#F3> build <#F4> diff --git a/Tests/Src/DUnit/LICENSE b/Tests/Src/DUnit/LICENSE index dd82c0a99..349a54e77 100644 --- a/Tests/Src/DUnit/LICENSE +++ b/Tests/Src/DUnit/LICENSE @@ -1,3 +1,3 @@ All files in the Tests/Src/DUnit directory have any copyright dedicated to the Public Domain. -http://creativecommons.org/publicdomain/zero/1.0/ \ No newline at end of file +https://creativecommons.org/publicdomain/zero/1.0/ \ No newline at end of file diff --git a/Tests/Src/DUnit/TestUUtils.pas b/Tests/Src/DUnit/TestUUtils.pas index 86a9445af..f4c117554 100644 --- a/Tests/Src/DUnit/TestUUtils.pas +++ b/Tests/Src/DUnit/TestUUtils.pas @@ -144,12 +144,12 @@ procedure TTestUtilsRoutines.TestURIBaseName; CheckEquals('bar', URIBaseName('foo/bar'), 'Test 4'); CheckEquals( 'foo.php', - URIBaseName('http://example.com/foo.php'), + URIBaseName('https://example.com/foo.php'), 'Test 5' ); CheckEquals( 'bar', - URIBaseName('http://example.com/foo/bar'), + URIBaseName('https://example.com/foo/bar'), 'Test 6' ); end; From 2ae61187717703e921e2f92bb311b2e5d77ecee1 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 17 Dec 2021 13:41:04 +0000 Subject: [PATCH 021/330] Change URLs in and tags to https The HTML files embedded in resources and those used in the help file had URLs in the statements and attributes that referenced w3.org addresses using the http protocol. These were all changed to use https. --- Build.html | 4 ++-- Docs/Design/FileFormats/backup.html | 4 ++-- Docs/Design/FileFormats/config.html | 4 ++-- Docs/Design/FileFormats/export.html | 4 ++-- Docs/Design/FileFormats/favourites.html | 4 ++-- Docs/Design/FileFormats/index.html | 4 ++-- Docs/Design/FileFormats/main-db.html | 4 ++-- Docs/Design/FileFormats/saved.html | 4 ++-- Docs/Design/FileFormats/selection.html | 4 ++-- Docs/Design/FileFormats/test-unit.html | 4 ++-- Docs/Design/FileFormats/user-db.html | 4 ++-- Src/Res/HTML/dlg-about-database-tplt.html | 4 ++-- Src/Res/HTML/dlg-about-head-tplt.html | 4 ++-- Src/Res/HTML/dlg-about-program-tplt.html | 4 ++-- Src/Res/HTML/dlg-activetext-preview-tplt.html | 4 ++-- Src/Res/HTML/dlg-comperror-tplt.html | 4 ++-- Src/Res/HTML/dlg-dbupdate-finish.html | 4 ++-- Src/Res/HTML/dlg-dbupdate-intro-tplt.html | 4 ++-- Src/Res/HTML/dlg-dbupdate-load.html | 4 ++-- Src/Res/HTML/dlg-easter-egg.html | 4 ++-- Src/Res/HTML/dlg-swag-import-intro-tplt.html | 4 ++-- Src/Res/HTML/dlg-swag-import-outro-tplt.html | 4 ++-- Src/Res/HTML/dlg-whatsnew.html | 4 ++-- Src/Res/HTML/info-basic-tplt.html | 4 ++-- Src/Res/HTML/info-empty-selection-tplt.html | 4 ++-- Src/Res/HTML/info-snippet-list-tplt.html | 4 ++-- Src/Res/HTML/info-snippet-tplt.html | 4 ++-- Src/Res/HTML/welcome-tplt.html | 4 ++-- 28 files changed, 56 insertions(+), 56 deletions(-) diff --git a/Build.html b/Build.html index bc094121d..1e0295733 100644 --- a/Build.html +++ b/Build.html @@ -1,5 +1,5 @@ + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> - + diff --git a/Docs/Design/FileFormats/backup.html b/Docs/Design/FileFormats/backup.html index d6b1c75cb..b1220c2e1 100644 --- a/Docs/Design/FileFormats/backup.html +++ b/Docs/Design/FileFormats/backup.html @@ -1,5 +1,5 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.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 @@ -9,7 +9,7 @@ * * CodeSnip File Format Documentation: Backup Files. --> -<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> +<html xmlns="https://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> <head> diff --git a/Docs/Design/FileFormats/config.html b/Docs/Design/FileFormats/config.html index e76605f1f..999f7f8b6 100644 --- a/Docs/Design/FileFormats/config.html +++ b/Docs/Design/FileFormats/config.html @@ -1,5 +1,5 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.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 @@ -9,7 +9,7 @@ * * CodeSnip File Format Documentation: Configuration Files --> -<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> +<html xmlns="https://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> <head> diff --git a/Docs/Design/FileFormats/export.html b/Docs/Design/FileFormats/export.html index ba31723a2..62c28b0f9 100644 --- a/Docs/Design/FileFormats/export.html +++ b/Docs/Design/FileFormats/export.html @@ -1,5 +1,5 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.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 @@ -9,7 +9,7 @@ * * CodeSnip File Format Documentation: Export --> -<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> +<html xmlns="https://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> <head> diff --git a/Docs/Design/FileFormats/favourites.html b/Docs/Design/FileFormats/favourites.html index a31291312..ce9f1f9d3 100644 --- a/Docs/Design/FileFormats/favourites.html +++ b/Docs/Design/FileFormats/favourites.html @@ -1,5 +1,5 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.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 @@ -9,7 +9,7 @@ * * CodeSnip File Format Documentation: Favourites Files --> -<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> +<html xmlns="https://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> <head> diff --git a/Docs/Design/FileFormats/index.html b/Docs/Design/FileFormats/index.html index 155cffebd..fb9c95561 100644 --- a/Docs/Design/FileFormats/index.html +++ b/Docs/Design/FileFormats/index.html @@ -1,5 +1,5 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.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 @@ -9,7 +9,7 @@ * * CodeSnip File Format Documentation: Index File --> -<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> +<html xmlns="https://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> <head> diff --git a/Docs/Design/FileFormats/main-db.html b/Docs/Design/FileFormats/main-db.html index de75a8b7f..720d55c65 100644 --- a/Docs/Design/FileFormats/main-db.html +++ b/Docs/Design/FileFormats/main-db.html @@ -1,5 +1,5 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.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 @@ -9,7 +9,7 @@ * * CodeSnip File Format Documentation: Main Database --> -<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> +<html xmlns="https://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> <head> diff --git a/Docs/Design/FileFormats/saved.html b/Docs/Design/FileFormats/saved.html index a0a875411..733b690db 100644 --- a/Docs/Design/FileFormats/saved.html +++ b/Docs/Design/FileFormats/saved.html @@ -1,5 +1,5 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.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 @@ -9,7 +9,7 @@ * * CodeSnip File Format Documentation: Saved Files --> -<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> +<html xmlns="https://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> <head> diff --git a/Docs/Design/FileFormats/selection.html b/Docs/Design/FileFormats/selection.html index e0f51a33f..814f477fc 100644 --- a/Docs/Design/FileFormats/selection.html +++ b/Docs/Design/FileFormats/selection.html @@ -1,5 +1,5 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.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 @@ -9,7 +9,7 @@ * * CodeSnip File Format Documentation: Selection Files --> -<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> +<html xmlns="https://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> <head> diff --git a/Docs/Design/FileFormats/test-unit.html b/Docs/Design/FileFormats/test-unit.html index b377806c5..d397c55de 100644 --- a/Docs/Design/FileFormats/test-unit.html +++ b/Docs/Design/FileFormats/test-unit.html @@ -1,5 +1,5 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.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 @@ -9,7 +9,7 @@ * * CodeSnip File Format Documentation: Test Units --> -<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> +<html xmlns="https://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> <head> diff --git a/Docs/Design/FileFormats/user-db.html b/Docs/Design/FileFormats/user-db.html index 95975507e..20270c576 100644 --- a/Docs/Design/FileFormats/user-db.html +++ b/Docs/Design/FileFormats/user-db.html @@ -1,5 +1,5 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.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 @@ -9,7 +9,7 @@ * * CodeSnip File Format Documentation: User Database --> -<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> +<html xmlns="https://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> <head> diff --git a/Src/Res/HTML/dlg-about-database-tplt.html b/Src/Res/HTML/dlg-about-database-tplt.html index 0cb1ae183..114ebd024 100644 --- a/Src/Res/HTML/dlg-about-database-tplt.html +++ b/Src/Res/HTML/dlg-about-database-tplt.html @@ -2,7 +2,7 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <!-- * This Source Code Form is subject to the terms of the Mozilla Public License, @@ -14,7 +14,7 @@ * Template for content displayed in database tab of about dialogue box. --> -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<html xmlns="https://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <title>dlg-about-database-tplt.html diff --git a/Src/Res/HTML/dlg-about-head-tplt.html b/Src/Res/HTML/dlg-about-head-tplt.html index 6637191df..106246197 100644 --- a/Src/Res/HTML/dlg-about-head-tplt.html +++ b/Src/Res/HTML/dlg-about-head-tplt.html @@ -2,7 +2,7 @@ + "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 d80714362..e11c7e3c5 100644 --- a/Src/Res/HTML/dlg-about-program-tplt.html +++ b/Src/Res/HTML/dlg-about-program-tplt.html @@ -2,7 +2,7 @@ + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> - + dlg-about-program-tplt diff --git a/Src/Res/HTML/dlg-activetext-preview-tplt.html b/Src/Res/HTML/dlg-activetext-preview-tplt.html index 1a8e5a9a6..279acb51d 100644 --- a/Src/Res/HTML/dlg-activetext-preview-tplt.html +++ b/Src/Res/HTML/dlg-activetext-preview-tplt.html @@ -2,7 +2,7 @@ + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> - + dlg-activetext-preview-tplt.html diff --git a/Src/Res/HTML/dlg-comperror-tplt.html b/Src/Res/HTML/dlg-comperror-tplt.html index a3fc70187..f30f9ac66 100644 --- a/Src/Res/HTML/dlg-comperror-tplt.html +++ b/Src/Res/HTML/dlg-comperror-tplt.html @@ -2,7 +2,7 @@ + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> - + dlg-comperror-tplt diff --git a/Src/Res/HTML/dlg-dbupdate-finish.html b/Src/Res/HTML/dlg-dbupdate-finish.html index 6cde81c41..cd24b6040 100644 --- a/Src/Res/HTML/dlg-dbupdate-finish.html +++ b/Src/Res/HTML/dlg-dbupdate-finish.html @@ -2,7 +2,7 @@ + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> - + dlg-dbupdate-finish.html diff --git a/Src/Res/HTML/dlg-dbupdate-intro-tplt.html b/Src/Res/HTML/dlg-dbupdate-intro-tplt.html index ff9a738b7..79abcd2f8 100644 --- a/Src/Res/HTML/dlg-dbupdate-intro-tplt.html +++ b/Src/Res/HTML/dlg-dbupdate-intro-tplt.html @@ -2,7 +2,7 @@ + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> - + dlg-dbupdate-intro-tplt.html diff --git a/Src/Res/HTML/dlg-dbupdate-load.html b/Src/Res/HTML/dlg-dbupdate-load.html index fb7a03b4d..4c02cc001 100644 --- a/Src/Res/HTML/dlg-dbupdate-load.html +++ b/Src/Res/HTML/dlg-dbupdate-load.html @@ -2,7 +2,7 @@ + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> - + dlg-dbupdate-load.html diff --git a/Src/Res/HTML/dlg-easter-egg.html b/Src/Res/HTML/dlg-easter-egg.html index 1b25fdca1..c5ef209b3 100644 --- a/Src/Res/HTML/dlg-easter-egg.html +++ b/Src/Res/HTML/dlg-easter-egg.html @@ -2,7 +2,7 @@ + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> - + diff --git a/Src/Res/HTML/dlg-swag-import-intro-tplt.html b/Src/Res/HTML/dlg-swag-import-intro-tplt.html index 381c8f359..b209c3022 100644 --- a/Src/Res/HTML/dlg-swag-import-intro-tplt.html +++ b/Src/Res/HTML/dlg-swag-import-intro-tplt.html @@ -2,7 +2,7 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <!-- * This Source Code Form is subject to the terms of the Mozilla Public License, @@ -15,7 +15,7 @@ * Wizard. --> -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<html xmlns="https://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <title>dlg-swag-import-intro.html diff --git a/Src/Res/HTML/dlg-swag-import-outro-tplt.html b/Src/Res/HTML/dlg-swag-import-outro-tplt.html index 5b76424c0..0593e8e17 100644 --- a/Src/Res/HTML/dlg-swag-import-outro-tplt.html +++ b/Src/Res/HTML/dlg-swag-import-outro-tplt.html @@ -2,7 +2,7 @@ + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> - + dlg-swag-import-outro-tplt.html diff --git a/Src/Res/HTML/dlg-whatsnew.html b/Src/Res/HTML/dlg-whatsnew.html index f4d33a8d1..6215a76c0 100644 --- a/Src/Res/HTML/dlg-whatsnew.html +++ b/Src/Res/HTML/dlg-whatsnew.html @@ -2,7 +2,7 @@ + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> - + What's New diff --git a/Src/Res/HTML/info-basic-tplt.html b/Src/Res/HTML/info-basic-tplt.html index d9a704d9c..96e83ab1e 100644 --- a/Src/Res/HTML/info-basic-tplt.html +++ b/Src/Res/HTML/info-basic-tplt.html @@ -2,7 +2,7 @@ + "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 1378be4af..087fd4c8e 100644 --- a/Src/Res/HTML/info-empty-selection-tplt.html +++ b/Src/Res/HTML/info-empty-selection-tplt.html @@ -2,7 +2,7 @@ + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> - + Empty Snippet List diff --git a/Src/Res/HTML/info-snippet-list-tplt.html b/Src/Res/HTML/info-snippet-list-tplt.html index 8b5c7530d..070669986 100644 --- a/Src/Res/HTML/info-snippet-list-tplt.html +++ b/Src/Res/HTML/info-snippet-list-tplt.html @@ -2,7 +2,7 @@ + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> - + Snippet List diff --git a/Src/Res/HTML/info-snippet-tplt.html b/Src/Res/HTML/info-snippet-tplt.html index fe4c25d9d..88ab62be6 100644 --- a/Src/Res/HTML/info-snippet-tplt.html +++ b/Src/Res/HTML/info-snippet-tplt.html @@ -2,7 +2,7 @@ + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> - + detail-info diff --git a/Src/Res/HTML/welcome-tplt.html b/Src/Res/HTML/welcome-tplt.html index 35a9c5370..5f7cabcf7 100644 --- a/Src/Res/HTML/welcome-tplt.html +++ b/Src/Res/HTML/welcome-tplt.html @@ -2,7 +2,7 @@ + "https://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> - + Welcome From 414a059baad2329e436dc445888d1b71976bf774 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 17 Dec 2021 13:41:40 +0000 Subject: [PATCH 022/330] Change example URL from http to https protocol --- Docs/Design/FileFormats/main-db.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Docs/Design/FileFormats/main-db.html b/Docs/Design/FileFormats/main-db.html index 720d55c65..6183e4682 100644 --- a/Docs/Design/FileFormats/main-db.html +++ b/Docs/Design/FileFormats/main-db.html @@ -933,7 +933,7 @@

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

    - If Credits="See [example]" and Credits_URL="http://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>. + 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>.

  • From 164ad5161c8b5fa590c3e916b6f557d5d4ab86eb Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 17 Dec 2021 13:51:09 +0000 Subject: [PATCH 023/330] Remove further redundant SourceForge ticket URLs --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42827f30c..64cd9bec1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -334,12 +334,12 @@ This is a significant update. It's purpose is to remove CodeSnip's dependencies ## Release v4.5.1 of 15 May 2013 + Added progress bars or marquees to several database operations that can take a long time on slower storage devices, i.e.: - - When local files are being updated after downloading an updated database in the Update From Web dialogue box. This fixes bug #79 ~~(https://sourceforge.net/p/codesnip/bugs/79/)~~: + - When local files are being updated after downloading an updated database in the Update From Web dialogue box. This fixes [SourceForge] bug #79 ~~(https://sourceforge.net/p/codesnip/bugs/79/)~~: - When the local database is being saved. - When the local database is being backed up or restored. - When the local database is being moved to a new location. -+ The user database can now be relocated to a network drive. This fixes issue #81 "Move database to a network drive" ~~(https://sourceforge.net/p/codesnip/bugs/81/)~~. -+ Fixed issue #80 "HTML output bug" ~~(https://sourceforge.net/p/codesnip/bugs/80/)~~. ++ The user database can now be relocated to a network drive. This fixes [SourceForge] issue #81 "Move database to a network drive". ++ Fixed [SourceForge] issue #80 "HTML output bug". + Fixed minor alignment bug that occurred when displaying a wait dialogue box over the main window. + Some refactoring. + Updated help file re changes. From 731644b0789479a22b49b7a2b7b769f1c39ca95b Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 17 Dec 2021 14:16:17 +0000 Subject: [PATCH 024/330] Remove yet another redundant SourceForge ticket URL --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64cd9bec1..498cbf052 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -334,7 +334,7 @@ This is a significant update. It's purpose is to remove CodeSnip's dependencies ## Release v4.5.1 of 15 May 2013 + Added progress bars or marquees to several database operations that can take a long time on slower storage devices, i.e.: - - When local files are being updated after downloading an updated database in the Update From Web dialogue box. This fixes [SourceForge] bug #79 ~~(https://sourceforge.net/p/codesnip/bugs/79/)~~: + - When local files are being updated after downloading an updated database in the Update From Web dialogue box. This fixes [SourceForge] bug #79: - When the local database is being saved. - When the local database is being backed up or restored. - When the local database is being moved to a new location. From bd9c815b68f17caa55f840b78b46e83da296d67b Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 17 Dec 2021 14:17:43 +0000 Subject: [PATCH 025/330] Remove broken URLs Fixes #30 --- Src/Browser.UIOMgr.pas | 1 - Src/Res/Scripts/overflowXFix.js | 3 +-- Src/UConsoleApp.pas | 4 ++-- Src/UJavaScriptUtils.pas | 3 +-- Src/USingleton.pas | 2 +- 5 files changed, 5 insertions(+), 8 deletions(-) diff --git a/Src/Browser.UIOMgr.pas b/Src/Browser.UIOMgr.pas index e25d7b06a..8f2f0ef29 100644 --- a/Src/Browser.UIOMgr.pas +++ b/Src/Browser.UIOMgr.pas @@ -187,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/Res/Scripts/overflowXFix.js b/Src/Res/Scripts/overflowXFix.js index 623f41d7c..d3f344729 100644 --- a/Src/Res/Scripts/overflowXFix.js +++ b/Src/Res/Scripts/overflowXFix.js @@ -36,8 +36,7 @@ } } - // Modification of function by Dustin Diaz: - // http://www.dustindiaz.com/getelementsbyclass + // Modification of function by Dustin Diaz function getElementsByClass(searchClass,node,tag) { var classElements = []; if (node == null) { diff --git a/Src/UConsoleApp.pas b/Src/UConsoleApp.pas index 29d1e650d..975ad297b 100644 --- a/Src/UConsoleApp.pas +++ b/Src/UConsoleApp.pas @@ -368,8 +368,8 @@ function TConsoleApp.StartProcess(const CmdLine, CurrentDir: string; // parameter. // See https://msdn.microsoft.com/en-us/library/ms682425.aspx for an // explanation of the problem: look under the lpCommandLine parameter section. - // Remy Lebeau suggested the workaround used below in his post to - // https://forums.codegear.com/thread.jspa?threadID=12826 + // Remy Lebeau suggested the workaround used below in a post on the old + // CodeGear forums. SafeCmdLine := CmdLine; UniqueString(SafeCmdLine); // Set up creation flags: special flag used to determine type of environment diff --git a/Src/UJavaScriptUtils.pas b/Src/UJavaScriptUtils.pas index 129868492..b0a7cc074 100644 --- a/Src/UJavaScriptUtils.pas +++ b/Src/UJavaScriptUtils.pas @@ -66,8 +66,7 @@ TJavaScript = record /// string. Required JavaScript code. /// We sometimes need to load scripts into strings and then embed /// in HTML document since linking to external resource script doesn't seem - /// to work in IE 9 (see bug report - /// https://sourceforge.net/p/codesnip/bugs/84/). + /// to work in IE 9. class function LoadScript(const ScriptName: string; const EncType: TEncodingType): string; static; end; diff --git a/Src/USingleton.pas b/Src/USingleton.pas index 1e202c248..1a323dd75 100644 --- a/Src/USingleton.pas +++ b/Src/USingleton.pas @@ -8,7 +8,7 @@ * Provides a base class for singleton objects along with a manager object that * records instances of each type of singleton. * - * Based on by code by Yoav Abrahami see , updated to + * Based on by code by Yoav Abrahami on the former Delphi3000.com, updated to * take advantage of modern Delphi features: generics, class vars, class * constructor and destructor etc. Further updated to use class types instead of * class names as dictionary keys following suggestions made in comments on my From 6b4278fd17a6fe865ed000295238e6b0294e79c7 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 17 Dec 2021 17:34:15 +0000 Subject: [PATCH 026/330] Fix copyright char changed in error in commit 80bf294 The copyright character on line 850 was changed, either by VS Code or SourceTree, to some erroneous character in commit 80bf294. This unexpected change was reverted here. --- Src/UREMLDataIO.pas | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Src/UREMLDataIO.pas b/Src/UREMLDataIO.pas index 54f2101e1..6377e1279 100644 --- a/Src/UREMLDataIO.pas +++ b/Src/UREMLDataIO.pas @@ -1,4 +1,4 @@ -{ +{ * 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/ @@ -847,7 +847,7 @@ class function TREMLEntities.CharToMnemonicEntity(const Ch: Char): string; fEntityMap[1] := TREMLEntity.Create('quot', DOUBLEQUOTE); fEntityMap[2] := TREMLEntity.Create('gt', '>'); fEntityMap[3] := TREMLEntity.Create('lt', '<'); - fEntityMap[4] := TREMLEntity.Create('copy', '�'); + fEntityMap[4] := TREMLEntity.Create('copy', '©'); end; class destructor TREMLEntities.Destroy; From 300936fbd9f370140fe6f4369e6cfa093cba9f39 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 29 Dec 2021 11:08:25 +0000 Subject: [PATCH 027/330] Replace tabs with list box in Preferences dlg All page control tabs hidden. A new list box added on left of dialogue box that displays tab names. Clicking a name selects the required tab. Fixes #24 --- Src/FmPreferencesDlg.dfm | 36 ++++++++++++-------- Src/FmPreferencesDlg.pas | 71 ++++++++++++++++++++-------------------- 2 files changed, 58 insertions(+), 49 deletions(-) diff --git a/Src/FmPreferencesDlg.dfm b/Src/FmPreferencesDlg.dfm index cf38aa3cf..02c3a5c19 100644 --- a/Src/FmPreferencesDlg.dfm +++ b/Src/FmPreferencesDlg.dfm @@ -3,27 +3,37 @@ 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 = 329 + ExplicitWidth = 609 + ExplicitHeight = 329 object pcMain: TPageControl - Left = 0 + Left = 163 Top = 0 Width = 446 - Height = 377 - Align = alClient + Height = 329 + Align = alRight MultiLine = True + TabOrder = 1 + ExplicitLeft = 159 + ExplicitHeight = 377 + end + object lbPages: TListBox + Left = 0 + Top = 0 + Width = 153 + Height = 329 + Align = alLeft + ItemHeight = 13 TabOrder = 0 - OnChange = pcMainChange - OnChanging = pcMainChanging - OnMouseDown = pcMainMouseDown + OnClick = lbPagesClick + ExplicitHeight = 377 end end inherited btnOK: TButton diff --git a/Src/FmPreferencesDlg.pas b/Src/FmPreferencesDlg.pas index 4f63547e6..2da9d6e52 100644 --- a/Src/FmPreferencesDlg.pas +++ b/Src/FmPreferencesDlg.pas @@ -32,26 +32,13 @@ interface /// TPreferencesDlg = class(TGenericOKDlg, INoPublicConstruct) pcMain: TPageControl; + lbPages: TListBox; /// OK button click event handler. Writes preference data to /// persistent storage. procedure btnOKClick(Sender: TObject); - /// Called when current tab sheet has changed. Gets newly selected - /// page to re-initialise its controls from local preferences. - /// This enables any pages that depend on preferences that may - /// have been changed in other pages to update appropriately. - procedure pcMainChange(Sender: TObject); - /// Called just before active tab sheet is changed. Causes page - /// about to be deselected to update local preferences with any changes. - /// - /// We do this in case another page needs to update due to changes - /// made on current page. - procedure pcMainChanging(Sender: TObject; var AllowChange: Boolean); - /// Handles event triggered when user clicks on one of page - /// control tabs. Ensures page control has focus. - /// Without this fix, page control does not always get focus when - /// a tab is clicked. - procedure pcMainMouseDown(Sender: TObject; Button: TMouseButton; - Shift: TShiftState; X, Y: Integer); + /// Handles event triggered when list box is clicked or changed + /// via keyboard. + procedure lbPagesClick(Sender: TObject); strict private class var /// List of registered page frames @@ -62,6 +49,8 @@ TPreferencesDlg = class(TGenericOKDlg, INoPublicConstruct) /// Records if main UI needs to be updated to reflect changed /// preferences. fUpdateUI: Boolean; + /// Records index of currently select tab/list item. + fCurrentPageIdx: Integer; /// Creates the required frames and displays each in a tab sheet /// within the page control. /// array of TPrefsFrameClass [in] Class @@ -83,6 +72,10 @@ TPreferencesDlg = class(TGenericOKDlg, INoPublicConstruct) /// Gets reference to preferences frame on currently selected tab. /// function GetSelectedPage: TPrefsBaseFrame; + /// Selects given tab. + /// Stores state of tab being closed and restores state of tab + /// being opened. + procedure SelectTab(TS: TTabSheet); strict protected /// Gets the help A-link keyword to be used when help button /// clicked. @@ -241,6 +234,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; @@ -313,7 +310,18 @@ procedure TPreferencesDlg.InitForm; for TabIdx := 0 to Pred(pcMain.PageCount) do MapTabSheetToPage(TabIdx).LoadPrefs(fLocalPrefs); // Select first TabSheet - pcMain.ActivePageIndex := 0; + 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): @@ -351,24 +359,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 @@ -389,5 +379,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); + fCurrentPageIdx := pcMain.ActivePageIndex; +end; + end. From 53adc328bb6484a2611f5389c80eb89e30ade93f Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 29 Dec 2021 11:53:32 +0000 Subject: [PATCH 028/330] Make Preferences dialogue remember last tab used Add new Preferences "meta" section to store name of tab that was displayed when dialogue box was last closed. Preferences dialogue now uses that preference to make the last tab current when it is opened. --- Src/FmPreferencesDlg.pas | 37 +++++++++++++++++++++++--- Src/UPreferences.pas | 57 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 4 deletions(-) diff --git a/Src/FmPreferencesDlg.pas b/Src/FmPreferencesDlg.pas index 2da9d6e52..1e52a9478 100644 --- a/Src/FmPreferencesDlg.pas +++ b/Src/FmPreferencesDlg.pas @@ -39,6 +39,7 @@ TPreferencesDlg = class(TGenericOKDlg, INoPublicConstruct) /// Handles event triggered when list box is clicked or changed /// via keyboard. procedure lbPagesClick(Sender: TObject); + procedure FormDestroy(Sender: TObject); strict private class var /// List of registered page frames @@ -76,6 +77,9 @@ TPreferencesDlg = class(TGenericOKDlg, INoPublicConstruct) /// Stores state of tab being closed and restores state of tab /// being opened. procedure SelectTab(TS: TTabSheet); + /// Returns index of tab selected when dialogue box was last + /// closed or -1 if no tab recorded or tab doesn't exist. + function GetLastTabIdx: Integer; strict protected /// Gets the help A-link keyword to be used when help button /// clicked. @@ -170,7 +174,8 @@ implementation uses // Project - IntfCommon; + IntfCommon, + UStrUtils; {$R *.dfm} @@ -292,6 +297,28 @@ class function TPreferencesDlg.Execute(AOwner: TComponent; Result := Execute(AOwner, [FrameClass], UpdateUI); end; +procedure TPreferencesDlg.FormDestroy(Sender: TObject); +begin + // Save current tab + 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); @@ -299,7 +326,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 @@ -309,8 +336,10 @@ procedure TPreferencesDlg.InitForm; // Display and initialise required pages for TabIdx := 0 to Pred(pcMain.PageCount) do MapTabSheetToPage(TabIdx).LoadPrefs(fLocalPrefs); - // Select first TabSheet - fCurrentPageIdx := 0; + // Select last use tab sheet (or 1st if last not known) + fCurrentPageIdx := GetLastTabIdx; + if fCurrentPageIdx < 0 then + fCurrentPageIdx := 0; pcMain.ActivePageIndex := fCurrentPageIdx; lbPages.ItemIndex := fCurrentPageIdx; end; diff --git a/Src/UPreferences.pas b/Src/UPreferences.pas index 24e729ee8..bdf2107ec 100644 --- a/Src/UPreferences.pas +++ b/Src/UPreferences.pas @@ -37,6 +37,23 @@ interface IPreferences = interface(IInterface) ['{381B9A92-B528-47E1-AC04-90E1FFFDADA7}'] + /// Gets last tab displayed by Preferences dialogue box when it + /// was last closed, or empty string if the tab is not known. + /// + /// This is meta data about the dialogue box itself, not about + /// user preferences. + function GetLastTab: string; + /// Sets last tab displayed by Preferences dialogue box when it + /// was last closed. + /// This is meta data about the dialogue box itself, not about + /// user preferences. + procedure SetLastTab(const Value: string); + /// Last tab displayed by Preferences dialogue box when it was + /// last closed, or empty string if the tab is not known. + /// This is meta data about the dialogue box itself, not about + /// user preferences. + property LastTab: string read GetLastTab write SetLastTab; + /// Gets style of commenting used to describe snippets in /// generated code. function GetSourceCommentStyle: TCommentStyle; @@ -283,6 +300,7 @@ TPreferences = class(TInterfacedObject, ) strict protected var + fLastTab: string; /// Default file extension / type used when writing code /// snippets file. fSourceDefaultFileType: TSourceFileType; @@ -344,6 +362,24 @@ TPreferences = class(TInterfacedObject, /// Destroys object instance. destructor Destroy; override; + /// Gets last tab displayed by Preferences dialogue box when it + /// was last closed, or empty string if the tab is not known. + /// + /// + /// This is meta data about the dialogue box itself, not about + /// user preferences. + /// Method of IPreferences. + /// + function GetLastTab: string; + /// Sets last tab displayed by Preferences dialogue box when it + /// was last closed. + /// + /// This is meta data about the dialogue box itself, not about user + /// preferences. + /// Method of IPreferences. + /// + procedure SetLastTab(const Value: string); + /// Gets style of commenting used to describe snippets in /// generated code. /// Method of IPreferences. @@ -608,6 +644,7 @@ procedure TPreferences.Assign(const Src: IInterface); if not Supports(Src, IPreferences, SrcPref) then raise EBug.Create(ClassName + '.Assign: Src is wrong type'); // Copy the data + Self.fLastTab := SrcPref.LastTab; Self.fSourceDefaultFileType := SrcPref.SourceDefaultFileType; Self.fSourceCommentStyle := SrcPref.SourceCommentStyle; Self.fTruncateSourceComments := SrcPref.TruncateSourceComments; @@ -671,6 +708,11 @@ function TPreferences.GetHiliteAttrs: IHiliteAttrs; Result := fHiliteAttrs; end; +function TPreferences.GetLastTab: string; +begin + Result := fLastTab; +end; + function TPreferences.GetMeasurementUnits: TMeasurementUnits; begin Result := fMeasurementUnits; @@ -768,6 +810,11 @@ procedure TPreferences.SetHiliteAttrs(const Attrs: IHiliteAttrs); (fHiliteAttrs as IAssignable).Assign(Attrs); end; +procedure TPreferences.SetLastTab(const Value: string); +begin + fLastTab := Value; +end; + procedure TPreferences.SetMeasurementUnits(const Value: TMeasurementUnits); begin fMeasurementUnits := Value; @@ -854,6 +901,7 @@ function TPreferencesPersist.Clone: IInterface; Result := TPreferences.Create; // Copy properties to it NewPref := Result as IPreferences; + NewPref.LastTab := Self.fLastTab; NewPref.SourceDefaultFileType := Self.fSourceDefaultFileType; NewPref.SourceCommentStyle := Self.fSourceCommentStyle; NewPref.TruncateSourceComments := Self.fTruncateSourceComments; @@ -886,6 +934,10 @@ constructor TPreferencesPersist.Create; begin inherited Create; + // Read meta data section (no sub-section name) + Storage := Settings.ReadSection(ssPreferences); + fLastTab := Storage.GetString('LastTab'); + // Read general section Storage := Settings.ReadSection(ssPreferences, cGeneral); fMeasurementUnits := TMeasurementUnits( @@ -969,6 +1021,11 @@ destructor TPreferencesPersist.Destroy; var Storage: ISettingsSection; // object used to access persistent storage begin + // Wreite meta section (no sub-section name) + Storage := Settings.EmptySection(ssPreferences); + Storage.SetString('LastTab', fLastTab); + Storage.Save; + // Write general section Storage := Settings.EmptySection(ssPreferences, cGeneral); Storage.SetInteger('Units', Ord(fMeasurementUnits)); From 4fcb12e4e723cd629746c35f6ba3f86ff295155b Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 29 Dec 2021 11:54:26 +0000 Subject: [PATCH 029/330] Update config file docs re new [Prefs] section [Prefs] section used to store tab displayed when Preferences dialogue was last closed. --- Docs/Design/FileFormats/config.html | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Docs/Design/FileFormats/config.html b/Docs/Design/FileFormats/config.html index 999f7f8b6..140c10991 100644 --- a/Docs/Design/FileFormats/config.html +++ b/Docs/Design/FileFormats/config.html @@ -732,6 +732,27 @@

    +

    + [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) +
    +
    + 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. +
    +
    +

    [Prefs:CodeGen] section

    From 74b4ec633a7325aabba1d025f1cdd79c814dc4e2 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 29 Dec 2021 17:09:56 +0000 Subject: [PATCH 030/330] Add support for passing flags to preferences frames Code that displays the preferences dialogue box can now pass optional flags to each frame. Each flag is a UInt64 that has the frame's unique index number in its high 32 bits and a frame specific flag or bitmask in the lower 32 bits. FrPrefsBase provides utility class methods for manipulating these flags. --- Src/FmPreferencesDlg.pas | 33 ++++++++++++++++++------------ Src/FrCodeGenPrefs.pas | 6 ++++-- Src/FrDisplayPrefs.pas | 6 ++++-- Src/FrGeneralPrefs.pas | 6 ++++-- Src/FrHiliterPrefs.pas | 6 ++++-- Src/FrPrefsBase.pas | 39 ++++++++++++++++++++++++++++++++---- Src/FrPrintingPrefs.pas | 6 ++++-- Src/FrSnippetLayoutPrefs.pas | 6 ++++-- Src/FrSourcePrefs.pas | 6 ++++-- 9 files changed, 83 insertions(+), 31 deletions(-) diff --git a/Src/FmPreferencesDlg.pas b/Src/FmPreferencesDlg.pas index 1e52a9478..623b4c453 100644 --- a/Src/FmPreferencesDlg.pas +++ b/Src/FmPreferencesDlg.pas @@ -50,6 +50,8 @@ TPreferencesDlg = class(TGenericOKDlg, INoPublicConstruct) /// Records if main UI needs to be updated to reflect changed /// preferences. fUpdateUI: Boolean; + /// Records flags to be passed to frames. + fFrameFlags: UInt64; /// Records index of currently select tab/list item. fCurrentPageIdx: Integer; /// Creates the required frames and displays each in a tab sheet @@ -108,7 +110,8 @@ TPreferencesDlg = class(TGenericOKDlg, INoPublicConstruct) /// Boolean. True if user clicks OK to accept changes or False if /// user cancels and no changes made. 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; /// Displays dialog with pages for each specified preferences /// frame. @@ -119,7 +122,8 @@ TPreferencesDlg = class(TGenericOKDlg, INoPublicConstruct) /// Boolean. True if user clicks OK to accept changes or False if /// user cancels and no changes made. class function Execute(AOwner: TComponent; - const Pages: array of TPrefsFrameClass): Boolean; overload; + const Pages: array of TPrefsFrameClass; const Flags: UInt64 = 0): + Boolean; overload; /// Displays preferences dialog with all registered preference /// frames. /// TComponent [in] Component that owns dialog. @@ -128,8 +132,8 @@ TPreferencesDlg = class(TGenericOKDlg, INoPublicConstruct) /// be updated as a result of preference changes. /// Boolean. True if user clicks OK to accept changes or False if /// user cancels and no changes made. - class function Execute(AOwner: TComponent; out UpdateUI: Boolean): Boolean; - overload; + class function Execute(AOwner: TComponent; out UpdateUI: Boolean; + const Flags: UInt64 = 0): Boolean; overload; /// Displays dialogue with showing a single frame, specified by /// its class name. /// TComponent [in] Component that owns dialog. @@ -141,7 +145,7 @@ TPreferencesDlg = class(TGenericOKDlg, INoPublicConstruct) /// Boolean. True if user clicks OK to accept changes or False if /// user cancels and no changes made. class function Execute(AOwner: TComponent; const PageClsName: string; - out UpdateUI: Boolean): Boolean; overload; + out UpdateUI: Boolean; const Flags: UInt64 = 0): Boolean; overload; /// Registers given preferences frame class for inclusion in the /// preferences dialog box. /// Registered frames are created when the dialog box is displayed @@ -258,10 +262,12 @@ 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; begin with InternalCreate(AOwner) do try + fFrameFlags := Flags; CreatePages(Pages); Result := ShowModal = mrOK; if Result then @@ -274,21 +280,22 @@ class function TPreferencesDlg.Execute(AOwner: TComponent; 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 @@ -335,7 +342,7 @@ 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); + MapTabSheetToPage(TabIdx).LoadPrefs(fLocalPrefs, fFrameFlags); // Select last use tab sheet (or 1st if last not known) fCurrentPageIdx := GetLastTabIdx; if fCurrentPageIdx < 0 then @@ -413,7 +420,7 @@ procedure TPreferencesDlg.SelectTab(TS: TTabSheet); Assert(Assigned(TS), ClassName + '.SelectTab: TS is nil'); GetSelectedPage.Deactivate(fLocalPrefs); pcMain.ActivePage := TS; - GetSelectedPage.Activate(fLocalPrefs); + GetSelectedPage.Activate(fLocalPrefs, fFrameFlags); fCurrentPageIdx := pcMain.ActivePageIndex; end; diff --git a/Src/FrCodeGenPrefs.pas b/Src/FrCodeGenPrefs.pas index 02b469817..f22bb4f6b 100644 --- a/Src/FrCodeGenPrefs.pas +++ b/Src/FrCodeGenPrefs.pas @@ -176,7 +176,8 @@ TCodeGenPrefsFrame = class(TPrefsBaseFrame) /// Records details of warnings from given preferences object and /// updates controls accordingly. /// Called when page is activated. - procedure Activate(const Prefs: IPreferences); override; + procedure Activate(const Prefs: IPreferences; const Flags: UInt64); + override; /// Updates given preferences object with details of warnings as /// modified by user. /// Called when page is deactivated. @@ -322,7 +323,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; diff --git a/Src/FrDisplayPrefs.pas b/Src/FrDisplayPrefs.pas index 27266be7c..b66d06bff 100644 --- a/Src/FrDisplayPrefs.pas +++ b/Src/FrDisplayPrefs.pas @@ -63,7 +63,8 @@ TDisplayPrefsFrame = 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. } @@ -106,7 +107,8 @@ implementation { 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. } diff --git a/Src/FrGeneralPrefs.pas b/Src/FrGeneralPrefs.pas index 6cd7d31e8..9fa331b5e 100644 --- a/Src/FrGeneralPrefs.pas +++ b/Src/FrGeneralPrefs.pas @@ -45,7 +45,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. } @@ -88,7 +89,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/FrHiliterPrefs.pas b/Src/FrHiliterPrefs.pas index aecbb5785..7217fa1c0 100644 --- a/Src/FrHiliterPrefs.pas +++ b/Src/FrHiliterPrefs.pas @@ -143,7 +143,8 @@ THiliterPrefsFrame = class(TPrefsBaseFrame) /// Updates controls from given preferences object. /// Called when the dialogue page containing the frame is /// activated. - procedure Activate(const Prefs: IPreferences); override; + procedure Activate(const Prefs: IPreferences; const Flags: UInt64); + override; /// Updates given preferences object with data entered in controls. /// /// Called when the dialogue page containing the frame is @@ -254,7 +255,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); diff --git a/Src/FrPrefsBase.pas b/Src/FrPrefsBase.pas index 652d3ec86..0f786a365 100644 --- a/Src/FrPrefsBase.pas +++ b/Src/FrPrefsBase.pas @@ -38,6 +38,9 @@ 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; public procedure SavePrefs(const Prefs: IPreferences); virtual; {Saves information user entered in frame. By default the method simply @@ -45,12 +48,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. } @@ -77,23 +82,49 @@ 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; + {$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; procedure TPrefsBaseFrame.SavePrefs(const Prefs: IPreferences); diff --git a/Src/FrPrintingPrefs.pas b/Src/FrPrintingPrefs.pas index ae2f652ad..d30b5d02c 100644 --- a/Src/FrPrintingPrefs.pas +++ b/Src/FrPrintingPrefs.pas @@ -60,7 +60,8 @@ TPrintingPrefsFrame = class(TPrefsBaseFrame) 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. } @@ -141,7 +142,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. } diff --git a/Src/FrSnippetLayoutPrefs.pas b/Src/FrSnippetLayoutPrefs.pas index a292792c6..246193f4a 100644 --- a/Src/FrSnippetLayoutPrefs.pas +++ b/Src/FrSnippetLayoutPrefs.pas @@ -64,7 +64,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; /// Checks if preference changes require that main window UI is /// updated. @@ -146,7 +147,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.pas b/Src/FrSourcePrefs.pas index b9bc9d19c..a96fa7674 100644 --- a/Src/FrSourcePrefs.pas +++ b/Src/FrSourcePrefs.pas @@ -74,7 +74,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. } @@ -165,7 +166,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. } From e8e1310e79dbbc7ee2ec75e9685360f0593845e2 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 29 Dec 2021 17:13:39 +0000 Subject: [PATCH 031/330] Permit hiding restart message in printer prefs When the printer preferences page of the preferences dialogue box is displayed as part of the full dialogue box it displays a message to inform that the settings won't take place until the program is restarted. But we don't want that message when frame is displayed on its own when displayed from print dialogue box. The new mechanism to pass custom flags to frames is used to pass a flag that hides the message. Also the message label was changed from static text to a TLabel to enable it to be displayed in the program's warning colour. --- Src/FmPrintDlg.pas | 6 +++++- Src/FrPrintingPrefs.dfm | 24 +++++++++++------------- Src/FrPrintingPrefs.pas | 25 ++++++++++++++++++++----- 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/Src/FmPrintDlg.pas b/Src/FmPrintDlg.pas index 7aa586148..9dc12e149 100644 --- a/Src/FmPrintDlg.pas +++ b/Src/FmPrintDlg.pas @@ -130,7 +130,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 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 d30b5d02c..792febbab 100644 --- a/Src/FrPrintingPrefs.pas +++ b/Src/FrPrintingPrefs.pas @@ -44,7 +44,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 @@ -56,6 +56,9 @@ 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. @@ -97,9 +100,9 @@ implementation // Delphi SysUtils, Windows, Graphics, Math, ComCtrls, // Project - FmPreferencesDlg, Hiliter.UAttrs, Hiliter.UHiliters, IntfCommon, UConsts, - UEncodings, UKeysHelper, UPrintInfo, URTFBuilder, URTFStyles, URTFUtils, - UStrUtils, UUtils; + FmPreferencesDlg, Hiliter.UAttrs, Hiliter.UHiliters, IntfCommon, UColours, + UConsts, UEncodings, UFontHelper, UKeysHelper, UPrintInfo, URTFBuilder, + URTFStyles, URTFUtils, UStrUtils, UUtils; {$R *.dfm} @@ -195,13 +198,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); From bb9beb2d47b17fbc45a2a46974caf03c7728eb5a Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 29 Dec 2021 17:34:32 +0000 Subject: [PATCH 032/330] Bump per-user config file version to 17 --- Src/FirstRun.UConfigFile.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Src/FirstRun.UConfigFile.pas b/Src/FirstRun.UConfigFile.pas index 71cf0a70f..3db8d4e84 100644 --- a/Src/FirstRun.UConfigFile.pas +++ b/Src/FirstRun.UConfigFile.pas @@ -82,7 +82,7 @@ TUserConfigFileUpdater = class(TConfigFileUpdater) strict private const /// Current user config file version. - FileVersion = 16; + FileVersion = 17; strict protected /// Returns current user config file version. class function GetFileVersion: Integer; override; From ae52bdc23f517cedd96dc8d467471f017af4d893 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 29 Dec 2021 17:34:46 +0000 Subject: [PATCH 033/330] Update config file docs with new version number --- Docs/Design/FileFormats/config.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Docs/Design/FileFormats/config.html b/Docs/Design/FileFormats/config.html index 140c10991..2e69e0cc8 100644 --- a/Docs/Design/FileFormats/config.html +++ b/Docs/Design/FileFormats/config.html @@ -167,7 +167,7 @@

    - There have been several versions of this file. The current one is version 16. The change to version 16 came with CodeSnip v4.16.0 and the removal of DelphiDabbler web service support. + There have been several versions of this file. The current one is version 17. The change to version 17 came with CodeSnip v4.19.0 and the addition of the [Prefs] section.

    From 2b38c4e6c14a4841af55cf3ccb99cf8e825bacf3 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 30 Dec 2021 11:01:18 +0000 Subject: [PATCH 034/330] Refactor: Move method to base class. The private ParentForm method of THiliterPrefsFrame was moved into the TPrefsBaseFrame base class as a protected method because it is needed by other TPrefsBaseFrame sub-classes. --- Src/FrHiliterPrefs.pas | 16 ---------------- Src/FrPrefsBase.pas | 19 ++++++++++++++++++- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/Src/FrHiliterPrefs.pas b/Src/FrHiliterPrefs.pas index 7217fa1c0..633dcad12 100644 --- a/Src/FrHiliterPrefs.pas +++ b/Src/FrHiliterPrefs.pas @@ -131,8 +131,6 @@ THiliterPrefsFrame = class(TPrefsBaseFrame) /// highlighter element.

    /// This RTF is used to display elememt in preview pane. function GenerateRTF: TRTF; - /// Returns reference to form that hosts the frame. - function ParentForm: TForm; public /// Constructs frame instance and initialises controls. /// TComponent [in] Component that owns the frame. @@ -523,20 +521,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 diff --git a/Src/FrPrefsBase.pas b/Src/FrPrefsBase.pas index 0f786a365..a72b09075 100644 --- a/Src/FrPrefsBase.pas +++ b/Src/FrPrefsBase.pas @@ -41,6 +41,8 @@ TPrefsBaseFrame = class(TFrame) strict protected class function IsFlagSupported(const Flag: UInt64): Boolean; inline; class function ExtractFrameFlag(const Flag: UInt64): UInt32; inline; + /// Returns reference to form that hosts the frame. + function ParentForm: TForm; public procedure SavePrefs(const Prefs: IPreferences); virtual; {Saves information user entered in frame. By default the method simply @@ -90,7 +92,8 @@ implementation uses // Delphi - SysUtils; + SysUtils, + Controls; {$R *.dfm} @@ -127,6 +130,20 @@ class function TPrefsBaseFrame.MakeFrameFlag(const Flag: UInt32): UInt64; 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); {Saves information user entered in frame. By default the method simply calls Deactivate. May be overridden to save any custom data that doesn't use Prefs From 8f737364e17fbf6e3a8ed342023bad005447df94 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 30 Dec 2021 11:01:58 +0000 Subject: [PATCH 035/330] Add info about and checks for common font sizes. --- Src/UFontHelper.pas | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/Src/UFontHelper.pas b/Src/UFontHelper.pas index 69383f904..43b4241e3 100644 --- a/Src/UFontHelper.pas +++ b/Src/UFontHelper.pas @@ -19,7 +19,7 @@ interface // Delphi Classes, Graphics, // Project - UBaseObjects; + UBaseObjects, UStructs; type @@ -45,6 +45,11 @@ TFontHelper = class(TNoConstructObject) @param List [in] Receives list of font sizes. Cleared before sizes added. } + /// Checks if given font size falls within range of common font + /// sizes. + /// Common font size range is between smallest and largest font + /// sizes returned by ListCommonFontSizes method. + class function IsInCommonFontSizeRange(const FontSize: Integer): Boolean; class procedure SetDefaultFont(const Font: TFont); {Sets a font to be the default UI font for the underlying operating system. @@ -92,6 +97,11 @@ TFontHelper = class(TNoConstructObject) DefaultMonoFontName = 'Courier New'; // Default mono font name DefaultMonoFontSize = 8; // Default mono font size + + public + const + // Range of common font sizes + CommonFontSizes: TRange = (Min: 7; Max: 32); end; @@ -148,6 +158,12 @@ class function TFontHelper.FontExists(const FontName: string): Boolean; Result := Screen.Fonts.IndexOf(FontName) >= 0; end; +class function TFontHelper.IsInCommonFontSizeRange( + const FontSize: Integer): Boolean; +begin + Result := CommonFontSizes.Contains(FontSize); +end; + class procedure TFontHelper.ListCommonFontSizes(const List: TStrings); {Lists all commonly used font sizes. @param List [in] Receives list of font sizes. Cleared before sizes added. @@ -157,7 +173,7 @@ class procedure TFontHelper.ListCommonFontSizes(const List: TStrings); begin Assert(Assigned(List), ClassName + '.ListCommonFontSizes: List is nil'); List.Clear; - for FontSize := 7 to 32 do + for FontSize := CommonFontSizes.Min to CommonFontSizes.Max do List.Add(IntToStr(FontSize)); end; From cae80a725d70a3b78cb8b7f0899f99a962bece7a Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 30 Dec 2021 11:05:02 +0000 Subject: [PATCH 036/330] Add facility to customise overview tree view font size. New font combo box added to Display tab of Preferences dialogue box that only permits common font sizes to be entered. New key/value pair added to Preferences object and hence to config file. Tree view drawing code updated to get font size from Preferences object --- Src/FrDisplayPrefs.dfm | 16 +++++++++ Src/FrDisplayPrefs.pas | 75 ++++++++++++++++++++++++++++++++++++++--- Src/UPreferences.pas | 33 ++++++++++++++++++ Src/USnippetsTVDraw.pas | 1 + 4 files changed, 121 insertions(+), 4 deletions(-) diff --git a/Src/FrDisplayPrefs.dfm b/Src/FrDisplayPrefs.dfm index 71572dce7..5f060c451 100644 --- a/Src/FrDisplayPrefs.dfm +++ b/Src/FrDisplayPrefs.dfm @@ -32,6 +32,14 @@ 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 cbOverviewTree: TComboBox Left = 192 Top = 2 @@ -66,4 +74,12 @@ inherited DisplayPrefsFrame: TDisplayPrefsFrame TabOrder = 3 OnClick = btnDefColoursClick end + object cbOverviewFontSize: TComboBox + Left = 192 + Top = 197 + Width = 57 + Height = 21 + TabOrder = 4 + OnChange = cbOverviewFontSizeChange + end end diff --git a/Src/FrDisplayPrefs.pas b/Src/FrDisplayPrefs.pas index b66d06bff..ab6733fe6 100644 --- a/Src/FrDisplayPrefs.pas +++ b/Src/FrDisplayPrefs.pas @@ -34,12 +34,16 @@ TDisplayPrefsFrame = class(TPrefsBaseFrame) lblUserColour: TLabel; btnDefColours: TButton; lblSourceBGColour: TLabel; + lblOverviewFontSize: TLabel; + cbOverviewFontSize: TComboBox; procedure chkHideEmptySectionsClick(Sender: TObject); procedure btnDefColoursClick(Sender: TObject); + procedure cbOverviewFontSizeChange(Sender: TObject); strict private var /// Flag indicating if changes affect UI. fUIChanged: Boolean; + fOverviewFontSize: Integer; fMainColourBox: TColorBoxEx; fMainColourDlg: TColorDialogEx; fUserColourBox: TColorBoxEx; @@ -58,6 +62,7 @@ TDisplayPrefsFrame = class(TPrefsBaseFrame) function CreateCustomColourBox(const ColourDlg: TColorDialogEx): TColorBoxEx; procedure ColourBoxChangeHandler(Sender: TObject); + procedure PopulateFontSizeCombo; public constructor Create(AOwner: TComponent); override; {Class constructor. Sets up frame and populates controls. @@ -97,14 +102,21 @@ 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; @@ -124,6 +136,8 @@ 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); + fOverviewFontSize := Prefs.OverviewFontSize; + cbOverviewFontSize.Text := IntToStr(fOverviewFontSize); end; procedure TDisplayPrefsFrame.ArrangeControls; @@ -133,12 +147,16 @@ procedure TDisplayPrefsFrame.ArrangeControls; TCtrlArranger.AlignLefts( [ lblOverviewTree, chkHideEmptySections, chkSnippetsInNewTab, - lblMainColour, lblUserColour, lblSourceBGColour, btnDefColours + lblMainColour, lblUserColour, lblSourceBGColour, btnDefColours, + lblOverviewFontSize ], 0 ); TCtrlArranger.AlignLefts( - [cbOverviewTree, fMainColourBox, fUserColourBox, fSourceBGColourBox], + [ + cbOverviewTree, fMainColourBox, fUserColourBox, fSourceBGColourBox, + cbOverviewFontSize + ], TCtrlArranger.RightOf( [lblOverviewTree, lblMainColour, lblUserColour, lblSourceBGColour], 8 @@ -164,6 +182,10 @@ procedure TDisplayPrefsFrame.ArrangeControls; TCtrlArranger.MoveBelow( [lblSourceBGColour, fSourceBGColourBox], btnDefColours, 12 ); + TCtrlArranger.AlignVCentres( + TCtrlArranger.BottomOf(btnDefColours, 12), + [lblOverviewFontSize, cbOverviewFontSize] + ); chkHideEmptySections.Width := Self.Width - 16; chkSnippetsInNewTab.Width := Self.Width - 16; end; @@ -178,6 +200,43 @@ procedure TDisplayPrefsFrame.btnDefColoursClick(Sender: TObject); fUIChanged := True; end; +procedure TDisplayPrefsFrame.cbOverviewFontSizeChange(Sender: TObject); +var + Size: Integer; // font size entered by user +begin + inherited; + // Do nothing if combo box text field cleared + if cbOverviewFontSize.Text = '' then + Exit; + if TryStrToInt(cbOverviewFontSize.Text, Size) then + begin + if TFontHelper.IsInCommonFontSizeRange(Size) then + begin + // Combo has valid value entered: update + fOverviewFontSize := Size; + fUIChanged := True; + end + else + begin + // Font size out of range + TMessageBox.Error( + ParentForm, + Format( + sErrBadOverviewFontRange, + [TFontHelper.CommonFontSizes.Min, TFontHelper.CommonFontSizes.Max] + ) + ); + cbOverviewFontSize.Text := IntToStr(fOverviewFontSize); + end; + end + else + begin + // Combo has invalid value: say so + TMessageBox.Error(ParentForm, sErrBadOverviewFontSize); + cbOverviewFontSize.Text := IntToStr(fOverviewFontSize); + end; +end; + procedure TDisplayPrefsFrame.chkHideEmptySectionsClick(Sender: TObject); {Handles clicks on "Hide Empty Sections" check box. Flags UI preferences has having changed. @@ -226,6 +285,8 @@ constructor TDisplayPrefsFrame.Create(AOwner: TComponent); fSourceBGColourBox := CreateCustomColourBox(fSourceBGColourDlg); fSourceBGColourBox.TabOrder := 5; lblSourceBGColour.FocusControl := fSourceBGColourBox; + + PopulateFontSizeCombo; end; function TDisplayPrefsFrame.CreateCustomColourBox( @@ -268,6 +329,7 @@ procedure TDisplayPrefsFrame.Deactivate(const Prefs: IPreferences); Prefs.SourceCodeBGCustomColours.CopyFrom( fSourceBGColourDlg.CustomColors, True ); + Prefs.OverviewFontSize := StrToIntDef(cbOverviewFontSize.Text, 8); end; function TDisplayPrefsFrame.DisplayName: string; @@ -309,6 +371,11 @@ function TDisplayPrefsFrame.OverviewTreeStateDesc( Result := cOTSStartStates[State]; end; +procedure TDisplayPrefsFrame.PopulateFontSizeCombo; +begin + TFontHelper.ListCommonFontSizes(cbOverviewFontSize.Items); +end; + procedure TDisplayPrefsFrame.SelectOverviewTreeState( const State: TOverviewStartState); {Selects combo box item associated with a overview treeview startup state. diff --git a/Src/UPreferences.pas b/Src/UPreferences.pas index bdf2107ec..b222b8230 100644 --- a/Src/UPreferences.pas +++ b/Src/UPreferences.pas @@ -175,6 +175,14 @@ interface property DBHeadingCustomColours[UserDefined: Boolean]: IStringList read GetDBHeadingCustomColours write SetDBHeadingCustomColours; + /// Gets size of font used in overview pane tree view. + function GetOverviewFontSize: Integer; + /// Sets size of font used in overview pane tree view. + procedure SetOverviewFontSize(const Value: Integer); + /// Size of font used in overview pane tree view. + property OverviewFontSize: Integer + read GetOverviewFontSize write SetOverviewFontSize; + /// Gets colour used for background of source code in main /// display. function GetSourceCodeBGColour: TColor; @@ -331,6 +339,9 @@ TPreferences = class(TInterfacedObject, /// either online database (UserDefined = False) or user database /// (UserDefined = True). fDBHeadingCustomColours: array[Boolean] of IStringList; + /// Records size of font used in overview pane tree view. + /// + fOverviewFontSize: Integer; /// Records colour used for background of source code in main /// display. fSourceCodeBGColour: TColor; @@ -491,6 +502,14 @@ TPreferences = class(TInterfacedObject, procedure SetDBHeadingCustomColours(UserDefined: Boolean; Value: IStringList); + /// Gets size of font used in overview pane tree view. + /// Method of IPreferences. + function GetOverviewFontSize: Integer; + + /// Sets size of font used in overview pane tree view. + /// Method of IPreferences. + procedure SetOverviewFontSize(const Value: Integer); + /// Gets colour used for background of source code in main /// display. /// Method of IPreferences. @@ -657,6 +676,7 @@ procedure TPreferences.Assign(const Src: IInterface); Self.fDBHeadingCustomColours[False] := SrcPref.DBHeadingCustomColours[False]; Self.fDBHeadingColours[True] := SrcPref.DBHeadingColours[True]; Self.fDBHeadingCustomColours[True] := SrcPref.DBHeadingCustomColours[True]; + Self.fOverviewFontSize := SrcPref.OverviewFontSize; Self.fSourceCodeBGColour := SrcPref.SourceCodeBGColour; Self.fSourceCodeBGCustomColours := SrcPref.SourceCodeBGCustomColours; Self.fPrinterOptions := SrcPref.PrinterOptions; @@ -723,6 +743,11 @@ function TPreferences.GetNamedHiliteAttrs: INamedHiliteAttrs; Result := fNamedHiliteAttrs; end; +function TPreferences.GetOverviewFontSize: Integer; +begin + Result := fOverviewFontSize +end; + function TPreferences.GetOverviewStartState: TOverviewStartState; begin Result := fOverviewStartState; @@ -825,6 +850,11 @@ procedure TPreferences.SetNamedHiliteAttrs(NamedHiliteAttrs: INamedHiliteAttrs); (fNamedHiliteAttrs as IAssignable).Assign(NamedHiliteAttrs); end; +procedure TPreferences.SetOverviewFontSize(const Value: Integer); +begin + fOverviewFontSize := Value; +end; + procedure TPreferences.SetOverviewStartState(const Value: TOverviewStartState); begin fOverviewStartState := Value; @@ -914,6 +944,7 @@ function TPreferencesPersist.Clone: IInterface; NewPref.DBHeadingCustomColours[False] := Self.fDBHeadingCustomColours[False]; NewPref.DBHeadingColours[True] := Self.fDBHeadingColours[True]; NewPref.DBHeadingCustomColours[True] := Self.fDBHeadingCustomColours[True]; + NewPref.OverviewFontSize := Self.fOverviewFontSize; NewPref.SourceCodeBGColour := Self.fSourceCodeBGColour; NewPref.SourceCodeBGCustomColours := Self.fSourceCodeBGCustomColours; NewPref.PrinterOptions := Self.fPrinterOptions; @@ -968,6 +999,7 @@ constructor TPreferencesPersist.Create; fDBHeadingCustomColours[True] := Storage.GetStrings( 'UserDBHeadingCustomColourCount', 'UserDBHeadingCustomColour%d' ); + fOverviewFontSize := Storage.GetInteger('OverviewFontSize', 9); fSourceCodeBGCustomColours := Storage.GetStrings( 'SourceCodeBGCustomColourCount', 'SourceCodeBGCustomColour%d' ); @@ -1038,6 +1070,7 @@ destructor TPreferencesPersist.Destroy; Storage.SetBoolean('ShowNewSnippetsInNewTabs', fShowNewSnippetsInNewTabs); Storage.SetInteger('MainDBHeadingColour', fDBHeadingColours[False]); Storage.SetInteger('UserDBHeadingColour', fDBHeadingColours[True]); + Storage.SetInteger('OverviewFontSize', fOverviewFontSize); Storage.SetInteger('SourceCodeBGColour', fSourceCodeBGColour); Storage.SetStrings( 'MainDBHeadingCustomColourCount', diff --git a/Src/USnippetsTVDraw.pas b/Src/USnippetsTVDraw.pas index a13af0cc1..fe568fc72 100644 --- a/Src/USnippetsTVDraw.pas +++ b/Src/USnippetsTVDraw.pas @@ -91,6 +91,7 @@ procedure TSnippetsTVDraw.CustomDrawItem(Sender: TCustomTreeView; Assert(Sender is TTreeView, ClassName + '.CustomDrawItem: Sender is not TTreeView'); TV := Sender as TTreeView; + TV.Font.Size := Preferences.OverviewFontSize; if Assigned(Node) then begin // Set font and background colour according to selected / focusses state From f1485135dbeefbb61b74a639c6ef6da9c0cf19df Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 30 Dec 2021 11:07:38 +0000 Subject: [PATCH 037/330] Update font size checks in Syntax Highlighter preferences Syntax Highlighter tab of Preferences dialogue box now checks that entered font sizes are within the range of common font sizes and displays an error where they are not. --- Src/FrHiliterPrefs.pas | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/Src/FrHiliterPrefs.pas b/Src/FrHiliterPrefs.pas index 633dcad12..08a8e26fb 100644 --- a/Src/FrHiliterPrefs.pas +++ b/Src/FrHiliterPrefs.pas @@ -215,6 +215,8 @@ implementation // Error messages sErrBadFontSize = 'Invalid font size'; + sErrBadFontRange = 'Font size out of range. ' + + 'Enter a value between %0:d and %1:d'; const /// Map of highlighter elements to descriptions. @@ -342,10 +344,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 From e08f8bbdc41a24e715148a646a14190d8439b66e Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 30 Dec 2021 11:13:30 +0000 Subject: [PATCH 038/330] Document new OverviewFontSize config file key This new key is in the per-user config file in the [Prefs:Display] section. --- Docs/Design/FileFormats/config.html | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Docs/Design/FileFormats/config.html b/Docs/Design/FileFormats/config.html index 2e69e0cc8..ed05ef9b2 100644 --- a/Docs/Design/FileFormats/config.html +++ b/Docs/Design/FileFormats/config.html @@ -913,6 +913,12 @@

    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 9. +

    From 6ad62f7f2a1257b45061ee05095015d0ce580938 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 30 Dec 2021 11:20:54 +0000 Subject: [PATCH 039/330] Update Display Preferences help topic Add information about new overview tree view font size combo box. --- Src/Help/HTML/dlg_prefs_display.htm | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Src/Help/HTML/dlg_prefs_display.htm b/Src/Help/HTML/dlg_prefs_display.htm index 2930f6aac..5ea68022a 100644 --- a/Src/Help/HTML/dlg_prefs_display.htm +++ b/Src/Help/HTML/dlg_prefs_display.htm @@ -60,8 +60,8 @@

    Choose whether empty section headings are hidden in the overview pane. Check the Hide empty section headings in - overview 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.

  • @@ -91,6 +91,12 @@

    by clicking the Use Default Colours button.

  • +
  • + Choose the size of font used in the overview pane's tree view using the Overview tree font + size combo box. +
  • From 035b2c868c1fe7267cf2bb5f20157247b216b87b Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 30 Dec 2021 11:42:58 +0000 Subject: [PATCH 040/330] Update installer messages re renamed database Fixes #28 --- Src/Install/EventHandlers.ps | Bin 7086 -> 7202 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Src/Install/EventHandlers.ps b/Src/Install/EventHandlers.ps index d81dcfb2c1c11858eaecdf1ca69e1606aae19543..2ecbd91c6c08a13a66b8d7c0ab22658b08865958 100644 GIT binary patch delta 380 zcmZ2yzQ|%j9hbzVEV4$s_KKTKgr6V5QD2iPYOA?b3i&GVVh9s6HX67UUty4(LFPa?0t|N+U zY+{~5eohL|DU+wM+lV2{IOn7$76ILns<8P5dkU{CKD%8eM~hnFSFw4Ts4S~CUPF)^ yotIxS`H@tGqi0@mNn%cpLVl4#XhBM1NvZ;>39ztJfcaPn5e^CppkT|Eb_M{ead}1n delta 255 zcmZ2vvCe!$9h-=AeoCrBa9(CXL25~{f`494W?t&#nQZRt`Cz8Pt;5N6kdK*+fpXCi&`8wDlcClIlrJ%AwLbQ9qJe~ hJy54jR+Fv}3@re Date: Thu, 30 Dec 2021 13:37:40 +0000 Subject: [PATCH 041/330] Update Preferences dialogue help pages Changed to take account of new appearance of dialogue box now that tabs have been removed. --- Src/Help/HTML/dlg_preferences.htm | 13 +++++--- Src/Help/HTML/dlg_prefs_codegen.htm | 23 ++++++++------ Src/Help/HTML/dlg_prefs_display.htm | 12 ++++---- Src/Help/HTML/dlg_prefs_general.htm | 14 ++++----- Src/Help/HTML/dlg_prefs_hiliter.htm | 16 +++++----- Src/Help/HTML/dlg_prefs_printing.htm | 37 ++++++++++++----------- Src/Help/HTML/dlg_prefs_snippetlayout.htm | 13 ++++---- Src/Help/HTML/dlg_prefs_sourcecode.htm | 16 +++++----- 8 files changed, 78 insertions(+), 66 deletions(-) diff --git a/Src/Help/HTML/dlg_preferences.htm b/Src/Help/HTML/dlg_preferences.htm index 86d6fcbdd..59991fa8a 100644 --- a/Src/Help/HTML/dlg_preferences.htm +++ b/Src/Help/HTML/dlg_preferences.htm @@ -31,8 +31,10 @@

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

    - The dialogue box is divided into 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:

    • @@ -61,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 e4eff51ca..cb08c1134 100644 --- a/Src/Help/HTML/dlg_prefs_codegen.htm +++ b/Src/Help/HTML/dlg_prefs_codegen.htm @@ -6,7 +6,7 @@ * * Copyright (C) 2010-2020, Peter Johnson (gravatar.com/delphidabbler). * - * Help topic for Code Generation tab of Preferences dialogue box. + * Help topic for Code Generation page of Preferences dialogue box. --> @@ -24,19 +24,22 @@

      - 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 when - creating test compilations and when outputting 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 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 + 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. +

      +

      + 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 @@ -107,7 +110,7 @@

      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.

      diff --git a/Src/Help/HTML/dlg_prefs_display.htm b/Src/Help/HTML/dlg_prefs_display.htm index 5ea68022a..c7d250943 100644 --- a/Src/Help/HTML/dlg_prefs_display.htm +++ b/Src/Help/HTML/dlg_prefs_display.htm @@ -6,7 +6,7 @@ * * Copyright (C) 2012-2020, Peter Johnson (gravatar.com/delphidabbler). * - * Help topic for Display tab of Preferences dialogue box. + * Help topic for Display page of Preferences dialogue box. --> @@ -24,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: diff --git a/Src/Help/HTML/dlg_prefs_general.htm b/Src/Help/HTML/dlg_prefs_general.htm index 6e0a68b05..7ec4c0f88 100644 --- a/Src/Help/HTML/dlg_prefs_general.htm +++ b/Src/Help/HTML/dlg_prefs_general.htm @@ -6,7 +6,7 @@ * * Copyright (C) 2007-2020, Peter Johnson (gravatar.com/delphidabbler). * - * Help topic for Miscellaneous tab of Preferences dialogue box. + * Help topic for Miscellaneous page of Preferences dialogue box. --> @@ -24,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 diff --git a/Src/Help/HTML/dlg_prefs_hiliter.htm b/Src/Help/HTML/dlg_prefs_hiliter.htm index c94cf2454..f690ad2c5 100644 --- a/Src/Help/HTML/dlg_prefs_hiliter.htm +++ b/Src/Help/HTML/dlg_prefs_hiliter.htm @@ -6,7 +6,7 @@ * * Copyright (C) 2007-2020, Peter Johnson (gravatar.com/delphidabbler). * - * Help topic for Syntax Highlighter tab of Preferences dialogue box. + * Help topic for Syntax Highlighter page of Preferences dialogue box. --> @@ -25,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 @@ -40,7 +41,8 @@

      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 @@ -56,7 +58,7 @@

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

      diff --git a/Src/Help/HTML/dlg_prefs_printing.htm b/Src/Help/HTML/dlg_prefs_printing.htm index 5fe14f566..ceca6a643 100644 --- a/Src/Help/HTML/dlg_prefs_printing.htm +++ b/Src/Help/HTML/dlg_prefs_printing.htm @@ -6,7 +6,7 @@ * * Copyright (C) 2007-2020, Peter Johnson (gravatar.com/delphidabbler). * - * Help topic for Printing tab of Preferences dialogue box. + * Help topic for Printing page of Preferences dialogue box. --> @@ -24,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.

      - Warning: 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. + 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. +

      +

      + 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 @@ -64,9 +66,9 @@

      When syntax highlighting is enabled the style used is as specified in the - Syntax Highlighter tab. When black and - white printing is specified the highlighter's font styles are used but - colours are ignored. + Syntax Highlighter peferences page. + When black and white printing is specified the highlighter's font styles + are used but colours are ignored.

      Page Margins @@ -79,7 +81,8 @@

      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 changed on the - Misc. tab, if it is visible. + 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 33e007c0f..d8aa5bfa4 100644 --- a/Src/Help/HTML/dlg_prefs_snippetlayout.htm +++ b/Src/Help/HTML/dlg_prefs_snippetlayout.htm @@ -6,7 +6,7 @@ * * Copyright (C) 2012-2020, Peter Johnson (gravatar.com/delphidabbler). * - * Help topic for Snippet Layout tab of Preferences dialogue box. + * Help topic for Snippet Layout page of Preferences dialogue box. --> @@ -24,10 +24,10 @@

      - 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 >Detail Pane.

      - The tab is accessed by selecting the Tools | Preferences menu - item then clicking the Snippet Layout 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 diff --git a/Src/Help/HTML/dlg_prefs_sourcecode.htm b/Src/Help/HTML/dlg_prefs_sourcecode.htm index 5a2b59f85..eef9145b5 100644 --- a/Src/Help/HTML/dlg_prefs_sourcecode.htm +++ b/Src/Help/HTML/dlg_prefs_sourcecode.htm @@ -6,7 +6,7 @@ * * Copyright (C) 2007-2020, Peter Johnson (gravatar.com/delphidabbler). * - * Help topic for Code Formatting tab of Preferences dialogue box. + * Help topic for Code Formatting page of Preferences dialogue box. --> @@ -26,14 +26,14 @@

      - Code Formatting Preferences + Code Formatting Preferences Page

      - This tab 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 tab is accessed by - selecting the Tools | Preferences menu item then clicking the - Code Formatting tab. + 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 @@ -87,7 +87,7 @@

      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. + Syntax Highlighter preferences page.

      You can override the default format when saving source code to file by From 025f60b1600c229dbb8d6503213c12de9c7a1679 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 30 Dec 2021 13:46:17 +0000 Subject: [PATCH 042/330] Update Preferences page terminology Change references to dialogue's "tabs" to "pages" --- Src/Help/HTML/about_compiler_checks.htm | 2 +- Src/Help/HTML/detail_pane.htm | 4 ++-- Src/Help/HTML/dlg_configcompilers.htm | 2 +- Src/Help/HTML/dlg_editsnippet.htm | 2 +- Src/Help/HTML/dlg_firstrun.htm | 4 ++-- Src/Help/HTML/dlg_hilitemgr.htm | 2 +- Src/Help/HTML/dlg_pagesetup.htm | 4 ++-- Src/Help/HTML/dlg_savehiliter.htm | 4 ++-- Src/Help/HTML/explain_all_compilers_hidden.htm | 4 ++-- Src/Help/HTML/new.htm | 8 ++++---- Src/Help/HTML/overview_pane.htm | 4 ++-- Src/Help/HTML/task_copysnippet.htm | 2 +- Src/Help/HTML/task_generateunit.htm | 2 +- Src/Help/HTML/task_printroutine.htm | 2 +- 14 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Src/Help/HTML/about_compiler_checks.htm b/Src/Help/HTML/about_compiler_checks.htm index e79e5626e..5d9469874 100644 --- a/Src/Help/HTML/about_compiler_checks.htm +++ b/Src/Help/HTML/about_compiler_checks.htm @@ -96,7 +96,7 @@

      warnings following a test compile, you may be able to inhibit them by configuring CodeSnip's code generator to emit suitable compiler directives. This is done using the Code - Generation tab of the Preferences + Generation page of the Preferences dialogue box. Display the dialogue box using the Tools | Preferences menu option.

      diff --git a/Src/Help/HTML/detail_pane.htm b/Src/Help/HTML/detail_pane.htm index 108747dba..2fcf14698 100644 --- a/Src/Help/HTML/detail_pane.htm +++ b/Src/Help/HTML/detail_pane.htm @@ -57,7 +57,7 @@

      Newly created snippets and categories may be displayed in new tabs. This happens only if the feature is selected in the Display tab of the Display page of the Preferences dialogue box.

    • @@ -205,7 +205,7 @@

      >snippet kind. The program's default settings for these views can be customised using the Snippet Layout tab of the Snippet Layout page of the Preferences dialogue box.

      diff --git a/Src/Help/HTML/dlg_configcompilers.htm b/Src/Help/HTML/dlg_configcompilers.htm index b93ea7c0d..4f82b47e8 100644 --- a/Src/Help/HTML/dlg_configcompilers.htm +++ b/Src/Help/HTML/dlg_configcompilers.htm @@ -93,7 +93,7 @@

      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 Snippet Layout - tab of the Preferences dialogue box to + page of the Preferences dialogue box to remove the Compiler Results Table component from the different kinds of snippet pages.

      diff --git a/Src/Help/HTML/dlg_editsnippet.htm b/Src/Help/HTML/dlg_editsnippet.htm index 54af7a6f2..1ed22d748 100644 --- a/Src/Help/HTML/dlg_editsnippet.htm +++ b/Src/Help/HTML/dlg_editsnippet.htm @@ -158,7 +158,7 @@

      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. diff --git a/Src/Help/HTML/dlg_firstrun.htm b/Src/Help/HTML/dlg_firstrun.htm index 7a5865f2d..769ca0d2b 100644 --- a/Src/Help/HTML/dlg_firstrun.htm +++ b/Src/Help/HTML/dlg_firstrun.htm @@ -85,7 +85,7 @@

      You will need to redo any customisation using the Syntax Highlighter tab of the Syntax Highlighter page of the Preferences dialogue box.
      @@ -98,7 +98,7 @@

      You will need to reconfigure them using the Code Formatting tab of the Code Formatting page of the Preferences dialogue box.
      diff --git a/Src/Help/HTML/dlg_hilitemgr.htm b/Src/Help/HTML/dlg_hilitemgr.htm index 0d05f5d03..049f3a1fb 100644 --- a/Src/Help/HTML/dlg_hilitemgr.htm +++ b/Src/Help/HTML/dlg_hilitemgr.htm @@ -30,7 +30,7 @@

      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.

      diff --git a/Src/Help/HTML/dlg_pagesetup.htm b/Src/Help/HTML/dlg_pagesetup.htm index f6aa6bafd..e1c5a01ed 100644 --- a/Src/Help/HTML/dlg_pagesetup.htm +++ b/Src/Help/HTML/dlg_pagesetup.htm @@ -74,13 +74,13 @@

      The default margin sizes can be specified using the - Printing tab of 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_savehiliter.htm b/Src/Help/HTML/dlg_savehiliter.htm index 8dc4e6d9e..105759401 100644 --- a/Src/Help/HTML/dlg_savehiliter.htm +++ b/Src/Help/HTML/dlg_savehiliter.htm @@ -28,7 +28,7 @@

      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.

      @@ -44,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's Use Named Style drop-down button. + preferences page's Use Named Style drop-down button.

      diff --git a/Src/Help/HTML/explain_all_compilers_hidden.htm b/Src/Help/HTML/explain_all_compilers_hidden.htm index 5a7ea9845..f776bb80a 100644 --- a/Src/Help/HTML/explain_all_compilers_hidden.htm +++ b/Src/Help/HTML/explain_all_compilers_hidden.htm @@ -95,13 +95,13 @@

      Compiler Results Table component from the page layout of each snippet type using the Snippet Layout tab of the Snippet Layout page of the Preferences dialogue box.

      Display the dialogue box from the Tools | Preferences menu option - and select the Snippet Layout tab. Select a page layout from the + and select the Snippet Layout page. Select a page layout from the Edit page layout for snippet kind drop-down list. Check to see if the Compiler Results Table component is listed in the Displayed page components list box. If so, select it and click diff --git a/Src/Help/HTML/new.htm b/Src/Help/HTML/new.htm index 5fe3e2af5..ec5caad10 100644 --- a/Src/Help/HTML/new.htm +++ b/Src/Help/HTML/new.htm @@ -65,7 +65,7 @@

    • There is now finer control over the warnings in generated code via the $WARN directive. This is controlled on the - Code Generation tab of the + Code Generation page of the Preferences dialogue box.
    • @@ -73,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.

    • @@ -107,7 +107,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.

    • @@ -125,7 +125,7 @@

    • 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.
    • diff --git a/Src/Help/HTML/overview_pane.htm b/Src/Help/HTML/overview_pane.htm index 9e7d037b7..2236acea4 100644 --- a/Src/Help/HTML/overview_pane.htm +++ b/Src/Help/HTML/overview_pane.htm @@ -46,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.

      @@ -98,7 +98,7 @@

      You can configure whether the overview pane tree view appears fully expanded or fully collapsed on start-up by using the Display tab of the Display page of the Preferences dialogue box.

      diff --git a/Src/Help/HTML/task_copysnippet.htm b/Src/Help/HTML/task_copysnippet.htm index 5b6fdd6f3..a7fe998ff 100644 --- a/Src/Help/HTML/task_copysnippet.htm +++ b/Src/Help/HTML/task_copysnippet.htm @@ -50,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.

      diff --git a/Src/Help/HTML/task_generateunit.htm b/Src/Help/HTML/task_generateunit.htm index 72f8794db..115839f93 100644 --- a/Src/Help/HTML/task_generateunit.htm +++ b/Src/Help/HTML/task_generateunit.htm @@ -69,7 +69,7 @@

      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 793148ca9..370895bdc 100644 --- a/Src/Help/HTML/task_printroutine.htm +++ b/Src/Help/HTML/task_printroutine.htm @@ -49,7 +49,7 @@

      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.

      From 5989361f695fe8ac166ece761f910ec95ab860f1 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 30 Dec 2021 14:03:44 +0000 Subject: [PATCH 043/330] Correct Preferences related terminology in comments --- Src/FmPreferencesDlg.pas | 23 ++++++++++++----------- Src/FrCodeGenPrefs.pas | 3 ++- Src/FrDisplayPrefs.pas | 3 ++- Src/FrGeneralPrefs.pas | 3 ++- Src/FrHiliterPrefs.pas | 5 +++-- Src/FrPrintingPrefs.pas | 5 +++-- Src/FrSnippetLayoutPrefs.pas | 3 ++- Src/FrSourcePrefs.pas | 3 ++- Src/UDialogMgr.pas | 6 +++--- 9 files changed, 31 insertions(+), 23 deletions(-) diff --git a/Src/FmPreferencesDlg.pas b/Src/FmPreferencesDlg.pas index 623b4c453..2572939b8 100644 --- a/Src/FmPreferencesDlg.pas +++ b/Src/FmPreferencesDlg.pas @@ -27,7 +27,7 @@ interface /// Dialog box that sets user preferences. /// /// - /// This dialog box displays tabs for preferences frames registered with the + /// This dialog box displays pages for preferences frames registered with the /// dialog box. /// TPreferencesDlg = class(TGenericOKDlg, INoPublicConstruct) @@ -161,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. } @@ -188,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 @@ -306,7 +307,7 @@ class function TPreferencesDlg.Execute(AOwner: TComponent; procedure TPreferencesDlg.FormDestroy(Sender: TObject); begin - // Save current tab + // Save current page if Assigned(pcMain.ActivePage) then Preferences.LastTab := MapTabSheetToPage(pcMain.ActivePage).DisplayName; inherited; @@ -343,7 +344,7 @@ procedure TPreferencesDlg.InitForm; // Display and initialise required pages for TabIdx := 0 to Pred(pcMain.PageCount) do MapTabSheetToPage(TabIdx).LoadPrefs(fLocalPrefs, fFrameFlags); - // Select last use tab sheet (or 1st if last not known) + // Select last used tab sheet (or 1st if last not known) fCurrentPageIdx := GetLastTabIdx; if fCurrentPageIdx < 0 then fCurrentPageIdx := 0; diff --git a/Src/FrCodeGenPrefs.pas b/Src/FrCodeGenPrefs.pas index f22bb4f6b..7e572fb9a 100644 --- a/Src/FrCodeGenPrefs.pas +++ b/Src/FrCodeGenPrefs.pas @@ -7,7 +7,8 @@ * * 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. } diff --git a/Src/FrDisplayPrefs.pas b/Src/FrDisplayPrefs.pas index ab6733fe6..fb331d6ba 100644 --- a/Src/FrDisplayPrefs.pas +++ b/Src/FrDisplayPrefs.pas @@ -6,7 +6,8 @@ * Copyright (C) 2012-2020, 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. } diff --git a/Src/FrGeneralPrefs.pas b/Src/FrGeneralPrefs.pas index 9fa331b5e..d2fb34865 100644 --- a/Src/FrGeneralPrefs.pas +++ b/Src/FrGeneralPrefs.pas @@ -6,7 +6,8 @@ * Copyright (C) 2007-2020, 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. } diff --git a/Src/FrHiliterPrefs.pas b/Src/FrHiliterPrefs.pas index 08a8e26fb..6c6e1cb69 100644 --- a/Src/FrHiliterPrefs.pas +++ b/Src/FrHiliterPrefs.pas @@ -6,8 +6,9 @@ * Copyright (C) 2006-2020, 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. } diff --git a/Src/FrPrintingPrefs.pas b/Src/FrPrintingPrefs.pas index 792febbab..02ad303aa 100644 --- a/Src/FrPrintingPrefs.pas +++ b/Src/FrPrintingPrefs.pas @@ -5,8 +5,9 @@ * * Copyright (C) 2007-2020, Peter Johnson (gravatar.com/delphidabbler). * - * Implements a frame that allows user to set printing preferences. Designed for - * use as one of the tabs in the Preferences dialogue box. + * Implements a frame that allows user to set printing preferences. + * + * Designed for use as one of the pages in the Preferences dialogue box. } diff --git a/Src/FrSnippetLayoutPrefs.pas b/Src/FrSnippetLayoutPrefs.pas index 246193f4a..42b04f88d 100644 --- a/Src/FrSnippetLayoutPrefs.pas +++ b/Src/FrSnippetLayoutPrefs.pas @@ -7,7 +7,8 @@ * * 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. } diff --git a/Src/FrSourcePrefs.pas b/Src/FrSourcePrefs.pas index a96fa7674..0666fd94b 100644 --- a/Src/FrSourcePrefs.pas +++ b/Src/FrSourcePrefs.pas @@ -6,7 +6,8 @@ * Copyright (C) 2006-2020, 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. } diff --git a/Src/UDialogMgr.pas b/Src/UDialogMgr.pas index 4488e5149..7c9c28c1a 100644 --- a/Src/UDialogMgr.pas +++ b/Src/UDialogMgr.pas @@ -69,17 +69,17 @@ TDialogMgr = class(TComponent) function ExecFindXRefsDlg(const ASnippet: TSnippet; out ASearch: ISearch): Boolean; - /// Displays Preferences dialogue box showing all tabs. + /// Displays Preferences dialogue box showing all pages. /// Boolean [out] Flag that indicates if the UI /// needs to be updated as a result of changes to preferences. /// Boolean. True if user OKd dialogue or False if user cancelled. /// function ExecPreferencesDlg(out UpdateUI: Boolean): Boolean; overload; - /// Displays Preference dialogue box showing a single tab. + /// Displays Preference dialogue box showing a single page. /// /// string [in] Class name of frame that - /// implements the content of the tab this is to be displayed. + /// implements the content of the page this is to be displayed. /// Boolean [out] Flag that indicates if the UI /// needs to be updated as a result of changes to preferences. /// Boolean. True if user OKd dialogue or False if user cancelled. From 08bdc2167fb72aa6bcdaee31a74a3925efd8d0c3 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 30 Dec 2021 14:07:15 +0000 Subject: [PATCH 044/330] Update re changes in Preferences dialogue terminology --- Docs/ReadMe.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Docs/ReadMe.txt b/Docs/ReadMe.txt index 19f157d12..c7c2339a8 100644 --- a/Docs/ReadMe.txt +++ b/Docs/ReadMe.txt @@ -245,14 +245,14 @@ 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" tab of + 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" tab of the + 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, From e7c2a77021ed6ec2e73b7a8e61b1a9b13ee09709 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 30 Dec 2021 17:58:40 +0000 Subject: [PATCH 045/330] Add change log to REML documentation --- Docs/Design/reml.html | 77 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/Docs/Design/reml.html b/Docs/Design/reml.html index ecf136fb9..0526d83dd 100644 --- a/Docs/Design/reml.html +++ b/Docs/Design/reml.html @@ -201,6 +201,9 @@
    • Character Entities
    • +
    • + Change Log +
    @@ -384,6 +387,80 @@

    +
    + +

    Change Log

    + +

    + This section notes the changes in the various versions of REML. +

    + +

    + v1 of 2008/12/31 +

    + +

    + Introduced in CodeSnip v2.2.5 +

    + +
      +
    • + Supported tags: <strong> and <a>. +
    • +
    • + Supported entities: &gt;, &lt;, &quot; and &amp;. +
    • +
    • + Supported protocols for use in <a> tags: http. +
    • +
    + +

    + v2 of 2009/06/29 +

    + +

    + Introduced in CodeSnip v3.0 +

    + +
      +
    • + Added tags: <em>, <var>, <warning>, <mono>, <p> and <heading>. +
    • +
    • + Added entity: &copy;. +
    • +
    + +

    + v3 of 2009/07/06 +

    + +

    + Introduced in CodeSnip v3.0.1 +

    + +
      +
    • + Added protocol for use in <a> tags: file. +
    • +
    + +

    + v4 of 2011/12/31 +

    + +

    + Introduced in CodeSnip v4.0 alpha 1 (preview) +

    + +
      +
    • + Added protocol for use in <a> tags: https. +
    • +
    + +
    From 60db2fa2cba7a68870d431545adbf2258646c0eb Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 30 Dec 2021 18:01:17 +0000 Subject: [PATCH 046/330] Major overhaul of user database documentation * Add contents section. * Revise last section into two: Change Log & Note For File Readers * Add minor version numbers retrospectively and detail how they relate to compiler support changes. * Correct some database and REML version errors --- Docs/Design/FileFormats/user-db.html | 405 +++++++++++++++++++++------ 1 file changed, 315 insertions(+), 90 deletions(-) diff --git a/Docs/Design/FileFormats/user-db.html b/Docs/Design/FileFormats/user-db.html index 20270c576..98e2b5101 100644 --- a/Docs/Design/FileFormats/user-db.html +++ b/Docs/Design/FileFormats/user-db.html @@ -41,6 +41,42 @@

    User Database Files

    +
    + +

    + Contents +

    + + + +
    + +
    +

    Introduction

    @@ -62,10 +98,13 @@

    - 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

    @@ -78,9 +117,7 @@

    - 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.

    @@ -88,11 +125,15 @@

    inherit a copy of a user database in an earlier format.

    +

    + +
    +

    File Format

    -

    +

    XML File

    @@ -415,16 +456,16 @@

    • - version 2: indicates REML v1 + version 2: supports REML v1.
    • - version 3: indicates REML v2 + version 3: supports REML v2.
    • - versions 4..5: indicates REML v3. + version 4: supports REML v3.
    • - version 6: indicates REML v4. + versions 5 & 6: supports REML v4.
    @@ -504,85 +545,87 @@

    • - d2 – Delphi 2 compiler + d2 – Delphi 2 compiler (all versions)
    • - d3 – Delphi 3 compiler + d3 – Delphi 3 compiler (all versions)
    • - d4 – Delphi 4 compiler + d4 – Delphi 4 compiler (all versions)
    • - d5 – Delphi 5 compiler + d5 – Delphi 5 compiler (all versions)
    • - d6 – Delphi 6 compiler + d6 – Delphi 6 compiler (all versions)
    • - d7 – Delphi 7 compiler + d7 – Delphi 7 compiler (all versions)
    • - d2005 – Delphi 2005 compiler + d2005 – Delphi 2005 compiler (all versions)
    • - d2006 – Delphi 2006 compiler + d2006 – Delphi 2006 compiler (all versions)
    • - d2007 – Delphi 2007 compiler + d2007 – Delphi 2007 compiler (all versions)
    • - d2009 – Delphi 2009 compiler + d2009 – Delphi 2009 compiler (v1.1 & later)
    • - d2010 – Delphi 2010 compiler + d2010 – Delphi 2010 compiler (v4.1 & later)
    • - dXE – Delphi XE compiler + dXE – Delphi XE compiler (v4.2 & later)
    • - dXE2 – Delphi XE2 compiler + dXE2 – Delphi XE2 compiler (v4.3 & later)
    • - dXE3 – Delphi XE3 compiler + dXE3 – Delphi XE3 compiler (v4.4..v4.5 and v6.1 & 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. + dXE4 – Delphi XE4 compiler (v4.5 only) +
      Note: CodeSnip 3 used correct dXE4 id, but CodeSnip 4 did not (see dDX4 below).
    • - dXE5 – Delphi XE5 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.
    • - dXE6 – Delphi XE6 compiler + dXE5 – Delphi XE5 compiler (v6.3 & later)
    • - dXE7 – Delphi XE7 compiler + dXE6 – Delphi XE6 compiler (v6.4 & later)
    • - dXE8 – Delphi XE8 compiler + dXE7 – Delphi XE7 compiler (v6.5 & later)
    • - d10s – Delphi 10 Seattle compiler + dXE8 – Delphi XE8 compiler (v6.6 & later)
    • - d101b – Delphi 10.1 Berlin compiler + d10s – Delphi 10 Seattle compiler (v6.7 & later)
    • - d102t – Delphi 10.2 Tokyo compiler + d101b – Delphi 10.1 Berlin compiler (v6.8 & later)
    • - d103r – Delphi 10.3 Rio compiler + d102t – Delphi 10.2 Tokyo compiler (v6.9 & later)
    • - d104s – Delphi 10.4 Sydney compiler + d103r – Delphi 10.3 Rio compiler (v6.9 & later)
    • - d11a – Delphi 11 Alexandria compiler + d104s – Delphi 10.4 Sydney compiler (v6.9 & later)
    • - fpc – Free Pascal compiler + d11a – Delphi 11 Alexandria compiler (v6.10 & later) +
    • +
    • + fpc – Free Pascal compiler (all versions)
    @@ -680,7 +723,7 @@

    -

    +

    Source Code Files

    @@ -696,29 +739,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 @@ -730,83 +807,137 @@

      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. -

    +

    +
    - 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 supported version of REML was updated to v4. -
    -
    +

    + 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 @@ -815,20 +946,99 @@

      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 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 Alexandria. +
    +
    -

    +

    + +
    + +

    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 valid REML code - 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.

    @@ -841,7 +1051,8 @@

    Readers of v1 to v5 files must:

    -
      + +
      • Convert the plain text snippet description read from codesnip-data/routines/routine/description into the REML @@ -853,6 +1064,20 @@

      +

      + 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. +

      + +

    + From 11297daff3c5c4a5cec3e3d7f93ee8a9c5d65d80 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 30 Dec 2021 20:16:50 +0000 Subject: [PATCH 047/330] Fix content & formatting errors --- Docs/Design/FileFormats/user-db.html | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Docs/Design/FileFormats/user-db.html b/Docs/Design/FileFormats/user-db.html index 98e2b5101..6c0fe0a88 100644 --- a/Docs/Design/FileFormats/user-db.html +++ b/Docs/Design/FileFormats/user-db.html @@ -335,7 +335,7 @@

    - codesnip-export/routines/routine/highlight-source + codesnip-data/routines/routine/highlight-source
      @@ -825,9 +825,9 @@

      Version 3 - 29 June 2009
      -

      - Introduced with CodeSnip v3.0. -

      +

      + Introduced with CodeSnip v3.0. +

      The following tag is no longer supported:

      @@ -943,7 +943,7 @@

      codesnip-data/routines/routine/display-name
    • - codesnip-export/routines/routine/highlight-source + codesnip-data/routines/routine/highlight-source
    @@ -1059,7 +1059,7 @@

    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.
  • From 5d3f046eb8b5eb08dfb1452123f3ca5bfc176764 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 30 Dec 2021 20:58:13 +0000 Subject: [PATCH 048/330] Major overhaul of export file documentation * Add contents section. * Revise last section into two: Change Log & Note For File Readers * Add minor version numbers retrospectively and detail how they relate to compiler support changes. * Correct some database and REML version errors. --- Docs/Design/FileFormats/export.html | 376 +++++++++++++++++++++------- 1 file changed, 286 insertions(+), 90 deletions(-) diff --git a/Docs/Design/FileFormats/export.html b/Docs/Design/FileFormats/export.html index 62c28b0f9..58e394aa6 100644 --- a/Docs/Design/FileFormats/export.html +++ b/Docs/Design/FileFormats/export.html @@ -41,6 +41,34 @@

    Export Files

    +
    + +

    + Contents +

    + + + +
    + +
    +

    Introduction

    @@ -55,6 +83,10 @@

    these is explained below.

    +

    + +
    +

    Encoding

    @@ -73,6 +105,10 @@

    this attribute.

    +

    + +
    +

    File Format

    @@ -308,7 +344,7 @@

    - codesnip-data/routines/routine/display-name + codesnip-export/routines/routine/display-name
      @@ -319,7 +355,7 @@

      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.

    @@ -395,18 +431,16 @@

    • - version 2: indicates REML v1 + version 2: supports REML v1.
    • - version 3: indicates REML v2 + version 3: supports REML v2.
    • - versions 4 and 5: indicates REML - v3. + version 4: supports REML v3.
    • - versions 6 and later: indicates REML - v4. + versions 5 & 6: supports REML v4.
    @@ -486,85 +520,87 @@

    • - d2 – Delphi 2 compiler + d2 – Delphi 2 compiler (all versions) +
    • +
    • + d3 – Delphi 3 compiler (all versions)
    • - d3 – Delphi 3 compiler + d4 – Delphi 4 compiler (all versions)
    • - d4 – Delphi 4 compiler + d5 – Delphi 5 compiler (all versions)
    • - d5 – Delphi 5 compiler + d6 – Delphi 6 compiler (all versions)
    • - d6 – Delphi 6 compiler + d7 – Delphi 7 compiler (all versions)
    • - d7 – Delphi 7 compiler + d2005 – Delphi 2005 compiler (all versions)
    • - d2005 – Delphi 2005 compiler + d2006 – Delphi 2006 compiler (all versions)
    • - d2006 – Delphi 2006 compiler + d2007 – Delphi 2007 compiler (all versions)
    • - d2007 – Delphi 2007 compiler + d2009 – Delphi 2009 compiler (all versions)
    • - d2009 – Delphi 2009 compiler + d2010 – Delphi 2010 compiler (v4.1 & later)
    • - d2010 – Delphi 2010 compiler + dXE – Delphi XE compiler (v4.2 & later)
    • - dXE – Delphi XE compiler + dXE2 – Delphi XE2 compiler (v4.3 & later)
    • - dXE2 – Delphi XE2 compiler + dXE3 – Delphi XE3 compiler (v4.4..v4.5 and v6.1 & later)
    • - dXE3 – Delphi XE3 compiler + dXE4 – Delphi XE4 compiler (v4.5 only) +
      Note: CodeSnip 3 used correct dXE4 id, but CodeSnip 4 did not (see dDX4 below).
    • - 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. + 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.
    • - dXE5 – Delphi XE5 compiler + dXE5 – Delphi XE5 compiler (v6.3 & later)
    • - dXE6 – Delphi XE6 compiler + dXE6 – Delphi XE6 compiler (v6.4 & later)
    • - dXE7 – Delphi XE7 compiler + dXE7 – Delphi XE7 compiler (v6.5 & later)
    • - dXE8 – Delphi XE8 compiler + dXE8 – Delphi XE8 compiler (v6.6 & later)
    • - d10s – Delphi 10 Seattle compiler + d10s – Delphi 10 Seattle compiler (v6.7 & later)
    • - d101b – Delphi 10.1 Berlin compiler + d101b – Delphi 10.1 Berlin compiler (v6.8 & later)
    • - d102t – Delphi 10.2 Tokyo compiler + d102t – Delphi 10.2 Tokyo compiler (v7.1 & later)
    • - d103r – Delphi 10.3 Rio compiler + d103r – Delphi 10.3 Rio compiler (v7.1 & later)
    • - d104s – Delphi 10.4 Sydney compiler + d104s – Delphi 10.4 Sydney compiler (v7.1 & later)
    • - d11a – Delphi 11 Alexandria compiler + d11a – Delphi 11 Alexandria compiler (v7.2 & later)
    • - fpc – Free Pascal compiler + fpc – Free Pascal compiler (all versions)
    @@ -662,28 +698,58 @@

    + + +
    +

    - 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 @@ -695,113 +761,241 @@

      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. +

    +

    + Introduced with CodeSnip v3.0. +

    • 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. -
    -
    - The supported version of REML was updated to v4. -
    -
    +

    + 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 + Version 7 - 31 May 2020
    - The codesnip-export/user-info tag and sub-tags were no longer supported. +

    + 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 Alexandria. +
    +
    -

    - 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".

    @@ -812,7 +1006,7 @@

    • 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.
    • @@ -825,6 +1019,8 @@

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

      +

    + From 31da54078491bb06752150b5c7200b4a4311ec92 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 30 Dec 2021 21:00:24 +0000 Subject: [PATCH 049/330] Delete redundant file File was long ago removed from project but not deleted. --- Src/FrNewsPrefs.pas | 149 -------------------------------------------- 1 file changed, 149 deletions(-) delete mode 100644 Src/FrNewsPrefs.pas diff --git a/Src/FrNewsPrefs.pas b/Src/FrNewsPrefs.pas deleted file mode 100644 index 27d82ca4f..000000000 --- a/Src/FrNewsPrefs.pas +++ /dev/null @@ -1,149 +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 https://mozilla.org/MPL/2.0/ - * - * Copyright (C) 2011-2020, Peter Johnson (gravatar.com/delphidabbler). - * - * 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. - } - /// Checks if preference changes require that main window UI is - /// updated. - /// Called when dialog box containing frame is closing. Always - /// returns False because these preferences never affect UI. - 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. From 7c0ab06bdb9ff2791b1247c41d06112ebad75e3e Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 31 Dec 2021 10:41:27 +0000 Subject: [PATCH 050/330] Add MPL2 boilerplate header comment Fixes #36 --- Src/FirstRun.FmWhatsNew.FrHTML.pas | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Src/FirstRun.FmWhatsNew.FrHTML.pas b/Src/FirstRun.FmWhatsNew.FrHTML.pas index ce0274836..69110c448 100644 --- a/Src/FirstRun.FmWhatsNew.FrHTML.pas +++ b/Src/FirstRun.FmWhatsNew.FrHTML.pas @@ -1,3 +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 https://mozilla.org/MPL/2.0/ + * + * Copyright (C) 2020-2021, Peter Johnson (gravatar.com/delphidabbler). + * + * Frame that displays HTML of "what's new" message in a TWebBrowser control. +} + + unit FirstRun.FmWhatsNew.FrHTML; interface From aca5520ec921b34f95a445a192e3f1a9339ff00f Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 31 Dec 2021 10:49:27 +0000 Subject: [PATCH 051/330] Update README re switch to master as GitHub default branch --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8d3a589e4..be346abbd 100644 --- a/README.md +++ b/README.md @@ -37,13 +37,13 @@ The following support is available CodeSnip users: There's also plenty of info available on how to compile CodeSnip from source - see below. -* These links take you to the most recent version of the documents -- they can change from release to release. +* This link takes you to the most recent version of the read-me file -- it can change from release to release. ## Source Code CodeSnip's source code is maintained in the [`delphidabbler/codesnip`](https://github.com/delphidabbler/codesnip) Git repository on GitHub†. -[Git Flow](https://nvie.com/posts/a-successful-git-branching-model/) methodology has been adopted, with the exception of some branches that have been used in abortive attempts to start work on CodeSnip 5. +[Git Flow](https://nvie.com/posts/a-successful-git-branching-model/) methodology has been adopted, with the exception of some branches that have been used in various attempts to start work on CodeSnip 5. The following branches existed at the time when CodeSnip v4.16.0 was released: @@ -52,9 +52,9 @@ The following branches existed at the time when CodeSnip v4.16.0 was released: * `pagoda`: An abortive attempt at developing CodeSnip 5. Work on this branch has halted. It does not follow GitFlow methodology. ***Do not use this branch: it may be pruned.*** * `pavilion`: Another attempt at working on CodeSnip 5. It branched off `pagoda` and it's future is uncertain. Again it does not follow GitFlow methodology. -New features and most bug fixes are worked on in `feature/xxxx` branches locally. They are merged into `develop` as they are completed and the branches are deleted. +New features and most bug fixes are worked on in `feature/xxxx` branches that are branched off `develop` locally. They are merged into `develop` as they are completed and the branches are deleted. -**Note** that the default branch on GitHub is `develop` rather than `master`. This is because that's where all the work takes place. If you want to see the state of play at the last release make sure to switch to `master`. +Note that the default branch on GitHub is `master`, which contains the state of the project as of the latest release. If you want to see the current state of play with new developments switch to `develop`. > † 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/). From 87978cfc5cfa3308afc736b89def26dd79178190 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 31 Dec 2021 11:04:26 +0000 Subject: [PATCH 052/330] Change code page for legacy database files to 1252 Pre-Unicode Code Snippet Database files were written using the default code page in the UK: 1252. Such legacy file were being read back using the system default code page, which outside the UK may not be 1252. Fixes #35 --- Src/UMainDBFileReader.pas | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Src/UMainDBFileReader.pas b/Src/UMainDBFileReader.pas index c8ea43000..235b20ed7 100644 --- a/Src/UMainDBFileReader.pas +++ b/Src/UMainDBFileReader.pas @@ -30,6 +30,10 @@ interface /// TMainDBFileReader = class(TObject) strict private + const + /// ANSI code page used for v4 and earlier database files. + /// + LegacyCodePage = TEncodingHelper.Windows1252CodePage; var /// Encoding to use when reading text files. fEncoding: TEncoding; @@ -129,7 +133,7 @@ function TMainDBFileReader.GetFileEncoding(const FileName: string): TEncoding; finally FS.Free; end; - Result := TEncoding.Default; + Result := TEncodingHelper.GetEncoding(LegacyCodePage); end; function TMainDBFileReader.ReadAllStrings(const FileName: string): IStringList; From 12aa2896f83d5348c91c2e2aeb6c58ed658ae317 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 31 Dec 2021 11:32:09 +0000 Subject: [PATCH 053/330] Delete redundant, orphaned file Thought I'd done this already! --- Src/FrNewsPrefs.dfm | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 Src/FrNewsPrefs.dfm 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 From 88a2f94479ea317dc8c99b4959a5c19900aefacc Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 31 Dec 2021 11:32:09 +0000 Subject: [PATCH 054/330] Delete redundant file This .dfm file matches .pas file deleted at commit 31da540 --- Src/FrNewsPrefs.dfm | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 Src/FrNewsPrefs.dfm 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 From fdd7e1d0c48c88303c8891443a12d7e66f4f4fc9 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 31 Dec 2021 14:27:18 +0000 Subject: [PATCH 055/330] Update copyright date in some license files Updated to have 2021 in copyright date ranges of licenses: * license displayed by installer * license for branding images --- Src/Install/Assets/License.rtf | Bin 1189 -> 1189 bytes Src/Res/Img/Branding/LICENSE | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Src/Install/Assets/License.rtf b/Src/Install/Assets/License.rtf index 28e3ecd94593d2e7f719cc3ad9eb4679db31eeae..998dd2d2fa0ba0479cd2a88f475f28678eab7bbd 100644 GIT binary patch delta 14 VcmZ3=xs-DQ8zZCPW_HGT%m5zH1EK%` delta 14 VcmZ3=xs-DQ8zZB^W_HGT%m5zB1EBx_ diff --git a/Src/Res/Img/Branding/LICENSE b/Src/Res/Img/Branding/LICENSE index 8f9a4b79b..96a458c5e 100644 --- a/Src/Res/Img/Branding/LICENSE +++ b/Src/Res/Img/Branding/LICENSE @@ -1,5 +1,5 @@ All image files in the Src/Res/Img/Branding directory are copyright -(C) 2012-2020, Peter Johnson (gravatar.com/delphidabbler). +(C) 2012-2021, Peter Johnson (gravatar.com/delphidabbler). 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. From a0d7ba0324967a3166b561a2f0b0aad32a63df1c Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 31 Dec 2021 17:03:52 +0000 Subject: [PATCH 056/330] Fix linting errors in CHANGELOG.md --- CHANGELOG.md | 2868 +++++++++++++++++++++++--------------------------- 1 file changed, 1304 insertions(+), 1564 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 498cbf052..dfc4ad359 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,5 @@ # Changelog - This is the change log for _DelphiDabbler CodeSnip_. All notable changes to this project are documented in this file. @@ -11,7 +10,6 @@ This change log begins with the first ever pre-release version of _CodeSnip_. Re From v4.1.0 the version numbering has attempted to adhere to the principles of [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - ## Release v4.18.1 of 29 November 2021 * Improved handling of control and whitespace characters in generated HTML: revised which characters were converted to HTML character attributes / entities. @@ -22,7 +20,6 @@ From v4.1.0 the version numbering has attempted to adhere to the principles of [ * Some refactoring. * Updated license document (`License.html`) following removal of dependency on GIFImage unit. - ## Release v4.18.0 of 13 September 2021 * Added support for test compilation with, and detection of, Delphi 11 Alexandria. @@ -32,14 +29,12 @@ From v4.1.0 the version numbering has attempted to adhere to the principles of [ * Updated help file re changes. * Minor documentation corrections. - ## Release v4.17.2 of 12 September 2020 Hotfix release. * Updated version of jQuery used by program easter egg from v1.8.0 to v1.12.4 to fix a known vulnerability in v1.8.0. Also updated jQuery Cycle Lite that depends on jQuery from v1.6 to 1.7. - ## Release v4.17.1 of 31 July 2020 Hotfix release. @@ -47,463 +42,420 @@ Hotfix release. * Corrected "What's New" dialogue box content that is displayed _only_ when updating from v4.15.1 and earlier. The correction is to ensure the text makes sense when release 4.16.0 has been skipped. This change should have been made in v4.17.0. * Removed a redundant resource. - ## Release v4.17.0 of 31 July 2020 -+ Added support for test compilation with, and detection of, Delphi 10.2 Tokyo, Delphi 10.3 Rio and Delphi 10.4 Sydney compilers. -+ Updated documentation re changes. -+ Updated help file re changes. - +* Added support for test compilation with, and detection of, Delphi 10.2 Tokyo, Delphi 10.3 Rio and Delphi 10.4 Sydney compilers. +* Updated documentation re changes. +* Updated help file re changes. ## Release v4.16.0 of 31 May 2020 This is a significant update. It's purpose is to remove CodeSnip's dependencies on the delphidabbler.com website and associated web services. This was done because of the expected June 2020 closure or reduced functionality of delphidabbler.com. Some affected features were removed and others replaced with alternatives. -+ Removed all dependencies on web services. The following changes were made as a consequence of this: - - Replaced the option to update the main DelphiDabbler Code Snippets database from the web with an option to update it from locally stored data: - - Replaced the _Update from Web_ dialogue box with a new wizard. Menu options on the _Snippets_ menu were renamed accordingly and the old tool bar button was removed. - - Changed the database update code to use data that has been manually downloaded from the `delphidabbler/code-snippets` GitHub project. - - Modified the database reading code to accept both the new Code Snippets database v2 format _and_ the legacy v1 format. - + Replaced the option to import SWAG snippets from an on-line REST service with an option to import snippets from locally stored data. - - Revised the SWAG import wizard re the changes to the import method. - - Modified the SWAG import code to use data that has been manually downloaded from the `delphidabbler/swag` GitHub project. - - The option to register the program was removed. No registration key is now generated or stored. - - Replaced the option to read and display the CodeSnip RSS news feed with one to display the CodeSnip Blog. - - Removed the menu option used to check for program updates. - - Removed the background task that automatically checked for program and database updates. - - Removed the option to submit snippets for addition to the DelphiDabbler Code Snippets database. - - Removed support for a proxy web server - now unnecessary. - - Removed support for the `--test-server` command line option that enabled use of a different server to test web services. - - Updated install program so it no longer displays a page stating that CodeSnip will go automatically go on-line to check for updates. -+ Removed references and links to delphidabbler.com from the program, the installer, the help file and documentation. Some references were deleted while others were replaced with alternatives, including: - - Changed the URL of the FAQs to refer to the `codesnip-faq` GitHub project. - - References to swag.delphidabbler.com were replaced with references to the `delphidabbler/swag` project on GitHub. - - URLs that were redirected via a service on delphidabbler.com were replaced by hard coded URLs. -+ The export file format was changed to exclude personal user information. The original format can still be read but any user information is ignored and discarded. -+ Config file processing changes: - - Removed support for reading or writing data relating to removed features. - - When CodeSnip is first run after updating from an earlier version, any pre-existing config files are purged of any information that is no longer relevant. - - The common config file is no longer used by the portable edition. Any pre-existing file is deleted the first time the portable edition is run. - - The common and per-user config file versions were bumped to 7 and 16 respectively. -+ Welcome page changes: - - Removed the _Update Checks_ and _Donate_ sections and related links. - - Removed links used to check for program and database updates. - - Replaced the link used to display the news feed with one that displays the CodeSnip blog. -+ Added a "What's New" type of dialogue box that can be selectively displayed when a new version of CodeSnip is run for the first time. v4.16.0 _always_ displays the dialogue box when first run. -+ The operating system detection code was updated to correctly detect all Windows and Windows Server releases as of March 2020. -+ Revised the _About_ dialogue box: - - To display version and licensing information extracted from Code Snippets Database v2 meta data. - - To remove credits for 3rd party code that is no longer used. -+ The bug tracker dialogue boxes were updated re the change of issue tracker from SourceForge to GitHub. -+ Removed redundant pages and controls from the _Preferences_ dialogue box. -+ Removed the _Donate_ dialogue box and associated menu options. -+ Revised and re-ordered some menu options. -+ The program no longer generates and saves an application identifier key. -+ Bugs fixed: - - Corrected license information stored in the _Extra_ information section of imported SWAG packages. - - Fixed a text formatting error in the SWAG import wizard ([issue #4](https://github.com/delphidabbler/codesnip/issues/4)). - - Fixed broken help topic links in some dialogue boxes ([issue #3](https://github.com/delphidabbler/codesnip/issues/3)) - - Fixed a bug in the portable edition's startup processing of its config file. - - Fixed the dialogue box displayed when updating from CodeSnip v3 or earlier to display an icon in the Windows task bar. - - Corrected the license details included in comments of generated source code that includes snippets from the main database. - - Corrected typos and errors in the UI. -+ Some source code refactoring and clarifications. -+ Removed redundant library code: - - Encryption library. - - Indy Internet components. -+ Help file overhauled: new topics added, redundant topics removed and many errors corrected. Some restyling. -+ Updated documentation, including: - - Major changes to `./README.md` and `./Docs/ReadMe.txt`. - - Merged all the major version specific changelogs into a single `./CHANGELOG.md` file and deleted the old files. - - File format documentation was overhauled re changes introduced in this release. - - Edited `./Docs/License.html` to remove license information and acknowledgements for 3rd party code that is no longer used. - - Fixed errors in `./Build.html` concerning the source code repository and made some other minor changes. - - Removed the privacy statement document, `./Docs/Privacy.txt` since CodeSnip no longer stores or transmits any personal information. (Also removed privacy help topic and menu item.) - - Removed `./Docs/Design/WebServices.txt` file that described the web services used by CodeSnip. - +* Removed all dependencies on web services. The following changes were made as a consequence of this: + * Replaced the option to update the main DelphiDabbler Code Snippets database from the web with an option to update it from locally stored data: + * Replaced the _Update from Web_ dialogue box with a new wizard. Menu options on the _Snippets_ menu were renamed accordingly and the old tool bar button was removed. + * Changed the database update code to use data that has been manually downloaded from the `delphidabbler/code-snippets` GitHub project. + * Modified the database reading code to accept both the new Code Snippets database v2 format _and_ the legacy v1 format. + * Replaced the option to import SWAG snippets from an on-line REST service with an option to import snippets from locally stored data. + * Revised the SWAG import wizard re the changes to the import method. + * Modified the SWAG import code to use data that has been manually downloaded from the `delphidabbler/swag` GitHub project. + * The option to register the program was removed. No registration key is now generated or stored. + * Replaced the option to read and display the CodeSnip RSS news feed with one to display the CodeSnip Blog. + * Removed the menu option used to check for program updates. + * Removed the background task that automatically checked for program and database updates. + * Removed the option to submit snippets for addition to the DelphiDabbler Code Snippets database. + * Removed support for a proxy web server - now unnecessary. + * Removed support for the `--test-server` command line option that enabled use of a different server to test web services. + * Updated install program so it no longer displays a page stating that CodeSnip will go automatically go on-line to check for updates. +* Removed references and links to delphidabbler.com from the program, the installer, the help file and documentation. Some references were deleted while others were replaced with alternatives, including: + * Changed the URL of the FAQs to refer to the `codesnip-faq` GitHub project. + * References to swag.delphidabbler.com were replaced with references to the `delphidabbler/swag` project on GitHub. + * URLs that were redirected via a service on delphidabbler.com were replaced by hard coded URLs. +* The export file format was changed to exclude personal user information. The original format can still be read but any user information is ignored and discarded. +* Config file processing changes: + * Removed support for reading or writing data relating to removed features. + * When CodeSnip is first run after updating from an earlier version, any pre-existing config files are purged of any information that is no longer relevant. + * The common config file is no longer used by the portable edition. Any pre-existing file is deleted the first time the portable edition is run. + * The common and per-user config file versions were bumped to 7 and 16 respectively. +* Welcome page changes: + * Removed the _Update Checks_ and _Donate_ sections and related links. + * Removed links used to check for program and database updates. + * Replaced the link used to display the news feed with one that displays the CodeSnip blog. +* Added a "What's New" type of dialogue box that can be selectively displayed when a new version of CodeSnip is run for the first time. v4.16.0 _always_ displays the dialogue box when first run. +* The operating system detection code was updated to correctly detect all Windows and Windows Server releases as of March 2020. +* Revised the _About_ dialogue box: + * To display version and licensing information extracted from Code Snippets Database v2 meta data. + * To remove credits for 3rd party code that is no longer used. +* The bug tracker dialogue boxes were updated re the change of issue tracker from SourceForge to GitHub. +* Removed redundant pages and controls from the _Preferences_ dialogue box. +* Removed the _Donate_ dialogue box and associated menu options. +* Revised and re-ordered some menu options. +* The program no longer generates and saves an application identifier key. +* Bugs fixed: + * Corrected license information stored in the _Extra_ information section of imported SWAG packages. + * Fixed a text formatting error in the SWAG import wizard ([issue #4](https://github.com/delphidabbler/codesnip/issues/4)). + * Fixed broken help topic links in some dialogue boxes ([issue #3](https://github.com/delphidabbler/codesnip/issues/3)) + * Fixed a bug in the portable edition's startup processing of its config file. + * Fixed the dialogue box displayed when updating from CodeSnip v3 or earlier to display an icon in the Windows task bar. + * Corrected the license details included in comments of generated source code that includes snippets from the main database. + * Corrected typos and errors in the UI. +* Some source code refactoring and clarifications. +* Removed redundant library code: + * Encryption library. + * Indy Internet components. +* Help file overhauled: new topics added, redundant topics removed and many errors corrected. Some restyling. +* Updated documentation, including: + * Major changes to `./README.md` and `./Docs/ReadMe.txt`. + * Merged all the major version specific changelogs into a single `./CHANGELOG.md` file and deleted the old files. + * File format documentation was overhauled re changes introduced in this release. + * Edited `./Docs/License.html` to remove license information and acknowledgements for 3rd party code that is no longer used. + * Fixed errors in `./Build.html` concerning the source code repository and made some other minor changes. + * Removed the privacy statement document, `./Docs/Privacy.txt` since CodeSnip no longer stores or transmits any personal information. (Also removed privacy help topic and menu item.) + * Removed `./Docs/Design/WebServices.txt` file that described the web services used by CodeSnip. ## Release v4.15.1 of 22 September 2016 -+ Updated OS detection code to detect Windows 10 Version 1607 (Anniversary update) and all technical previews of Windows 2016 Server to date. - +* Updated OS detection code to detect Windows 10 Version 1607 (Anniversary update) and all technical previews of Windows 2016 Server to date. ## Release v4.15.0 of 13 July 2016 -+ Added support for test compilation with, and detection of, Delphi 10.1 Berlin compiler. -+ Tweaked size of compiler list in Configure Compilers dialogue box to accommodate length of Delphi 10.1 Berline compiler name! -+ Updated documentation re changes. -+ Updated help file re changes. - +* Added support for test compilation with, and detection of, Delphi 10.1 Berlin compiler. +* Tweaked size of compiler list in Configure Compilers dialogue box to accommodate length of Delphi 10.1 Berline compiler name! +* Updated documentation re changes. +* Updated help file re changes. ## Release v4.14.0 of 19 March 2016 -+ Changes to About Box's "Paths" tab: - - Added new buttons to display the contents of the system and per-user config files. - - Renamed tab as "Paths & Files". -+ Implemented [SourceForge] feature request #83 to enable the name and port of any web service test server to be passed on command line by using the new "--test-server" command line option. This replaces hard-wired test server name & port that was activated using the now removed "-localhost" command line switch. -+ Fixed [SourceForge] bug #96: "Some open / save dialogues too small". The height of customised dialogue boxes was increased. -+ Updated operating system detection code to detect Windows 10 TH2. -+ New photo of Sophie the dog added to the Easter egg slide show! -+ Updated help file re About Box changes. - +* Changes to About Box's "Paths" tab: + * Added new buttons to display the contents of the system and per-user config files. + * Renamed tab as "Paths & Files". +* Implemented [SourceForge] feature request #83 to enable the name and port of any web service test server to be passed on command line by using the new "--test-server" command line option. This replaces hard-wired test server name & port that was activated using the now removed "-localhost" command line switch. +* Fixed [SourceForge] bug #96: "Some open / save dialogues too small". The height of customised dialogue boxes was increased. +* Updated operating system detection code to detect Windows 10 TH2. +* New photo of Sophie the dog added to the Easter egg slide show! +* Updated help file re About Box changes. ## Release v4.13.2 of 20 February 2016 -+ Updated and corrected hints displayed in main window. -+ Changed some menu options and associated dialogue box captions. -+ Tweaked some button captions in Select Snippets dialogue box. -+ Updated help file re the menu and caption changes. -+ Updated copyright date in program license as displayed in help, about box, installer and documentation. - +* Updated and corrected hints displayed in main window. +* Changed some menu options and associated dialogue box captions. +* Tweaked some button captions in Select Snippets dialogue box. +* Updated help file re the menu and caption changes. +* Updated copyright date in program license as displayed in help, about box, installer and documentation. ## Release v4.13.1 of 29 September 2015 -+ Improved operating system detection to detect Windows 10. -+ Modified program's manifest to declare it compatible with Windows 8 to 10. -+ Code that determines which system font to use no longer depends on OS version but simply on font availability. -+ Updated copyright date in program license as displayed in help, about box, installer and documentation. - +* Improved operating system detection to detect Windows 10. +* Modified program's manifest to declare it compatible with Windows 8 to 10. +* Code that determines which system font to use no longer depends on OS version but simply on font availability. +* Updated copyright date in program license as displayed in help, about box, installer and documentation. ## Release v4.13.0 of 5 September 2015 -+ Added support for test compilation with, and detection of, Delphi 10 Seattle compiler. -+ Made some minor changes to method used to build required type library to remove dependency on the MS MIDL compiler, greatly simplifying build process. -+ Updated documentation re changes. -+ Updated help file re changes. - +* Added support for test compilation with, and detection of, Delphi 10 Seattle compiler. +* Made some minor changes to method used to build required type library to remove dependency on the MS MIDL compiler, greatly simplifying build process. +* Updated documentation re changes. +* Updated help file re changes. ## Release v4.12.0 of 6 May 2015 -+ Added support for test compilation with, and detection of, Delphi XE8 compiler. -+ Updated documentation re changes. -+ Updated help file re changes and fixed some spelling mistakes. - +* Added support for test compilation with, and detection of, Delphi XE8 compiler. +* Updated documentation re changes. +* Updated help file re changes and fixed some spelling mistakes. ## Release v4.11.1 of 26 October 2014 -+ Corrected an erroneous error message that is displayed when circular snippet references are detected in the snippets editor (no bug report filed). -+ Corrected some spelling errors in UI. -+ Some documentation corrections. - +* Corrected an erroneous error message that is displayed when circular snippet references are detected in the snippets editor (no bug report filed). +* Corrected some spelling errors in UI. +* Some documentation corrections. ## Release v4.11.0 of 25 September 2014 -+ Changes re licensing of snippets from online Code Snippets Database under MIT license: - - Generated code now carries a reference to the MIT license where relevant. - - Submit Snippets wizard has a new page where user must confirm that any submitted snippets may be MIT licensed. - - "About the Database" of the About Box now refers to the license. - - Documentation and help file updated accordingly. -+ Now uses version 6 of the Code Snippets Database update web service which supports the downloading of category and source code files larger than 32Kb. -+ The undocumented -localhost command line option now causes CodeSnip to expect web services to be on localhost port 8080 instead of port 80. This change has no effect on normal operation and is used only in testing. -+ Minor layout tweaks in in Display tab of Preferences dialogue box. -+ Minor changes to help file and documentation. - +* Changes re licensing of snippets from online Code Snippets Database under MIT license: + * Generated code now carries a reference to the MIT license where relevant. + * Submit Snippets wizard has a new page where user must confirm that any submitted snippets may be MIT licensed. + * "About the Database" of the About Box now refers to the license. + * Documentation and help file updated accordingly. +* Now uses version 6 of the Code Snippets Database update web service which supports the downloading of category and source code files larger than 32Kb. +* The undocumented -localhost command line option now causes CodeSnip to expect web services to be on localhost port 8080 instead of port 80. This change has no effect on normal operation and is used only in testing. +* Minor layout tweaks in in Display tab of Preferences dialogue box. +* Minor changes to help file and documentation. ## Release v4.10.0 of 12 September 2014 -+ Added support for test compilation with, and detection of, Delphi XE7 compiler. -+ Updated documentation re changes. -+ Updated help file re changes. - +* Added support for test compilation with, and detection of, Delphi XE7 compiler. +* Updated documentation re changes. +* Updated help file re changes. ## Release v4.9.0 of 30 April 2014 -+ Added support for test compilation with, and detection of, Delphi XE6 compiler. -+ Updated documentation re changes. -+ Updated help file re changes. - +* Added support for test compilation with, and detection of, Delphi XE6 compiler. +* Updated documentation re changes. +* Updated help file re changes. ## Release v4.8.7 of 06 March 2014 -+ Fixed automatic update checker so that it correctly records last update date. Fixes [SourceForge] bug #93. -+ Updated to use v2 of the DelphiDabbler CodeSnip update web service when checking for availability of program updates. -+ Minor corrections to help file. - +* Fixed automatic update checker so that it correctly records last update date. Fixes [SourceForge] bug #93. +* Updated to use v2 of the DelphiDabbler CodeSnip update web service when checking for availability of program updates. +* Minor corrections to help file. ## Release v4.8.6 of 28 February 2014 -+ Improved operating system detection to handle Windows 8.1. -+ Added compatibility section to application manifest that declares the program has been tested with Windows Vista and Windows 7. - +* Improved operating system detection to handle Windows 8.1. +* Added compatibility section to application manifest that declares the program has been tested with Windows Vista and Windows 7. ## Release v4.8.5 of 13 January 2014 -+ Fixed [SourceForge] bug #91: "Generated units won't compile on Delphi XE5". Compiler directives that are used to change compiler warnings now includes a conditionally compiled $LEGACYIFEND ON directive. -+ Fixed potential bug when checking for the existence of files. It had been possible that a "sym-link" to a file could give misleading results. -+ Updated program copyright date in license file, about box, help file and installer. - +* Fixed [SourceForge] bug #91: "Generated units won't compile on Delphi XE5". Compiler directives that are used to change compiler warnings now includes a conditionally compiled $LEGACYIFEND ON directive. +* Fixed potential bug when checking for the existence of files. It had been possible that a "sym-link" to a file could give misleading results. +* Updated program copyright date in license file, about box, help file and installer. ## Release v4.8.4 of 28 November 2013 -+ Improved user interface of SWAG Import Wizard. -+ Renamed "Save Snippet" and "Copy Snippet" menu options to "Save Annotated Source" and "Copy Annotated Source". This fixes [SourceForge] bug #90: " Wrong caption on menu option for copying category to clipboard". -+ Revised and corrected numerous main menu and pop-up menu hints. -+ Updated help file re changes to menu options and SWAG Import Wizards. - +* Improved user interface of SWAG Import Wizard. +* Renamed "Save Snippet" and "Copy Snippet" menu options to "Save Annotated Source" and "Copy Annotated Source". This fixes [SourceForge] bug #90: " Wrong caption on menu option for copying category to clipboard". +* Revised and corrected numerous main menu and pop-up menu hints. +* Updated help file re changes to menu options and SWAG Import Wizards. ## Release v4.8.3 of 06 November 2013 -+ Fixed registry access code so that the 64 bit view of the registry is used when CodeSnip runs on a Windows 64 bit operating system. -+ Changed to avoid use of a deprecated API call when using the Windows Browse for Folder dialogue box. -+ Updated documentation. - +* Fixed registry access code so that the 64 bit view of the registry is used when CodeSnip runs on a Windows 64 bit operating system. +* Changed to avoid use of a deprecated API call when using the Windows Browse for Folder dialogue box. +* Updated documentation. ## Release v4.8.2 of 30 October 2013 -+ Modified Syntax Highlighter tab of Preferences dialogue box so that "vertical" fonts (whose names begin with "@") no longer appear in list of available fonts. -+ Fixed potential bug in operating system detection code that may fail on Windows 2000. - +* Modified Syntax Highlighter tab of Preferences dialogue box so that "vertical" fonts (whose names begin with "@") no longer appear in list of available fonts. +* Fixed potential bug in operating system detection code that may fail on Windows 2000. ## Release v4.8.1 of 18 September 2013 -+ Removed "File | Page Setup" menu option because some settings made there were being ignored when a file was printed. This is a fix for [SourceForge] bug #89: "Setup selections not being remembered". -+ Updated help file re changes. - +* Removed "File | Page Setup" menu option because some settings made there were being ignored when a file was printed. This is a fix for [SourceForge] bug #89: "Setup selections not being remembered". +* Updated help file re changes. ## Release v4.8.0 of 12 September 2013 -+ Added support for Delphi XE5 compiler. -+ Fixed bug in code that reads or imports user database files written using CodeSnip 3 where Delphi XE4 compile results would be lost. -+ Updated documentation re changes. -+ Updated help file re changes. - +* Added support for Delphi XE5 compiler. +* Fixed bug in code that reads or imports user database files written using CodeSnip 3 where Delphi XE4 compile results would be lost. +* Updated documentation re changes. +* Updated help file re changes. ## Release v4.7.2 of 27 August 2013 -+ Fixed [SourceForge] bug #88: "SWAG Import Wizard display bug" where duplicate snippets could be displayed on the "Ready to import" page in certain circumstances. - +* Fixed [SourceForge] bug #88: "SWAG Import Wizard display bug" where duplicate snippets could be displayed on the "Ready to import" page in certain circumstances. ## Release v4.7.1 of 18 August 2013 -+ Fixed bug where right clicking a tab in the detail pane sometimes caused the contents of the pane to be temporarily blanked out while the context menu was displayed. - - This fix also, as a side effect, fixed [SourceForge] bug #87: "Tab headings and contents don't match after a tab is closed." - +* Fixed bug where right clicking a tab in the detail pane sometimes caused the contents of the pane to be temporarily blanked out while the context menu was displayed. +* The above fix also, as a side effect, fixed [SourceForge] bug #87: "Tab headings and contents don't match after a tab is closed." ## Release v4.7.0 of 31 July 2013 -+ Implemented [SourceForge] feature request #71: "Support importing of one or more snippets from the SWAG database": - - Uses DelphiDabbler SWAG web service to get SWAG data. - - New wizard to permit user to select required SWAG snippets. This is accessible from the new "Snippets | Import Snippets From SWAG" menu option. - - Snippets are imported into a new "SWAG Imports" category. -+ Implemented [SourceForge] feature request #80: "Enable detail pane tabs to be re-ordered". -+ In detail pane source code and compiler table now display horizontal scroll bars if they do not fit within the width of the pane. This implements [SourceForge] feature requests #60 and #61. -+ Minor changes to dialogue box that appears during long operations. -+ Fixed [SourceForge] bug #86: "Snippets are sorted by snippet name in snippet table listings in detail pane". -+ Fixed a few code errors that could have surfaced as bugs. -+ Modified how HTML based detail pane display is generated and displayed. -+ Some refactoring. -+ Updated some 3rd party code to latest available versions. -+ Updated help file re changes. -+ Updated privacy statement. - +* Implemented [SourceForge] feature request #71: "Support importing of one or more snippets from the SWAG database": + * Uses DelphiDabbler SWAG web service to get SWAG data. + * New wizard to permit user to select required SWAG snippets. This is accessible from the new "Snippets | Import Snippets From SWAG" menu option. + * Snippets are imported into a new "SWAG Imports" category. +* Implemented [SourceForge] feature request #80: "Enable detail pane tabs to be re-ordered". +* In detail pane source code and compiler table now display horizontal scroll bars if they do not fit within the width of the pane. This implements [SourceForge] feature requests #60 and #61. +* Minor changes to dialogue box that appears during long operations. +* Fixed [SourceForge] bug #86: "Snippets are sorted by snippet name in snippet table listings in detail pane". +* Fixed a few code errors that could have surfaced as bugs. +* Modified how HTML based detail pane display is generated and displayed. +* Some refactoring. +* Updated some 3rd party code to latest available versions. +* Updated help file re changes. +* Updated privacy statement. ## Release v4.6.4 of 24 July 2013 -+ Fix for IE 9 related browser control script bugs introduced in v4.6.3 when IE 10 bugs were fixed: - - [SourceForge] Bug #84: "Script errors on startup" - - [SourceForge] Bug #85: "Check For Updates link" - +* Fix for IE 9 related browser control script bugs introduced in v4.6.3 when IE 10 bugs were fixed: + * [SourceForge] Bug #84: "Script errors on startup" + * [SourceForge] Bug #85: "Check For Updates link" ## Release v4.6.3 of 14 July 2013 -+ Further fix for IE 10 related [SourceForge] bug #75 "Floating point error in 4.4.1". Re-implemented method used to display content in main window's detail pane using the IE web browser control. - +* Further fix for IE 10 related [SourceForge] bug #75 "Floating point error in 4.4.1". Re-implemented method used to display content in main window's detail pane using the IE web browser control. ## Release v4.6.2 of 09 July 2013 -+ Tentative fix for [SourceForge] bug #83: "Error when the main form is shown" that has been reported on Windows 8. The fix is tentative because the bug hasn't been reproduced. - +* Tentative fix for [SourceForge] bug #83: "Error when the main form is shown" that has been reported on Windows 8. The fix is tentative because the bug hasn't been reproduced. ## Release v4.6.1 of 01 July 2013 -+ Provided fix for reported [SourceForge] bug #75: "Floating point error in 4.4.1" that apparently affects Windows 8, probably with IE 10 installed. -+ Fixed unreported bug where IE 10 browser was being reported as IE 9. -+ Fixed potential bug in code that processes class / advanced record snippet types ready for test compilation and inclusion in generated units. - +* Provided fix for reported [SourceForge] bug #75: "Floating point error in 4.4.1" that apparently affects Windows 8, probably with IE 10 installed. +* Fixed unreported bug where IE 10 browser was being reported as IE 9. +* Fixed potential bug in code that processes class / advanced record snippet types ready for test compilation and inclusion in generated units. ## Release v4.6.0 of 02 June 2013 -+ Added new options to "Find Cross References" dialogue box to allow snippets that either cross reference or depend upon the selected snippet to be included in the search. This implements [SourceForge] feature request #30. -+ Added a new "Select and Close" button to the Dependencies dialogue box that causes the snippets displayed on the current tab to be selected in the main display. This implements [SourceForge] feature request #77. -+ The background colour of source code displayed in the main display can now be customised via a new option on the Display tab of the Preferences dialogue box. This implements [SourceForge] feature request #36. -+ CodeSnip now compiled with Delphi XE. -+ Per-user configuration file format changed to v15 which is not entirely compatible with previous versions of CodeSnip. -+ Updated help file re changes. - +* Added new options to "Find Cross References" dialogue box to allow snippets that either cross reference or depend upon the selected snippet to be included in the search. This implements [SourceForge] feature request #30. +* Added a new "Select and Close" button to the Dependencies dialogue box that causes the snippets displayed on the current tab to be selected in the main display. This implements [SourceForge] feature request #77. +* The background colour of source code displayed in the main display can now be customised via a new option on the Display tab of the Preferences dialogue box. This implements [SourceForge] feature request #36. +* CodeSnip now compiled with Delphi XE. +* Per-user configuration file format changed to v15 which is not entirely compatible with previous versions of CodeSnip. +* Updated help file re changes. ## Release v4.5.1 of 15 May 2013 -+ Added progress bars or marquees to several database operations that can take a long time on slower storage devices, i.e.: - - When local files are being updated after downloading an updated database in the Update From Web dialogue box. This fixes [SourceForge] bug #79: - - When the local database is being saved. - - When the local database is being backed up or restored. - - When the local database is being moved to a new location. -+ The user database can now be relocated to a network drive. This fixes [SourceForge] issue #81 "Move database to a network drive". -+ Fixed [SourceForge] issue #80 "HTML output bug". -+ Fixed minor alignment bug that occurred when displaying a wait dialogue box over the main window. -+ Some refactoring. -+ Updated help file re changes. - +* Added progress bars or marquees to several database operations that can take a long time on slower storage devices, i.e.: + * When local files are being updated after downloading an updated database in the Update From Web dialogue box. This fixes [SourceForge] bug #79: + * When the local database is being saved. + * When the local database is being backed up or restored. + * When the local database is being moved to a new location. +* The user database can now be relocated to a network drive. This fixes [SourceForge] issue #81 "Move database to a network drive". +* Fixed [SourceForge] issue #80 "HTML output bug". +* Fixed minor alignment bug that occurred when displaying a wait dialogue box over the main window. +* Some refactoring. +* Updated help file re changes. ## Release v4.5.0 of 02 May 2013 -+ Added support for Delphi XE4 compiler. Implements [SourceForge] feature request #78. -+ Updated documentation re changes. -+ Updated help file re changes. - +* Added support for Delphi XE4 compiler. Implements [SourceForge] feature request #78. +* Updated documentation re changes. +* Updated help file re changes. ## Release v4.4.2 of 26 April 2013 -+ Fixed [SourceForge] bugs: - - #76: An advanced record snippet with a method name that clashes with a directive is not test compiling correctly. - - #77: Syntax highlighter highlights "contains", "requires" and "package" directives when used in method names. - - #78: CodeSnip doesn't restore window in correct position when task bar on left or top of screen. - +* Fixed [SourceForge] bugs: + * #76: An advanced record snippet with a method name that clashes with a directive is not test compiling correctly. + * #77: Syntax highlighter highlights "contains", "requires" and "package" directives when used in method names. + * #78: CodeSnip doesn't restore window in correct position when task bar on left or top of screen. ## Release v4.4.1 of 09 April 2013 -+ Fixed [SourceForge] bug #73: "Attempting to check for program updates returns a 404 'Not found' error" - this error happened only when using remote server, not localhost test server. - +* Fixed [SourceForge] bug #73: "Attempting to check for program updates returns a 404 'Not found' error" - this error happened only when using remote server, not localhost test server. ## Release v4.4.0 of 08 April 2013 -+ Implemented [SourceForge] feature request #75 "Check for updates on start-up": - - CodeSnip checks for both program and Code Snippets Database updates in low priority background threads that run when the program is first started. - - Update checking takes place at intervals between once per day and once per month. - - A new "Updates" tab was added to the "Preferences" dialogue box where update frequencies can be chosen, or the auto-update feature switched off. Program and database update checking can be configured individually. - - Updates are notified via a new slide-in, slide-out notification window that is displayed for a fixed amount of time or until closed by the user. The notification window contains a button that can be used to initiate the appropriate update. For database updates the "Update From Web" dialogue box is opened while for program updates a suitable download web page is displayed in the default browser. - - Program checking is edition specific, i.e. the standard edition checks for standard edition updates and the portable edition behaves similarly. -+ A new "Update Checks" section was added the welcome screen that gives information about the current auto-update settings and provides a link to change them. Some other text on the screen was tweaked. -+ The "Check For Program Updates" dialogue box now opens the correct version and edition specific web page to download the latest version of CodeSnip instead of simply opening a general download page. -+ A new CodeSnip specific program update web service on the codesnip.delphidabbler.com sub-domain is now used to get information about CodeSnip updates instead of the generic update service on delphidabbler.com. -+ Additional usage information is now sent to the DelphiDabbler Code Snippets database update web service. -+ Some refactoring and code clean-up. -+ The installer may now display an information page that describes the new automatic update checking feature. This page is displayed only when updating from v4.3.0 or earlier to v4.4.0 (or later). -+ Updated help file: - - Updated and added help topics for all new features of the release. - - Updated "What's New" topic re new features. -+ Updated documentation, including privacy statement, with information about automatic update checking. -+ Per-user configuration file format changed to v13 which is incompatible with previous versions of CodeSnip. - +* Implemented [SourceForge] feature request #75 "Check for updates on start-up": + * CodeSnip checks for both program and Code Snippets Database updates in low priority background threads that run when the program is first started. + * Update checking takes place at intervals between once per day and once per month. + * A new "Updates" tab was added to the "Preferences" dialogue box where update frequencies can be chosen, or the auto-update feature switched off. Program and database update checking can be configured individually. + * Updates are notified via a new slide-in, slide-out notification window that is displayed for a fixed amount of time or until closed by the user. The notification window contains a button that can be used to initiate the appropriate update. For database updates the "Update From Web" dialogue box is opened while for program updates a suitable download web page is displayed in the default browser. + * Program checking is edition specific, i.e. the standard edition checks for standard edition updates and the portable edition behaves similarly. +* A new "Update Checks" section was added the welcome screen that gives information about the current auto-update settings and provides a link to change them. Some other text on the screen was tweaked. +* The "Check For Program Updates" dialogue box now opens the correct version and edition specific web page to download the latest version of CodeSnip instead of simply opening a general download page. +* A new CodeSnip specific program update web service on the codesnip.delphidabbler.com sub-domain is now used to get information about CodeSnip updates instead of the generic update service on delphidabbler.com. +* Additional usage information is now sent to the DelphiDabbler Code Snippets database update web service. +* Some refactoring and code clean-up. +* The installer may now display an information page that describes the new automatic update checking feature. This page is displayed only when updating from v4.3.0 or earlier to v4.4.0 (or later). +* Updated help file: + * Updated and added help topics for all new features of the release. + * Updated "What's New" topic re new features. +* Updated documentation, including privacy statement, with information about automatic update checking. +* Per-user configuration file format changed to v13 which is incompatible with previous versions of CodeSnip. ## Release v4.3.0 of 27 February 2013 -+ Implemented [SourceForge] feature request #40: "Add 'Namespaces' tab to Configure Compilers dialogue box". The new tab appears only for Delphi XE2 and later and obviates the need to manually create -NS commands for passing to the compilers. Suitable default namespaces are provided if none have been configured. -+ Implemented [SourceForge] feature request #70: "Let user specify location of user database". This feature is accessed from the new "Move Use Database" option on the Database menu. NOTE: The feature is not available in the portable edition which is designed to keep the user database together with the program. -+ Implemented [SourceForge] feature request #69: "Enable custom syntax highlighter styles to be saved". The Syntax Highlighter tab of the Preferences dialogue box has been modified to enable custom syntax highlighter attributes to be saved under a given name and existing named styles to be used or deleted. -+ Changed name of "Delphi 2006" predefined syntax highlighter to "RAD Studio". This remains the default highlighter. -+ A little refactoring. -+ Enlarged Configure Compilers dialogue box. -+ Updated help file: - - Updated and added help topics re all new features in the release. - - Updated "What's New" topic re new features. - - Removed help topic for "Browse For Folders" dialogue box accessed from Search Paths tab of Configure Compilers dialogue. -+ Per-user configuration file format changed to v12 which is not fully compatible with previous versions of CodeSnip. -+ Updated documentation. - +* Implemented [SourceForge] feature request #40: "Add 'Namespaces' tab to Configure Compilers dialogue box". The new tab appears only for Delphi XE2 and later and obviates the need to manually create -NS commands for passing to the compilers. Suitable default namespaces are provided if none have been configured. +* Implemented [SourceForge] feature request #70: "Let user specify location of user database". This feature is accessed from the new "Move Use Database" option on the Database menu. NOTE: The feature is not available in the portable edition which is designed to keep the user database together with the program. +* Implemented [SourceForge] feature request #69: "Enable custom syntax highlighter styles to be saved". The Syntax Highlighter tab of the Preferences dialogue box has been modified to enable custom syntax highlighter attributes to be saved under a given name and existing named styles to be used or deleted. +* Changed name of "Delphi 2006" predefined syntax highlighter to "RAD Studio". This remains the default highlighter. +* A little refactoring. +* Enlarged Configure Compilers dialogue box. +* Updated help file: + * Updated and added help topics re all new features in the release. + * Updated "What's New" topic re new features. + * Removed help topic for "Browse For Folders" dialogue box accessed from Search Paths tab of Configure Compilers dialogue. +* Per-user configuration file format changed to v12 which is not fully compatible with previous versions of CodeSnip. +* Updated documentation. ## Release v4.2.1 of 14 February 2013 -+ Bug fix: changed Favourites dialogue to display snippet display names instead of unique names. Fixes [SourceForge] bug #72. -+ Updated program copyright date in About box. - +* Bug fix: changed Favourites dialogue to display snippet display names instead of unique names. Fixes [SourceForge] bug #72. +* Updated program copyright date in About box. ## Release v4.2.0 of 07 February 2013 -+ Added support for "favourite" snippets. Implements [SourceForge] feature request #37: - - Any displayed snippet can be flagged as a favourite via a menu option or toolbar button. - - A new non-modal dialogue box can now be displayed alongside the CodeSnip window for easy selection and management of favourite snippets. -+ Changes to Duplicate Snippets dialogue box: - - Display name of duplicate snippet can be edited. Implements [SourceForge] feature request #64. - - Snippets Editor can be opened immediately the Duplicate Snippet dialogue box closes to edit the duplicated snippet. Implements [SourceForge] feature request #65. -+ Status bar changed: first panel now displays no category information, but displays both total number of snippets and number of snippets in each database. -+ Fixed unreported bug in save dialogue boxes where overwrite permission requests could be displayed erroneously. -+ Program closes gracefully if run on unsupported versions. -+ Updated some 3rd party code to latest available versions. -+ Some refactoring. -+ Per-user configuration file format changed to v11 which is not fully compatible with older versions of CodeSnip. -+ Updated help file re changes in Duplicate Snippets dialogue box and addition of support for Favourites. -+ Updated documentation. - +* Added support for "favourite" snippets. Implements [SourceForge] feature request #37: + * Any displayed snippet can be flagged as a favourite via a menu option or toolbar button. + * A new non-modal dialogue box can now be displayed alongside the CodeSnip window for easy selection and management of favourite snippets. +* Changes to Duplicate Snippets dialogue box: + * Display name of duplicate snippet can be edited. Implements [SourceForge] feature request #64. + * Snippets Editor can be opened immediately the Duplicate Snippet dialogue box closes to edit the duplicated snippet. Implements [SourceForge] feature request #65. +* Status bar changed: first panel now displays no category information, but displays both total number of snippets and number of snippets in each database. +* Fixed unreported bug in save dialogue boxes where overwrite permission requests could be displayed erroneously. +* Program closes gracefully if run on unsupported versions. +* Updated some 3rd party code to latest available versions. +* Some refactoring. +* Per-user configuration file format changed to v11 which is not fully compatible with older versions of CodeSnip. +* Updated help file re changes in Duplicate Snippets dialogue box and addition of support for Favourites. +* Updated documentation. ## Release v4.1.1 of 30 January 2013 -+ Fixed [SourceForge] bugs: - - #68: Comments missing in unit / code generation for some types. - - #70: Changing syntax highlighter font has no effect in main display. - - #71: Option to select monochrome printing not working properly. - - Unreported: Changing syntax highlighter font has no effect when printing or when copying text to clipboard as RTF (related to bug #70). -+ Updated help file re syntax highlighter changes. - +* Fixed [SourceForge] bugs: + * #68: Comments missing in unit / code generation for some types. + * #70: Changing syntax highlighter font has no effect in main display. + * #71: Option to select monochrome printing not working properly. + * Unreported: Changing syntax highlighter font has no effect when printing or when copying text to clipboard as RTF (related to bug #70). +* Updated help file re syntax highlighter changes. ## Release v4.1.0 of 06 January 2013 -+ This is the first non-beta release to made available in both standard and portable editions, compiled from a common code base. The portable edition differs from the standard as follows (as per release v4.0.1 portable beta 1): - - Executable file name is CodeSnip-p.exe. - - Program caption identifies program as portable version. - - Data directories are sub-directories of executable program directory. - - Common file dialogue boxes default to program's working directory. - - First run processing does not give the option to import old settings or existing user databases. - - Different version information and program identifier. - - There is no set up program. -+ Changes to snippets editor: - - Added context menus to cross-references and dependencies check box lists. Both have menu item to clear list. Dependencies list has item to view dependencies. Implements [SourceForge] feature request #3560960. - - Deleted "View Dependencies" button now that its functionality is now on context menu. - - Enlarged various controls on all except "Code" tab. - - Units listed on "References" tab is now persistent. Units can be removed, defaults restored and selection cleared via a new context menu. Implements [SourceForge] feature request #3560962. -+ New source code formatting option added to only use first paragraph of a snippet description as snippet comment in generated code. This is configured via "Code Formatting" tab of "Preferences" dialogue box and / or from relevant "Save" dialogue boxes. Implements [SourceForge] feature request #3560647. -+ Changed mini-toolbar in overview pane to expand / collapse all overview tree view instead of just selected node. Implements [SourceForge] feature request #3560646. -+ Reimplemented database search engine. -+ Some external links modified that will seamlessly accommodate future changes in destination URLs. -+ Changed some glyphs used in menus. -+ Per-user configuration file format changed: bumped file version to 10. -+ Some refactoring. -+ Help file updated re changes & added privacy statement to TOC. -+ Updated documentation: - - Re changes to source code repository and bug / feature request trackers. - - Re portable version. - +* This is the first non-beta release to made available in both standard and portable editions, compiled from a common code base. The portable edition differs from the standard as follows (as per release v4.0.1 portable beta 1): + * Executable file name is CodeSnip-p.exe. + * Program caption identifies program as portable version. + * Data directories are sub-directories of executable program directory. + * Common file dialogue boxes default to program's working directory. + * First run processing does not give the option to import old settings or existing user databases. + * Different version information and program identifier. + * There is no set up program. +* Changes to snippets editor: + * Added context menus to cross-references and dependencies check box lists. Both have menu item to clear list. Dependencies list has item to view dependencies. Implements [SourceForge] feature request #3560960. + * Deleted "View Dependencies" button now that its functionality is now on context menu. + * Enlarged various controls on all except "Code" tab. + * Units listed on "References" tab is now persistent. Units can be removed, defaults restored and selection cleared via a new context menu. Implements [SourceForge] feature request #3560962. +* New source code formatting option added to only use first paragraph of a snippet description as snippet comment in generated code. This is configured via "Code Formatting" tab of "Preferences" dialogue box and / or from relevant "Save" dialogue boxes. Implements [SourceForge] feature request #3560647. +* Changed mini-toolbar in overview pane to expand / collapse all overview tree view instead of just selected node. Implements [SourceForge] feature request #3560646. +* Reimplemented database search engine. +* Some external links modified that will seamlessly accommodate future changes in destination URLs. +* Changed some glyphs used in menus. +* Per-user configuration file format changed: bumped file version to 10. +* Some refactoring. +* Help file updated re changes & added privacy statement to TOC. +* Updated documentation: + * Re changes to source code repository and bug / feature request trackers. + * Re portable version. ## Release v4.0.2 of 17 December 2012 -+ Improvements to keyboard handling: - - Fixed some keyboard focus bugs. - - Fixed broken, missing and duplicate Alt-key short-cuts in several dialogue boxes. - - Fixed broken keyboard access to list view in Code Generation tab of Preferences dialogue box. -+ Corrected an incorrect font in Compilers dialogue box. -+ Added title to "View Link" dialogue box displayed from mark-up editor. -+ Corrected error in "Submit Code to the Database" task help topic. - +* Improvements to keyboard handling: + * Fixed some keyboard focus bugs. + * Fixed broken, missing and duplicate Alt-key short-cuts in several dialogue boxes. + * Fixed broken keyboard access to list view in Code Generation tab of Preferences dialogue box. +* Corrected an incorrect font in Compilers dialogue box. +* Added title to "View Link" dialogue box displayed from mark-up editor. +* Corrected error in "Submit Code to the Database" task help topic. ## Release v4.0.1 portable edition, beta 1 of 12 December 2012 -_Internal CodeSnip version 4.0.1.213_ - -+ Modified version of Release v4.0.1 that can run from a writeable removable medium without writing files or registry on host computer. This implements [SourceForge] feature request #3577431. -+ Changes that apply only to portable version: - - Changed executable file name to CodeSnip-p.exe. - - Program caption changed to identify as portable version. - - Data directories changed to be sub-directories of executable program directory. - - Changed common file dialogue boxes to working directory by default. - - First run processing no longer gives option to import old settings or existing user databases. - - Different version information and program identifier format. - - No set up program. -+ Code base modified to conditionally compile either portable or standard edition. -+ Updated documentation, including privacy statement. +Internal CodeSnip version 4.0.1.213 +* Modified version of Release v4.0.1 that can run from a writeable removable medium without writing files or registry on host computer. This implements [SourceForge] feature request #3577431. +* Changes that apply only to portable version: + * Changed executable file name to CodeSnip-p.exe. + * Program caption changed to identify as portable version. + * Data directories changed to be sub-directories of executable program directory. + * Changed common file dialogue boxes to working directory by default. + * First run processing no longer gives option to import old settings or existing user databases. + * Different version information and program identifier format. + * No set up program. +* Code base modified to conditionally compile either portable or standard edition. +* Updated documentation, including privacy statement. ## Release v4.0.1 of 08 December 2012 -_Internal CodeSnip version 4.0.1.212_ - -+ Fixed [SourceForge] bug #3578652: "Pre-processor directive errors in main db ini files" by removing support for problematic directives. -+ Rolling mouse over links in detail pane no longer displays a hint in the status bar. This change fixes [SourceForge] bug #3577407: Clicking detail pane snippet link leaves hint in status bar. -+ Windows no longer scale automatically when screen DPI differs from that on design system. This fixes [SourceForge] bug #3591818: "Strange window behaviour in Windows 7" and [SourceForge] bug #3591820: "Incorrect font size used for some bold text". -+ Update operating system detection code to detect Windows 8 & 2012 server. -+ Some refactoring and some redundant code removed. -+ Updated documentation. -+ Updated help topic that describes main display. +Internal CodeSnip version 4.0.1.212 +* Fixed [SourceForge] bug #3578652: "Pre-processor directive errors in main db ini files" by removing support for problematic directives. +* Rolling mouse over links in detail pane no longer displays a hint in the status bar. This change fixes [SourceForge] bug #3577407: Clicking detail pane snippet link leaves hint in status bar. +* Windows no longer scale automatically when screen DPI differs from that on design system. This fixes [SourceForge] bug #3591818: "Strange window behaviour in Windows 7" and [SourceForge] bug #3591820: "Incorrect font size used for some bold text". +* Update operating system detection code to detect Windows 8 & 2012 server. +* Some refactoring and some redundant code removed. +* Updated documentation. +* Updated help topic that describes main display. ## Release v4.0.0 of 12 October 2012 @@ -511,1886 +463,1674 @@ _Final v4 release._ See also changes from alpha, beta and release candidates below for details of changes since v3.9.3. -+ New glyphs that describes level of testing applied to snippets from online Code Snippets Database now appear in top right of detail pane. -+ Changed main window caption and task bar entry to include version number "4" after program name. -+ Fixed bugs: - - [SourceForge] bug #3572382 Automatic conversion of blank lines to paragraphs in REML mark-up editor gets confused if block level tags are already present. - - Unreported: Controls in News dialogue box do not use correct font. -+ A little refactoring. -+ Updated help file: - - Modified re recent changes - - Added new "What's new" topic giving details of changes in v4. Implements [SourceForge] feature request. - - Renamed "Welcome" page as "Overview". -+ Updated documentation, including read-me file. - +* New glyphs that describes level of testing applied to snippets from online Code Snippets Database now appear in top right of detail pane. +* Changed main window caption and task bar entry to include version number "4" after program name. +* Fixed bugs: + * [SourceForge] bug #3572382 Automatic conversion of blank lines to paragraphs in REML mark-up editor gets confused if block level tags are already present. + * Unreported: Controls in News dialogue box do not use correct font. +* A little refactoring. +* Updated help file: + * Modified re recent changes + * Added new "What's new" topic giving details of changes in v4. Implements [SourceForge] feature request. + * Renamed "Welcome" page as "Overview". +* Updated documentation, including read-me file. ## Release v4.0 RC 3 of 18 September 2012 -_Internal CodeSnip version 3.999.3_ - -+ Fixed serious [SourceForge] bug #3568628: "CodeSnip faulting at startup after fresh install with no previous v3 installation". +Internal CodeSnip version 3.999.3 +* Fixed serious [SourceForge] bug #3568628: "CodeSnip faulting at startup after fresh install with no previous v3 installation". ## Release v4.0 RC 2 of 17 September 2012 -_Internal CodeSnip version 3.999.2_ - -+ Fixed serious [SourceForge] bug #3568515: Duplicating a snippet with a display name causes crash. -+ Minor update to licensing information and about box credits. +Internal CodeSnip version 3.999.2 +* Fixed serious [SourceForge] bug #3568515: Duplicating a snippet with a display name causes crash. +* Minor update to licensing information and about box credits. ## Release v4.0 RC 1 of 14 September 2012 -_Internal CodeSnip version 3.999.1_ - -+ UI changes: - - Welcome page completely revised. Instead of a program overview the page now describes the state of the databases and available compilers and displays help links and a donation request. There are also some links to the about box, news and program updates. - - Many changes to glyphs used in menus and toolbar. - - Removed some images that relate to trade marks, i.e. PayPal Donate button and Delphi compiler icons. - - No clicking noise is now issued by UI in response to user interaction with Details pane. - - Links to external commands and to other snippets re-styled. - - Revised and updated program's main icon. -+ Fixed [SourceForge] bug #3566426: About Box Paths Page displays wrongly when themes not available. -+ Added support for Delphi XE3 compiler. Implements [SourceForge] feature request #3566346. -+ Completely new Easter Egg. -+ Refactoring and internal code changes, including a revision of the "external" object that communicates with JavaScript in browser controls. -+ Changed License: - - EULA for executable code changed to Mozilla Public License v2.0. - - Most original source code changed to Mozilla Public License v2.0 from v1.1. - - Only an abbreviated version of the license is now displayed by the installer and in the help file. - - License information has been consolidated into a new file: License.html. -+ About box changed re new license and changes in required and voluntary acknowledgements and credits. -+ Help file updated. -+ Documentation updated. - +Internal CodeSnip version 3.999.1 + +* UI changes: + * Welcome page completely revised. Instead of a program overview the page now describes the state of the databases and available compilers and displays help links and a donation request. There are also some links to the about box, news and program updates. + * Many changes to glyphs used in menus and toolbar. + * Removed some images that relate to trade marks, i.e. PayPal Donate button and Delphi compiler icons. + * No clicking noise is now issued by UI in response to user interaction with Details pane. + * Links to external commands and to other snippets re-styled. + * Revised and updated program's main icon. +* Fixed [SourceForge] bug #3566426: About Box Paths Page displays wrongly when themes not available. +* Added support for Delphi XE3 compiler. Implements [SourceForge] feature request #3566346. +* Completely new Easter Egg. +* Refactoring and internal code changes, including a revision of the "external" object that communicates with JavaScript in browser controls. +* Changed License: + * EULA for executable code changed to Mozilla Public License v2.0. + * Most original source code changed to Mozilla Public License v2.0 from v1.1. + * Only an abbreviated version of the license is now displayed by the installer and in the help file. + * License information has been consolidated into a new file: License.html. +* About box changed re new license and changes in required and voluntary acknowledgements and credits. +* Help file updated. +* Documentation updated. ## Release v4.0 beta 2 of 25 August 2012 -_Internal CodeSnip version 3.99.2_ - -+ [SourceForge] Bug fixes: - - #3556620: Serious flaw in generating units containing class types when the classes contain method types other than procedure or function. - - #3556713: Context menus are not displayed when pressing Alt+F10. - - #3556715: Deleting a category then returning to it via the history list causes a GPF. - - #3556718: Inconsistent context menus for edit controls in Snippets Editor. - - #3557107: New snippets and categories are not added to the history list. - - #3558649: Closing the Preferences dialogue always refreshes the main display even if the dialogue box was cancelled or if nothing was changed. - - #3559156: "Previews" giving examples of the effect of changes made in the Preferences dialogue box sometimes disappear when the tab key is pressed. - - #3559239: Snippet names and display names are used inconsistently in the UI. - - #3559257: Compile Results displayed from the main menu can get out of sync with the actual compile results of snippets that have been edited since they were last compiled. - - #3559265: Viewing dependencies for an unnamed snippet or a snippet not in the database causes a GPF. - - #3559266: When include files are generated for a snippet that depends on a class type, the required class is not listed in the file's header comments. - - #3560317: The caption of the Active Text preview dialogue box refers to "Extra" text when it is used to preview a snippet's description. - - #3560521: The state of the Overview tree often doesn't restore correctly after a database update. - - #3560958: Snippets are not sorted correctly (i.e. on display name) in the Overview pane. - - #3561014: The current view in the Display pane is not cleared, even though all tabs are closed, when the database is re-loading. - - #3561047: The Category view in the Overview pane sometimes appears fully expanded when it is expected to be fully collapsed. - - Untracked: Removed "(v4 preview)" text that had been left in main window title bar. - - Untracked: Minor accelerator key related problems in the Preferences dialogue box. -+ Main UI changes: - - The Detail pane tab-set now has a context menu that can be used to close the current tab or all but the current tab. The Close tab option is also added to current view's context menu. - - Right-clicking a tab in the Overview or Detail pane now selects the tab. - - Middle-clicking an item in the Overview pane now selects it. Previously middle clicks were ignored. - - Many more parts of the main display and dialogue boxes now display display names for snippets rather than unique names. - - Ctrl clicking snippet and category links in the Detail pane and History UI controls now opens the chosen item in a new tab. Implements [SourceForge] feature requests #3559377 and #3559378. - - Different link styles are now used for the different types of link in the Detail pane. Implements [SourceForge] feature request #3559464. - - Pressing Ctrl+Return on an active snippet or category link in the Details pane now opens the item in a new tab instead of the current tab. Pressing Return now opens the item in the current tab. - - The Detail pane's context menu now displays more options when text selections and links are right clicked. Implements [SourceForge] feature request #3559375 with some minor differences. - - New button added to the Display tab of the Preferences dialogue box that resets default snippet heading colours. Implements [SourceForge] feature request #3559140. - - The "Types" unit is now displayed by default in the Snippets Editor Reference Tab's predefined units list. -+ When a category is printed, any URLs in snippet descriptions are output and styled. -+ Names of units referenced by snippets may now contain dots. -+ Some refactoring and internal code changes, including a major reworking of the "first run" program configuration and a revision to the "external" object that communicates with JavaScript in browser controls. -+ Help file updated: - - Re UI changes. - - With a description of the need to configure namespaces for Delphi XE2 for each unit referenced by the code it is test compiling - addresses [SourceForge] bug. -+ Updated documentation. - +Internal CodeSnip version 3.99.2 + +* [SourceForge] Bug fixes: + * #3556620: Serious flaw in generating units containing class types when the classes contain method types other than procedure or function. + * #3556713: Context menus are not displayed when pressing Alt+F10. + * #3556715: Deleting a category then returning to it via the history list causes a GPF. + * #3556718: Inconsistent context menus for edit controls in Snippets Editor. + * #3557107: New snippets and categories are not added to the history list. + * #3558649: Closing the Preferences dialogue always refreshes the main display even if the dialogue box was cancelled or if nothing was changed. + * #3559156: "Previews" giving examples of the effect of changes made in the Preferences dialogue box sometimes disappear when the tab key is pressed. + * #3559239: Snippet names and display names are used inconsistently in the UI. + * #3559257: Compile Results displayed from the main menu can get out of sync with the actual compile results of snippets that have been edited since they were last compiled. + * #3559265: Viewing dependencies for an unnamed snippet or a snippet not in the database causes a GPF. + * #3559266: When include files are generated for a snippet that depends on a class type, the required class is not listed in the file's header comments. + * #3560317: The caption of the Active Text preview dialogue box refers to "Extra" text when it is used to preview a snippet's description. + * #3560521: The state of the Overview tree often doesn't restore correctly after a database update. + * #3560958: Snippets are not sorted correctly (i.e. on display name) in the Overview pane. + * #3561014: The current view in the Display pane is not cleared, even though all tabs are closed, when the database is re-loading. + * #3561047: The Category view in the Overview pane sometimes appears fully expanded when it is expected to be fully collapsed. + * Untracked: Removed "(v4 preview)" text that had been left in main window title bar. + * Untracked: Minor accelerator key related problems in the Preferences dialogue box. +* Main UI changes: + * The Detail pane tab-set now has a context menu that can be used to close the current tab or all but the current tab. The Close tab option is also added to current view's context menu. + * Right-clicking a tab in the Overview or Detail pane now selects the tab. + * Middle-clicking an item in the Overview pane now selects it. Previously middle clicks were ignored. + * Many more parts of the main display and dialogue boxes now display display names for snippets rather than unique names. + * Ctrl clicking snippet and category links in the Detail pane and History UI controls now opens the chosen item in a new tab. Implements [SourceForge] feature requests #3559377 and #3559378. + * Different link styles are now used for the different types of link in the Detail pane. Implements [SourceForge] feature request #3559464. + * Pressing Ctrl+Return on an active snippet or category link in the Details pane now opens the item in a new tab instead of the current tab. Pressing Return now opens the item in the current tab. + * The Detail pane's context menu now displays more options when text selections and links are right clicked. Implements [SourceForge] feature request #3559375 with some minor differences. + * New button added to the Display tab of the Preferences dialogue box that resets default snippet heading colours. Implements [SourceForge] feature request #3559140. + * The "Types" unit is now displayed by default in the Snippets Editor Reference Tab's predefined units list. +* When a category is printed, any URLs in snippet descriptions are output and styled. +* Names of units referenced by snippets may now contain dots. +* Some refactoring and internal code changes, including a major reworking of the "first run" program configuration and a revision to the "external" object that communicates with JavaScript in browser controls. +* Help file updated: + * Re UI changes. + * With a description of the need to configure namespaces for Delphi XE2 for each unit referenced by the code it is test compiling - addresses [SourceForge] bug. +* Updated documentation. ## Release v4.0 beta 1 of 11 August 2012 -_Internal CodeSnip version 3.99.1_ - -+ New features: - - Structure of snippet pages in details pane is now customisable: various page elements can be omitted and order of elements can be specified. Each snippet type has its own page customisation. Implements [SourceForge] feature request #3519456. - - Snippets can now have a "display name" that can contain any characters and does not need to be unique. When provided the display name is displayed in preference to the normal name. Implements [SourceForge] feature request #3519460. - - Snippet descriptions can now be formatted and contain multiple paragraphs. This implements [SourceForge] feature requests #3411890 and #3520405. - - Snippets can now be configured so that their source code is not syntax highlighted. This change allows snippets in other languages not to be highlighted as if they are Pascal. Implements [SourceForge] feature request #3519935. - - Colour of headings for snippets and categories from main and user databases are now user configurable. This implements [SourceForge] feature request #3519463. - - User can now limit the number of compilers that appear in the compiler results table in the display pane. This is done via the Configure Compilers dialogue box. Implements [SourceForge] feature request #3519459. - - New option on Tools menu that checks availability of new versions of CodeSnip. -+ User interface changes: - - "Test Compile" link removed from snippet display in details pane. - - "Test Compile" dialogue box changed so that only the installed compilers that CodeSnip uses for test compilation are displayed, instead of all known compilers. - - Welcome page revised. -+ Changes to snippets editor: - - New field on Code tab to enter optional snippet display name. - - New check box on Code tab to specify if Pascal syntax highlighter is to be used for source code. - - New tabbed "mark-up editor" lets user enter multi-paragraph snippet descriptions and extra information either as plain text or as REML mark-up. - - Controls on Code tab re-ordered. - - Extra Information tab revised with much larger edit control and deletion of explanatory text. - - Editor enlarged. -+ Changes to Preferences dialogue box: - - New "Snippet Layout" tab added where composition and layout of snippet pages can be customised. - - "General" preferences tab split into two: "Misc." that contains only measurement units and "Display" that contains display related options. - - Added controls to "Display" tab to set main and user database heading colours. - - Changes that affect appearance of content of details pane are now reflected in the display as soon as the Preferences dialogue box closes, rather than on program restart. -+ Changes to REML mark-up handling: - - Any REML text not embedded in block level tags is now automatically wrapped in `

    `...`

    ` tags. - - Nested REML block level tags are no longer allowed. - - Changed handling of multiple spaces in REML code to be the same as in HTML. - - Formatting of REML code improved when re-displayed. -+ Bug fixes: - - [SourceForge] bug #3536331 fixed: words at the end of some paragraphs in a snippet's extra information were not being found in "whole word only" searches. - - Fixed unreported file parsing bug that occurred when loading a saved snippet selection from disk. -+ Changed Delphi compiler detection so that compilers can be detected by examining current user registry key in addition to local machine registry key. This enables Delphis that were installed for a given user only to be detected. -+ Improved error handling when reading and writing snippet selection files. -+ User database and export file formats updated to v6. -+ Per-user configuration file format changes: bumped file version to v9. -+ Major changes to installer: - - Now always brings forward any earlier common configuration files if needed. - - Per user configuration files are now ignored: they are handled by main program (see below). - - Only main database, not user database, is now imported from earlier versions on user request. User databases are now handled by main program (see below). - - There is no longer an option to delete old databases or configuration files. - - Updating from a v4 preview (alpha) release causes an extra page to be displayed that gives instructions relating to updating from preview to current release. -+ Program now detects if it is running for first time since updating: - - If this is first run since updating from v3 or earlier a "first run" wizard guides user through importing any old preferences or user databases. - - For point v4.x point updates user configuration file is silently updated as necessary. -+ Significant refactoring. -+ Updated help file in line with changes and new features. -+ Updated documentation, including minor changes to privacy statement and license. - +Internal CodeSnip version 3.99.1 + +* New features: + * Structure of snippet pages in details pane is now customisable: various page elements can be omitted and order of elements can be specified. Each snippet type has its own page customisation. Implements [SourceForge] feature request #3519456. + * Snippets can now have a "display name" that can contain any characters and does not need to be unique. When provided the display name is displayed in preference to the normal name. Implements [SourceForge] feature request #3519460. + * Snippet descriptions can now be formatted and contain multiple paragraphs. This implements [SourceForge] feature requests #3411890 and #3520405. + * Snippets can now be configured so that their source code is not syntax highlighted. This change allows snippets in other languages not to be highlighted as if they are Pascal. Implements [SourceForge] feature request #3519935. + * Colour of headings for snippets and categories from main and user databases are now user configurable. This implements [SourceForge] feature request #3519463. + * User can now limit the number of compilers that appear in the compiler results table in the display pane. This is done via the Configure Compilers dialogue box. Implements [SourceForge] feature request #3519459. + * New option on Tools menu that checks availability of new versions of CodeSnip. +* User interface changes: + * "Test Compile" link removed from snippet display in details pane. + * "Test Compile" dialogue box changed so that only the installed compilers that CodeSnip uses for test compilation are displayed, instead of all known compilers. + * Welcome page revised. +* Changes to snippets editor: + * New field on Code tab to enter optional snippet display name. + * New check box on Code tab to specify if Pascal syntax highlighter is to be used for source code. + * New tabbed "mark-up editor" lets user enter multi-paragraph snippet descriptions and extra information either as plain text or as REML mark-up. + * Controls on Code tab re-ordered. + * Extra Information tab revised with much larger edit control and deletion of explanatory text. + * Editor enlarged. +* Changes to Preferences dialogue box: + * New "Snippet Layout" tab added where composition and layout of snippet pages can be customised. + * "General" preferences tab split into two: "Misc." that contains only measurement units and "Display" that contains display related options. + * Added controls to "Display" tab to set main and user database heading colours. + * Changes that affect appearance of content of details pane are now reflected in the display as soon as the Preferences dialogue box closes, rather than on program restart. +* Changes to REML mark-up handling: + * Any REML text not embedded in block level tags is now automatically wrapped in `

    `...`

    ` tags. + * Nested REML block level tags are no longer allowed. + * Changed handling of multiple spaces in REML code to be the same as in HTML. + * Formatting of REML code improved when re-displayed. +* Bug fixes: + * [SourceForge] bug #3536331 fixed: words at the end of some paragraphs in a snippet's extra information were not being found in "whole word only" searches. + * Fixed unreported file parsing bug that occurred when loading a saved snippet selection from disk. +* Changed Delphi compiler detection so that compilers can be detected by examining current user registry key in addition to local machine registry key. This enables Delphis that were installed for a given user only to be detected. +* Improved error handling when reading and writing snippet selection files. +* User database and export file formats updated to v6. +* Per-user configuration file format changes: bumped file version to v9. +* Major changes to installer: + * Now always brings forward any earlier common configuration files if needed. + * Per user configuration files are now ignored: they are handled by main program (see below). + * Only main database, not user database, is now imported from earlier versions on user request. User databases are now handled by main program (see below). + * There is no longer an option to delete old databases or configuration files. + * Updating from a v4 preview (alpha) release causes an extra page to be displayed that gives instructions relating to updating from preview to current release. +* Program now detects if it is running for first time since updating: + * If this is first run since updating from v3 or earlier a "first run" wizard guides user through importing any old preferences or user databases. + * For point v4.x point updates user configuration file is silently updated as necessary. +* Significant refactoring. +* Updated help file in line with changes and new features. +* Updated documentation, including minor changes to privacy statement and license. ## Release v4.0 alpha 3 (preview) of 18 June 2012 -_Internal CodeSnip version 3.98.3_ - -+ New features: - - Compiler warnings can now be switched on as well as off in generated code. - - Names and descriptions of snippets in a category can now be printed. - - Text and compiler searches can now be nested so that the later search refines the earlier one. - - Current selection (i.e. search result set) can be saved to disk and loaded again later. -+ User interface changes: - - Overview pane now displays buttons that can be used to collapse or expand non-empty section headings. - - Ctrl + arrow keys can now be used to scroll overview pane tree view vertically and horizontally without changing selection in overview pane. - - Main window is now refreshed whenever changes that affect it are made in the Preferences dialogue box. - - Some main menu short-cut keys changed. - - Dependencies dialogue box now has two tabs: the first displays the snippets required to compile the selected snippet while the second tab displays snippets that depend upon the selected snippet. - - Code Generation tab of Preferences dialogue box updated to enable warnings to be switched on or off. In addition default warnings can be restored, list view columns can be sorted and Alt key short-cuts tweaked. - - Code Import dialogue box improved: now sorts imported units in list view and scrolls to make renamed snippets visible. - - Snippet selection and cross-reference search dialogues now report if existing search results will be overwritten. - - Text and compiler search dialogues now ask if any current search results are to be refined. - - Tree views in Snippet Selection, Snippets export and Snippets submission dialogue boxes can now be expanded and collapsed. - - Appearance of message boxes tweaked. - - Program tab of About box updated with credits for new third party code. -+ Bug fixes: - - Error in logic of code that generates program ID was fixed. - - [SourceForge] Bug #2868708 fixed: edited snippets are no longer lost from manual snippet selections unless snippet names are changed. - - [SourceForge] Bug #3534138 fixed: details pane display is now cleared when last tab is closed: previously content of last closed tab remained on screen. -+ Info about user's OS and IE version is now sent to web server during online database updates. -+ Some refactoring. -+ Help file updated in line with changes and new features. Some US English spellings changed to UK English for consistency. -+ Updated documentation, including: - - Privacy statement updated re changes in data recorded via database update log-ons. - - Licensing docs updated re introduction of some MPL 2.0 files. - +Internal CodeSnip version 3.98.3 + +* New features: + * Compiler warnings can now be switched on as well as off in generated code. + * Names and descriptions of snippets in a category can now be printed. + * Text and compiler searches can now be nested so that the later search refines the earlier one. + * Current selection (i.e. search result set) can be saved to disk and loaded again later. +* User interface changes: + * Overview pane now displays buttons that can be used to collapse or expand non-empty section headings. + * Ctrl+arrow keys can now be used to scroll overview pane tree view vertically and horizontally without changing selection in overview pane. + * Main window is now refreshed whenever changes that affect it are made in the Preferences dialogue box. + * Some main menu short-cut keys changed. + * Dependencies dialogue box now has two tabs: the first displays the snippets required to compile the selected snippet while the second tab displays snippets that depend upon the selected snippet. + * Code Generation tab of Preferences dialogue box updated to enable warnings to be switched on or off. In addition default warnings can be restored, list view columns can be sorted and Alt key short-cuts tweaked. + * Code Import dialogue box improved: now sorts imported units in list view and scrolls to make renamed snippets visible. + * Snippet selection and cross-reference search dialogues now report if existing search results will be overwritten. + * Text and compiler search dialogues now ask if any current search results are to be refined. + * Tree views in Snippet Selection, Snippets export and Snippets submission dialogue boxes can now be expanded and collapsed. + * Appearance of message boxes tweaked. + * Program tab of About box updated with credits for new third party code. +* Bug fixes: + * Error in logic of code that generates program ID was fixed. + * [SourceForge] Bug #2868708 fixed: edited snippets are no longer lost from manual snippet selections unless snippet names are changed. + * [SourceForge] Bug #3534138 fixed: details pane display is now cleared when last tab is closed: previously content of last closed tab remained on screen. +* Info about user's OS and IE version is now sent to web server during online database updates. +* Some refactoring. +* Help file updated in line with changes and new features. Some US English spellings changed to UK English for consistency. +* Updated documentation, including: + * Privacy statement updated re changes in data recorded via database update log-ons. + * Licensing docs updated re introduction of some MPL 2.0 files. ## Release v4.0 alpha 2 (preview) of 21 April 2012 -_Internal CodeSnip version 3.98.2_ - -+ New features: - - New "unit" snippet type that enables complete units to be stored in database and to be test compiled. - - New "classes" snippet type that enables a single Object Pascal class or advanced record-with-methods to be stored in database, test compiled and included in generated units. - - Snippets from both the user and main databases can now be duplicated. Duplicates are editable and are stored in the user database. - - Online CodeSnip FAQs can now be displayed in the default browser from a new option on the "Help" menu. -+ User interface changes: - - New "Snippets" and "Categories" top level menus have been added. They are populated with items previously on the "Database" menu. "Snippets" menu also has new "Duplicate Snippet" item. - - "Help" menu re-arranged: items from the former "On The Web" sub-menu are now placed directly on "Help" menu. - - Numerous new and updated glyphs on toolbar, menu and in main display. - - Minor tweaks to controls in the Code tab of the Snippets Editor. - - Minor changes to the style of version info displayed on the splash screen. -+ Bug fixes: - - Fixed potential source of a bug in code that edits user-defined categories. - - Fixed unreported minor bug in dialogue boxes that display tabbed page controls: clicking a tab did not always give it the keyboard focus. - - Fixed [SourceForge] bug #3519784 where multi-line "type" or "constant" snippets that start on the same line as the type or const keyword were corrupted when included in units using the "comments after snippet header" comment style. -+ Characters used to introduced switches on the command line were changed: '/' replaces '\'. '-' is still permitted. -+ User and main database formats modified. User databases saved with this version may not be readable with release 4.0 alpha 1. -+ Some refactoring. -+ Help file updated in line with changes and some errors fixed. -+ Updated documentation. - +Internal CodeSnip version 3.98.2 + +* New features: + * New "unit" snippet type that enables complete units to be stored in database and to be test compiled. + * New "classes" snippet type that enables a single Object Pascal class or advanced record-with-methods to be stored in database, test compiled and included in generated units. + * Snippets from both the user and main databases can now be duplicated. Duplicates are editable and are stored in the user database. + * Online CodeSnip FAQs can now be displayed in the default browser from a new option on the "Help" menu. +* User interface changes: + * New "Snippets" and "Categories" top level menus have been added. They are populated with items previously on the "Database" menu. "Snippets" menu also has new "Duplicate Snippet" item. + * "Help" menu re-arranged: items from the former "On The Web" sub-menu are now placed directly on "Help" menu. + * Numerous new and updated glyphs on toolbar, menu and in main display. + * Minor tweaks to controls in the Code tab of the Snippets Editor. + * Minor changes to the style of version info displayed on the splash screen. +* Bug fixes: + * Fixed potential source of a bug in code that edits user-defined categories. + * Fixed unreported minor bug in dialogue boxes that display tabbed page controls: clicking a tab did not always give it the keyboard focus. + * Fixed [SourceForge] bug #3519784 where multi-line "type" or "constant" snippets that start on the same line as the type or const keyword were corrupted when included in units using the "comments after snippet header" comment style. +* Characters used to introduced switches on the command line were changed: '/' replaces '\'. '-' is still permitted. +* User and main database formats modified. User databases saved with this version may not be readable with release 4.0 alpha 1. +* Some refactoring. +* Help file updated in line with changes and some errors fixed. +* Updated documentation. ## Release v4.0 alpha 1 (preview) of 31 December 2011 -_Internal CodeSnip version 3.98.1_ +Internal CodeSnip version 3.98.1 **Changes relate to v3.9.3:** CodeSnip 4 development branched off CodeSnip 3.9.3. CodeSnip v3 continued to be developed in parallel. -+ User interface changes: - - New multi-tab detail pane can now show more than one snippet, category etc. - - Results of test compiles now appear in a dialogue box instead of in details pane. - - New code import wizard for cleaner control over import process. - - New "Compile" top level menu that groups all actions relating to test compilation. - - Empty section headings can now be displayed in overview pane if required. - - New display options relating to multi-tab display. - - New view displayed instead of welcome screen when database updated. - - Some additions and changes to main window navigation keys. - - Main window and task bar captions changed. - - Some dialogue boxes tweaked. - - Compiler configuration dialogue box heavily revised to support default compiler paths. - - "About" dialogue box paths tab display improved. - - Compile error dialogue box display standardised. - - Splash screen updated. -+ Improved Delphi code syntax highlighter: - - Recognises Delphi 2010 keywords - - Correctly handles context sensitive directives within "property", "exports" and "external" statements. - - Recognises `&` prefix that causes keywords to be treated as identifiers. -+ Compiler search paths can now be specified for included units permitting non-VCL units to be used by snippets. -+ Database: - - Non-empty categories can no longer be deleted. - - File format of both user-defined and main databases changed. - - Database locations changed: updates to main database and edits to user database do not affect databases used by v3 and earlier. - - Location and file format of both user defined and main databases changed. - - Database now supports Unicode Delphi source code. - - Unicode Delphi identifiers can now be used for snippet names. - - Export and backup file formats updated: new formats are not backward compatible but older versions can still be imported. - - Code submission service now supports Unicode source code. - - Database updates now use v5 of delphidabbler.com web update service with revised checksum handling. -+ Unicode support: - - Program now fully supports Unicode internally. - - Test units now use UTF-8 format if source code contains non-ANSI characters. ANSI format is used otherwise to permit compilation on older Delphi compilers. - - Many export file formats now support Unicode and UTF8 formats. User may specify file types from Save dialogue box. - - Configuration files are now in Unicode format. - - Some Unicode support added to database (see above). -+ Web service data handling code improved: now includes ability to send raw bytes and can detect and adapt to character encoding used in responses. -+ Common and per-user configuration file names and locations changed. Bumped file version numbers to 6 and 8 respectively. -+ Fixed some bugs: - - Various Unicode and code page related problems in RTF code generation. - - Memory leaks. - - Version detection in backup file restoration. -+ Cascading style sheet handling improved. -+ Any errors in scripts run in browser control now trapped and reported as exceptions instead of via browser control's own error dialogue box. -+ Revised external object that communicates with JavaScript in browser controls. -+ Hyper-links used in snippets now support the https:// protocol. -+ A default title now used in print spooler if none specified. -+ Source code heavily refactored. -+ Help file updated in line with changes. -+ Installer: - - Changed so that v3 and v4 installs can co-exist - default install locations are different and v4 does not overwrite v3. - - Converts v3 configuration files to v4 Unicode format and copies to new locations. File version stamps are updated. - - Installer is now compiled with Unicode version of Inno Setup instead of ANSI version. - - Scripts updated and refactored. -+ Updated documentation, including changes to privacy statement and new file format documentation. - +* User interface changes: + * New multi-tab detail pane can now show more than one snippet, category etc. + * Results of test compiles now appear in a dialogue box instead of in details pane. + * New code import wizard for cleaner control over import process. + * New "Compile" top level menu that groups all actions relating to test compilation. + * Empty section headings can now be displayed in overview pane if required. + * New display options relating to multi-tab display. + * New view displayed instead of welcome screen when database updated. + * Some additions and changes to main window navigation keys. + * Main window and task bar captions changed. + * Some dialogue boxes tweaked. + * Compiler configuration dialogue box heavily revised to support default compiler paths. + * "About" dialogue box paths tab display improved. + * Compile error dialogue box display standardised. + * Splash screen updated. +* Improved Delphi code syntax highlighter: + * Recognises Delphi 2010 keywords + * Correctly handles context sensitive directives within "property", "exports" and "external" statements. + * Recognises `&` prefix that causes keywords to be treated as identifiers. +* Compiler search paths can now be specified for included units permitting non-VCL units to be used by snippets. +* Database: + * Non-empty categories can no longer be deleted. + * File format of both user-defined and main databases changed. + * Database locations changed: updates to main database and edits to user database do not affect databases used by v3 and earlier. + * Location and file format of both user defined and main databases changed. + * Database now supports Unicode Delphi source code. + * Unicode Delphi identifiers can now be used for snippet names. + * Export and backup file formats updated: new formats are not backward compatible but older versions can still be imported. + * Code submission service now supports Unicode source code. + * Database updates now use v5 of delphidabbler.com web update service with revised checksum handling. +* Unicode support: + * Program now fully supports Unicode internally. + * Test units now use UTF-8 format if source code contains non-ANSI characters. ANSI format is used otherwise to permit compilation on older Delphi compilers. + * Many export file formats now support Unicode and UTF8 formats. User may specify file types from Save dialogue box. + * Configuration files are now in Unicode format. + * Some Unicode support added to database (see above). +* Web service data handling code improved: now includes ability to send raw bytes and can detect and adapt to character encoding used in responses. +* Common and per-user configuration file names and locations changed. Bumped file version numbers to 6 and 8 respectively. +* Fixed some bugs: + * Various Unicode and code page related problems in RTF code generation. + * Memory leaks. + * Version detection in backup file restoration. +* Cascading style sheet handling improved. +* Any errors in scripts run in browser control now trapped and reported as exceptions instead of via browser control's own error dialogue box. +* Revised external object that communicates with JavaScript in browser controls. +* Hyper-links used in snippets now support the https:// protocol. +* A default title now used in print spooler if none specified. +* Source code heavily refactored. +* Help file updated in line with changes. +* Installer: + * Changed so that v3 and v4 installs can co-exist - default install locations are different and v4 does not overwrite v3. + * Converts v3 configuration files to v4 Unicode format and copies to new locations. File version stamps are updated. + * Installer is now compiled with Unicode version of Inno Setup instead of ANSI version. + * Scripts updated and refactored. +* Updated documentation, including changes to privacy statement and new file format documentation. ## Release v3.13.2 of 31 October 2013 -+ Modified Syntax Highlighter tab of Preferences dialogue box so that "vertical" fonts (whose names begin with "@") no longer appear in list of available fonts. -+ Fixed potential bug in operating system detection code that may fail on Windows 2000. -+ Fixed registry access code so that the 64 bit view of the registry is used when CodeSnip runs on a Windows 64 bit operating system. - +* Modified Syntax Highlighter tab of Preferences dialogue box so that "vertical" fonts (whose names begin with "@") no longer appear in list of available fonts. +* Fixed potential bug in operating system detection code that may fail on Windows 2000. +* Fixed registry access code so that the 64 bit view of the registry is used when CodeSnip runs on a Windows 64 bit operating system. ## Release v3.13.1 of 18 September 2013 -+ Removed File | Page Setup menu option because some settings made there were being ignored when a file was printed. This is a fix for [SourceForge] bug #89 "Setup selections not being remembered". -+ Updated help file re changes. - +* Removed File | Page Setup menu option because some settings made there were being ignored when a file was printed. This is a fix for [SourceForge] bug #89 "Setup selections not being remembered". +* Updated help file re changes. ## Release v3.13.0 of 12 September 2013 -+ Added support for Delphi XE5 compiler. -+ Updated documentation re changes. -+ Updated help file re changes. - +* Added support for Delphi XE5 compiler. +* Updated documentation re changes. +* Updated help file re changes. ## Release v3.12.1 of 01 July 2013 -+ Fixed [SourceForge] bug #82 "Fatal divide by zero exception on start-up" that affected all v3.x versions when the IE 10 browser was installed. -+ Fixed unreported bug where IE 10 browser was being reported as IE 9. -+ Updated all third party DelphiDabbler code to latest available versions. -+ Updated documentation re changes. +* Fixed [SourceForge] bug #82 "Fatal divide by zero exception on start-up" that affected all v3.x versions when the IE 10 browser was installed. +* Fixed unreported bug where IE 10 browser was being reported as IE 9. +* Updated all third party DelphiDabbler code to latest available versions. +* Updated documentation re changes. ## Release v3.12.0 of 02 May 2013 -+ Added support for Delphi XE4 compiler. Implements [SourceForge] feature request #78. -+ Fixed [SourceForge] bug #78: CodeSnip doesn't restore window in correct position when task bar on left or top of screen. -+ Updated documentation re changes. -+ Updated help file re changes. - +* Added support for Delphi XE4 compiler. Implements [SourceForge] feature request #78. +* Fixed [SourceForge] bug #78: CodeSnip doesn't restore window in correct position when task bar on left or top of screen. +* Updated documentation re changes. +* Updated help file re changes. ## Release v3.11.1 of 08 December 2012 -+ Fixed [SourceForge] bug #3578654: "Pre-processor directive errors in main db ini files" by removing support for problematic directives. -+ Hints are no longer displayed in status bar when user rolls mouse over a link in the display pane. This fixes [SourceForge] bug #3577408: "Clicking detail pane snippet link leaves hint in status bar". -+ Windows no longer scale automatically when screen DPI differs from that on design system. This fixes [SourceForge] bug #3591818: "Strange window behaviour in Windows 7" and [SourceForge] bug #3591820: "Incorrect font size used for some bold text". -+ Updated operating system detection code to detect Windows 8 & 2012 server. -+ Updated documentation - +* Fixed [SourceForge] bug #3578654: "Pre-processor directive errors in main db ini files" by removing support for problematic directives. +* Hints are no longer displayed in status bar when user rolls mouse over a link in the display pane. This fixes [SourceForge] bug #3577408: "Clicking detail pane snippet link leaves hint in status bar". +* Windows no longer scale automatically when screen DPI differs from that on design system. This fixes [SourceForge] bug #3591818: "Strange window behaviour in Windows 7" and [SourceForge] bug #3591820: "Incorrect font size used for some bold text". +* Updated operating system detection code to detect Windows 8 & 2012 server. +* Updated documentation ## Release v3.11.0 of 17 September 2012 -+ Added support for Delphi XE3 compiler. Implements [SourceForge] feature request #3566345. -+ [SourceForge] Bug fixes: - - #3561713: The Category view in the Overview pane sometimes appears fully expanded when it is expected to be fully collapsed. - - #3566430: About Box Paths Page displays wrongly when themes not available. -+ Updated documentation re changes. -+ Updated help file re changes. - +* Added support for Delphi XE3 compiler. Implements [SourceForge] feature request #3566345. +* [SourceForge] Bug fixes: + * #3561713: The Category view in the Overview pane sometimes appears fully expanded when it is expected to be fully collapsed. + * #3566430: About Box Paths Page displays wrongly when themes not available. +* Updated documentation re changes. +* Updated help file re changes. ## Release v3.10.5 of 21 August 2012 -+ Fixed [SourceForge] bugs: - - #3559257: Compile Results accessed from menu can get out of sync. - - #3559156: "Previews" disappearing in Preferences dialogue box - +* Fixed [SourceForge] bugs: + * #3559257: Compile Results accessed from menu can get out of sync. + * #3559156: "Previews" disappearing in Preferences dialogue box ## Release v3.10.4 of 16 August 2012 -+ Added support for displaying pop-up menus over appropriate control when Alt+F10 is pressed. Fixes [SourceForge] bug #3556713. -+ Changes to snippets editor: - - Added missing edit context menu to "add unit" edit control on References tab. Fixes [SourceForge] bug #3556718 as it relates to v3. - - Predefined list of units in Units list on References tab now includes the "Types" unit. - - Referenced unit names may now contain dots. - - Snippets Editor help topic now explains need to configure Delphi XE2 compiler to search namespaces containing referenced units. This provides a solution to [SourceForge] bug #3536531. - +* Added support for displaying pop-up menus over appropriate control when Alt+F10 is pressed. Fixes [SourceForge] bug #3556713. +* Changes to snippets editor: + * Added missing edit context menu to "add unit" edit control on References tab. Fixes [SourceForge] bug #3556718 as it relates to v3. + * Predefined list of units in Units list on References tab now includes the "Types" unit. + * Referenced unit names may now contain dots. + * Snippets Editor help topic now explains need to configure Delphi XE2 compiler to search namespaces containing referenced units. This provides a solution to [SourceForge] bug #3536531. ## Release v3.10.3 of 25 July 2012 -+ Changed so that Delphi compilers can be detected by examining current user registry key in addition to local machine registry key. This enables Delphis that were installed for a given user only to be detected. -+ Fixed bug in Compiler tab of Configure Compilers dialogue box that failed to flag selected compiler as unavailable after button was pressed. - +* Changed so that Delphi compilers can be detected by examining current user registry key in addition to local machine registry key. This enables Delphis that were installed for a given user only to be detected. +* Fixed bug in Compiler tab of Configure Compilers dialogue box that failed to flag selected compiler as unavailable after button was pressed. ## Release v3.10.2 of 19 June 2012 -+ Fixed [SourceForge] bug #3536331 where some distinct words in a snippet's Extra text where not being found in text searches. -+ Info about user's OS and IE version is now sent to web server during online database updates. -+ Updated privacy statement re changes in information sent by update web service. - +* Fixed [SourceForge] bug #3536331 where some distinct words in a snippet's Extra text where not being found in text searches. +* Info about user's OS and IE version is now sent to web server during online database updates. +* Updated privacy statement re changes in information sent by update web service. ## Release v3.10.1 of 20 April 2012 -+ Fixed [SourceForge] bug #3519784 where multi-line type or constant snippets that start on same line as type or const keyword were corrupted when included in units using the "comments after snippet header" comment style. -+ Also fixed potential source of a bug in code that edits user-defined categories. - +* Fixed [SourceForge] bug #3519784 where multi-line type or constant snippets that start on same line as type or const keyword were corrupted when included in units using the "comments after snippet header" comment style. +* Also fixed potential source of a bug in code that edits user-defined categories. ## Release v3.10.0 of 17 April 2012 -+ Added new Help | On The Web | FAQs menu option to display CodeSnip FAQs in default browser. -+ Fixed unreported minor bug in dialogue boxes that display tabbed page controls: clicking a tab did not always give it the keyboard focus. -+ Characters used to introduced switches on command line were changed: '/' replaces non-standard '\'. '-' is still permitted. -+ Updated help file: added topic for new menu option and minor change to FAQ help topic. - +* Added new Help | On The Web | FAQs menu option to display CodeSnip FAQs in default browser. +* Fixed unreported minor bug in dialogue boxes that display tabbed page controls: clicking a tab did not always give it the keyboard focus. +* Characters used to introduced switches on command line were changed: '/' replaces non-standard '\'. '-' is still permitted. +* Updated help file: added topic for new menu option and minor change to FAQ help topic. ## Release v3.9.3 of 23 November 2011 **Note:** Development of CodeSnip 4 branched off this release. -+ Fixed some bugs in main window: - - Toolbar was truncated when window is too narrow to display it all. It now wraps. - - Treeview state in Overview pane was not restoring correctly after navigating away from and then returning to a tab. - - Pressing Ctrl+Tab or Shift+Ctrl+Tab did not necessarily change the tab in the expected tab set in either the Overview or Detail panes. -+ Fixed a broken URL in about box. -+ Bumped installer program helper build number re Delphi 2010 compilation (should have been done at v3.5.1). - +* Fixed some bugs in main window: + * Toolbar was truncated when window is too narrow to display it all. It now wraps. + * Treeview state in Overview pane was not restoring correctly after navigating away from and then returning to a tab. + * Pressing Ctrl+Tab or Shift+Ctrl+Tab did not necessarily change the tab in the expected tab set in either the Overview or Detail panes. +* Fixed a broken URL in about box. +* Bumped installer program helper build number re Delphi 2010 compilation (should have been done at v3.5.1). ## Release v3.9.2 of 28 October 2011 -+ Fixed [SourceForge] bug #3427741 where details pane tabs didn't change in response to key presses. -+ Fixed [SourceForge] bug #3427866 where selection in overview was not always same as item displayed in details pane. -+ Fixed [SourceForge] bug #3427889 where there was the possibility of a GPF in overview pane. - +* Fixed [SourceForge] bug #3427741 where details pane tabs didn't change in response to key presses. +* Fixed [SourceForge] bug #3427866 where selection in overview was not always same as item displayed in details pane. +* Fixed [SourceForge] bug #3427889 where there was the possibility of a GPF in overview pane. ## Release v3.9.1 of 18 September 2011 -+ Fixed [SourceForge] bug #3369422 in Pascal highlighter that was causing an assertion failure when parsing malformed Pascal general format floating point numbers. - +* Fixed [SourceForge] bug #3369422 in Pascal highlighter that was causing an assertion failure when parsing malformed Pascal general format floating point numbers. ## Release v3.9.0 of 07 September 2011 -+ Added support for Delphi XE2 Windows 32 bit compiler: - - Can now test compile and display results with Delphi XE2 32 bit. - - Delphi XE2 compiler version 23.0 has been added to the drop down menu in the Code Generation tab of the preference dialogue box. - - Updated help file re Delphi XE2 support. - - Updated documentation. -+ Limited user name edit control to 48 chars in registration wizard because this is limit in online registration database. - +* Added support for Delphi XE2 Windows 32 bit compiler: + * Can now test compile and display results with Delphi XE2 32 bit. + * Delphi XE2 compiler version 23.0 has been added to the drop down menu in the Code Generation tab of the preference dialogue box. + * Updated help file re Delphi XE2 support. + * Updated documentation. +* Limited user name edit control to 48 chars in registration wizard because this is limit in online registration database. ## Release v3.8.11 of 02 July 2011 -+ Fixed display problem in about box and compiler error dialogue boxes on systems using Internet Explorer v9 web browser control. This fixes [SourceForge] issue #3349186. -+ Updated read-me file re support for IE9 browser control. - +* Fixed display problem in about box and compiler error dialogue boxes on systems using Internet Explorer v9 web browser control. This fixes [SourceForge] issue #3349186. +* Updated read-me file re support for IE9 browser control. ## Release v3.8.10 of 20 May 2011 -+ Reverted checked tree views and list boxes to standard Windows behaviour. Clicking item text no longer toggles associated check boxes. This behaviour was more problematic then helpful. -+ Updated documentation, including new info about CodeSnip FAQ. -+ Added FAQs topic and TOC entry to help file that links to online FAQ. - +* Reverted checked tree views and list boxes to standard Windows behaviour. Clicking item text no longer toggles associated check boxes. This behaviour was more problematic then helpful. +* Updated documentation, including new info about CodeSnip FAQ. +* Added FAQs topic and TOC entry to help file that links to online FAQ. ## Release v3.8.9 of 10 May 2011 -+ Fixed [SourceForge] bug #3299870 that was allowing imported snippets with duplicate names to be renamed with invalid names. -+ Improved UI used to edit imported snippet names. -+ Any "warning" compile results in main database are now treated and displayed as "success" results per [SourceForge] feature request #3290359. -+ Fixed unreported potential bug in code that sets window class names. -+ Updated documentation. - +* Fixed [SourceForge] bug #3299870 that was allowing imported snippets with duplicate names to be renamed with invalid names. +* Improved UI used to edit imported snippet names. +* Any "warning" compile results in main database are now treated and displayed as "success" results per [SourceForge] feature request #3290359. +* Fixed unreported potential bug in code that sets window class names. +* Updated documentation. ## Release v3.8.8 of 19 January 2011 -+ Added facility for user to specify maximum age of news items displayed in news dialogue box. New preferences tab added where the maximum age can be customised. -+ Preferences dialogue box now displays multi-line tabs when necessary. -+ Refactored some code used to align controls on forms. -+ Updated license. License HTML help file is no longer MPLd and may not be altered by third parties. -+ Updated help file re changes. -+ Updated documentation. - +* Added facility for user to specify maximum age of news items displayed in news dialogue box. New preferences tab added where the maximum age can be customised. +* Preferences dialogue box now displays multi-line tabs when necessary. +* Refactored some code used to align controls on forms. +* Updated license. License HTML help file is no longer MPLd and may not be altered by third parties. +* Updated help file re changes. +* Updated documentation. ## Release v3.8.7 of 16 December 2010 -+ Delphi XE compiler version 22.0 has been added to the drop down menu in the Code Generation tab of the preference dialogue box. -+ Bug fix: compiler results are no longer listed when free-form snippets are printed or copied to the clipboard using the "Edit | Copy Information" menu item. - +* Delphi XE compiler version 22.0 has been added to the drop down menu in the Code Generation tab of the preference dialogue box. +* Bug fix: compiler results are no longer listed when free-form snippets are printed or copied to the clipboard using the "Edit | Copy Information" menu item. ## Release v3.8.6 of 06 December 2010 -+ Bug fix release (none reported in bug tracker): - - Corrected XML file validation so that it does not reject XML processing instructions that contain an "encoding" attribute. - - Fixed long standing bug that was crashing CodeSnip when the database was updated or restored after editing, adding or deleting any user defined snippet. - - Attempting to restore a database backup with an unknown (later) file format now raises an exception. Previously CodeSnip tried, unsuccessfully, to read the file. - +* Bug fix release (none reported in bug tracker): + * Corrected XML file validation so that it does not reject XML processing instructions that contain an "encoding" attribute. + * Fixed long standing bug that was crashing CodeSnip when the database was updated or restored after editing, adding or deleting any user defined snippet. + * Attempting to restore a database backup with an unknown (later) file format now raises an exception. Previously CodeSnip tried, unsuccessfully, to read the file. ## Release v3.8.5 of 28 November 2010 -+ Fixed bug where user was able to create snippets with valid names that would crash the alphabetic overview. Snippet names are now limited to letters from English alphabet and the underscore. Fixes [SourceForge] bug #3120958. -+ Fixed bug where snippets that have names beginning with a lower case letter were being omitted from from the associated list of snippets shown in the detail pane. Fixes [SourceForge] bug #3120962. -+ Updated Snippets Editor topic in help file. - +* Fixed bug where user was able to create snippets with valid names that would crash the alphabetic overview. Snippet names are now limited to letters from English alphabet and the underscore. Fixes [SourceForge] bug #3120958. +* Fixed bug where snippets that have names beginning with a lower case letter were being omitted from from the associated list of snippets shown in the detail pane. Fixes [SourceForge] bug #3120962. +* Updated Snippets Editor topic in help file. ## Release v3.8.4 of 26 November 2010 -+ User can now opt to terminate the application when an unexpected exception is trapped. This implements [SourceForge] feature request #3074914. -+ Wording of bug report dialogue boxes changed. -+ Snippets selection dialogue box now displays wait cursor while waiting for it to be displayed. -+ Some corrections and clarifications made to comments that appear in generated "include" files. -+ Custom message boxes can now display custom title and icon. -+ Imported some updates from "new-backend" development tree: - - Some source code re-organisation and renaming. - - Updated some sorted list management code. - +* User can now opt to terminate the application when an unexpected exception is trapped. This implements [SourceForge] feature request #3074914. +* Wording of bug report dialogue boxes changed. +* Snippets selection dialogue box now displays wait cursor while waiting for it to be displayed. +* Some corrections and clarifications made to comments that appear in generated "include" files. +* Custom message boxes can now display custom title and icon. +* Imported some updates from "new-backend" development tree: + * Some source code re-organisation and renaming. + * Updated some sorted list management code. ## Release v3.8.3 of 24 November 2010 -+ Added button to "Compile" tab of Snippets Editor to display unit used to test compile snippets. This implements [SourceForge] feature request #3108008. -+ Fixed unreported bugs in handling of exceptions raised in threads. -+ Simplified method used to load database on start up. No longer uses a separate thread. -+ Overhauled and simplified code used to display "wait" dialogues during test compilations and database reloading. -+ Refactorings: - - Increased use of generics in lists and enumerators. - - Reorganised source code tree by moving some code to more relevant units, renaming some units and increasing use of namespaces. - - Removed some redundant code. -+ Updated help file re changes to snippets editor. - +* Added button to "Compile" tab of Snippets Editor to display unit used to test compile snippets. This implements [SourceForge] feature request #3108008. +* Fixed unreported bugs in handling of exceptions raised in threads. +* Simplified method used to load database on start up. No longer uses a separate thread. +* Overhauled and simplified code used to display "wait" dialogues during test compilations and database reloading. +* Refactorings: + * Increased use of generics in lists and enumerators. + * Reorganised source code tree by moving some code to more relevant units, renaming some units and increasing use of namespaces. + * Removed some redundant code. +* Updated help file re changes to snippets editor. ## Release v3.8.2 of 16 November 2010 -+ The position of the caret in the Snippets Editor's Extra Information control is now displayed. Implements [SourceForge] feature request #3105288. -+ Code that displays caret positions was refactored and improved. -+ Display of errors in the Snippets Editor's text edit controls has been improved in most cases either by positioning the caret near the error or selecting the erroneous text. This implements [SourceForge] feature request #3107042. -+ Made significant changes to code that parses REML mark-up: - - Rationalised error reporting and added support for reporting the position of errors. - - Fixed unreported bug that produced wrong error message when empty tags are encountered. - - Fixed [SourceForge] bug #3107982 that failed to report some unclosed tags as errors. - - Refactored and reorganised much of the code. -+ All encoding and decoding of URIs is now RFC 3986 compliant. -+ Refactored character detection and string encoding support code. -+ Renamed some units and classes. -+ Updated documentation. - +* The position of the caret in the Snippets Editor's Extra Information control is now displayed. Implements [SourceForge] feature request #3105288. +* Code that displays caret positions was refactored and improved. +* Display of errors in the Snippets Editor's text edit controls has been improved in most cases either by positioning the caret near the error or selecting the erroneous text. This implements [SourceForge] feature request #3107042. +* Made significant changes to code that parses REML mark-up: + * Rationalised error reporting and added support for reporting the position of errors. + * Fixed unreported bug that produced wrong error message when empty tags are encountered. + * Fixed [SourceForge] bug #3107982 that failed to report some unclosed tags as errors. + * Refactored and reorganised much of the code. +* All encoding and decoding of URIs is now RFC 3986 compliant. +* Refactored character detection and string encoding support code. +* Renamed some units and classes. +* Updated documentation. ## Release v3.8.1 of 08 November 2010 -+ Fixed [SourceForge] bug #3015589 where some user syntax highlighter settings were being ignored in main display. -+ Changed Test Unit view dialogue box to use user syntax highlighter settings. -+ Revised credits in About Box program tab. -+ Updated third party units: PJMD5 to v0.3, PJSysInfo to v3.3, PJVersionInfo to v3.3. -+ Modified version info code to use new features of new PJVersionInfo 3rd party unit. -+ Refactored code that parses XHTML-style code. -+ Updated compiler warnings used in project and made command line and IDE options the same. -+ Updated documentation. - +* Fixed [SourceForge] bug #3015589 where some user syntax highlighter settings were being ignored in main display. +* Changed Test Unit view dialogue box to use user syntax highlighter settings. +* Revised credits in About Box program tab. +* Updated third party units: PJMD5 to v0.3, PJSysInfo to v3.3, PJVersionInfo to v3.3. +* Modified version info code to use new features of new PJVersionInfo 3rd party unit. +* Refactored code that parses XHTML-style code. +* Updated compiler warnings used in project and made command line and IDE options the same. +* Updated documentation. ## Release v3.8.0 of 23 October 2010 -+ Added support for Delphi XE to program. Can now test compile and display results with Delphi XE. -+ Updated help file re Delphi XE support. -+ Some refactoring. -+ Standardised bug-trap and assertion failure exception messages. - +* Added support for Delphi XE to program. Can now test compile and display results with Delphi XE. +* Updated help file re Delphi XE support. +* Some refactoring. +* Standardised bug-trap and assertion failure exception messages. ## Release v3.7.0 of 23 September 2010 -+ Added new "Help | CodeSnip News" menu option that displays latest news about CodeSnip and the online database in a dialogue box. The news comes from the CodeSnip RSS news feed. -+ Removed news pane from "Update from Web" dialogue box and replaced with button that displays new "CodeSnip News" dialogue box. -+ Removed mailing list subscription facility: - - Removed subscription dialogue box and associated menu option. - - Removed code that accessed mailing list web service. - - Removed subscription option from program registration dialogue box. -+ Fixed a memory leak. -+ Added code that downloads XML document and reads and parses RSS feeds. -+ Refactored and improved HTTP request handling code. -+ Some further refactoring. -+ Updated help file re changes in this release. -+ Updated privacy statement. - +* Added new "Help | CodeSnip News" menu option that displays latest news about CodeSnip and the online database in a dialogue box. The news comes from the CodeSnip RSS news feed. +* Removed news pane from "Update from Web" dialogue box and replaced with button that displays new "CodeSnip News" dialogue box. +* Removed mailing list subscription facility: + * Removed subscription dialogue box and associated menu option. + * Removed code that accessed mailing list web service. + * Removed subscription option from program registration dialogue box. +* Fixed a memory leak. +* Added code that downloads XML document and reads and parses RSS feeds. +* Refactored and improved HTTP request handling code. +* Some further refactoring. +* Updated help file re changes in this release. +* Updated privacy statement. ## Release v3.6.3 of 22 July 2010 -+ Completely overhauled code that interacts with web services. - - Character encodings are now correctly handled per information in HTTP header and several different encodings are supported. - - MD5 checksums in HTTP headers are now supported. -+ Updated and corrected the contents of the About Box's "About The Program" Tab. -+ Some refactorings, mainly to code that uses MD5 message digests. -+ Attempts to compile source with Delphi 2009 and earlier are now prevented. -+ Updated documentation. - +* Completely overhauled code that interacts with web services. + * Character encodings are now correctly handled per information in HTTP header and several different encodings are supported. + * MD5 checksums in HTTP headers are now supported. +* Updated and corrected the contents of the About Box's "About The Program" Tab. +* Some refactorings, mainly to code that uses MD5 message digests. +* Attempts to compile source with Delphi 2009 and earlier are now prevented. +* Updated documentation. ## Release v3.6.2 of 18 June 2010 -+ Fixed source code formatting problem in code generator where "forward" declarations were sometimes preceded with an unwanted blank line. -+ Fixed potential bug in code that parses mark-up used for a snippet's Extra information. Symbolic entities were not case sensitive. -+ Fixed a memory leak. -+ Some refactorings that increase use of generics and some others. -+ Read-me file updated re v3.6.1 password changes. - +* Fixed source code formatting problem in code generator where "forward" declarations were sometimes preceded with an unwanted blank line. +* Fixed potential bug in code that parses mark-up used for a snippet's Extra information. Symbolic entities were not case sensitive. +* Fixed a memory leak. +* Some refactorings that increase use of generics and some others. +* Read-me file updated re v3.6.1 password changes. ## Release v3.6.1 of 01 June 2010 -+ Proxy server passwords can now contain any Unicode character, not just those included in the Windows-1252 code page. -+ Password format in per user ini file changed. Existing passwords have to be re-entered. Ini file format updated to v7. -+ Installer updated: - - It deletes any passwords from v6 and earlier per user ini files. - - Per-user ini file now stamped as v7. -+ Some potential Unicode-ANSI string conversion problems fixed. -+ Updated documentation. - +* Proxy server passwords can now contain any Unicode character, not just those included in the Windows-1252 code page. +* Password format in per user ini file changed. Existing passwords have to be re-entered. Ini file format updated to v7. +* Installer updated: + * It deletes any passwords from v6 and earlier per user ini files. + * Per-user ini file now stamped as v7. +* Some potential Unicode-ANSI string conversion problems fixed. +* Updated documentation. ## Release v3.6.0 of 26 May 2010 -+ Added support for emitting compiler directives to switch off specified warnings. This implement [SourceForge] feature request #2994485. -+ Preferences dialogue box updated: - - New "Code Generation" tab used to configure which if any warnings are to be inhibited. - - Renamed "Source Code" tab to "Code Formatting". -+ Added new tab to About Box that displays and enables exploration of some key directories used by CodeSnip. -+ Snippets editor now displays row and column occupied by text cursor. -+ Per user ini file format changed. It now supports code generation preferences. Ini file version updated to v6. -+ Installer updated: - - Ini files are stamped with correct program and ini file version information. - - Older versions (v1..v5) of per-user ini file are updated with default code generation preferences. - - Per-user ini file now stamped as v6. -+ Fixed numerous memory leaks. -+ Fixed some other potential and unreported minor bugs. -+ Some refactoring. -+ Updated help file re changes. - +* Added support for emitting compiler directives to switch off specified warnings. This implement [SourceForge] feature request #2994485. +* Preferences dialogue box updated: + * New "Code Generation" tab used to configure which if any warnings are to be inhibited. + * Renamed "Source Code" tab to "Code Formatting". +* Added new tab to About Box that displays and enables exploration of some key directories used by CodeSnip. +* Snippets editor now displays row and column occupied by text cursor. +* Per user ini file format changed. It now supports code generation preferences. Ini file version updated to v6. +* Installer updated: + * Ini files are stamped with correct program and ini file version information. + * Older versions (v1..v5) of per-user ini file are updated with default code generation preferences. + * Per-user ini file now stamped as v6. +* Fixed numerous memory leaks. +* Fixed some other potential and unreported minor bugs. +* Some refactoring. +* Updated help file re changes. ## Release v3.5.5 of 24 March 2010 -+ Fixes download stream read [SourceForge] bug #2976048. - +* Fixes download stream read [SourceForge] bug #2976048. ## Release v3.5.4 of 18 March 2010 -+ Temporary fix for download error checking [SourceForge] bug #2970055. -+ Fixed https protocol [SourceForge] bug #2970896. - +* Temporary fix for download error checking [SourceForge] bug #2970055. +* Fixed https protocol [SourceForge] bug #2970896. ## Release v3.5.3 of 08 March 2010 -+ Fixed database download error checking [SourceForge] bug #2964767. -+ Updated PayPal donations narrative on welcome page. - +* Fixed database download error checking [SourceForge] bug #2964767. +* Updated PayPal donations narrative on welcome page. ## Release v3.5.2 of 22 February 2010 -+ Changed database downloader to: - - Use web service's revised download file format - - Validate download data before updating local database. - - Provide better download error messages. -+ Fixed [SourceForge] bug #2947794 in view link dialogue box. -+ Refactored some exception handling code. - +* Changed database downloader to: + * Use web service's revised download file format + * Validate download data before updating local database. + * Provide better download error messages. +* Fixed [SourceForge] bug #2947794 in view link dialogue box. +* Refactored some exception handling code. ## Release v3.5.1 of 09 February 2010 -+ New Unicode build of the program compiled with Delphi 2010. File I/O remains ANSI. -+ Windows NT is no longer supported. Windows 2000 is now the minimum OS. Set-up program changed to enforce this. -+ More rigorous enforcement of rules for REML tag attributes used in a snippet's Extra information. -+ Fixed a couple of minor UI problems in the Proxy Server and Trapped Bug Report dialogue boxes. -+ Minor changes to HTML and embedded browser code. -+ Some refactoring. -+ Updated documentation. - +* New Unicode build of the program compiled with Delphi 2010. File I/O remains ANSI. +* Windows NT is no longer supported. Windows 2000 is now the minimum OS. Set-up program changed to enforce this. +* More rigorous enforcement of rules for REML tag attributes used in a snippet's Extra information. +* Fixed a couple of minor UI problems in the Proxy Server and Trapped Bug Report dialogue boxes. +* Minor changes to HTML and embedded browser code. +* Some refactoring. +* Updated documentation. ## Release v3.5.0 of 16 January 2010 -+ Overview pane can now be configured using Preferences dialogue box to start up with all sections collapsed. -+ Reference to ability to donate by credit / debit card removed from Donate dialogue box: now PayPal only. -+ Help file updated re above changes. -+ Minor refactoring of code that provides information about and renders source code comments. - +* Overview pane can now be configured using Preferences dialogue box to start up with all sections collapsed. +* Reference to ability to donate by credit / debit card removed from Donate dialogue box: now PayPal only. +* Help file updated re above changes. +* Minor refactoring of code that provides information about and renders source code comments. ## Release v3.4.8 of 10 January 2010 -+ Made some changes to key presses responded to by overview pane and fixed bug where Alt+F4 was not closing program when pane had focus. -+ Made some changes to hints displayed when rolling over links in compiler check pane. Also removed pop-up windows describing compiler errors. -+ Updated help file: noted Delphi 2010 compiler support and added new information about overview pane keyboard short-cuts. - +* Made some changes to key presses responded to by overview pane and fixed bug where Alt+F4 was not closing program when pane had focus. +* Made some changes to hints displayed when rolling over links in compiler check pane. Also removed pop-up windows describing compiler errors. +* Updated help file: noted Delphi 2010 compiler support and added new information about overview pane keyboard short-cuts. ## Release v3.4.7 of 31 December 2009 -+ Added IE version number to OS information submitted when program is registered. -+ Program now displays "[localhost]" in main window caption when started with -localhost switch. -+ All text edit controls in snippets editor now have custom pop-up menus and short-cuts for "cut", "copy", "paste", "select all" and "undo" now work. -+ Refactored code that supports use of fonts. -+ Updated privacy statement re registration changes. - +* Added IE version number to OS information submitted when program is registered. +* Program now displays "[localhost]" in main window caption when started with -localhost switch. +* All text edit controls in snippets editor now have custom pop-up menus and short-cuts for "cut", "copy", "paste", "select all" and "undo" now work. +* Refactored code that supports use of fonts. +* Updated privacy statement re registration changes. ## Release v3.4.6 of 18 November 2009 -+ Changed code that takes a security backup of main database during updates to store backup in a single file rather as separate files in a temporary folder. This should fix [SourceForge] bug #2898687. -+ Slightly modified user database backup file format to match that now used for main database backup. -+ Fixed potential bugs: - - Code that performs busy waits could have caused program to freeze. - - Negative numbers written to backup files were not being written correctly. - - A garbled error message was corrected. - +* Changed code that takes a security backup of main database during updates to store backup in a single file rather as separate files in a temporary folder. This should fix [SourceForge] bug #2898687. +* Slightly modified user database backup file format to match that now used for main database backup. +* Fixed potential bugs: + * Code that performs busy waits could have caused program to freeze. + * Negative numbers written to backup files were not being written correctly. + * A garbled error message was corrected. ## Release v3.4.5 of 09 November 2009 -+ Home, Ctrl+Home, End and Ctrl+End keys now work in overview pane and go to first and last item in tree view respectively per [SourceForge] feature request #2888880. -+ State of tree view in overview pane is now maintained after editing the user database: the tree is no longer always fully expanded after each edit. -+ Removed "Properties" button from print dialogue box along with associated dialogue boxes. This option has always been buggy. This "fixes" [SourceForge] bug #2868706. -+ Fixed unreported makefile bug. - +* Home, Ctrl+Home, End and Ctrl+End keys now work in overview pane and go to first and last item in tree view respectively per [SourceForge] feature request #2888880. +* State of tree view in overview pane is now maintained after editing the user database: the tree is no longer always fully expanded after each edit. +* Removed "Properties" button from print dialogue box along with associated dialogue boxes. This option has always been buggy. This "fixes" [SourceForge] bug #2868706. +* Fixed unreported makefile bug. ## Release v3.4.4 of 21 October 2009 -+ Changed bug reporting mechanism. Bugs are now reported via the on-line bug tracker. Bug report dialogues changed accordingly. Access to the old bug report web service was removed. -+ Added two new default syntax highlighter styles: "Visual Studio" and "No Highlighter". The latter switches off syntax highlighting. -+ Fixed [SourceForge] bug #2882331. This was a bug in the syntax highlighter that occurred when an unexpected character was encountered. -+ Updated help file re changes. -+ Some minor source code corrections. - +* Changed bug reporting mechanism. Bugs are now reported via the on-line bug tracker. Bug report dialogues changed accordingly. Access to the old bug report web service was removed. +* Added two new default syntax highlighter styles: "Visual Studio" and "No Highlighter". The latter switches off syntax highlighting. +* Fixed [SourceForge] bug #2882331. This was a bug in the syntax highlighter that occurred when an unexpected character was encountered. +* Updated help file re changes. +* Some minor source code corrections. ## Release v3.4.3 of 19 October 2009 -+ User's OS is now reported and recorded when program is registered. -+ Text displayed in preview dialogue boxes can now be scrolled horizontally. -+ Added support for building source against later releases of Indy 10 components. -+ Help file and privacy statement updated. -+ Further updated third party GIF image handling code to latest release. -+ Some changes to source code project options. - +* User's OS is now reported and recorded when program is registered. +* Text displayed in preview dialogue boxes can now be scrolled horizontally. +* Added support for building source against later releases of Indy 10 components. +* Help file and privacy statement updated. +* Further updated third party GIF image handling code to latest release. +* Some changes to source code project options. ## Release v3.4.2 of 10 October 2009 -+ Fixed [SourceForge] bugs #2868706 and #2875857. -+ Updated GIF image handling code. - +* Fixed [SourceForge] bugs #2868706 and #2875857. +* Updated GIF image handling code. ## Release v3.4.1 of 29 September 2009 -+ All dialogue boxes that request a user's name and / or email address now remember the information last entered, to save retyping the same data. -+ Changed to use Indy Internet Components v10 instead of v9 for net access. -+ Refactored: - - Code that stores information about a user. - - Code that gets details of system folders on local system and other file system related code. -+ Updated privacy statement (text file and in help file). - +* All dialogue boxes that request a user's name and / or email address now remember the information last entered, to save retyping the same data. +* Changed to use Indy Internet Components v10 instead of v9 for net access. +* Refactored: + * Code that stores information about a user. + * Code that gets details of system folders on local system and other file system related code. +* Updated privacy statement (text file and in help file). ## Release v3.4 of 24 September 2009 -+ Added support for Delphi 2010 to program. Can now test compile and display results with Delphi 2010. -+ Bug fixes: - - "Invalid cast" error that occasionally appears when a snippet is updated. - - Bug that kept backup files locked open after restoring a database backup. - - Current selection is now displayed in Alphabetic and Snippet Kind tabs of overview pane: previously all the database was shown, regardless of search. - +* Added support for Delphi 2010 to program. Can now test compile and display results with Delphi 2010. +* Bug fixes: + * "Invalid cast" error that occasionally appears when a snippet is updated. + * Bug that kept backup files locked open after restoring a database backup. + * Current selection is now displayed in Alphabetic and Snippet Kind tabs of overview pane: previously all the database was shown, regardless of search. ## Release v3.3 of 21 September 2009 -+ Added support for user defined categories which can now be added, renamed or deleted. -+ Made changes to snippets editor: - - On the "Compile Results" Tab, a single simplified list box is now used to both display and change compile results. This replaces two linked controls. - - The text case of a snippet name can now be changed without causing a duplicate name error. - - Some controls resized. -+ Fixed bug where attempting to overwrite files that are in use caused the bug report dialogue box to appear instead of simply reporting the problem. -+ Improved validity checking of snippets that are included in generated source code. -+ Help file updated. -+ Refactored: - - UI handling code in snippets editor. - - Some Snippets database and validation code. - +* Added support for user defined categories which can now be added, renamed or deleted. +* Made changes to snippets editor: + * On the "Compile Results" Tab, a single simplified list box is now used to both display and change compile results. This replaces two linked controls. + * The text case of a snippet name can now be changed without causing a duplicate name error. + * Some controls resized. +* Fixed bug where attempting to overwrite files that are in use caused the bug report dialogue box to appear instead of simply reporting the problem. +* Improved validity checking of snippets that are included in generated source code. +* Help file updated. +* Refactored: + * UI handling code in snippets editor. + * Some Snippets database and validation code. ## Release v3.2.3 of 14 September 2009 -+ Fixed bug in "update from web" dialogue box where most up to date news item was not being displayed. -+ Dialogue boxes that that enable selection of categories and snippets by means of tree views and associated check boxes now sort categories by description. -+ Categories and snippet kinds displayed in the snippets editor are now sorted by description. -+ Refactored: - - Code that displays tree views in overview pane and snippet selection dialogues. - - Some list management code. - - Some snippets editor code. - +* Fixed bug in "update from web" dialogue box where most up to date news item was not being displayed. +* Dialogue boxes that that enable selection of categories and snippets by means of tree views and associated check boxes now sort categories by description. +* Categories and snippet kinds displayed in the snippets editor are now sorted by description. +* Refactored: + * Code that displays tree views in overview pane and snippet selection dialogues. + * Some list management code. + * Some snippets editor code. ## Release v3.2.2 of 08 September 2009 -+ Fixed bug in check list boxes where moving selection using keyboard causes check state to be toggled. -+ Custom colours used in colour dialogue, on syntax highlighter page of preferences dialogue box, are now persistent. -+ Re-implemented code that displays pop-up menus in detail pane, and fixed a minor glitch as a side effect. -+ Simplified code that manages help system. -+ Refactored code that manages and customises common dialogues. - +* Fixed bug in check list boxes where moving selection using keyboard causes check state to be toggled. +* Custom colours used in colour dialogue, on syntax highlighter page of preferences dialogue box, are now persistent. +* Re-implemented code that displays pop-up menus in detail pane, and fixed a minor glitch as a side effect. +* Simplified code that manages help system. +* Refactored code that manages and customises common dialogues. ## Release v3.2.1 of 24 August 2009 -+ Appearance of comments that appear at the top of generated source code was changed. -+ Slightly modified "license" that appears at the top of some generated units. -+ Information about contributor of imported code is now appended to snippet's "extra" information. -+ Added a garbage collector. -+ Changed size of About box - now wider and credits scroll boxes are now taller. Added credit for encryption code. -+ Fixed minor bug that could display a JavaScript error dialogue if an exception occurred in an action initiated by clicking a link in the main display. -+ Numerous refactorings. - +* Appearance of comments that appear at the top of generated source code was changed. +* Slightly modified "license" that appears at the top of some generated units. +* Information about contributor of imported code is now appended to snippet's "extra" information. +* Added a garbage collector. +* Changed size of About box - now wider and credits scroll boxes are now taller. Added credit for encryption code. +* Fixed minor bug that could display a JavaScript error dialogue if an exception occurred in an action initiated by clicking a link in the main display. +* Numerous refactorings. ## Release v3.2 of 17 August 2009 -+ Added facility for CodeSnip to use a proxy server when accessing the Internet. -+ Provided a new dialogue box to configure any proxy server. -+ Updated help file re proxy server support and configuration. -+ UI is no longer frozen while web services are executing requests. "Update from Web" dialogue box changed to indicate if cancel button pressed when a web request is executing. -+ Product version reported in generated source code header comments, splash screen and about box now includes any special build information. -+ Some minor code tweaks and refactoring. - +* Added facility for CodeSnip to use a proxy server when accessing the Internet. +* Provided a new dialogue box to configure any proxy server. +* Updated help file re proxy server support and configuration. +* UI is no longer frozen while web services are executing requests. "Update from Web" dialogue box changed to indicate if cancel button pressed when a web request is executing. +* Product version reported in generated source code header comments, splash screen and about box now includes any special build information. +* Some minor code tweaks and refactoring. ## Release v3.1.1 of 15 August 2009 -+ Check list boxes throughout program changed so that clicking anywhere on an item toggles check state. -+ Button used to render and display extra information in snippets editor is now disabled when there is no extra information to display. -+ Made minor changes to layout of some dialogue boxes: replaced missing text in bug report dialogue box. -+ Some refactorings. - +* Check list boxes throughout program changed so that clicking anywhere on an item toggles check state. +* Button used to render and display extra information in snippets editor is now disabled when there is no extra information to display. +* Made minor changes to layout of some dialogue boxes: replaced missing text in bug report dialogue box. +* Some refactorings. ## Release v3.1 of 11 August 2009 -+ Added a button to the snippets editor to preview an HTML rendering of the mark-up entered as extra information. Includes facility to check any links in the mark-up. - +* Added a button to the snippets editor to preview an HTML rendering of the mark-up entered as extra information. Includes facility to check any links in the mark-up. ## Release v3.0.5 of 21 July 2009 -+ Default font is now dependent on underlying OS: Vista - Segoe UI, XP/2000 - Tahoma, NT - MS Sans Serif. -+ Some dialogues and splash screen modified to accommodate OS font, in particular larger Vista font. Some also given a light makeover. - +* Default font is now dependent on underlying OS: Vista - Segoe UI, XP/2000 - Tahoma, NT - MS Sans Serif. +* Some dialogues and splash screen modified to accommodate OS font, in particular larger Vista font. Some also given a light makeover. ## Release v3.0.4 of 13 July 2009 -+ Added a snippet's category description to main display and to snippet information copied to clipboard or printed. Category description in main display can be clicked to display the category. -+ Refactored code that displays clicked routines and code that displays a snippet for editing. - +* Added a snippet's category description to main display and to snippet information copied to clipboard or printed. Category description in main display can be clicked to display the category. +* Refactored code that displays clicked routines and code that displays a snippet for editing. ## Unreleased v3.0.3 of 12 July 2009 -+ Refactored code: - - Rationalised some JavaScript code. - - Rationalised some dialogue alignment code. - - Changed some object types and class hierarchies. - - Added some automatic object lifetime management logic. - - Removed some duplicate code and merged some units. -+ Fixed an obscure bug in category code snippet generation as a side effect of refactoring. - +* Refactored code: + * Rationalised some JavaScript code. + * Rationalised some dialogue alignment code. + * Changed some object types and class hierarchies. + * Added some automatic object lifetime management logic. + * Removed some duplicate code and merged some units. +* Fixed an obscure bug in category code snippet generation as a side effect of refactoring. ## Release v3.0.2 of 08 July 2009 -+ Fixed broken link to CodeSnip database in welcome page. -+ Fixed bug in selection search that was selecting both user defined and main database snippets with same name if only one was selected. -+ Fixed bug where units required by constants and type definitions were not being added to generated units. - +* Fixed broken link to CodeSnip database in welcome page. +* Fixed bug in selection search that was selecting both user defined and main database snippets with same name if only one was selected. +* Fixed bug where units required by constants and type definitions were not being added to generated units. ## Release v3.0.1 of 06 July 2009 -+ Added support for file:// protocol in links in a snippet's extra information. -+ Updated help file re changes to extra info. -+ Updated exported code and user database file formats to v4 to accommodate revised extra information, although we now save data in v3 format if possible. - +* Added support for file:// protocol in links in a snippet's extra information. +* Updated help file re changes to extra info. +* Updated exported code and user database file formats to v4 to accommodate revised extra information, although we now save data in v3 format if possible. ## Release v3.0 of 29 June 2009 -+ Added support for constants and type definition snippets: there are now four types of snippets - routines, constants, types and free-form (which don't conform to any format). Free-form snippets cannot be included in generated units. -+ Further formatting instructions added to the active text used in database's Extra information field. Also added a contributors field to database. -+ Three predefined syntax highlighters are now provided, with default changing to Delphi 2006 default style. Syntax highlighting used in main display is now customisable. Highlighter keyword list updated. -+ Main display changed: - - Test unit is no longer displayed in compiler check pane: it's now displayed in a dialogue box. - - Compiler check pane's font changed to true type, with face depending on OS. - - Information pane now hides compiler table when a free-form snippet is displayed. - - Compiler check pane now displays special "not available" pages when no compilers installed or a free-form snippet or a section header is selected. - - "Uncategorised" tab removed from overview pane and replaced with new "Alphabetical" tab that groups snippets by initial letter and "Snippet Kind" tab that groups snippets by kind. - - "Section" nodes in overview pane can now be expanded and collapsed: pane now has toolbar to perform these actions. - - "Edit snippet" links displayed in information pane are now also displayed in compiler check pane. - - Information about snippet type added to information pane. - - Context menu added to overview pane. - - Some changes to menu glyphs and short-cut keys. - - Welcome display modified and now has a link to the donate dialogue box. -+ Added option to copy an snippet's source code to clipboard in text and RTF formats. -+ Exporting and copying of snippets complete with descriptions and cross references is restricted to routines: not supported for free-form, types and constants. -+ Improved detection of invalid dependencies in snippets, including circular dependencies, and provided option to view all dependencies for any snippet from main window and snippets editor. -+ Revised content of many dialogue boxes etc to refer to "snippets" instead of "routines" where necessary. -+ Changed format and location of user-defined database and format of exported and submitted files. -+ Added new "Imported Snippets" category that receives imported routines: they were formerly imported into the "User Defined" category -+ Modified code that reads main database to deal with revised file format for new snippets types and introduction of pre-processor instructions to enable retrofitting of new snippets without breaking earlier versions of CodeSnip. -+ Changed name and location of user preference configuration file. -+ Revised external object that communicates with JavaScript in browser controls. -+ Updated program credits in about box, restyled and widened it. -+ Changed size of preferences dialogue box and revised syntax highlighter tab. -+ Changed captions in preview dialogue box. -+ Changed appearance of splash screen. -+ Modified snippets editor to work with new snippet types, improved error checking code and prevented test compilation of free-form snippets. -+ Speeded up loading of main database. -+ Added an Easter egg! -+ A few refactorings. -+ Fixed several bugs: - - Bug in backup files including database files larger than 32Kb was fixed. - - Bug in history list following editing user defined snippets fixed by clearing list after snippets have been edited. - - Imported user defined routines no longer forget any dependencies on main database snippets. - - Occasional bug in displaying test unit fixed by displaying test unit in dialogue box instead of main display. -+ Modified installer re new folder structure and copying over data from previous versions. -+ Revised help file to reflect changes. Added new main contents "chapter" about the various snippet types. - +* Added support for constants and type definition snippets: there are now four types of snippets - routines, constants, types and free-form (which don't conform to any format). Free-form snippets cannot be included in generated units. +* Further formatting instructions added to the active text used in database's Extra information field. Also added a contributors field to database. +* Three predefined syntax highlighters are now provided, with default changing to Delphi 2006 default style. Syntax highlighting used in main display is now customisable. Highlighter keyword list updated. +* Main display changed: + * Test unit is no longer displayed in compiler check pane: it's now displayed in a dialogue box. + * Compiler check pane's font changed to true type, with face depending on OS. + * Information pane now hides compiler table when a free-form snippet is displayed. + * Compiler check pane now displays special "not available" pages when no compilers installed or a free-form snippet or a section header is selected. + * "Uncategorised" tab removed from overview pane and replaced with new "Alphabetical" tab that groups snippets by initial letter and "Snippet Kind" tab that groups snippets by kind. + * "Section" nodes in overview pane can now be expanded and collapsed: pane now has toolbar to perform these actions. + * "Edit snippet" links displayed in information pane are now also displayed in compiler check pane. + * Information about snippet type added to information pane. + * Context menu added to overview pane. + * Some changes to menu glyphs and short-cut keys. + * Welcome display modified and now has a link to the donate dialogue box. +* Added option to copy an snippet's source code to clipboard in text and RTF formats. +* Exporting and copying of snippets complete with descriptions and cross references is restricted to routines: not supported for free-form, types and constants. +* Improved detection of invalid dependencies in snippets, including circular dependencies, and provided option to view all dependencies for any snippet from main window and snippets editor. +* Revised content of many dialogue boxes etc to refer to "snippets" instead of "routines" where necessary. +* Changed format and location of user-defined database and format of exported and submitted files. +* Added new "Imported Snippets" category that receives imported routines: they were formerly imported into the "User Defined" category +* Modified code that reads main database to deal with revised file format for new snippets types and introduction of pre-processor instructions to enable retrofitting of new snippets without breaking earlier versions of CodeSnip. +* Changed name and location of user preference configuration file. +* Revised external object that communicates with JavaScript in browser controls. +* Updated program credits in about box, restyled and widened it. +* Changed size of preferences dialogue box and revised syntax highlighter tab. +* Changed captions in preview dialogue box. +* Changed appearance of splash screen. +* Modified snippets editor to work with new snippet types, improved error checking code and prevented test compilation of free-form snippets. +* Speeded up loading of main database. +* Added an Easter egg! +* A few refactorings. +* Fixed several bugs: + * Bug in backup files including database files larger than 32Kb was fixed. + * Bug in history list following editing user defined snippets fixed by clearing list after snippets have been edited. + * Imported user defined routines no longer forget any dependencies on main database snippets. + * Occasional bug in displaying test unit fixed by displaying test unit in dialogue box instead of main display. +* Modified installer re new folder structure and copying over data from previous versions. +* Revised help file to reflect changes. Added new main contents "chapter" about the various snippet types. ## Unreleased v2.4.1 of 13 May 2009 -+ Refactored code that provides information about the program and web URLs and services it accesses. -+ Changed URL accessed by donations dialogue box. - +* Refactored code that provides information about the program and web URLs and services it accesses. +* Changed URL accessed by donations dialogue box. ## Release v2.4 of 11 May 2009 -+ Added donate menu option and dialogue that accesses a PayPal donation web page. -+ Removed support for the Windows 9x platform since CodeSnip now generates fatal errors on that platform: - - Removed Windows 9x specific code. - - Changed installer to prevent installation on Windows 9x. -+ Updated help file re changes. - +* Added donate menu option and dialogue that accesses a PayPal donation web page. +* Removed support for the Windows 9x platform since CodeSnip now generates fatal errors on that platform: + * Removed Windows 9x specific code. + * Changed installer to prevent installation on Windows 9x. +* Updated help file re changes. ## Release v2.3.7 of 26 April 2009 -+ Made user name and email address entered in Code Submission Wizard persistent on a per-user basis. -+ Updated Code Submission Wizard and Privacy Statement help topics re the changes. - +* Made user name and email address entered in Code Submission Wizard persistent on a per-user basis. +* Updated Code Submission Wizard and Privacy Statement help topics re the changes. ## Unreleased v2.3.6 of 26 January 2009 -+ Changed method that is used to get locale information to be compatible with Vista as well as earlier OSs. - +* Changed method that is used to get locale information to be compatible with Vista as well as earlier OSs. ## Release v2.3.5 of 25 January 2009 -+ Changed method used to generate HTML displayed in main program window to avoid dynamic updating of documents in attempt to counter a reported JavaScript bug. -+ Refactored generation of HTML tags in all parts of program that use HTML in display. -+ Corrected method naming error. - +* Changed method used to generate HTML displayed in main program window to avoid dynamic updating of documents in attempt to counter a reported JavaScript bug. +* Refactored generation of HTML tags in all parts of program that use HTML in display. +* Corrected method naming error. ## Unreleased v2.3.4 of 16 January 2009 -+ Copy Source Code menu item now places a copy of selected snippet on clipboard in syntax highlighted rich text in addition to plain text. -+ Updated help file accordingly. - +* Copy Source Code menu item now places a copy of selected snippet on clipboard in syntax highlighted rich text in addition to plain text. +* Updated help file accordingly. ## Unreleased v2.3.3 of 14 January 2009 -+ Browser controls and snippets tree-views are now selected when containing frame is entered. -+ Discrepancy in way highlighting works in snippets tree-views fixed. -+ "&" characters are now rendered correctly in TMessageBox dialogues. -+ Code that executes compilers now uses one thread instead of two. -+ Refactorings: - - Some constants relocated. - - Rationalised some routine and method calls. - - Replaced some control character literals with constants. - - Updated IntfUIHandlers unit with IE6/7 related constants. - +* Browser controls and snippets tree-views are now selected when containing frame is entered. +* Discrepancy in way highlighting works in snippets tree-views fixed. +* "&" characters are now rendered correctly in TMessageBox dialogues. +* Code that executes compilers now uses one thread instead of two. +* Refactorings: + * Some constants relocated. + * Rationalised some routine and method calls. + * Replaced some control character literals with constants. + * Updated IntfUIHandlers unit with IE6/7 related constants. ## Unreleased v2.3.2 of 10 January 2009 -+ Revised compilers object. Singleton instance removed. Local instances of object are created where needed. -+ Added new method to compiler objects to detect errors and warnings -+ Fixed incorrect caption in compiler error dialogue. -+ Added new object that manages test compilations, compiler set-up and viewing compile errors. Used by main form and snippets editor. -+ Added "View Compile Errors" menu option to Database menu. -+ Added Alt+V hot key to view compile errors in Snippets editor. -+ Updated help file for database menu to add "View Compile Errors" and missing entries for Submission, import and export of user database. - +* Revised compilers object. Singleton instance removed. Local instances of object are created where needed. +* Added new method to compiler objects to detect errors and warnings +* Fixed incorrect caption in compiler error dialogue. +* Added new object that manages test compilations, compiler set-up and viewing compile errors. Used by main form and snippets editor. +* Added "View Compile Errors" menu option to Database menu. +* Added Alt+V hot key to view compile errors in Snippets editor. +* Updated help file for database menu to add "View Compile Errors" and missing entries for Submission, import and export of user database. ## Unreleased v2.3.1 of 06 January 2009 -+ Fixed test compilation bug in snippets editor that could corrupt compiler errors or warnings displayed from main display. -+ Added support for tab switching in compiler errors dialogue box using Ctrl+Tab and Shift+Ctrl+Tab. - +* Fixed test compilation bug in snippets editor that could corrupt compiler errors or warnings displayed from main display. +* Added support for tab switching in compiler errors dialogue box using Ctrl+Tab and Shift+Ctrl+Tab. ## Release v2.3 of 05 January 2009 -+ Changed name of Copy Snippet menu item to Copy Source Code. -+ Added new Copy Information menu item to Copy menu - copies all snippet information to clipboard in text and RTF. -+ Added Save Database button to toolbar. -+ Changed status bar to display a count of user defined routines and an indicator that shows when user database has been modified. -+ Refactored and extended clipboard management code. -+ Added new buttons to selection search dialogue box to select user defined or main database routines. -+ Added facility to test compile routines to user defined snippets editor dialogue box. -+ Modified compiler errors dialogue box to be able to display results of compilation with more than one compiler. -+ Updated help file re changes. - +* Changed name of Copy Snippet menu item to Copy Source Code. +* Added new Copy Information menu item to Copy menu - copies all snippet information to clipboard in text and RTF. +* Added Save Database button to toolbar. +* Changed status bar to display a count of user defined routines and an indicator that shows when user database has been modified. +* Refactored and extended clipboard management code. +* Added new buttons to selection search dialogue box to select user defined or main database routines. +* Added facility to test compile routines to user defined snippets editor dialogue box. +* Modified compiler errors dialogue box to be able to display results of compilation with more than one compiler. +* Updated help file re changes. ## Release v2.2.5 of 31 December 2008 -+ Replaced routine's credits and comments properties with new Extra information property that can store formatted text. -+ Added parser for mark-up language used by new Extra property. -+ Modified snippets edit dialogue box to use new Extra property. -+ Changed main database, user database and export file format to support new Extra property. User database and export files generated by this version can't be read by earlier versions of the program. -+ Modified and refactored print document generation code to use new Extra property. -+ Refactored some HTML generation code. -+ Fixed a bug that occasionally causes a GPF when updating a user defined routine. -+ Removed redundant topic from help file. - +* Replaced routine's credits and comments properties with new Extra information property that can store formatted text. +* Added parser for mark-up language used by new Extra property. +* Modified snippets edit dialogue box to use new Extra property. +* Changed main database, user database and export file format to support new Extra property. User database and export files generated by this version can't be read by earlier versions of the program. +* Modified and refactored print document generation code to use new Extra property. +* Refactored some HTML generation code. +* Fixed a bug that occasionally causes a GPF when updating a user defined routine. +* Removed redundant topic from help file. ## Release v2.2.4 of 19 December 2008 -+ Fixed bug in text and RTF preview dialogue boxes that was setting margins incorrectly and clipping displayed text. - +* Fixed bug in text and RTF preview dialogue boxes that was setting margins incorrectly and clipping displayed text. ## Unreleased v2.2.3 of 17 December 2008 -+ Refactored code that handles XML files (user database and import / export). Pulled out common code and further extended XML document object. - +* Refactored code that handles XML files (user database and import / export). Pulled out common code and further extended XML document object. ## Unreleased v2.2.2 of 16 December 2008 -+ Added glyphs to printers in print dialogue box. -+ Various refactorings of print and highlighting related code. -+ Printing now uses user-defined highlighters. Current highlighting is now previewed in print preferences. -+ Bug fixes: - - Help button now displays in page set-up dialogue on Vista. - - Page set-up dialogue now makes use of custom margin settings. - +* Added glyphs to printers in print dialogue box. +* Various refactorings of print and highlighting related code. +* Printing now uses user-defined highlighters. Current highlighting is now previewed in print preferences. +* Bug fixes: + * Help button now displays in page set-up dialogue on Vista. + * Page set-up dialogue now makes use of custom margin settings. ## Unreleased v2.2.1 of 16 December 2008 -+ Several refactorings: - - Rationalised email validation code. - - Rationalised exceptions raised when validating entry into dialogue box. - - Rationalised code that momentarily pauses execution of a thread. - - Made wide use of extended TRect structure. - - Changed various loops to use enumerators. - - Removed some unused code. -+ Fixed minor bug in open and save dialogues that occasionally failed to detect existence of a file. - +* Several refactorings: + * Rationalised email validation code. + * Rationalised exceptions raised when validating entry into dialogue box. + * Rationalised code that momentarily pauses execution of a thread. + * Made wide use of extended TRect structure. + * Changed various loops to use enumerators. + * Removed some unused code. +* Fixed minor bug in open and save dialogues that occasionally failed to detect existence of a file. ## Unreleased v2.2 of 15 December 2008 -+ Added facility to submit user defined snippets via Internet for inclusion in main database. -+ Added facility to export user defined routines to file and to import exported files. -+ Made minor changes to wizard dialogue boxes. -+ Rewrote message dialogue box code. -+ Made minor changes to open and save dialogue boxes. -+ Updated help file for the new code import, export and submission features. - +* Added facility to submit user defined snippets via Internet for inclusion in main database. +* Added facility to export user defined routines to file and to import exported files. +* Made minor changes to wizard dialogue boxes. +* Rewrote message dialogue box code. +* Made minor changes to open and save dialogue boxes. +* Updated help file for the new code import, export and submission features. ## Release v2.1 of 11 October 2008 -+ Added support for Delphi 2009 Win 32 personality. -+ Added a button to set all compiler results to success to snippets edit dialogue box. -+ Refactored some code. -+ Updated help file re Delphi 2009 support. - +* Added support for Delphi 2009 Win 32 personality. +* Added a button to set all compiler results to success to snippets edit dialogue box. +* Refactored some code. +* Updated help file re Delphi 2009 support. ## Unreleased v2.0.7 of 05 October 2008 -+ Fixed residual bug in alt key bug work-around (CodeGear Quality Central bug report #374030). The bug was manifesting itself only for the first dialogue box displayed after the program started. - +* Fixed residual bug in alt key bug work-around (CodeGear Quality Central bug report #374030). The bug was manifesting itself only for the first dialogue box displayed after the program started. ## Unreleased v2.0.6 of 05 October 2008 -+ Refactoring: - - Added class methods to instantiate and use various classes that have only one public method to save caller having to create, execute and destroy object. Public constructors of these classes were changed to cause assertion failure if directly called. - - Made static classes derive from new base class that causes assertion failure if constructor called. - - Combined some action update handlers in main form. - - Updated assertions and raising of EBug exceptions to programatically get name of class triggering error. - - Made some class' protected and private sections strict. - +* Refactoring: + * Added class methods to instantiate and use various classes that have only one public method to save caller having to create, execute and destroy object. Public constructors of these classes were changed to cause assertion failure if directly called. + * Made static classes derive from new base class that causes assertion failure if constructor called. + * Combined some action update handlers in main form. + * Updated assertions and raising of EBug exceptions to programatically get name of class triggering error. + * Made some class' protected and private sections strict. ## Unreleased v2.0.5 of 03 October 2008 -+ Refactoring: changed custom save source dialogue to descend from extended save dialogue box. - +* Refactoring: changed custom save source dialogue to descend from extended save dialogue box. ## Release v2.0.4 of 21 September 2008 -+ Improved speed of looking up routines in database. -+ Prevented any user defined routine from referencing itself. -+ User defined routines now always reference routines from user database in preference to main database when there is a name conflict. - +* Improved speed of looking up routines in database. +* Prevented any user defined routine from referencing itself. +* User defined routines now always reference routines from user database in preference to main database when there is a name conflict. ## Unreleased v2.0.3 of 20 September 2008 -+ Fixed bug that caused an assertion failure when an attempt was made to display the Select Routines dialogue box when an empty category was present in database. - +* Fixed bug that caused an assertion failure when an attempt was made to display the Select Routines dialogue box when an empty category was present in database. ## Unreleased v2.0.2 of 19 September 2008 -+ Now gives option to save changed user defined database before updated main database. -+ When a routine is updated or deleted references to it in other routines are updated or removed. -+ Corrected reference in installer to menu item used to update database (this changed from v2). - +* Now gives option to save changed user defined database before updated main database. +* When a routine is updated or deleted references to it in other routines are updated or removed. +* Corrected reference in installer to menu item used to update database (this changed from v2). ## Release v2.0.1 of 18 September 2008 -+ Fixed bug that fails to load user database and deletes it if a category is added to main database during on-line update. -+ Fixed bug that ignores any user defined snippets that have same name as snippets in main database. -+ Ensured main form is disabled when database is loading. -+ Ensured splash form is hidden if an exception occurs while splash form is displayed. - +* Fixed bug that fails to load user database and deletes it if a category is added to main database during on-line update. +* Fixed bug that ignores any user defined snippets that have same name as snippets in main database. +* Ensured main form is disabled when database is loading. +* Ensured splash form is hidden if an exception occurs while splash form is displayed. ## Release v2.0 of 15 September 2008 -+ Added support for user defined snippets: - - User database can be edited, saved, backed-up and restored. - - User database can reference code in main database. - - Names of user defined snippets are coloured blue to distinguish them from main database. - - User database is stored as a mix of XML and source files in a sub-folder of the per-user application data folder. - - Queries can now be refreshed when content of user database changes. -+ Modified extended external object that communicates between browser controls and application. -+ Main database engine heavily modified. -+ Greater use of DHTML to manipulate main display. -+ Made browser pop-up menu display glyphs for items menu items that replicate links in browser control. -+ Modified welcome page to appear differently depending on state of main and user defined databases. -+ Disclaimers, copyright and other headers of saved, printed and copied documents changed. -+ Commenting of exported code changed slightly to allow for user snippets that may not support all commenting styles. -+ Fixed status bar display bug. -+ Category headers in overview pane are now in bold. -+ Added enumerators to several list objects to support for..in construct. -+ Tweaked exception handling. -+ Added support for converting GIF resources into bitmaps for use in image lists. -+ Changed URL used to access program's home page. -+ Updated help file to reflect changes. -+ Added credits for use of Anders Melander's GIFImage unit to about box. - +* Added support for user defined snippets: + * User database can be edited, saved, backed-up and restored. + * User database can reference code in main database. + * Names of user defined snippets are coloured blue to distinguish them from main database. + * User database is stored as a mix of XML and source files in a sub-folder of the per-user application data folder. + * Queries can now be refreshed when content of user database changes. +* Modified extended external object that communicates between browser controls and application. +* Main database engine heavily modified. +* Greater use of DHTML to manipulate main display. +* Made browser pop-up menu display glyphs for items menu items that replicate links in browser control. +* Modified welcome page to appear differently depending on state of main and user defined databases. +* Disclaimers, copyright and other headers of saved, printed and copied documents changed. +* Commenting of exported code changed slightly to allow for user snippets that may not support all commenting styles. +* Fixed status bar display bug. +* Category headers in overview pane are now in bold. +* Added enumerators to several list objects to support for..in construct. +* Tweaked exception handling. +* Added support for converting GIF resources into bitmaps for use in image lists. +* Changed URL used to access program's home page. +* Updated help file to reflect changes. +* Added credits for use of Anders Melander's GIFImage unit to about box. ## Release v1.9.4 of 01 September 2008 -+ Improved handling of errors encountered when running compilers. -+ Provided checks for invalid compiler executable files in Config Compilers dialogue box. -+ Added enumerator to Compilers object. -+ Made ECodeSnip exceptions and descendants clonable when copying between threads. - +* Improved handling of errors encountered when running compilers. +* Provided checks for invalid compiler executable files in Config Compilers dialogue box. +* Added enumerator to Compilers object. +* Made ECodeSnip exceptions and descendants clonable when copying between threads. ## Unreleased v1.9.3 of 24 August 2008 -+ Fixed bug in the database updater which could cause a deleted local file not to be noticed and not replaced. - +* Fixed bug in the database updater which could cause a deleted local file not to be noticed and not replaced. ## Unreleased v1.9.2 of 24 August 2008 -+ Changed method used to generate program key. No longer uses MAC Address, since code to find this fails on Windows Vista. -+ Refactored to remove knowledge of how contributor information and database are stored from TAppInfo class. -+ Revised code that manages contributors so that storage details are private to the classes. - +* Changed method used to generate program key. No longer uses MAC Address, since code to find this fails on Windows Vista. +* Refactored to remove knowledge of how contributor information and database are stored from TAppInfo class. +* Revised code that manages contributors so that storage details are private to the classes. ## Unreleased v1.9.1 of 24 August 2008 -+ Rebuilt CodeSnip and install helper program with Delphi 2006: -+ Modified CodeSnip source to compile without warnings. -+ Replaced deprecated library calls with alternatives. - +* Rebuilt CodeSnip and install helper program with Delphi 2006: +* Modified CodeSnip source to compile without warnings. +* Replaced deprecated library calls with alternatives. ## Release v1.9 of 14 August 2008 -+ Changed so that all user accounts use the same database rather than having their own copy. Database now stored in common application data folder, along with registration information. Per-user configuration information remains in per-user application data folder in renamed file. -+ Installer can now optionally preserve data stored in database and configuration file used by earlier versions of the program. This involves creating new configuration files and moving the database. -+ Updated help file re these changes. - +* Changed so that all user accounts use the same database rather than having their own copy. Database now stored in common application data folder, along with registration information. Per-user configuration information remains in per-user application data folder in renamed file. +* Installer can now optionally preserve data stored in database and configuration file used by earlier versions of the program. This involves creating new configuration files and moving the database. +* Updated help file re these changes. ## Unreleased v1.8.11 of 11 August 2008 -+ Removed duplicate compiler glyph resources and modified compiler handling code accordingly. - +* Removed duplicate compiler glyph resources and modified compiler handling code accordingly. ## Unreleased v1.8.10 of 11 August 2008 -+ Refactored various units to use extended theme support. -+ Fixed redraw bug in tree views that use check boxes: check boxes were redrawing in wrong state when themes changed. -+ Improved support for theme changes. Theme manager now gets notified of changes directly from Windows. -+ Suppressed unnecessary compiler warnings. - +* Refactored various units to use extended theme support. +* Fixed redraw bug in tree views that use check boxes: check boxes were redrawing in wrong state when themes changed. +* Improved support for theme changes. Theme manager now gets notified of changes directly from Windows. +* Suppressed unnecessary compiler warnings. ## Unreleased v1.8.9 of 10 August 2008 -+ Modified Select Compiler dialogue box (opened from Configure Compiler dialogue) and Choose Element Colour dialogue (opened from Preferences dialogue) to be aligned correctly over dialogues, work correctly with Vista task bar and support help keywords. -+ Select Compiler file open dialogue now defaults to display any current compiler executable. -+ Choose Element Colour dialogue box now uses UK English and has custom title. -+ Added help topics for Select Compiler and Choose Element Colour dialogues. - +* Modified Select Compiler dialogue box (opened from Configure Compiler dialogue) and Choose Element Colour dialogue (opened from Preferences dialogue) to be aligned correctly over dialogues, work correctly with Vista task bar and support help keywords. +* Select Compiler file open dialogue now defaults to display any current compiler executable. +* Choose Element Colour dialogue box now uses UK English and has custom title. +* Added help topics for Select Compiler and Choose Element Colour dialogues. ## Release v1.8.8 of 16 June 2008 -+ Changed to make application minimisation, task bar preview window, and appearance in "Flip 3D" task switching display correctly on Windows Vista. -+ Provided work-around for Delphi's Alt key bug on XP and Vista (CodeGear Quality Central bug report #374030). - +* Changed to make application minimisation, task bar preview window, and appearance in "Flip 3D" task switching display correctly on Windows Vista. +* Provided work-around for Delphi's Alt key bug on XP and Vista (CodeGear Quality Central bug report #374030). ## Unreleased v1.8.7 of 05 June 2008 -+ Made selected tabs in information and detail pane persistent. -+ Fixed bug in build script. - +* Made selected tabs in information and detail pane persistent. +* Fixed bug in build script. ## Unreleased v1.8.6 of 02 June 2008 -+ Fixed lock-up that could occur when displaying wait dialogue box while background tasks execute. Previous attempt to fix this problem failed. -+ Changed "marquee" that is displayed in wait dialogue box to appear correctly on Vista. - +* Fixed lock-up that could occur when displaying wait dialogue box while background tasks execute. Previous attempt to fix this problem failed. +* Changed "marquee" that is displayed in wait dialogue box to appear correctly on Vista. ## Release v1.8.5 of 30 May 2008 -+ Fixed bug that was causing Save Snippet and Save Unit dialogue boxes to ignore file type +* Fixed bug that was causing Save Snippet and Save Unit dialogue boxes to ignore file type selected by user, always outputting default file type. -+ Deleted some unused source code. -+ Removed option to install a desktop icon from installer. Also refactored install script to conform to current Inno Setup standards. - +* Deleted some unused source code. +* Removed option to install a desktop icon from installer. Also refactored install script to conform to current Inno Setup standards. ## Release v1.8.4 of 22 April 2008 -+ Added manifest resource to ensure compatibility with Windows Vista and to use Vista themes. -+ Fixed border problem in web update dialogue box and about box when displayed under Vista / IE7 browser control. -+ Prevented selection of text in previews displayed in preferences dialogue box. -+ Updated set-up script to use macros. -+ Modified Build batch file to work with Windows SDK 2008. - +* Added manifest resource to ensure compatibility with Windows Vista and to use Vista themes. +* Fixed border problem in web update dialogue box and about box when displayed under Vista / IE7 browser control. +* Prevented selection of text in previews displayed in preferences dialogue box. +* Updated set-up script to use macros. +* Modified Build batch file to work with Windows SDK 2008. ## Unreleased v1.8.3 of 05 November 2007 -+ Refactored dynamic CSS generation code. - +* Refactored dynamic CSS generation code. ## Unreleased v1.8.2 of 04 November 2007 -+ Refactored assignable interfaced objects. - +* Refactored assignable interfaced objects. ## Release v1.8.1 of 04 November 2007 -+ Made changes to browser control and URL handling. - +* Made changes to browser control and URL handling. ## Unreleased v1.8 of 04 November 2007 -+ Added pop-up context menus to main display's detail pane. - +* Added pop-up context menus to main display's detail pane. ## Unreleased v1.7.7 of 29 October 2007 -+ Modified code of compiler wait dialogue box and splash screen to try to prevent bug that occasionally prevent the dialogue from closing, locking up application. - +* Modified code of compiler wait dialogue box and splash screen to try to prevent bug that occasionally prevent the dialogue from closing, locking up application. ## Unreleased v1.7.6 of 18 October 2007 -+ Shift-clicking links in the main display and some dialogue boxes was starting Internet Explorer. Fixed so that Internet Explorer is no longer started and shift-clicking external links now starts default browser. - +* Shift-clicking links in the main display and some dialogue boxes was starting Internet Explorer. Fixed so that Internet Explorer is no longer started and shift-clicking external links now starts default browser. ## Unreleased v1.7.5 of 17 October 2007 -+ Modified Preferences dialogue box: - - Refactored code that displays measurement units. - - Preview on Source Code tab now takes on appearance of source code highlighter defined on Syntax Highlighter tab. -+ Changed format of ini file that stores persistent settings so that source code highlighter preferences are now stored in Prefs section of ini file rather than own section. -+ Customised installer to update existing ini files to revised version. - +* Modified Preferences dialogue box: + * Refactored code that displays measurement units. + * Preview on Source Code tab now takes on appearance of source code highlighter defined on Syntax Highlighter tab. +* Changed format of ini file that stores persistent settings so that source code highlighter preferences are now stored in Prefs section of ini file rather than own section. +* Customised installer to update existing ini files to revised version. ## Release v1.7.4 of 14 October 2007 -+ Fixed display bug when selecting routines following a text search. -+ Improved text search algorithm to permit search strings containing punctuation characters. -+ Fixed typo in the "About The Database" section of the About box. - +* Fixed display bug when selecting routines following a text search. +* Improved text search algorithm to permit search strings containing punctuation characters. +* Fixed typo in the "About The Database" section of the About box. ## Release v1.7.3 of 27 September 2007 -+ Improved alignment of dialogue boxes and splash screen over owning forms. Alignment code substantially refactored. -+ Added support for multiple monitors. - +* Improved alignment of dialogue boxes and splash screen over owning forms. Alignment code substantially refactored. +* Added support for multiple monitors. ## Release v1.7.2 of 24 September 2007 -+ Fixed bug that was preventing wait dialogue box from displaying during long compilations. - +* Fixed bug that was preventing wait dialogue box from displaying during long compilations. ## Unreleased v1.7.1 of 22 September 2007 -+ Added list of testers to credits section of Database tab in About box. -+ Added new help menu item that displays privacy statement. -+ Rearranged help menu items. -+ Updated help file re changes to help menu. - +* Added list of testers to credits section of Database tab in About box. +* Added new help menu item that displays privacy statement. +* Rearranged help menu items. +* Updated help file re changes to help menu. ## Release v1.7 of 08 September 2007 -+ Added new facility to print information about selected routines, with page set-up and printer configuration. -+ Added new "general" tab (sets default measurement units) and "printing" tab (to set printing defaults) to preferences dialogue box. -+ Changed format of ini file that stores persistent settings. -+ Updated help file to reflect changes. -+ Customised installer to update existing ini files to revised version. - +* Added new facility to print information about selected routines, with page set-up and printer configuration. +* Added new "general" tab (sets default measurement units) and "printing" tab (to set printing defaults) to preferences dialogue box. +* Changed format of ini file that stores persistent settings. +* Updated help file to reflect changes. +* Customised installer to update existing ini files to revised version. ## Unreleased v1.6.4 of 02 July 2007 -+ Corrected typos in generated source code header comments. -+ Added support for embedding titles in generated documents where document supports title meta data. -+ Added suggested file name to save unit and save snippets dialogue boxes. -+ Refactored code in syntax highlighter that generates XHTML. - +* Corrected typos in generated source code header comments. +* Added support for embedding titles in generated documents where document supports title meta data. +* Added suggested file name to save unit and save snippets dialogue boxes. +* Refactored code in syntax highlighter that generates XHTML. ## Unreleased v1.6.3 of 13 May 2007 -+ Added support for selecting and copying text displayed in preview dialogue. -+ Changed so that each document type displayed in preview dialogue box has same margins. -+ Updated help file re changes to preview dialogue box. - +* Added support for selecting and copying text displayed in preview dialogue. +* Changed so that each document type displayed in preview dialogue box has same margins. +* Updated help file re changes to preview dialogue box. ## Unreleased v1.6.2 of 12 May 2007 -+ Updated to use revised news data format provided by web service. -+ Update from Web dialogue box now displays number of news items along with page number of currently displayed item. - +* Updated to use revised news data format provided by web service. +* Update from Web dialogue box now displays number of news items along with page number of currently displayed item. ## Release v1.6.1 of 09 May 2007 -+ Fixed bug that allowed user to select a different routine while compiling another causing display to get out sync. - +* Fixed bug that allowed user to select a different routine while compiling another causing display to get out sync. ## Release v1.6 of 08 May 2007 -+ Added support for Delphi 2007 compiler. -+ Updated help file re new compiler support. - +* Added support for Delphi 2007 compiler. +* Updated help file re new compiler support. ## Release v1.5.13 of 04 March 2007 -+ Fixed bug from v1.5.11 where Tools | Register CodeSnip and View | Show/Hide Test Unit menu options were permanently disabled. -+ Fixed bug from v1.5.9 where showing and hiding test units from menus was out of sync with links in compiler check pane. - +* Fixed bug from v1.5.11 where Tools | Register CodeSnip and View | Show/Hide Test Unit menu options were permanently disabled. +* Fixed bug from v1.5.9 where showing and hiding test units from menus was out of sync with links in compiler check pane. ## Release v1.5.12 of 01 March 2007 -+ Made long operations (loading database and compiling test units) execute in threads. -+ Changed to display wait dialogue while updated database is being loaded. -+ Made progress meters displayed in wait dialogues update more smoothly. - +* Made long operations (loading database and compiling test units) execute in threads. +* Changed to display wait dialogue while updated database is being loaded. +* Made progress meters displayed in wait dialogues update more smoothly. ## Release v1.5.11 of 25 February 2007 -+ Added splash screen displayed when program is loading. -+ Main window, menu and toolbar is now disabled when program is initialising and when updated database is loading. -+ Program window is now centred on screen first time it is run and program is now never started minimized. - +* Added splash screen displayed when program is loading. +* Main window, menu and toolbar is now disabled when program is initialising and when updated database is loading. +* Program window is now centred on screen first time it is run and program is now never started minimized. ## Unreleased v1.5.10 of 17 February 2007 -+ Refactored code that handles web browser controls. Moved various pieces of code that manipulates and queries browser controls into central UI and IO manager classes. Also added helper classes to manipulate HTML documents and browser controls. -+ Some code made redundant by above changes was removed. -+ Lightened and centralised colours used to highlight text search results. - +* Refactored code that handles web browser controls. Moved various pieces of code that manipulates and queries browser controls into central UI and IO manager classes. Also added helper classes to manipulate HTML documents and browser controls. +* Some code made redundant by above changes was removed. +* Lightened and centralised colours used to highlight text search results. ## Unreleased v1.5.9 of 16 February 2007 -+ Refactoring update. Revised code that manages the main display, i.e overview and details panes. - +* Refactoring update. Revised code that manages the main display, i.e overview and details panes. ## Release v1.5.8 of 16 February 2007 -+ Fixed bug in view history where selecting an item from the history list could cause a crash after database has been updated. This was fixed by clearing the history list after updating the database. -+ Now clears the main display before re-displaying an updated database to prevent an item from the old version of the database being selected. - +* Fixed bug in view history where selecting an item from the history list could cause a crash after database has been updated. This was fixed by clearing the history list after updating the database. +* Now clears the main display before re-displaying an updated database to prevent an item from the old version of the database being selected. ## Unreleased v1.5.7 of 12 February 2007 -+ Refactored, relocated and extended use of some utility routines, resulting in some other minor changes: - - All error and information message boxes now have properly terminated sentences. - - Generated XHTML less likely to contain illegal characters. - +* Refactored, relocated and extended use of some utility routines, resulting in some other minor changes: + * All error and information message boxes now have properly terminated sentences. + * Generated XHTML less likely to contain illegal characters. ## Unreleased v1.5.6 of 11 February 2007 -+ Modified about dialogue box to display information about the Code Snippets Database in addition to the program. The two kinds of information are displayed in two tabs. -+ Added code to get list of database contributors from a file downloaded with database updates. - +* Modified about dialogue box to display information about the Code Snippets Database in addition to the program. The two kinds of information are displayed in two tabs. +* Added code to get list of database contributors from a file downloaded with database updates. ## Unreleased v1.5.5 of 11 February 2007 -+ Made keyboard interaction with application more consistent: - - Made browser controls activate and focus properly when user tabs into them. - - Fixed tab order problems in main display and about dialogue box so that only controls that may need to receive user input are now activated by tabbing. - - Links displayed in browser controls are always now included in tab sequence and can be activated by Ctrl+Return when focused. - - Fixed inconsistency in tab sets in overview and details pane responded inconsistently to Ctrl+Tab and Shift+Ctrl+Tab. -+ Changed browser control respond to activation via the mouse to be the same as for the keyboard. - +* Made keyboard interaction with application more consistent: + * Made browser controls activate and focus properly when user tabs into them. + * Fixed tab order problems in main display and about dialogue box so that only controls that may need to receive user input are now activated by tabbing. + * Links displayed in browser controls are always now included in tab sequence and can be activated by Ctrl+Return when focused. + * Fixed inconsistency in tab sets in overview and details pane responded inconsistently to Ctrl+Tab and Shift+Ctrl+Tab. +* Changed browser control respond to activation via the mouse to be the same as for the keyboard. ## Release v1.5.4 of 09 February 2007 -+ Added disclaimers re database code to generated units and snippets and to program's welcome page. -+ Made slight modifications to source code generation code. +* Added disclaimers re database code to generated units and snippets and to program's welcome page. +* Made slight modifications to source code generation code. ## Release v1.5.3 of 08 February 2007 -+ Refactored and rationalised code in main form and moved some code into help classes. -+ Revised code that performs customisation, auto-sizing and alignment of forms and dialogue boxes. -+ Standardised execution method of dialogue boxes. - +* Refactored and rationalised code in main form and moved some code into help classes. +* Revised code that performs customisation, auto-sizing and alignment of forms and dialogue boxes. +* Standardised execution method of dialogue boxes. ## Unreleased v1.5.2 of 04 February 2007 -+ Refactored help manager system to make it easier to swap in new help systems in future. -+ Modified help handlers in forms to remove redundant code. -+ Modified how help menu items call help topics. - +* Refactored help manager system to make it easier to swap in new help systems in future. +* Modified help handlers in forms to remove redundant code. +* Modified how help menu items call help topics. ## Unreleased v1.5.1 of 04 February 2007 -+ Refactored handling of database searches by creating new global query object to store information about current query on database. -+ Changed relevant code to use the new object and deleted resulting redundant code. -+ Made some other minor code improvements and modifications. - +* Refactored handling of database searches by creating new global query object to store information about current query on database. +* Changed relevant code to use the new object and deleted resulting redundant code. +* Made some other minor code improvements and modifications. ## Unreleased v1.5 of 03 February 2007 -+ Made status bar display database and search information along with other prompts in addition to displaying hints. - +* Made status bar display database and search information along with other prompts in addition to displaying hints. ## Unreleased v1.4.6 of 17 December 2006 -+ Made minor changes to appearance: - - Changed some colours to system colours from hard-wired colours. - - Changed help links in main display from blue to green. - - Removed text highlighting from welcome page. - +* Made minor changes to appearance: + * Changed some colours to system colours from hard-wired colours. + * Changed help links in main display from blue to green. + * Removed text highlighting from welcome page. ## Unreleased v1.4.5 of 04 December 2006 -+ Refactored code that generates test units. As a consequence names of test units displayed in Compiler Check pane have been corrected to the actual names used in test compilations. - +* Refactored code that generates test units. As a consequence names of test units displayed in Compiler Check pane have been corrected to the actual names used in test compilations. ## Release v1.4.4 of 04 December 2006 -+ Added new menu item to View menu that toggles visibility of test units in the compiler check tab. -+ Changed glyph used for link that performs same action in compiler check tab and made image change depending on visibility of test unit. - +* Added new menu item to View menu that toggles visibility of test units in the compiler check tab. +* Changed glyph used for link that performs same action in compiler check tab and made image change depending on visibility of test unit. ## Unreleased v1.4.3 of 03 December 2006 -+ Changed information pane to load routines dynamically via DHTML rather than reloading document each time. -+ Refactored DHTML code and detail frames that support DHTML. -+ Refactored routine HTML generation code. -+ Rationalised some dynamic CSS generating code. -+ Revised information pane's underlying HTML code. - +* Changed information pane to load routines dynamically via DHTML rather than reloading document each time. +* Refactored DHTML code and detail frames that support DHTML. +* Refactored routine HTML generation code. +* Rationalised some dynamic CSS generating code. +* Revised information pane's underlying HTML code. ## Unreleased v1.4.2 of 03 December 2006 -+ Corrected alignment of About and Compiler Errors dialogue boxes over main form. - +* Corrected alignment of About and Compiler Errors dialogue boxes over main form. ## Unreleased v1.4.1 of 03 December 2006 -+ Fixed bug where Test Compile menu option and tool button were always enabled and could cause an assertion failure when no routine was selected or no compilers were available. - +* Fixed bug where Test Compile menu option and tool button were always enabled and could cause an assertion failure when no routine was selected or no compilers were available. ## Unreleased v1.4 of 03 December 2006 -+ Revised display in compiler check pane. Now lists database and test results side by side. -+ Changed routine compiler check page to be updated dynamically (using JavaScript) when routine selection changes rather than always reloading page. -+ Modified some JavaScript support code. -+ Fixed potential bug in compiler code. -+ Updated help file re changes to Compiler Check tab. -+ Fixed a typo and index error in help file. - +* Revised display in compiler check pane. Now lists database and test results side by side. +* Changed routine compiler check page to be updated dynamically (using JavaScript) when routine selection changes rather than always reloading page. +* Modified some JavaScript support code. +* Fixed potential bug in compiler code. +* Updated help file re changes to Compiler Check tab. +* Fixed a typo and index error in help file. ## Unreleased v1.3.5 of 01 December 2006 -+ Changed to display a border-less message dialogue during long test compilations. The dialogue is not displayed for shorter compilations. -+ Updated help file re above and fixed an error in the search menu topic. - +* Changed to display a border-less message dialogue during long test compilations. The dialogue is not displayed for shorter compilations. +* Updated help file re above and fixed an error in the search menu topic. ## Unreleased v1.3.4 of 26 November 2006 -+ Refactored JavaScript used to interface between main program and HTML display. -+ Centralised generation of JavaScript in main code. - +* Refactored JavaScript used to interface between main program and HTML display. +* Centralised generation of JavaScript in main code. ## Unreleased v1.3.3 of 25 November 2006 -+ Refactored handling of CSS and XHTML: - - Changed way CSS is provided to enable use of system font and colours. - - Tidied source HTML documents to remove illegal XHTML strict attributes and to remove hard-wired colours. - +* Refactored handling of CSS and XHTML: + * Changed way CSS is provided to enable use of system font and colours. + * Tidied source HTML documents to remove illegal XHTML strict attributes and to remove hard-wired colours. ## Release v1.3.2 of 24 November 2006 -+ Made program remember whether test units are displayed or hidden until end of session. - +* Made program remember whether test units are displayed or hidden until end of session. ## Unreleased v1.3.1 of 21 November 2006 -+ Made minor modification to appearance of Configure Compilers dialogue box. - +* Made minor modification to appearance of Configure Compilers dialogue box. ## Unreleased v1.3 of 18 November 2006 -+ Added facility to sign up to CodeSnip mailing list on-line. -+ Corrected further typos in registration wizard. -+ Updated help file re mailing list sign-up, changed privacy statement and added license to contents page. - +* Added facility to sign up to CodeSnip mailing list on-line. +* Corrected further typos in registration wizard. +* Updated help file re mailing list sign-up, changed privacy statement and added license to contents page. ## Release v1.2.5 of 16 November 2006 -+ Corrected and modified text displayed on last page of Registration Wizard when user elects to join mailing list. - +* Corrected and modified text displayed on last page of Registration Wizard when user elects to join mailing list. ## Unreleased v1.2.4 of 14 November 2006 -+ Changed about box and help menu to display end user license agreement in help file rather than separate text file. -+ Added license topic and made related changes to help file. - +* Changed about box and help menu to display end user license agreement in help file rather than separate text file. +* Added license topic and made related changes to help file. ## Unreleased v1.2.3 of 12 November 2006 -+ Fixed incorrect glyph used for Show All search menu item and tool button. -+ Moved Tools | Preferences menu option to top of Tools menu. - +* Fixed incorrect glyph used for Show All search menu item and tool button. +* Moved Tools | Preferences menu option to top of Tools menu. ## Unreleased v1.2.2 of 12 November 2006 -+ Added hot tracking to tree view check boxes used in Select Routines dialogue box when Windows XP themes are enabled. - +* Added hot tracking to tree view check boxes used in Select Routines dialogue box when Windows XP themes are enabled. ## Unreleased v1.2.1 of 11 November 2006 -+ Refactoring release: - - Method used to construct and use help file changed. - - Moved code that detects HTML and RTF files to appropriate utility units. - - Streamlined code in preview dialogue box. - +* Refactoring release: + * Method used to construct and use help file changed. + * Moved code that detects HTML and RTF files to appropriate utility units. + * Streamlined code in preview dialogue box. ## Release v1.2 of 11 November 2006 -+ Changed syntax highlighter used to format units and code snippets to be able to read custom settings from persistent storage. -+ Added Syntax Highlighter tab to preferences dialogue box to enable users to customise the font, style and colours used by the syntax highlighter. -+ Modified preferences dialogue box's Source Code tab to display a preview of routines using various comment styles. -+ Updated help file re revised preferences dialogue box. - +* Changed syntax highlighter used to format units and code snippets to be able to read custom settings from persistent storage. +* Added Syntax Highlighter tab to preferences dialogue box to enable users to customise the font, style and colours used by the syntax highlighter. +* Modified preferences dialogue box's Source Code tab to display a preview of routines using various comment styles. +* Updated help file re revised preferences dialogue box. ## Unreleased v1.1.2 of 07 November 2006 -+ Refactoring release: - - Added code to generate CSS properties. - - Added new classes to generate RTF code. - - Re-implemented RTF highlighted code. -+ Now generates much smaller RTF export files. - +* Refactoring release: + * Added code to generate CSS properties. + * Added new classes to generate RTF code. + * Re-implemented RTF highlighted code. +* Now generates much smaller RTF export files. ## Unreleased v1.1.1 of 31 October 2006 -+ Changed Select Routines dialogue to use XP style check boxes when XP themes active and custom check boxes when XP themes inactive. - +* Changed Select Routines dialogue to use XP style check boxes when XP themes active and custom check boxes when XP themes inactive. ## Release v1.1 of 30 October 2006 -+ Added ability to generate and save whole Pascal unit containing currently selected routines. -+ Added new search that can find all routines cross-referenced by a given routine. -+ Added ability to manually select routines that are displayed in overview pane. -+ Added short-cut key and changed glyph for File | Save Snippet option / tool button. -+ Updated way source code preferences are stored. Broke backwards compatibility with previous storage method, so upgraders may loose settings. -+ Made minor changes to preferences dialogue box. -+ Word-wrapped long uses lists in generated units. -+ Refactored code that determines type of exported files. -+ Refactored and expanded code that deals with source code exporting. -+ Fixed some minor bugs: - - Previews of large rich text documents were displaying RTF source instead of rendering document. - - Assertion failure could (rarely) happen when displaying message boxes without specifying parent form. - - Saving snippets in a file without supplying a file extension could silently overwrite existing files. - - Comment style was being ignored when generating a unit. -+ Updated help file: - - Added new topics, index entries and TOC entries for new features. - - Updated some existing topics to refer to new features. - - Revised and corrected several existing help topics. - +* Added ability to generate and save whole Pascal unit containing currently selected routines. +* Added new search that can find all routines cross-referenced by a given routine. +* Added ability to manually select routines that are displayed in overview pane. +* Added short-cut key and changed glyph for File | Save Snippet option / tool button. +* Updated way source code preferences are stored. Broke backwards compatibility with previous storage method, so upgraders may loose settings. +* Made minor changes to preferences dialogue box. +* Word-wrapped long uses lists in generated units. +* Refactored code that determines type of exported files. +* Refactored and expanded code that deals with source code exporting. +* Fixed some minor bugs: + * Previews of large rich text documents were displaying RTF source instead of rendering document. + * Assertion failure could (rarely) happen when displaying message boxes without specifying parent form. + * Saving snippets in a file without supplying a file extension could silently overwrite existing files. + * Comment style was being ignored when generating a unit. +* Updated help file: + * Added new topics, index entries and TOC entries for new features. + * Updated some existing topics to refer to new features. + * Revised and corrected several existing help topics. ## Unreleased v1.0.3 of 26 October 2006 -+ Refactored various parts of source code. No changes to program's functionality. Details are: - - Standardised all singleton objects on interface based implementation. - - Centralised code that gets location of license file. - - Standardised links that trigger JavaScript in some HTML resources. - - Changed bug report dialogue box to descend from common wizard dialogue box. - +* Refactored various parts of source code. No changes to program's functionality. Details are: + * Standardised all singleton objects on interface based implementation. + * Centralised code that gets location of license file. + * Standardised links that trigger JavaScript in some HTML resources. + * Changed bug report dialogue box to descend from common wizard dialogue box. ## Release v1.0.2 of 25 October 2006 -+ Changed so that links from program to external web pages display in default browser rather than IE. -+ Refactored code that displays license text file in external application. -+ Reworded some of welcome screen and added links to on-line database. -+ Refactored some JavaScript code that works with main display HTML and web browser code. -+ Made minor changes to hints displayed in status bar when cursor is over links. - +* Changed so that links from program to external web pages display in default browser rather than IE. +* Refactored code that displays license text file in external application. +* Reworded some of welcome screen and added links to on-line database. +* Refactored some JavaScript code that works with main display HTML and web browser code. +* Made minor changes to hints displayed in status bar when cursor is over links. ## Release v1.0.1 of 14 October 2006 -+ Fixed problem in web update that caused program to crash on Windows 9x platforms. - +* Fixed problem in web update that caused program to crash on Windows 9x platforms. ## Release v1.0 of 09 June 2006 -+ Revised About Box text and appearance and added link that displays license file. -+ Refactored and renamed some code. -+ Made minor changes to appearance and effect of Configure Compilers dialogue box. -+ Fixed potential bug displaying JavaScript error dialogue if help called from links in main display fail. -+ Made some literal strings resource strings. -+ Made calls to help system fail gracefully on machines without HTML Help installed. -+ Modified code that reads program's version information. -+ Added important compiler directives. -+ Standardised appearance of all groups of action links in main display. -+ Added Help menu item to display license and to access CodeSnip web page. -+ Moved bug report and registration menu options from Help to Tools menu. -+ Updated and help file re new commands, corrected some errors and re-styled menu help sections. -+ Created installer using Inno Setup. -+ Added new batch file to build program. -+ Fully commented code. -+ Changed to new end user license agreement for the executable program. The program remains open source. - +* Revised About Box text and appearance and added link that displays license file. +* Refactored and renamed some code. +* Made minor changes to appearance and effect of Configure Compilers dialogue box. +* Fixed potential bug displaying JavaScript error dialogue if help called from links in main display fail. +* Made some literal strings resource strings. +* Made calls to help system fail gracefully on machines without HTML Help installed. +* Modified code that reads program's version information. +* Added important compiler directives. +* Standardised appearance of all groups of action links in main display. +* Added Help menu item to display license and to access CodeSnip web page. +* Moved bug report and registration menu options from Help to Tools menu. +* Updated and help file re new commands, corrected some errors and re-styled menu help sections. +* Created installer using Inno Setup. +* Added new batch file to build program. +* Fully commented code. +* Changed to new end user license agreement for the executable program. The program remains open source. ## Release v1.0 RC 3 of 01 May 2006 -_Internal CodeSnip version 0.12.0_ - -+ 3rd release candidate for the v1.0 release. -+ Updated to use v4 of update web service that uses completely new update protocol. Significant changes to code were needed to achieve this. -+ Redesigned update dialogue box accordingly. -+ Added ability to update dialogue to display latest CodeSnip news delivered as part update process. -+ Updated help file re changes to update dialogue box. +Internal CodeSnip version 0.12.0 +* 3rd release candidate for the v1.0 release. +* Updated to use v4 of update web service that uses completely new update protocol. Significant changes to code were needed to achieve this. +* Redesigned update dialogue box accordingly. +* Added ability to update dialogue to display latest CodeSnip news delivered as part update process. +* Updated help file re changes to update dialogue box. ## Release v1.0 RC 2 of 16 April 2006 -_Internal CodeSnip version 0.11.3_ - -+ 2nd release candidate for the v1.0 release. -+ Fixed bug where user could drag and drop files onto web browser controls and file contents would overwrite the display. +Internal CodeSnip version 0.11.3 +* 2nd release candidate for the v1.0 release. +* Fixed bug where user could drag and drop files onto web browser controls and file contents would overwrite the display. ## Release v1.0 RC 1 of 11 April 2006 -_Internal CodeSnip version 0.11.2_ - -+ 1st release candidate for the v1.0 release. -+ Updated help file: - - Ensured that external links display in a web browser window rather than in the help window. - - Added additional internal links to some help topics. -+ Fixed compiler warnings. -+ Removed some redundant code. +Internal CodeSnip version 0.11.2 +* 1st release candidate for the v1.0 release. +* Updated help file: + * Ensured that external links display in a web browser window rather than in the help window. + * Added additional internal links to some help topics. +* Fixed compiler warnings. +* Removed some redundant code. ## Unreleased v0.11.1 Beta of 10 April 2006 -+ Improved and fixed interaction with database update web service: - - Download manager now sends program's key and registration code to web service instead of place-holder strings. - - Handling for HTTP error messages improved. Short HTTP error descriptions are displayed rather than full content of error pages. - +* Improved and fixed interaction with database update web service: + * Download manager now sends program's key and registration code to web service instead of place-holder strings. + * Handling for HTTP error messages improved. Short HTTP error descriptions are displayed rather than full content of error pages. ## Release v0.11.0 Beta of 07 April 2006 -+ Added ability to register CodeSnip at DelphiDabbler.com: - - Registration is performed via a new wizard that gathers registration information and interacts with web server. - - Wizard is accessed via a Help menu option and About box button that appear only when application is unregistered. - - Application is identified by a unique key. - - Registration information is stored in persistent storage. -+ Reworked and added classes to centralise access to system and application information. -+ Updated help file with details of registration dialogue and CodeSnip mailing list. - +* Added ability to register CodeSnip at DelphiDabbler.com: + * Registration is performed via a new wizard that gathers registration information and interacts with web server. + * Wizard is accessed via a Help menu option and About box button that appear only when application is unregistered. + * Application is identified by a unique key. + * Registration information is stored in persistent storage. +* Reworked and added classes to centralise access to system and application information. +* Updated help file with details of registration dialogue and CodeSnip mailing list. ## Unreleased v0.10.12 Beta of 04 April 2006 -+ Improved code that stores global application settings. Prepared way for having per-user and global settings rather than just per-user settings as at present. - +* Improved code that stores global application settings. Prepared way for having per-user and global settings rather than just per-user settings as at present. ## Unreleased v0.10.11 Beta of 03 April 2006 -+ Revised to work with v3.1 of CodeSnip database update web service. - +* Revised to work with v3.1 of CodeSnip database update web service. ## Unreleased v0.10.10 Beta of 02 April 2006 -+ Added program icon (16x16, 32x32 and 48x48 versions). - +* Added program icon (16x16, 32x32 and 48x48 versions). ## Release v0.10.9 Beta of 02 April 2006 -+ Fixed bug where browser controls displayed JavaScript error dialogue when exceptions were raised by database updates initiated from browser control's "external" object. -+ Refactored some code in main form and main snippets object as a result of above fix. -+ Also refactored some of search code in main form. - +* Fixed bug where browser controls displayed JavaScript error dialogue when exceptions were raised by database updates initiated from browser control's "external" object. +* Refactored some code in main form and main snippets object as a result of above fix. +* Also refactored some of search code in main form. ## Unreleased v0.10.8 Beta of 02 April 2006 -+ Removed bug in database update manager that was causing database to be restored unnecessarily. -+ Heavily refactored update manager code as part of bug fix. - +* Removed bug in database update manager that was causing database to be restored unnecessarily. +* Heavily refactored update manager code as part of bug fix. ## Release v0.10.7 Beta of 28 January 2006 -+ Fixed display problems in details pane when running on Windows 2000. -+ Changed style of scroll bars from flat to normal when running in Windows XP classic style or on earlier Windows version. -+ Made compiler check pane update itself when compilers are added or removed. - +* Fixed display problems in details pane when running on Windows 2000. +* Changed style of scroll bars from flat to normal when running in Windows XP classic style or on earlier Windows version. +* Made compiler check pane update itself when compilers are added or removed. ## Release v0.10.6 Beta of 20 January 2006 -+ Fixed bug where backup directory was not being deleted after database updates. - +* Fixed bug where backup directory was not being deleted after database updates. ## Release v0.10.5 Beta of 14 January 2006 -+ Added credits for third party code to about box. -+ Completed help file. - +* Added credits for third party code to about box. +* Completed help file. ## Release v0.10.4 Beta of 12 January 2006 -+ Added checking of checksums of downloaded files to increase security. Exceptions now raised when a file's checksum is incorrect. -+ Fixed small alignment problem in update dialogue. - +* Added checking of checksums of downloaded files to increase security. Exceptions now raised when a file's checksum is incorrect. +* Fixed small alignment problem in update dialogue. ## Unreleased v0.10.3 Beta of 11 January 2006 -+ Changed so that compiler output is now captured directly rather than via temporary log file. -+ Compiler execution is now time-sliced and time-limited rather than being allowed infinite processing time. - +* Changed so that compiler output is now captured directly rather than via temporary log file. +* Compiler execution is now time-sliced and time-limited rather than being allowed infinite processing time. ## Unreleased v0.10.2 Beta of 10 January 2006 -+ Reverted to Delphi 7 to avoid Delphi 2006 bug that was enabling dialogues to be minimized and maximized. -+ Reordered controls in Find Compiler dialogues. -+ Restored title bar close button to web update dialogue. -+ Reverted to Indy 9 Internet controls (from Indy 10) and made relevant adjustments to code. -+ Completed help topics for Find Text and Find Compiler dialogues. - +* Reverted to Delphi 7 to avoid Delphi 2006 bug that was enabling dialogues to be minimized and maximized. +* Reordered controls in Find Compiler dialogues. +* Restored title bar close button to web update dialogue. +* Reverted to Indy 9 Internet controls (from Indy 10) and made relevant adjustments to code. +* Completed help topics for Find Text and Find Compiler dialogues. ## Release v0.10.1 Beta of 09 January 2006 -+ Removed debug code (message box) mistakenly left in compiler execution code. -+ Refactored compiler support classes. - +* Removed debug code (message box) mistakenly left in compiler execution code. +* Refactored compiler support classes. ## Release v0.10.0 Beta of 08 January 2006 -+ Added support for Delphi 2005/6 Win32 compilers. -+ Refactored some compiler support code. -+ Added support for user-configurable compiler switches. -+ Used new tabbed layout for Configure Compilers dialogue box and added tab for configuring compiler switches. -+ Updated help file to reflect redesign of Configure Compilers dialogue box. - +* Added support for Delphi 2005/6 Win32 compilers. +* Refactored some compiler support code. +* Added support for user-configurable compiler switches. +* Used new tabbed layout for Configure Compilers dialogue box and added tab for configuring compiler switches. +* Updated help file to reflect redesign of Configure Compilers dialogue box. ## Release v0.9.0 Beta of 06 January 2006 -+ Added facility to copy code snippets to clipboard. -+ Added new preferences dialogue box to enable configuration of default format for code snippets. -+ Added new preferences class to persist data entered in the preferences dialogue. -+ Refactored main snippets class to simplify addition of new copy snippet facility. -+ Updated help file re new additions. - +* Added facility to copy code snippets to clipboard. +* Added new preferences dialogue box to enable configuration of default format for code snippets. +* Added new preferences class to persist data entered in the preferences dialogue. +* Refactored main snippets class to simplify addition of new copy snippet facility. +* Updated help file re new additions. ## Unreleased v0.8.3 Beta of 04 January 2006 -+ Modified to compile with Indy Components v10 and Delphi 2006 for Win 32. - +* Modified to compile with Indy Components v10 and Delphi 2006 for Win 32. ## Unreleased v0.8.2 Beta of 04 January 2006 -+ Created static class to interpret command line and changed other code to work with the new class. - +* Created static class to interpret command line and changed other code to work with the new class. ## Unreleased v0.8.1 Beta of 04 January 2006 -+ Fixed minor display bug in web update dialogue box. -+ Fixed about box's problem in displaying help in response to F1 key press. - +* Fixed minor display bug in web update dialogue box. +* Fixed about box's problem in displaying help in response to F1 key press. ## Release v0.8.0 Beta of 30 November 2005 -+ Changed help file from WinHelp (.hlp) format to HTML Help (.chm) format. -+ Changed program to use new format help file. - +* Changed help file from WinHelp (.hlp) format to HTML Help (.chm) format. +* Changed program to use new format help file. ## Release v0.7.7 Beta of 22 November 2005 -+ Refactored and revised code that accesses DelphiDabbler web services. -+ Updated to use v2 of the database update web service. -+ Added topics for Bug Report dialogue and Web Update dialogue to help file. - +* Refactored and revised code that accesses DelphiDabbler web services. +* Updated to use v2 of the database update web service. +* Added topics for Bug Report dialogue and Web Update dialogue to help file. ## Release v0.7.6 Beta of 04 June 2005 -+ Fixed Delphi compiler auto-detection bug. -+ Fixed bug that caused endless loop of exceptions when "database" was corrupt. -+ Syntax highlighter now generates correct XHTML for multi-line comments and generates correct CSS for mono-spaced fonts. -+ Occasional failure to create compiler log files now reported as error rather than bug. - +* Fixed Delphi compiler auto-detection bug. +* Fixed bug that caused endless loop of exceptions when "database" was corrupt. +* Syntax highlighter now generates correct XHTML for multi-line comments and generates correct CSS for mono-spaced fonts. +* Occasional failure to create compiler log files now reported as error rather than bug. ## Release v0.7.5 Beta of 03 June 2005 -+ Fixes bugs that surface when user has disabled scripts in IE's Internet zone. Program no longer runs in Internet zone. - +* Fixes bugs that surface when user has disabled scripts in IE's Internet zone. Program no longer runs in Internet zone. ## Release v0.7.4 Beta of 09 May 2005 -+ Made user defined settings in Save Snippets dialogue box persistent. - +* Made user defined settings in Save Snippets dialogue box persistent. ## Unreleased v0.7.3 Beta of 25 April 2005 -+ Disabled test compile button on compiler checks pane along with associated menu and toolbar button when no compilers installed. -+ Rewrote main welcome page, adding links to compiler check dialogue. Made same welcome page appear in both detail panes. -+ Updated compiler check pages by adding links to compiler check dialogue and new "about compiler checks" help topic. -+ Updated help file with new "about compiler checks" topic and completed "QuickStart" topic. -+ Added new features to DOM's external object to support above changes. - +* Disabled test compile button on compiler checks pane along with associated menu and toolbar button when no compilers installed. +* Rewrote main welcome page, adding links to compiler check dialogue. Made same welcome page appear in both detail panes. +* Updated compiler check pages by adding links to compiler check dialogue and new "about compiler checks" help topic. +* Updated help file with new "about compiler checks" topic and completed "QuickStart" topic. +* Added new features to DOM's external object to support above changes. ## Unreleased v0.7.2 Beta of 21 April 2005 -+ Refactored code that maintains persistent application data. -+ Refactored syntax highlighter code and moved interfaces and enumerated types to own unit. -+ Renamed unit generation unit now that it generates source code other than complete units. -+ Carried out minor refactoring of Pascal analyser unit. - +* Refactored code that maintains persistent application data. +* Refactored syntax highlighter code and moved interfaces and enumerated types to own unit. +* Renamed unit generation unit now that it generates source code other than complete units. +* Carried out minor refactoring of Pascal analyser unit. ## Unreleased v0.7.1 Beta of 20 April 2005 -+ Disabled F1 key press handling in dialogues with no help button. Was triggering bad topic errors in WinHelp. -+ Added "Compile" prefix to compiler check page's "Test Compile" button. -+ Fixed errors in "do nothing" doc host handler used by web browser control. - +* Disabled F1 key press handling in dialogues with no help button. Was triggering bad topic errors in WinHelp. +* Added "Compile" prefix to compiler check page's "Test Compile" button. +* Fixed errors in "do nothing" doc host handler used by web browser control. ## Release v0.7.0 Beta of 17 March 2005 -+ Added new facility to save a routine, or a whole category of routines, to file. -+ Reworked syntax highlighter implementation. -+ Updated help file with details of new routine saving feature. - +* Added new facility to save a routine, or a whole category of routines, to file. +* Reworked syntax highlighter implementation. +* Updated help file with details of new routine saving feature. ## Release v0.6.0 Beta of 10 March 2005 -+ Added syntax highlighting for source code displayed in detail panes. - +* Added syntax highlighting for source code displayed in detail panes. ## Release v0.5.0 Beta of 05 March 2005 -+ Added support for Free Pascal compiler by totally reworking the compiler support engine. -+ Added new dialogue box to configure compilers. Compiler detection ability retained but now only works in response to user request. -+ Revised about box to include "powered by Delphi" logo. -+ Updated help file: - - Added incomplete topics for each of the main menus. - - Added complete new topic for the compiler configuration dialogue box. - - Fixed K-keyword errors and added extra keywords for dialogue boxes. - +* Added support for Free Pascal compiler by totally reworking the compiler support engine. +* Added new dialogue box to configure compilers. Compiler detection ability retained but now only works in response to user request. +* Revised about box to include "powered by Delphi" logo. +* Updated help file: + * Added incomplete topics for each of the main menus. + * Added complete new topic for the compiler configuration dialogue box. + * Fixed K-keyword errors and added extra keywords for dialogue boxes. ## Release v0.4.0 Beta of 28 February 2005 -+ Text search results are now highlighted when routines are displayed in the information pane. - +* Text search results are now highlighted when routines are displayed in the information pane. ## Unreleased v0.3.4 Beta of 26 February 2005 -+ Separated back end database code from Snippets object. -+ New back end code designed to make it easy to change the data provider in future versions. Current version accesses data in .ini and .dat files. - +* Separated back end database code from Snippets object. +* New back end code designed to make it easy to change the data provider in future versions. Current version accesses data in .ini and .dat files. ## Unreleased v0.3.3 Beta of 25 February 2005 -+ Fixed bug that was preventing Ctrl+F from activating Find Text dialogue box. -+ Realigned controls in bug report dialogue and fixed tab order problems. -+ Fixed email address validation error in bug report dialogue - +* Fixed bug that was preventing Ctrl+F from activating Find Text dialogue box. +* Realigned controls in bug report dialogue and fixed tab order problems. +* Fixed email address validation error in bug report dialogue ## Unreleased v0.3.2 Beta of 24 February 2005 -+ Refactored code that provides compiler names and introduced global Compilers object. - +* Refactored code that provides compiler names and introduced global Compilers object. ## Unreleased v0.3.1 Beta of 24 February 2005 -+ Centralised code that displays message dialogues and standardised their appearance. - +* Centralised code that displays message dialogues and standardised their appearance. ## Unreleased v0.3.0 Beta of 23 February 2005 -+ Updated welcome pages to provide more help on using CodeSnip. -+ Removed dialogues that appeared on start up when database was empty. Welcome page now provides this information along with an option to download database. -+ Improved handling of welcome page. - +* Updated welcome pages to provide more help on using CodeSnip. +* Removed dialogues that appeared on start up when database was empty. Welcome page now provides this information along with an option to download database. +* Improved handling of welcome page. ## Unreleased v0.2.4 Beta of 23 February 2005 -+ Refactored and simplified access to dialogue boxes. -+ Improved search code. - +* Refactored and simplified access to dialogue boxes. +* Improved search code. ## Unreleased v0.2.3 Beta of 23 February 2005 -+ Created a class hierarchy for all frames that display HTML in a web browser control. - +* Created a class hierarchy for all frames that display HTML in a web browser control. ## Unreleased v0.2.2 Beta of 22 February 2005 -+ Localised various literal strings and moved some constant values to a common location. - +* Localised various literal strings and moved some constant values to a common location. ## Unreleased v0.2.1 Beta of 22 February 2005 -+ Overhauled web browser external object extender that communicates browser events to application. -+ Added new notifier object that centralises handling of GUI user interaction. - +* Overhauled web browser external object extender that communicates browser events to application. +* Added new notifier object that centralises handling of GUI user interaction. ## Unreleased v0.2.0 Beta of 21 February 2005 -+ Made minor changes to appearance of main display. -+ Refactored the HTML generation engine, added several HTML templates to resources and localised all strings used in generated HTML. - +* Made minor changes to appearance of main display. +* Refactored the HTML generation engine, added several HTML templates to resources and localised all strings used in generated HTML. ## Unreleased v0.1.4 Beta of 19 February 2005 -+ Refactored some code. - +* Refactored some code. ## Unreleased v0.1.3 Beta of 18 February 2005 -+ Removed redundant code. - +* Removed redundant code. -##Unreleased v0.1.2 Beta of 18 February 2005 - -+ Removed debug code. +## Unreleased v0.1.2 Beta of 18 February 2005 +* Removed debug code. ## Unreleased v0.1.1 Beta of 18 February 2005 -+ Fixed minor bugs. - +* Fixed minor bugs. ## Release v0.1.0 Beta of 30 January 2005 -+ Original beta release. +* Original beta release. From 7d7544b108c9889fa0e33b14044296aa253e84d7 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 31 Dec 2021 17:26:07 +0000 Subject: [PATCH 057/330] Update change log re v4.19.0 changes --- CHANGELOG.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dfc4ad359..4f15f6d67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,27 @@ This change log begins with the first ever pre-release version of _CodeSnip_. Re From v4.1.0 the version numbering has attempted to adhere to the principles of [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Release v4.19.0 of 31 December 2021 + +* Improved user-friendliness of Preferences dialogue box: + * Removed multi-line tab sets and replacing navigation pane on left hand side of window. + * Hid warning on Printing preferences page that changes will not be made until after program is restarted, if and only if page is displayed from Print dialogue box. + * Last preferences page displayed is now remembered and restored the next time the dialogue box is displayed. +* Added facility to customise size of font used in Overview pane's tree view. A new preference added to Display pane of Preferences dialogue box is used to set the font size. +* Fixed obscure bug in code that reads legacy ANSI Code Snippets Database files that was potentially using the incorrect ANSI code page. +* Updated help file re changes +* Fixed errors in custom installer dialogue boxes. +* Documentation corrected, expanded and updated. +* Some tidying up: + * Fixed some broken web links in source code comments and elsewhere. + * Replaced `http` protocol in URLS with `https` wherever supported - mainly in source code comments & documentation. + * Removed some orphaned files long since removed from project. + * Added missing header comments to source file. + * Updated copyright dates in files modified during year to include 2021. + * Change log was overhauled to fix linting errors. +* Bumped version of per-user config file to 17 following addition of new preferences. +* Small amount of refactoring + ## Release v4.18.1 of 29 November 2021 * Improved handling of control and whitespace characters in generated HTML: revised which characters were converted to HTML character attributes / entities. From 4f55ac08de0d4b39e9da180caa7c5afd4a0d930b Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 31 Dec 2021 20:07:48 +0000 Subject: [PATCH 058/330] Tweak to CHANGELOG.md for v4.19.0 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f15f6d67..fb395c211 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,7 @@ From v4.1.0 the version numbering has attempted to adhere to the principles of [ * Fixed obscure bug in code that reads legacy ANSI Code Snippets Database files that was potentially using the incorrect ANSI code page. * Updated help file re changes * Fixed errors in custom installer dialogue boxes. -* Documentation corrected, expanded and updated. +* Documentation corrected, expanded and updated, with some file format documentation having a major overhaul. * Some tidying up: * Fixed some broken web links in source code comments and elsewhere. * Replaced `http` protocol in URLS with `https` wherever supported - mainly in source code comments & documentation. From b8f2290c91d85995a9eaccd42ebc414ec3e9de63 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 31 Dec 2021 20:09:37 +0000 Subject: [PATCH 059/330] Bump version information to v2.19.0 build 263 --- Src/VCodeSnip.vi | 4 ++-- Src/VCodeSnipPortable.vi | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Src/VCodeSnip.vi b/Src/VCodeSnip.vi index bc41d494f..b9320937b 100644 --- a/Src/VCodeSnip.vi +++ b/Src/VCodeSnip.vi @@ -8,8 +8,8 @@ [Fixed File Info] -File Version #=4, 18, 1, 262 -Product Version #=4, 18, 1, 0 +File Version #=4, 19, 0, 263 +Product Version #=4, 19, 0, 0 File OS=4 File Type=1 File Sub-Type=0 diff --git a/Src/VCodeSnipPortable.vi b/Src/VCodeSnipPortable.vi index 387b2f31a..6b8647290 100644 --- a/Src/VCodeSnipPortable.vi +++ b/Src/VCodeSnipPortable.vi @@ -8,8 +8,8 @@ [Fixed File Info] -File Version #=4, 18, 1, 262 -Product Version #=4, 18, 1, 0 +File Version #=4, 19, 0, 263 +Product Version #=4, 19, 0, 0 File OS=4 File Type=1 File Sub-Type=0 From c1b073f40f29c2e734734c2044e8a014469c61cd Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 2 Jan 2022 10:40:32 +0000 Subject: [PATCH 060/330] Update 3rd party PJSysInfo to v5.8.0 Fixes #43 --- Src/3rdParty/PJSysInfo.pas | 162 +++++++++++++++---------------------- 1 file changed, 63 insertions(+), 99 deletions(-) diff --git a/Src/3rdParty/PJSysInfo.pas b/Src/3rdParty/PJSysInfo.pas index 4b0a2dbd3..fa4cd593b 100644 --- a/Src/3rdParty/PJSysInfo.pas +++ b/Src/3rdParty/PJSysInfo.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 https://mozilla.org/MPL/2.0/ + * obtain one at http://mozilla.org/MPL/2.0/ * * Copyright (C) 2001-2021, Peter Johnson (@delphidabbler). * - * $Rev: 2079 $ - * $Date: 2021-11-27 14:29:47 +0000 (Sat, 27 Nov 2021) $ + * $Rev: 2082 $ + * $Date: 2022-01-01 10:12:03 +0000 (Sat, 01 Jan 2022) $ * * This unit contains various static classes, constants, type definitions and * global variables for use in providing information about the host computer and @@ -1247,6 +1247,18 @@ implementation // - 10.0.22463.1000 (RSPRERELEASE) Win11v21H2PreRel5Build = 22468; // Windows 11 version 21H2 // - 10.0.22468.1000 (RSPRERELEASE) + Win11v21H2PreRel6Build = 22471; // Windows 11 version 21H2 + // - 10.0.22471.1000 (RSPRERELEASE) + Win11v21H2PreRel7Build = 22478; // Windows 11 version 21H2 + // - 10.0.22478.1000 (RSPRERELEASE) + Win11v21H2PreRel8Build = 22483; // Windows 11 version 21H2 + // - 10.0.22483.1000 (RSPRERELEASE) + Win11v21H2PreRel9Build = 22489; // Windows 11 version 21H2 + // - 10.0.22489.1000 (RSPRERELEASE) + Win11v21H2PreRel10Build = 22494;// Windows 11 version 21H2 + // - 10.0.22494.1000 (RSPRERELEASE) + Win11v21H2PreRel11Build = 22509;// Windows 11 version 21H2 + // - 10.0.22509.1000 (RSPRERELEASE) Win11FirstBuild = Win11DevBuild; // First build number of Windows 11 @@ -1398,6 +1410,28 @@ function IsBuildNumber(BuildNumber: DWORD): Boolean; Result := VerifyVersionInfo(POSVI, VER_BUILDNUMBER, ConditionalMask); 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 the OS has the given product type. // Assumes VerifyVersionInfo & VerSetConditionMask APIs functions are available function IsWindowsProductType(ProductType: Byte): Boolean; @@ -1631,13 +1665,6 @@ procedure InitPlatformIdEx; GetProductInfo: TGetProductInfo; // pointer to GetProductInfo API function SI: TSystemInfo; // structure from GetSystemInfo API call - // Return name of Windows Server 2019 insider preview release for given build - // number. Build must be a valid insider preview release number - function Win2019IPExtra(const Build: Integer): string; - begin - Result := Format('Insider Preview Build %d', [Build]); - end; - // Get OS's revision number from registry. function GetOSRevisionNumber(const IsNT: Boolean): Integer; begin @@ -1824,8 +1851,8 @@ procedure InitPlatformIdEx; // release of Win 11 -- well hidden eh?! InternalBuildNumber := Win11v21H2Build; case InternalBuildNumber of - 194: - // First public release of Windows 11 + 194..MaxInt: + // Public releases of Windows 11 have build number >= 194 InternalExtraUpdateInfo := 'Version 21H2'; 51, 65, 71, 100, 120, 132, 168: InternalExtraUpdateInfo := Format( @@ -1844,41 +1871,18 @@ procedure InitPlatformIdEx; ); end; end - else if IsBuildNumber(Win11v21H2PreRel1Build) then - begin - InternalBuildNumber := Win11v21H2PreRel1Build; - InternalExtraUpdateInfo := Format( - 'Version 21H2 [RSPRERELEASE v10.0.%d.%d]', - [InternalBuildNumber, InternalRevisionNumber] - ); - end - else if IsBuildNumber(Win11v21H2PreRel2Build) then - begin - InternalBuildNumber := Win11v21H2PreRel2Build; - InternalExtraUpdateInfo := Format( - 'Version 21H2 [RSPRERELEASE v10.0.%d.%d]', - [InternalBuildNumber, InternalRevisionNumber] - ); - end - else if IsBuildNumber(Win11v21H2PreRel3Build) then - begin - InternalBuildNumber := Win11v21H2PreRel3Build; - InternalExtraUpdateInfo := Format( - 'Version 21H2 [RSPRERELEASE v10.0.%d.%d]', - [InternalBuildNumber, InternalRevisionNumber] - ); - end - else if IsBuildNumber(Win11v21H2PreRel4Build) then - begin - InternalBuildNumber := Win11v21H2PreRel4Build; - InternalExtraUpdateInfo := Format( - 'Version 21H2 [RSPRERELEASE v10.0.%d.%d]', - [InternalBuildNumber, InternalRevisionNumber] - ); - end - else if IsBuildNumber(Win11v21H2PreRel5Build) then + else if FindBuildNumberFrom( + [ + Win11v21H2PreRel1Build, Win11v21H2PreRel2Build, + Win11v21H2PreRel3Build, Win11v21H2PreRel4Build, + Win11v21H2PreRel5Build, Win11v21H2PreRel6Build, + Win11v21H2PreRel7Build, Win11v21H2PreRel8Build, + Win11v21H2PreRel9Build, Win11v21H2PreRel10Build, + Win11v21H2PreRel11Build + ], + InternalBuildNumber + ) then begin - InternalBuildNumber := Win11v21H2PreRel5Build; InternalExtraUpdateInfo := Format( 'Version 21H2 [RSPRERELEASE v10.0.%d.%d]', [InternalBuildNumber, InternalRevisionNumber] @@ -1925,60 +1929,20 @@ procedure InitPlatformIdEx; InternalBuildNumber := Win2016v1803Build; InternalExtraUpdateInfo := 'Version 1803'; end - else if IsBuildNumber(Win2019IP180320Build) then - begin - InternalBuildNumber := Win2019IP180320Build; - InternalExtraUpdateInfo := Win2019IPExtra(Win2019IP180320Build); - end - else if IsBuildNumber(Win2019IP180324Build) then - begin - InternalBuildNumber := Win2019IP180324Build; - InternalExtraUpdateInfo := Win2019IPExtra(Win2019IP180324Build); - end - else if IsBuildNumber(Win2019IP180515Build) then - begin - InternalBuildNumber := Win2019IP180515Build; - InternalExtraUpdateInfo := Win2019IPExtra(Win2019IP180515Build); - end - else if IsBuildNumber(Win2019IP180619Build) then - begin - InternalBuildNumber := Win2019IP180619Build; - InternalExtraUpdateInfo := Win2019IPExtra(Win2019IP180619Build); - end - else if IsBuildNumber(Win2019IP180710Build) then + else if FindBuildNumberFrom( + [ + Win2019IP180320Build, Win2019IP180324Build, + Win2019IP180515Build, Win2019IP180619Build, + Win2019IP180710Build, Win2019IP180716Build, + Win2019IP180731Build, Win2019IP180814Build, + Win2019IP180821Build, Win2019IP180828Build + ], + InternalBuildNumber + ) then begin - InternalBuildNumber := Win2019IP180710Build; - InternalExtraUpdateInfo := Win2019IPExtra(Win2019IP180710Build); - end - else if IsBuildNumber(Win2019IP180716Build) then - begin - InternalBuildNumber := Win2019IP180716Build; - InternalExtraUpdateInfo := Win2019IPExtra(Win2019IP180716Build); - end - else if IsBuildNumber(Win2019IP180716Build) then - begin - InternalBuildNumber := Win2019IP180716Build; - InternalExtraUpdateInfo := Win2019IPExtra(Win2019IP180716Build); - end - else if IsBuildNumber(Win2019IP180731Build) then - begin - InternalBuildNumber := Win2019IP180731Build; - InternalExtraUpdateInfo := Win2019IPExtra(Win2019IP180731Build); - end - else if IsBuildNumber(Win2019IP180814Build) then - begin - InternalBuildNumber := Win2019IP180814Build; - InternalExtraUpdateInfo := Win2019IPExtra(Win2019IP180814Build); - end - else if IsBuildNumber(Win2019IP180821Build) then - begin - InternalBuildNumber := Win2019IP180821Build; - InternalExtraUpdateInfo := Win2019IPExtra(Win2019IP180821Build); - end - else if IsBuildNumber(Win2019IP180828Build) then - begin - InternalBuildNumber := Win2019IP180828Build; - InternalExtraUpdateInfo := Win2019IPExtra(Win2019IP180828Build); + InternalExtraUpdateInfo := Format( + 'Insider Preview Build %d', [InternalBuildNumber] + ); end else if IsBuildNumber(Win2019v1809Build) then begin From 4887bd176baef2db74a20d13fb661abf8f7eb9e0 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 2 Jan 2022 16:27:50 +0000 Subject: [PATCH 061/330] Add new methods to get default font sizes. --- Src/UFontHelper.pas | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Src/UFontHelper.pas b/Src/UFontHelper.pas index a149d6ada..5e40c0296 100644 --- a/Src/UFontHelper.pas +++ b/Src/UFontHelper.pas @@ -80,6 +80,8 @@ TFontHelper = class(TNoConstructObject) @return Handle to cloned font. Caller is responsible for releasing the handle. } + class function GetDefaultFontSize: Integer; + class function GetDefaultContentFontSize: Integer; strict private const FallbackFontName = 'Arial'; // Fallback font name @@ -158,6 +160,32 @@ class function TFontHelper.FontExists(const FontName: string): Boolean; Result := Screen.Fonts.IndexOf(FontName) >= 0; end; +class function TFontHelper.GetDefaultContentFontSize: Integer; +var + Font: TFont; +begin + Font := TFont.Create; + try + SetContentFont(Font); + Result := Font.Size; + finally + Font.Free; + end; +end; + +class function TFontHelper.GetDefaultFontSize: Integer; +var + Font: TFont; +begin + Font := TFont.Create; + try + SetDefaultFont(Font); + Result := Font.Size; + finally + Font.Free; + end; +end; + class function TFontHelper.IsInCommonFontSizeRange( const FontSize: Integer): Boolean; begin From 042dd8a6f2c8ad7b1e7ac7b579681864915bdc0d Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 2 Jan 2022 16:31:21 +0000 Subject: [PATCH 062/330] Add DetailFontSize preference & change font size defaults Change font size defaults for overview pane and detail pane to get defaults that apply to underlying OS, rather than hard wiring them. Setting either detail pane or overview pane font size preferences to an out of range value resets the preference to its default value. --- Src/UPreferences.pas | 63 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/Src/UPreferences.pas b/Src/UPreferences.pas index dc843214f..59bd8e09a 100644 --- a/Src/UPreferences.pas +++ b/Src/UPreferences.pas @@ -183,6 +183,14 @@ interface property OverviewFontSize: Integer read GetOverviewFontSize write SetOverviewFontSize; + /// Gets size of base font used in detail pane. + function GetDetailFontSize: Integer; + /// Sets size of base font used in detail pane. + procedure SetDetailFontSize(const Value: Integer); + /// Size of base font used in detail pane. + property DetailFontSize: Integer + read GetDetailFontSize write SetDetailFontSize; + /// Gets colour used for background of source code in main /// display. function GetSourceCodeBGColour: TColor; @@ -289,7 +297,7 @@ implementation SysUtils, // Project Hiliter.UAttrs, Hiliter.UPersist, IntfCommon, UExceptions, UColours, - USettings; + UFontHelper, USettings; type @@ -342,6 +350,8 @@ TPreferences = class(TInterfacedObject, /// Records size of font used in overview pane tree view. /// fOverviewFontSize: Integer; + /// Records size of font used in details pane. + fDetailFontSize: Integer; /// Records colour used for background of source code in main /// display. fSourceCodeBGColour: TColor; @@ -366,6 +376,11 @@ TPreferences = class(TInterfacedObject, /// Information describing snippet detail page customisations. /// fPageStructures: TSnippetPageStructures; + /// Returns default font size for overview pane tree view. + /// + function DefaultOverviewFontSize: Integer; + /// Returns default font size for details pane. + function DefaultDetailFontSize: Integer; public /// Constructs a new object instance. constructor Create; @@ -510,6 +525,14 @@ TPreferences = class(TInterfacedObject, /// Method of IPreferences. procedure SetOverviewFontSize(const Value: Integer); + /// Gets size of base font used in detail pane. + /// Method of IPreferences. + function GetDetailFontSize: Integer; + + /// Sets size of base font used in detail pane. + /// Method of IPreferences. + procedure SetDetailFontSize(const Value: Integer); + /// Gets colour used for background of source code in main /// display. /// Method of IPreferences. @@ -677,6 +700,7 @@ procedure TPreferences.Assign(const Src: IInterface); Self.fDBHeadingColours[True] := SrcPref.DBHeadingColours[True]; Self.fDBHeadingCustomColours[True] := SrcPref.DBHeadingCustomColours[True]; Self.fOverviewFontSize := SrcPref.OverviewFontSize; + Self.fDetailFontSize := SrcPref.DetailFontSize; Self.fSourceCodeBGColour := SrcPref.SourceCodeBGColour; Self.fSourceCodeBGCustomColours := SrcPref.SourceCodeBGCustomColours; Self.fPrinterOptions := SrcPref.PrinterOptions; @@ -701,6 +725,16 @@ constructor TPreferences.Create; TDefaultPageStructures.SetDefaults(fPageStructures); end; +function TPreferences.DefaultDetailFontSize: Integer; +begin + Result := TFontHelper.GetDefaultContentFontSize; +end; + +function TPreferences.DefaultOverviewFontSize: Integer; +begin + Result := TFontHelper.GetDefaultFontSize; +end; + destructor TPreferences.Destroy; begin fPageStructures.Free; @@ -723,6 +757,11 @@ function TPreferences.GetDBHeadingCustomColours( Result := fDBHeadingCustomColours[UserDefined]; end; +function TPreferences.GetDetailFontSize: Integer; +begin + Result := fDetailFontSize; +end; + function TPreferences.GetHiliteAttrs: IHiliteAttrs; begin Result := fHiliteAttrs; @@ -830,6 +869,14 @@ procedure TPreferences.SetDBHeadingCustomColours(UserDefined: Boolean; fDBHeadingCustomColours[UserDefined] := Value; end; +procedure TPreferences.SetDetailFontSize(const Value: Integer); +begin + if TFontHelper.IsInCommonFontSizeRange(Value) then + fDetailFontSize := Value + else + fDetailFontSize := DefaultDetailFontSize; +end; + procedure TPreferences.SetHiliteAttrs(const Attrs: IHiliteAttrs); begin (fHiliteAttrs as IAssignable).Assign(Attrs); @@ -852,7 +899,10 @@ procedure TPreferences.SetNamedHiliteAttrs(NamedHiliteAttrs: INamedHiliteAttrs); procedure TPreferences.SetOverviewFontSize(const Value: Integer); begin - fOverviewFontSize := Value; + if TFontHelper.IsInCommonFontSizeRange(Value) then + fOverviewFontSize := Value + else + fOverviewFontSize := DefaultOverviewFontSize; end; procedure TPreferences.SetOverviewStartState(const Value: TOverviewStartState); @@ -945,6 +995,7 @@ function TPreferencesPersist.Clone: IInterface; NewPref.DBHeadingColours[True] := Self.fDBHeadingColours[True]; NewPref.DBHeadingCustomColours[True] := Self.fDBHeadingCustomColours[True]; NewPref.OverviewFontSize := Self.fOverviewFontSize; + NewPref.DetailFontSize := Self.fDetailFontSize; NewPref.SourceCodeBGColour := Self.fSourceCodeBGColour; NewPref.SourceCodeBGCustomColours := Self.fSourceCodeBGCustomColours; NewPref.PrinterOptions := Self.fPrinterOptions; @@ -999,7 +1050,12 @@ constructor TPreferencesPersist.Create; fDBHeadingCustomColours[True] := Storage.GetStrings( 'UserDBHeadingCustomColourCount', 'UserDBHeadingCustomColour%d' ); - fOverviewFontSize := Storage.GetInteger('OverviewFontSize', 9); + fOverviewFontSize := Storage.GetInteger( + 'OverviewFontSize', DefaultOverviewFontSize + ); + fDetailFontSize := Storage.GetInteger( + 'DetailFontSize', DefaultDetailFontSize + ); fSourceCodeBGCustomColours := Storage.GetStrings( 'SourceCodeBGCustomColourCount', 'SourceCodeBGCustomColour%d' ); @@ -1071,6 +1127,7 @@ destructor TPreferencesPersist.Destroy; Storage.SetInteger('MainDBHeadingColour', fDBHeadingColours[False]); Storage.SetInteger('UserDBHeadingColour', fDBHeadingColours[True]); Storage.SetInteger('OverviewFontSize', fOverviewFontSize); + Storage.SetInteger('DetailFontSize', fDetailFontSize); Storage.SetInteger('SourceCodeBGColour', fSourceCodeBGColour); Storage.SetStrings( 'MainDBHeadingCustomColourCount', From 410543eaa8a446a9a99aca9f227f8e94cff9f3d6 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 2 Jan 2022 16:33:33 +0000 Subject: [PATCH 063/330] Add detail pane font size option & refactor font size code OnChange event handler for font size combo boxes is now shared between the two. This required that current font size was store in each combo box's Tag property rather than in separate field of frame. --- Src/FrDisplayPrefs.dfm | 18 ++++++- Src/FrDisplayPrefs.pas | 115 +++++++++++++++++++++++------------------ 2 files changed, 83 insertions(+), 50 deletions(-) diff --git a/Src/FrDisplayPrefs.dfm b/Src/FrDisplayPrefs.dfm index 5f060c451..e28212e79 100644 --- a/Src/FrDisplayPrefs.dfm +++ b/Src/FrDisplayPrefs.dfm @@ -40,6 +40,14 @@ inherited DisplayPrefsFrame: TDisplayPrefsFrame 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 cbOverviewTree: TComboBox Left = 192 Top = 2 @@ -80,6 +88,14 @@ inherited DisplayPrefsFrame: TDisplayPrefsFrame Width = 57 Height = 21 TabOrder = 4 - OnChange = cbOverviewFontSizeChange + 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 de3b90943..0109107c2 100644 --- a/Src/FrDisplayPrefs.pas +++ b/Src/FrDisplayPrefs.pas @@ -37,14 +37,15 @@ TDisplayPrefsFrame = class(TPrefsBaseFrame) lblSourceBGColour: TLabel; lblOverviewFontSize: TLabel; cbOverviewFontSize: TComboBox; + lblDetailFontSize: TLabel; + cbDetailFontSize: TComboBox; procedure chkHideEmptySectionsClick(Sender: TObject); procedure btnDefColoursClick(Sender: TObject); - procedure cbOverviewFontSizeChange(Sender: TObject); + procedure FontSizeChange(Sender: TObject); strict private var /// Flag indicating if changes affect UI. fUIChanged: Boolean; - fOverviewFontSize: Integer; fMainColourBox: TColorBoxEx; fMainColourDlg: TColorDialogEx; fUserColourBox: TColorBoxEx; @@ -63,7 +64,7 @@ TDisplayPrefsFrame = class(TPrefsBaseFrame) function CreateCustomColourBox(const ColourDlg: TColorDialogEx): TColorBoxEx; procedure ColourBoxChangeHandler(Sender: TObject); - procedure PopulateFontSizeCombo; + procedure PopulateFontSizeCombos; public constructor Create(AOwner: TComponent); override; {Class constructor. Sets up frame and populates controls. @@ -137,8 +138,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); - fOverviewFontSize := Prefs.OverviewFontSize; - cbOverviewFontSize.Text := IntToStr(fOverviewFontSize); + 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; @@ -149,14 +152,14 @@ procedure TDisplayPrefsFrame.ArrangeControls; [ lblOverviewTree, chkHideEmptySections, chkSnippetsInNewTab, lblMainColour, lblUserColour, lblSourceBGColour, btnDefColours, - lblOverviewFontSize + lblOverviewFontSize, lblDetailFontSize ], 0 ); TCtrlArranger.AlignLefts( [ cbOverviewTree, fMainColourBox, fUserColourBox, fSourceBGColourBox, - cbOverviewFontSize + cbOverviewFontSize, cbDetailFontSize ], TCtrlArranger.RightOf( [lblOverviewTree, lblMainColour, lblUserColour, lblSourceBGColour], @@ -165,11 +168,11 @@ 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( @@ -187,6 +190,10 @@ procedure TDisplayPrefsFrame.ArrangeControls; TCtrlArranger.BottomOf(btnDefColours, 12), [lblOverviewFontSize, cbOverviewFontSize] ); + TCtrlArranger.AlignVCentres( + TCtrlArranger.BottomOf(cbOverviewFontSize, 8), + [lblDetailFontSize, cbDetailFontSize] + ); chkHideEmptySections.Width := Self.Width - 16; chkSnippetsInNewTab.Width := Self.Width - 16; end; @@ -201,43 +208,6 @@ procedure TDisplayPrefsFrame.btnDefColoursClick(Sender: TObject); fUIChanged := True; end; -procedure TDisplayPrefsFrame.cbOverviewFontSizeChange(Sender: TObject); -var - Size: Integer; // font size entered by user -begin - inherited; - // Do nothing if combo box text field cleared - if cbOverviewFontSize.Text = '' then - Exit; - if TryStrToInt(cbOverviewFontSize.Text, Size) then - begin - if TFontHelper.IsInCommonFontSizeRange(Size) then - begin - // Combo has valid value entered: update - fOverviewFontSize := Size; - fUIChanged := True; - end - else - begin - // Font size out of range - TMessageBox.Error( - ParentForm, - Format( - sErrBadOverviewFontRange, - [TFontHelper.CommonFontSizes.Min, TFontHelper.CommonFontSizes.Max] - ) - ); - cbOverviewFontSize.Text := IntToStr(fOverviewFontSize); - end; - end - else - begin - // Combo has invalid value: say so - TMessageBox.Error(ParentForm, sErrBadOverviewFontSize); - cbOverviewFontSize.Text := IntToStr(fOverviewFontSize); - end; -end; - procedure TDisplayPrefsFrame.chkHideEmptySectionsClick(Sender: TObject); {Handles clicks on "Hide Empty Sections" check box. Flags UI preferences has having changed. @@ -287,7 +257,7 @@ constructor TDisplayPrefsFrame.Create(AOwner: TComponent); fSourceBGColourBox.TabOrder := 5; lblSourceBGColour.FocusControl := fSourceBGColourBox; - PopulateFontSizeCombo; + PopulateFontSizeCombos; end; function TDisplayPrefsFrame.CreateCustomColourBox( @@ -330,7 +300,10 @@ procedure TDisplayPrefsFrame.Deactivate(const Prefs: IPreferences); Prefs.SourceCodeBGCustomColours.CopyFrom( fSourceBGColourDlg.CustomColors, True ); - Prefs.OverviewFontSize := StrToIntDef(cbOverviewFontSize.Text, 8); + // 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; @@ -344,6 +317,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. @@ -372,9 +386,12 @@ function TDisplayPrefsFrame.OverviewTreeStateDesc( Result := cOTSStartStates[State]; end; -procedure TDisplayPrefsFrame.PopulateFontSizeCombo; +procedure TDisplayPrefsFrame.PopulateFontSizeCombos; begin + cbOverviewFontSize.Clear; TFontHelper.ListCommonFontSizes(cbOverviewFontSize.Items); + cbDetailFontSize.Clear; + TFontHelper.ListCommonFontSizes(cbDetailFontSize.Items); end; procedure TDisplayPrefsFrame.SelectOverviewTreeState( From c7a09ed992bbc5d72ba263bf05c3f8295019c627 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 2 Jan 2022 16:37:41 +0000 Subject: [PATCH 064/330] Modify CSS generation to enable user specified font size. Adjustments had to be made to preserve ratio of sizes of baseline content font in relation to heading a mono font styles. --- Src/FrDetailView.pas | 76 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 63 insertions(+), 13 deletions(-) diff --git a/Src/FrDetailView.pas b/Src/FrDetailView.pas index 06da2e9bf..78d56a712 100644 --- a/Src/FrDetailView.pas +++ b/Src/FrDetailView.pas @@ -107,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, @@ -119,27 +119,53 @@ 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 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); + CSSFont.Assign(ContentFont); AddProperty(TCSS.FontProps(CSSFont)); AddProperty(TCSS.BackgroundColorProp(clWindow)); end; + with CSSBuilder.Selectors['code'] do + begin + CSSFont.Assign(MonoFont); + AddProperty(TCSS.FontProps(CSSFont)); + end; // Set table to use required font with CSSBuilder.AddSelector('table') do begin - TFontHelper.SetContentFont(CSSFont); + CSSFont.Assign(ContentFont); AddProperty(TCSS.FontProps(CSSFont)); AddProperty(TCSS.BackgroundColorProp(clBorder)); end; @@ -149,8 +175,10 @@ procedure TDetailViewFrame.BuildCSS(const CSSBuilder: TCSSBuilder); // Sets H1 heading font size and border with CSSBuilder.AddSelector('h1') do begin - TFontHelper.SetContentFont(CSSFont); - CSSFont.Size := CSSFont.Size + 2; + CSSFont.Assign(ContentFont); + CSSFont.Size := CSSFont.Size + Max( + Round(2 * ContentFontScaleFactor * CSSFont.Size), 2 + ); CSSFont.Style := [fsBold]; AddProperty(TCSS.FontProps(CSSFont)); AddProperty(TCSS.BorderProp(cssBottom, 1, cbsSolid, clBorder)); @@ -158,22 +186,42 @@ procedure TDetailViewFrame.BuildCSS(const CSSBuilder: TCSSBuilder); // Sets H2 heading font size and border with CSSBuilder.AddSelector('h2') do begin - TFontHelper.SetContentFont(CSSFont); + CSSFont.Assign(ContentFont); CSSFont.Style := [fsBold]; AddProperty(TCSS.FontProps(CSSFont)); end; // Set H2 heading font for use in rendered active text with CSSBuilder.AddSelector('.active-text h2') do begin - TFontHelper.SetContentFont(CSSFont); + CSSFont.Assign(ContentFont); CSSFont.Style := [fsBold]; - CSSFont.Size := CSSFont.Size + 1; + CSSFont.Size := CSSFont.Size + Max( + Round(ContentFontScaleFactor * CSSFont.Size), 1 + ); + AddProperty(TCSS.FontProps(CSSFont)); + end; + // Set CODE tag within H2 heading for use in rendered active text + with CSSBuilder.AddSelector('.active-text h2 code') do + begin + CSSFont.Assign(MonoFont); + CSSFont.Style := [fsBold]; + CSSFont.Size := CSSFont.Size + Max( + Round(ContentFontScaleFactor * CSSFont.Size), 1 + ); AddProperty(TCSS.FontProps(CSSFont)); end; // 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.Assign(ContentFont); + CSSFont.Style := [fsBold]; + AddProperty(TCSS.FontProps(CSSFont)); + end; + // Set CODE within H2 heading font for use in rendered active text in + // snippet list table + with CSSBuilder.AddSelector('.snippet-list .active-text h2 code') do + begin + CSSFont.Assign(MonoFont); CSSFont.Style := [fsBold]; AddProperty(TCSS.FontProps(CSSFont)); end; @@ -187,8 +235,8 @@ procedure TDetailViewFrame.BuildCSS(const CSSBuilder: TCSSBuilder); // Sets CSS for style of New Tab text with CSSBuilder.AddSelector('#newtab') do begin - TFontHelper.SetContentFont(CSSFont); - CSSFont.Size := 36; + CSSFont.Assign(ContentFont); + CSSFont.Size := 36 + Round(36 * ContentFontScaleFactor); CSSFont.Color := clNewTabText; AddProperty(TCSS.FontProps(CSSFont)); end; @@ -218,6 +266,8 @@ procedure TDetailViewFrame.BuildCSS(const CSSBuilder: TCSSBuilder); AddProperty(TCSS.FontWeightProp(cfwNormal)); end; finally + ContentFont.Free; + MonoFont.Free; CSSFont.Free; end; end; From 3771b42880e6962f33aa967b2868ef7aaf465a09 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 2 Jan 2022 17:19:21 +0000 Subject: [PATCH 065/330] Change to restore default font size when bad value assigned. Setting highlighter font size preference to an out of range value resets the preference to its default value. --- Src/Hiliter.UAttrs.pas | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Src/Hiliter.UAttrs.pas b/Src/Hiliter.UAttrs.pas index b137e6f9e..24a91dd25 100644 --- a/Src/Hiliter.UAttrs.pas +++ b/Src/Hiliter.UAttrs.pas @@ -107,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. /// @@ -307,7 +311,10 @@ 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 } From 6d58923d2e5b773eee560a63e8c8ff73abcd406f Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 2 Jan 2022 17:21:08 +0000 Subject: [PATCH 066/330] Condense dialogue controls & add info label Add an information label explaining that Syntax Highlighter preferences page must be used to set source code font size. --- Src/FrDisplayPrefs.dfm | 13 +++++++++++++ Src/FrDisplayPrefs.pas | 14 ++++++++++---- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/Src/FrDisplayPrefs.dfm b/Src/FrDisplayPrefs.dfm index e28212e79..80a7370ef 100644 --- a/Src/FrDisplayPrefs.dfm +++ b/Src/FrDisplayPrefs.dfm @@ -48,6 +48,19 @@ inherited DisplayPrefsFrame: TDisplayPrefsFrame 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 diff --git a/Src/FrDisplayPrefs.pas b/Src/FrDisplayPrefs.pas index 0109107c2..83ed29e2c 100644 --- a/Src/FrDisplayPrefs.pas +++ b/Src/FrDisplayPrefs.pas @@ -39,6 +39,7 @@ TDisplayPrefsFrame = class(TPrefsBaseFrame) cbOverviewFontSize: TComboBox; lblDetailFontSize: TLabel; cbDetailFontSize: TComboBox; + lblHiliterInfo: TLabel; procedure chkHideEmptySectionsClick(Sender: TObject); procedure btnDefColoursClick(Sender: TObject); procedure FontSizeChange(Sender: TObject); @@ -152,7 +153,7 @@ procedure TDisplayPrefsFrame.ArrangeControls; [ lblOverviewTree, chkHideEmptySections, chkSnippetsInNewTab, lblMainColour, lblUserColour, lblSourceBGColour, btnDefColours, - lblOverviewFontSize, lblDetailFontSize + lblOverviewFontSize, lblDetailFontSize, lblHiliterInfo ], 0 ); @@ -176,15 +177,15 @@ procedure TDisplayPrefsFrame.ArrangeControls; [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), @@ -194,6 +195,11 @@ procedure TDisplayPrefsFrame.ArrangeControls; 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; From f5a2b5f8ef268f935fbd0cddd56409f1951cc776 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Mon, 3 Jan 2022 10:37:46 +0000 Subject: [PATCH 067/330] Update help file re Display Preference Page changes Noted new Display pane font size combo box --- Src/Help/HTML/dlg_prefs_display.htm | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Src/Help/HTML/dlg_prefs_display.htm b/Src/Help/HTML/dlg_prefs_display.htm index 425181740..0dbb43732 100644 --- a/Src/Help/HTML/dlg_prefs_display.htm +++ b/Src/Help/HTML/dlg_prefs_display.htm @@ -97,6 +97,21 @@

    >overview pane's tree view using the Overview tree font size combo box. +
  • +

    + 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. +

    +
  • From c79be6c466d0dae01c20b8977a91c536e39ad998 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 6 Jan 2022 09:52:46 +0000 Subject: [PATCH 068/330] Add feature to delete user database. New dialogue box unit to get permission to delete, with associated HTML frame contents. Update UUserDBMgr to add new method to perform deletion. Add new "Delete User Database" action and menu option to main form. Fixes #15 --- Src/CodeSnip.dpr | 5 +- Src/CodeSnip.dproj | 3 + Src/FmDeleteUserDBDlg.dfm | 48 +++++++++++++++ Src/FmDeleteUserDBDlg.pas | 108 +++++++++++++++++++++++++++++++++ Src/FmMain.dfm | 18 +++++- Src/FmMain.pas | 14 ++++- Src/HTML.hrc | 5 +- Src/Res/HTML/dlg-dbdelete.html | 48 +++++++++++++++ Src/UUserDBMgr.pas | 19 +++++- 9 files changed, 258 insertions(+), 10 deletions(-) create mode 100644 Src/FmDeleteUserDBDlg.dfm create mode 100644 Src/FmDeleteUserDBDlg.pas create mode 100644 Src/Res/HTML/dlg-dbdelete.html diff --git a/Src/CodeSnip.dpr b/Src/CodeSnip.dpr index da093abaa..d608babf8 100644 --- a/Src/CodeSnip.dpr +++ b/Src/CodeSnip.dpr @@ -3,7 +3,7 @@ * 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) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2022, Peter Johnson (gravatar.com/delphidabbler). * * CodeSnip application project file. } @@ -369,7 +369,8 @@ uses UWindowSettings in 'UWindowSettings.pas', UXMLDocConsts in 'UXMLDocConsts.pas', UXMLDocHelper in 'UXMLDocHelper.pas', - UXMLDocumentEx in 'UXMLDocumentEx.pas'; + UXMLDocumentEx in 'UXMLDocumentEx.pas', + FmDeleteUserDBDlg in 'FmDeleteUserDBDlg.pas' {DeleteUserDBDlg}; // Include resources {$Resource ExternalObj.tlb} // Type library file diff --git a/Src/CodeSnip.dproj b/Src/CodeSnip.dproj index f1ccf4093..e1a91d26d 100644 --- a/Src/CodeSnip.dproj +++ b/Src/CodeSnip.dproj @@ -572,6 +572,9 @@ + +
    DeleteUserDBDlg
    +
    Base diff --git a/Src/FmDeleteUserDBDlg.dfm b/Src/FmDeleteUserDBDlg.dfm new file mode 100644 index 000000000..9f30a2285 --- /dev/null +++ b/Src/FmDeleteUserDBDlg.dfm @@ -0,0 +1,48 @@ +inherited DeleteUserDBDlg: TDeleteUserDBDlg + Caption = 'DeleteUserDBDlg' + 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..3aee061a4 --- /dev/null +++ b/Src/FmDeleteUserDBDlg.pas @@ -0,0 +1,108 @@ +{ + * 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 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 + /// Protected constructor that sets up form. + 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: not deleting'; +begin + inherited; + fPermissionGranted := IsValidPassword; + if not fPermissionGranted then + begin + TMessageBox.Error(Self, sBadPassword); + 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; +begin + with InternalCreate(AOwner) do + try + ShowModal; + Result := fPermissionGranted; + finally + 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/FmMain.dfm b/Src/FmMain.dfm index 0128f4c4f..e2441f840 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 @@ -866,6 +866,14 @@ inherited MainForm: TMainForm ' 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 + end end object mnuMain: TMainMenu Images = ilMain @@ -1093,6 +1101,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' diff --git a/Src/FmMain.pas b/Src/FmMain.pas index eb1ed4992..16a67871b 100644 --- a/Src/FmMain.pas +++ b/Src/FmMain.pas @@ -3,7 +3,7 @@ * 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) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2022, Peter Johnson (gravatar.com/delphidabbler). * * Application's main form. Handles the program's main window display and user * interaction. @@ -69,6 +69,7 @@ TMainForm = class(THelpAwareForm) actCopySource: TAction; actDeleteCategory: TAction; actDeleteSnippet: TAction; + actDeleteUserDatabase: TAction; actDuplicateSnippet: TAction; actEditSnippet: TAction; actExit: TFileExit; @@ -138,6 +139,7 @@ TMainForm = class(THelpAwareForm) miDatabase: TMenuItem; miDeleteCategory: TMenuItem; miDeleteSnippet: TMenuItem; + miDeleteUserDatabase: TMenuItem; miDuplicateSnippet: TMenuItem; miEdit: TMenuItem; miEditSnippet: TMenuItem; @@ -195,6 +197,7 @@ TMainForm = class(THelpAwareForm) miSpacer17: TMenuItem; miSpacer18: TMenuItem; miSpacer20: TMenuItem; + miSpacer21: TMenuItem; miSWAGImport: TMenuItem; miTestCompile: TMenuItem; miTools: TMenuItem; @@ -294,6 +297,9 @@ TMainForm = class(THelpAwareForm) /// Attempts to delete the current user defined snippet from the /// database. procedure actDeleteSnippetExecute(Sender: TObject); + /// Requests permission then attempts to delete the user defined + /// snippets database. + procedure actDeleteUserDatabaseExecute(Sender: TObject); /// Displays a dialogue box that can be used to duplicate the /// selected snippet. procedure actDuplicateSnippetExecute(Sender: TObject); @@ -713,6 +719,12 @@ procedure TMainForm.actDeleteSnippetExecute(Sender: TObject); // display update is handled by snippets change event handler end; +procedure TMainForm.actDeleteUserDatabaseExecute(Sender: TObject); +begin + if TUserDBMgr.DeleteDatabase then + ReloadDatabase; +end; + procedure TMainForm.actDuplicateSnippetExecute(Sender: TObject); begin TUserDBMgr.DuplicateSnippet(fMainDisplayMgr.CurrentView); diff --git a/Src/HTML.hrc b/Src/HTML.hrc index 8bae1d50b..a731c26f2 100644 --- a/Src/HTML.hrc +++ b/Src/HTML.hrc @@ -2,7 +2,7 @@ # 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) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). +# Copyright (C) 2005-2022, Peter Johnson (gravatar.com/delphidabbler). # # Manifest file used to generate HTML.res resource file. @@ -38,9 +38,10 @@ 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 # Detail pane pages, scripts and CSS diff --git a/Src/Res/HTML/dlg-dbdelete.html b/Src/Res/HTML/dlg-dbdelete.html new file mode 100644 index 000000000..92e7f7c2f --- /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/UUserDBMgr.pas b/Src/UUserDBMgr.pas index 84b43f8c3..48ce2ecd8 100644 --- a/Src/UUserDBMgr.pas +++ b/Src/UUserDBMgr.pas @@ -3,7 +3,7 @@ * 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) 2008-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2008-2022, Peter Johnson (gravatar.com/delphidabbler). * * Implements a static class that manages user's interaction with user database. } @@ -96,6 +96,8 @@ TUserDBMgr = class(TNoConstructObject) /// Moves the user database to a new location specified by the /// user. class procedure MoveDatabase; + /// Deletes the user database, with permission. + class function DeleteDatabase: Boolean; end; @@ -104,7 +106,7 @@ implementation uses // Delphi - SysUtils, Dialogs, Windows {for inlining}, + SysUtils, Dialogs, Windows {for inlining}, IOUtils, // Project DB.UMain, DB.USnippet, FmAddCategoryDlg, FmDeleteCategoryDlg, FmDuplicateSnippetDlg, @@ -112,7 +114,8 @@ implementation {$IFNDEF PORTABLE} FmUserDataPathDlg, {$ENDIF} - FmWaitDlg, + FmDeleteUserDBDlg, FmWaitDlg, + UAppInfo, UConsts, UExceptions, UIStringList, UMessageBox, UOpenDialogEx, UOpenDialogHelper, UReservedCategories, USaveDialogEx, USnippetIDs, UUserDBBackup, UWaitForThreadUI; @@ -377,6 +380,16 @@ class procedure TUserDBMgr.DeleteACategory; end; end; +class function TUserDBMgr.DeleteDatabase: Boolean; +begin + if not TDeleteUserDBDlg.Execute(nil) then + Exit(False); + if not TDirectory.Exists(TAppInfo.UserDataDir) then + Exit(False); + TDirectory.Delete(TAppInfo.UserDataDir, True); + Result := True; +end; + class procedure TUserDBMgr.DeleteSnippet(ViewItem: IView); // Builds a list of snippet names from a given snippet ID list. From 3616165e376523c6186a0d210d1fdff63a7d03b9 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 6 Jan 2022 09:53:09 +0000 Subject: [PATCH 069/330] Tidy main form published fields --- Src/FmMain.pas | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Src/FmMain.pas b/Src/FmMain.pas index 16a67871b..6e56de410 100644 --- a/Src/FmMain.pas +++ b/Src/FmMain.pas @@ -56,6 +56,7 @@ TMainForm = class(THelpAwareForm) actAddFavourite: TAction; actAddSnippet: TAction; actBackupDatabase: TAction; + actBlog: TBrowseURL; actBugReport: TAction; actCloseAllDetailsTabs: TAction; actCloseDetailsTab: TAction; @@ -126,6 +127,7 @@ TMainForm = class(THelpAwareForm) miAddFavourite: TMenuItem; miAddSnippet: TMenuItem; miBackupDatabase: TMenuItem; + miBlog: TMenuItem; miCategories: TMenuItem; miCloseAllDetailsTabs: TMenuItem; miCloseDetailsTab: TMenuItem; @@ -239,8 +241,6 @@ TMainForm = class(THelpAwareForm) tbSpacer7: TToolButton; tbSpacer8: TToolButton; tbTestCompile: TToolButton; - actBlog: TBrowseURL; - miBlog: TMenuItem; /// Displays About Box. procedure actAboutExecute(Sender: TObject); /// Gets a new category from user and adds to database. From a2a0d2de886afe43b694bc3643a315c9e37ec722 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 6 Jan 2022 10:07:46 +0000 Subject: [PATCH 070/330] Fix caption of "delete user database" dialogue box --- Src/FmDeleteUserDBDlg.dfm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Src/FmDeleteUserDBDlg.dfm b/Src/FmDeleteUserDBDlg.dfm index 9f30a2285..1bd52b82a 100644 --- a/Src/FmDeleteUserDBDlg.dfm +++ b/Src/FmDeleteUserDBDlg.dfm @@ -1,5 +1,5 @@ inherited DeleteUserDBDlg: TDeleteUserDBDlg - Caption = 'DeleteUserDBDlg' + Caption = 'Delete User Database' ExplicitWidth = 474 ExplicitHeight = 375 PixelsPerInch = 96 From 4040a56aa349f5b692e655ba0db43765b2ba7c82 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 6 Jan 2022 11:24:44 +0000 Subject: [PATCH 071/330] Add new help topics re user database deletion One new topic for Delete User Database dialogue box. One new topic re related "common" task. Update help index, TOC and project file as required. --- Src/Help/CodeSnip.hhp | 2 + Src/Help/HTML/dlg_deleteuserdb.htm | 82 +++++++++++++++++++++++++++++ Src/Help/HTML/menu_database.htm | 14 ++++- Src/Help/HTML/task_deleteuserdb.htm | 34 ++++++++++++ Src/Help/Index.hhk | 7 +++ Src/Help/TOC.hhc | 4 ++ 6 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 Src/Help/HTML/dlg_deleteuserdb.htm create mode 100644 Src/Help/HTML/task_deleteuserdb.htm diff --git a/Src/Help/CodeSnip.hhp b/Src/Help/CodeSnip.hhp index 272bac8f8..1bde50619 100644 --- a/Src/Help/CodeSnip.hhp +++ b/Src/Help/CodeSnip.hhp @@ -25,6 +25,7 @@ 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 @@ -96,6 +97,7 @@ 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 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 @@ + + + + + + + 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/menu_database.htm b/Src/Help/HTML/menu_database.htm index 92f236ee6..303a0d8c4 100644 --- a/Src/Help/HTML/menu_database.htm +++ b/Src/Help/HTML/menu_database.htm @@ -4,7 +4,7 @@ * 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) 2008-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2008-2022, Peter Johnson (gravatar.com/delphidabbler). * * Help topic describing Database menu. --> @@ -96,6 +96,18 @@

    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/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/Index.hhk b/Src/Help/Index.hhk index 148b650e4..9b3a0f57a 100644 --- a/Src/Help/Index.hhk +++ b/Src/Help/Index.hhk @@ -75,6 +75,10 @@ +
  • + + +
  • @@ -383,6 +387,9 @@
  • +
  • + + diff --git a/Src/Help/TOC.hhc b/Src/Help/TOC.hhc index de429d3fe..f206f44bc 100644 --- a/Src/Help/TOC.hhc +++ b/Src/Help/TOC.hhc @@ -144,6 +144,10 @@
  • +
  • + + +
  • From ed8becf4c39299a992676e110af97c8d6e31890b Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 6 Jan 2022 11:30:53 +0000 Subject: [PATCH 072/330] Minor changes to Delete User Database dialogue box Punctuation & spacing changes Confirmation text string now cleared when invalid text entered --- Src/FmDeleteUserDBDlg.pas | 3 ++- Src/Res/HTML/dlg-dbdelete.html | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Src/FmDeleteUserDBDlg.pas b/Src/FmDeleteUserDBDlg.pas index 3aee061a4..d6c04b056 100644 --- a/Src/FmDeleteUserDBDlg.pas +++ b/Src/FmDeleteUserDBDlg.pas @@ -64,13 +64,14 @@ procedure TDeleteUserDBDlg.ArrangeForm; procedure TDeleteUserDBDlg.btnOKClick(Sender: TObject); resourcestring - sBadPassword = 'Invalid confirmation text entered: not deleting'; + sBadPassword = 'Invalid confirmation text entered'; begin inherited; fPermissionGranted := IsValidPassword; if not fPermissionGranted then begin TMessageBox.Error(Self, sBadPassword); + edConfirm.Text := ''; ModalResult := mrNone; end; end; diff --git a/Src/Res/HTML/dlg-dbdelete.html b/Src/Res/HTML/dlg-dbdelete.html index 92e7f7c2f..b21cfb4c7 100644 --- a/Src/Res/HTML/dlg-dbdelete.html +++ b/Src/Res/HTML/dlg-dbdelete.html @@ -34,11 +34,11 @@

    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. + 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.

    From 830a888e44cfb831fe99ab3a0a64bfa1b4bc1157 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 6 Jan 2022 16:45:52 +0000 Subject: [PATCH 073/330] Add new TSnippetList.IsEmpty(Boolean) method override; The Boolean parameter specifies whether we're checking User defined database (True) or main database (False) for emptiness. --- Src/DB.USnippet.pas | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Src/DB.USnippet.pas b/Src/DB.USnippet.pas index 15b33452c..5af498162 100644 --- a/Src/DB.USnippet.pas +++ b/Src/DB.USnippet.pas @@ -3,7 +3,7 @@ * 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) 2011-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2011-2022, Peter Johnson (gravatar.com/delphidabbler). * * Objects, records etc that encapsulate a code snippet, its data and lists of * code snippets. @@ -328,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; @@ -764,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. From d1593988f0caf02a81b2e0a21003494188e9f8a6 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 6 Jan 2022 16:50:51 +0000 Subject: [PATCH 074/330] Fix bug in accessing some database operations from main menu. Database menu's Backup User Database, Move User Database & Delete User Database options are now disabled if user database is empty. Additionally, new, un-saved snippets are now saved to user database before the database operations take place. Fixes #45 --- Src/FmMain.dfm | 3 +++ Src/FmMain.pas | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/Src/FmMain.dfm b/Src/FmMain.dfm index e2441f840..343bc0d9b 100644 --- a/Src/FmMain.dfm +++ b/Src/FmMain.dfm @@ -619,6 +619,7 @@ inherited MainForm: TMainForm Hint = 'Backup user database|Backup the user-defined snippet database' ImageIndex = 33 OnExecute = actBackupDatabaseExecute + OnUpdate = ActNonEmptyUserDBUpdate end object actRestoreDatabase: TAction Category = 'Database' @@ -849,6 +850,7 @@ 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' @@ -873,6 +875,7 @@ inherited MainForm: TMainForm 'Delete User Database|Deletes the user'#39's snippets database - USE ' + 'WITH CAUTION' OnExecute = actDeleteUserDatabaseExecute + OnUpdate = ActNonEmptyUserDBUpdate end end object mnuMain: TMainMenu diff --git a/Src/FmMain.pas b/Src/FmMain.pas index 6e56de410..1342be28c 100644 --- a/Src/FmMain.pas +++ b/Src/FmMain.pas @@ -495,6 +495,7 @@ TMainForm = class(THelpAwareForm) /// position is permitted and blocks the move if not. procedure splitVertCanResize(Sender: TObject; var NewSize: Integer; var Accept: Boolean); + procedure ActNonEmptyUserDBUpdate(Sender: TObject); strict private var /// Object that notifies user-initiated events by triggering @@ -621,7 +622,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); @@ -721,8 +725,13 @@ procedure TMainForm.actDeleteSnippetExecute(Sender: TObject); procedure TMainForm.actDeleteUserDatabaseExecute(Sender: TObject); begin + if (Database as IDatabaseEdit).Updated then + TUserDBMgr.Save(Self); if TUserDBMgr.DeleteDatabase then + begin ReloadDatabase; + fStatusBarMgr.Update; + end; end; procedure TMainForm.actDuplicateSnippetExecute(Sender: TObject); @@ -902,6 +911,8 @@ procedure TMainForm.actLoadSelectionExecute(Sender: TObject); procedure TMainForm.actMoveUserDatabaseExecute(Sender: TObject); begin + if (Database as IDatabaseEdit).Updated then + TUserDBMgr.Save(Self); TUserDBMgr.MoveDatabase; end; @@ -920,6 +931,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 @@ -979,7 +995,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); From 81e6df5ba32ae7005dd1fdacb3e445d962ed0e43 Mon Sep 17 00:00:00 2001 From: Peter Johnson <5164283+delphidabbler@users.noreply.github.com> Date: Sat, 22 Jan 2022 15:26:50 +0000 Subject: [PATCH 075/330] Update README.md Overhaul and update "Source Code" section --- README.md | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index be346abbd..d3ec8ce78 100644 --- a/README.md +++ b/README.md @@ -43,14 +43,15 @@ There's also plenty of info available on how to compile CodeSnip from source - s CodeSnip's source code is maintained in the [`delphidabbler/codesnip`](https://github.com/delphidabbler/codesnip) Git repository on GitHub†. -[Git Flow](https://nvie.com/posts/a-successful-git-branching-model/) methodology has been adopted, with the exception of some branches that have been used in various attempts to start work on CodeSnip 5. +The [Git Flow](https://nvie.com/posts/a-successful-git-branching-model/) methodology has been adopted, with the exception of some branches that have been used in various attempts to start work on CodeSnip 5. -The following branches existed at the time when CodeSnip v4.16.0 was released: +The following branches existed as of 2022/01/01: * `master`: Always reflects the state of the source code as of the latest release.‡ -* `develop`: Main development branch. The head of this branch contains the latest development code. +* `develop`: Main development branch. The head of this branch contains the latest v4 development code. +* `belvedere`: The latest attempt to develop CodeSnip 5. See the [Belvedere Readme file](https://github.com/delphidabbler/codesnip/blob/belvedere/README.md) for a full explanation. * `pagoda`: An abortive attempt at developing CodeSnip 5. Work on this branch has halted. It does not follow GitFlow methodology. ***Do not use this branch: it may be pruned.*** -* `pavilion`: Another attempt at working on CodeSnip 5. It branched off `pagoda` and it's future is uncertain. Again it does not follow GitFlow methodology. +* `pavilion`: Another attempt at working on CodeSnip 5. It branched off `pagoda` and work on it has halted. Again it does not follow GitFlow methodology. ***Do not use this branch: it may be pruned.*** New features and most bug fixes are worked on in `feature/xxxx` branches that are branched off `develop` locally. They are merged into `develop` as they are completed and the branches are deleted. @@ -62,7 +63,17 @@ Note that the default branch on GitHub is `master`, which contains the state of ### Contributions -To contribute to the project please fork the repository on GitHub. Create a feature branch off the `develop` branch. Make your changes to the feature branch then submit a pull request via GitHub. +#### CodeSnip 4 + +To contribute to CodeSnip 4 development please fork the repository on GitHub. Create a feature branch off the `develop` branch. Make your changes to your feature branch then submit a pull request via GitHub. + +> **Do not create branches off `master`, always branch from `develop`.** + +#### CodeSnip 5 Belvedere + +Proceed as for CodeSnip 4 except create your feature branch off the `belvedere` branch instead of `develop`. + +> **Do not submit changes to the earlier `pagoda` or `pavilion` branches because they will not be accepted.** ### Compiling From 5aa3d93e90705eb74314530c04882334c30376ad Mon Sep 17 00:00:00 2001 From: Peter Johnson <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 23 Jan 2022 04:01:19 +0000 Subject: [PATCH 076/330] Update README.md Update "Compiling" section. Update "Contributions" section (a) delete mention of belvedere branch (b) add "Licensing of contributions" subsection. --- README.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d3ec8ce78..74fad43e0 100644 --- a/README.md +++ b/README.md @@ -63,24 +63,28 @@ Note that the default branch on GitHub is `master`, which contains the state of ### Contributions -#### CodeSnip 4 - To contribute to CodeSnip 4 development please fork the repository on GitHub. Create a feature branch off the `develop` branch. Make your changes to your feature branch then submit a pull request via GitHub. > **Do not create branches off `master`, always branch from `develop`.** -#### CodeSnip 5 Belvedere +#### Licensing of contributions + +The license that applies to any existing file you edit will continue to apply to the edited file. Any existing license text or copyright statement **must not** be altered or removed. -Proceed as for CodeSnip 4 except create your feature branch off the `belvedere` branch instead of `develop`. +Any new file you contribute **must** either be licensed under the Mozilla Public License v2.0 (MPL2) or have a license compatible with the MPL2. If a license is not specified then the MPL2 will be applied to the file. You should insert a suitable copyright statement in the file. -> **Do not submit changes to the earlier `pagoda` or `pavilion` branches because they will not be accepted.** +Any third party code used by your contributed code **must** also have a license compatible with the MPL2. + +> MPL2 boilerplate text, in several programming language's comment formats, can be found in the file `Docs/MPL-2.0-Boilerplate.txt`. You will need to change the name of the copyright holder. ### Compiling -`master` has a file in the root directory named [`Build.html`](https://htmlpreview.github.io/?https://github.com/delphidabbler/codesnip/blob/master/Build.html) that gives detailed information about how to compile the current release of CodeSnip. +`master` has a file in the root directory named [`Build.html`](https://htmlpreview.github.io/?https://github.com/delphidabbler/codesnip/blob/master/Build.html) that gives detailed information about how to compile the current release of CodeSnip 4. There is also a [Compiling & Source Code FAQ](https://github.com/delphidabbler/codesnip-faq/blob/master/SourceCode.md). +CodeSnip 4 **must** be compiled with Delphi XE. See [Compiling & Source Code FAQ 11](https://github.com/delphidabbler/codesnip-faq/blob/master/SourceCode.md#faq-11) for the reason why. + ## Change Log The program's current change log can be found in the file `CHANGELOG.md` in the root of the `master` branch. From 4f1d56f98bfad25e0a0e752146459b9babb2cc4a Mon Sep 17 00:00:00 2001 From: Peter Johnson <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 23 Jan 2022 04:17:32 +0000 Subject: [PATCH 077/330] Update Build.html Add link to explanation of why CodeSnip uses Delphi XE. --- Build.html | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Build.html b/Build.html index f30a3d15b..a6a432a39 100644 --- a/Build.html +++ b/Build.html @@ -80,6 +80,13 @@

    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 From 9ba823e1768b7cff6d7b47df1dca211ef461f0a1 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 15 May 2022 09:02:29 +0100 Subject: [PATCH 078/330] Bump config file version number to 18 This because of addition of DetailFontSize property to Prefs:Display section of per-user config file. --- Src/FirstRun.UConfigFile.pas | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Src/FirstRun.UConfigFile.pas b/Src/FirstRun.UConfigFile.pas index 30bca5999..2645b6acc 100644 --- a/Src/FirstRun.UConfigFile.pas +++ b/Src/FirstRun.UConfigFile.pas @@ -3,7 +3,7 @@ * 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) 2007-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2007-2022, Peter Johnson (gravatar.com/delphidabbler). * * Implements class that manages the updating of older config files to the * current format. @@ -82,7 +82,7 @@ TUserConfigFileUpdater = class(TConfigFileUpdater) strict private const ///

    Current user config file version. - FileVersion = 17; + FileVersion = 18; strict protected /// Returns current user config file version. class function GetFileVersion: Integer; override; From 39af1835851b4dab6a243ca3a9e9ce9816c2761b Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 15 May 2022 09:03:44 +0100 Subject: [PATCH 079/330] Update config file docs Note addition of DetailFontSize property to Prefs:Display section of per-user config file and resulting bumping of file's version number. --- Docs/Design/FileFormats/config.html | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Docs/Design/FileFormats/config.html b/Docs/Design/FileFormats/config.html index ed05ef9b2..cf2ce3f32 100644 --- a/Docs/Design/FileFormats/config.html +++ b/Docs/Design/FileFormats/config.html @@ -5,7 +5,7 @@ * 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-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2012-2022, Peter Johnson (gravatar.com/delphidabbler). * * CodeSnip File Format Documentation: Configuration Files --> @@ -167,7 +167,7 @@

    - There have been several versions of this file. The current one is version 17. The change to version 17 came with CodeSnip v4.19.0 and the addition of the [Prefs] section. + There have been several versions of this file. The current one is version 18. The change to version 18 came with CodeSnip v4.20.0 and the addition of the [Prefs] section.

    @@ -919,6 +919,12 @@

    Size of font to be used in overview pane tree view. If missing or empty the default value is 9.
    +
    + 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. +

    From eff0390cbb44cec6705cb5ed332d681bfa80ff1b Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 15 May 2022 09:07:25 +0100 Subject: [PATCH 080/330] Fix description of Prefs:Display OverviewFontSize option --- Docs/Design/FileFormats/config.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Docs/Design/FileFormats/config.html b/Docs/Design/FileFormats/config.html index cf2ce3f32..b84ff246d 100644 --- a/Docs/Design/FileFormats/config.html +++ b/Docs/Design/FileFormats/config.html @@ -917,7 +917,7 @@

    OverviewFontSize (Integer)
    - Size of font to be used in overview pane tree view. If missing or empty the default value is 9. + 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) From 3aa37bc8e5f25fd326fc7c9fa9fcbb98f588fa52 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 15 May 2022 09:23:30 +0100 Subject: [PATCH 081/330] Update 3rd party PJSysInfo unit to v5.11.0 Fixes #49 --- Src/3rdParty/PJSysInfo.pas | 189 ++++++++++++++++++++++++------------- 1 file changed, 123 insertions(+), 66 deletions(-) diff --git a/Src/3rdParty/PJSysInfo.pas b/Src/3rdParty/PJSysInfo.pas index fa4cd593b..b1040bb83 100644 --- a/Src/3rdParty/PJSysInfo.pas +++ b/Src/3rdParty/PJSysInfo.pas @@ -3,10 +3,7 @@ * 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) 2001-2021, Peter Johnson (@delphidabbler). - * - * $Rev: 2082 $ - * $Date: 2022-01-01 10:12:03 +0000 (Sat, 01 Jan 2022) $ + * Copyright (C) 2001-2022, Peter Johnson (@delphidabbler). * * This unit contains various static classes, constants, type definitions and * global variables for use in providing information about the host computer and @@ -419,7 +416,8 @@ interface osWin10Svr, // Windows 2016 Server osWinSvr2019, // Windows 2019 Server osWin11, // Windows 11 - osWinSvr2022 // Windows 2022 Server + osWinSvr2022, // Windows 2022 Server + osWinServer // Windows Server (between Server 2019 & 2022) ); type @@ -1209,6 +1207,7 @@ implementation Win8Point1Build = 9600; // Build number used for all Win 8.1/Svr 2012 R2 // Windows 10 ---------------------------------------------------------------- + Win10TH1Build = 10240; // Windows 10 TH1 - version 1507 (1st release) Win10TH2Build = 10586; // Windows 10 TH2 - version 1511 Win10RS1Build = 14393; // Windows 10 RS1 - version 1607 @@ -1221,9 +1220,28 @@ implementation Win1020H1Build = 19041; // Windows 10 20H1 - version 2004 Win1020H2Build = 19042; // Windows 10 20H2 - version 20H2 Win1021H1Build = 19043; // Windows 10 21H1 - version 21H1 + // revisions 844..964 were beta Win1021H2Build = 19044; // Windows 10 21H2 - version 21H2 + // revisions 1147..1266 were previews + + // Fast ring + Win10FastRing: array[0..21] of Integer = ( + 19536, 19541, 19546, 19551, 19555, 19559, 19564, 19569, 19577, 19582, 19587, + 19592, 19603, 19608, 19613, 19619, 19624, 19628, 19631, 19635, 19640, 19645 + ); + + // Dev channel + // Assuming all Dev channel releases had version string "Dev" + Win10DevChannel: array[0..44] of Integer = ( + 20150, 20152, 20161, 20170, 20175, 20180, 20185, 20190, 20197, 20201, 20206, + 20211, 20215, 20221, 20226, 20231, 20236, 20241, 20246, 20251, 20257, 20262, + 20270, 20277, 21277, 20279, 21286, 21292, 21296, 21301, 21313, 21318, 21322, + 21327, 21332, 21337, 21343, 21354, 21359, 21364, 21370, 21376, 21382, 21387, + 21390 // transitioned to Windows 11 after here + ); // Windows 11 ---------------------------------------------------------------- + // NOTE: Preview and beta & release versions of Windows 11 report version 10.0 Win11DevBuild = 21996; // Windows 11 version Dev // - 10.0.21996.1 (Insider version) @@ -1237,28 +1255,28 @@ implementation // Revision # 194 // Windows 11 version 21H2 // - ** 1st Public Release ** - Win11v21H2PreRel1Build = 22449; // Windows 11 version 21H2 - // - 10.0.22449.000 (RSPRERELEASE) - Win11v21H2PreRel2Build = 22454; // Windows 11 version 21H2 - // - 10.0.22454.1000 (RSPRERELEASE) - Win11v21H2PreRel3Build = 22458; // Windows 11 version 21H2 - // - 10.0.22458.1000 (RSPRERELEASE) - Win11v21H2PreRel4Build = 22463; // Windows 11 version 21H2 - // - 10.0.22463.1000 (RSPRERELEASE) - Win11v21H2PreRel5Build = 22468; // Windows 11 version 21H2 - // - 10.0.22468.1000 (RSPRERELEASE) - Win11v21H2PreRel6Build = 22471; // Windows 11 version 21H2 - // - 10.0.22471.1000 (RSPRERELEASE) - Win11v21H2PreRel7Build = 22478; // Windows 11 version 21H2 - // - 10.0.22478.1000 (RSPRERELEASE) - Win11v21H2PreRel8Build = 22483; // Windows 11 version 21H2 - // - 10.0.22483.1000 (RSPRERELEASE) - Win11v21H2PreRel9Build = 22489; // Windows 11 version 21H2 - // - 10.0.22489.1000 (RSPRERELEASE) - Win11v21H2PreRel10Build = 22494;// Windows 11 version 21H2 - // - 10.0.22494.1000 (RSPRERELEASE) - Win11v21H2PreRel11Build = 22509;// Windows 11 version 21H2 - // - 10.0.22509.1000 (RSPRERELEASE) + + // Dev channel release - different sources give different names. + // From what I can gather (and take this with a pinch of salt!): + // * Insider Dev channel releases from the RS_PRERELEASE branch weren't + // matched to a Windows 11 release and had version string "Dev"). + // * The NI_RELEASE channel was used from 2022/02/16 (build 2257). + // * From build 22567 the release string changed from "Dev" to "22H" + + // Builds with version string "Dev" + Win11DevChannelDevBuilds: array[0..20] of Integer = ( + 22449, 22454, 22458, 22463, 22468, // pre Win 11 release + 22471, 22478, 22483, 22489, 22494, 22499, 22504, 22509, 22518, 22523, 22526, + 22533, 22538, 22543, 22557, 22563 + ); + // Builds with version string "22H2" in Dev channel + Win11DevChannel22H2Builds: array[0..2] of Integer = ( + 22567, 22572, 22579 + ); + // Builds with version string "22H2" in Dev & Beta channels + Win11DevBetaChannels22H2Builds: array[0..3] of Integer = ( + 22581, 22593, 22598, 22610 + ); Win11FirstBuild = Win11DevBuild; // First build number of Windows 11 @@ -1275,23 +1293,23 @@ implementation // After this it's Win 2019 Server // Windows 2019 Server ------------------------------------------------------- - Win2019IP180320Build = 17623; // Win Server 2019 Insider Preview Build 17623 - Win2019IP180324Build = 17627; // Win Server 2019 Insider Preview Build 17627 - Win2019IP180515Build = 17666; // Win Server 2019 Insider Preview Build 17666 - Win2019IP180619Build = 17692; // Win Server 2019 Insider Preview Build 17692 - Win2019IP180710Build = 17709; // Win Server 2019 Insider Preview Build 17709 - Win2019IP180716Build = 17713; // Win Server 2019 Insider Preview Build 17713 - Win2019IP180731Build = 17723; // Win Server 2019 Insider Preview Build 17723 - Win2019IP180814Build = 17733; // Win Server 2019 Insider Preview Build 17733 - Win2019IP180821Build = 17738; // Win Server 2019 Insider Preview Build 17738 - Win2019IP180828Build = 17744; // Win Server 2019 Insider Preview Build 17744 + // Insider Preview builds + Win2019IPBuilds: array[0..9] of Integer = ( + 17623, 17627, 17666, 17692, 17709, 17713, 17723, 17733, 17738, 17744 + ); + // Release builds Win2019v1809Build = 17763; // Win Server 2019 version 1809 Win2019v1903Build = 18362; // Win Server 2019 version 1903 Win2019v1909Build = 18363; // Win Server 2019 version 1909 - Win2019v2004Build = 19041; // Win Server 2019 version 2004 - Win2019v20H2Build = 19042; // Win Server 2019 version 20H2 - Win2019LastBuild = Win2019v20H2Build; // Last build number of Win 2019 Server - // After this it's Win 2022 Server + Win2019LastBuild = Win2019v1909Build; // Last build number of Win 2019 Server + // After this it's Windows Server + + // Windows Server ------------------------------------------------------------ + WinServerv2004Build = 19041; // Win Server version 2004 + WinServerv20H2Build = 19042; // Win Server version 20H2 + WinServerLastBuild = WinServerv20H2Build; // Last build number of Windows + // Server. After this it's Window + // 2022 Sever // Windows 2022 Server ------------------------------------------------------- Win2022v21H2Build = 20348; // Win Server 2022 version 21H2 @@ -1510,6 +1528,14 @@ function ExcludeTrailingPathDelimiter(const DirOrPath: string) : string; end; {$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; + // Returns the value of the given environment variable. function GetEnvVar(const VarName: string): string; var @@ -1823,6 +1849,8 @@ procedure InitPlatformIdEx; begin InternalBuildNumber := Win1021H1Build; InternalExtraUpdateInfo := 'Version 21H1'; + if IsInRange(InternalRevisionNumber, 844, 964) then + InternalExtraUpdateInfo := InternalExtraUpdateInfo + ' (beta)'; end else if IsBuildNumber(Win1021H2Build) then begin @@ -1830,8 +1858,29 @@ procedure InitPlatformIdEx; // yearly cycle InternalBuildNumber := Win1021H2Build; InternalExtraUpdateInfo := 'Version 21H2'; + if IsInRange(InternalRevisionNumber, 1147, 1266) then + InternalExtraUpdateInfo := InternalExtraUpdateInfo + + ' (preview)'; + end + else if FindBuildNumberFrom( + Win10DevChannel, InternalBuildNumber + ) then + begin + // Windows 10 Dev Channel releases + InternalExtraUpdateInfo := Format( + 'Dev Channel v10.0.%d.%d (Dev)', + [InternalBuildNumber, InternalRevisionNumber] + ); end - // As of 2021-09-11, Win 11 pre-releases are reporting v10.0 + else if FindBuildNumberFrom(Win10FastRing, InternalBuildNumber) then + begin + // Windows 10 Fast Ring releases + InternalExtraUpdateInfo := Format( + 'Fast ring v10.0.%d.%d', + [InternalBuildNumber, InternalRevisionNumber] + ); + end + // Win 11 releases are reporting v10.0 // Details taken from: https://tinyurl.com/usupsz4a // Correct according to above web page as of 2021-09-11 else if IsBuildNumber(Win11DevBuild) then @@ -1872,19 +1921,32 @@ procedure InitPlatformIdEx; end; end else if FindBuildNumberFrom( - [ - Win11v21H2PreRel1Build, Win11v21H2PreRel2Build, - Win11v21H2PreRel3Build, Win11v21H2PreRel4Build, - Win11v21H2PreRel5Build, Win11v21H2PreRel6Build, - Win11v21H2PreRel7Build, Win11v21H2PreRel8Build, - Win11v21H2PreRel9Build, Win11v21H2PreRel10Build, - Win11v21H2PreRel11Build - ], - InternalBuildNumber + Win11DevChannelDevBuilds, InternalBuildNumber + ) then + begin + // Win11 Dev Channel builds with version string "Dev" + InternalExtraUpdateInfo := Format( + 'Dev Channel v10.0.%d.%d (Dev)', + [InternalBuildNumber, InternalRevisionNumber] + ); + end + else if FindBuildNumberFrom( + Win11DevChannel22H2Builds, InternalBuildNumber + ) then + begin + // Win11 Dev channel builds with version string "22H2" + InternalExtraUpdateInfo := Format( + 'Dev Channel v10.0.%d.%d (22H2)', + [InternalBuildNumber, InternalRevisionNumber] + ); + end + else if FindBuildNumberFrom( + Win11DevBetaChannels22H2Builds, InternalBuildNumber ) then begin + // Win 11 Dev & Beta channel builds with verison string "22H2" InternalExtraUpdateInfo := Format( - 'Version 21H2 [RSPRERELEASE v10.0.%d.%d]', + 'Dev & Beta Channels v10.0.%d.%d (22H2)', [InternalBuildNumber, InternalRevisionNumber] ); end @@ -1930,14 +1992,7 @@ procedure InitPlatformIdEx; InternalExtraUpdateInfo := 'Version 1803'; end else if FindBuildNumberFrom( - [ - Win2019IP180320Build, Win2019IP180324Build, - Win2019IP180515Build, Win2019IP180619Build, - Win2019IP180710Build, Win2019IP180716Build, - Win2019IP180731Build, Win2019IP180814Build, - Win2019IP180821Build, Win2019IP180828Build - ], - InternalBuildNumber + Win2019IPBuilds, InternalBuildNumber ) then begin InternalExtraUpdateInfo := Format( @@ -1959,14 +2014,14 @@ procedure InitPlatformIdEx; InternalBuildNumber := Win2019v1909Build; InternalExtraUpdateInfo := 'Version 1909'; end - else if IsBuildNumber(Win2019v2004Build) then + else if IsBuildNumber(WinServerv2004Build) then begin - InternalBuildNumber := Win2019v2004Build; + InternalBuildNumber := WinServerv2004Build; InternalExtraUpdateInfo := 'Version 2004'; end - else if IsBuildNumber(Win2019v20H2Build) then + else if IsBuildNumber(WinServerv20H2Build) then begin - InternalBuildNumber := Win2019v20H2Build; + InternalBuildNumber := WinServerv20H2Build; InternalExtraUpdateInfo := 'Version 20H2'; end else if IsBuildNumber(Win2022v21H2Build) then @@ -2132,7 +2187,7 @@ class function TPJOSInfo.Edition: string; osWin7, osWinSvr2008R2, osWin8, osWinSvr2012, osWin8Point1, osWinSvr2012R2, - osWin10, osWin11, osWin10Svr, osWinSvr2019, osWinSvr2022: + 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 @@ -2682,8 +2737,9 @@ class function TPJOSInfo.Product: TPJOSProduct; Result := osWin10Svr else if InternalBuildNumber <= Win2019LastBuild then Result := osWinSvr2019 + else if InternalBuildNumber <= WinServerLastBuild then + Result := osWinServer else - // Result := osWinSvr2022; end; end; @@ -2734,6 +2790,7 @@ class function TPJOSInfo.ProductName: string; osWinSvr2019: Result := 'Windows Server 2019'; osWin11: Result := 'Windows 11'; osWinSvr2022: Result := 'Windows Server 2022'; + osWinServer: Result := 'Windows Server'; else raise EPJSysInfo.Create(sUnknownProduct); end; From fae18d192b8704941fbc8aae785b00100bd4e5d5 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 15 May 2022 09:34:49 +0100 Subject: [PATCH 082/330] Update version displayed for Delphi Alexandria Change version number from 11 to 11.x to reflect fact that there is more than one version number applied to Delphi Alexandria. --- Src/Compilers.UBDS.pas | 4 ++-- Src/FrCodeGenPrefs.pas | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Src/Compilers.UBDS.pas b/Src/Compilers.UBDS.pas index 6d5cd7b3e..668916c19 100644 --- a/Src/Compilers.UBDS.pas +++ b/Src/Compilers.UBDS.pas @@ -3,7 +3,7 @@ * 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) 2006-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2006-2022, Peter Johnson (gravatar.com/delphidabbler). * * Class that controls and provides information about Borland CodeGear and * Embarcadero "BDS" Win32 compilers. @@ -175,7 +175,7 @@ function TBDSCompiler.GetName: string; sDelphi102T = 'Delphi 10.2 Tokyo'; sDelphi103R = 'Delphi 10.3 Rio'; sDelphi104S = 'Delphi 10.4 Sydney'; - sDelphi11A = 'Delphi 11 Alexandria'; + sDelphi11A = 'Delphi 11.x Alexandria'; begin case GetID of ciDXE: diff --git a/Src/FrCodeGenPrefs.pas b/Src/FrCodeGenPrefs.pas index 7e572fb9a..7814ad14b 100644 --- a/Src/FrCodeGenPrefs.pas +++ b/Src/FrCodeGenPrefs.pas @@ -3,7 +3,7 @@ * 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) 2010-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2010-2022, Peter Johnson (gravatar.com/delphidabbler). * * Implements a frame that allows user to set source code generation * preferences. @@ -685,7 +685,7 @@ procedure TCodeGenPrefsFrame.PopulatePreDefCompilerMenu; AddMenuItem('Delphi 10.2 Tokyo', 32.0); AddMenuItem('Delphi 10.3 Rio', 33.0); AddMenuItem('Delphi 10.4 Sydney', 34.0); - AddMenuItem('Delphi 11 Alexandria', 35.0); + AddMenuItem('Delphi 11.x Alexandria', 35.0); end; procedure TCodeGenPrefsFrame.PreDefCompilerMenuClick(Sender: TObject); From 4c34f8bd406a2e7beaa4a74d38a464f4c442e8cd Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 15 May 2022 09:35:09 +0100 Subject: [PATCH 083/330] Update version number for Delphi Alexandria in docs Change version number from 11 to 11.x to reflect fact that there is more than one version number applied to Delphi Alexandria. --- Docs/Design/FileFormats/config.html | 4 ++-- Docs/Design/FileFormats/export.html | 6 +++--- Docs/Design/FileFormats/main-db.html | 4 ++-- Docs/Design/FileFormats/user-db.html | 6 +++--- Docs/ReadMe.txt | 4 ++-- Src/Compilers.UGlobals.pas | 4 ++-- Src/Help/HTML/about_compiler_checks.htm | 4 ++-- Src/Help/HTML/dlg_configcompilers.htm | 4 ++-- 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Docs/Design/FileFormats/config.html b/Docs/Design/FileFormats/config.html index b84ff246d..c81851d94 100644 --- a/Docs/Design/FileFormats/config.html +++ b/Docs/Design/FileFormats/config.html @@ -257,7 +257,7 @@

    D104S – Delphi 10.4 Sydney
  • - D11A – Delphi 11 Alexandria + D11A – Delphi 11.x Alexandria
  • FPC – Free Pascal @@ -570,7 +570,7 @@

    D11A (Boolean)

  • - Indicates whether Delphi 11 Alexandria was included in the search. + Indicates whether Delphi 11.x Alexandria was included in the search.
    FPC (Boolean) diff --git a/Docs/Design/FileFormats/export.html b/Docs/Design/FileFormats/export.html index 58e394aa6..91e1a1059 100644 --- a/Docs/Design/FileFormats/export.html +++ b/Docs/Design/FileFormats/export.html @@ -5,7 +5,7 @@ * 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-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2012-2022, Peter Johnson (gravatar.com/delphidabbler). * * CodeSnip File Format Documentation: Export --> @@ -597,7 +597,7 @@

    d104s – Delphi 10.4 Sydney compiler (v7.1 & later)
  • - d11a – Delphi 11 Alexandria compiler (v7.2 & later) + d11a – Delphi 11.x Alexandria compiler (v7.2 & later)
  • fpc – Free Pascal compiler (all versions) @@ -969,7 +969,7 @@

    Version 7.2 - 13 September 2021

  • - Updated with CodeSnip v4.18.0 to add support for Delphi 11 Alexandria. + Updated with CodeSnip v4.18.0 to add support for Delphi 11.x Alexandria.
    diff --git a/Docs/Design/FileFormats/main-db.html b/Docs/Design/FileFormats/main-db.html index 6183e4682..031fac904 100644 --- a/Docs/Design/FileFormats/main-db.html +++ b/Docs/Design/FileFormats/main-db.html @@ -5,7 +5,7 @@ * 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-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2012-2022, Peter Johnson (gravatar.com/delphidabbler). * * CodeSnip File Format Documentation: Main Database --> @@ -417,7 +417,7 @@

    Delphi104S – Delphi 10.4 Sydney compiler *
  • - Delphi11A – Delphi 11 Alexandria compiler * + Delphi11A – Delphi 11.x Alexandria compiler *
  • FPC – Free Pascal compiler diff --git a/Docs/Design/FileFormats/user-db.html b/Docs/Design/FileFormats/user-db.html index 6c0fe0a88..baeb84715 100644 --- a/Docs/Design/FileFormats/user-db.html +++ b/Docs/Design/FileFormats/user-db.html @@ -5,7 +5,7 @@ * 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-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2012-2022, Peter Johnson (gravatar.com/delphidabbler). * * CodeSnip File Format Documentation: User Database --> @@ -622,7 +622,7 @@

    d104s – Delphi 10.4 Sydney compiler (v6.9 & later)

  • - d11a – Delphi 11 Alexandria compiler (v6.10 & later) + d11a – Delphi 11.x Alexandria compiler (v6.10 & later)
  • fpc – Free Pascal compiler (all versions) @@ -1005,7 +1005,7 @@

    Version 6.10 - 13 September 2021
    - Updated with CodeSnip v4.18.0 to add support for Delphi 11 Alexandria. + Updated with CodeSnip v4.18.0 to add support for Delphi 11.x Alexandria.
    diff --git a/Docs/ReadMe.txt b/Docs/ReadMe.txt index c7c2339a8..3346f91af 100644 --- a/Docs/ReadMe.txt +++ b/Docs/ReadMe.txt @@ -14,7 +14,7 @@ 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 11 +with each installed Win32 version of Delphi from Delphi 2 to Delphi 11.x Alexandria and Free Pascal. Compilable Pascal units can be created that contain selected snippets. @@ -201,7 +201,7 @@ 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 11 Alexandria) and +Windows 32 version of Delphi (from Delphi 2 to Delphi 11.x Alexandria) and FreePascal, providing some simple rules are followed. When CodeSnip is first installed it knows nothing about the available compilers diff --git a/Src/Compilers.UGlobals.pas b/Src/Compilers.UGlobals.pas index 83e287164..5653202f9 100644 --- a/Src/Compilers.UGlobals.pas +++ b/Src/Compilers.UGlobals.pas @@ -3,7 +3,7 @@ * 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) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2022, Peter Johnson (gravatar.com/delphidabbler). * * Declares various types that describe the compiler and compilation results and * defines interfaces to compiler objects. @@ -43,7 +43,7 @@ interface ciD102T, // Delphi 10.2 Tokyo ciD103R, // Delphi 10.3 Rio ciD104S, // Delphi 10.4 Sydney, - ciD11A, // Delphi 11 Alexandria + ciD11A, // Delphi 11.x Alexandria ciFPC // Free Pascal ); diff --git a/Src/Help/HTML/about_compiler_checks.htm b/Src/Help/HTML/about_compiler_checks.htm index 5d9469874..0aa09cb0e 100644 --- a/Src/Help/HTML/about_compiler_checks.htm +++ b/Src/Help/HTML/about_compiler_checks.htm @@ -4,7 +4,7 @@ * 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) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2022, Peter Johnson (gravatar.com/delphidabbler). * * Help topic explaining compiler checks. --> @@ -34,7 +34,7 @@

    The supported compilers are the Win32 Delphi compilers from Delphi 2 to - Delphi 11 Alexandria and Free Pascal. + Delphi 11.x Alexandria and Free Pascal.

    Configuring CodeSnip diff --git a/Src/Help/HTML/dlg_configcompilers.htm b/Src/Help/HTML/dlg_configcompilers.htm index 4f82b47e8..9055f3c9f 100644 --- a/Src/Help/HTML/dlg_configcompilers.htm +++ b/Src/Help/HTML/dlg_configcompilers.htm @@ -4,7 +4,7 @@ * 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) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2022, Peter Johnson (gravatar.com/delphidabbler). * * Help topic for Configure Compilers dialogue box. --> @@ -281,7 +281,7 @@

    CodeSnip can automatically detect the presence of Win 32 Delphi - compilers from Delphi 2 to Delphi 11 Alexandria. Click the Detect + compilers from Delphi 2 to Delphi 11.x Alexandria. Click the Detect Delphi Compilers button to do this. Any supported installed version of Delphi will be recorded. This can save considerable time and avoid errors. From 5b9325024f48ead0482e8f04f6a6314cd1502063 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 15 May 2022 09:43:19 +0100 Subject: [PATCH 084/330] Widen Compilers list in Configure Compilers dlg Widened the compilers list box by 8px to display the longer name "Delphi 11.x Alexandria". Widened dlg and rearranged controls to suit. --- Src/FmCompilersDlg.dfm | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/Src/FmCompilersDlg.dfm b/Src/FmCompilersDlg.dfm index f3bf3134f..12ae8c2a1 100644 --- a/Src/FmCompilersDlg.dfm +++ b/Src/FmCompilersDlg.dfm @@ -1,16 +1,16 @@ inherited CompilersDlg: TCompilersDlg Caption = 'Configure Compilers' ClientHeight = 381 - ClientWidth = 547 - ExplicitWidth = 553 + ClientWidth = 588 + ExplicitWidth = 594 ExplicitHeight = 410 PixelsPerInch = 96 TextHeight = 13 inherited pnlBody: TPanel - Width = 531 - ExplicitWidth = 531 + Width = 539 + ExplicitWidth = 539 object pbBanner: TPaintBox - Left = 161 + Left = 169 Top = 0 Width = 370 Height = 23 @@ -26,7 +26,7 @@ inherited CompilersDlg: TCompilersDlg object lbCompilers: TListBox Left = 0 Top = 0 - Width = 155 + Width = 163 Height = 292 Style = lbOwnerDrawFixed Ctl3D = True @@ -36,7 +36,7 @@ inherited CompilersDlg: TCompilersDlg TabOrder = 0 end object pcCompiler: TPageControl - Left = 161 + Left = 169 Top = 29 Width = 370 Height = 263 @@ -52,8 +52,6 @@ inherited CompilersDlg: TCompilersDlg Height = 235 Align = alClient TabOrder = 0 - ExplicitLeft = -4 - ExplicitTop = 2 end end object tsSwitches: TTabSheet From e873b90834aa71899615c888ed1ef3818035c9ab Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 15 May 2022 10:24:00 +0100 Subject: [PATCH 085/330] Bump version information to v2.20.0 build 264 --- Src/VCodeSnip.vi | 6 +++--- Src/VCodeSnipPortable.vi | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Src/VCodeSnip.vi b/Src/VCodeSnip.vi index b9320937b..336a57137 100644 --- a/Src/VCodeSnip.vi +++ b/Src/VCodeSnip.vi @@ -2,14 +2,14 @@ ; 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) 2008-2021, Peter Johnson (gravatar.com/delphidabbler). +; Copyright (C) 2008-2022, Peter Johnson (gravatar.com/delphidabbler). ; ; Version information description file for CodeSnip. [Fixed File Info] -File Version #=4, 19, 0, 263 -Product Version #=4, 19, 0, 0 +File Version #=4, 20, 0, 264 +Product Version #=4, 20, 0, 0 File OS=4 File Type=1 File Sub-Type=0 diff --git a/Src/VCodeSnipPortable.vi b/Src/VCodeSnipPortable.vi index 6b8647290..6f78cbaff 100644 --- a/Src/VCodeSnipPortable.vi +++ b/Src/VCodeSnipPortable.vi @@ -2,14 +2,14 @@ ; 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-2021, Peter Johnson (gravatar.com/delphidabbler). +; Copyright (C) 2012-2022, Peter Johnson (gravatar.com/delphidabbler). ; ; Version information description file for the portable edition of CodeSnip [Fixed File Info] -File Version #=4, 19, 0, 263 -Product Version #=4, 19, 0, 0 +File Version #=4, 20, 0, 264 +Product Version #=4, 20, 0, 0 File OS=4 File Type=1 File Sub-Type=0 From 7dcbab3916532174dd8aaadf0433a5c3c25e356c Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 15 May 2022 10:24:23 +0100 Subject: [PATCH 086/330] Update change log re v4.20.0 changes --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb395c211..251825295 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,22 @@ This change log begins with the first ever pre-release version of _CodeSnip_. Re From v4.1.0 the version numbering has attempted to adhere to the principles of [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Release v4.20.0 of 15 May 2022 + +* Added an option to delete the user defined database. +* Fixed bug that enabled the user to attempt to move, or back up, the user database when it doesn't exist. These options are now disabled when there is no user database. +* Added facility to customise the size of font used in the details pane for all items except the source code font (which could already be modified separately). A new preference was added to the Display pane of the Preferences dialogue box to be used to set the font size. +* Rearranged the controls on the Preferences dialogue box's Display pane. +* Changed the default font used for the overview pane from a fixed value to the default size for the underlying operating system. +* Changed the description of "Delphi 11 Alexandria" to "Delphi 11.x Alexandria" to reflect the fact the Delphi 11 updates have different minor version numbers, but can't be installed alongside each other. +* Widened the compiler list box in the Configure Compilers dialogue box to accommodate the longer name used for Delphi 11.x compilers. +* Refactored some font handling code. +* Operating system detection code was updated to (a) fix some bugs and (b) detect some Dev channel builds of Windows 11. +* Bumped the version of the per-user config file to 18 following the addition of a new preference. +* Help file updated re the changes in this release. +* Documentation updated to reflect changes in this release. +* Updated `README.md` and `Build.html` + ## Release v4.19.0 of 31 December 2021 * Improved user-friendliness of Preferences dialogue box: From 5ee57b09466425da3ef2cef2e301d7f39e07a1db Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 15 May 2022 10:25:32 +0100 Subject: [PATCH 087/330] Bump copyright date in header comments Change copyright date range to include 2022 for files modified for this release. --- Src/FrDetailView.pas | 2 +- Src/FrDisplayPrefs.pas | 2 +- Src/Help/CodeSnip.hhp | 2 +- Src/Help/HTML/dlg_prefs_display.htm | 2 +- Src/Help/Index.hhk | 2 +- Src/Help/TOC.hhc | 2 +- Src/Hiliter.UAttrs.pas | 2 +- Src/UFontHelper.pas | 2 +- Src/UPreferences.pas | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Src/FrDetailView.pas b/Src/FrDetailView.pas index 78d56a712..b6feecc76 100644 --- a/Src/FrDetailView.pas +++ b/Src/FrDetailView.pas @@ -3,7 +3,7 @@ * 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) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2022, Peter Johnson (gravatar.com/delphidabbler). * * Implements a frame that can display detailed views. } diff --git a/Src/FrDisplayPrefs.pas b/Src/FrDisplayPrefs.pas index 83ed29e2c..015d1ea02 100644 --- a/Src/FrDisplayPrefs.pas +++ b/Src/FrDisplayPrefs.pas @@ -3,7 +3,7 @@ * 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-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2012-2022, Peter Johnson (gravatar.com/delphidabbler). * * Implements a frame that allows user to set application display preferences. diff --git a/Src/Help/CodeSnip.hhp b/Src/Help/CodeSnip.hhp index 1bde50619..271557747 100644 --- a/Src/Help/CodeSnip.hhp +++ b/Src/Help/CodeSnip.hhp @@ -2,7 +2,7 @@ ; 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) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). +; Copyright (C) 2005-2022, Peter Johnson (gravatar.com/delphidabbler). ; ; CodeSnip help project file. diff --git a/Src/Help/HTML/dlg_prefs_display.htm b/Src/Help/HTML/dlg_prefs_display.htm index 0dbb43732..50ed4fcfd 100644 --- a/Src/Help/HTML/dlg_prefs_display.htm +++ b/Src/Help/HTML/dlg_prefs_display.htm @@ -4,7 +4,7 @@ * 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-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2012-2022, Peter Johnson (gravatar.com/delphidabbler). * * Help topic for Display page of Preferences dialogue box. --> diff --git a/Src/Help/Index.hhk b/Src/Help/Index.hhk index 9b3a0f57a..f5550c5d1 100644 --- a/Src/Help/Index.hhk +++ b/Src/Help/Index.hhk @@ -4,7 +4,7 @@ * 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) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2022, Peter Johnson (gravatar.com/delphidabbler). * * CodeSnip help index file. --> diff --git a/Src/Help/TOC.hhc b/Src/Help/TOC.hhc index f206f44bc..cd91c1f05 100644 --- a/Src/Help/TOC.hhc +++ b/Src/Help/TOC.hhc @@ -4,7 +4,7 @@ * 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) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2022, Peter Johnson (gravatar.com/delphidabbler). * * CodeSnip help table of contents file. --> diff --git a/Src/Hiliter.UAttrs.pas b/Src/Hiliter.UAttrs.pas index 24a91dd25..42ab29f1d 100644 --- a/Src/Hiliter.UAttrs.pas +++ b/Src/Hiliter.UAttrs.pas @@ -3,7 +3,7 @@ * 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) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2022, Peter Johnson (gravatar.com/delphidabbler). * * Implements classes that define syntax highlighter attributes along with an * object that provides a list of named highlighter attributes. diff --git a/Src/UFontHelper.pas b/Src/UFontHelper.pas index 5e40c0296..dca71c381 100644 --- a/Src/UFontHelper.pas +++ b/Src/UFontHelper.pas @@ -3,7 +3,7 @@ * 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) 2006-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2006-2022, Peter Johnson (gravatar.com/delphidabbler). * * Implements a static class used to assist when working with fonts. } diff --git a/Src/UPreferences.pas b/Src/UPreferences.pas index 59bd8e09a..26a412804 100644 --- a/Src/UPreferences.pas +++ b/Src/UPreferences.pas @@ -3,7 +3,7 @@ * 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) 2006-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2006-2022, Peter Johnson (gravatar.com/delphidabbler). * * Implements a singletion object that exposes and persists user preferences. } From 8fcc715fa781be3fc27de56b346c013b0b215adb Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 15 May 2022 12:01:46 +0100 Subject: [PATCH 088/330] Update copyright date in License.html Fixes #50 --- Docs/License.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Docs/License.html b/Docs/License.html index 3fd6ff97f..9964fa5df 100644 --- a/Docs/License.html +++ b/Docs/License.html @@ -5,7 +5,7 @@ * 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-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2012-2022, Peter Johnson (gravatar.com/delphidabbler). * * CodeSnip license. --> @@ -225,7 +225,7 @@

    Executable Program

    - DelphiDabbler CodeSnip is copyright © 2005-2021 by CodeSnip is copyright © 2005-2022 by Peter D Johnson.

    @@ -327,7 +327,7 @@

    This condition applies to all files in the Src/Res/Img/Branding directory, all of which are original - work copyright © 2012-2021 by Peter D Johnson.
    From 3d71453660b0bbec1e7e552e28b3e2be29458b1c Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 1 Jul 2022 15:33:20 +0100 Subject: [PATCH 089/330] Update 3rd party PJSysInfo unit to v5.13.0 Fix serous long term bug in PJSysInfo Fixes #53 --- Src/3rdParty/PJSysInfo.pas | 396 ++++++++++++++++++------------------- 1 file changed, 195 insertions(+), 201 deletions(-) diff --git a/Src/3rdParty/PJSysInfo.pas b/Src/3rdParty/PJSysInfo.pas index b1040bb83..75bba654b 100644 --- a/Src/3rdParty/PJSysInfo.pas +++ b/Src/3rdParty/PJSysInfo.pas @@ -1172,6 +1172,14 @@ 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; + Name: string; + end; + const { Known windows build numbers. @@ -1208,21 +1216,35 @@ implementation // Windows 10 ---------------------------------------------------------------- - Win10TH1Build = 10240; // Windows 10 TH1 - version 1507 (1st release) - Win10TH2Build = 10586; // Windows 10 TH2 - version 1511 - Win10RS1Build = 14393; // Windows 10 RS1 - version 1607 - Win10RS2Build = 15063; // Windows 10 RS2 - version 1703 - Win10RS3Build = 16299; // Windows 10 RS3 - version 1709 - Win10RS4Build = 17134; // Windows 10 RS4 - version 1803 - Win10RS5Build = 17763; // Windows 10 RS5 - version 1809 - Win1019H1Build = 18362; // Windows 10 19H1 - version 1903 - Win1019H2Build = 18363; // Windows 10 19H2 - version 1909 - Win1020H1Build = 19041; // Windows 10 20H1 - version 2004 - Win1020H2Build = 19042; // Windows 10 20H2 - version 20H2 - Win1021H1Build = 19043; // Windows 10 21H1 - version 21H1 - // revisions 844..964 were beta - Win1021H2Build = 19044; // Windows 10 21H2 - version 21H2 - // revisions 1147..1266 were previews + // Map of Win 10 builds from 1st release (version 1507) to version 20H2 + Win10BuildMap: array[0..10] of TBuildNameMap = ( + (Build: 10240; Name: 'Version 1507'), + (Build: 10586; Name: 'Version 1511: November Update'), + (Build: 14393; Name: 'Version 1607: Anniversary Update'), + (Build: 15063; Name: 'Version 1703: Creators Update'), + (Build: 16299; Name: 'Version 1709: Fall Creators Update'), + (Build: 17134; Name: 'Version 1803: April 2018 Update'), + (Build: 17763; Name: 'Version 1809: October 2018 Update'), + (Build: 18362; Name: 'Version 1903: May 2019 Update'), + (Build: 18363; Name: 'Version 1909: November 2019 Update'), + (Build: 19041; Name: 'Version 2004: May 2020 Update'), + // Note: Microsoft announced the official version name of build 19042 as + // '20H2', not '2010' which some had expected it to be + (Build: 19042; Name: 'Version 20H2: October 2020 Update') + ); + + // Additional information is available for Win 10 buulds from version 21H1, + // as follows: + + // Windows 10 version 21H1: + // * revisions 844..964 were Beta builds + // * later revisions were Public Release builds + Win1021H1Build = 19043; + + // Windows 10 version 21H2: + // * revisions 1147..1266 were Preview builds + // * later revisions were Public Release builds + Win1021H2Build = 19044; // Fast ring Win10FastRing: array[0..21] of Integer = ( @@ -1243,18 +1265,21 @@ implementation // Windows 11 ---------------------------------------------------------------- // NOTE: Preview and beta & release versions of Windows 11 report version 10.0 - Win11DevBuild = 21996; // Windows 11 version Dev - // - 10.0.21996.1 (Insider version) - Win11v21H2Build = 22000; // Version depends on revision # [Rev#]: - // Revision # 51,65,71,100,120,132,168: - // Windows 11 version 21H2 - // - 10.0.22000.[Rev#] (Insider version) - // Revision # 184 - // Windows 11 version 21H2 - // - 10.0.22000.184 (Beta Version) - // Revision # 194 - // Windows 11 version 21H2 - // - ** 1st Public Release ** + + // Windows 11 version Dev: 10.0.21996.1 (Insider version) + Win11DevBuild = 21996; + + // Windows 11 version 21H2: + // * revisions 51,65,71,100,120,132,168 were Insider builds + // * revision 184 was Beta build + // * revision 194 and later were Public Release builds + Win11v21H2Build = 22000; + + // Windows 11 version 22H2: + // * revision 1 was Beta & Release Preview build + // * revisions 105 & 169 were Release Preview builds + // * revision 160 was Beta build + Win11v22H2Build = 22621; // Dev channel release - different sources give different names. // From what I can gather (and take this with a pinch of salt!): @@ -1264,55 +1289,67 @@ implementation // * From build 22567 the release string changed from "Dev" to "22H" // Builds with version string "Dev" - Win11DevChannelDevBuilds: array[0..20] of Integer = ( - 22449, 22454, 22458, 22463, 22468, // pre Win 11 release + Win11DevChannelDevBuilds: array[0..28] of Integer = ( + // pre Win 11 release + 22449, 22454, 22458, 22463, 22468, + // post Win 11 release, pre Win 11 22H2 beta release 22471, 22478, 22483, 22489, 22494, 22499, 22504, 22509, 22518, 22523, 22526, - 22533, 22538, 22543, 22557, 22563 + 22533, 22538, 22543, 22557, 22563, + // post Win 11 22H2 beta release + 25115, 25120, 25126, 25131, 25136, 25140, 25145, 25151 ); // Builds with version string "22H2" in Dev channel Win11DevChannel22H2Builds: array[0..2] of Integer = ( 22567, 22572, 22579 ); // Builds with version string "22H2" in Dev & Beta channels - Win11DevBetaChannels22H2Builds: array[0..3] of Integer = ( - 22581, 22593, 22598, 22610 + Win11DevBetaChannels22H2Builds: array[0..4] of Integer = ( + 22581, 22593, 22598, 22610, 22616 ); Win11FirstBuild = Win11DevBuild; // First build number of Windows 11 - // Windows 2016 Server ------------------------------------------------------- - Win2016TP1Build = 9841; // Win 2016 Server Technical Preview 1 - Win2016TP2Build = 10074; // Win 2016 Server Technical Preview 2 - Win2016TP3Build = 10514; // Win 2016 Server Technical Preview 3 - Win2016TP4Build = 10586; // Win 2016 Server Technical Preview 4 - Win2016TP5Build = 14300; // Win 2016 Server Technical Preview 5 - Win2016RTMBuild = 14393; // Win 2016 Server Release To Manufacturing - Win2016v1709Build = 16299; // Win Server 2016 version 1709 - Win2016v1803Build = 17134; // Win Server 2016 version 1803 - Win2016LastBuild = Win2016v1803Build; // Last build number of Win 2016 Server - // After this it's Win 2019 Server - - // Windows 2019 Server ------------------------------------------------------- - // Insider Preview builds + // 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 + Win2016LastBuild = 17134; + Win2019LastBuild = 18363; + WinServerLastBuild = 19042; + + // Map of Windows server releases that are named straightforwardly + WinServerSimpleBuildMap: array[0..12] of TBuildNameMap = ( + // Windows Server 2016 + (Build: 10074; Name: 'Technical Preview 2'), + (Build: 10514; Name: 'Technical Preview 3'), + (Build: 10586; Name: 'Technical Preview 4'), + (Build: 14300; Name: 'Technical Preview 5'), + (Build: 14393; Name: 'Version 1607'), + (Build: 16299; Name: 'Version 1709'), + (Build: Win2016LastBuild; Name: 'Version 1803'), + // Windows Server 2019 + (Build: 17763; Name: 'Version 1809'), + (Build: 18362; Name: 'Version 1903'), + (Build: Win2019LastBuild; Name: 'Version 1909'), + // Windows Server (no year number) + (Build: 19041; Name: 'Version 2004'), + (Build: WinServerLastBuild; Name: 'Version 20H2'), + // Windows Sever 2022 + (Build: 20348; Name: 'Version 21H2') + ); + + // Windows server releases needing special handling + + // Server 2016 Technical Preview 1: reports version 6.4 instead of 10.0! + Win2016TP1Build = 9841; + + // Server 2019 Insider Preview builds: require format strings in names Win2019IPBuilds: array[0..9] of Integer = ( 17623, 17627, 17666, 17692, 17709, 17713, 17723, 17733, 17738, 17744 ); - // Release builds - Win2019v1809Build = 17763; // Win Server 2019 version 1809 - Win2019v1903Build = 18362; // Win Server 2019 version 1903 - Win2019v1909Build = 18363; // Win Server 2019 version 1909 - Win2019LastBuild = Win2019v1909Build; // Last build number of Win 2019 Server - // After this it's Windows Server - - // Windows Server ------------------------------------------------------------ - WinServerv2004Build = 19041; // Win Server version 2004 - WinServerv20H2Build = 19042; // Win Server version 20H2 - WinServerLastBuild = WinServerv20H2Build; // Last build number of Windows - // Server. After this it's Window - // 2022 Sever - - // Windows 2022 Server ------------------------------------------------------- - Win2022v21H2Build = 20348; // Win Server 2022 version 21H2 + type // Function type of the GetNativeSystemInfo and GetSystemInfo functions @@ -1411,9 +1448,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; @@ -1422,12 +1462,20 @@ 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 @@ -1450,6 +1498,31 @@ function FindBuildNumberFrom(const BNs: array of Integer; var FoundBN: Integer): end; end; +// Checks if any of the build numbers in the given array match that of the +// current OS. If so 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): Boolean; +var + I: Integer; +begin + FoundBN := 0; + FoundExtra := ''; + Result := False; + for I := Low(Infos) to High(Infos) do + begin + if IsBuildNumber(Infos[I].Build) then + begin + FoundBN := Infos[I].Build; + FoundExtra := Infos[I].Name; + 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; @@ -1782,68 +1855,17 @@ procedure InitPlatformIdEx; begin case InternalMinorVersion of 0: - // ** As of 2021/10/05 all releases of Windows 10 **and** + // ** 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 - if IsBuildNumber(Win10TH1Build) then - begin - // First public release of Window 10 - InternalBuildNumber := Win10TH1Build; - InternalExtraUpdateInfo := 'Version 1507'; - end - else if IsBuildNumber(Win10TH2Build) then - begin - InternalBuildNumber := Win10TH2Build; - InternalExtraUpdateInfo := 'Version 1511: November Update'; - end - else if IsBuildNumber(Win10RS1Build) then - begin - InternalBuildNumber := Win10RS1Build; - InternalExtraUpdateInfo := 'Version 1607: Anniversary Update'; - end - else if IsBuildNumber(Win10RS2Build) then - begin - InternalBuildNumber := Win10RS2Build; - InternalExtraUpdateInfo := 'Version 1703: Creators Update'; - end - else if IsBuildNumber(Win10RS3Build) then - begin - InternalBuildNumber := Win10RS3Build; - InternalExtraUpdateInfo := 'Version 1709: Fall Creators Update'; - end - else if IsBuildNumber(Win10RS4Build) then - begin - InternalBuildNumber := Win10RS4Build; - InternalExtraUpdateInfo := 'Version 1803: April 2018 Update'; - end - else if IsBuildNumber(Win10RS5Build) then - begin - InternalBuildNumber := Win10RS5Build; - InternalExtraUpdateInfo := 'Version 1809: October 2018 Update'; - end - else if IsBuildNumber(Win1019H1Build) then - begin - InternalBuildNumber := Win1019H1Build; - InternalExtraUpdateInfo := 'Version 1903: May 2019 Update'; - end - else if IsBuildNumber(Win1019H2Build) then - begin - InternalBuildNumber := Win1019H2Build; - InternalExtraUpdateInfo := 'Version 1909: November 2019 Update'; - end - else if IsBuildNumber(Win1020H1Build) then - begin - InternalBuildNumber := Win1020H1Build; - InternalExtraUpdateInfo := 'Version 2004: May 2020 Update'; - end - else if IsBuildNumber(Win1020H2Build) then + if FindBuildNameAndExtraFrom( + Win10BuildMap, InternalBuildNumber, InternalExtraUpdateInfo + ) then begin - InternalBuildNumber := Win1020H2Build; - // Note: Microsoft announced the official version name is '20H2', - // not '2010' which some had expected it to be - InternalExtraUpdateInfo := 'Version 20H2: October 2020 Update'; + // Nothing to do: required internal variables set in function call end else if IsBuildNumber(Win1021H1Build) then begin @@ -1882,7 +1904,6 @@ procedure InitPlatformIdEx; end // Win 11 releases are reporting v10.0 // Details taken from: https://tinyurl.com/usupsz4a - // Correct according to above web page as of 2021-09-11 else if IsBuildNumber(Win11DevBuild) then begin InternalBuildNumber := Win11DevBuild; @@ -1899,7 +1920,7 @@ procedure InitPlatformIdEx; // *** Amazingly one of them, revision 194, is the 1st public // release of Win 11 -- well hidden eh?! InternalBuildNumber := Win11v21H2Build; - case InternalBuildNumber of + case InternalRevisionNumber of 194..MaxInt: // Public releases of Windows 11 have build number >= 194 InternalExtraUpdateInfo := 'Version 21H2'; @@ -1915,7 +1936,35 @@ procedure InitPlatformIdEx; ); else InternalExtraUpdateInfo := Format( - 'Unknown release v10.0.%d.%d', + 'Version 21H2 [Unknown release v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + end; + end + else if IsBuildNumber(Win11v22H2Build) then + begin + InternalBuildNumber := Win11v22H2Build; + // See comments with declaration of Win11v22H2Build for details + // of naming of revisions + case InternalRevisionNumber of + 1: + InternalExtraUpdateInfo := Format( + 'Version 22H2 [Beta & Release Preview v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + 105, 169: + InternalExtraUpdateInfo := Format( + 'Version 22H2 [Release Preview v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + 160: + 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; @@ -1953,92 +2002,37 @@ procedure InitPlatformIdEx; end else // Win32ProductType in [VER_NT_DOMAIN_CONTROLLER, VER_NT_SERVER] begin - // Check for Win Server 2016 technical previews. - // We don't check for TP1 // here because that reported version 6.4, - // not version 10! - if IsBuildNumber(Win2016TP2Build) then - begin - InternalBuildNumber := Win2016TP2Build; - InternalExtraUpdateInfo := 'Technical Preview 2'; - end - else if IsBuildNumber(Win2016TP3Build) then - begin - InternalBuildNumber := Win2016TP3Build; - InternalExtraUpdateInfo := 'Technical Preview 3'; - end - else if IsBuildNumber(Win2016TP4Build) then - begin - InternalBuildNumber := Win2016TP4Build; - InternalExtraUpdateInfo := 'Technical Preview 4'; - end - else if IsBuildNumber(Win2016TP5Build) then - begin - InternalBuildNumber := Win2016TP5Build; - InternalExtraUpdateInfo := 'Technical Preview 5'; - end - else if IsBuildNumber(Win2016RTMBuild) then - begin - InternalBuildNumber := Win2016RTMBuild; - InternalExtraUpdateInfo := 'Version 1607'; - end - else if IsBuildNumber(Win2016v1709Build) then - begin - InternalBuildNumber := Win2016v1709Build; - InternalExtraUpdateInfo := 'Version 1709'; - end - else if IsBuildNumber(Win2016v1803Build) then + // 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 + ) then begin - InternalBuildNumber := Win2016v1803Build; - InternalExtraUpdateInfo := 'Version 1803'; + // Nothing to do: required internal variables set in function call end else if FindBuildNumberFrom( Win2019IPBuilds, InternalBuildNumber ) then begin + // Windows 2019 Insider preview builds require build number in + // Extra Update Info. InternalExtraUpdateInfo := Format( 'Insider Preview Build %d', [InternalBuildNumber] ); end - else if IsBuildNumber(Win2019v1809Build) then - begin - InternalBuildNumber := Win2019v1809Build; - InternalExtraUpdateInfo := 'Version 1809'; - end - else if IsBuildNumber(Win2019v1903Build) then - begin - InternalBuildNumber := Win2019v1903Build; - InternalExtraUpdateInfo := 'Version 1903'; - end - else if IsBuildNumber(Win2019v1909Build) then - begin - InternalBuildNumber := Win2019v1909Build; - InternalExtraUpdateInfo := 'Version 1909'; - end - else if IsBuildNumber(WinServerv2004Build) then - begin - InternalBuildNumber := WinServerv2004Build; - InternalExtraUpdateInfo := 'Version 2004'; - end - else if IsBuildNumber(WinServerv20H2Build) then - begin - InternalBuildNumber := WinServerv20H2Build; - InternalExtraUpdateInfo := 'Version 20H2'; - end - else if IsBuildNumber(Win2022v21H2Build) then - begin - InternalBuildNumber := Win2022v21H2Build; - InternalExtraUpdateInfo := 'Version 21H2'; - 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 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 @@ -2725,7 +2719,7 @@ class function TPJOSInfo.Product: TPJOSProduct; 0: if not IsServer then begin - if InternalBuildNumber < Win11FirstBuild then + if TestBuildNumber(VER_LESS, Win11FirstBuild) then Result := osWin10 else // ** As of 2021-10-05 Win 11 is reporting version 10.0! @@ -2733,11 +2727,11 @@ class function TPJOSInfo.Product: TPJOSProduct; end else begin - if InternalBuildNumber <= Win2016LastBuild then + if TestBuildNumber(VER_LESS_EQUAL, Win2016LastBuild) then Result := osWin10Svr - else if InternalBuildNumber <= Win2019LastBuild then + else if TestBuildNumber(VER_LESS_EQUAL, Win2019LastBuild) then Result := osWinSvr2019 - else if InternalBuildNumber <= WinServerLastBuild then + else if TestBuildNumber(VER_LESS_EQUAL, WinServerLastBuild) then Result := osWinServer else Result := osWinSvr2022; From 41305ed8017d3e5685e529021fb1aaaf7e3f26ca Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 1 Jul 2022 15:41:31 +0100 Subject: [PATCH 090/330] Bump version information to v2.20.1 build 265 --- Src/VCodeSnip.vi | 4 ++-- Src/VCodeSnipPortable.vi | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Src/VCodeSnip.vi b/Src/VCodeSnip.vi index 336a57137..0a826d5a3 100644 --- a/Src/VCodeSnip.vi +++ b/Src/VCodeSnip.vi @@ -8,8 +8,8 @@ [Fixed File Info] -File Version #=4, 20, 0, 264 -Product Version #=4, 20, 0, 0 +File Version #=4, 20, 1, 265 +Product Version #=4, 20, 1, 0 File OS=4 File Type=1 File Sub-Type=0 diff --git a/Src/VCodeSnipPortable.vi b/Src/VCodeSnipPortable.vi index 6f78cbaff..d5fdc997b 100644 --- a/Src/VCodeSnipPortable.vi +++ b/Src/VCodeSnipPortable.vi @@ -8,8 +8,8 @@ [Fixed File Info] -File Version #=4, 20, 0, 264 -Product Version #=4, 20, 0, 0 +File Version #=4, 20, 1, 265 +Product Version #=4, 20, 1, 0 File OS=4 File Type=1 File Sub-Type=0 From 83aee213d2421cc1cdadc3c9323e19accf184829 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 1 Jul 2022 15:46:31 +0100 Subject: [PATCH 091/330] Update change log re v4.20.1 changes --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 251825295..5f9ed8c24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,11 @@ This change log begins with the first ever pre-release version of _CodeSnip_. Re From v4.1.0 the version numbering has attempted to adhere to the principles of [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Release v4.20.1 of 01 July 2022 + +* Operating system detection code was updated to (a) fix bugs and (b) detect some Dev, Beta and Release Preview builds of Windows 11 22H2. +* Fixed copyright date in `Docs/License.html`. + ## Release v4.20.0 of 15 May 2022 * Added an option to delete the user defined database. From 918b871cc667a7e18ccd857195f9cd4480665c7c Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 1 Jul 2022 16:01:47 +0100 Subject: [PATCH 092/330] Update README.md Minor edits Add several links --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 74fad43e0..5bfc29a5d 100644 --- a/README.md +++ b/README.md @@ -18,13 +18,13 @@ CodeSnip can import code from the DelphiDabbler [Code Snippets Database](https:/ The program is available in both standard and portable editions. -CodeSnip requires Windows 2000 or later and Internet Explorer 6 or later, although XP and IE 8 and later are preferred. +CodeSnip requires Windows 2000 or later and Internet Explorer 6 or later, although XP and IE 8 and later are recommended. ## Installation The standard edition of CodeSnip is installed and removed using a standard Windows installer. Administrator privileges are required for installation. -The portable edition has no installer. Simply follow the instructions in the [read me file](https://github.com/delphidabbler/codesnip/blob/master/Docs/ReadMe.txt) that is included in the download zip file. +The portable edition has no installer. Simply follow the instructions in the [read me file](https://raw.githubusercontent.com/delphidabbler/codesnip/master/Docs/ReadMe.txt) that is included in the download zip file. ## Support @@ -37,7 +37,7 @@ The following support is available CodeSnip users: There's also plenty of info available on how to compile CodeSnip from source - see below. -* This link takes you to the most recent version of the read-me file -- it can change from release to release. +> * This link takes you to the most recent version of the read-me file -- it can change from release to release. ## Source Code @@ -45,7 +45,7 @@ CodeSnip's source code is maintained in the [`delphidabbler/codesnip`](https://g The [Git Flow](https://nvie.com/posts/a-successful-git-branching-model/) methodology has been adopted, with the exception of some branches that have been used in various attempts to start work on CodeSnip 5. -The following branches existed as of 2022/01/01: +The following branches existed as of 2022/07/01: * `master`: Always reflects the state of the source code as of the latest release.‡ * `develop`: Main development branch. The head of this branch contains the latest v4 development code. @@ -71,11 +71,11 @@ To contribute to CodeSnip 4 development please fork the repository on GitHub. Cr The license that applies to any existing file you edit will continue to apply to the edited file. Any existing license text or copyright statement **must not** be altered or removed. -Any new file you contribute **must** either be licensed under the Mozilla Public License v2.0 (MPL2) or have a license compatible with the MPL2. If a license is not specified then the MPL2 will be applied to the file. You should insert a suitable copyright statement in the file. +Any new file you contribute **must** either be licensed under the [Mozilla Public License v2.0](https://www.mozilla.org/MPL/2.0/) (MPL2) or have a license compatible with the MPL2. If a license is not specified then MPL2 will be assumed and will be applied to the file. You should insert a suitable copyright statement in the file. Any third party code used by your contributed code **must** also have a license compatible with the MPL2. -> MPL2 boilerplate text, in several programming language's comment formats, can be found in the file `Docs/MPL-2.0-Boilerplate.txt`. You will need to change the name of the copyright holder. +> MPL2 boilerplate text, in several programming language's comment formats, can be found in the file [`Docs/MPL-2.0-Boilerplate.txt`](https://raw.githubusercontent.com/delphidabbler/codesnip/master/Docs/MPL-2.0-Boilerplate.txt). You will need to change the name of the copyright holder. ### Compiling @@ -87,7 +87,7 @@ CodeSnip 4 **must** be compiled with Delphi XE. See [Compiling & Source Code FAQ ## Change Log -The program's current change log can be found in the file `CHANGELOG.md` in the root of the `master` branch. +The program's current change log can be found in the file [`CHANGELOG.md`](https://github.com/delphidabbler/codesnip/blob/master/CHANGELOG.md) in the root of the `master` branch. > Note that 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. From 633a18dd45d93376492edff7b14b9a74d7a5d601 Mon Sep 17 00:00:00 2001 From: Peter Johnson <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 31 Jul 2022 07:27:39 +0100 Subject: [PATCH 093/330] Update .gitignore Remove invalid paths --- .gitignore | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitignore b/.gitignore index 34885dadb..c5a26ecfb 100644 --- a/.gitignore +++ b/.gitignore @@ -15,5 +15,3 @@ Exe Release Src/CodeSnip.cfg Src/AutoGen/IntfExternalObj.pas -Src/Portable/CodeSnipPortable.cfg -Src/Main/AutoGen/IntfExternalObj.pas From a6fd1892c3317c811e4d4f7e408ae587b7722ac3 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Mon, 29 Aug 2022 16:30:29 +0100 Subject: [PATCH 094/330] Fix F1 key press over menu item bug Add OnHelp event handler on main form that cancels help operation that triggered the exception. Fixes #54 --- Src/FmMain.dfm | 1 + Src/FmMain.pas | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/Src/FmMain.dfm b/Src/FmMain.dfm index 343bc0d9b..902e71720 100644 --- a/Src/FmMain.dfm +++ b/Src/FmMain.dfm @@ -1174,6 +1174,7 @@ inherited MainForm: TMainForm end end object appEvents: TApplicationEvents + OnHelp = appEventsHelp OnHint = appEventsHint Left = 291 Top = 96 diff --git a/Src/FmMain.pas b/Src/FmMain.pas index 1342be28c..828ef997f 100644 --- a/Src/FmMain.pas +++ b/Src/FmMain.pas @@ -478,6 +478,11 @@ TMainForm = class(THelpAwareForm) procedure actViewTestUnitUpdate(Sender: TObject); /// Displays the Welcome page in the details pane. procedure actWelcomeExecute(Sender: TObject); + /// Handles events triggered when help system is invoked. Prevents + /// exception being raised when F1 key is pressed when a menu is dropped + /// down. + function appEventsHelp(Command: Word; Data: Integer; + var CallHelp: Boolean): Boolean; /// Handles events triggered when a control issues a hint. The /// hint is displayed in the form's status bar. procedure appEventsHint(Sender: TObject); @@ -1215,6 +1220,16 @@ procedure TMainForm.AfterShowForm; fMainDisplayMgr.ShowWelcomePage; 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); begin if Assigned(fStatusBarMgr) then From 984a71393c5c96e839a13a613810b47eb700a639 Mon Sep 17 00:00:00 2001 From: Peter Johnson <5164283+delphidabbler@users.noreply.github.com> Date: Sat, 17 Sep 2022 23:11:10 +0100 Subject: [PATCH 095/330] Create .gitattributes Get Linguist to highlight .ps files as Pascal (fixes #58) Also get Linguist to count Markdown files in repo stats (no related issue) --- .gitattributes | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .gitattributes 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 From 7a1fcc6775490157c4023b73a827e880233b9a0b Mon Sep 17 00:00:00 2001 From: Peter Johnson <5164283+delphidabbler@users.noreply.github.com> Date: Sat, 17 Sep 2022 23:39:39 +0100 Subject: [PATCH 096/330] Update PJSysInfo.pas Update to release v5.15.0 Fixes #55 --- Src/3rdParty/PJSysInfo.pas | 111 +++++++++++++++++++++++++------------ 1 file changed, 76 insertions(+), 35 deletions(-) diff --git a/Src/3rdParty/PJSysInfo.pas b/Src/3rdParty/PJSysInfo.pas index 75bba654b..ec8838745 100644 --- a/Src/3rdParty/PJSysInfo.pas +++ b/Src/3rdParty/PJSysInfo.pas @@ -1,9 +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-2022, Peter Johnson (@delphidabbler). + * Copyright (C) 2001-2022, 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 @@ -232,7 +232,7 @@ interface // 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 + // https://msdn.microsoft.com/en-us/library/ms724358 PRODUCT_BUSINESS = $00000006; PRODUCT_BUSINESS_N = $00000010; PRODUCT_CLUSTER_SERVER = $00000012; @@ -324,7 +324,7 @@ interface // 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 @@ -806,7 +806,7 @@ TPJComputerInfo = class(TObject) /// 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. - /// Based on code at http://edn.embarcadero.com/article/26752 + /// Based on a former Embarcadero article. /// class function IsAdmin: Boolean; @@ -818,7 +818,7 @@ TPJComputerInfo = class(TObject) /// earlier compatibility mode on Windows Vista or later, regardless of /// whether UAC is enabled or not. /// Based on code on Stack Overflow, answer by norgepaul, at - /// http://tinyurl.com/avlztmg + /// https://tinyurl.com/avlztmg /// class function IsUACActive: Boolean; @@ -983,7 +983,7 @@ implementation // Map of product codes per GetProductInfo API to product names // ** 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 + // https://msdn.microsoft.com/en-us/library/ms724358 cProductMap: array[1..87] of record Id: Cardinal; // product ID Name: string; // product name @@ -1246,6 +1246,10 @@ TBuildNameMap = record // * later revisions were Public Release builds Win1021H2Build = 19044; + // Windows 10 version 22H2 + // * revision 1865 was Release Preview build (KB5015878) + Win1022H2Build = 19045; + // Fast ring Win10FastRing: array[0..21] of Integer = ( 19536, 19541, 19546, 19551, 19555, 19559, 19564, 19569, 19577, 19582, 19587, @@ -1270,16 +1274,22 @@ TBuildNameMap = record Win11DevBuild = 21996; // Windows 11 version 21H2: - // * revisions 51,65,71,100,120,132,168 were Insider builds - // * revision 184 was Beta build - // * revision 194 and later were Public Release builds + // * Dev channel: revs 51,65,71 + // * Dev & Beta channels: revs 100,120,132,160,168 + // * Beta & Release Preview channels: revs 176,184 + // * Public Release: rev 194 and later Win11v21H2Build = 22000; - // Windows 11 version 22H2: - // * revision 1 was Beta & Release Preview build - // * revisions 105 & 169 were Release Preview builds - // * revision 160 was Beta build + // Windows 11 version 22H2 + // + // Build 22631 was the original beta build. + // * Beta & Release Preview channels: rev 1 + // * Beta channel: revs 160,290,436,440,450,575,586,590 + // * Release Preview channel: revs 105,169,232,317,382,457 Win11v22H2Build = 22621; + // Build 22632 was added as an alternative Beta channel build as of rev 290: + // * Beta channel: revs 290,436,440,450,575,586,590 + Win11v22H2BuildAlt = 22622; // Dev channel release - different sources give different names. // From what I can gather (and take this with a pinch of salt!): @@ -1289,14 +1299,15 @@ TBuildNameMap = record // * From build 22567 the release string changed from "Dev" to "22H" // Builds with version string "Dev" - Win11DevChannelDevBuilds: array[0..28] of Integer = ( + Win11DevChannelDevBuilds: array[0..36] of Integer = ( // pre Win 11 release 22449, 22454, 22458, 22463, 22468, // post Win 11 release, pre Win 11 22H2 beta release 22471, 22478, 22483, 22489, 22494, 22499, 22504, 22509, 22518, 22523, 22526, 22533, 22538, 22543, 22557, 22563, // post Win 11 22H2 beta release - 25115, 25120, 25126, 25131, 25136, 25140, 25145, 25151 + 25115, 25120, 25126, 25131, 25136, 25140, 25145, 25151, 25158, 25163, 25169, + 25174, 25179, 25182, 25188, 25193 ); // Builds with version string "22H2" in Dev channel Win11DevChannel22H2Builds: array[0..2] of Integer = ( @@ -1813,7 +1824,7 @@ procedure InitPlatformIdEx; Win32ProductType := 0; // NOTE: It's going to be very slow to test for all possible build numbers, // so I've narrowed the search down using the information at - // http://en.wikipedia.org/wiki/Windows_NT + // https://en.wikipedia.org/wiki/Windows_NT case InternalMajorVersion of 6: begin @@ -1884,6 +1895,13 @@ procedure InitPlatformIdEx; InternalExtraUpdateInfo := InternalExtraUpdateInfo + ' (preview)'; end + else if IsBuildNumber(Win1022H2Build) then + begin + InternalBuildNumber := Win1022H2Build; + { TODO: As of 1 Aug 2022 all rev numbers are previews. + Change following once this is no longer the case. } + InternalExtraUpdateInfo := 'Version 22H2 (preview)'; + end else if FindBuildNumberFrom( Win10DevChannel, InternalBuildNumber ) then @@ -1924,14 +1942,20 @@ procedure InitPlatformIdEx; 194..MaxInt: // Public releases of Windows 11 have build number >= 194 InternalExtraUpdateInfo := 'Version 21H2'; - 51, 65, 71, 100, 120, 132, 168: + 51, 65, 71: + InternalExtraUpdateInfo := Format( + 'Version 21H2 [Dev Channel v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + 100, 120, 132, 160, 168: InternalExtraUpdateInfo := Format( - 'Version 21H2 [Insider v10.0.%d.%d]', + 'Version 21H2 [Dev & Beta Channels v10.0.%d.%d]', [InternalBuildNumber, InternalRevisionNumber] ); - 184: + 176, 184: InternalExtraUpdateInfo := Format( - 'Version 21H2 [Beta v10.0.%d.%d]', + 'Version 21H2 ' + + '[Beta & Release Preview Channels v10.0.%d.%d]', [InternalBuildNumber, InternalRevisionNumber] ); else @@ -1943,21 +1967,40 @@ procedure InitPlatformIdEx; end else if IsBuildNumber(Win11v22H2Build) then begin + // See comments with declarations of Win11v22H2Build and + // Win11v22H2BuildAlt for details of naming of revisions. InternalBuildNumber := Win11v22H2Build; - // See comments with declaration of Win11v22H2Build for details - // of naming of revisions case InternalRevisionNumber of 1: InternalExtraUpdateInfo := Format( 'Version 22H2 [Beta & Release Preview v10.0.%d.%d]', [InternalBuildNumber, InternalRevisionNumber] ); - 105, 169: + 105, 169, 232, 317, 382, 457: InternalExtraUpdateInfo := Format( 'Version 22H2 [Release Preview v10.0.%d.%d]', [InternalBuildNumber, InternalRevisionNumber] ); - 160: + 160, 290, 436, 440, 450, 575, 586, 590: + 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(Win11v22H2BuildAlt) then + begin + // See comments with declarations of Win11v22H2Build and + // Win11v22H2BuildAlt for details of naming of revisions. + InternalBuildNumber := Win11v22H2BuildAlt; + // Set fallback update info for unknown revisions + case InternalRevisionNumber of + 290, 436, 440, 450, 575, 586, 590: InternalExtraUpdateInfo := Format( 'Version 22H2 [Beta v10.0.%d.%d]', [InternalBuildNumber, InternalRevisionNumber] @@ -2196,8 +2239,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 @@ -2688,9 +2731,9 @@ 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 @@ -2699,7 +2742,7 @@ class function TPJOSInfo.Product: TPJOSProduct; // 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 http://bit.ly/MJSO8Q. + // See https://bit.ly/MJSO8Q. if IsServer then Result := osWin10Svr; else @@ -2711,7 +2754,7 @@ 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????!!!! @@ -3001,8 +3044,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 @@ -3252,4 +3294,3 @@ initialization InitPlatformIdEx; end. - From 49b5dff87a5595dfc42a776943e60494d43d9a7f Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 4 Nov 2022 16:38:20 +0000 Subject: [PATCH 097/330] Update DDabLib PJSysInfo unit to v5.17.0 Replaces #55 that updated to PJSysInfo v5.15.5 Closes #62 --- Src/3rdParty/PJSysInfo.pas | 58 ++++++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/Src/3rdParty/PJSysInfo.pas b/Src/3rdParty/PJSysInfo.pas index ec8838745..9a93aff09 100644 --- a/Src/3rdParty/PJSysInfo.pas +++ b/Src/3rdParty/PJSysInfo.pas @@ -1186,13 +1186,10 @@ TBuildNameMap = record 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://en.wikipedia.org/wiki/Windows_Server https://en.wikipedia.org/wiki/Windows_Server_2019 https://en.wikipedia.org/wiki/Windows_Server_2016 https://tinyurl.com/y8tfadm2 (MS Windows Server release information) - https://tinyurl.com/usupsz4a (Win 11 Version Numbers & Build Versions) 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) @@ -1282,13 +1279,12 @@ TBuildNameMap = record // Windows 11 version 22H2 // - // Build 22631 was the original beta build. - // * Beta & Release Preview channels: rev 1 - // * Beta channel: revs 160,290,436,440,450,575,586,590 - // * Release Preview channel: revs 105,169,232,317,382,457 + // Build 22621 was the original beta build. Same build used for releases and + // various other channels. + // See **REF1** in implementation Win11v22H2Build = 22621; // Build 22632 was added as an alternative Beta channel build as of rev 290: - // * Beta channel: revs 290,436,440,450,575,586,590 + // * Beta channel: revs 290,436,440,450,575,586,590,598,601 Win11v22H2BuildAlt = 22622; // Dev channel release - different sources give different names. @@ -1299,7 +1295,7 @@ TBuildNameMap = record // * From build 22567 the release string changed from "Dev" to "22H" // Builds with version string "Dev" - Win11DevChannelDevBuilds: array[0..36] of Integer = ( + Win11DevChannelDevBuilds: array[0..43] of Integer = ( // pre Win 11 release 22449, 22454, 22458, 22463, 22468, // post Win 11 release, pre Win 11 22H2 beta release @@ -1307,7 +1303,9 @@ TBuildNameMap = record 22533, 22538, 22543, 22557, 22563, // post Win 11 22H2 beta release 25115, 25120, 25126, 25131, 25136, 25140, 25145, 25151, 25158, 25163, 25169, - 25174, 25179, 25182, 25188, 25193 + 25174, 25179, 25182, 25188, 25193, 25197, 25201, 25206, 25211, + // post Win 11 22H2 release + 25217, 25227, 25231 ); // Builds with version string "22H2" in Dev channel Win11DevChannel22H2Builds: array[0..2] of Integer = ( @@ -1318,6 +1316,8 @@ TBuildNameMap = record 22581, 22593, 22598, 22610, 22616 ); + Win11FutureComponentBetaChannelBuilds: array[0..0] of Integer = (22623); + Win11FirstBuild = Win11DevBuild; // First build number of Windows 11 // Windows server v10.0 version ---------------------------------------------- @@ -1856,7 +1856,7 @@ procedure InitPlatformIdEx; // ** 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] @@ -1866,7 +1866,7 @@ procedure InitPlatformIdEx; begin case InternalMinorVersion of 0: - // ** As of 2022/06/01 all releases of Windows 10 **and** + // ** 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) @@ -1898,9 +1898,13 @@ procedure InitPlatformIdEx; else if IsBuildNumber(Win1022H2Build) then begin InternalBuildNumber := Win1022H2Build; - { TODO: As of 1 Aug 2022 all rev numbers are previews. - Change following once this is no longer the case. } - InternalExtraUpdateInfo := 'Version 22H2 (preview)'; + if IsInRange(InternalRevisionNumber, 1865, 2075) then + InternalExtraUpdateInfo := Format( + 'Version 22H2 [Release Preview v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ) + else + InternalExtraUpdateInfo := 'Version 22H2'; end else if FindBuildNumberFrom( Win10DevChannel, InternalBuildNumber @@ -1967,21 +1971,23 @@ procedure InitPlatformIdEx; end else if IsBuildNumber(Win11v22H2Build) then begin - // See comments with declarations of Win11v22H2Build and - // Win11v22H2BuildAlt for details of naming of revisions. + // **REF1** InternalBuildNumber := Win11v22H2Build; case InternalRevisionNumber of + 876..MaxInt, 382, 521, 525, 608, 674, 675, 755: + InternalExtraUpdateInfo := 'Version 22H2'; 1: InternalExtraUpdateInfo := Format( 'Version 22H2 [Beta & Release Preview v10.0.%d.%d]', [InternalBuildNumber, InternalRevisionNumber] ); - 105, 169, 232, 317, 382, 457: + 105, 169, 232, 317, 457, 607, 754: InternalExtraUpdateInfo := Format( 'Version 22H2 [Release Preview v10.0.%d.%d]', [InternalBuildNumber, InternalRevisionNumber] ); - 160, 290, 436, 440, 450, 575, 586, 590: + 160, 290, 436, 440, 450, 575, 586, 590, 598, 601, 730, 741, 746, + 870, 875: InternalExtraUpdateInfo := Format( 'Version 22H2 [Beta v10.0.%d.%d]', [InternalBuildNumber, InternalRevisionNumber] @@ -2000,9 +2006,9 @@ procedure InitPlatformIdEx; InternalBuildNumber := Win11v22H2BuildAlt; // Set fallback update info for unknown revisions case InternalRevisionNumber of - 290, 436, 440, 450, 575, 586, 590: + 290, 436, 440, 450, 575, 586, 590, 598, 601: InternalExtraUpdateInfo := Format( - 'Version 22H2 [Beta v10.0.%d.%d]', + 'Version 22H2 [October Component Update v10.0.%d.%d]', [InternalBuildNumber, InternalRevisionNumber] ); else @@ -2042,6 +2048,15 @@ procedure InitPlatformIdEx; [InternalBuildNumber, InternalRevisionNumber] ); end + else if FindBuildNumberFrom( + Win11FutureComponentBetaChannelBuilds, InternalBuildNumber + ) then + begin + InternalExtraUpdateInfo := Format( + 'Future Component Update Beta v10.0.%d.%d', + [InternalBuildNumber, InternalRevisionNumber] + ); + end; end else // Win32ProductType in [VER_NT_DOMAIN_CONTROLLER, VER_NT_SERVER] begin @@ -3294,3 +3309,4 @@ initialization InitPlatformIdEx; end. + From 6ea0ea5cca9569cc08b573961852958ded2b9da8 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 4 Nov 2022 17:00:14 +0000 Subject: [PATCH 098/330] Fix corrupted copyright symbols is .vi files --- Src/VCodeSnip.vi | 2 +- Src/VCodeSnipPortable.vi | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Src/VCodeSnip.vi b/Src/VCodeSnip.vi index 0a826d5a3..60eb51dc0 100644 --- a/Src/VCodeSnip.vi +++ b/Src/VCodeSnip.vi @@ -26,7 +26,7 @@ Company Name=DelphiDabbler File Description=CodeSnip Database Viewer File Version=<#F1>.<#F2>.<#F3> build <#F4> Internal Name= -Legal Copyright=Copyright � P.D.Johnson, 2005-. +Legal Copyright=Copyright © P.D.Johnson, 2005-. Legal Trademark= Original File Name=CodeSnip.exe Private Build= diff --git a/Src/VCodeSnipPortable.vi b/Src/VCodeSnipPortable.vi index d5fdc997b..238d407c8 100644 --- a/Src/VCodeSnipPortable.vi +++ b/Src/VCodeSnipPortable.vi @@ -26,7 +26,7 @@ Company Name=DelphiDabbler File Description=CodeSnip Database Viewer (Portable Edition) File Version=<#F1>.<#F2>.<#F3> build <#F4> Internal Name= -Legal Copyright=Copyright � P.D.Johnson, 2005-. +Legal Copyright=Copyright © P.D.Johnson, 2005-. Legal Trademark= Original File Name=CodeSnip-p.exe Private Build= From 09ebe9328a625f7c2f6bae57603925e6b29dca19 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 4 Nov 2022 17:00:52 +0000 Subject: [PATCH 099/330] Bump version numbers to 2.20.2 build 266 --- Src/VCodeSnip.vi | 4 ++-- Src/VCodeSnipPortable.vi | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Src/VCodeSnip.vi b/Src/VCodeSnip.vi index 60eb51dc0..3ed5f7959 100644 --- a/Src/VCodeSnip.vi +++ b/Src/VCodeSnip.vi @@ -8,8 +8,8 @@ [Fixed File Info] -File Version #=4, 20, 1, 265 -Product Version #=4, 20, 1, 0 +File Version #=4, 20, 2, 266 +Product Version #=4, 20, 2, 0 File OS=4 File Type=1 File Sub-Type=0 diff --git a/Src/VCodeSnipPortable.vi b/Src/VCodeSnipPortable.vi index 238d407c8..ff5d8d1b6 100644 --- a/Src/VCodeSnipPortable.vi +++ b/Src/VCodeSnipPortable.vi @@ -8,8 +8,8 @@ [Fixed File Info] -File Version #=4, 20, 1, 265 -Product Version #=4, 20, 1, 0 +File Version #=4, 20, 2, 266 +Product Version #=4, 20, 2, 0 File OS=4 File Type=1 File Sub-Type=0 From 00297eac331d3263d106acfacea3fe9758b5b1cd Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 4 Nov 2022 17:18:31 +0000 Subject: [PATCH 100/330] Update CHANGELOG.md re changes in release v4.20.2 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f9ed8c24..2c58946c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,12 @@ This change log begins with the first ever pre-release version of _CodeSnip_. Re From v4.1.0 the version numbering has attempted to adhere to the principles of [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Release v4.20.2 of 04 November 2022 + +* Fixes bug where an exception was raised when selecting a main menu item with the cursor keys then pressing F1. [issue 54] +* Update operating system detection code to correctly detect Version 22H2 of Windows 10 & Windows 11 plus some other Windows releases on the Dev, Beta & Release channels. [issues 55, 61 & 62] +* Fix appearance of copyright symbol in version information. + ## Release v4.20.1 of 01 July 2022 * Operating system detection code was updated to (a) fix bugs and (b) detect some Dev, Beta and Release Preview builds of Windows 11 22H2. From 3f3a9bb029e348e025056cb288a803c36d9242ea Mon Sep 17 00:00:00 2001 From: Peter Johnson <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 9 Nov 2022 23:04:19 +0000 Subject: [PATCH 101/330] Update License.html * Change image attribution for CodeSnip images. * Strike out a broken link. * Change links that support it to use https instead of http. --- Docs/License.html | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/Docs/License.html b/Docs/License.html index 9964fa5df..d4b0f895c 100644 --- a/Docs/License.html +++ b/Docs/License.html @@ -340,12 +340,12 @@

    License.
    - The license requires that the images should be attributed. To do this + This 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 CodeSnip and provide a link to https://github.com/delphidabbler/codesnip. + href="https://delphidabbler.com/software/codesnip" + >https://delphidabbler.com/software/codesnip.
    These images include modifications and remixes of icons supplied under @@ -1614,7 +1614,7 @@

    Toolbar Icons is made available under the terms of the MIT License. See http://toolbaricons.sourceforge.net/ for more information.

    Copyright © 2010 Florian Haag

    @@ -1685,9 +1685,7 @@

    Led Icon Set v1.0: http://led24.de/iconset/ [link broken].

  • - 16x16-free-application-icons by Aha-Soft: https://www.aha-soft.com. + 16x16-free-application-icons by Aha-Soft: https://www.aha-soft.com [link broken].
  • @@ -1715,7 +1713,7 @@

  • Some program icons are based on Florian Haag's Toolbar Icons set at http://toolbaricons.sourceforge.net/.
  • From 2bed7c731cb5f28ef029541a69285e54a8972a19 Mon Sep 17 00:00:00 2001 From: Peter Johnson <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 9 Nov 2022 23:31:09 +0000 Subject: [PATCH 102/330] Update License.rtf Update copyright date to 2022. Fix typo. --- Src/Install/Assets/License.rtf | Bin 1189 -> 1192 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Src/Install/Assets/License.rtf b/Src/Install/Assets/License.rtf index 998dd2d2fa0ba0479cd2a88f475f28678eab7bbd..3795e032266c876568ad121ba7a2cd003548536c 100644 GIT binary patch delta 27 jcmZ3=xq@>88zZC9W_HGtOiY=1lP@!0WPQJXFE1AWcee>@ delta 24 gcmZ3%xs-DQ8zZCPW_HGtOw5USm6I Date: Sat, 3 Dec 2022 10:40:21 +0000 Subject: [PATCH 103/330] Update README.md Change Source Code section re addition of caboli branch and abandonment of belvedere. Some other corrections & clarifications --- README.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 5bfc29a5d..61d3680c7 100644 --- a/README.md +++ b/README.md @@ -22,13 +22,13 @@ CodeSnip requires Windows 2000 or later and Internet Explorer 6 or later, althou ## Installation -The standard edition of CodeSnip is installed and removed using a standard Windows installer. Administrator privileges are required for installation. +The standard edition of CodeSnip is installed and removed using a Windows installer. Administrator privileges are required for installation. The portable edition has no installer. Simply follow the instructions in the [read me file](https://raw.githubusercontent.com/delphidabbler/codesnip/master/Docs/ReadMe.txt) that is included in the download zip file. ## Support -The following support is available CodeSnip users: +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) * that discusses installation, configuration, updating and known issues. @@ -43,19 +43,17 @@ There's also plenty of info available on how to compile CodeSnip from source - s CodeSnip's source code is maintained in the [`delphidabbler/codesnip`](https://github.com/delphidabbler/codesnip) Git repository on GitHub†. -The [Git Flow](https://nvie.com/posts/a-successful-git-branching-model/) methodology has been adopted, with the exception of some branches that have been used in various attempts to start work on CodeSnip 5. +The [Git Flow](https://nvie.com/posts/a-successful-git-branching-model/) methodology has been adopted, with the exception of some experimental branches. -The following branches existed as of 2022/07/01: +The following branches existed as of 2022/12/03: -* `master`: Always reflects the state of the source code as of the latest release.‡ -* `develop`: Main development branch. The head of this branch contains the latest v4 development code. -* `belvedere`: The latest attempt to develop CodeSnip 5. See the [Belvedere Readme file](https://github.com/delphidabbler/codesnip/blob/belvedere/README.md) for a full explanation. -* `pagoda`: An abortive attempt at developing CodeSnip 5. Work on this branch has halted. It does not follow GitFlow methodology. ***Do not use this branch: it may be pruned.*** -* `pavilion`: Another attempt at working on CodeSnip 5. It branched off `pagoda` and work on it has halted. Again it does not follow GitFlow methodology. ***Do not use this branch: it may be pruned.*** - -New features and most bug fixes are worked on in `feature/xxxx` branches that are branched off `develop` locally. They are merged into `develop` as they are completed and the branches are deleted. - -Note that the default branch on GitHub is `master`, which contains the state of the project as of the latest release. If you want to see the current state of play with new developments switch to `develop`. +* [`master`](https://github.com/delphidabbler/codesnip/tree/master): Always reflects the state of the source code as of the latest release.‡ +* [`develop`](https://github.com/delphidabbler/codesnip/tree/develop): Main development branch. The head of this branch contains the latest v4 development code. Normal development of CodeSnip 4 takes place in `feature/xxx` branches off `develop`. +* [`caboli`](https://github.com/delphidabbler/codesnip/tree/caboli): Experimental branch where an attempt is being made to (a) modernise the UI and (b) get the code to work properly when compiled with Delphi 11. +* Abandoned branches: + * [`pagoda`](https://github.com/delphidabbler/codesnip/tree/pagoda): An abortive attempt at developing CodeSnip 5. + * [`pavilion`](https://github.com/delphidabbler/codesnip/tree/pavilion): Another attempt at working on CodeSnip 5 that branched off `pagoda`. + * [`belvedere`](https://github.com/delphidabbler/codesnip/tree/belvedere): A thiird, failed attempt to develop CodeSnip 5 as a ground up rewrite. Not related to `pagoda` & `pavilion`. > † 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/). @@ -65,7 +63,9 @@ Note that the default branch on GitHub is `master`, which contains the state of To contribute to CodeSnip 4 development please fork the repository on GitHub. Create a feature branch off the `develop` branch. Make your changes to your feature branch then submit a pull request via GitHub. -> **Do not create branches off `master`, always branch from `develop`.** +:warning: **Do not create branches off `master`, always branch from `develop`.** + +:no_entry: Contributions to experimental branches are not being excepted just now. #### Licensing of contributions From 3b99e86907a052e1032047e5406aa1d292889d37 Mon Sep 17 00:00:00 2001 From: Peter Johnson <5164283+delphidabbler@users.noreply.github.com> Date: Mon, 12 Dec 2022 04:30:45 +0000 Subject: [PATCH 104/330] Update copyright date range in help license --- Src/Help/HTML/license.htm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Src/Help/HTML/license.htm b/Src/Help/HTML/license.htm index 66c40cee7..50c1357c2 100644 --- a/Src/Help/HTML/license.htm +++ b/Src/Help/HTML/license.htm @@ -27,7 +27,7 @@

    Summary of End User License Agreement

    - DelphiDabbler CodeSnip is copyright © 2005-2020 by Peter D + DelphiDabbler CodeSnip is copyright © 2005-2022 by Peter D Johnson, Date: Sun, 11 Dec 2022 21:01:40 +0000 Subject: [PATCH 105/330] Add new REML character entities in UREMLDataIO unit Remove REML docs from comments: they are in a separate unit and fix other header comments. --- Src/UREMLDataIO.pas | 96 +++++++++++++++++---------------------------- 1 file changed, 37 insertions(+), 59 deletions(-) diff --git a/Src/UREMLDataIO.pas b/Src/UREMLDataIO.pas index 5f809c127..5381b6565 100644 --- a/Src/UREMLDataIO.pas +++ b/Src/UREMLDataIO.pas @@ -7,7 +7,7 @@ * * Implements classes that render and parse Routine Extra Markup Language (REML) * code. This markup is used to read and store active text objects as used by - * the Extra property of a TSnippet object. Also includes helper classes. + * some properties of a TSnippet object. Also includes helper classes. } @@ -139,59 +139,8 @@ implementation It comprises plain text with limited inline and block level formatting and hyperlink specified by HTML like tags. - Supported tags are as follows. Unless otherwise specified, no tags may have - any attributes: - - Inline: - xxxx - Hyperlink: must have an href attribute that - specifies the link destination as a valid URL. - URLs must not be URL encoded. No other attributes - may be specified. - .. - Renders enclosed text with strong emphasis. - .. - Renders enclosed text emphasised. - .. - Renders enclosed text as a programming variable. - .. - Renders enclosed text as a warning. - .. - Renders enclosed text as mono spaced. - - Block: -

    ..

    - Enclosed text is formatted as a paragraph. - .. - Enclosed text is formatted as a heading. - - Certain characters in plain text or in attribute values must be encoded as - HTML-like character entities. Attribute names must not contain any of these - characters. The characters that must be encoded are: - - Character Entity - > > - < < - " " - & & - � © - - No other entities are supported. Any other character can be encoded using its - unicode or ascii value. For example, the @ symbol (ascii 64) is encoded as - @ - - Example: - Hello -

    "Hello" to - you

    - - This example specifes a heading "Hello" followed by a single paragraph. In the - paragraph, "Hello" will be bold, "to" should be plain text and "you" should - hyperlink to "example.com". - - There are two versions of REML as follows: - v1 - supported tags: and . - - supported entities: >, <, ", &. - v2 - added tags: , , , , Returns a string containing Count copies of character Ch. +/// +/// If Count is zero then the empty string is returned. +function StrOfChar(const Ch: Char; const Count: Word): string; + +/// Returns a string of a given number of spaces. +/// Word [in] Required number of spaces. +/// string. Required number of spaces. +/// If Count is zero then an empty string is returned. +function StrOfSpaces(const Count: Word): string; implementation @@ -769,7 +779,6 @@ function StrWrap(const Str: UnicodeString; const MaxLen, Margin: Integer): Word: UnicodeString; // next word in input Str Line: UnicodeString; // current output line Words: TStringList; // list of words in input Str - I: Integer; // loops thru all words in input Str // ------------------------------------------------------------------------- /// Adds a line of text to output, offseting line by Margin spaces @@ -777,7 +786,7 @@ function StrWrap(const Str: UnicodeString; const MaxLen, Margin: Integer): begin if Result <> '' then // not first line: insert new line Result := Result + EOL; - Result := Result + StringOfChar(' ', Margin) + Line; + Result := Result + StrOfSpaces(Margin) + Line; end; // ------------------------------------------------------------------------- @@ -789,9 +798,8 @@ function StrWrap(const Str: UnicodeString; const MaxLen, Margin: Integer): Result := ''; Line := ''; // Loop for each word in Str - for I := 0 to Pred(Words.Count) do + for Word in Words do begin - Word := Words[I]; if Length(Line) + Length(Word) + 1 <= MaxLen then begin // Word fits on current line: add it @@ -904,5 +912,17 @@ function StrIsEmpty(const S: string; const IgnoreWhiteSpace: Boolean = False): Result := S = ''; end; +function StrOfChar(const Ch: Char; const Count: Word): string; +begin + if Count = 0 then + Exit(''); + Result := System.StringOfChar(Ch, Count); +end; + +function StrOfSpaces(const Count: Word): string; +begin + Result := StrOfChar(' ', Count); +end; + end. From c4ef947cfee1cfc42eabc8c7132a59b59b670430 Mon Sep 17 00:00:00 2001 From: delphidabbler Date: Sun, 19 Jul 2020 05:18:48 +0100 Subject: [PATCH 107/330] ActiveText.UMain.pas: Rename parameters --- Src/ActiveText.UMain.pas | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/Src/ActiveText.UMain.pas b/Src/ActiveText.UMain.pas index bb5e534a5..5a5ce1fcc 100644 --- a/Src/ActiveText.UMain.pas +++ b/Src/ActiveText.UMain.pas @@ -383,13 +383,14 @@ TActiveTextActionElem = class(TInterfacedObject, fAttrs: IActiveTextAttrs; public /// Object constructor. Creates an action element. - /// TActiveTextElemKind [in] Required kind of element. + /// TActiveTextElemKind [in] Required kind of element. /// - /// IActiveTextAttrs [in] Element's attributes. - /// TActiveTextElemState [in] State of element: opening - /// or closing. - constructor Create(const Kind: TActiveTextActionElemKind; - Attrs: IActiveTextAttrs; const State: TActiveTextElemState); + /// IActiveTextAttrs [in] Element's attributes. + /// + /// TActiveTextElemState [in] State of element: + /// opening or closing. + constructor Create(const AKind: TActiveTextActionElemKind; + AAttrs: IActiveTextAttrs; const AState: TActiveTextElemState); /// Assigns properties of another object to this object. /// IInterface [in] Object whose properties are to be /// assigned. Src must support IActiveTextActionElem. @@ -674,13 +675,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; From 5ec55ba6ee5cc6624a483c0b52bb2d92f9abb460 Mon Sep 17 00:00:00 2001 From: delphidabbler Date: Fri, 24 Jul 2020 05:26:06 +0100 Subject: [PATCH 108/330] Add new TCSSBuilder.EnsureSelector method This added because attempting to add an existing selector raises an exception and reading a non-existant selector returns a null pointer. This makes it hard to write bug-free code without checking for the existence of a selector. It's been annoying me for years! --- Src/UCSSBuilder.pas | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Src/UCSSBuilder.pas b/Src/UCSSBuilder.pas index fbc1d307f..662e44dd6 100644 --- a/Src/UCSSBuilder.pas +++ b/Src/UCSSBuilder.pas @@ -83,6 +83,12 @@ TCSSBuilder = class(TObject) @param Selector [in] Name of new selector. @return New empty selector object. } + function EnsureSelector(const Selector: string): TCSSSelector; + {Returns selector object with given name or adds a new selector with the + given name if no such selector exists. + @parm Selector [in] Name of selector. + @return Reference to new or pre-existing selector. + } procedure Clear; {Clears all selectors from style sheet and frees selector objects. } @@ -204,6 +210,18 @@ destructor TCSSBuilder.Destroy; inherited; end; +function TCSSBuilder.EnsureSelector(const Selector: string): TCSSSelector; + {Returns selector object with given name or adds a new selector with the given + name if no such selector exists. + @parm Selector [in] Name of selector. + @return Reference to new or pre-existing selector. + } +begin + Result := GetSelector(Selector); + if not Assigned(Result) then + Result := AddSelector(Selector); +end; + function TCSSBuilder.GetSelector(const Selector: string): TCSSSelector; {Read access method for Selectors property. Returns selector object with given name. From 473739d1f29d5955a910ad2ac8492e1284353646 Mon Sep 17 00:00:00 2001 From: delphidabbler Date: Sun, 19 Jul 2020 04:53:44 +0100 Subject: [PATCH 109/330] Add list support to REML New tags supported:
      ,
        &
      • . * Major support to REML parser in UREMLDataIO: * Added support for
          ,
            and
          1. block level items. * Moved some code to active text units: it logically belonged this. * This change required some major changes to the parser: it previously assumed that block level tags could not be nested. * Comments added to indicate REML version support for entities. * Add support for lists to active text: * Added ordered & unordered lists and list items. * Update renderers re recent method name changes in active text code. * Also extracted some info from REML code that belongs in active text. * Revised REML writer to indent blocks. * Refactor code to use TActiveTextElemCaps for element information. Determination of capabilities was moved from UREMLDataIO unit into UActiveText.UMain where it logically belongs. * Add CSS support for lists: * In UCSSUtils - Added methods to generate CSS "list-style-position" and "list-style-type" properties. * Update main display and Active Text preview dialogue box. * Set list CSS styling for display frame. * Update USnippetExtraHelper: * Revise code that adds paragraphs around text not contained in block. Revision was to get the code to work with blocks nested more than one level deep. * Changes re renamed TActiveTextElemCaps method. --- Src/ActiveText.UHTMLRenderer.pas | 6 +- Src/ActiveText.UMain.pas | 243 +++++++++++++++++++++++++++---- Src/ActiveText.URTFRenderer.pas | 2 +- Src/ActiveText.UTextRenderer.pas | 2 +- Src/FmActiveTextPreviewDlg.pas | 40 ++++- Src/FrDetailView.pas | 32 ++++ Src/Res/CSS/detail.css | 35 +++++ Src/UCSSUtils.pas | 61 ++++++++ Src/UREMLDataIO.pas | 243 +++++++++++++++++-------------- Src/USnippetExtraHelper.pas | 99 ++++++------- 10 files changed, 565 insertions(+), 198 deletions(-) diff --git a/Src/ActiveText.UHTMLRenderer.pas b/Src/ActiveText.UHTMLRenderer.pas index 81a844a91..daa25dc0d 100644 --- a/Src/ActiveText.UHTMLRenderer.pas +++ b/Src/ActiveText.UHTMLRenderer.pas @@ -134,7 +134,7 @@ procedure TActiveTextHTML.InitialiseTagInfoMap; ElemKind: TActiveTextActionElemKind; const Tags: array[TActiveTextActionElemKind] of string = ( - 'a', 'strong', 'em', 'var', 'p', 'span', 'h2', 'code' + 'a', 'strong', 'em', 'var', 'p', 'span', 'h2', 'code', 'ul', 'ol', 'li' ); begin NullAttrs := function(Elem: IActiveTextActionElem): IHTMLAttributes @@ -194,7 +194,7 @@ function TActiveTextHTML.Render(ActiveText: IActiveText): string; 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); @@ -244,7 +244,7 @@ procedure TActiveTextHTML.RenderTextElem(Elem: IActiveTextTextElem); constructor TActiveTextHTML.TCSSStyles.Create; const DefaultClasses: array[TActiveTextActionElemKind] of string = ( - 'external-link', '', '', '', '', 'warning', '', '' + 'external-link', '', '', '', '', 'warning', '', '', '', '', '' ); var ElemKind: TActiveTextActionElemKind; diff --git a/Src/ActiveText.UMain.pas b/Src/ActiveText.UMain.pas index 5a5ce1fcc..8d6e54602 100644 --- a/Src/ActiveText.UMain.pas +++ b/Src/ActiveText.UMain.pas @@ -120,14 +120,17 @@ TActiveTextAttrNames = record type /// Supported types of active text action elements. 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 level) + 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 level) + ekOrderedList, // container for ordered list (block level) + ekListItem // list item (block level) ); type @@ -157,12 +160,6 @@ TActiveTextAttrNames = record function GetAttrs: IActiveTextAttrs; /// Object describing element's attributes. property Attrs: IActiveTextAttrs read GetAttrs; - /// Returns value that indicates whether element is an inline or - /// block element. - function GetDisplayStyle: TActiveTextDisplayStyle; - /// Indicates whether element displays inline or as a block. - /// - property DisplayStyle: TActiveTextDisplayStyle read GetDisplayStyle; end; type @@ -273,6 +270,162 @@ TActiveTextFactory = class(TNoConstructObject) IActiveTextAttrs; overload; end; +type + /// Provides information about the capabilities of each supported + /// active text element. + TActiveTextElemCaps = record + strict private + type + /// Fields used to define an active text element's capabilities. + /// + TCaps = record + public + var + /// Determines how element is to be displayed. + DisplayStyle: TActiveTextDisplayStyle; + /// Set of elements that may not occur inside the element. + /// + Exclusions: TActiveTextActionElemKinds; + /// Set of elements that are permitted as parents of the + /// element. + /// An empty set is taken to mean any element is permitted. + /// + RequiredParents: TActiveTextActionElemKinds; + /// Specifies whether plain text can be contained within the + /// element. + PermitsText: Boolean; + end; + const + /// Set of block level elements. + BlockElems = [ + ekPara, ekHeading, ekUnorderedList, ekOrderedList, ekListItem + ]; + /// Set of inline elements. + InlineElems = [ + ekLink, ekStrong, ekEm, ekVar, ekWarning, ekMono + ]; + /// Set of all elements. + AllElems = BlockElems + InlineElems; + /// Map of all elements to their capabilities. + Map: array[TActiveTextActionElemKind] of TCaps = + ( + ( + // ekLink + // may contain any inline elements but no block elements + DisplayStyle: dsInline; + Exclusions: BlockElems; + RequiredParents: []; + PermitsText: True; + ), + ( + // ekStrong + // may contain any inline elements but no block elements + DisplayStyle: dsInline; + Exclusions: BlockElems; + RequiredParents: []; + PermitsText: True; + ), + ( + // ekEm + // may contain any inline elements but no block elements + DisplayStyle: dsInline; + Exclusions: BlockElems; + RequiredParents: []; + PermitsText: True; + ), + ( + // ekVar + // may contain any inline elements but no block elements + DisplayStyle: dsInline; + Exclusions: BlockElems; + RequiredParents: []; + PermitsText: True; + ), + ( + // ekPara + // may contain any inline elements but no block elements + DisplayStyle: dsBlock; + Exclusions: BlockElems; + RequiredParents: []; + PermitsText: True; + ), + ( + // ekWarning + // may contain any inline elements but no block elements + DisplayStyle: dsInline; + Exclusions: BlockElems; + RequiredParents: []; + PermitsText: True; + ), + ( + // ekHeading + // may contain any inline elements but no block elements + DisplayStyle: dsBlock; + Exclusions: BlockElems; + RequiredParents: []; + PermitsText: True; + ), + ( + // ekMono + // may contain any inline elements but no block elements + DisplayStyle: dsInline; + Exclusions: BlockElems; + RequiredParents: []; + PermitsText: True; + ), + ( + // ekUnorderedList + // may contain only list item elements + DisplayStyle: dsBlock; + Exclusions: AllElems - [ekListItem]; + RequiredParents: []; + PermitsText: False + ), + ( + // ekOrderedList + // may contain only list item elements + DisplayStyle: dsBlock; + Exclusions: AllElems - [ekListItem]; + RequiredParents: []; + PermitsText: False; + ), + ( + // ekListItem + // may contain any inline or block elements except another list + // item + DisplayStyle: dsBlock; + Exclusions: [ekListItem]; + RequiredParents: [ekOrderedList, ekUnorderedList]; + PermitsText: True; + ) + ); + public + /// Returns the display type of the given element. + class function DisplayStyleOf(const Elem: TActiveTextActionElemKind): + TActiveTextDisplayStyle; static; + /// Checks whether the given element can contain text. + class function CanContainText(const Elem: TActiveTextActionElemKind): + Boolean; static; + /// Checks whether the given Parent element can contain the given + /// Child element. + class function CanContainElem( + const Parent, Child: TActiveTextActionElemKind): Boolean; static; + /// Checks whether the given Parent element is required as a + /// parent of the given Child element. + class function IsRequiredParent( + const Parent, Child: TActiveTextActionElemKind): Boolean; static; + /// Checks whether the given element is permitted in the root of + /// an active text document, i.e. outside any other block level element. + /// + class function IsElemPermittedInRoot(const Elem: TActiveTextActionElemKind): + Boolean; static; + /// Checks whether the given child element is excluded from being + /// a child of the given parent element. + class function IsExcludedElem( + const Parent, Child: TActiveTextActionElemKind): Boolean; static; + + end; + implementation @@ -354,7 +507,7 @@ TActiveTextTextElem = class(TInterfacedObject, fText: string; public /// Object constructor. Records given element text and sets - /// required kind for a text element. + /// required Elem for a text element.

    and . - - added entity: ©. - - The implementation of active text's link element changed over time. At first - it supported only the http:// protocol for URLs. This limited REML v1 tags - to using just that protocol. CodeSnip v3.0.1 added support to active text for - the file:// protocol. From CodeSnip v4.0 active text was extended to support - the https:// protocol. + Valid REML tags and character entities are documented in the file reml.html in + the Docs/Design directory. } @@ -841,13 +790,42 @@ class function TREMLEntities.CharToMnemonicEntity(const Ch: Char): string; {Class constructor. Creates map of mnemonic entities to equivalent characters. } begin - SetLength(fEntityMap, 5); - // Record all supported character entities - fEntityMap[0] := TREMLEntity.Create('amp', '&'); + SetLength(fEntityMap, 34); + // Supported character entities. All are optional unless otherwise stated + fEntityMap[0] := TREMLEntity.Create('amp', '&'); // required in REML fEntityMap[1] := TREMLEntity.Create('quot', DOUBLEQUOTE); - fEntityMap[2] := TREMLEntity.Create('gt', '>'); - fEntityMap[3] := TREMLEntity.Create('lt', '<'); + fEntityMap[2] := TREMLEntity.Create('gt', '>'); + fEntityMap[3] := TREMLEntity.Create('lt', '<'); // required in REML fEntityMap[4] := TREMLEntity.Create('copy', '©'); + fEntityMap[5] := TREMLEntity.Create('times', '×'); + fEntityMap[6] := TREMLEntity.Create('divide', '÷'); + fEntityMap[7] := TREMLEntity.Create('div', '÷'); + fEntityMap[8] := TREMLEntity.Create('plusmn', '±'); + fEntityMap[9] := TREMLEntity.Create('ne', '≠'); + fEntityMap[10] := TREMLEntity.Create('neq', '≠'); + fEntityMap[11] := TREMLEntity.Create('sum', '∑'); + fEntityMap[12] := TREMLEntity.Create('infin', '∞'); + fEntityMap[13] := TREMLEntity.Create('pound', '£'); + fEntityMap[14] := TREMLEntity.Create('curren', '¤'); + fEntityMap[15] := TREMLEntity.Create('yen', 'Â¥'); + fEntityMap[16] := TREMLEntity.Create('euro', '€'); + fEntityMap[17] := TREMLEntity.Create('dagger', '†'); + fEntityMap[18] := TREMLEntity.Create('ddagger', '‡'); + fEntityMap[19] := TREMLEntity.Create('Dagger', '‡'); + fEntityMap[20] := TREMLEntity.Create('hellip', '…'); + fEntityMap[21] := TREMLEntity.Create('para', '¶'); + fEntityMap[22] := TREMLEntity.Create('sect', '§'); + fEntityMap[23] := TREMLEntity.Create('reg', '®'); + fEntityMap[24] := TREMLEntity.Create('frac14', '¼'); + fEntityMap[25] := TREMLEntity.Create('frac12', '½'); + fEntityMap[26] := TREMLEntity.Create('half', '½'); + fEntityMap[27] := TREMLEntity.Create('frac34', '¾'); + fEntityMap[28] := TREMLEntity.Create('micro', 'µ'); + fEntityMap[29] := TREMLEntity.Create('deg', '°'); + fEntityMap[30] := TREMLEntity.Create('cent', '¢'); + fEntityMap[31] := TREMLEntity.Create('laquo', '«'); + fEntityMap[32] := TREMLEntity.Create('raquo', '»'); + fEntityMap[33] := TREMLEntity.Create('iquest', '¿'); end; class destructor TREMLEntities.Destroy; From 0ff201464e15100a7b253d1b5ef75d0433dbcf1f Mon Sep 17 00:00:00 2001 From: delphidabbler Date: Thu, 23 Jul 2020 21:01:56 +0100 Subject: [PATCH 106/330] Update UStrUtils unit & update other unit re changes * Add new StrOfChar and StrOfSpaces functions * Refactor to use StrOfSpaces instead of StringOfChar routine. * Update indexed for loop to for .. in loop FmSWAGImportDlg: Replace StringOfChar routine call with StrOfChar --- Src/FmSWAGImportDlg.pas | 2 +- Src/UStrUtils.pas | 28 ++++++++++++++++++++++++---- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/Src/FmSWAGImportDlg.pas b/Src/FmSWAGImportDlg.pas index efeb0e8a9..421840eb8 100644 --- a/Src/FmSWAGImportDlg.pas +++ b/Src/FmSWAGImportDlg.pas @@ -812,7 +812,7 @@ procedure TSWAGImportDlg.PreviewSelectedPacket; FullPacket.FileName, FullPacket.Title, FullPacket.Author, - StringOfChar('-', 80), + StrOfChar('-', 80), StrWindowsLineBreaks(FullPacket.SourceCode) ] ); diff --git a/Src/UStrUtils.pas b/Src/UStrUtils.pas index 972302609..0d4eb057d 100644 --- a/Src/UStrUtils.pas +++ b/Src/UStrUtils.pas @@ -269,6 +269,16 @@ procedure StrArrayToStrList(const SA: array of string; const SL: TStrings); function StrIsEmpty(const S: string; const IgnoreWhiteSpace: Boolean = False): Boolean; +///

  • constructor Create(const Text: string); /// Assigns properties of another object to this object. /// IInterface [in] Object whose properties are to be @@ -375,7 +528,7 @@ TActiveTextActionElem = class(TInterfacedObject, IActiveTextElem, IActiveTextActionElem, IAssignable, IClonable ) strict private - /// Kind of element encapsulated by this object. + /// Elem of element encapsulated by this object. fKind: TActiveTextActionElemKind; /// State of element: opening or closing. fState: TActiveTextElemState; @@ -383,7 +536,7 @@ TActiveTextActionElem = class(TInterfacedObject, fAttrs: IActiveTextAttrs; public /// Object constructor. Creates an action element. - /// TActiveTextElemKind [in] Required kind of element. + /// TActiveTextElemKind [in] Required Elem of element. /// /// IActiveTextAttrs [in] Element's attributes. /// @@ -402,7 +555,7 @@ TActiveTextActionElem = class(TInterfacedObject, /// Returns a cloned instance of this object. /// Method of IClonable. function Clone: IInterface; - /// Returns kind of action represented by this element. + /// Returns Elem of action represented by this element. /// Method of IActiveTextActionElem. function GetKind: TActiveTextActionElemKind; /// Returns state of element. @@ -411,10 +564,6 @@ TActiveTextActionElem = class(TInterfacedObject, /// Returns object describing element's attributes. /// Method of IActiveTextActionElem. function GetAttrs: IActiveTextAttrs; - /// Returns value that indicates whether element is an inline or - /// block element. - /// Method of IActiveTextActionElem. - function GetDisplayStyle: TActiveTextDisplayStyle; end; type @@ -618,7 +767,7 @@ 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 @@ -689,14 +838,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; @@ -756,5 +897,45 @@ function TActiveTextAttrs.GetEnumerator: TEnumerator>; Result := fMap.GetEnumerator; end; +{ TActiveTextElemCapsMap } + +class function TActiveTextElemCaps.CanContainElem(const Parent, + Child: TActiveTextActionElemKind): Boolean; +begin + Result := not (Child in Map[Parent].Exclusions); +end; + +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.IsElemPermittedInRoot( + const Elem: TActiveTextActionElemKind): Boolean; +begin + Result := Map[Elem].RequiredParents = []; +end; + +class function TActiveTextElemCaps.IsExcludedElem(const Parent, + Child: TActiveTextActionElemKind): Boolean; +begin + Result := Child in Map[Parent].Exclusions; +end; + +class function TActiveTextElemCaps.IsRequiredParent( + const Parent, Child: TActiveTextActionElemKind): Boolean; +begin + if Map[Child].RequiredParents = [] then + Exit(True); + Result := Parent in Map[Child].RequiredParents; +end; + end. diff --git a/Src/ActiveText.URTFRenderer.pas b/Src/ActiveText.URTFRenderer.pas index d11fc5ade..dc0267b79 100644 --- a/Src/ActiveText.URTFRenderer.pas +++ b/Src/ActiveText.URTFRenderer.pas @@ -196,7 +196,7 @@ procedure TActiveTextRTF.Render(ActiveText: IActiveText; 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); diff --git a/Src/ActiveText.UTextRenderer.pas b/Src/ActiveText.UTextRenderer.pas index 7b844fe43..bd658666e 100644 --- a/Src/ActiveText.UTextRenderer.pas +++ b/Src/ActiveText.UTextRenderer.pas @@ -97,7 +97,7 @@ function TActiveTextTextRenderer.Render(ActiveText: IActiveText): string; 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); diff --git a/Src/FmActiveTextPreviewDlg.pas b/Src/FmActiveTextPreviewDlg.pas index 1f415c2c0..e9bd1071f 100644 --- a/Src/FmActiveTextPreviewDlg.pas +++ b/Src/FmActiveTextPreviewDlg.pas @@ -220,7 +220,7 @@ procedure TActiveTextPreviewDlg.UpdateCSS(Sender: TObject; try TFontHelper.SetContentFont(ContentFont); // Set rendered REML container - with CSSBuilder.AddSelector('#content') do + with CSSBuilder.EnsureSelector('#content') do begin AddProperty(TCSS.FontProps(ContentFont)); AddProperty(TCSS.BackgroundColorProp(clWindow)); @@ -235,22 +235,54 @@ procedure TActiveTextPreviewDlg.UpdateCSS(Sender: TObject; else AddProperty(TCSS.HeightProp(MaxHTMLHeight)); end; - with CSSBuilder.AddSelector('.active-text h2') do + with CSSBuilder.EnsureSelector('.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 + with CSSBuilder.EnsureSelector('.active-text p') do AddProperty(TCSS.MarginProp(4, 0, 0, 0)); // Show or hide text about links depending on if links are present - with CSSBuilder.AddSelector('#linktext') do + with CSSBuilder.EnsureSelector('#linktext') do begin if ContainsLinks then AddProperty(TCSS.DisplayProp(cdsInline)) else AddProperty(TCSS.DisplayProp(cdsNone)); end; + // Set up lists + with CSSBuilder.EnsureSelector('.active-text ul') do + begin + 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)); + end; + with CSSBuilder.EnsureSelector('.active-text ol') do + begin + 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)); + end; + with CSSBuilder.EnsureSelector('.active-text li') do + begin + AddProperty(TCSS.PaddingProp(cssAll, 0)); + AddProperty(TCSS.MarginProp(cssAll, 0)); + end; + with CSSBuilder.EnsureSelector('.active-text li ol') do + AddProperty(TCSS.MarginProp(cssTop, 0)); + with CSSBuilder.EnsureSelector('.active-text li ul') do + AddProperty(TCSS.MarginProp(cssTop, 0)); + with CSSBuilder.EnsureSelector('.active-text ul li') do + AddProperty(TCSS.PaddingProp(cssLeft, 8)); + with CSSBuilder.EnsureSelector('.active-text ul li ol li') do + AddProperty(TCSS.PaddingProp(cssLeft, 0)); finally ContentFont.Free; end; diff --git a/Src/FrDetailView.pas b/Src/FrDetailView.pas index b6feecc76..7c21d29ba 100644 --- a/Src/FrDetailView.pas +++ b/Src/FrDetailView.pas @@ -265,6 +265,38 @@ procedure TDetailViewFrame.BuildCSS(const CSSBuilder: TCSSBuilder); AddProperty(TCSS.BackgroundColorProp(clCompTblHeadBg)); AddProperty(TCSS.FontWeightProp(cfwNormal)); end; + // Set active text list classes + with CSSBuilder.EnsureSelector('.active-text ul') do + begin + 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)); + end; + with CSSBuilder.EnsureSelector('.active-text ol') do + begin + 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)); + end; + with CSSBuilder.EnsureSelector('.active-text li') do + begin + AddProperty(TCSS.PaddingProp(cssAll, 0)); + AddProperty(TCSS.MarginProp(cssAll, 0)); + end; + with CSSBuilder.EnsureSelector('.active-text li ol') do + AddProperty(TCSS.MarginProp(cssTop, 0)); + with CSSBuilder.EnsureSelector('.active-text li ul') do + AddProperty(TCSS.MarginProp(cssTop, 0)); + with CSSBuilder.EnsureSelector('.active-text ul li') do + AddProperty(TCSS.PaddingProp(cssLeft, 8)); + with CSSBuilder.EnsureSelector('.active-text ul li ol li') do + AddProperty(TCSS.PaddingProp(cssLeft, 0)); finally ContentFont.Free; MonoFont.Free; diff --git a/Src/Res/CSS/detail.css b/Src/Res/CSS/detail.css index db791bb02..bea2dc732 100644 --- a/Src/Res/CSS/detail.css +++ b/Src/Res/CSS/detail.css @@ -57,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; diff --git a/Src/UCSSUtils.pas b/Src/UCSSUtils.pas index bb2e83a35..53d6bb4f0 100644 --- a/Src/UCSSUtils.pas +++ b/Src/UCSSUtils.pas @@ -155,6 +155,32 @@ interface codY // overflow in y direction only: "overflow-y" property ); +type + /// Enumeration of different list markers. + /// This is not a complete list. Additional values are defined in + /// CSS. + TCSSListStyleType = ( + clstNone, // no marker + clstInitial, // default marker + clstInherit, // inherit from parent + clstDisc, // filled circle + clstCircle, // un-filled circle + clstSquare, // filled square + clstDecimal, // decimal number (1, 2, 3...) + clstDecimal0, // decimal number padding with leading zeros (01, 02, 03...) + clstLowerRoman, // lower case roman numeral (i, ii, iii, iv...) + clstUpperRoman, // upper case roman numeral (I, II, III, IV...) + clstLowerAlpha, // lower case letter (a, b, c, d...) + clstUpperAlpha // upper case letter (A, B, C, D...) + ); + +type + /// Enumeration of possible list item markers. + TCSSListStylePosition = ( + clspOutside, // list marker outside the list item + clspInside // list marker inside the list item + ); + type /// /// Container for static methods that return CSS properties as text. @@ -429,6 +455,20 @@ TCSS = record /// Only the percentage version of line height is supported. /// class function LineHeightProp(const Percentage: UInt16): string; static; + + /// Creates a CSS "list-style-type" property. + /// TCSSListStyleType [in] Required property value. + /// + /// string. Required CSS property. + class function ListStyleTypeProp(const Value: TCSSListStyleType): string; + static; + + /// Creates a CSS "list-style-position" property. + /// TCSSListStylePosition [in] Required property value. + /// + /// string. Required CSS property. + class function ListStylePositionProp(const Value: TCSSListStylePosition): + string; static; end; @@ -640,6 +680,27 @@ class function TCSS.LineHeightProp(const Percentage: UInt16): string; Result := 'line-height: ' + IntToStr(Percentage) + '%'; end; +class function TCSS.ListStylePositionProp( + const Value: TCSSListStylePosition): string; +const + Positions: array[TCSSListStylePosition] of string = ('outside', 'inside'); +begin + Result := 'list-style-position: ' + Positions[Value] + ';'; +end; + +class function TCSS.ListStyleTypeProp(const Value: TCSSListStyleType): string; +const + Types: array[TCSSListStyleType] of string = ( + 'none', 'initial', 'inherit', + 'disc', 'circle', 'square', + 'decimal', 'decimal-leading-zero', + 'lower-roman', 'upper-roman', + 'lower-alpha', 'upper-alpha' + ); +begin + Result := 'list-style-type: ' + Types[Value] + ';'; +end; + class function TCSS.MarginProp(const Margin: array of Integer): string; begin Assert(Length(Margin) in [1,2,4], diff --git a/Src/UREMLDataIO.pas b/Src/UREMLDataIO.pas index 5381b6565..acd5bc135 100644 --- a/Src/UREMLDataIO.pas +++ b/Src/UREMLDataIO.pas @@ -34,24 +34,11 @@ interface } TREMLReader = class(TInterfacedObject, IActiveTextParser) strict private - type - TOpenTagTracker = class(TObject) - strict private - var - fTagState: array[TActiveTextActionElemKind] of Cardinal; - public - constructor Create; - destructor Destroy; override; - procedure Clear; - procedure OpenTag(Tag: TActiveTextActionElemKind); - procedure CloseTag(Tag: TActiveTextActionElemKind); - function TagsOpen(Tags: TActiveTextActionElemKinds): Boolean; - end; - strict private - fLexer: TTaggedTextLexer; // Analysis REML markup - fOpenTagTracker: TOpenTagTracker; + fLexer: TTaggedTextLexer; // Analyses REML markup // Stack of tag params for use in closing tags fParamStack: TStack; + // Stack of block level tags + fBlockTagStack: TStack; function TagInfo(const TagIdx: Integer; out TagName: string; out TagCode: Word; out IsContainer: Boolean): Boolean; {Callback that provides lexer with information about supported tags. Lexer @@ -96,6 +83,11 @@ TOpenTagTracker = class(TObject) } TREMLWriter = class(TNoPublicConstructObject) strict private + const + IndentMult = 2; // Number of spaces to indent for each block elem level + var + fLevel: Integer; // Indent level based on block nesting level + fIsStartOfTextLine: Boolean; // flag true if we're at start of a line function TextToREMLText(const Text: string): string; {Converts plain text to REML compatible text by replacing illegal characters with related character entities. @@ -148,7 +140,7 @@ implementation // Delphi SysUtils, // Project - UConsts, UExceptions, UStrUtils; + UConsts, UExceptions, UIStringList, UStrUtils; type @@ -158,9 +150,6 @@ implementation Class that provides information about REML tags. } TREMLTags = class(TNoConstructObject) - strict private - const - BlockTags = [ekPara, ekHeading]; strict private type { @@ -170,12 +159,9 @@ TREMLTags = class(TNoConstructObject) TREMLTag = record public Id: TActiveTextActionElemKind; // active text element kind - // ids of tags that can't nest inside this tag - Exclusions: TActiveTextActionElemKinds; TagName: string; // corresponding REML tag name ParamName: string; // name of any REML parameter constructor Create(const AId: TActiveTextActionElemKind; - const AExclusions: TActiveTextActionElemKinds; const ATagName: string; const AParamName: string = ''); {Record contructor. Initialises fields. @@ -206,8 +192,6 @@ TREMLTag = record @param Idx [in] Zero based index of required tag name. @return Required tag name. } - class function GetExclusions(Idx: Integer): TActiveTextActionElemKinds; - static; public class constructor Create; {Class constructor. Sets up map of REML tags. @@ -236,8 +220,6 @@ TREMLTag = record {List of tag ids} class property Names[Idx: Integer]: string read GetName; {List of tag names} - class property Exclusions[Idx: Integer]: TActiveTextActionElemKinds - read GetExclusions; end; { @@ -313,14 +295,14 @@ constructor TREMLReader.Create; inherited Create; fLexer := TTaggedTextLexer.Create(TagInfo, EntityInfo); fParamStack := TStack.Create; - fOpenTagTracker := TOpenTagTracker.Create; + fBlockTagStack := TStack.Create; end; destructor TREMLReader.Destroy; {Class destructor. Finalises object. } begin - fOpenTagTracker.Free; + fBlockTagStack.Free; FreeAndNil(fParamStack); FreeAndNil(fLexer); inherited; @@ -356,13 +338,40 @@ procedure TREMLReader.Parse(const Markup: string; ParamValue: string; // value of a parameter TagId: TActiveTextActionElemKind; // id of a tag Attr: TActiveTextAttr; // attributes of tag + + function IsTextPermittedInParentBlock: Boolean; + begin + if fBlockTagStack.Count = 0 then + Exit(True); + Result := TActiveTextElemCaps.CanContainText(fBlockTagStack.Peek); + end; + + function IsElemPermittedParentBlock(const Elem: TActiveTextActionElemKind): + Boolean; + begin + if fBlockTagStack.Count = 0 then + Exit(TActiveTextElemCaps.IsElemPermittedInRoot(Elem)); + Result := TActiveTextElemCaps.IsRequiredParent(fBlockTagStack.Peek, Elem); + end; + + function IsElemExcluded(const Elem: TActiveTextActionElemKind): + Boolean; + begin + if fBlockTagStack.Count = 0 then + Exit(False); + Result := TActiveTextElemCaps.IsExcludedElem(fBlockTagStack.Peek, Elem); + end; + resourcestring // Error message sErrMissingParam = 'Expected a "%0:s" parameter value in tag "%1:s"'; sErrNesting = 'Illegal nesting of "%0:s" tag'; + sBadParentBlock = 'Invalid parent block for tag %0:s'; + sNoTextPermitted = 'Text is not permitted in enclosing block'; + sMismatchedCloser = 'Mismatching closing block tag %0:s'; begin Assert(Assigned(ActiveText), ClassName + '.Parse: ActiveText is nil'); - fOpenTagTracker.Clear; + fBlockTagStack.Clear; try // Nothing to do if there is no markup if Markup = '' then @@ -373,25 +382,45 @@ procedure TREMLReader.Parse(const Markup: string; while fLexer.NextItem <> ttsEOF do begin case fLexer.Kind of + ttsText: begin - if fLexer.PlainText <> '' then - // Plain text: add text element (lexer will have replaced character - // entities with actual characters + if IsTextPermittedInParentBlock then + begin + // Plain text is allowed in parent block: add it ActiveText.AddElem( TActiveTextFactory.CreateTextElem(fLexer.PlainText) ); + end + else + begin + // Plain text not allowed in parent block: raise exception UNLESS + // text is only white space or empty, in which case we simply ignore + // the text. This is because white space will often occur after + // end tag of enclosed blocks + if not StrIsEmpty(fLexer.PlainText, True) then + raise EActiveTextParserError.Create(sNoTextPermitted); + end end; + ttsCompoundStartTag: begin // Start of an action element // Get tag id and any parameter TagId := TActiveTextActionElemKind(fLexer.TagCode); - if fOpenTagTracker.TagsOpen(TREMLTags.Exclusions[fLexer.TagCode]) then + + // Validate tag id + if IsElemExcluded(TagId) then raise EActiveTextParserError.CreateFmt( sErrNesting, [fLexer.TagName] ); - fOpenTagTracker.OpenTag(TagId); + if not IsElemPermittedParentBlock(TagID) then + raise EActiveTextParserError.CreateFmt( + sBadParentBlock, [fLexer.TagName] + ); + + if TActiveTextElemCaps.DisplayStyleOf(TagId) = dsBlock then + fBlockTagStack.Push(TagId); TREMLTags.LookupParamName(TagId, ParamName); if ParamName <> '' then begin @@ -419,12 +448,24 @@ procedure TREMLReader.Parse(const Markup: string; ); end; end; + ttsCompoundEndTag: begin // End of an action element - // Get tag id and note if tag should have a parameter + // Get elem id TagId := TActiveTextActionElemKind(fLexer.TagCode); - fOpenTagTracker.CloseTag(TagId); + + // Validate elem + if TActiveTextElemCaps.DisplayStyleOf(TagId) = dsBlock then + begin + if fBlockTagStack.Peek <> TagId then + raise EActiveTextParserError.CreateFmt( + sMismatchedCloser, [fLexer.TagName] + ); + fBlockTagStack.Pop; + end; + + // Process params TREMLTags.LookupParamName(TagId, ParamName); if ParamName <> '' then begin @@ -449,9 +490,12 @@ procedure TREMLReader.Parse(const Markup: string; ); end; end; + end; end; + except + // Handle exceptions: convert expected exceptions to EActiveTextParserError on E: ETaggedTextLexer do raise EActiveTextParserError.Create(E); @@ -481,51 +525,6 @@ function TREMLReader.TagInfo(const TagIdx: Integer; out TagName: string; end; end; -{ TREMLReader.TOpenTagTracker } - -procedure TREMLReader.TOpenTagTracker.Clear; -var - TagId: TActiveTextActionElemKind; -begin - for TagId := Low(TActiveTextActionElemKind) - to High(TActiveTextActionElemKind) do - fTagState[TagId] := 0; -end; - -procedure TREMLReader.TOpenTagTracker.CloseTag(Tag: TActiveTextActionElemKind); -begin - if fTagState[Tag] > 0 then - Dec(fTagState[Tag]); -end; - -constructor TREMLReader.TOpenTagTracker.Create; -begin - inherited Create; - Clear; -end; - -destructor TREMLReader.TOpenTagTracker.Destroy; -begin - - inherited; -end; - -procedure TREMLReader.TOpenTagTracker.OpenTag(Tag: TActiveTextActionElemKind); -begin - Inc(fTagState[Tag]); -end; - -function TREMLReader.TOpenTagTracker.TagsOpen( - Tags: TActiveTextActionElemKinds): Boolean; -var - TagId: TActiveTextActionElemKind; -begin - for TagId in Tags do - if fTagState[TagId] > 0 then - Exit(True); - Result := False; -end; - { TREMLWriter } constructor TREMLWriter.InternalCreate; @@ -545,17 +544,32 @@ class function TREMLWriter.Render(const ActiveText: IActiveText): string; 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 with InternalCreate do try - Result := ''; + Text := ''; + fLevel := 0; for Elem in ActiveText do begin if Supports(Elem, IActiveTextTextElem, TextElem) then - Result := Result + RenderText(TextElem) + Text := Text + RenderText(TextElem) else if Supports(Elem, IActiveTextActionElem, TagElem) then - Result := Result + RenderTag(TagElem); + 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; + Result := DestLines.GetText(EOL, False); finally Free; end; @@ -580,8 +594,11 @@ function TREMLWriter.RenderTag( begin // closing tag Result := Format('', [TagName]); - if TagElem.DisplayStyle = dsBlock then - Result := Result + EOL; + if TActiveTextElemCaps.DisplayStyleOf(TagElem.Kind) = dsBlock then + begin + Dec(fLevel); + Result := EOL + StrOfSpaces(IndentMult * fLevel) + Result + EOL; + end; end; fsOpen: begin @@ -598,6 +615,12 @@ function TREMLWriter.RenderTag( TextToREMLText(TagElem.Attrs[TActiveTextAttrNames.Link_URL]) ] ); + if TActiveTextElemCaps.DisplayStyleOf(TagElem.Kind) = dsBlock then + begin + Result := EOL + StrOfSpaces(IndentMult * fLevel) + Result + EOL; + Inc(fLevel); + fIsStartOfTextLine := True; + end; end; end; end; @@ -610,7 +633,14 @@ function TREMLWriter.RenderText( @return REML-safe text containing necessary character entities. } begin - Result := TextToREMLText(TextElem.Text); + if fIsStartOfTextLine then + begin + Result := StrOfSpaces(IndentMult * fLevel); + fIsStartOfTextLine := False; + end + else + Result := ''; + Result := Result + TextToREMLText(TextElem.Text); end; function TREMLWriter.TextToREMLText(const Text: string): string; @@ -641,15 +671,21 @@ function TREMLWriter.TextToREMLText(const Text: string): string; } begin // Record all supported tags - SetLength(fTagMap, 8); - fTagMap[0] := TREMLTag.Create(ekLink, [], 'a', 'href'); - fTagMap[1] := TREMLTag.Create(ekStrong, [], 'strong'); - fTagMap[2] := TREMLTag.Create(ekEm, [], 'em'); - fTagMap[3] := TREMLTag.Create(ekVar, [], 'var'); - fTagMap[4] := TREMLTag.Create(ekPara, BlockTags, 'p'); - fTagMap[5] := TREMLTag.Create(ekWarning, [], 'warning'); - fTagMap[6] := TREMLTag.Create(ekHeading, BlockTags, 'heading'); - fTagMap[7] := TREMLTag.Create(ekMono, [], 'mono'); + SetLength(fTagMap, 11); + // REML v1 + fTagMap[0] := TREMLTag.Create(ekLink, 'a', 'href'); // has href param + fTagMap[1] := TREMLTag.Create(ekStrong, 'strong'); + // REML v2 + fTagMap[2] := TREMLTag.Create(ekEm, 'em'); + fTagMap[3] := TREMLTag.Create(ekVar, 'var'); + fTagMap[4] := TREMLTag.Create(ekPara, 'p'); + fTagMap[5] := TREMLTag.Create(ekWarning, 'warning'); + fTagMap[6] := TREMLTag.Create(ekHeading, 'heading'); + fTagMap[7] := TREMLTag.Create(ekMono, 'mono'); + // REML v5 + fTagMap[8] := TREMLTag.Create(ekUnorderedList, 'ul'); + fTagMap[9] := TREMLTag.Create(ekOrderedList, 'ol'); + fTagMap[10] := TREMLTag.Create(ekListItem, 'li'); end; class destructor TREMLTags.Destroy; @@ -667,12 +703,6 @@ class function TREMLTags.GetCount: Integer; Result := Length(fTagMap); end; -class function TREMLTags.GetExclusions(Idx: Integer): - TActiveTextActionElemKinds; -begin - Result := fTagMap[Idx].Exclusions; -end; - class function TREMLTags.GetId(Idx: Integer): TActiveTextActionElemKind; {Read accessor for Ids[] property. @param Idx [in] Zero based index of required id. @@ -751,8 +781,7 @@ class function TREMLTags.LookupTagName(const Id: TActiveTextActionElemKind; { TREMLTags.TREMLTag } constructor TREMLTags.TREMLTag.Create(const AId: TActiveTextActionElemKind; - const AExclusions: TActiveTextActionElemKinds; const ATagName, - AParamName: string); + const ATagName, AParamName: string); {Record contructor. Initialises fields. @param AId [in] Active text element kind. @param ATagName [in] REML tag name. @@ -760,7 +789,6 @@ constructor TREMLTags.TREMLTag.Create(const AId: TActiveTextActionElemKind; } begin Id := AId; - Exclusions := AExclusions; TagName := ATagName; ParamName := AParamName; end; @@ -792,11 +820,14 @@ class function TREMLEntities.CharToMnemonicEntity(const Ch: Char): string; begin SetLength(fEntityMap, 34); // Supported character entities. All are optional unless otherwise stated + // REML v1 fEntityMap[0] := TREMLEntity.Create('amp', '&'); // required in REML fEntityMap[1] := TREMLEntity.Create('quot', DOUBLEQUOTE); fEntityMap[2] := TREMLEntity.Create('gt', '>'); fEntityMap[3] := TREMLEntity.Create('lt', '<'); // required in REML + // REML v2 fEntityMap[4] := TREMLEntity.Create('copy', '©'); + // REML v5 fEntityMap[5] := TREMLEntity.Create('times', '×'); fEntityMap[6] := TREMLEntity.Create('divide', '÷'); fEntityMap[7] := TREMLEntity.Create('div', '÷'); diff --git a/Src/USnippetExtraHelper.pas b/Src/USnippetExtraHelper.pas index 80598eada..fafad6eea 100644 --- a/Src/USnippetExtraHelper.pas +++ b/Src/USnippetExtraHelper.pas @@ -124,39 +124,46 @@ class function TSnippetExtraHelper.BuildActiveText( empty string. } + // Check for an opening block tag function IsBlockOpener(Elem: IActiveTextElem): Boolean; var ActionElem: IActiveTextActionElem; begin if not Supports(Elem, IActiveTextActionElem, ActionElem) then Exit(False); - Result := (ActionElem.DisplayStyle = dsBlock) + Result := (TActiveTextElemCaps.DisplayStyleOf(ActionElem.Kind) = dsBlock) and (ActionElem.State = fsOpen); end; + // Check for a closing block tag function IsBlockCloser(Elem: IActiveTextElem): Boolean; var ActionElem: IActiveTextActionElem; begin if not Supports(Elem, IActiveTextActionElem, ActionElem) then Exit(False); - Result := (ActionElem.DisplayStyle = dsBlock) + Result := (TActiveTextElemCaps.DisplayStyleOf(ActionElem.Kind) = dsBlock) and (ActionElem.State = fsClose); end; -type - /// Describes different parts of parsed REML code in relation to - /// block tags. - /// Can be within a pair of block tags; without, i.e. not enclosed - /// by block tags or in the transitional state between one and the other. - /// - TBlockState = (bsWithin, bsWithout, bsTransition); + // Embed given content in a para block and append to result, unless content is + // empty when do nothing. + procedure AddNoneEmptyParaToResult(ParaContent: IActiveText); + begin + if ParaContent.IsEmpty then + Exit; + if StrTrim(ParaContent.ToString) = '' then + Exit; + Result.AddElem(TActiveTextFactory.CreateActionElem(ekPara, fsOpen)); + Result.Append(ParaContent); + Result.AddElem(TActiveTextFactory.CreateActionElem(ekPara, fsClose)); + end; + var - ActiveText: IActiveText; // receives active text built from REML - TextElem: IActiveTextTextElem; - Text: string; - BlockState: TBlockState; // state of current position relating to block tags - Elem: IActiveTextElem; // each element in active text + ActiveText: IActiveText; // receives active text built from REML + OutsideBlockActiveText: IActiveText; // receives text outside of blocks + Elem: IActiveTextElem; // each element in active text + Level: Integer; // depth of block levels begin Result := TActiveTextFactory.CreateActiveText; if REML = '' then @@ -165,59 +172,47 @@ class function TSnippetExtraHelper.BuildActiveText( ActiveText := TActiveTextFactory.CreateActiveText(REML, TREMLReader.Create); if ActiveText.IsEmpty then Exit; - // Scan active text, inserting paragraph level block tags where the active - // text is not enclosed by them: this can be at the start, at the end or - // between existing blocks. E.g for "xxx

    yyy

    xxx

    yyy

    xxx", xxx - // is without any block and will be enclosed in paragraphs while yyy is within - // a block and will be unchanged. - BlockState := bsTransition; + // Init block level & obj used to accumulate text outside blocks + Level := 0; + OutsideBlockActiveText := TActiveTextFactory.CreateActiveText; for Elem in ActiveText do begin if IsBlockOpener(Elem) then begin - Assert(BlockState <> bsWithin, - ClassName + '.BuildActiveText: Block is nested.'); - if BlockState = bsWithout then - Result.AddElem(TActiveTextFactory.CreateActionElem(ekPara, fsClose)); + // We have block opener tag. Check for any text that preceeded a level + // zero block and wrap it in a paragraph before writing the block opener + if Level = 0 then + begin + if not OutsideBlockActiveText.IsEmpty then + begin + AddNoneEmptyParaToResult(OutsideBlockActiveText); + OutsideBlockActiveText := TActiveTextFactory.CreateActiveText; + end; + end; Result.AddElem(Elem); - BlockState := bsWithin; + Inc(Level); // drop down one level end else if IsBlockCloser(Elem) then begin - Assert(BlockState = bsWithin, - ClassName + '.BuildActiveText: Block closer outside block.'); - Result.AddElem(Elem); - BlockState := bsTransition; + // Block closer + Dec(Level); + Result.AddElem(Elem); // climb up one level end else begin - if BlockState = bsTransition then - begin - if Supports(Elem, IActiveTextTextElem, TextElem) then - begin - // make sure we don't start a paragraph block if text contains only - // spaces - Text := StrTrimLeft(TextElem.Text); - if Text <> '' then - begin - Result.AddElem(TActiveTextFactory.CreateActionElem(ekPara, fsOpen)); - Result.AddElem(TActiveTextFactory.CreateTextElem(Text)); - BlockState := bsWithout; - end; - end - else - begin - Result.AddElem(TActiveTextFactory.CreateActionElem(ekPara, fsOpen)); - Result.AddElem(Elem); - BlockState := bsWithout; - end; - end + // Not block opener or closer + // If we're outside any block, append elem to store of elems not included + // in blocks. If we're in a block, just add the elem to output + if Level = 0 then + OutsideBlockActiveText.AddElem(Elem) else Result.AddElem(Elem); end; end; - if BlockState = bsWithout then - Result.AddElem(TActiveTextFactory.CreateActionElem(ekPara, fsClose)); + Assert(Level = 0, ClassName + '.BuildActiveText: Unbalanced blocks'); + // Write any outstanding elems that occured outside a block + if not OutsideBlockActiveText.IsEmpty then + AddNoneEmptyParaToResult(OutsideBlockActiveText); end; class function TSnippetExtraHelper.BuildREMLMarkup( From aa5f8dbe829e10a0f574c1b2065c971c734d46cb Mon Sep 17 00:00:00 2001 From: delphidabbler Date: Fri, 17 Jul 2020 01:24:08 +0100 Subject: [PATCH 110/330] Update docs & help topic re REML v5 changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update Docs/Design/reml & help topic: * With details of
      ,

    +
  • + + +
  • @@ -146,6 +150,10 @@ +
  • + + +
  • diff --git a/Src/Help/TOC.hhc b/Src/Help/TOC.hhc index cd91c1f05..ac2c63139 100644 --- a/Src/Help/TOC.hhc +++ b/Src/Help/TOC.hhc @@ -132,6 +132,10 @@ +
  • + + +
  • From d8b61dda10ba74b53ee85186a78aae661b93500f Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 16 Dec 2022 00:14:41 +0000 Subject: [PATCH 125/330] Documents new values & section in config file Update config file documentation with details of changes resulting from implementing issue 19. Add TODO placeholders that need to be resolved once next release version and date are known. Closes #74 --- Docs/Design/FileFormats/config.html | 43 ++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/Docs/Design/FileFormats/config.html b/Docs/Design/FileFormats/config.html index c81851d94..d925f86c5 100644 --- a/Docs/Design/FileFormats/config.html +++ b/Docs/Design/FileFormats/config.html @@ -167,7 +167,7 @@

    - There have been several versions of this file. The current one is version 18. The change to version 18 came with CodeSnip v4.20.0 and the addition of the [Prefs] section. + There have been several versions of this file. The current one is version 18 ««TODO»». The change to version 18 came with CodeSnip v4.20.0««TODO»» and the addition of the [Prefs] section.

    @@ -333,8 +333,49 @@

    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

    From 4ee4a9c2888b551710e260abb4badbe734cdcd88 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 16 Dec 2022 01:17:02 +0000 Subject: [PATCH 126/330] Extract common version information into include file Version information from VCodeSnip.vi and VCodeSnipPortable.vi that is common to both files was extracted into the VersionInfo.vi-inc file as macro definitions. The macros were then imported into to .vi files. Changed file description. Renamed program from historic "Viewer" (that dates back to v1!) to "Repository". Also added "Standard Version" for standard version to match "Portable Version" that was already appended to file description in portable version. As a side effect, this commit fixes problem of rendering copyright symbol, because VIEd handles UTF-8 correctly in macro import files but not in .vi files! Closes #75 --- Src/VCodeSnip.vi | 19 ++++++++++++------- Src/VCodeSnipPortable.vi | 19 ++++++++++++------- Src/VersionInfo.vi-inc | 12 ++++++++++++ 3 files changed, 36 insertions(+), 14 deletions(-) create mode 100644 Src/VersionInfo.vi-inc diff --git a/Src/VCodeSnip.vi b/Src/VCodeSnip.vi index 3ed5f7959..8dc6fb9de 100644 --- a/Src/VCodeSnip.vi +++ b/Src/VCodeSnip.vi @@ -7,9 +7,12 @@ ; Version information description file for CodeSnip. +[Macros] +Import:ver=.\VersionInfo.vi-inc + [Fixed File Info] -File Version #=4, 20, 2, 266 -Product Version #=4, 20, 2, 0 +File Version #=<%ver.version>.<%ver.build> +Product Version #=<%ver.version> File OS=4 File Type=1 File Sub-Type=0 @@ -21,19 +24,21 @@ Language=2057 Character Set=1252 [String File Info] -Comments=Released under the terms of the Mozilla Public License v2.0 (https://www.mozilla.org/MPL/2.0/) -Company Name=DelphiDabbler -File Description=CodeSnip Database Viewer +Comments=<%var.license> +Company Name=<%ver.company> +File Description=<%ver.description> (Standard Edition) File Version=<#F1>.<#F2>.<#F3> build <#F4> Internal Name= -Legal Copyright=Copyright © P.D.Johnson, 2005-. +Legal Copyright=<%ver.copyright> Legal Trademark= Original File Name=CodeSnip.exe Private Build= -Product Name=DelphiDabbler CodeSnip +Product Name=<%ver.company> <%ver.name> Product Version=Release <#P1>.<#P2>.<#P3> Special Build= [Configuration Details] Identifier= NumRCComments=0 +ResOutputDir= +FileVersion=1 diff --git a/Src/VCodeSnipPortable.vi b/Src/VCodeSnipPortable.vi index ff5d8d1b6..90646ad38 100644 --- a/Src/VCodeSnipPortable.vi +++ b/Src/VCodeSnipPortable.vi @@ -7,9 +7,12 @@ ; Version information description file for the portable edition of CodeSnip +[Macros] +Import:ver=.\VersionInfo.vi-inc + [Fixed File Info] -File Version #=4, 20, 2, 266 -Product Version #=4, 20, 2, 0 +File Version #=<%ver.version>.<%ver.build> +Product Version #=<%ver.version> File OS=4 File Type=1 File Sub-Type=0 @@ -21,19 +24,21 @@ Language=2057 Character Set=1252 [String File Info] -Comments=Released under the terms of the Mozilla Public License v2.0 (https://www.mozilla.org/MPL/2.0/) -Company Name=DelphiDabbler -File Description=CodeSnip Database Viewer (Portable Edition) +Comments=<%var.license> +Company Name=<%ver.company> +File Description=<%ver.description> (Portable Edition) File Version=<#F1>.<#F2>.<#F3> build <#F4> Internal Name= -Legal Copyright=Copyright © P.D.Johnson, 2005-. +Legal Copyright=<%ver.copyright> Legal Trademark= Original File Name=CodeSnip-p.exe Private Build= -Product Name=DelphiDabbler CodeSnip +Product Name=<%ver.company> <%ver.name> Product Version=Release <#P1>.<#P2>.<#P3> Special Build=Portable [Configuration Details] Identifier= NumRCComments=0 +ResOutputDir= +FileVersion=1 diff --git a/Src/VersionInfo.vi-inc b/Src/VersionInfo.vi-inc new file mode 100644 index 000000000..b75936ffb --- /dev/null +++ b/Src/VersionInfo.vi-inc @@ -0,0 +1,12 @@ +# CodeSnip Version Information Macros for Including in .vi files + +# Version & build numbers +version=4.22.2 +build=266 + +# String file information +copyright=Copyright © P.D.Johnson, 2005-. +description=Code Snippets Repository +company=DelphiDabbler +name=CodeSnip +license=Released under the terms of the Mozilla Public License v2.0 (https://www.mozilla.org/MPL/2.0/) From be944290f0710d797b1d629ae9f61b54fdae6ef8 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 16 Dec 2022 01:28:03 +0000 Subject: [PATCH 127/330] Update Build.html re change in required VIEd version --- Build.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Build.html b/Build.html index a6a432a39..679825b07 100644 --- a/Build.html +++ b/Build.html @@ -6,7 +6,7 @@ * 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) 2009-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2009-2022, Peter Johnson (gravatar.com/delphidabbler). * * Instructions for building CodeSnip from source. --> @@ -191,10 +191,10 @@

    - 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 + This tool is used to compile version information (.vi) files and + any associated macro file(s) into intermediate resource source + (.rc) files. Version 2.14.0 or later is required. Version + Information Editor can be obtained from https://github.com/delphidabbler/vied/releases. From c29bd68727653e19f3ce2eb7cb7b6b8ad044b252 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 16 Dec 2022 16:24:53 +0000 Subject: [PATCH 128/330] Fix format error in REML code generator Code in UREMLDataIO unit that pretty-printed REML from active text was malforming some lines where (a) text followed a closing block tag and (b) an inline tag immediately followed an opening block tag. --- Src/UREMLDataIO.pas | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Src/UREMLDataIO.pas b/Src/UREMLDataIO.pas index acd5bc135..c6ae83bbc 100644 --- a/Src/UREMLDataIO.pas +++ b/Src/UREMLDataIO.pas @@ -598,6 +598,7 @@ function TREMLWriter.RenderTag( begin Dec(fLevel); Result := EOL + StrOfSpaces(IndentMult * fLevel) + Result + EOL; + fIsStartOfTextLine := True; end; end; fsOpen: @@ -620,6 +621,14 @@ function TREMLWriter.RenderTag( 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; From e073b9c443abb2e721eb71cca41ce100d2822a96 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 16 Dec 2022 16:30:27 +0000 Subject: [PATCH 129/330] Fix welcome page redraw after compilers registered Main form was not redrawing the welcome page to reflect the addition of compilers after user accepted new compilers automatically detected at startup. Changed UCompileMgr.TMainCompileMgr.CheckForNewCompilerInstalls to return True if user had permitted compilers to be registered. Main form's AfterShowForm event handler wa changed to refresh the display when the method returns True. --- Src/FmMain.pas | 3 ++- Src/UCompileMgr.pas | 9 ++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Src/FmMain.pas b/Src/FmMain.pas index f2f5d1e91..54ee4a6bf 100644 --- a/Src/FmMain.pas +++ b/Src/FmMain.pas @@ -1219,7 +1219,8 @@ procedure TMainForm.AfterShowForm; fMainDisplayMgr.Initialise(fWindowSettings.OverviewTab); fMainDisplayMgr.ShowWelcomePage; // check for registerable Delphi compiler installations - fCompileMgr.CheckForNewCompilerInstalls; + if fCompileMgr.CheckForNewCompilerInstalls then + fMainDisplayMgr.Refresh; end; function TMainForm.appEventsHelp(Command: Word; Data: Integer; diff --git a/Src/UCompileMgr.pas b/Src/UCompileMgr.pas index 8a7cea3b5..47dc26f18 100644 --- a/Src/UCompileMgr.pas +++ b/Src/UCompileMgr.pas @@ -116,12 +116,14 @@ TMainCompileMgr = class(TCompileMgr) ///

    Check for new compiler installations, get user permission to /// install any that are found and register any compilers that user /// selects. + /// Boolean. True if any compilers were registered, False if not. + /// /// /// Does nothing if compiler detection is disabled or if there are /// no installed but unregistered compilers. /// Should be called at program startup. /// - procedure CheckForNewCompilerInstalls; + function CheckForNewCompilerInstalls: Boolean; end; @@ -264,7 +266,7 @@ function TMainCompileMgr.CanCompile(View: IView): Boolean; and SnippetView.Snippet.CanCompile; end; -procedure TMainCompileMgr.CheckForNewCompilerInstalls; +function TMainCompileMgr.CheckForNewCompilerInstalls: Boolean; var CandidateCompilers: TCompilerList; // compilers available for registration SelectedCompilers: TCompilerList; // compilers chosen for registration @@ -313,6 +315,7 @@ procedure TMainCompileMgr.CheckForNewCompilerInstalls; end; begin + Result := False; if not TCompilerSettings.PermitStartupDetection then Exit; SelectedCompilers := nil; @@ -345,6 +348,7 @@ procedure TMainCompileMgr.CheckForNewCompilerInstalls; Persister.Save(Compilers); // tell user what got registered NotifyResults; + Result := True; end else // User didn't select a file: tell them @@ -381,4 +385,3 @@ function TMainCompileMgr.IsLastCompiledView(View: IView): Boolean; end; end. - From e45c1490e6d360a930bab3f32e6be7efa30bb1f3 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 16 Dec 2022 16:35:27 +0000 Subject: [PATCH 130/330] Fix corrupted bullet character in message box. Modified UCompileMgr.TMainCompileMgr.CheckForNewCompilerInstalls to specify Unicode hex character representation of bullet character instead of literal character when displaying message box that confirms new compilers have been registered. --- Src/UCompileMgr.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Src/UCompileMgr.pas b/Src/UCompileMgr.pas index 47dc26f18..ded36eca3 100644 --- a/Src/UCompileMgr.pas +++ b/Src/UCompileMgr.pas @@ -292,7 +292,7 @@ function TMainCompileMgr.CheckForNewCompilerInstalls: Boolean; if (SelectedCompilers.IndexOf(Compiler) >= 0) and Compiler.IsAvailable then begin - CompList := CompList + '• ' + Compiler.GetName + EOL; + CompList := CompList + #$2022' ' + Compiler.GetName + EOL; Inc(RegCount); end; end; From 845c392cb0640218706bd860b96bc66a7c9c54a5 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 16 Dec 2022 16:38:08 +0000 Subject: [PATCH 131/330] Replace TODO placeholders with CodeSnip ver & date Update design documents with CodeSnip release version and release date relating to changes in config, export, user database and REML file formats. Some other minor tweak too. --- Docs/Design/FileFormats/config.html | 4 ++-- Docs/Design/FileFormats/export.html | 6 +++--- Docs/Design/FileFormats/user-db.html | 6 +++--- Docs/Design/reml.html | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Docs/Design/FileFormats/config.html b/Docs/Design/FileFormats/config.html index d925f86c5..8c0061093 100644 --- a/Docs/Design/FileFormats/config.html +++ b/Docs/Design/FileFormats/config.html @@ -167,7 +167,7 @@

    - There have been several versions of this file. The current one is version 18 ««TODO»». The change to version 18 came with CodeSnip v4.20.0««TODO»» and the addition of the [Prefs] section. + There have been several versions of this file. The current one is version 19. The change to version 19 came with CodeSnip v4.21.0 and the addition of the [Compilers] section and the CanAutoInstall key in the [Cmp:XXX] sections.

    @@ -762,7 +762,7 @@

    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.
    - The current value is 16. + The current value is 19.
    diff --git a/Docs/Design/FileFormats/export.html b/Docs/Design/FileFormats/export.html index 3a383591d..5f57b1c60 100644 --- a/Docs/Design/FileFormats/export.html +++ b/Docs/Design/FileFormats/export.html @@ -175,7 +175,7 @@

    version

    - Identifies version of file. Determines which tags are valid and + Identifies major version of file. Determines which tags are valid and establishes rules concerning content. Valid versions are 1 to 7.
    @@ -979,10 +979,10 @@

    Updated with CodeSnip v4.18.0 to add support for Delphi 11.x Alexandria.
    - Version 7.3 - ««TODO»» + Version 7.3 - 16 December 2022
    - Updated with CodeSnip v««TODO»» to add support for REML v5, which is backward compatible with REML v4. + Updated with CodeSnip v4.21.0 to add support for REML v5, which is backward compatible with REML v4.
    diff --git a/Docs/Design/FileFormats/user-db.html b/Docs/Design/FileFormats/user-db.html index db90b23df..a5e03cfca 100644 --- a/Docs/Design/FileFormats/user-db.html +++ b/Docs/Design/FileFormats/user-db.html @@ -199,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.
    @@ -1015,10 +1015,10 @@

    Updated with CodeSnip v4.18.0 to add support for Delphi 11.x Alexandria.
    - Version 6.11 - ««TODO»» + Version 6.11 - 16 December 2022
    - Updated with CodeSnip v««TODO»» to add support for REML v5, which is backwards compatible with REML v4. + Updated with CodeSnip v4.21.0 to add support for REML v5, which is backwards compatible with REML v4.
    diff --git a/Docs/Design/reml.html b/Docs/Design/reml.html index 3fce8fae1..9762decaf 100644 --- a/Docs/Design/reml.html +++ b/Docs/Design/reml.html @@ -625,11 +625,11 @@

    Change Log

    - v5 of ««TODO»» + v5 of 2022-12-16

    - Introduced in CodeSnip ««TODO»» + Introduced in CodeSnip v4.21.0

      From aa102f9c31e2f27312e1c9d6aa89bac0e630fc5f Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 16 Dec 2022 16:39:53 +0000 Subject: [PATCH 132/330] Bump version # of user config file from 18 to 19 v19 is now stamped into config files when CodeSnip v4.21.0 starts for the first time. --- Src/FirstRun.UConfigFile.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Src/FirstRun.UConfigFile.pas b/Src/FirstRun.UConfigFile.pas index 2645b6acc..50bba121b 100644 --- a/Src/FirstRun.UConfigFile.pas +++ b/Src/FirstRun.UConfigFile.pas @@ -82,7 +82,7 @@ TUserConfigFileUpdater = class(TConfigFileUpdater) strict private const /// Current user config file version. - FileVersion = 18; + FileVersion = 19; strict protected /// Returns current user config file version. class function GetFileVersion: Integer; override; From ef878a6611397cd3caadfff1bf2654d973eb737d Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 16 Dec 2022 16:41:25 +0000 Subject: [PATCH 133/330] Update program version to 4.21.0 build 267 !!!NOTE The version number was actually reduced from v4.22.2 because that number was entered in error when VersionInfo.vi-inc was first created: the version should have been v4.20.2 !!! --- Src/VersionInfo.vi-inc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Src/VersionInfo.vi-inc b/Src/VersionInfo.vi-inc index b75936ffb..f255c1655 100644 --- a/Src/VersionInfo.vi-inc +++ b/Src/VersionInfo.vi-inc @@ -1,8 +1,8 @@ # CodeSnip Version Information Macros for Including in .vi files # Version & build numbers -version=4.22.2 -build=266 +version=4.21.0 +build=267 # String file information copyright=Copyright © P.D.Johnson, 2005-. From 97030525bcb2ab2812af20057ece7f46284f9bd2 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 16 Dec 2022 16:43:23 +0000 Subject: [PATCH 134/330] Update change log with details of release v4.21.0 --- CHANGELOG.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c58946c1..36e25aaf7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,26 @@ This change log begins with the first ever pre-release version of _CodeSnip_. Re From v4.1.0 the version numbering has attempted to adhere to the principles of [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 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: `
        `, `
          ` & `
        • `. +* Program now automatically detects new (supported) Delphi installations at startup and offers to register the compiler(s) to be used for test compiling snippets. This feature is on by default but can be turned off completely or for specifically excluded compilers [issue #19]. +* Modified Configure Compilers dialogue box: + * Added facility to customise automatic compiler detection on per-compiler or global basis. + * Changed manually triggered compiler detection to ignore excluded compilers. +* Some refactoring [including issues #73 and #75]. +* Minor changes to program license + * Changed required image attribution in `Docs/License.html` [issue #63] + * Corrected copyright date & fix typo in licenses displayed by installer and help file [issue #65 & PR #72]. +* Bump per-user config file to version 19. +* Documentation updates: + * Updated `README.md` re abandoned and new Git repo branches. + * Updated config file, database, export file & REML documentation re changes in this release [including issue #74]. + * Help file updated with details of changes in this release. + * Updated development tool chain requirements in `Build.html`. + ## Release v4.20.2 of 04 November 2022 * Fixes bug where an exception was raised when selecting a main menu item with the cursor keys then pressing F1. [issue 54] From e3d402941e345c8e79e2f36b50401cbc2682cc2a Mon Sep 17 00:00:00 2001 From: Peter Johnson <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 16 Dec 2022 19:00:53 +0000 Subject: [PATCH 135/330] Update CHANGELOG.md Fix broken link to REML docs in v4.21.0 section --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36e25aaf7..b65c83ffd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ From v4.1.0 the version numbering has attempted to adhere to the principles of [ ## 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]: +* 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: `
            `, `
              ` & `
            • `. * Program now automatically detects new (supported) Delphi installations at startup and offers to register the compiler(s) to be used for test compiling snippets. This feature is on by default but can be turned off completely or for specifically excluded compilers [issue #19]. From 89d5e3a5a327cbd35815552cb67c51e16001839a Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 16 Dec 2022 22:37:40 +0000 Subject: [PATCH 136/330] Revise build files to use _build dir & new zip name Various files that are invloved in the Makefile build process were updated to write output to _build/bin, _build/exe and _build/release directories instead of Bin, Exe & Release in repo root. CodeSnip.dproj was also update to ensure that builds using the IDE used the same _build/bin & _build/exe directories. Makefile was also updated to (a) increase use of macros for path names (b) name .zip files codesnip-exe and codesnip-portable-exe (c) append any version number in VERSION define to zip file base names Also fixed malformed quotes in a #define in CodeSnip.iss --- Src/CodeSnip.cfg.tplt | 12 +++--- Src/CodeSnip.dproj | 8 ++-- Src/Help/CodeSnip.hhp | 2 +- Src/Install/CodeSnip.iss | 8 ++-- Src/Makefile | 82 ++++++++++++++++++++++++---------------- 5 files changed, 64 insertions(+), 48 deletions(-) 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.dproj b/Src/CodeSnip.dproj index 628ac2cce..14d04cbd9 100644 --- a/Src/CodeSnip.dproj +++ b/Src/CodeSnip.dproj @@ -16,9 +16,9 @@ WinTypes=Windows;WinProcs=Windows;DbiTypes=BDE;DbiProcs=BDE;DbiErrs=BDE;$(DCC_UnitAlias) false - ..\Exe - ..\Bin;3rdParty;$(DCC_UnitSearchPath) - ..\Exe\CodeSnip.exe + ..\_build\exe + ..\_build\bin;3rdParty;$(DCC_UnitSearchPath) + ..\_build\exe\CodeSnip.exe true true x86 @@ -29,7 +29,7 @@ false false 1 - ..\Bin + ..\_build\bin false diff --git a/Src/Help/CodeSnip.hhp b/Src/Help/CodeSnip.hhp index c9d4acebd..48d4ec0f2 100644 --- a/Src/Help/CodeSnip.hhp +++ b/Src/Help/CodeSnip.hhp @@ -8,7 +8,7 @@ [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 diff --git a/Src/Install/CodeSnip.iss b/Src/Install/CodeSnip.iss index 7b9f93944..47a85f471 100644 --- a/Src/Install/CodeSnip.iss +++ b/Src/Install/CodeSnip.iss @@ -2,7 +2,7 @@ ; 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) 2006-2021, Peter Johnson (gravatar.com/delphidabbler). +; Copyright (C) 2006-2022, Peter Johnson (gravatar.com/delphidabbler). ; ; Install file generation script for use with Inno Setup. @@ -25,10 +25,10 @@ #define ReadMeFile "ReadMe.txt" #define LicenseFile "License.rtf" #define LicenseTextFile "License.html" -#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 ProgDataSubDir AppName + ".4" #define ExeProg SrcExePath + ExeFile #define AppVersion DeleteToVerStart(GetFileProductVersion(ExeProg)) diff --git a/Src/Makefile b/Src/Makefile index ceaf21094..17b443abf 100644 --- a/Src/Makefile +++ b/Src/Makefile @@ -2,15 +2,22 @@ # 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) 2009-2021, Peter Johnson (gravatar.com/delphidabbler). +# Copyright (C) 2009-2022, 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 +DOCS_ROOT = Docs +SRC_ROOT = Src + +# Defines macros giving directories relative to location of the Makefile +BIN_REL = ..\$(BIN_ROOT) +EXE_REL = ..\$(EXE_ROOT) # Check for required environment variables @@ -75,11 +82,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: @@ -104,11 +110,12 @@ 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) + @cd $(SRC_ROOT) # Builds CodeSnip pascal files and links program pascal: CodeSnip.exe @@ -118,14 +125,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 $(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 @@ -144,16 +151,16 @@ 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-* + @del $(EXE_REL)\CodeSnip-Setup-* @$(ISCC) Install\CodeSnip.iss !else @echo **** Portable build - no setup file created **** @@ -161,17 +168,26 @@ setup: # 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 @@ -179,14 +195,14 @@ release: @cd .. -@if exist $(OUTFILE) del $(OUTFILE) !ifndef PORTABLE - @$(ZIP) -j -9 $(OUTFILE) Exe\CodeSnip-Setup-*.exe Docs\ReadMe.txt + @$(ZIP) -j -9 $(OUTFILE) $(EXE_ROOT)\CodeSnip-Setup-*.exe $(DOCS_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\License.html + @$(ZIP) -j -9 $(OUTFILE) $(EXE_ROOT)\CodeSnip-p.exe + @$(ZIP) -j -9 $(OUTFILE) $(EXE_ROOT)\CodeSnip.chm + @$(ZIP) -j -9 $(OUTFILE) $(DOCS_ROOT)\ReadMe.txt + @$(ZIP) -j -9 $(OUTFILE) $(DOCS_ROOT)\License.html !endif - @cd Src + @cd $(SRC_ROOT) # Clean up unwanted files clean: @@ -200,4 +216,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) From 1923cb53b909dff1970957c592380c7eca482343 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sat, 17 Dec 2022 00:03:22 +0000 Subject: [PATCH 137/330] Revise CodeSnipTests.dproj to output to _build dir CodeSnipTests.dproj now wirtes binary and exe files to respective subdirectories of a _build directory. NOTE: This is a different _build directory to the one used by the main CodeSnip project. --- Tests/Src/DUnit/CodeSnipTests.dproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/Src/DUnit/CodeSnipTests.dproj b/Tests/Src/DUnit/CodeSnipTests.dproj index 724362495..359ddcf60 100644 --- a/Tests/Src/DUnit/CodeSnipTests.dproj +++ b/Tests/Src/DUnit/CodeSnipTests.dproj @@ -18,14 +18,14 @@ $(BDS)\Source\DUnit\src;..\..\Bin\DUnit;$(DCC_UnitSearchPath) WinTypes=Windows;WinProcs=Windows;DbiTypes=BDE;DbiProcs=BDE;DbiErrs=BDE;$(DCC_UnitAlias) TESTING;_CONSOLE_TESTRUNNER;$(DCC_Define) - ..\..\Exe + ..\..\_build\exe 00400000 true true true false x86 - ..\..\Bin\DUnit + ..\..\_build\bin\DUnit 1 From 020199f7de4a90cabfe3b4f54323e96e042d260f Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 16 Dec 2022 23:42:37 +0000 Subject: [PATCH 138/330] Update .gitignore to ignore _build directory --- .gitignore | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index c5a26ecfb..64f314858 100644 --- a/.gitignore +++ b/.gitignore @@ -10,8 +10,6 @@ __history/ # Project specific directories & files -Bin -Exe -Release +_build Src/CodeSnip.cfg Src/AutoGen/IntfExternalObj.pas From 4611f3e7c0ae870ba612203722eebef23ead9035 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 16 Dec 2022 23:48:57 +0000 Subject: [PATCH 139/330] Update Build.html re changes to build process: Use of _build directory for all binaries New VERSION macro to add version numbers to release zip files --- Build.html | 83 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 62 insertions(+), 21 deletions(-) diff --git a/Build.html b/Build.html index 679825b07..b918791bf 100644 --- a/Build.html +++ b/Build.html @@ -460,8 +460,7 @@

              | +-- 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. + 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. @@ -498,18 +497,18 @@

              ./
                 |
              -  +-- Bin                   - receives object files for CodeSnip
              -  |
              -  ...
              -  |
              -  +-- Exe                   - receives executable code and compiled help file
              -  |
              -  +-- Release               - receives release files
              +  +-- _build                - contains all the build files
              +  |   |
              +  |   +-- 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. + If the _build/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 Git. @@ -625,7 +624,7 @@

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

              @@ -640,7 +639,7 @@

              > Make -DPORTABLE codesnip

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

              @@ -654,13 +653,16 @@

              > Make help
              +

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

              Build the Setup Program

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

              @@ -679,7 +681,7 @@

              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. + information. It is placed in the _build/exe directory.

              @@ -704,6 +706,7 @@

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

              @@ -723,13 +726,17 @@

              > Make release

              - By default the release file is named dd-codesnip.zip. You can + 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.zip release
              +
              > Make -DRELEASEFILENAME=MyRelease release
              + +

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

              Portable edition @@ -754,7 +761,11 @@

              MyPortableRelease.zip by doing:

              -
              > Make -DPORTABLE -DRELEASEFILENAME=MyPortableRelease.zip release
              +
              > 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 @@ -763,6 +774,35 @@

              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 @@ -784,16 +824,17 @@

              > Make setup > Make release -

              - Portable edition -

              -

              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. +

              +

              Clean Up

              From eb65b892cb784212270a3d275b569e5f17df8b22 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sat, 17 Dec 2022 01:24:01 +0000 Subject: [PATCH 140/330] Update StrWrap routines to take non -ve int params StrWrap was accepting Integer maximum line lengths and indents, which makes no sense. Unsurprisingly the routine was failing tests that passed negative margin and maximum lengths. So MaxLineLen and Margin parameters were changed from Integer to Word. --- Src/UStrUtils.pas | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Src/UStrUtils.pas b/Src/UStrUtils.pas index 0d4eb057d..0bc25e56b 100644 --- a/Src/UStrUtils.pas +++ b/Src/UStrUtils.pas @@ -228,7 +228,7 @@ function StrSplit(const Str: UnicodeString; const Delim: UnicodeString; /// offsets each line using spaces to form a left margin of size given by /// Margin. /// Output lines are separated by CRLF. -function StrWrap(const Str: UnicodeString; const MaxLen, Margin: Integer): +function StrWrap(const Str: UnicodeString; const MaxLen, Margin: UInt16): UnicodeString; overload; /// Word wraps each paragraph of text in Paras so that each line of a @@ -236,7 +236,7 @@ function StrWrap(const Str: UnicodeString; const MaxLen, Margin: Integer): /// number of spaces gvien by Margin. Blanks lines are used to separate /// output paragraphs iff SeparateParas is true. /// Output lines are separated by CRLF. -function StrWrap(const Paras: TStrings; const MaxLineLen, Margin: Integer; +function StrWrap(const Paras: TStrings; const MaxLineLen, Margin: UInt16; const SeparateParas: Boolean): UnicodeString; overload; /// Checks in string Str forms a valid sentence and, if not, adds a @@ -773,7 +773,7 @@ function StrWindowsLineBreaks(const Str: UnicodeString): UnicodeString; Result := StrReplace(Result, LF, CRLF); end; -function StrWrap(const Str: UnicodeString; const MaxLen, Margin: Integer): +function StrWrap(const Str: UnicodeString; const MaxLen, Margin: UInt16): UnicodeString; var Word: UnicodeString; // next word in input Str @@ -823,7 +823,7 @@ function StrWrap(const Str: UnicodeString; const MaxLen, Margin: Integer): end; end; -function StrWrap(const Paras: TStrings; const MaxLineLen, Margin: Integer; +function StrWrap(const Paras: TStrings; const MaxLineLen, Margin: UInt16; const SeparateParas: Boolean): UnicodeString; overload; var Para: string; From 6fead6b7df6238716e42396302e3d874f1a557b5 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sat, 17 Dec 2022 01:32:02 +0000 Subject: [PATCH 141/330] Remove tests that compile now that StrWrap modified Two tests passed negative Margin and MaxLineLen parameters to StrWrap routine. Since such values make no sense, StrWrap was modified to make these parameters unsigned: they were changed from Integer to UInt16. --- Tests/Src/DUnit/TestUStrUtils.pas | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Tests/Src/DUnit/TestUStrUtils.pas b/Tests/Src/DUnit/TestUStrUtils.pas index ee38bb402..4be2b4ea6 100644 --- a/Tests/Src/DUnit/TestUStrUtils.pas +++ b/Tests/Src/DUnit/TestUStrUtils.pas @@ -1069,10 +1069,8 @@ procedure TTestStrUtilsRoutines.TestStrWrap_overload1; CheckEquals(ResA, StrWrap(Text, 10, 0), 'Test 5'); CheckEquals(ResB, StrWrap(Text, 10, 2), 'Test 6'); CheckEquals(ResC, StrWrap(Text, 15, 0), 'Test 7'); - CheckEquals(ResC, StrWrap(Text, 15, -2), 'Test 8'); - CheckEquals(ResD, StrWrap(Text, 1, 0), 'Test 9'); - CheckEquals(ResD, StrWrap(Text, 0, 0), 'Test 10'); - CheckEquals(ResD, StrWrap(Text, -1, 0), 'Test 11'); + CheckEquals(ResD, StrWrap(Text, 1, 0), 'Test 8'); + CheckEquals(ResD, StrWrap(Text, 0, 0), 'Test 9'); end; procedure TTestStrUtilsRoutines.TestStrWrap_overload2; From 31909d7e6d2af84eb83dfb47ea99a8dee9fc58c5 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sat, 17 Dec 2022 02:09:57 +0000 Subject: [PATCH 142/330] Fix garbled (C) symbol in generated source code Replaced literal (C) symbol in resourcestring statements with constant name. Defined COPYRIGHT constant in UConsts unit as the appropriate Unicode hex character code. Fixes #80 --- Src/UConsts.pas | 2 ++ Src/USaveUnitMgr.pas | 10 ++++++---- Src/USnippetSourceGen.pas | 10 ++++++---- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/Src/UConsts.pas b/Src/UConsts.pas index d6bffd449..7715b2c0d 100644 --- a/Src/UConsts.pas +++ b/Src/UConsts.pas @@ -36,6 +36,8 @@ interface GT = '>'; // greater-than / closing angle bracket character LT = '<'; // less-than / opening angle bracket character + COPYRIGHT = #$00A9; + CRLF = CR + LF; // carriage return followed by line feed EOL = CRLF; // end of line character sequence for Windows systems EOL2 = EOL + EOL; // 2 end of line sequences diff --git a/Src/USaveUnitMgr.pas b/Src/USaveUnitMgr.pas index 7b4e677d8..d1d16a05d 100644 --- a/Src/USaveUnitMgr.pas +++ b/Src/USaveUnitMgr.pas @@ -1,9 +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 https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2006-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2006-2022, Peter Johnson (gravatar.com/delphidabbler). * * Defines a class that manages generation, previewing and saving of a pascal * unit. @@ -98,6 +98,7 @@ implementation // Project DB.UMetaData, UAppInfo, + UConsts, UUrl, UUtils; @@ -115,8 +116,9 @@ implementation // Error message sErrorMsg = 'Filename is not valid for a Pascal unit'; // Unit header comments - sLicense = 'The unit is copyright � %0:s by %1:s and is licensed under ' - + 'the %2:s.'; + sLicense = 'The unit is copyright ' + + COPYRIGHT + + ' %0:s by %1:s and is licensed under the %2:s.'; sMainDescription = 'This unit was generated automatically. It incorporates a ' + 'selection of source code taken from the Code Snippets Database at %0:s.'; sGenerated = 'Generated on : %0:s.'; diff --git a/Src/USnippetSourceGen.pas b/Src/USnippetSourceGen.pas index 8dc71274e..952b5a1ee 100644 --- a/Src/USnippetSourceGen.pas +++ b/Src/USnippetSourceGen.pas @@ -1,9 +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 https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2006-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2006-2022, Peter Johnson (gravatar.com/delphidabbler). * * Implements a static class that generates source code for code snippet(s) * contained in a routine snippet or category view. @@ -90,6 +90,7 @@ implementation DB.UMetaData, DB.USnippet, DB.USnippetKind, + UConsts, UAppInfo, UQuery, UUtils; @@ -108,8 +109,9 @@ function TSnippetSourceGen.BuildHeaderComments: IStringList; // when snippets include those from main database sMainDBGenerator = 'This code snippet was generated by %0:s %1:s on %2:s.'; sMainDBLicense = 'It includes code taken from the DelphiDabbler Code ' - + 'Snippets database that is copyright � %0:s by %1:s and is licensed ' - + 'under the %2:s.'; + + 'Snippets database that is copyright ' + + COPYRIGHT + + ' %0:s by %1:s and is licensed under the %2:s.'; // when snippets are all from user defined database sUserGenerator = 'This user defined code snippet was generated by ' + '%0:s %1:s on %2:s.'; From 71f013fd2344901a2fee4cc3be45e887d8eeb7f9 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 28 Dec 2022 11:12:47 +0000 Subject: [PATCH 143/330] Remove information about semantic versioning. CodeSnip isn't actually semantically versioned because it declares no API and major version numbers can be bumped when there are major changes that are not necessarily breaking changes. --- CHANGELOG.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b65c83ffd..a922e7060 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,11 @@ # Changelog -This is the change log for _DelphiDabbler CodeSnip_. +This is the change log for _DelphiDabbler CodeSnip_. It begins with the first ever pre-release version of _CodeSnip_. -All notable changes to this project are documented in this file. - -This change log begins with the first ever pre-release version of _CodeSnip_. Releases are listed in reverse version number order. +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. -From v4.1.0 the version numbering has attempted to adhere to the principles of [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - ## 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]: From 60bdfb1b11e8cd78911e76af85a56afa72554d09 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 1 Jan 2023 16:56:05 +0000 Subject: [PATCH 144/330] Update PJSysInfo vendor code to v5.19.0 Updates recognition of Windows 10/11 builds as of 31 Dec 2022. --- Src/3rdParty/PJSysInfo.pas | 778 +++++++++++++++++++++++++++---------- 1 file changed, 565 insertions(+), 213 deletions(-) diff --git a/Src/3rdParty/PJSysInfo.pas b/Src/3rdParty/PJSysInfo.pas index 9a93aff09..cf1110edc 100644 --- a/Src/3rdParty/PJSysInfo.pas +++ b/Src/3rdParty/PJSysInfo.pas @@ -3,7 +3,7 @@ * 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) 2001-2022, Peter Johnson (https://gravatar.com/delphidabbler). + * Copyright (C) 2001-2023, 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 @@ -230,18 +230,19 @@ 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 - // https://msdn.microsoft.com/en-us/library/ms724358 + // ** Thanks to Laurent Pierre for providing these definitions originally. + // ** Subsequent additions were obtained from https://tinyurl.com/3rhhbs2z 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_N = $00000062; PRODUCT_CORE_SINGLELANGUAGE = $00000064; PRODUCT_DATACENTER_EVALUATION_SERVER = $00000050; + PRODUCT_DATACENTER_A_SERVER_CORE = $00000091; + PRODUCT_STANDARD_A_SERVER_CORE = $00000092; PRODUCT_DATACENTER_SERVER = $00000008; PRODUCT_DATACENTER_SERVER_CORE = $0000000C; PRODUCT_DATACENTER_SERVER_CORE_V = $00000027; @@ -250,18 +251,22 @@ interface 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_N = $0000001B; + PRODUCT_ENTERPRISE_N_EVALUATION = $00000054; + PRODUCT_ENTERPRISE_S = $0000007D; + PRODUCT_ENTERPRISE_S_EVALUATION = $00000081; + PRODUCT_ENTERPRISE_S_N = $0000007E; + PRODUCT_ENTERPRISE_S_N_EVALUATION = $00000082; 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_ESSENTIALBUSINESS_SERVER_MGMT = $0000003B; + PRODUCT_ESSENTIALBUSINESS_SERVER_MGMTSVC = $0000003D; PRODUCT_HOME_BASIC = $00000002; PRODUCT_HOME_BASIC_E = $00000043; PRODUCT_HOME_BASIC_N = $00000005; @@ -271,13 +276,19 @@ interface PRODUCT_HOME_PREMIUM_SERVER = $00000022; PRODUCT_HOME_SERVER = $00000013; PRODUCT_HYPERV = $0000002A; + PRODUCT_IOTENTERPRISE = $000000BC; + PRODUCT_IOTENTERPRISE_S = $000000BF; + PRODUCT_IOTUAP = $0000007B; + PRODUCT_IOTUAPCOMMERCIAL = $00000083; 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_MULTIPOINT_STANDARD_SERVER = $0000004C; + PRODUCT_PRO_WORKSTATION = $000000A1; + PRODUCT_PRO_WORKSTATION_N = $000000A2; PRODUCT_PROFESSIONAL = $00000030; PRODUCT_PROFESSIONAL_E = $00000045; PRODUCT_PROFESSIONAL_N = $00000031; @@ -296,8 +307,8 @@ interface 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_V = $00000024; PRODUCT_STANDARD_SERVER_SOLUTIONS = $00000034; PRODUCT_STANDARD_SERVER_SOLUTIONS_CORE = $00000035; PRODUCT_STARTER = $0000000B; @@ -313,10 +324,10 @@ interface 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_UNDEFINED = $00000000; PRODUCT_WEB_SERVER = $00000011; PRODUCT_WEB_SERVER_CORE = $0000001D; PRODUCT_UNLICENSED = $ABCDABCD; @@ -333,19 +344,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. @@ -984,7 +996,7 @@ implementation // ** Laurent Pierre supplied original code on which this map is based // It has been modified and extended using MSDN documentation at // https://msdn.microsoft.com/en-us/library/ms724358 - cProductMap: array[1..87] of record + cProductMap: array[1..99] of record Id: Cardinal; // product ID Name: string; // product name end = ( @@ -997,23 +1009,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 +1030,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 +1062,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 +1087,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 +1133,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 +1153,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,14 +1184,14 @@ 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; @@ -1177,6 +1213,8 @@ implementation // Generally used in arrays TBuildNameMap = record Build: Integer; + LoRev: Integer; + HiRev: Integer; Name: string; end; @@ -1186,21 +1224,37 @@ TBuildNameMap = record 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://en.wikipedia.org/wiki/Windows_Server https://en.wikipedia.org/wiki/Windows_Server_2019 https://en.wikipedia.org/wiki/Windows_Server_2016 https://tinyurl.com/y8tfadm2 (MS Windows Server release information) + https://tinyurl.com/usupsz4a (Win 11 Version Numbers & Build Versions) 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. } + { + 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 ------------------------------------------------------------- WinVistaBaseBuild = 6000; @@ -1213,68 +1267,176 @@ TBuildNameMap = record // Windows 10 ---------------------------------------------------------------- + // Version 1507 previews + // Preview builds with major/minor version number 6.4 + Win10_6point4Builds: array[0..2] of Integer = (9841, 9860, 9879); + // Preview builds with major/minor version number 10.0 + Win10_1507_Preview_Builds: array[0..10] of Integer = ( + 9926, 10041, 10049, 10061, 10074, 10122, 10130, 10158, 10159, 10162, 10166 + ); + + // Version 1511 previews + Win10_1511_Preview_Builds: array[0..4] of Integer = ( + 10525, 10532, 10547, 10565, 10576 + ); + + // Version 1607 previews + Win10_1607_Preview_Builds: array[0..24] of Integer = ( + 11082, 11099, 11102, 14251, 14257, 14271, 14279, 14291, 14295, 14316, + 14328, 14332, 14342, 14352, 14361, 14366, 14367, 14371, 14372, 14376, + 14379, 14383, 14385, 14388, 14390 + ); + + // 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 2022-12-31). + 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 | 2023-09-05 | N/a + 21H1 | ended | N/a + 21H2 | 2024-06-11 | 2032-01-13 + 22H2 | 2025-05-13 | N/a + } + // Map of Win 10 builds from 1st release (version 1507) to version 20H2 + // + // 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. Win10BuildMap: array[0..10] of TBuildNameMap = ( - (Build: 10240; Name: 'Version 1507'), - (Build: 10586; Name: 'Version 1511: November Update'), - (Build: 14393; Name: 'Version 1607: Anniversary Update'), - (Build: 15063; Name: 'Version 1703: Creators Update'), - (Build: 16299; Name: 'Version 1709: Fall Creators Update'), - (Build: 17134; Name: 'Version 1803: April 2018 Update'), - (Build: 17763; Name: 'Version 1809: October 2018 Update'), - (Build: 18362; Name: 'Version 1903: May 2019 Update'), - (Build: 18363; Name: 'Version 1909: November 2019 Update'), - (Build: 19041; Name: 'Version 2004: May 2020 Update'), - // Note: Microsoft announced the official version name of build 19042 as - // '20H2', not '2010' which some had expected it to be - (Build: 19042; Name: 'Version 20H2: October 2020 Update') + (Build: 10240; LoRev: 16484; HiRev: MaxInt; + Name: 'Version 1507'), + (Build: 10586; LoRev: 0; HiRev: 1540; + Name: 'Version 1511: November Update'), + (Build: 14393; LoRev: 0; HiRev: MaxInt; + Name: 'Version 1607: Anniversary Update'), + (Build: 15063; LoRev: 0; HiRev: 2679; + Name: 'Version 1703: Creators Update'), + (Build: 16299; LoRev: 15; HiRev: 2166; + Name: 'Version 1709: Fall Creators Update'), + (Build: 17134; LoRev: 1; HiRev: 2208; + Name: 'Version 1803: April 2018 Update'), + (Build: 17763; LoRev: 1; HiRev: MaxInt; + Name: 'Version 1809: October 2018 Update'), + (Build: Win10_19XX_Shared_Build; LoRev: 116; HiRev: 1256; + Name: 'Version 1903: May 2019 Update'), + (Build: 18363; LoRev: 327; HiRev: 2274; + Name: 'Version 1909: November 2019 Update'), + (Build: 19041; LoRev: 264; HiRev: 1415; + Name: 'Version 2004: May 2020 Update'), + (Build: 19042; LoRev: 572; HiRev: MaxInt; + Name: 'Version 20H2: October 2020 Update') ); - // Additional information is available for Win 10 buulds from version 21H1, + // Additional information is available for Win 10 builds from version 21H1, // as follows: - // Windows 10 version 21H1: - // * revisions 844..964 were Beta builds - // * later revisions were Public Release builds - Win1021H1Build = 19043; + // Windows 10 version 21H1 - see **REF3** in implementation for details + Win1021H1Build = 19043; // ** End of service 2022-12-13, rev 2364 - // Windows 10 version 21H2: - // * revisions 1147..1266 were Preview builds - // * later revisions were Public Release builds + // Windows 10 version 21H2 - see **REF4** in implementation for details Win1021H2Build = 19044; - // Windows 10 version 22H2 - // * revision 1865 was Release Preview build (KB5015878) + // Windows 10 version 22H2 - see **REF5** in implementation for details Win1022H2Build = 19045; - // Fast ring - Win10FastRing: array[0..21] of Integer = ( - 19536, 19541, 19546, 19551, 19555, 19559, 19564, 19569, 19577, 19582, 19587, - 19592, 19603, 19608, 19613, 19619, 19624, 19628, 19631, 19635, 19640, 19645 - ); - - // Dev channel - // Assuming all Dev channel releases had version string "Dev" - Win10DevChannel: array[0..44] of Integer = ( - 20150, 20152, 20161, 20170, 20175, 20180, 20185, 20190, 20197, 20201, 20206, - 20211, 20215, 20221, 20226, 20231, 20236, 20241, 20246, 20251, 20257, 20262, - 20270, 20277, 21277, 20279, 21286, 21292, 21296, 21301, 21313, 21318, 21322, - 21327, 21332, 21337, 21343, 21354, 21359, 21364, 21370, 21376, 21382, 21387, - 21390 // transitioned to Windows 11 after here - ); + // 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: Preview and beta & release versions of Windows 11 report version 10.0 + // NOTE: All releases of Windows 11 report version 10.0 + + { + End of support (EOS) information for Windows 11 versions (as of 2022-12-31). + + Version | Home, Pro | Education, + | etc EOS | Enterprise + | | etc EOS + --------|------------|------------ + 21H2 | 2023-10-10 | 2024-10-08 + 22H2 | 2024-10-08 | 2025-10-14 + } - // Windows 11 version Dev: 10.0.21996.1 (Insider version) + // 1st build released branded as Windows 11 + // Insider version, Dev channel, v10.0.21996.1 Win11DevBuild = 21996; - // Windows 11 version 21H2: - // * Dev channel: revs 51,65,71 - // * Dev & Beta channels: revs 100,120,132,160,168 - // * Beta & Release Preview channels: revs 176,184 - // * Public Release: rev 194 and later + // Windows 11 version 21H2 - see **REF6** in implementation for details Win11v21H2Build = 22000; // Windows 11 version 22H2 @@ -1283,40 +1445,40 @@ TBuildNameMap = record // various other channels. // See **REF1** in implementation Win11v22H2Build = 22621; - // Build 22632 was added as an alternative Beta channel build as of rev 290: - // * Beta channel: revs 290,436,440,450,575,586,590,598,601 + // Build 22632 was added as an alternative Beta channel build as of rev 290. + // See **REF2** in implementation Win11v22H2BuildAlt = 22622; - // Dev channel release - different sources give different names. - // From what I can gather (and take this with a pinch of salt!): - // * Insider Dev channel releases from the RS_PRERELEASE branch weren't - // matched to a Windows 11 release and had version string "Dev"). - // * The NI_RELEASE channel was used from 2022/02/16 (build 2257). - // * From build 22567 the release string changed from "Dev" to "22H" - - // Builds with version string "Dev" - Win11DevChannelDevBuilds: array[0..43] of Integer = ( - // pre Win 11 release - 22449, 22454, 22458, 22463, 22468, - // post Win 11 release, pre Win 11 22H2 beta release - 22471, 22478, 22483, 22489, 22494, 22499, 22504, 22509, 22518, 22523, 22526, - 22533, 22538, 22543, 22557, 22563, - // post Win 11 22H2 beta release - 25115, 25120, 25126, 25131, 25136, 25140, 25145, 25151, 25158, 25163, 25169, - 25174, 25179, 25182, 25188, 25193, 25197, 25201, 25206, 25211, - // post Win 11 22H2 release - 25217, 25227, 25231 + // Windows 11 Dev channel releases (with version string "Dev"). + // For details see https://en.wikipedia.org/wiki/Windows_11_version_history + Win11DevChannelDevBuilds: array[0..14] of Integer = ( + // 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, + // 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 (expiring 2023/09/15): + 25182, 25188, 25193, 25197, 25201, 25206, 25211, + // post Win 11 22H2 release (expiring 2023/09/15): + 25217, 25227, 25231, 25236, 25247, 25252, 25262, 25267 ); - // Builds with version string "22H2" in Dev channel - Win11DevChannel22H2Builds: array[0..2] of Integer = ( - 22567, 22572, 22579 - ); - // Builds with version string "22H2" in Dev & Beta channels - Win11DevBetaChannels22H2Builds: array[0..4] of Integer = ( - 22581, 22593, 22598, 22610, 22616 + + // Windows 11 Dev channel builds with version string "22H2" + // expired 2022/09/15): + // 22567, 22572, 22579 + + // Windows 11 Dev & Beta channel builds with version string "22H2" + Win11DevBetaChannels22H2Builds: array[0..1] of Integer = ( + // expired 2022/09/15: 22581, 22593, 22598, + 22610, 22616 ); - Win11FutureComponentBetaChannelBuilds: array[0..0] of Integer = (22623); + Win11FutureComponentBetaChannelBuild = 22623; Win11FirstBuild = Win11DevBuild; // First build number of Windows 11 @@ -1330,25 +1492,52 @@ TBuildNameMap = record Win2019LastBuild = 18363; WinServerLastBuild = 19042; + { + 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; Name: 'Technical Preview 2'), - (Build: 10514; Name: 'Technical Preview 3'), - (Build: 10586; Name: 'Technical Preview 4'), - (Build: 14300; Name: 'Technical Preview 5'), - (Build: 14393; Name: 'Version 1607'), - (Build: 16299; Name: 'Version 1709'), - (Build: Win2016LastBuild; Name: 'Version 1803'), + (Build: 10074; LoRev: 0; HiRev: MaxInt; Name: 'Technical Preview 2'), + (Build: 10514; LoRev: 0; HiRev: MaxInt; Name: 'Technical Preview 3'), + (Build: 10586; LoRev: 0; HiRev: MaxInt; Name: 'Technical Preview 4'), + (Build: 14300; LoRev: 0; HiRev: MaxInt; Name: 'Technical Preview 5'), + (Build: 14393; LoRev: 0; HiRev: MaxInt; Name: 'Version 1607'), + (Build: 16299; LoRev: 0; HiRev: MaxInt; Name: 'Version 1709'), + (Build: Win2016LastBuild; LoRev: 0; HiRev: MaxInt; Name: 'Version 1803'), // Windows Server 2019 - (Build: 17763; Name: 'Version 1809'), - (Build: 18362; Name: 'Version 1903'), - (Build: Win2019LastBuild; Name: 'Version 1909'), + (Build: 17763; LoRev: 0; HiRev: MaxInt; Name: 'Version 1809'), + (Build: 18362; LoRev: 0; HiRev: MaxInt; Name: 'Version 1903'), + (Build: Win2019LastBuild; LoRev: 0; HiRev: MaxInt; Name: 'Version 1909'), // Windows Server (no year number) - (Build: 19041; Name: 'Version 2004'), - (Build: WinServerLastBuild; Name: 'Version 20H2'), - // Windows Sever 2022 - (Build: 20348; Name: 'Version 21H2') + (Build: 19041; LoRev: 0; HiRev: MaxInt; Name: 'Version 2004'), + (Build: WinServerLastBuild; LoRev: 0; HiRev: MaxInt; Name: 'Version 20H2'), + // Windows Server 2022 + (Build: 20348; LoRev: 0; HiRev: MaxInt; Name: 'Version 21H2') ); // Windows server releases needing special handling @@ -1412,6 +1601,14 @@ TBuildNameMap = record 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 @@ -1510,10 +1707,11 @@ function FindBuildNumberFrom(const BNs: array of Integer; var FoundBN: Integer): end; // Checks if any of the build numbers in the given array match that of the -// current OS. If so 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 ''. +// 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): Boolean; var @@ -1524,7 +1722,8 @@ function FindBuildNameAndExtraFrom(const Infos: array of TBuildNameMap; Result := False; for I := Low(Infos) to High(Infos) do begin - if IsBuildNumber(Infos[I].Build) then + if IsBuildNumber(Infos[I].Build) and + IsInRange(InternalRevisionNumber, Infos[I].LoRev, Infos[I].HiRev) then begin FoundBN := Infos[I].Build; FoundExtra := Infos[I].Name; @@ -1534,6 +1733,29 @@ function FindBuildNameAndExtraFrom(const Infos: array of TBuildNameMap; 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; @@ -1612,14 +1834,6 @@ function ExcludeTrailingPathDelimiter(const DirOrPath: string) : string; end; {$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; - // Returns the value of the given environment variable. function GetEnvVar(const VarName: string): string; var @@ -1850,6 +2064,17 @@ procedure InitPlatformIdEx; // Windows 2016 Server tech preview 1 InternalBuildNumber := Win2016TP1Build; InternalExtraUpdateInfo := 'Technical Preview 6'; + end + else + begin + if FindBuildNumberFrom( + Win10_6point4Builds, InternalBuildNumber + ) then + // Early Win 10 preview builds report v6.4, not v10.0 + InternalExtraUpdateInfo := Format( + 'Version 1507 Preview v6.4.%d.%d', + [InternalBuildNumber, InternalRevisionNumber] + ) end; end; if Win32ServicePackMajor > 0 then @@ -1880,49 +2105,76 @@ procedure InitPlatformIdEx; end else if IsBuildNumber(Win1021H1Build) then begin + // **REF3** InternalBuildNumber := Win1021H1Build; - InternalExtraUpdateInfo := 'Version 21H1'; - if IsInRange(InternalRevisionNumber, 844, 964) then - InternalExtraUpdateInfo := InternalExtraUpdateInfo + ' (beta)'; + 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(Win1021H2Build) then begin + // **REF4** // From 21H2 Windows 10 moves from a 6 monthly update cycle to a // yearly cycle InternalBuildNumber := Win1021H2Build; - InternalExtraUpdateInfo := 'Version 21H2'; - if IsInRange(InternalRevisionNumber, 1147, 1266) then - InternalExtraUpdateInfo := InternalExtraUpdateInfo - + ' (preview)'; + 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..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: + 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(Win1022H2Build) then begin + // **REF5** InternalBuildNumber := Win1022H2Build; - if IsInRange(InternalRevisionNumber, 1865, 2075) then - InternalExtraUpdateInfo := Format( - 'Version 22H2 [Release Preview v10.0.%d.%d]', - [InternalBuildNumber, InternalRevisionNumber] - ) - else - InternalExtraUpdateInfo := 'Version 22H2'; - end - else if FindBuildNumberFrom( - Win10DevChannel, InternalBuildNumber - ) then - begin - // Windows 10 Dev Channel releases - InternalExtraUpdateInfo := Format( - 'Dev Channel v10.0.%d.%d (Dev)', - [InternalBuildNumber, InternalRevisionNumber] - ); - end - else if FindBuildNumberFrom(Win10FastRing, InternalBuildNumber) then - begin - // Windows 10 Fast Ring releases - InternalExtraUpdateInfo := Format( - 'Fast ring v10.0.%d.%d', - [InternalBuildNumber, InternalRevisionNumber] - ); + case InternalBuildNumber of + 2006, 2130, 2132, 2193, 2194, 2251, 2311, 2364..MaxInt: + InternalExtraUpdateInfo := 'Version 22H2'; + 1865, 1889, 1949, 2075, 2301: + InternalExtraUpdateInfo := Format( + 'Version 22H2 [Release Preview Channel v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + else + InternalExtraUpdateInfo := Format( + 'Version 22H1 [Unknown release v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + end; end // Win 11 releases are reporting v10.0 // Details taken from: https://tinyurl.com/usupsz4a @@ -1936,6 +2188,7 @@ procedure InitPlatformIdEx; end else if IsBuildNumber(Win11v21H2Build) then begin + // **REF6** // There are several Win 11 releases with this build number // Which release we're talking about depends on the revision // number. @@ -1943,8 +2196,10 @@ procedure InitPlatformIdEx; // release of Win 11 -- well hidden eh?! InternalBuildNumber := Win11v21H2Build; case InternalRevisionNumber of - 194..MaxInt: - // Public releases of Windows 11 have build number >= 194 + 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..MaxInt: + // Public releases of Windows 11 InternalExtraUpdateInfo := 'Version 21H2'; 51, 65, 71: InternalExtraUpdateInfo := Format( @@ -1956,12 +2211,17 @@ procedure InitPlatformIdEx; 'Version 21H2 [Dev & Beta Channels v10.0.%d.%d]', [InternalBuildNumber, InternalRevisionNumber] ); - 176, 184: + 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: + InternalExtraUpdateInfo := Format( + 'Version 21H1 Release Preview Channel v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); else InternalExtraUpdateInfo := Format( 'Version 21H2 [Unknown release v10.0.%d.%d]', @@ -1974,20 +2234,20 @@ procedure InitPlatformIdEx; // **REF1** InternalBuildNumber := Win11v22H2Build; case InternalRevisionNumber of - 876..MaxInt, 382, 521, 525, 608, 674, 675, 755: + 382, 521, 525, 608, 674, 675, 755, 819, 900, 963, 1038..MaxInt: InternalExtraUpdateInfo := 'Version 22H2'; 1: InternalExtraUpdateInfo := Format( 'Version 22H2 [Beta & Release Preview v10.0.%d.%d]', [InternalBuildNumber, InternalRevisionNumber] ); - 105, 169, 232, 317, 457, 607, 754: + 105, 169, 232, 317, 457, 607, 754, 898: 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: + 870, 875, 885, 891, 1020, 1028, 1037: InternalExtraUpdateInfo := Format( 'Version 22H2 [Beta v10.0.%d.%d]', [InternalBuildNumber, InternalRevisionNumber] @@ -2001,8 +2261,7 @@ procedure InitPlatformIdEx; end else if IsBuildNumber(Win11v22H2BuildAlt) then begin - // See comments with declarations of Win11v22H2Build and - // Win11v22H2BuildAlt for details of naming of revisions. + // **REF2** InternalBuildNumber := Win11v22H2BuildAlt; // Set fallback update info for unknown revisions case InternalRevisionNumber of @@ -2028,16 +2287,6 @@ procedure InitPlatformIdEx; [InternalBuildNumber, InternalRevisionNumber] ); end - else if FindBuildNumberFrom( - Win11DevChannel22H2Builds, InternalBuildNumber - ) then - begin - // Win11 Dev channel builds with version string "22H2" - InternalExtraUpdateInfo := Format( - 'Dev Channel v10.0.%d.%d (22H2)', - [InternalBuildNumber, InternalRevisionNumber] - ); - end else if FindBuildNumberFrom( Win11DevBetaChannels22H2Builds, InternalBuildNumber ) then @@ -2048,15 +2297,114 @@ procedure InitPlatformIdEx; [InternalBuildNumber, InternalRevisionNumber] ); end - else if FindBuildNumberFrom( - Win11FutureComponentBetaChannelBuilds, InternalBuildNumber + else if IsBuildNumber(Win11FutureComponentBetaChannelBuild) then + begin + InternalBuildNumber := Win11FutureComponentBetaChannelBuild; + case InternalRevisionNumber of + 730, 741, 746, 870, 875, 885, 891, 1020, 1028, 1037..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 + // 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 - InternalExtraUpdateInfo := Format( - 'Future Component Update Beta v10.0.%d.%d', - [InternalBuildNumber, InternalRevisionNumber] - ); - end; + // Nothing to do: required internal variables set in function call + end + else if FindWin10PreviewBuildNameAndExtraFrom( + Win10_2004_Preview_Builds, '2004', + InternalBuildNumber, InternalExtraUpdateInfo + ) then + begin + // Nothing to do: required internal variables set in function call + 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 + InternalExtraUpdateInfo := Format( + 'Version 1903 Preview Build %d.%d', + [InternalBuildNumber, InternalRevisionNumber] + ) + else if IsInRange(InternalRevisionNumber, 10000, 10024) then + InternalExtraUpdateInfo := Format( + 'Version 1909 Preview Build %d.%d', + [InternalBuildNumber, InternalRevisionNumber] + ); + end + else if FindWin10PreviewBuildNameAndExtraFrom( + Win10_1903_Preview_Builds, '1903', + InternalBuildNumber, InternalExtraUpdateInfo + ) then + begin + // Nothing to do: required internal variables set in function call + end + else if FindWin10PreviewBuildNameAndExtraFrom( + Win10_1809_Preview_Builds, '1809', + InternalBuildNumber, InternalExtraUpdateInfo + ) then + begin + // Nothing to do: required internal variables set in function call + end + else if FindWin10PreviewBuildNameAndExtraFrom( + Win10_1803_Preview_Builds, '1803', + InternalBuildNumber, InternalExtraUpdateInfo + ) then + begin + // Nothing to do: required internal variables set in function call + end + else if FindWin10PreviewBuildNameAndExtraFrom( + Win10_1709_Preview_Builds, '1709', + InternalBuildNumber, InternalExtraUpdateInfo + ) then + begin + // Nothing to do: required internal variables set in function call + end + else if FindWin10PreviewBuildNameAndExtraFrom( + Win10_1703_Preview_Builds, '1703', + InternalBuildNumber, InternalExtraUpdateInfo + ) then + begin + // Nothing to do: required internal variables set in function call + end + else if FindWin10PreviewBuildNameAndExtraFrom( + Win10_1607_Preview_Builds, '1607', + InternalBuildNumber, InternalExtraUpdateInfo + ) then + begin + // Nothing to do: required internal variables set in function call + end + else if FindWin10PreviewBuildNameAndExtraFrom( + Win10_1511_Preview_Builds, '1511', + InternalBuildNumber, InternalExtraUpdateInfo + ) then + begin + // Nothing to do: required internal variables set in function call + end + else if FindWin10PreviewBuildNameAndExtraFrom( + Win10_1507_Preview_Builds, '1507', + InternalBuildNumber, InternalExtraUpdateInfo + ) then + begin + // Nothing to do: required internal variables set in function call + end end else // Win32ProductType in [VER_NT_DOMAIN_CONTROLLER, VER_NT_SERVER] begin @@ -2671,6 +3019,8 @@ class function TPJOSInfo.Platform: TPJOSPlatform; end; class function TPJOSInfo.Product: TPJOSProduct; +var + DummyBN: Integer; // dummy build number begin Result := osUnknown; case Platform of @@ -2754,12 +3104,14 @@ class function TPJOSInfo.Product: TPJOSProduct; else Result := osWinSvr2012R2; 4: - // 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. if IsServer then - Result := osWin10Svr; + // 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 + else if FindBuildNumberFrom(Win10_6point4Builds, DummyBN) then + Result := osWin10; else // Higher minor version: must be an unknown later OS Result := osWinLater From 5203fdc9aa10cec29b4e3fe899ddfa9f3acbb575 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 2 Apr 2023 18:47:21 +0100 Subject: [PATCH 145/330] Fix bug in StrCompressWhiteSpace function Bug per issue in StrCompressWhiteSpace function. Fixes #95 --- Src/UStrUtils.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Src/UStrUtils.pas b/Src/UStrUtils.pas index 0bc25e56b..61d52b8a9 100644 --- a/Src/UStrUtils.pas +++ b/Src/UStrUtils.pas @@ -404,7 +404,7 @@ function StrCompressWhiteSpace(const Str: UnicodeString): UnicodeString; Inc(ResCount); // Skip past any following white space Inc(Idx); - while TCharacter.IsWhiteSpace(Str[Idx]) do + while (Idx <= Length(Str)) and TCharacter.IsWhiteSpace(Str[Idx]) do Inc(Idx); end else From 9ed3f8dcb253997bd917b38e185132f329cb233f Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 18 Dec 2022 00:28:57 +0000 Subject: [PATCH 146/330] Refactor to generalise TCompilerSettings TCompilerSettings was generalised to make it easier to add new properties. --- Src/Compilers.USettings.pas | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/Src/Compilers.USettings.pas b/Src/Compilers.USettings.pas index a4baee07f..23223d8dd 100644 --- a/Src/Compilers.USettings.pas +++ b/Src/Compilers.USettings.pas @@ -14,6 +14,9 @@ interface uses + // Delphi + SysUtils, + // Project UBaseObjects, USettings; @@ -24,7 +27,11 @@ TCompilerSettings = class(TNoConstructObject) const AllCompilersConfigSection = ssCompilers; PermitStartupDetectionKey = 'PermitStartupDetection'; + ListFPCAtTopKey = 'Lists:FPCAtTop'; + ListDelphiOldestFirstKey = 'Lists:DelphiOldestFirst'; class function ReadStorage: ISettingsSection; + class procedure DoSaveProperty(const WriteProp: TProc); + class procedure SaveProperty(const Key: string; const Value: Boolean); class function GetPermitStartupDetection: Boolean; static; class procedure SetPermitStartupDetection(const Value: Boolean); static; public @@ -37,6 +44,16 @@ implementation { TCompilerSettings } +class procedure TCompilerSettings.DoSaveProperty( + const WriteProp: TProc); +var + Stg: ISettingsSection; +begin + Stg := ReadStorage; + WriteProp(Stg); + Stg.Save; +end; + class function TCompilerSettings.GetPermitStartupDetection: Boolean; begin Result := ReadStorage.GetBoolean(PermitStartupDetectionKey, True); @@ -47,14 +64,21 @@ class function TCompilerSettings.ReadStorage: ISettingsSection; 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); -var - Stg: ISettingsSection; begin - Stg := ReadStorage; - Stg.SetBoolean(PermitStartupDetectionKey, Value); - Stg.Save; + SaveProperty(PermitStartupDetectionKey, Value); end; end. From 6728e64f9e086fff5cc586d674a3406b63b6322e Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 15 Dec 2022 09:04:39 +0000 Subject: [PATCH 147/330] Add TODO at site of exception from last crash Crash on 2022-12-14 after resuming from hibernation showed a nil pointer exception from a nil IView instance in FrOverview.pas --- Src/FrOverview.pas | 1 + 1 file changed, 1 insertion(+) diff --git a/Src/FrOverview.pas b/Src/FrOverview.pas index c9f56b15d..aaa1e9e0d 100644 --- a/Src/FrOverview.pas +++ b/Src/FrOverview.pas @@ -979,6 +979,7 @@ function TOverviewFrame.TTVDraw.IsUserDefinedNode( ViewItem: IView; // view item represented by node begin ViewItem := (Node as TViewItemTreeNode).ViewItem; + // TODO -cBug: Exception reported as issue #70 seems to be triggered here Result := ViewItem.IsUserDefined; end; From 1fdc8124b1bb2a985758d2e74614218bd37ff29d Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 23 Dec 2022 10:46:58 +0000 Subject: [PATCH 148/330] Add new TActiveText.FirstBlock method This method returns the first whole block of the current active text object. In most cases this will be the first paragraph, but in the case of lists, it will be the whole list, including any child lists. --- Src/ActiveText.UMain.pas | 82 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/Src/ActiveText.UMain.pas b/Src/ActiveText.UMain.pas index 8d6e54602..a75cba4ce 100644 --- a/Src/ActiveText.UMain.pas +++ b/Src/ActiveText.UMain.pas @@ -175,6 +175,16 @@ TActiveTextAttrNames = record /// Appends elements from another given active text object to the /// current object. procedure Append(const ActiveText: IActiveText); + /// Returns a new IActiveText instance containing just the first + /// block of the current object. + /// + /// 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. + /// If the current object is empty then an empty object is returned. + /// + /// + function FirstBlock: IActiveText; /// Checks if the active text object contains any elements. /// function IsEmpty: Boolean; @@ -474,6 +484,17 @@ TActiveText = class(TInterfacedObject, /// /// Method of IActiveText. procedure Append(const ActiveText: IActiveText); + /// Returns a new IActiveText instance containing just the first + /// block of the current object. + /// + /// 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. + /// If the current object is empty then an empty object is returned. + /// + /// Method of IActiveText. + /// + function FirstBlock: IActiveText; /// Checks if the element list is empty. /// Method of IActiveText. function IsEmpty: Boolean; @@ -719,6 +740,67 @@ destructor TActiveText.Destroy; inherited; end; +function TActiveText.FirstBlock: IActiveText; + + function IsBlockWithState(Elem: IActiveTextElem; State: TActiveTextElemState): + Boolean; + var + ActionElem: IActiveTextActionElem; + begin + Result := False; + if not Supports(Elem, IActiveTextActionElem, ActionElem) then + Exit; + if TActiveTextElemCaps.DisplayStyleOf(ActionElem.Kind) <> dsBlock then + Exit; + if ActionElem.State <> State then + Exit; + Result := True; + end; + + function IsBlockOpener(Elem: IActiveTextElem): Boolean; inline; + begin + Result := IsBlockWithState(Elem, fsOpen); + end; + + function IsBlockCloser(Elem: IActiveTextElem): Boolean; inline; + begin + Result := IsBlockWithState(Elem, fsClose); + end; + +var + Depth: Cardinal; + Elem: IActiveTextElem; + Idx: Integer; +begin + Result := TActiveText.Create; + if IsEmpty then + Exit; + + Elem := GetElem(0); + + if not IsBlockOpener(Elem) then + begin + Result.Append(Self); + Exit; + end; + + Depth := 1; + Result.AddElem(Elem); + for Idx := 1 to Pred(GetCount) do + begin + Elem := GetElem(Idx); + Result.AddElem(Elem); + // NOTE: we're not checking for matching openers and closers + if IsBlockOpener(Elem) then + Inc(Depth); + if IsBlockCloser(Elem) then + Dec(Depth); + if Depth = 0 then + Break; + end; + // We're not checking for balancing block closer here either: +end; + function TActiveText.GetCount: Integer; begin Result := fElems.Count; From 204465f27d48c0ed9dfb4614a40c024b4bfa7a66 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Mon, 19 Dec 2022 03:18:38 +0000 Subject: [PATCH 149/330] Adapt StrWrap to support different 1st line offset. Add new, optional, FirstLineOffset parameter to one of the StrWrap overloads that offsets first line relative to margin used for remaining lines. Leaving out the parameter reverts to old behaviour of adding same margin for each line. --- Src/UStrUtils.pas | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/Src/UStrUtils.pas b/Src/UStrUtils.pas index 61d52b8a9..b85d180d5 100644 --- a/Src/UStrUtils.pas +++ b/Src/UStrUtils.pas @@ -226,10 +226,19 @@ function StrSplit(const Str: UnicodeString; const Delim: UnicodeString; /// Word wraps text Str to form lines of maximum length MaxLen and /// offsets each line using spaces to form a left margin of size given by -/// Margin. -/// Output lines are separated by CRLF. -function StrWrap(const Str: UnicodeString; const MaxLen, Margin: UInt16): - UnicodeString; overload; +/// Margin. The first line is offset from the margin by FirstLineOffset spaces. +/// +/// +/// FirstLineOffset offsets to the left of Margin if -ve and to the right +/// of Margin if +ve. +/// If FirstLineOffset is -ve then Abs(FirstLineOffset) must be less than +/// or equal to Margin. +/// If FirstLineOffset is +ve then FirstLineOffset + Margin must fit in +/// a UInt16. +/// Output lines are separated by CRLF. +/// +function StrWrap(const Str: UnicodeString; const MaxLen, Margin: UInt16; + const FirstLineOffset: Int16 = 0): UnicodeString; overload; /// Word wraps each paragraph of text in Paras so that each line of a /// paragraph has lines of maximum length MaxLineLen and is offset by the @@ -773,8 +782,8 @@ function StrWindowsLineBreaks(const Str: UnicodeString): UnicodeString; Result := StrReplace(Result, LF, CRLF); end; -function StrWrap(const Str: UnicodeString; const MaxLen, Margin: UInt16): - UnicodeString; +function StrWrap(const Str: UnicodeString; const MaxLen, Margin: UInt16; + const FirstLineOffset: Int16): UnicodeString; overload; var Word: UnicodeString; // next word in input Str Line: UnicodeString; // current output line @@ -783,14 +792,25 @@ function StrWrap(const Str: UnicodeString; const MaxLen, Margin: UInt16): // ------------------------------------------------------------------------- /// Adds a line of text to output, offseting line by Margin spaces procedure AddLine(const Line: string); + var + AdjustedMargin: UInt16; begin + AdjustedMargin := Margin; if Result <> '' then // not first line: insert new line - Result := Result + EOL; - Result := Result + StrOfSpaces(Margin) + Line; + Result := Result + EOL + else // 1st line - adjust margin + AdjustedMargin := Margin + FirstLineOffset; + Result := Result + StrOfSpaces(AdjustedMargin) + Line; end; // ------------------------------------------------------------------------- begin + // FirstLineOffset, if negative, must have absolute value <= Margin and + // FirstLineOffset, if positive, added to Margin must fit in UInt16 + Assert((Margin + FirstLineOffset >= 0) + and (Margin + FirstLineOffset < High(Margin)), + 'StrWrap: FirstLineOffset + Margin out of range' + ); // Get all words in Str Words := TStringList.Create; try From d903ff1f1eb8a25b217ab1b05643ee7fdbbc59b8 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 21 Dec 2022 15:36:27 +0000 Subject: [PATCH 150/330] Add support for rendering REML lists as plain text ActiveText.UTextRenderer has support added to parsing and rendering REML lists and to format the resulting text with given margins and page widths. This was provided in a new TActiveTextTextRenderer.RenderWrapped method. TActiveTextTextRenderer.Render was made private since it is now only called internally. USourceGen & UTextSnippetDoc were modified to use the above new method. ** This solution is a little kludgy and relies on parsing NBSP characters emitted by TActiveTextTextRenderer.Render. This was done originally because the formatting was being done in USourceGen and UTextSnippetDoc which didn't have access to the inner workings of TActiveTextTextRenderer. The formatting code was then moved into TActiveTextTextRenderer and so there's probably a more elegant solutuion available now. --- Src/ActiveText.UTextRenderer.pas | 289 +++++++++++++++++++++++++++++-- Src/USourceGen.pas | 86 +++++---- Src/UTextSnippetDoc.pas | 36 ++-- 3 files changed, 326 insertions(+), 85 deletions(-) diff --git a/Src/ActiveText.UTextRenderer.pas b/Src/ActiveText.UTextRenderer.pas index bd658666e..be9084294 100644 --- a/Src/ActiveText.UTextRenderer.pas +++ b/Src/ActiveText.UTextRenderer.pas @@ -3,7 +3,7 @@ * 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-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2012-2022, Peter Johnson (gravatar.com/delphidabbler). * * Implements class that renders active text as plain text in fixed width, word * wrapped paragraphs. @@ -15,17 +15,53 @@ interface uses - SysUtils, - ActiveText.UMain; + SysUtils, Generics.Collections, + ActiveText.UMain, + UConsts; type TActiveTextTextRenderer = class(TObject) + public + const + /// Special space character used to indicate the start of a list + /// item. + /// This special character is a necessary kludge because some + /// c odethat 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 calling + /// code doesn't convert it. + LISpacer = NBSP; // Do not localise. Must be <> #32 + /// Bullet character used when rendering unordered list items. + /// + Bullet = '*'; // Do not localise. Must be <> #32 and <> LISpacer strict private + const + IndentDelta = 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; + fListStack: TStack; + fLIStack: TStack; + fIndent: UInt16; + fInPara: Boolean; + fInListItem: Boolean; + function CanEmitInline: Boolean; + procedure AppendToPara(const AText: string); procedure InitialiseRender; procedure FinaliseRender; procedure OutputParagraph; @@ -33,32 +69,66 @@ 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; + function RenderWrapped(ActiveText: IActiveText; const PageWidth, LMargin, + ParaOffset: Cardinal; const Prefix: string = ''; + const Suffix: string = ''): 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 := not (fBlocksStack.Peek in [ekOrderedList, ekUnorderedList]); +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.Create; + fListStack := TStack.Create; + fLIStack := TStack.Create; + fIndent := 0; + fInPara := False; + fInListItem := False; end; destructor TActiveTextTextRenderer.Destroy; begin + fLIStack.Free; + fListStack.Free; + fBlocksStack.Free; fDocBuilder.Free; fParaBuilder.Free; inherited; @@ -76,11 +146,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; @@ -90,7 +182,6 @@ function TActiveTextTextRenderer.Render(ActiveText: IActiveText): string; ActionElem: IActiveTextActionElem; begin InitialiseRender; - fInBlock := False; for Elem in ActiveText do begin if Supports(Elem, IActiveTextTextElem, TextElem) then @@ -109,16 +200,82 @@ function TActiveTextTextRenderer.Render(ActiveText: IActiveText): string; procedure TActiveTextTextRenderer.RenderBlockActionElem( Elem: IActiveTextActionElem); +var + ListState: TListState; begin case Elem.State of fsOpen: begin - fInBlock := True; + fBlocksStack.Push(Elem.Kind); + case Elem.Kind of + ekPara: {Do nothing} ; + ekHeading: {Do nothing} ; + ekUnorderedList: + begin + if (fListStack.Count > 0) and (fInPara) then + OutputParagraph; + fListStack.Push(TListState.Create(lkBullet)); + Inc(fIndent, IndentDelta); + end; + ekOrderedList: + begin + if (fListStack.Count > 0) and (fInPara) then + OutputParagraph; + fListStack.Push(TListState.Create(lkNumber)); + Inc(fIndent, IndentDelta); + end; + 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: + begin + // Number list: start a new numbered item, with current number + fParaBuilder.Append(IntToStr(fListStack.Peek.ListNumber)); + fParaBuilder.Append(NBSP); + end; + lkBullet: + begin + // Bullet list: start a new bullet point + fParaBuilder.Append(Bullet + NBSP); + end; + end; + end; + end; end; fsClose: begin - OutputParagraph; - fInBlock := False; + case Elem.Kind of + ekPara: + OutputParagraph; + ekHeading: + OutputParagraph; + ekUnorderedList: + begin + OutputParagraph; + fListStack.Pop; + Dec(fIndent, IndentDelta); + end; + ekOrderedList: + begin + OutputParagraph; + fListStack.Pop; + Dec(fIndent, IndentDelta); + end; + ekListItem: + begin + OutputParagraph; + fInListItem := False; + fLIStack.Pop; + end; + end; + fBlocksStack.Pop; end; end; end; @@ -126,17 +283,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; - fParaBuilder.Append(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; + AppendToPara(TheText); end; procedure TActiveTextTextRenderer.RenderURL(Elem: IActiveTextActionElem); @@ -144,7 +311,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, ParaOffset: Cardinal; const Prefix, Suffix: string): + 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(Prefix + Render(ActiveText) + Suffix, EOL, True); + for Para in Paras do + begin + if IsListItem then + begin + Offset := -ParaOffset; + ParaIndent := CalcParaIndent + LMargin + ParaOffset; + 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/USourceGen.pas b/Src/USourceGen.pas index a8c5f1cd7..3d10b57ef 100644 --- a/Src/USourceGen.pas +++ b/Src/USourceGen.pas @@ -3,7 +3,7 @@ * 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) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2022, Peter Johnson (gravatar.com/delphidabbler). * * Implements a class that is used to generate Pascal source code containing * specified database snippets. @@ -38,10 +38,10 @@ interface TSourceComments = class(TNoConstructObject) strict private /// Formats the given comment text into lines with a fixed - /// maximum width indented by the given number of spaces on the left. - /// - class function FormatCommentLines(const Text: string; - const Indent: Cardinal): string; + /// maximum width indented by the given number of spaces on the left, + /// optionally truncated to the first paragraph. + class function FormatActiveTextCommentInner(ActiveText: IActiveText; + const Indent: Cardinal; const Truncate: Boolean): string; public /// Returns a description of the given comment style. @@ -250,7 +250,7 @@ implementation uses // Delphi - SysUtils, + SysUtils, Character, // Project ActiveText.UTextRenderer, DB.USnippetKind, UConsts, UExceptions, UPreferences, USnippetValidator, UStrUtils, UWarnings, Hiliter.UPasLexer; @@ -272,7 +272,7 @@ TRoutineFormatter = class(TNoConstructObject) /// Splits source code of a routine snippet into the head (routine /// prototype) and body. - /// TSnippet [in] Routine whose source code is to be + /// TSnippet [in3] Routine whose source code is to be /// split. /// string [out] Set to routine prototype. /// string [out] Body of routine that follows the @@ -1136,17 +1136,25 @@ class function TSourceComments.CommentStyleDesc( Result := sDescriptions[Style]; end; -class function TSourceComments.FormatCommentLines(const Text: string; - const Indent: Cardinal): string; +class function TSourceComments.FormatActiveTextCommentInner( + ActiveText: IActiveText; const Indent: Cardinal; const Truncate: Boolean): + string; var - Lines: TStringList; + Renderer: TActiveTextTextRenderer; + ProcessedActiveText: IActiveText; begin - Lines := TStringList.Create; + if Truncate then + ProcessedActiveText := ActiveText.FirstBlock + else + ProcessedActiveText := ActiveText; + Renderer := TActiveTextTextRenderer.Create; try - Lines.Text := Text; - Result := StrTrimRight(StrWrap(Lines, cLineWidth - Indent, Indent, False)); + Renderer.DisplayURLs := False; + Result := Renderer.RenderWrapped( + ProcessedActiveText, cLineWidth, Indent, Indent + ); finally - Lines.Free; + Renderer.Free; end; end; @@ -1156,7 +1164,7 @@ class function TSourceComments.FormatHeaderComments( Line: string; // loops thru each line of comments & exploded comments Lines: IStringList; // comments after exploding multiple wrapped lines const - cLinePrefix = ' * '; // prefixes each comment line + cLinePrefix = ' * '; // prefixes each header omment line begin // Only create comment if some comment text is provided if Assigned(Comments) and (Comments.Count > 0) then @@ -1182,38 +1190,24 @@ class function TSourceComments.FormatHeaderComments( class function TSourceComments.FormatSnippetComment(const Style: TCommentStyle; const TruncateComments: Boolean; const Text: IActiveText): string; -var - Renderer: TActiveTextTextRenderer; - PlainText: string; - Lines: IStringList; begin - Renderer := TActiveTextTextRenderer.Create; - try - Renderer.DisplayURLs := False; - PlainText := Renderer.Render(Text); - if TruncateComments then - begin - // use first non-empty paragraph of Text as comment - Lines := TIStringList.Create(PlainText, string(sLineBreak), False); - if Lines.Count > 0 then - PlainText := Lines[0]; - end; - case Style of - csNone: - Result := ''; - csBefore: - Result := '{' - + EOL - + FormatCommentLines(PlainText, cIndent) - + EOL - + '}'; - csAfter: - Result := FormatCommentLines( - '{' + PlainText + '}', cIndent - ); - end; - finally - Renderer.Free; + case Style of + csNone: + Result := ''; + csBefore: + Result := '{' + + EOL + + FormatActiveTextCommentInner(Text, cIndent, TruncateComments) + + EOL + + '}'; + csAfter: + Result := StrOfChar(TActiveTextTextRenderer.LISpacer, cIndent) + + '{' + + EOL + + FormatActiveTextCommentInner(Text, 2 * cIndent, TruncateComments) + + EOL + + StrOfChar(TActiveTextTextRenderer.LISpacer, cIndent) + + '}'; end; end; diff --git a/Src/UTextSnippetDoc.pas b/Src/UTextSnippetDoc.pas index ad6d1fbf7..87e72c4dd 100644 --- a/Src/UTextSnippetDoc.pas +++ b/Src/UTextSnippetDoc.pas @@ -3,7 +3,7 @@ * 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) 2009-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2009-2022, Peter Johnson (gravatar.com/delphidabbler). * * Implements a class that renders a document that describes a snippet as plain * text. @@ -39,10 +39,8 @@ TTextSnippetDoc = class(TSnippetDoc) cIndent = 2; strict private /// Renders given active text as word-wrapped paragraphs of width - /// cPageWidth and given indent. Blank lines are added between paragraphs - /// iff SpaceParas in True. - procedure RenderActiveText(ActiveText: IActiveText; const Indent: Cardinal; - const SpaceParas: Boolean); + /// cPageWidth. + procedure RenderActiveText(ActiveText: IActiveText); strict protected /// Initialises plain text document. procedure InitialiseDoc; override; @@ -88,9 +86,9 @@ implementation uses // Delphi - SysUtils, + SysUtils, Character, // Project - ActiveText.UTextRenderer, UStrUtils; + ActiveText.UTextRenderer, UConsts, UStrUtils; { TTextSnippetDoc } @@ -106,28 +104,16 @@ procedure TTextSnippetDoc.InitialiseDoc; fWriter := TStringWriter.Create; end; -procedure TTextSnippetDoc.RenderActiveText(ActiveText: IActiveText; - const Indent: Cardinal; const SpaceParas: Boolean); +procedure TTextSnippetDoc.RenderActiveText(ActiveText: IActiveText); var Renderer: TActiveTextTextRenderer; - Lines: TStringList; begin Renderer := TActiveTextTextRenderer.Create; try Renderer.DisplayURLs := True; - Lines := TStringList.Create; - try - Lines.Text := Renderer.Render(ActiveText); - fWriter.WriteLine( - StrTrimRight( - StrWrap( - Lines, cPageWidth - Indent, Indent, True - ) - ) - ); - finally - Lines.Free; - end; + fWriter.WriteLine( + Renderer.RenderWrapped(ActiveText, cPageWidth, 0, cIndent) + ); finally Renderer.Free; end; @@ -153,14 +139,14 @@ procedure TTextSnippetDoc.RenderDBInfo(const Text: string); procedure TTextSnippetDoc.RenderDescription(const Desc: IActiveText); begin fWriter.WriteLine; - RenderActiveText(Desc, 0, True); + RenderActiveText(Desc); end; procedure TTextSnippetDoc.RenderExtra(const ExtraText: IActiveText); begin Assert(not ExtraText.IsEmpty, ClassName + '.RenderExtra: ExtraText is empty'); fWriter.WriteLine; - RenderActiveText(ExtraText, 0, True); + RenderActiveText(ExtraText); end; procedure TTextSnippetDoc.RenderHeading(const Heading: string; From 96ea2bbbaf7a3816d7b89682bce290202f89cb9b Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Mon, 19 Dec 2022 03:26:35 +0000 Subject: [PATCH 151/330] Update UStrUtils tests re changes to StrWrap --- Tests/Src/DUnit/TestUStrUtils.pas | 41 +++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/Tests/Src/DUnit/TestUStrUtils.pas b/Tests/Src/DUnit/TestUStrUtils.pas index 4be2b4ea6..b540f3171 100644 --- a/Tests/Src/DUnit/TestUStrUtils.pas +++ b/Tests/Src/DUnit/TestUStrUtils.pas @@ -64,7 +64,8 @@ TTestStrUtilsRoutines = class(TTestCase) procedure TestStrJoin; procedure TestStrExplode; procedure TestStrSplit; - procedure TestStrWrap_overload1; + procedure TestStrWrap_overload1_default_param; + procedure TestStrWrap_overload1_no_default_param; procedure TestStrWrap_overload2; procedure TestStrMakeSentence; procedure TestStrIf; @@ -1037,7 +1038,7 @@ procedure TTestStrUtilsRoutines.TestStrWindowsLineBreaks; CheckEquals(#13#10#13#10#13#10, StrWindowsLineBreaks(#10#13#13#10), 'Test 8'); end; -procedure TTestStrUtilsRoutines.TestStrWrap_overload1; +procedure TTestStrUtilsRoutines.TestStrWrap_overload1_default_param; const Text = 'The quick brown fox jumped-over-the lazy dog.'; // 123456789012345678901234567890123456789012345 @@ -1073,6 +1074,42 @@ procedure TTestStrUtilsRoutines.TestStrWrap_overload1; CheckEquals(ResD, StrWrap(Text, 0, 0), 'Test 9'); end; +procedure TTestStrUtilsRoutines.TestStrWrap_overload1_no_default_param; +const + Text = 'The quick brown fox jumped-over-the lazy dog.'; + // 123456789012345678901234567890123456789012345 + // 1 2 3 4 + ResA = 'The quick' + EOL + + 'brown fox' + EOL + + 'jumped-over-the' + EOL + + 'lazy dog.'; + ResB = ' The quick' + EOL + + ' brown fox' + EOL + + ' jumped-over-the' + EOL + + ' lazy dog.'; + ResC = ' The quick' + EOL + + ' brown fox' + EOL + + ' jumped-over-the' + EOL + + ' lazy dog.'; + ResD = ' The quick' + EOL + + ' brown fox' + EOL + + ' jumped-over-the' + EOL + + ' lazy dog.'; + +begin + CheckEquals('', StrWrap('', 12, 0, 0), 'Test 1a'); + CheckEquals('', StrWrap('', 12, 4, 2), 'Test 1b'); + CheckEquals('', StrWrap('', 12, 4, -2), 'Test 1b'); + CheckEquals('X', StrWrap('X', 12, 0), 'Test 2a'); + CheckEquals(' X', StrWrap('X', 12, 4, 2), 'Test 2b'); + CheckEquals(' X', StrWrap('X', 12, 4, -2), 'Test 2c'); + CheckEquals(' X', StrWrap(' X', 12, 4, -2), 'Test 2c'); + CheckEquals(ResA, StrWrap(Text, 12, 0, 0), 'Test 3a'); + CheckEquals(ResB, StrWrap(Text, 12, 2, 0), 'Test 3b'); + CheckEquals(ResC, StrWrap(Text, 12, 2, 2), 'Test 3b'); + CheckEquals(ResD, StrWrap(Text, 12, 4, -2), 'Test 3b'); +end; + procedure TTestStrUtilsRoutines.TestStrWrap_overload2; const Para1 = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'; From 976b6e5548cecd5eb228e2636a7be601f7d45773 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 21 Dec 2022 16:00:50 +0000 Subject: [PATCH 152/330] Missing text at end of detail pane list items fixed Code added to track and detect if text is being written inside a list item and to output any inline text as HTML between the end of a nested list and the end of a list item. Such detection is then used in logic that determines when to output such orphaned text. Fixes #82 --- Src/ActiveText.UHTMLRenderer.pas | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Src/ActiveText.UHTMLRenderer.pas b/Src/ActiveText.UHTMLRenderer.pas index daa25dc0d..05e3f862f 100644 --- a/Src/ActiveText.UHTMLRenderer.pas +++ b/Src/ActiveText.UHTMLRenderer.pas @@ -3,7 +3,7 @@ * 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) 2009-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2009-2022, Peter Johnson (gravatar.com/delphidabbler). * * Provides a class that renders active text as HTML. } @@ -17,9 +17,9 @@ interface uses // Delphi - SysUtils, Graphics, Generics.Collections, + SysUtils, // Project - ActiveText.UMain, UBaseObjects, UCSSBuilder, UHTMLUtils; + ActiveText.UMain, UHTMLUtils; type @@ -65,6 +65,7 @@ TCSSStyles = class(TObject) fBuilder: TStringBuilder; fInBlock: Boolean; fTagInfoMap: TTagInfoMap; + fLINestingDepth: Cardinal; procedure InitialiseTagInfoMap; procedure InitialiseRender; procedure RenderTextElem(Elem: IActiveTextTextElem); @@ -84,11 +85,6 @@ TCSSStyles = class(TObject) implementation -uses - // Project - UColours, UCSSUtils, UFontHelper, UIStringList; - - { TActiveTextHTML } constructor TActiveTextHTML.Create; @@ -96,6 +92,7 @@ constructor TActiveTextHTML.Create; inherited Create; fCSSStyles := TCSSStyles.Create; fBuilder := TStringBuilder.Create; + fLINestingDepth := 0; InitialiseTagInfoMap; end; @@ -209,6 +206,8 @@ procedure TActiveTextHTML.RenderBlockActionElem(Elem: IActiveTextActionElem); case Elem.State of fsOpen: begin + if Elem.Kind = ekListItem then + Inc(fLINestingDepth); fBuilder.Append(MakeOpeningTag(Elem)); fInBlock := True; end; @@ -216,13 +215,15 @@ procedure TActiveTextHTML.RenderBlockActionElem(Elem: IActiveTextActionElem); begin fInBlock := False; fBuilder.AppendLine(MakeClosingTag(Elem)); + if Elem.Kind = ekListItem then + Dec(fLINestingDepth); end; end; end; procedure TActiveTextHTML.RenderInlineActionElem(Elem: IActiveTextActionElem); begin - if not fInBlock then + if not fInBlock and (fLINestingDepth = 0) then Exit; case Elem.State of fsOpen: @@ -234,7 +235,7 @@ procedure TActiveTextHTML.RenderInlineActionElem(Elem: IActiveTextActionElem); procedure TActiveTextHTML.RenderTextElem(Elem: IActiveTextTextElem); begin - if not fInBlock then + if not fInBlock and (fLINestingDepth = 0) then Exit; fBuilder.Append(THTML.Entities(Elem.Text)); end; From b8f1ceaabd706cf39009e23676d2ce623bc02717 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 21 Dec 2022 11:10:06 +0000 Subject: [PATCH 153/330] Add new NBSP constant to UConsts unit. Constant for Unicode non-breaking space character --- Src/UConsts.pas | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Src/UConsts.pas b/Src/UConsts.pas index 7715b2c0d..449090022 100644 --- a/Src/UConsts.pas +++ b/Src/UConsts.pas @@ -3,7 +3,7 @@ * 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) 2009-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2009-2022, Peter Johnson (gravatar.com/delphidabbler). * * Defines various character, string and resource id constants. } @@ -36,7 +36,8 @@ interface GT = '>'; // greater-than / closing angle bracket character LT = '<'; // less-than / opening angle bracket character - COPYRIGHT = #$00A9; + NBSP = #$00A0; // non-breaking space + COPYRIGHT = #$00A9; // copyright symbol CRLF = CR + LF; // carriage return followed by line feed EOL = CRLF; // end of line character sequence for Windows systems From a58a8e402bea6a17bc5800472cafa3c7fba60120 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Mon, 3 Apr 2023 11:56:27 +0100 Subject: [PATCH 154/330] Update 3rd party PJSysInfo unit to v5.20.0 --- Src/3rdParty/PJSysInfo.pas | 66 ++++++++++++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 13 deletions(-) diff --git a/Src/3rdParty/PJSysInfo.pas b/Src/3rdParty/PJSysInfo.pas index cf1110edc..efd2c4de6 100644 --- a/Src/3rdParty/PJSysInfo.pas +++ b/Src/3rdParty/PJSysInfo.pas @@ -1451,7 +1451,7 @@ TBuildNameMap = record // Windows 11 Dev channel releases (with version string "Dev"). // For details see https://en.wikipedia.org/wiki/Windows_11_version_history - Win11DevChannelDevBuilds: array[0..14] of Integer = ( + Win11DevChannelDevBuilds: array[0..25] of Integer = ( // pre Win 11 release (expired 2021/10/31): // 22449, 22454, 22458, 22463, // pre Win 11 release (expired 2022/09/15): @@ -1465,7 +1465,15 @@ TBuildNameMap = record // post Win 11 22H2 beta release (expiring 2023/09/15): 25182, 25188, 25193, 25197, 25201, 25206, 25211, // post Win 11 22H2 release (expiring 2023/09/15): - 25217, 25227, 25231, 25236, 25247, 25252, 25262, 25267 + 25217, 25227, 25231, 25236, 25247, 25252, 25262, 25267, 25272, 25276, 25281, + 25284, 25290, 25295, 25300, 25309, 23403, 23419, 23424 + ); + + // Preview builds of Windows 11 in the Canary Channel + // For details see https://en.wikipedia.org/wiki/Windows_11_version_history + Win11CanaryPreviewBuilds: array[0..2] of Integer = ( + // expiring 2023/09/15: + 25314, 25324, 25330 ); // Windows 11 Dev channel builds with version string "22H2" @@ -1478,7 +1486,8 @@ TBuildNameMap = record 22610, 22616 ); - Win11FutureComponentBetaChannelBuild = 22623; + Win11Feb23ComponentBetaChannelBuild = 22623; + Win11FutureComponentBetaChannelBuild = 22624; Win11FirstBuild = Win11DevBuild; // First build number of Windows 11 @@ -2141,11 +2150,11 @@ procedure InitPlatformIdEx; 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..MaxInt: + 2311, 2364, 2486, 2546, 2604, 2673, 2728, 2788 .. 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: + 2192, 2545: InternalExtraUpdateInfo := Format( 'Version 21H2 [Release Preview Channel v10.0.%d.%d]', [InternalBuildNumber, InternalRevisionNumber] @@ -2162,9 +2171,10 @@ procedure InitPlatformIdEx; // **REF5** InternalBuildNumber := Win1022H2Build; case InternalBuildNumber of - 2006, 2130, 2132, 2193, 2194, 2251, 2311, 2364..MaxInt: + 2006, 2130, 2132, 2193, 2194, 2251, 2311, 2364, 2486, 2546, + 2604, 2673, 2728, 2788 .. MaxInt: InternalExtraUpdateInfo := 'Version 22H2'; - 1865, 1889, 1949, 2075, 2301: + 1865, 1889, 1949, 2075, 2301, 2670, 2787: InternalExtraUpdateInfo := Format( 'Version 22H2 [Release Preview Channel v10.0.%d.%d]', [InternalBuildNumber, InternalRevisionNumber] @@ -2198,7 +2208,8 @@ procedure InitPlatformIdEx; 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..MaxInt: + 1098, 1100, 1165, 1219, 1281, 1335, 1455, 1516, 1574, 1641, + 1696, 1761 .. MaxInt: // Public releases of Windows 11 InternalExtraUpdateInfo := 'Version 21H2'; 51, 65, 71: @@ -2217,7 +2228,7 @@ procedure InitPlatformIdEx; + '[Beta & Release Preview Channels v10.0.%d.%d]', [InternalBuildNumber, InternalRevisionNumber] ); - 651, 706, 776, 829, 917, 1041, 1163, 1279: + 651, 706, 776, 829, 917, 1041, 1163, 1279, 1515, 1639, 1757: InternalExtraUpdateInfo := Format( 'Version 21H1 Release Preview Channel v10.0.%d.%d]', [InternalBuildNumber, InternalRevisionNumber] @@ -2234,20 +2245,22 @@ procedure InitPlatformIdEx; // **REF1** InternalBuildNumber := Win11v22H2Build; case InternalRevisionNumber of - 382, 521, 525, 608, 674, 675, 755, 819, 900, 963, 1038..MaxInt: + 382, 521, 525, 608, 674, 675, 755, 819, 900, 963, 1105, 1194, + 1265, 1344, 1413, 1485, {placeholder->}1538 .. MaxInt: InternalExtraUpdateInfo := 'Version 22H2'; 1: InternalExtraUpdateInfo := Format( 'Version 22H2 [Beta & Release Preview v10.0.%d.%d]', [InternalBuildNumber, InternalRevisionNumber] ); - 105, 169, 232, 317, 457, 607, 754, 898: + 105, 169, 232, 317, 457, 607, 754, 898, 1192, 1343, 1483: 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: + 870, 875, 885, 891, 1020, 1028, 1037, 1095, 1180, 1245, 1250, + 1255, 1325, 1391, 1465, 1470, 1537: InternalExtraUpdateInfo := Format( 'Version 22H2 [Beta v10.0.%d.%d]', [InternalBuildNumber, InternalRevisionNumber] @@ -2287,6 +2300,16 @@ procedure InitPlatformIdEx; [InternalBuildNumber, InternalRevisionNumber] ); end + else if FindBuildNumberFrom( + Win11CanaryPreviewBuilds, InternalBuildNumber + ) then + begin + // Win11 Canary Channel builds + InternalExtraUpdateInfo := Format( + 'Canary Channel v10.0.%d.%d (Dev)', + [InternalBuildNumber, InternalRevisionNumber] + ); + end else if FindBuildNumberFrom( Win11DevBetaChannels22H2Builds, InternalBuildNumber ) then @@ -2297,11 +2320,28 @@ procedure InitPlatformIdEx; [InternalBuildNumber, InternalRevisionNumber] ); end + else if IsBuildNumber(Win11Feb23ComponentBetaChannelBuild) then + begin + InternalBuildNumber := Win11Feb23ComponentBetaChannelBuild; + 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(Win11FutureComponentBetaChannelBuild) then begin InternalBuildNumber := Win11FutureComponentBetaChannelBuild; case InternalRevisionNumber of - 730, 741, 746, 870, 875, 885, 891, 1020, 1028, 1037..MaxInt: + 1391, 1465, 1470, 1537 .. MaxInt: InternalExtraUpdateInfo := Format( 'Future Component Update Beta v10.0.%d.%d', [InternalBuildNumber, InternalRevisionNumber] From 08059c3264e0591fd548fb09216e95a6e0ff50b8 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Tue, 4 Apr 2023 23:57:17 +0100 Subject: [PATCH 155/330] Update links in License.html Fix displayed URLs that didn't match actual linked URLs. Remove broken link, mark as deleted and added "[link broken]" text. --- Docs/License.html | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Docs/License.html b/Docs/License.html index d4b0f895c..66df27d97 100644 --- a/Docs/License.html +++ b/Docs/License.html @@ -5,7 +5,7 @@ * 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-2022, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2012-2023, Peter Johnson (gravatar.com/delphidabbler). * * CodeSnip license. --> @@ -1615,7 +1615,7 @@

              Toolbar Icons is made available under the terms of the MIT License. See http://toolbaricons.sourceforge.net/ for more information.

              + >https://toolbaricons.sourceforge.net/ for more information.

              Copyright © 2010 Florian Haag

              @@ -1674,12 +1674,10 @@

              • - Silk Icon Set 1.3 by Mark James: http://www.famfamfam.com/lab/icons/silk/. + Silk Icon Set 1.3 by Mark James: http://www.famfamfam.com/lab/icons/silk/ [link broken].
              • - Silk Companion 1 by Damien Guard: https://www.damieng.com/icons/silkcompanion [link broken] + Silk Companion 1 by Damien Guard: https://www.damieng.com/icons/silkcompanion [link broken].
              • Led Icon Set v1.0: http://led24.de/iconset/ [link broken]. @@ -1714,7 +1712,7 @@

              • Some program icons are based on Florian Haag's Toolbar Icons set at http://toolbaricons.sourceforge.net/. + >https://toolbaricons.sourceforge.net/.
              • Some images used in the program's Easter Egg are based on public domain From 088ad579c5d5e208932edd246caeb91a4a562e00 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 5 Apr 2023 00:19:44 +0100 Subject: [PATCH 156/330] Update copyright date in program license files --- Docs/License.html | 4 ++-- Src/Help/HTML/license.htm | 4 ++-- Src/Install/Assets/License.rtf | 2 +- Src/Res/HTML/dlg-about-program-tplt.html | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Docs/License.html b/Docs/License.html index 66df27d97..f5cae9b16 100644 --- a/Docs/License.html +++ b/Docs/License.html @@ -225,7 +225,7 @@

                Executable Program

                - DelphiDabbler CodeSnip is copyright © 2005-2022 by CodeSnip is copyright © 2005-2023 by Peter D Johnson.

                @@ -327,7 +327,7 @@

                This condition applies to all files in the Src/Res/Img/Branding directory, all of which are original - work copyright © 2012-2022 by Peter D Johnson.
                diff --git a/Src/Help/HTML/license.htm b/Src/Help/HTML/license.htm index 50c1357c2..d9a261f15 100644 --- a/Src/Help/HTML/license.htm +++ b/Src/Help/HTML/license.htm @@ -4,7 +4,7 @@ * 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-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2012-2023, Peter Johnson (gravatar.com/delphidabbler). * * Help topic containing summary of CodeSnip license. --> @@ -27,7 +27,7 @@

                Summary of End User License Agreement

                - DelphiDabbler CodeSnip is copyright © 2005-2022 by Peter D + DelphiDabbler CodeSnip is copyright © 2005-2023 by Peter D Johnson, @@ -47,7 +47,7 @@

                - DelphiDabbler CodeSnip is copyright © 2005-2020 by CodeSnip is copyright © 2005-2023 by Peter D Johnson. From e43816c6007ac3eed30d5e400f4889f278976218 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 5 Apr 2023 00:27:11 +0100 Subject: [PATCH 157/330] Fix out of range bug in UEncodings unit Bug in 1st WideCharToMultiByte call in WideCharToChar function. Fixes #97 --- Src/UEncodings.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Src/UEncodings.pas b/Src/UEncodings.pas index eb1586899..f33dad412 100644 --- a/Src/UEncodings.pas +++ b/Src/UEncodings.pas @@ -439,7 +439,7 @@ function WideCharToChar(const Source: WideChar; const CodePage: Integer; BufSize: Integer; begin BufSize := WideCharToMultiByte( - CodePage, 0, @Source, 1, @Dest[0], 0, nil, nil + CodePage, 0, @Source, 1, nil, 0, nil, nil ); SetLength(Dest, BufSize + 1); if WideCharToMultiByte( From 4111d58910abf10d41189d4fb54bdc051fbde3d6 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 21 Dec 2022 15:36:27 +0000 Subject: [PATCH 158/330] Add support for rendering REML lists as plain text ActiveText.UTextRenderer has support added to parsing and rendering REML lists and to format the resulting text with given margins and page widths. This was provided in a new TActiveTextTextRenderer.RenderWrapped method. TActiveTextTextRenderer.Render was made private since it is now only called internally. USourceGen & UTextSnippetDoc were modified to use the above new method. ** This solution is a little kludgy and relies on parsing NBSP characters emitted by TActiveTextTextRenderer.Render. This was done originally because the formatting was being done in USourceGen and UTextSnippetDoc which didn't have access to the inner workings of TActiveTextTextRenderer. The formatting code was then moved into TActiveTextTextRenderer and so there's probably a more elegant solutuion available now. Generalised TActiveTextTextRenderer.CanEmitInline to determine which elements can contain text by calling TActiveTextElemCaps rather than hard wiring the elements. --- Src/ActiveText.UTextRenderer.pas | 289 +++++++++++++++++++++++++++++-- Src/USourceGen.pas | 86 +++++---- Src/UTextSnippetDoc.pas | 36 ++-- 3 files changed, 326 insertions(+), 85 deletions(-) diff --git a/Src/ActiveText.UTextRenderer.pas b/Src/ActiveText.UTextRenderer.pas index bd658666e..c060188e6 100644 --- a/Src/ActiveText.UTextRenderer.pas +++ b/Src/ActiveText.UTextRenderer.pas @@ -3,7 +3,7 @@ * 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-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2012-2022, Peter Johnson (gravatar.com/delphidabbler). * * Implements class that renders active text as plain text in fixed width, word * wrapped paragraphs. @@ -15,17 +15,53 @@ interface uses - SysUtils, - ActiveText.UMain; + SysUtils, Generics.Collections, + ActiveText.UMain, + UConsts; type TActiveTextTextRenderer = class(TObject) + public + const + ///

                Special space character used to indicate the start of a list + /// item. + /// This special character is a necessary kludge because some + /// c odethat 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 calling + /// code doesn't convert it. + LISpacer = NBSP; // Do not localise. Must be <> #32 + /// Bullet character used when rendering unordered list items. + /// + Bullet = '*'; // Do not localise. Must be <> #32 and <> LISpacer strict private + const + IndentDelta = 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; + fListStack: TStack; + fLIStack: TStack; + fIndent: UInt16; + fInPara: Boolean; + fInListItem: Boolean; + function CanEmitInline: Boolean; + procedure AppendToPara(const AText: string); procedure InitialiseRender; procedure FinaliseRender; procedure OutputParagraph; @@ -33,32 +69,66 @@ 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; + function RenderWrapped(ActiveText: IActiveText; const PageWidth, LMargin, + ParaOffset: Cardinal; const Prefix: string = ''; + const Suffix: string = ''): 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.Create; + fListStack := TStack.Create; + fLIStack := TStack.Create; + fIndent := 0; + fInPara := False; + fInListItem := False; end; destructor TActiveTextTextRenderer.Destroy; begin + fLIStack.Free; + fListStack.Free; + fBlocksStack.Free; fDocBuilder.Free; fParaBuilder.Free; inherited; @@ -76,11 +146,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; @@ -90,7 +182,6 @@ function TActiveTextTextRenderer.Render(ActiveText: IActiveText): string; ActionElem: IActiveTextActionElem; begin InitialiseRender; - fInBlock := False; for Elem in ActiveText do begin if Supports(Elem, IActiveTextTextElem, TextElem) then @@ -109,16 +200,82 @@ function TActiveTextTextRenderer.Render(ActiveText: IActiveText): string; procedure TActiveTextTextRenderer.RenderBlockActionElem( Elem: IActiveTextActionElem); +var + ListState: TListState; begin case Elem.State of fsOpen: begin - fInBlock := True; + fBlocksStack.Push(Elem.Kind); + case Elem.Kind of + ekPara: {Do nothing} ; + ekHeading: {Do nothing} ; + ekUnorderedList: + begin + if (fListStack.Count > 0) and (fInPara) then + OutputParagraph; + fListStack.Push(TListState.Create(lkBullet)); + Inc(fIndent, IndentDelta); + end; + ekOrderedList: + begin + if (fListStack.Count > 0) and (fInPara) then + OutputParagraph; + fListStack.Push(TListState.Create(lkNumber)); + Inc(fIndent, IndentDelta); + end; + 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: + begin + // Number list: start a new numbered item, with current number + fParaBuilder.Append(IntToStr(fListStack.Peek.ListNumber)); + fParaBuilder.Append(NBSP); + end; + lkBullet: + begin + // Bullet list: start a new bullet point + fParaBuilder.Append(Bullet + NBSP); + end; + end; + end; + end; end; fsClose: begin - OutputParagraph; - fInBlock := False; + case Elem.Kind of + ekPara: + OutputParagraph; + ekHeading: + OutputParagraph; + ekUnorderedList: + begin + OutputParagraph; + fListStack.Pop; + Dec(fIndent, IndentDelta); + end; + ekOrderedList: + begin + OutputParagraph; + fListStack.Pop; + Dec(fIndent, IndentDelta); + end; + ekListItem: + begin + OutputParagraph; + fInListItem := False; + fLIStack.Pop; + end; + end; + fBlocksStack.Pop; end; end; end; @@ -126,17 +283,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; - fParaBuilder.Append(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; + AppendToPara(TheText); end; procedure TActiveTextTextRenderer.RenderURL(Elem: IActiveTextActionElem); @@ -144,7 +311,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, ParaOffset: Cardinal; const Prefix, Suffix: string): + 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(Prefix + Render(ActiveText) + Suffix, EOL, True); + for Para in Paras do + begin + if IsListItem then + begin + Offset := -ParaOffset; + ParaIndent := CalcParaIndent + LMargin + ParaOffset; + 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/USourceGen.pas b/Src/USourceGen.pas index a8c5f1cd7..3d10b57ef 100644 --- a/Src/USourceGen.pas +++ b/Src/USourceGen.pas @@ -3,7 +3,7 @@ * 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) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2022, Peter Johnson (gravatar.com/delphidabbler). * * Implements a class that is used to generate Pascal source code containing * specified database snippets. @@ -38,10 +38,10 @@ interface TSourceComments = class(TNoConstructObject) strict private /// Formats the given comment text into lines with a fixed - /// maximum width indented by the given number of spaces on the left. - /// - class function FormatCommentLines(const Text: string; - const Indent: Cardinal): string; + /// maximum width indented by the given number of spaces on the left, + /// optionally truncated to the first paragraph. + class function FormatActiveTextCommentInner(ActiveText: IActiveText; + const Indent: Cardinal; const Truncate: Boolean): string; public /// Returns a description of the given comment style. @@ -250,7 +250,7 @@ implementation uses // Delphi - SysUtils, + SysUtils, Character, // Project ActiveText.UTextRenderer, DB.USnippetKind, UConsts, UExceptions, UPreferences, USnippetValidator, UStrUtils, UWarnings, Hiliter.UPasLexer; @@ -272,7 +272,7 @@ TRoutineFormatter = class(TNoConstructObject) /// Splits source code of a routine snippet into the head (routine /// prototype) and body. - /// TSnippet [in] Routine whose source code is to be + /// TSnippet [in3] Routine whose source code is to be /// split. /// string [out] Set to routine prototype. /// string [out] Body of routine that follows the @@ -1136,17 +1136,25 @@ class function TSourceComments.CommentStyleDesc( Result := sDescriptions[Style]; end; -class function TSourceComments.FormatCommentLines(const Text: string; - const Indent: Cardinal): string; +class function TSourceComments.FormatActiveTextCommentInner( + ActiveText: IActiveText; const Indent: Cardinal; const Truncate: Boolean): + string; var - Lines: TStringList; + Renderer: TActiveTextTextRenderer; + ProcessedActiveText: IActiveText; begin - Lines := TStringList.Create; + if Truncate then + ProcessedActiveText := ActiveText.FirstBlock + else + ProcessedActiveText := ActiveText; + Renderer := TActiveTextTextRenderer.Create; try - Lines.Text := Text; - Result := StrTrimRight(StrWrap(Lines, cLineWidth - Indent, Indent, False)); + Renderer.DisplayURLs := False; + Result := Renderer.RenderWrapped( + ProcessedActiveText, cLineWidth, Indent, Indent + ); finally - Lines.Free; + Renderer.Free; end; end; @@ -1156,7 +1164,7 @@ class function TSourceComments.FormatHeaderComments( Line: string; // loops thru each line of comments & exploded comments Lines: IStringList; // comments after exploding multiple wrapped lines const - cLinePrefix = ' * '; // prefixes each comment line + cLinePrefix = ' * '; // prefixes each header omment line begin // Only create comment if some comment text is provided if Assigned(Comments) and (Comments.Count > 0) then @@ -1182,38 +1190,24 @@ class function TSourceComments.FormatHeaderComments( class function TSourceComments.FormatSnippetComment(const Style: TCommentStyle; const TruncateComments: Boolean; const Text: IActiveText): string; -var - Renderer: TActiveTextTextRenderer; - PlainText: string; - Lines: IStringList; begin - Renderer := TActiveTextTextRenderer.Create; - try - Renderer.DisplayURLs := False; - PlainText := Renderer.Render(Text); - if TruncateComments then - begin - // use first non-empty paragraph of Text as comment - Lines := TIStringList.Create(PlainText, string(sLineBreak), False); - if Lines.Count > 0 then - PlainText := Lines[0]; - end; - case Style of - csNone: - Result := ''; - csBefore: - Result := '{' - + EOL - + FormatCommentLines(PlainText, cIndent) - + EOL - + '}'; - csAfter: - Result := FormatCommentLines( - '{' + PlainText + '}', cIndent - ); - end; - finally - Renderer.Free; + case Style of + csNone: + Result := ''; + csBefore: + Result := '{' + + EOL + + FormatActiveTextCommentInner(Text, cIndent, TruncateComments) + + EOL + + '}'; + csAfter: + Result := StrOfChar(TActiveTextTextRenderer.LISpacer, cIndent) + + '{' + + EOL + + FormatActiveTextCommentInner(Text, 2 * cIndent, TruncateComments) + + EOL + + StrOfChar(TActiveTextTextRenderer.LISpacer, cIndent) + + '}'; end; end; diff --git a/Src/UTextSnippetDoc.pas b/Src/UTextSnippetDoc.pas index ad6d1fbf7..87e72c4dd 100644 --- a/Src/UTextSnippetDoc.pas +++ b/Src/UTextSnippetDoc.pas @@ -3,7 +3,7 @@ * 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) 2009-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2009-2022, Peter Johnson (gravatar.com/delphidabbler). * * Implements a class that renders a document that describes a snippet as plain * text. @@ -39,10 +39,8 @@ TTextSnippetDoc = class(TSnippetDoc) cIndent = 2; strict private /// Renders given active text as word-wrapped paragraphs of width - /// cPageWidth and given indent. Blank lines are added between paragraphs - /// iff SpaceParas in True. - procedure RenderActiveText(ActiveText: IActiveText; const Indent: Cardinal; - const SpaceParas: Boolean); + /// cPageWidth. + procedure RenderActiveText(ActiveText: IActiveText); strict protected /// Initialises plain text document. procedure InitialiseDoc; override; @@ -88,9 +86,9 @@ implementation uses // Delphi - SysUtils, + SysUtils, Character, // Project - ActiveText.UTextRenderer, UStrUtils; + ActiveText.UTextRenderer, UConsts, UStrUtils; { TTextSnippetDoc } @@ -106,28 +104,16 @@ procedure TTextSnippetDoc.InitialiseDoc; fWriter := TStringWriter.Create; end; -procedure TTextSnippetDoc.RenderActiveText(ActiveText: IActiveText; - const Indent: Cardinal; const SpaceParas: Boolean); +procedure TTextSnippetDoc.RenderActiveText(ActiveText: IActiveText); var Renderer: TActiveTextTextRenderer; - Lines: TStringList; begin Renderer := TActiveTextTextRenderer.Create; try Renderer.DisplayURLs := True; - Lines := TStringList.Create; - try - Lines.Text := Renderer.Render(ActiveText); - fWriter.WriteLine( - StrTrimRight( - StrWrap( - Lines, cPageWidth - Indent, Indent, True - ) - ) - ); - finally - Lines.Free; - end; + fWriter.WriteLine( + Renderer.RenderWrapped(ActiveText, cPageWidth, 0, cIndent) + ); finally Renderer.Free; end; @@ -153,14 +139,14 @@ procedure TTextSnippetDoc.RenderDBInfo(const Text: string); procedure TTextSnippetDoc.RenderDescription(const Desc: IActiveText); begin fWriter.WriteLine; - RenderActiveText(Desc, 0, True); + RenderActiveText(Desc); end; procedure TTextSnippetDoc.RenderExtra(const ExtraText: IActiveText); begin Assert(not ExtraText.IsEmpty, ClassName + '.RenderExtra: ExtraText is empty'); fWriter.WriteLine; - RenderActiveText(ExtraText, 0, True); + RenderActiveText(ExtraText); end; procedure TTextSnippetDoc.RenderHeading(const Heading: string; From 36bc98384821240ebe182bd5d9dbaa3aacd5fb2f Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 21 Dec 2022 16:00:50 +0000 Subject: [PATCH 159/330] Missing text at end of detail pane list items fixed Code added to track and detect if text is being written inside a list item and to output any inline text as HTML between the end of a nested list and the end of a list item. Such detection is then used in logic that determines when to output such orphaned text. Fixes #82 --- Src/ActiveText.UHTMLRenderer.pas | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Src/ActiveText.UHTMLRenderer.pas b/Src/ActiveText.UHTMLRenderer.pas index daa25dc0d..05e3f862f 100644 --- a/Src/ActiveText.UHTMLRenderer.pas +++ b/Src/ActiveText.UHTMLRenderer.pas @@ -3,7 +3,7 @@ * 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) 2009-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2009-2022, Peter Johnson (gravatar.com/delphidabbler). * * Provides a class that renders active text as HTML. } @@ -17,9 +17,9 @@ interface uses // Delphi - SysUtils, Graphics, Generics.Collections, + SysUtils, // Project - ActiveText.UMain, UBaseObjects, UCSSBuilder, UHTMLUtils; + ActiveText.UMain, UHTMLUtils; type @@ -65,6 +65,7 @@ TCSSStyles = class(TObject) fBuilder: TStringBuilder; fInBlock: Boolean; fTagInfoMap: TTagInfoMap; + fLINestingDepth: Cardinal; procedure InitialiseTagInfoMap; procedure InitialiseRender; procedure RenderTextElem(Elem: IActiveTextTextElem); @@ -84,11 +85,6 @@ TCSSStyles = class(TObject) implementation -uses - // Project - UColours, UCSSUtils, UFontHelper, UIStringList; - - { TActiveTextHTML } constructor TActiveTextHTML.Create; @@ -96,6 +92,7 @@ constructor TActiveTextHTML.Create; inherited Create; fCSSStyles := TCSSStyles.Create; fBuilder := TStringBuilder.Create; + fLINestingDepth := 0; InitialiseTagInfoMap; end; @@ -209,6 +206,8 @@ procedure TActiveTextHTML.RenderBlockActionElem(Elem: IActiveTextActionElem); case Elem.State of fsOpen: begin + if Elem.Kind = ekListItem then + Inc(fLINestingDepth); fBuilder.Append(MakeOpeningTag(Elem)); fInBlock := True; end; @@ -216,13 +215,15 @@ procedure TActiveTextHTML.RenderBlockActionElem(Elem: IActiveTextActionElem); begin fInBlock := False; fBuilder.AppendLine(MakeClosingTag(Elem)); + if Elem.Kind = ekListItem then + Dec(fLINestingDepth); end; end; end; procedure TActiveTextHTML.RenderInlineActionElem(Elem: IActiveTextActionElem); begin - if not fInBlock then + if not fInBlock and (fLINestingDepth = 0) then Exit; case Elem.State of fsOpen: @@ -234,7 +235,7 @@ procedure TActiveTextHTML.RenderInlineActionElem(Elem: IActiveTextActionElem); procedure TActiveTextHTML.RenderTextElem(Elem: IActiveTextTextElem); begin - if not fInBlock then + if not fInBlock and (fLINestingDepth = 0) then Exit; fBuilder.Append(THTML.Entities(Elem.Text)); end; From 02e85186bdc8c233c9c083a967da2ac7f5f16d38 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 6 Apr 2023 02:31:57 +0100 Subject: [PATCH 160/330] Fix active text editor re revised active text docs Modified code that converts from plain text to active text when user switched from editing plain text to editing active text. The active text document that is created from plain text is now wrapped in ekDocument elements. --- Src/FmSnippetsEditorDlg.FrActiveTextEditor.pas | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Src/FmSnippetsEditorDlg.FrActiveTextEditor.pas b/Src/FmSnippetsEditorDlg.FrActiveTextEditor.pas index 541f19416..13282fa4a 100644 --- a/Src/FmSnippetsEditorDlg.FrActiveTextEditor.pas +++ b/Src/FmSnippetsEditorDlg.FrActiveTextEditor.pas @@ -222,6 +222,7 @@ function TSnippetsActiveTextEdFrame.PlainTextToActiveText(Text: string): 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)); @@ -230,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; From 27155851e28dc52a0566d1da8e2fdcfc33dcdbc8 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 6 Apr 2023 02:35:56 +0100 Subject: [PATCH 161/330] Heavily overhaul REML writer and parser. Major changes to REML parser to: 1. correctly deal with plain text outside of any REML blocks -> such text is now included in ekBlock blocks. 2. create active text that conforms to the revised document structure and rules. REML Writer revised to handle new ekDocument and ekBlock active text elements. Neither element has any equivalent REML tag. ekDocument is simply ignored, while active text contained in ekBlock elements is written out with no surrounding block tag. --- Src/UREMLDataIO.pas | 626 +++++++++++++++++++++++++++++--------------- 1 file changed, 417 insertions(+), 209 deletions(-) diff --git a/Src/UREMLDataIO.pas b/Src/UREMLDataIO.pas index c6ae83bbc..526369a89 100644 --- a/Src/UREMLDataIO.pas +++ b/Src/UREMLDataIO.pas @@ -34,11 +34,28 @@ interface } TREMLReader = class(TInterfacedObject, IActiveTextParser) strict private - fLexer: TTaggedTextLexer; // Analyses REML markup - // Stack of tag params for use in closing tags - fParamStack: TStack; - // Stack of block level tags - fBlockTagStack: TStack; + type + TBlockTagInfo = record + strict private + var + fTag: TActiveTextActionElemKind; + fHasContent: Boolean; + public + constructor Create(ATag: TActiveTextActionElemKind); + property Tag: TActiveTextActionElemKind read fTag; + property HasContent: Boolean read fHasContent write fHasContent; + end; + var + fLexer: TTaggedTextLexer; // Analyses REML markup + // Stack of tag params for use in closing tags + fParamStack: TStack; + // Stack of block level tags with record of whether block has yet got any + // content. + fBlockTagStack: TStack; + // Stack of all compound tags + fTagStack: TStack; + // Flag indicating whether parsing an implied block + fIsImpliedBlock: Boolean; function TagInfo(const TagIdx: Integer; out TagName: string; out TagCode: Word; out IsContainer: Boolean): Boolean; {Callback that provides lexer with information about supported tags. Lexer @@ -61,6 +78,27 @@ TREMLReader = class(TInterfacedObject, IActiveTextParser) @return True if entity information was provided or False to indicate no more entities. } + /// Note that block tag at top of stack has had content written to + /// it. + procedure NoteBlockTagHasContent; + procedure StartElem(const AActiveText: IActiveText; + const AElem: TActiveTextActionElemKind); + procedure EndElem(const AActiveText: IActiveText; + const AElem: TActiveTextActionElemKind); + procedure StartInlineElem(const AActiveText: IActiveText; + const AElem: TActiveTextActionElemKind); + procedure EndInlineElem(const AActiveText: IActiveText; + const AElem: TActiveTextActionElemKind); + procedure StartBlockElem(const AActiveText: IActiveText; + const AElem: TActiveTextActionElemKind); + procedure EndBlockElem(const AActiveText: IActiveText; + const AElem: TActiveTextActionElemKind); + procedure StartImpliedBlockElem(const AActiveText: IActiveText); + procedure EndImpliedBlockElem(const AActiveText: IActiveText); + procedure WriteText(const AActiveText: IActiveText; const AText: string); + procedure ParsePlainText(const AActiveText: IActiveText); + procedure ParseStartTag(const AActiveText: IActiveText); + procedure ParseEndTag(const AActiveText: IActiveText); public constructor Create; {Class constructor. Sets up object. @@ -69,12 +107,12 @@ TREMLReader = class(TInterfacedObject, IActiveTextParser) {Class destructor. Finalises object. } { IActiveTextParser method } - procedure Parse(const Markup: string; const ActiveText: IActiveText); {Parses markup and updates active text object with details. @param Markup [in] Markup containing definition of active text. Must be in format understood by parser. @param ActiveText [in] Active text object updated by parser. } + procedure Parse(const Markup: string; const ActiveText: IActiveText); end; { @@ -295,7 +333,8 @@ constructor TREMLReader.Create; inherited Create; fLexer := TTaggedTextLexer.Create(TagInfo, EntityInfo); fParamStack := TStack.Create; - fBlockTagStack := TStack.Create; + fTagStack := TStack.Create; + fBlockTagStack := TStack.Create; end; destructor TREMLReader.Destroy; @@ -303,11 +342,80 @@ destructor TREMLReader.Destroy; } begin fBlockTagStack.Free; + fTagStack.Free; FreeAndNil(fParamStack); FreeAndNil(fLexer); inherited; end; +procedure TREMLReader.EndBlockElem(const AActiveText: IActiveText; + const AElem: TActiveTextActionElemKind); +resourcestring + // Error message + sMismatchedTag = 'Closing block tag does not match opening block tag.'; +begin + Assert(fBlockTagStack.Count > 0); + if fBlockTagStack.Peek.Tag <> AElem then + raise EActiveTextParserError.Create(sMismatchedTag); + EndElem(AActiveText, AElem); + fBlockTagStack.Pop; +end; + +procedure TREMLReader.EndElem(const AActiveText: IActiveText; + const AElem: TActiveTextActionElemKind); +var + ParamName: string; // name of a parameter + Attr: TActiveTextAttr; // attributes of tag +resourcestring + // Error message + sMismatchedTag = 'Closing tag does not match opening tag.'; +begin + Assert(fTagStack.Count > 0); + if AElem <> fTagStack.Peek then + raise EActiveTextParserError.Create(sMismatchedTag); + // Retrive any parameters and record element with any parameter + TREMLTags.LookupParamName(AElem, ParamName); + if ParamName <> '' then + begin + // We should have a param which must be stored in closing action + // element, but closing REML tags have no parameters. We solve this + // by popping the parameter value from the stack. This works because + // we use a stack for params and opening and closing tags are + // matched. + Assert(fParamStack.Count > 0); + Attr := fParamStack.Pop; + // Add closing action element + AActiveText.AddElem( + TActiveTextFactory.CreateActionElem( + AElem, TActiveTextFactory.CreateAttrs(Attr), fsClose + ) + ); + end + else + begin + // No parameter: simple add closing parameterless action element + AActiveText.AddElem( + TActiveTextFactory.CreateActionElem(AElem, fsClose) + ); + end; + // Pop tag from tag stack + fTagStack.Pop; +end; + +procedure TREMLReader.EndImpliedBlockElem(const AActiveText: IActiveText); +begin + Assert(fIsImpliedBlock); + Assert(fBlockTagStack.Peek.Tag = ekBlock); + fIsImpliedBlock := False; + EndBlockElem(AActiveText, ekBlock); +end; + +procedure TREMLReader.EndInlineElem(const AActiveText: IActiveText; + const AElem: TActiveTextActionElemKind); +begin + EndElem(AActiveText, AElem); +end; + function TREMLReader.EntityInfo(const EntityIdx: Integer; out EntityName: string; out EntityChar: Char): Boolean; {Callback that provides lexer with information about supported character @@ -326,176 +434,62 @@ function TREMLReader.EntityInfo(const EntityIdx: Integer; EntityChar := TREMLEntities.Chars[EntityIdx]; end; -procedure TREMLReader.Parse(const Markup: string; - const ActiveText: IActiveText); - {Parses markup and updates active text object with details. - @param Markup [in] Markup containing definition of active text. Must be in - format understood by parser. - @param ActiveText [in] Active text object updated by parser. - } +procedure TREMLReader.NoteBlockTagHasContent; var - ParamName: string; // name of a parameter - ParamValue: string; // value of a parameter - TagId: TActiveTextActionElemKind; // id of a tag - Attr: TActiveTextAttr; // attributes of tag - - function IsTextPermittedInParentBlock: Boolean; - begin - if fBlockTagStack.Count = 0 then - Exit(True); - Result := TActiveTextElemCaps.CanContainText(fBlockTagStack.Peek); - end; - - function IsElemPermittedParentBlock(const Elem: TActiveTextActionElemKind): - Boolean; - begin - if fBlockTagStack.Count = 0 then - Exit(TActiveTextElemCaps.IsElemPermittedInRoot(Elem)); - Result := TActiveTextElemCaps.IsRequiredParent(fBlockTagStack.Peek, Elem); - end; - - function IsElemExcluded(const Elem: TActiveTextActionElemKind): - Boolean; - begin - if fBlockTagStack.Count = 0 then - Exit(False); - Result := TActiveTextElemCaps.IsExcludedElem(fBlockTagStack.Peek, Elem); - end; + Block: TBlockTagInfo; +begin + Assert(fBlockTagStack.Count > 0); + if fBlockTagStack.Peek.HasContent then + Exit; + Block := fBlockTagStack.Pop; + Block.HasContent := True; + fBlockTagStack.Push(Block); +end; +procedure TREMLReader.Parse(const Markup: string; + const ActiveText: IActiveText); resourcestring - // Error message - sErrMissingParam = 'Expected a "%0:s" parameter value in tag "%1:s"'; - sErrNesting = 'Illegal nesting of "%0:s" tag'; - sBadParentBlock = 'Invalid parent block for tag %0:s'; - sNoTextPermitted = 'Text is not permitted in enclosing block'; - sMismatchedCloser = 'Mismatching closing block tag %0:s'; + sMismatchedTags = 'There is not a closing tag for each opening tag'; + sBadTagType = 'Unexpected tag type'; begin Assert(Assigned(ActiveText), ClassName + '.Parse: ActiveText is nil'); + + // TODO: consider changing this so document tags are written + if Markup = '' then + Exit; + fBlockTagStack.Clear; + fTagStack.Clear; + fParamStack.Clear; + fIsImpliedBlock := False; + try - // Nothing to do if there is no markup - if Markup = '' then - Exit; // Use lexer to process markup fLexer.TaggedText := Markup; - // Scan REML a token at a time + + StartBlockElem(ActiveText, ekDocument); + while fLexer.NextItem <> ttsEOF do begin case fLexer.Kind of - ttsText: - begin - if IsTextPermittedInParentBlock then - begin - // Plain text is allowed in parent block: add it - ActiveText.AddElem( - TActiveTextFactory.CreateTextElem(fLexer.PlainText) - ); - end - else - begin - // Plain text not allowed in parent block: raise exception UNLESS - // text is only white space or empty, in which case we simply ignore - // the text. This is because white space will often occur after - // end tag of enclosed blocks - if not StrIsEmpty(fLexer.PlainText, True) then - raise EActiveTextParserError.Create(sNoTextPermitted); - end - end; - + ParsePlainText(ActiveText); ttsCompoundStartTag: - begin - // Start of an action element - // Get tag id and any parameter - TagId := TActiveTextActionElemKind(fLexer.TagCode); - - // Validate tag id - if IsElemExcluded(TagId) then - raise EActiveTextParserError.CreateFmt( - sErrNesting, [fLexer.TagName] - ); - if not IsElemPermittedParentBlock(TagID) then - raise EActiveTextParserError.CreateFmt( - sBadParentBlock, [fLexer.TagName] - ); - - if TActiveTextElemCaps.DisplayStyleOf(TagId) = dsBlock then - fBlockTagStack.Push(TagId); - TREMLTags.LookupParamName(TagId, ParamName); - if ParamName <> '' then - begin - // We have a parameter: must not be empty - ParamValue := fLexer.TagParams.Values[ParamName]; - if ParamValue = '' then - raise EActiveTextParserError.CreateFmt( - sErrMissingParam, [ParamName, fLexer.TagName] - ); - // Record param for use by closing tag - Attr := TActiveTextAttr.Create(ParamName, ParamValue); - fParamStack.Push(Attr); - // Add opening action element - ActiveText.AddElem( - TActiveTextFactory.CreateActionElem( - TagId, TActiveTextFactory.CreateAttrs(Attr), fsOpen - ) - ); - end - else - begin - // No parameter: simply add opening parameterless action element - ActiveText.AddElem( - TActiveTextFactory.CreateActionElem(TagId, fsOpen) - ); - end; - end; - + ParseStartTag(ActiveText); ttsCompoundEndTag: - begin - // End of an action element - // Get elem id - TagId := TActiveTextActionElemKind(fLexer.TagCode); - - // Validate elem - if TActiveTextElemCaps.DisplayStyleOf(TagId) = dsBlock then - begin - if fBlockTagStack.Peek <> TagId then - raise EActiveTextParserError.CreateFmt( - sMismatchedCloser, [fLexer.TagName] - ); - fBlockTagStack.Pop; - end; - - // Process params - TREMLTags.LookupParamName(TagId, ParamName); - if ParamName <> '' then - begin - // We should have a param which must be stored in closing action - // element, but closing REML tags have no parameters. We solve this - // by popping the parameter value from the stack. This works because - // we use a stack for params and opening and closing tags are - // matched. - Attr := fParamStack.Pop; - // Add closing action element - ActiveText.AddElem( - TActiveTextFactory.CreateActionElem( - TagId, TActiveTextFactory.CreateAttrs(Attr), fsClose - ) - ); - end - else - begin - // No parameter: simple add closing parameterless action element - ActiveText.AddElem( - TActiveTextFactory.CreateActionElem(TagId, fsClose) - ); - end; - end; - + ParseEndTag(ActiveText); + else + raise EActiveTextParserError.Create(sBadTagType); end; end; - except + if fIsImpliedBlock then + EndImpliedBlockElem(ActiveText); + EndBlockElem(ActiveText, ekDocument); + if fBlockTagStack.Count <> 0 then + raise EActiveTextParserError.Create(sMismatchedTags); + except // Handle exceptions: convert expected exceptions to EActiveTextParserError on E: ETaggedTextLexer do raise EActiveTextParserError.Create(E); @@ -504,6 +498,177 @@ procedure TREMLReader.Parse(const Markup: string; end; end; +procedure TREMLReader.ParseEndTag(const AActiveText: IActiveText); +var + TagId: TActiveTextActionElemKind; // id of a tag +begin + Assert(flexer.Kind = ttsCompoundEndTag); + // Get tag id + TagId := TActiveTextActionElemKind(fLexer.TagCode); + + if TActiveTextElemCaps.DisplayStyleOf(TagId) = dsBlock then + begin + // Closing block tag + if fIsImpliedBlock then + // An implied block is being written: close it + EndImpliedBlockElem(AActiveText); + // End read closing block + EndBlockElem(AActiveText, TagId) + end + else // TActiveTextElemCaps.DisplayStyleOf(TagId) = dsInline + // Closing inline tag: just close it + EndInlineElem(AActiveText, TagId); +end; + +procedure TREMLReader.ParsePlainText(const AActiveText: IActiveText); +var + Text: string; +resourcestring + sNoTextPermitted = 'Text is not permitted in enclosing block'; +begin + Assert(fLexer.Kind = ttsText); + Text := fLexer.PlainText; + if TActiveTextElemCaps.CanContainText(fBlockTagStack.Peek.Tag) then + // Parent block accepts text: write it + WriteText(AActiveText, Text) + else if TActiveTextElemCaps.IsPermittedChildElem( + fBlockTagStack.Peek.Tag, ekBlock + ) then + begin + // Parent block can contain an ekBlock: + // create block if text is not strictly empty string, and add text to it + if not StrIsEmpty(Text) then + begin + StartImpliedBlockElem(AActiveText); + WriteText(AActiveText, Text); + end; + end + else if not StrIsEmpty(Text, True) then + // Unless text is just white space, report an error. We allow white space + // since there may be white space between tags. If there is white space we + // do nothing - we don't want to write white space. + raise EActiveTextParserError.Create(sNoTextPermitted); +end; + +procedure TREMLReader.ParseStartTag(const AActiveText: IActiveText); +var + TagId: TActiveTextActionElemKind; // id of a tag +resourcestring + // Error message + sErrMissingParam = 'Expected a "%0:s" parameter value in tag "%1:s"'; + sErrNesting = 'Illegal nesting of "%0:s" tag'; + sBadParentBlock = 'Invalid parent block for tag %0:s'; + sNoTextPermitted = 'Text is not permitted in enclosing block'; + sMismatchedCloser = 'Mismatching closing block tag %0:s'; + sErrDocEndExpected = 'End of document expected'; +begin + Assert(fLexer.Kind = ttsCompoundStartTag); + // Get tag id + TagId := TActiveTextActionElemKind(fLexer.TagCode); + if TActiveTextElemCaps.DisplayStyleOf(TagId) = dsBlock then + begin + // Opening block tag found + // If writing an implied block, close the block before processing new block + if fIsImpliedBlock then + EndImpliedBlockElem(AActiveText); + // Output block tag if it is valid within parent block, else error + if TActiveTextElemCaps.IsPermittedChildElem( + fBlockTagStack.Peek.Tag, TagId + ) then + StartBlockElem(AActiveText, TagId) + else + raise EActiveTextParserError.CreateFmt(sBadParentBlock, [fLexer.TagName]); + end + else // TActiveTextElemCaps.DisplayStyleOf(TagId) = dsInline + begin + // Opeing inline tag found + if TActiveTextElemCaps.IsPermittedChildElem( + fBlockTagStack.Peek.Tag, TagId + ) then + // Tag is permitted within parent block: output it + StartInlineElem(AActiveText, TagId) + else if + TActiveTextElemCaps.IsPermittedChildElem( + fBlockTagStack.Peek.Tag, ekBlock + ) and + TActiveTextElemCaps.IsPermittedChildElem(ekBlock, TagId) then + begin + // Tag not directly permitted, but we can create an implied block iff: + // 1. parent block permits an ekBlock child element + // 2. ekBlock permits current tag as child element + StartImpliedBlockElem(AActiveText); + StartInlineElem(AActiveText, TagId); + end + else + // Tag not permitted in parent block: error + raise EActiveTextParserError.CreateFmt(sBadParentBlock, [fLexer.TagName]); + end; +end; + +procedure TREMLReader.StartBlockElem(const AActiveText: IActiveText; + const AElem: TActiveTextActionElemKind); +begin + Assert((TActiveTextElemCaps.DisplayStyleOf(AElem) = dsBlock)); + StartElem(AActiveText, AElem); + fBlockTagStack.Push(TBlockTagInfo.Create(AElem)); +end; + +procedure TREMLReader.StartElem(const AActiveText: IActiveText; + const AElem: TActiveTextActionElemKind); +var + ParamName: string; // name of a parameter + ParamValue: string; // value of a parameter + Attr: TActiveTextAttr; // attributes of tag +resourcestring + // Error message + sErrMissingParam = 'Expected a "%0:s" parameter value in tag "%1:s"'; +begin + // Find any parameters and record element with any parameter + TREMLTags.LookupParamName(AElem, ParamName); + if ParamName <> '' then + begin + // We have a parameter: must not be empty + ParamValue := fLexer.TagParams.Values[ParamName]; + if ParamValue = '' then + raise EActiveTextParserError.CreateFmt( + sErrMissingParam, [ParamName, fLexer.TagName] + ); + // Record param for use by closing tag + Attr := TActiveTextAttr.Create(ParamName, ParamValue); + fParamStack.Push(Attr); + // Add opening action element + AActiveText.AddElem( + TActiveTextFactory.CreateActionElem( + AElem, TActiveTextFactory.CreateAttrs(Attr), fsOpen + ) + ); + end + else + begin + // No parameter: simply add opening parameterless opening action element + AActiveText.AddElem( + TActiveTextFactory.CreateActionElem(AElem, fsOpen) + ); + end; + // Push tag onto tag stack + fTagStack.Push(AElem); +end; + +procedure TREMLReader.StartImpliedBlockElem(const AActiveText: IActiveText); +begin + Assert(not fIsImpliedBlock); + StartBlockElem(AActiveText, ekBlock); + fIsImpliedBlock := True; +end; + +procedure TREMLReader.StartInlineElem(const AActiveText: IActiveText; + const AElem: TActiveTextActionElemKind); +begin + Assert(TActiveTextElemCaps.DisplayStyleOf(AElem) = dsInline); + StartElem(AActivetext, AElem); + NoteBlockTagHasContent; +end; + function TREMLReader.TagInfo(const TagIdx: Integer; out TagName: string; out TagCode: Word; out IsContainer: Boolean): Boolean; {Callback that provides lexer with information about supported tags. Lexer @@ -525,6 +690,25 @@ function TREMLReader.TagInfo(const TagIdx: Integer; out TagName: string; end; end; +procedure TREMLReader.WriteText(const AActiveText: IActiveText; + const AText: string); +begin + // Don't write anything if text is strictly empty string + if not StrIsEmpty(AText) then + begin + AActiveText.AddElem(TActiveTextFactory.CreateTextElem(AText)); + NoteBlockTagHasContent; + end; +end; + +{ TREMLReader.TBlockTagInfo } + +constructor TREMLReader.TBlockTagInfo.Create(ATag: TActiveTextActionElemKind); +begin + fTag := ATag; + fHasContent := False; +end; + { TREMLWriter } constructor TREMLWriter.InternalCreate; @@ -549,30 +733,33 @@ class function TREMLWriter.Render(const ActiveText: IActiveText): string; SrcLine: string; DestLines: IStringList; DestLine: string; + RW: TREMLWriter; begin - with InternalCreate do - try - Text := ''; - fLevel := 0; - for Elem in ActiveText do - begin - if Supports(Elem, IActiveTextTextElem, TextElem) then - 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; - Result := DestLines.GetText(EOL, False); - finally - Free; + if ActiveText.IsEmpty then + Exit(''); + RW := TREMLWriter.InternalCreate; + try + Text := ''; + RW.fLevel := 0; + for Elem in ActiveText do + begin + if Supports(Elem, IActiveTextTextElem, TextElem) then + Text := Text + RW.RenderText(TextElem) + else if Supports(Elem, IActiveTextActionElem, TagElem) then + Text := Text + RW.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; + Result := DestLines.GetText(EOL, False); + finally + RW.Free; + end; end; function TREMLWriter.RenderTag( @@ -582,54 +769,72 @@ function TREMLWriter.RenderTag( @return Required REML tag. } var - TagName: string; // name of tag + TagName: string; // name of tag ParamName: string; // name of any parameter begin - if not TREMLTags.LookupTagName(TagElem.Kind, TagName) then - raise EBug.CreateFmt('%s.RenderTag: Invalid REML tag id', [ClassName]); + TREMLTags.LookupTagName(TagElem.Kind, TagName); Result := ''; TREMLTags.LookupParamName(TagElem.Kind, ParamName); case TagElem.State of fsClose: begin // closing tag - Result := Format('', [TagName]); - if TActiveTextElemCaps.DisplayStyleOf(TagElem.Kind) = dsBlock then + if TagName <> '' then begin - Dec(fLevel); - Result := EOL + StrOfSpaces(IndentMult * fLevel) + Result + EOL; + Result := Format('', [TagName]); + if TActiveTextElemCaps.DisplayStyleOf(TagElem.Kind) = dsBlock then + begin + Dec(fLevel); + Result := EOL + StrOfSpaces(IndentMult * fLevel) + Result + EOL; + fIsStartOfTextLine := True; + end; + end + else + begin + Result := ''; fIsStartOfTextLine := True; end; end; fsOpen: begin // opening tag: may have a parameter - if ParamName ='' then - Result := Format('<%s>', [TagName]) - else - // have a parameter: value must be safely encoded - Result := Format( - '<%0:s %1:s="%2:s">', - [ - TagName, - ParamName, - TextToREMLText(TagElem.Attrs[TActiveTextAttrNames.Link_URL]) - ] - ); - 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 + if TagName <> '' then begin - if fIsStartOfTextLine then + if ParamName ='' then + Result := Format('<%s>', [TagName]) + else + { TODO: revise to not assume parameter must be Link URL } + // have a parameter: value must be safely encoded + Result := Format( + '<%0:s %1:s="%2:s">', + [ + TagName, + ParamName, + TextToREMLText(TagElem.Attrs[TActiveTextAttrNames.Link_URL]) + ] + ); + 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 - Result := StrOfSpaces(IndentMult * fLevel) + Result; - fIsStartOfTextLine := False; + if fIsStartOfTextLine then + begin + Result := StrOfSpaces(IndentMult * fLevel) + Result; + fIsStartOfTextLine := False; + end; end; end; + end + else + begin + if TagElem.Kind = ekBlock then + fIsStartOfTextLine := True; + // ekDocument is a no-op and there should be no other elems here + Result := ''; end; end; end; @@ -695,6 +900,9 @@ function TREMLWriter.TextToREMLText(const Text: string): string; fTagMap[8] := TREMLTag.Create(ekUnorderedList, 'ul'); fTagMap[9] := TREMLTag.Create(ekOrderedList, 'ol'); fTagMap[10] := TREMLTag.Create(ekListItem, 'li'); + // NOTE: ekBlock and ekDocument are not used REML + // content of ekBlock is rendered as text outside any block + // content of ekDocument is rendered without outputing a tag end; class destructor TREMLTags.Destroy; From ac2f1388d35dd4a5e70195a3a6ce39941ab2631e Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 6 Apr 2023 02:42:26 +0100 Subject: [PATCH 162/330] Major overhaul of active text HTML renderer Code now based in part on the REML active text writer. The REML based code was simplified due to the fact that the HTML renderer has tags that match the ekBlock and ekDocument elements, whereas REML doesn't. HTML code is now indented to highlight the document structure (although this doesn't matter really since the code is never displayed to the user). --- Src/ActiveText.UHTMLRenderer.pas | 151 ++++++++++++++++--------------- 1 file changed, 79 insertions(+), 72 deletions(-) diff --git a/Src/ActiveText.UHTMLRenderer.pas b/Src/ActiveText.UHTMLRenderer.pas index 05e3f862f..8a00367b4 100644 --- a/Src/ActiveText.UHTMLRenderer.pas +++ b/Src/ActiveText.UHTMLRenderer.pas @@ -47,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; @@ -55,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; @@ -63,27 +61,29 @@ TCSSStyles = class(TObject) var fCSSStyles: TCSSStyles; fBuilder: TStringBuilder; - fInBlock: Boolean; + fLevel: Integer; fTagInfoMap: TTagInfoMap; + fIsStartOfTextLine: Boolean; fLINestingDepth: Cardinal; + 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; destructor Destroy; override; function Render(ActiveText: IActiveText): string; - property Styles: TCSSStyles read fCSSStyles; end; implementation +uses + UConsts, UIStringList, UStrUtils; + { TActiveTextHTML } @@ -107,22 +107,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; @@ -131,7 +115,10 @@ procedure TActiveTextHTML.InitialiseTagInfoMap; ElemKind: TActiveTextActionElemKind; const Tags: array[TActiveTextActionElemKind] of string = ( - 'a', 'strong', 'em', 'var', 'p', 'span', 'h2', 'code', 'ul', 'ol', 'li' + '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 @@ -178,66 +165,84 @@ function TActiveTextHTML.MakeOpeningTag(const Elem: IActiveTextActionElem): 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 ActiveText.IsEmpty 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 TActiveTextElemCaps.DisplayStyleOf(ActionElem.Kind) = dsBlock then - RenderBlockActionElem(ActionElem) - else - RenderInlineActionElem(ActionElem); - end; + Text := Text + RenderText(TextElem) + else if Supports(Elem, IActiveTextActionElem, TagElem) then + Text := Text + RenderTag(TagElem); end; - FinaliseRender; - Result := fBuilder.ToString; + 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; + 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 - if Elem.Kind = ekListItem then - Inc(fLINestingDepth); - fBuilder.Append(MakeOpeningTag(Elem)); - fInBlock := True; - end; + Result := ''; + case TagElem.State of fsClose: begin - fInBlock := False; - fBuilder.AppendLine(MakeClosingTag(Elem)); - if Elem.Kind = ekListItem then - Dec(fLINestingDepth); + 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 and (fLINestingDepth = 0) 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 and (fLINestingDepth = 0) then - Exit; - fBuilder.Append(THTML.Entities(Elem.Text)); + if fIsStartOfTextLine then + begin + Result := StrOfSpaces(IndentMult * fLevel); + fIsStartOfTextLine := False; + end + else + Result := ''; + Result := Result + THTML.Entities(TextElem.Text); end; { TActiveTextHTML.TCSSStyles } @@ -245,13 +250,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]); From c34fb83f2f92ce185585debec1eee02c03677175 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 6 Apr 2023 02:23:54 +0100 Subject: [PATCH 163/330] Overhaul ActiveText.UMain unit Add new action elements: ekDocument - wrapper for whole active text document ekBlock - generic block element Changed rules of which elements can be permitted as as children of other elements. The new action elements help to simplify the rules. In turn, the simplification of rules makes writing parsers and renderers easier. Revise IActiveText & TActiveText Add new IActiveText.IsValidActiveTextDocument method with implementation in TActiveText Rewrite TActiveText.Append to produce a valid active text document topped and tailed with ekDocumnent elements --- Src/ActiveText.UMain.pas | 298 ++++++++++++++++++++++++++++----------- 1 file changed, 213 insertions(+), 85 deletions(-) diff --git a/Src/ActiveText.UMain.pas b/Src/ActiveText.UMain.pas index 8d6e54602..648420fee 100644 --- a/Src/ActiveText.UMain.pas +++ b/Src/ActiveText.UMain.pas @@ -124,13 +124,15 @@ TActiveTextAttrNames = record ekStrong, // text formatted as strong (inline) ekEm, // text formatted as emphasised (inline) ekVar, // text formatted as variable (inline) - ekPara, // delimits a paragraph (block level) + 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 level) - ekOrderedList, // container for ordered list (block level) - ekListItem // list item (block level) + 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 @@ -175,6 +177,16 @@ TActiveTextAttrNames = record /// Appends elements from another given active text object to the /// current object. procedure Append(const ActiveText: IActiveText); + /// Returns a new IActiveText instance containing just the first + /// block of the current object. + /// + /// 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. + /// If the current object is empty then an empty object is returned. + /// + /// + function FirstBlock: IActiveText; /// Checks if the active text object contains any elements. /// function IsEmpty: Boolean; @@ -184,6 +196,11 @@ TActiveTextAttrNames = record /// elements except for "para". This can rendered in plain text with no /// loss of formatting. function IsPlainText: Boolean; + /// Checks if the active text object is a valid active text + /// document. + /// A valid document is either empty or it is surrounded by + /// matching ekDocument elements. + function IsValidActiveTextDocument: Boolean; /// Returns element at given index in active text object's element /// list. function GetElem(Idx: Integer): IActiveTextElem; @@ -283,22 +300,28 @@ TCaps = record var /// Determines how element is to be displayed. DisplayStyle: TActiveTextDisplayStyle; - /// Set of elements that may not occur inside the element. - /// - Exclusions: TActiveTextActionElemKinds; - /// Set of elements that are permitted as parents of the - /// element. - /// An empty set is taken to mean any element is permitted. - /// - RequiredParents: TActiveTextActionElemKinds; /// Specifies whether plain text can be contained within the /// element. PermitsText: Boolean; + /// Specifies the elements that are permitted as child + /// elements of this element. + PermittedChildElems: TActiveTextActionElemKinds; end; const /// Set of block level elements. BlockElems = [ - ekPara, ekHeading, ekUnorderedList, ekOrderedList, ekListItem + ekPara, ekHeading, ekUnorderedList, ekOrderedList, ekListItem, + ekBlock, ekDocument + ]; + /// Set of block level elements that can directly contain text + /// and inline elements. + TextContentBlocks = [ + ekPara, ekHeading, ekBlock + ]; + /// Set of block level elements that can contain only blocks + /// that are not container blocks. + ContainerBlocks = [ + ekDocument, ekListItem ]; /// Set of inline elements. InlineElems = [ @@ -313,90 +336,94 @@ TCaps = record // ekLink // may contain any inline elements but no block elements DisplayStyle: dsInline; - Exclusions: BlockElems; - RequiredParents: []; PermitsText: True; + PermittedChildElems: InlineElems - [ekLink]; ), ( // ekStrong // may contain any inline elements but no block elements DisplayStyle: dsInline; - Exclusions: BlockElems; - RequiredParents: []; PermitsText: True; + PermittedChildElems: InlineElems; ), ( // ekEm // may contain any inline elements but no block elements DisplayStyle: dsInline; - Exclusions: BlockElems; - RequiredParents: []; PermitsText: True; + PermittedChildElems: InlineElems; ), ( // ekVar // may contain any inline elements but no block elements DisplayStyle: dsInline; - Exclusions: BlockElems; - RequiredParents: []; PermitsText: True; + PermittedChildElems: InlineElems; ), ( // ekPara // may contain any inline elements but no block elements DisplayStyle: dsBlock; - Exclusions: BlockElems; - RequiredParents: []; PermitsText: True; + PermittedChildElems: InlineElems; ), ( // ekWarning // may contain any inline elements but no block elements DisplayStyle: dsInline; - Exclusions: BlockElems; - RequiredParents: []; PermitsText: True; + PermittedChildElems: InlineElems; ), ( // ekHeading // may contain any inline elements but no block elements DisplayStyle: dsBlock; - Exclusions: BlockElems; - RequiredParents: []; PermitsText: True; + PermittedChildElems: InlineElems; ), ( // ekMono // may contain any inline elements but no block elements DisplayStyle: dsInline; - Exclusions: BlockElems; - RequiredParents: []; PermitsText: True; + PermittedChildElems: InlineElems; ), ( // ekUnorderedList // may contain only list item elements DisplayStyle: dsBlock; - Exclusions: AllElems - [ekListItem]; - RequiredParents: []; - PermitsText: False + PermitsText: False; + PermittedChildElems: [ekListItem]; ), ( // ekOrderedList // may contain only list item elements DisplayStyle: dsBlock; - Exclusions: AllElems - [ekListItem]; - RequiredParents: []; PermitsText: False; + PermittedChildElems: [ekListItem]; ), ( // ekListItem - // may contain any inline or block elements except another list - // item + // 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; - Exclusions: [ekListItem]; - RequiredParents: [ekOrderedList, ekUnorderedList]; 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 @@ -406,24 +433,10 @@ TCaps = record /// Checks whether the given element can contain text. class function CanContainText(const Elem: TActiveTextActionElemKind): Boolean; static; - /// Checks whether the given Parent element can contain the given - /// Child element. - class function CanContainElem( - const Parent, Child: TActiveTextActionElemKind): Boolean; static; - /// Checks whether the given Parent element is required as a - /// parent of the given Child element. - class function IsRequiredParent( - const Parent, Child: TActiveTextActionElemKind): Boolean; static; - /// Checks whether the given element is permitted in the root of - /// an active text document, i.e. outside any other block level element. - /// - class function IsElemPermittedInRoot(const Elem: TActiveTextActionElemKind): - Boolean; static; - /// Checks whether the given child element is excluded from being - /// a child of the given parent element. - class function IsExcludedElem( + /// Checks whether the given child element is permitted as a child + /// of the given parent element. + class function IsPermittedChildElem( const Parent, Child: TActiveTextActionElemKind): Boolean; static; - end; @@ -434,7 +447,10 @@ implementation // Delphi SysUtils, // Project - IntfCommon; + IntfCommon, + UConsts, + UStrUtils, + UUtils; type @@ -474,6 +490,17 @@ TActiveText = class(TInterfacedObject, /// /// Method of IActiveText. procedure Append(const ActiveText: IActiveText); + /// Returns a new IActiveText instance containing just the first + /// block of the current object. + /// + /// 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. + /// If the current object is empty then an empty object is returned. + /// + /// Method of IActiveText. + /// + function FirstBlock: IActiveText; /// Checks if the element list is empty. /// Method of IActiveText. function IsEmpty: Boolean; @@ -486,6 +513,14 @@ TActiveText = class(TInterfacedObject, /// Method of IActiveText. /// function IsPlainText: Boolean; + /// Checks if the active text object is a valid active text + /// document. + /// + /// A valid document is either empty or it is surrounded by matching + /// ekDocument elements. + /// Method of IActiveText. + /// + function IsValidActiveTextDocument: Boolean; /// Returns element at given index in element list. /// Method of IActiveText. function GetElem(Idx: Integer): IActiveTextElem; @@ -681,15 +716,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); @@ -719,6 +782,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; @@ -747,12 +882,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 [ekPara, 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; @@ -773,7 +921,7 @@ function TActiveText.ToString: string; // 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; @@ -899,12 +1047,6 @@ function TActiveTextAttrs.GetEnumerator: TEnumerator>; { TActiveTextElemCapsMap } -class function TActiveTextElemCaps.CanContainElem(const Parent, - Child: TActiveTextActionElemKind): Boolean; -begin - Result := not (Child in Map[Parent].Exclusions); -end; - class function TActiveTextElemCaps.CanContainText( const Elem: TActiveTextActionElemKind): Boolean; begin @@ -917,24 +1059,10 @@ class function TActiveTextElemCaps.DisplayStyleOf( Result := Map[Elem].DisplayStyle; end; -class function TActiveTextElemCaps.IsElemPermittedInRoot( - const Elem: TActiveTextActionElemKind): Boolean; -begin - Result := Map[Elem].RequiredParents = []; -end; - -class function TActiveTextElemCaps.IsExcludedElem(const Parent, - Child: TActiveTextActionElemKind): Boolean; -begin - Result := Child in Map[Parent].Exclusions; -end; - -class function TActiveTextElemCaps.IsRequiredParent( +class function TActiveTextElemCaps.IsPermittedChildElem( const Parent, Child: TActiveTextActionElemKind): Boolean; begin - if Map[Child].RequiredParents = [] then - Exit(True); - Result := Parent in Map[Child].RequiredParents; + Result := Child in Map[Parent].PermittedChildElems; end; end. From 66690fa86fa918d3a074a40576ff660daa12837a Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 6 Apr 2023 02:29:39 +0100 Subject: [PATCH 164/330] Update TActiveTextValidator and private TErrorInfo Validator was updated to check that any non-empty active text is topped and tailed by ekDocument elements. Rewrite TActiveTextValidator.ValidateDocumentStructure to call into IActiveText.IsValidActiveTextDocument to do the heavy lifting. Removed unused Element field from TErrorInfo private advanced record of TActiveTextValidator. Modified constructor and calls to it accordingly. --- Src/ActiveText.UValidator.pas | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/Src/ActiveText.UValidator.pas b/Src/ActiveText.UValidator.pas index 898fcc265..96aa2e77a 100644 --- a/Src/ActiveText.UValidator.pas +++ b/Src/ActiveText.UValidator.pas @@ -36,16 +36,11 @@ TErrorInfo = record public /// Error code. Code: TErrorCode; - /// Reference to element causing problem. - /// May be nil if error doesn't relate to an element. - /// - Element: IActiveTextElem; /// Description of error. Description: string; /// Constructs a record. Sets fields from parameter values. /// - constructor Create(const ACode: TErrorCode; AElement: IActiveTextElem; - const ADescription: string); overload; + constructor Create(const ACode: TErrorCode; const ADescription: string); end; strict private /// Validates given link element. @@ -56,6 +51,14 @@ TErrorInfo = record /// Boolean. True on success or False on failure. class function ValidateLink(LinkElem: IActiveTextActionElem; out ErrInfo: TErrorInfo): Boolean; static; + /// Validates document structure. + /// IActiveText [in] Active text to be validated. + /// + /// TErrorInfo [out] Contains error information if + /// validation fails. Undefined if validation succeeds. + /// Boolean. True on success or False on failure. + class function ValidateDocumentStructure(ActiveText: IActiveText; + out ErrInfo: TErrorInfo): Boolean; static; public /// Validates given active text. /// IActiveText [in] Active text to be validated. @@ -92,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 @@ -115,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 @@ -150,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; @@ -160,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; From 9b947edb67f045ed898bbde4e29a15bed5c6939c Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 6 Apr 2023 17:28:48 +0100 Subject: [PATCH 165/330] Heavily revise USnippetExtraHelper unit 1. Moved class that parses old style credits text into active text from USnippetCreditsParser into the TSnippetExtraHelper.TCreditsParser private class in USnippetExtraHelper. 2. Removed the now redundant USnippetCreditsParser unit from project. 3. Modify methods of TSnippetExtraHelper that create active text documents to embed the active text between matched ekDocument elements. 4. Rewrote TSnippetExtraHelper.BuildActiveText to simply hand off the work by calling to the TActiveTextFactory.CreateActiveText parser overload, passing the REML parser. --- Src/CodeSnip.dpr | 1 - Src/CodeSnip.dproj | 1 - Src/USnippetCreditsParser.pas | 166 ------------------------ Src/USnippetExtraHelper.pas | 235 +++++++++++++++++++++------------- 4 files changed, 143 insertions(+), 260 deletions(-) delete mode 100644 Src/USnippetCreditsParser.pas diff --git a/Src/CodeSnip.dpr b/Src/CodeSnip.dpr index e12778a39..60436f1a5 100644 --- a/Src/CodeSnip.dpr +++ b/Src/CodeSnip.dpr @@ -317,7 +317,6 @@ uses 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', diff --git a/Src/CodeSnip.dproj b/Src/CodeSnip.dproj index 14d04cbd9..af19fb220 100644 --- a/Src/CodeSnip.dproj +++ b/Src/CodeSnip.dproj @@ -519,7 +519,6 @@ - diff --git a/Src/USnippetCreditsParser.pas b/Src/USnippetCreditsParser.pas deleted file mode 100644 index 07e784073..000000000 --- a/Src/USnippetCreditsParser.pas +++ /dev/null @@ -1,166 +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 https://mozilla.org/MPL/2.0/ - * - * Copyright (C) 2008-2021, Peter Johnson (gravatar.com/delphidabbler). - * - * Provides an implementation of IActiveTextParser that can parse the markup - * used in Credits elements of data files and convert the markup into an active - * text object. -} - - -unit USnippetCreditsParser; - - -interface - - -uses - // Project - ActiveText.UMain; - - -type - - /// Class that parses markup used in Credits element read from - /// snippets data files. Markup is translated into active text. - /// The Credits element may occur in main database files and v1 of - /// the user database and export files. - TSnippetCreditsParser = class(TInterfacedObject, IActiveTextParser) - strict private - var - /// URL to be used in any link contained in markup. - fURL: string; - public - /// Object constructor. Sets up object. - /// string [in] URL to be used in any hyperlinks defined - /// by Credit markup. - constructor Create(const URL: string); - /// Parses markup and updates active text object. - /// string [in] Markup containing definition of active - /// text. Must be valid Credits element markup. - /// IActiveText [in] Active text object updated by - /// parser. - /// Implements IActiveTextParser.Parse. - procedure Parse(const Markup: string; const ActiveText: IActiveText); - end; - - -implementation - - -{ - About the "Credits" Markup - -------------------------- - The markup is simple. It is just plain text with at most one group of text - delimited by '[' and ']' characters. The text enclosed in brackets represents - a hyperlink. The destination URL of the hyperlink is given by the URL - parameter passed to the constructor. - - Examples: - "Some markup without a link." - "Some markup with a [link]." -} - - -uses - // Project - UStrUtils; - - -{ TSnippetCreditsParser } - -constructor TSnippetCreditsParser.Create(const URL: string); -begin - inherited Create; - fURL := URL; -end; - -procedure TSnippetCreditsParser.Parse(const Markup: string; - const ActiveText: IActiveText); -const - cOpenBracket = '['; // open bracket character that starts a link - cCloseBracket = ']'; // close bracket character that ends a link -resourcestring - // Error messages - sUnexpectedCloser = 'Unexpected closing bracket found'; - sUnterminatedLink = 'Unterminated link'; - sEmptyLink = 'Empty link definition'; - sWrongBracketOrder = 'Close bracket preceeds link open bracket'; - sMultipleOpeners = 'More than one open bracket is present'; - sMultipleClosers = 'More than one close bracket is present'; - sNoURL = 'No URL specified'; -var - OpenBracketPos: Integer; // position of opening bracket in markup - CloseBracketPos: Integer; // position of closing bracket in markup - Prefix, Postfix: string; // text before and after link (can be empty) - LinkText: string; // link text -begin - // Find open and closing brackets that delimit link text - OpenBracketPos := StrPos(cOpenBracket, Markup); - CloseBracketPos := StrPos(cCloseBracket, Markup); - if OpenBracketPos = 0 then - begin - // No links: plain text only - // check for errors - if CloseBracketPos > 0 then - raise EActiveTextParserError.Create(sUnexpectedCloser); - // record text element - ActiveText.AddElem(TActiveTextFactory.CreateTextElem(Markup)); - end - else - begin - // We have a potential link - // check for errors - if CloseBracketPos = 0 then - raise EActiveTextParserError.Create(sUnterminatedLink); - if CloseBracketPos = OpenBracketPos + 1 then - raise EActiveTextParserError.Create(sEmptyLink); - if CloseBracketPos < OpenBracketPos then - raise EActiveTextParserError.Create(sWrongBracketOrder); - if StrCountDelims(cOpenBracket, Markup) > 1 then - raise EActiveTextParserError.Create(sMultipleOpeners); - if StrCountDelims(cCloseBracket, Markup) > 1 then - raise EActiveTextParserError.Create(sMultipleClosers); - // must have a URL - if fURL = '' then - raise EActiveTextParserError.Create(sNoURL); - // get the various components - LinkText := StrSlice( - Markup, OpenBracketPos + 1, CloseBracketPos - OpenBracketPos - 1 - ); - Assert(LinkText <> '', - ClassName + '.Parse: Link text is '' but has passed check'); - Prefix := StrSliceLeft(Markup, OpenBracketPos - 1); - Postfix := StrSliceRight(Markup, Length(Markup) - CloseBracketPos); - // record the elements - if Prefix <> '' then - ActiveText.AddElem(TActiveTextFactory.CreateTextElem(Prefix)); - ActiveText.AddElem( - TActiveTextFactory.CreateActionElem( - ekLink, - TActiveTextFactory.CreateAttrs( - TActiveTextAttr.Create(TActiveTextAttrNames.Link_URL, fURL) - ), - fsOpen - ) - ); - ActiveText.AddElem(TActiveTextFactory.CreateTextElem(LinkText)); - ActiveText.AddElem( - TActiveTextFactory.CreateActionElem( - ekLink, - TActiveTextFactory.CreateAttrs( - TActiveTextAttr.Create(TActiveTextAttrNames.Link_URL, fURL) - ), - fsClose - ) - ); - if Postfix <> '' then - ActiveText.AddElem(TActiveTextFactory.CreateTextElem(Postfix)); - end; -end; - -end. - diff --git a/Src/USnippetExtraHelper.pas b/Src/USnippetExtraHelper.pas index fafad6eea..f813d543a 100644 --- a/Src/USnippetExtraHelper.pas +++ b/Src/USnippetExtraHelper.pas @@ -29,11 +29,52 @@ interface text and vice versa. } TSnippetExtraHelper = class(TNoConstructObject) + strict private + type + /// Class that parses markup used in Credits element read from + /// snippets data files. Markup is translated into active text. + /// + /// Generated active text IS NOT embedded in an ekDocument block. + /// + /// The Credits element may occur in main database files and v1 of + /// the user database and export files. + /// Credits markup is simple. It is just plain text with at most + /// one group of text delimited by '[' and ']' characters. The text + /// enclosed in brackets represents a hyperlink. The destination URL of + /// the hyperlink is given by the URL parameter passed to the + /// constructor. + /// Eamples: + /// Some markup without a link. + /// Some markup with a [link]. + /// + TCreditsParser = class(TInterfacedObject, IActiveTextParser) + strict private + var + /// URL to be used in any link contained in markup. + /// + fURL: string; + public + /// Object constructor. Sets up object. + /// string [in] URL to be used in any hyperlinks + /// defined by Credit markup. + constructor Create(const URL: string); + /// Parses markup and updates active text object. + /// string [in] Markup containing definition of + /// active text. Must be valid Credits element markup. + /// IActiveText [in] Active text object + /// updated by parser. + /// + /// NOTE: Does not wrap generated text in any block tags, + /// including top level document tags. + /// Implements IActiveTextParser.Parse. + /// + procedure Parse(const Markup: string; const ActiveText: IActiveText); + end; public class function BuildActiveText(const PrefixText, CreditsMarkup, URL: string): IActiveText; overload; - {Builds an active text object containing some plain followed by active - text defined by markup in the "Credits" format. + {Builds an active text object containing some plain text followed by + active text defined by markup in the "Credits" format. @param PrefixText [in] PrefixText text. If not empty string this is added as plain text before any credits markup. @param CreditsMarkup [in] "Credits" markup. May contain a link indicated @@ -71,7 +112,7 @@ implementation // Delphi SysUtils, // Project - UREMLDataIO, USnippetCreditsParser, UStrUtils; + UREMLDataIO, UStrUtils; { TSnippetExtraHelper } @@ -92,6 +133,7 @@ class function TSnippetExtraHelper.BuildActiveText(const PrefixText, begin // Create new empty active text object Result := TActiveTextFactory.CreateActiveText; + Result.AddElem(TActiveTextFactory.CreateActionElem(ekDocument, fsOpen)); if (PrefixText <> '') then begin // We have prefix text: add it to result as a paragraph containing a single @@ -109,11 +151,12 @@ class function TSnippetExtraHelper.BuildActiveText(const PrefixText, Result.Append( TActiveTextFactory.CreateActiveText( StrMakeSentence(CreditsMarkup), - TSnippetCreditsParser.Create(URL) + TCreditsParser.Create(URL) ) ); Result.AddElem(TActiveTextFactory.CreateActionElem(ekPara, fsClose)); end; + Result.AddElem(TActiveTextFactory.CreateActionElem(ekDocument, fsClose)); end; class function TSnippetExtraHelper.BuildActiveText( @@ -123,96 +166,10 @@ class function TSnippetExtraHelper.BuildActiveText( @return Required active text object. Will be an empty object if REML is empty string. } - - // Check for an opening block tag - function IsBlockOpener(Elem: IActiveTextElem): Boolean; - var - ActionElem: IActiveTextActionElem; - begin - if not Supports(Elem, IActiveTextActionElem, ActionElem) then - Exit(False); - Result := (TActiveTextElemCaps.DisplayStyleOf(ActionElem.Kind) = dsBlock) - and (ActionElem.State = fsOpen); - end; - - // Check for a closing block tag - function IsBlockCloser(Elem: IActiveTextElem): Boolean; - var - ActionElem: IActiveTextActionElem; - begin - if not Supports(Elem, IActiveTextActionElem, ActionElem) then - Exit(False); - Result := (TActiveTextElemCaps.DisplayStyleOf(ActionElem.Kind) = dsBlock) - and (ActionElem.State = fsClose); - end; - - // Embed given content in a para block and append to result, unless content is - // empty when do nothing. - procedure AddNoneEmptyParaToResult(ParaContent: IActiveText); - begin - if ParaContent.IsEmpty then - Exit; - if StrTrim(ParaContent.ToString) = '' then - Exit; - Result.AddElem(TActiveTextFactory.CreateActionElem(ekPara, fsOpen)); - Result.Append(ParaContent); - Result.AddElem(TActiveTextFactory.CreateActionElem(ekPara, fsClose)); - end; - -var - ActiveText: IActiveText; // receives active text built from REML - OutsideBlockActiveText: IActiveText; // receives text outside of blocks - Elem: IActiveTextElem; // each element in active text - Level: Integer; // depth of block levels begin - Result := TActiveTextFactory.CreateActiveText; - if REML = '' then - Exit; // Create active text by parsing REML - ActiveText := TActiveTextFactory.CreateActiveText(REML, TREMLReader.Create); - if ActiveText.IsEmpty then - Exit; - // Init block level & obj used to accumulate text outside blocks - Level := 0; - OutsideBlockActiveText := TActiveTextFactory.CreateActiveText; - for Elem in ActiveText do - begin - if IsBlockOpener(Elem) then - begin - // We have block opener tag. Check for any text that preceeded a level - // zero block and wrap it in a paragraph before writing the block opener - if Level = 0 then - begin - if not OutsideBlockActiveText.IsEmpty then - begin - AddNoneEmptyParaToResult(OutsideBlockActiveText); - OutsideBlockActiveText := TActiveTextFactory.CreateActiveText; - end; - end; - Result.AddElem(Elem); - Inc(Level); // drop down one level - end - else if IsBlockCloser(Elem) then - begin - // Block closer - Dec(Level); - Result.AddElem(Elem); // climb up one level - end - else - begin - // Not block opener or closer - // If we're outside any block, append elem to store of elems not included - // in blocks. If we're in a block, just add the elem to output - if Level = 0 then - OutsideBlockActiveText.AddElem(Elem) - else - Result.AddElem(Elem); - end; - end; - Assert(Level = 0, ClassName + '.BuildActiveText: Unbalanced blocks'); - // Write any outstanding elems that occured outside a block - if not OutsideBlockActiveText.IsEmpty then - AddNoneEmptyParaToResult(OutsideBlockActiveText); + // .. the REML parser returns correct document or empty object if REML='' + Result := TActiveTextFactory.CreateActiveText(REML, TREMLReader.Create); end; class function TSnippetExtraHelper.BuildREMLMarkup( @@ -232,11 +189,105 @@ class function TSnippetExtraHelper.PlainTextToActiveText( Text := StrTrim(Text); if Text = '' then Exit; + Result.AddElem(TActiveTextFactory.CreateActionElem(ekDocument, fsOpen)); Result.AddElem(TActiveTextFactory.CreateActionElem(ekPara, fsOpen)); Result.AddElem( TActiveTextFactory.CreateTextElem(Text) ); Result.AddElem(TActiveTextFactory.CreateActionElem(ekPara, fsClose)); + Result.AddElem(TActiveTextFactory.CreateActionElem(ekDocument, fsClose)); +end; + +{ TSnippetExtraHelper.TCreditsParser } + +constructor TSnippetExtraHelper.TCreditsParser.Create(const URL: string); +begin + inherited Create; + fURL := URL; +end; + +procedure TSnippetExtraHelper.TCreditsParser.Parse(const Markup: string; + const ActiveText: IActiveText); +const + cOpenBracket = '['; // open bracket character that starts a link + cCloseBracket = ']'; // close bracket character that ends a link +resourcestring + // Error messages + sUnexpectedCloser = 'Unexpected closing bracket found'; + sUnterminatedLink = 'Unterminated link'; + sEmptyLink = 'Empty link definition'; + sWrongBracketOrder = 'Close bracket preceeds link open bracket'; + sMultipleOpeners = 'More than one open bracket is present'; + sMultipleClosers = 'More than one close bracket is present'; + sNoURL = 'No URL specified'; +var + OpenBracketPos: Integer; // position of opening bracket in markup + CloseBracketPos: Integer; // position of closing bracket in markup + Prefix, Postfix: string; // text before and after link (can be empty) + LinkText: string; // link text +begin + // Find open and closing brackets that delimit link text + OpenBracketPos := StrPos(cOpenBracket, Markup); + CloseBracketPos := StrPos(cCloseBracket, Markup); + if OpenBracketPos = 0 then + begin + // No links: plain text only + // check for errors + if CloseBracketPos > 0 then + raise EActiveTextParserError.Create(sUnexpectedCloser); + // record text element + ActiveText.AddElem(TActiveTextFactory.CreateTextElem(Markup)); + end + else + begin + // We have a potential link + // check for errors + if CloseBracketPos = 0 then + raise EActiveTextParserError.Create(sUnterminatedLink); + if CloseBracketPos = OpenBracketPos + 1 then + raise EActiveTextParserError.Create(sEmptyLink); + if CloseBracketPos < OpenBracketPos then + raise EActiveTextParserError.Create(sWrongBracketOrder); + if StrCountDelims(cOpenBracket, Markup) > 1 then + raise EActiveTextParserError.Create(sMultipleOpeners); + if StrCountDelims(cCloseBracket, Markup) > 1 then + raise EActiveTextParserError.Create(sMultipleClosers); + // must have a URL + if fURL = '' then + raise EActiveTextParserError.Create(sNoURL); + // get the various components + LinkText := StrSlice( + Markup, OpenBracketPos + 1, CloseBracketPos - OpenBracketPos - 1 + ); + Assert(LinkText <> '', + ClassName + '.Parse: Link text is '' but has passed check'); + Prefix := StrSliceLeft(Markup, OpenBracketPos - 1); + Postfix := StrSliceRight(Markup, Length(Markup) - CloseBracketPos); + // record the elements + if Prefix <> '' then + ActiveText.AddElem(TActiveTextFactory.CreateTextElem(Prefix)); + ActiveText.AddElem( + TActiveTextFactory.CreateActionElem( + ekLink, + TActiveTextFactory.CreateAttrs( + TActiveTextAttr.Create(TActiveTextAttrNames.Link_URL, fURL) + ), + fsOpen + ) + ); + ActiveText.AddElem(TActiveTextFactory.CreateTextElem(LinkText)); + ActiveText.AddElem( + TActiveTextFactory.CreateActionElem( + ekLink, + TActiveTextFactory.CreateAttrs( + TActiveTextAttr.Create(TActiveTextAttrNames.Link_URL, fURL) + ), + fsClose + ) + ); + if Postfix <> '' then + ActiveText.AddElem(TActiveTextFactory.CreateTextElem(Postfix)); + end; end; end. From bbaca9d24497bc07e9e6d1a5bab67f4e984d0ce5 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 7 Apr 2023 00:03:31 +0100 Subject: [PATCH 166/330] Fix bugs in rendering of source code comments Revised TActiveTextTextRenderer and made more customisable. Heavily revised code used in TSourceComments to generate source code comments from snippet description active text. Also increased indent size. Update UTextSnippetDoc re change in TActiveTextTextRenderer & increased indent size. --- Src/ActiveText.UTextRenderer.pas | 85 ++++++++++++++------------------ Src/USourceGen.pas | 54 ++++++++++++-------- Src/UTextSnippetDoc.pas | 5 +- 3 files changed, 75 insertions(+), 69 deletions(-) diff --git a/Src/ActiveText.UTextRenderer.pas b/Src/ActiveText.UTextRenderer.pas index c060188e6..78c90800f 100644 --- a/Src/ActiveText.UTextRenderer.pas +++ b/Src/ActiveText.UTextRenderer.pas @@ -21,23 +21,21 @@ interface type TActiveTextTextRenderer = class(TObject) - public + strict private const /// Special space character used to indicate the start of a list /// item. /// This special character is a necessary kludge because some - /// c odethat renders active text as formatted plain text strips away + /// 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 calling - /// code doesn't convert it. + /// NBSP was chosen since it should render the same as a space if not + /// removed. LISpacer = NBSP; // Do not localise. Must be <> #32 /// Bullet character used when rendering unordered list items. /// Bullet = '*'; // Do not localise. Must be <> #32 and <> LISpacer - strict private - const - IndentDelta = 2; + DefaultIndentDelta = 2; type TListKind = (lkNumber, lkBullet); TListState = record @@ -60,6 +58,7 @@ TLIState = record fIndent: UInt16; fInPara: Boolean; fInListItem: Boolean; + fIndentDelta: UInt8; function CanEmitInline: Boolean; procedure AppendToPara(const AText: string); procedure InitialiseRender; @@ -75,9 +74,10 @@ TLIState = record destructor Destroy; override; property DisplayURLs: Boolean read fDisplayURLs write fDisplayURLs default False; - function RenderWrapped(ActiveText: IActiveText; const PageWidth, LMargin, - ParaOffset: Cardinal; const Prefix: string = ''; - const Suffix: string = ''): string; + property IndentDelta: UInt8 read fIndentDelta write fIndentDelta + default DefaultIndentDelta; + function RenderWrapped(ActiveText: IActiveText; const PageWidth, + LMargin: Cardinal): string; end; @@ -122,6 +122,7 @@ constructor TActiveTextTextRenderer.Create; fIndent := 0; fInPara := False; fInListItem := False; + fIndentDelta := DefaultIndentDelta; end; destructor TActiveTextTextRenderer.Destroy; @@ -200,6 +201,21 @@ 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 @@ -208,22 +224,12 @@ procedure TActiveTextTextRenderer.RenderBlockActionElem( begin fBlocksStack.Push(Elem.Kind); case Elem.Kind of - ekPara: {Do nothing} ; - ekHeading: {Do nothing} ; + ekPara, ekHeading, ekBlock: + {Do nothing} ; ekUnorderedList: - begin - if (fListStack.Count > 0) and (fInPara) then - OutputParagraph; - fListStack.Push(TListState.Create(lkBullet)); - Inc(fIndent, IndentDelta); - end; + OpenListContainer(lkBullet); ekOrderedList: - begin - if (fListStack.Count > 0) and (fInPara) then - OutputParagraph; - fListStack.Push(TListState.Create(lkNumber)); - Inc(fIndent, IndentDelta); - end; + OpenListContainer(lkNumber); ekListItem: begin // Update list number of current list @@ -235,16 +241,9 @@ procedure TActiveTextTextRenderer.RenderBlockActionElem( // Act depending on current list kind case fListStack.Peek.ListKind of lkNumber: - begin - // Number list: start a new numbered item, with current number - fParaBuilder.Append(IntToStr(fListStack.Peek.ListNumber)); - fParaBuilder.Append(NBSP); - end; + AddListMarker(IntToStr(fListStack.Peek.ListNumber)); lkBullet: - begin - // Bullet list: start a new bullet point - fParaBuilder.Append(Bullet + NBSP); - end; + AddListMarker(Bullet); end; end; end; @@ -252,17 +251,9 @@ procedure TActiveTextTextRenderer.RenderBlockActionElem( fsClose: begin case Elem.Kind of - ekPara: + ekPara, ekHeading, ekBlock: OutputParagraph; - ekHeading: - OutputParagraph; - ekUnorderedList: - begin - OutputParagraph; - fListStack.Pop; - Dec(fIndent, IndentDelta); - end; - ekOrderedList: + ekUnorderedList, ekOrderedList: begin OutputParagraph; fListStack.Pop; @@ -315,7 +306,7 @@ procedure TActiveTextTextRenderer.RenderURL(Elem: IActiveTextActionElem); end; function TActiveTextTextRenderer.RenderWrapped(ActiveText: IActiveText; - const PageWidth, LMargin, ParaOffset: Cardinal; const Prefix, Suffix: string): + const PageWidth, LMargin: Cardinal): string; var Paras: IStringList; @@ -367,13 +358,13 @@ function TActiveTextTextRenderer.RenderWrapped(ActiveText: IActiveText; begin Result := ''; - Paras := TIStringList.Create(Prefix + Render(ActiveText) + Suffix, EOL, True); + Paras := TIStringList.Create(Render(ActiveText), EOL, True); for Para in Paras do begin if IsListItem then begin - Offset := -ParaOffset; - ParaIndent := CalcParaIndent + LMargin + ParaOffset; + Offset := -IndentDelta; + ParaIndent := CalcParaIndent + LMargin + IndentDelta; end else begin diff --git a/Src/USourceGen.pas b/Src/USourceGen.pas index 3d10b57ef..ccf94698a 100644 --- a/Src/USourceGen.pas +++ b/Src/USourceGen.pas @@ -41,7 +41,7 @@ TSourceComments = class(TNoConstructObject) /// maximum width indented by the given number of spaces on the left, /// optionally truncated to the first paragraph. class function FormatActiveTextCommentInner(ActiveText: IActiveText; - const Indent: Cardinal; const Truncate: Boolean): string; + const LineWidth: Cardinal; const Truncate: Boolean): string; public /// Returns a description of the given comment style. @@ -60,7 +60,7 @@ TSourceComments = class(TNoConstructObject) /// string.Formatted comment or empty string if Style = csNone. /// class function FormatSnippetComment(const Style: TCommentStyle; - const TruncateComments: Boolean; const Text: IActiveText): string; + const TruncateComments: Boolean; Text: IActiveText): string; /// Formats document's header text as a Pascal comment. /// IStringList [in] List of paragraphs of header @@ -259,9 +259,11 @@ implementation const /// Maximum number of characters on a source code line. cLineWidth = 80; -const /// Size of indenting used for source code, in characters. cIndent = 2; + /// Size of indenting used for rendering comments from active text. + /// + cCommentIndent = 4; type @@ -1137,11 +1139,13 @@ class function TSourceComments.CommentStyleDesc( end; class function TSourceComments.FormatActiveTextCommentInner( - ActiveText: IActiveText; const Indent: Cardinal; const Truncate: Boolean): - string; + ActiveText: IActiveText; const LineWidth: Cardinal; + const Truncate: Boolean): string; var Renderer: TActiveTextTextRenderer; ProcessedActiveText: IActiveText; + Lines: IStringList; + Line: string; begin if Truncate then ProcessedActiveText := ActiveText.FirstBlock @@ -1150,9 +1154,17 @@ class function TSourceComments.FormatActiveTextCommentInner( Renderer := TActiveTextTextRenderer.Create; try Renderer.DisplayURLs := False; - Result := Renderer.RenderWrapped( - ProcessedActiveText, cLineWidth, Indent, Indent + Renderer.IndentDelta := cCommentIndent; + Result := ''; + Lines := TIStringList.Create( + Renderer.RenderWrapped(ProcessedActiveText, LineWidth, 0), + EOL, + True, + False ); + for Line in Lines do + Result := Result + StringOfChar(' ', cLineWidth - LineWidth) + Line + EOL; + Result := StrTrimRight(Result); finally Renderer.Free; end; @@ -1189,25 +1201,27 @@ class function TSourceComments.FormatHeaderComments( end; class function TSourceComments.FormatSnippetComment(const Style: TCommentStyle; - const TruncateComments: Boolean; const Text: IActiveText): string; + const TruncateComments: Boolean; Text: IActiveText): string; begin case Style of csNone: Result := ''; csBefore: - Result := '{' - + EOL - + FormatActiveTextCommentInner(Text, cIndent, TruncateComments) - + EOL - + '}'; + begin + Result := '{' + EOL + + FormatActiveTextCommentInner( + Text, cLineWidth - cIndent, TruncateComments + ) + + EOL + '}'; + end; csAfter: - Result := StrOfChar(TActiveTextTextRenderer.LISpacer, cIndent) - + '{' - + EOL - + FormatActiveTextCommentInner(Text, 2 * cIndent, TruncateComments) - + EOL - + StrOfChar(TActiveTextTextRenderer.LISpacer, cIndent) - + '}'; + begin + Result := StrOfChar(' ', cIndent) + '{' + EOL + + FormatActiveTextCommentInner( + Text, cLineWidth - 2 * cIndent, TruncateComments + ) + + EOL + StringOfChar(' ', cIndent) + '}'; + end; end; end; diff --git a/Src/UTextSnippetDoc.pas b/Src/UTextSnippetDoc.pas index 87e72c4dd..d046c38c0 100644 --- a/Src/UTextSnippetDoc.pas +++ b/Src/UTextSnippetDoc.pas @@ -36,7 +36,7 @@ TTextSnippetDoc = class(TSnippetDoc) cPageWidth = 80; /// Size of a single level of indentation in characters. /// - cIndent = 2; + cIndent = 4; strict private /// Renders given active text as word-wrapped paragraphs of width /// cPageWidth. @@ -111,8 +111,9 @@ procedure TTextSnippetDoc.RenderActiveText(ActiveText: IActiveText); Renderer := TActiveTextTextRenderer.Create; try Renderer.DisplayURLs := True; + Renderer.IndentDelta := cIndent; fWriter.WriteLine( - Renderer.RenderWrapped(ActiveText, cPageWidth, 0, cIndent) + Renderer.RenderWrapped(ActiveText, cPageWidth, 0) ); finally Renderer.Free; From 2d3be989b2c2301eb1c37f7a5f02d1c4e2168280 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 7 Apr 2023 23:10:00 +0100 Subject: [PATCH 167/330] Support rendering lists in snippet RTF reports Update URTFUtils to support 3 more RTF controls for controlling first line offset, left indent and tabstops. Add support to TRTFStyles for recording indentation and calculating the identation required at a given identation depth. Add new methods to TRTFBuilder to set indents and tab stops. (Also did minor refactoring of SetParaSpacing method.) Major rewrite of TActiveTextRTF to support lists. Added support for rendering active text bullet and numbered lists in RTF. Also added support for new ekBlock active text element. Add styles for lists in TRTFSnippetDoc and adjusted some other styles to suit. Also evised spacing between description and source code & added "Source Code" heading. Updated and added some constants. --- Src/ActiveText.URTFRenderer.pas | 220 ++++++++++++++++++++++++++++++-- Src/URTFBuilder.pas | 31 ++++- Src/URTFSnippetDoc.pas | 123 +++++++++++++----- Src/URTFStyles.pas | 36 +++++- Src/URTFUtils.pas | 10 +- 5 files changed, 360 insertions(+), 60 deletions(-) diff --git a/Src/ActiveText.URTFRenderer.pas b/Src/ActiveText.URTFRenderer.pas index dc0267b79..44564d3b8 100644 --- a/Src/ActiveText.URTFRenderer.pas +++ b/Src/ActiveText.URTFRenderer.pas @@ -45,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; + fListStack: TStack; + fIndentStack: TStack; + fLIStack: TStack; + fIndentLevel: Byte; // logical indent level + fInPara: Boolean; procedure SetElemStyleMap(const ElemStyleMap: TActiveTextRTFStyleMap); procedure Initialise(const Builder: TRTFBuilder); procedure RenderTextElem(Elem: IActiveTextTextElem; @@ -60,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; @@ -76,8 +100,10 @@ implementation uses + // Delphi + SysUtils, Generics.Defaults, // Project - SysUtils, Generics.Defaults; + UConsts, UStrUtils; { TActiveTextRTFStyleMap } @@ -155,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.Create; + fListStack := TStack.Create; + fIndentStack := TStack.Create; + fLIStack := TStack.Create; + fIndentLevel := 0; + fInPara := False; end; destructor TActiveTextRTF.Destroy; begin + fLIStack.Free; + fIndentStack.Free; + fListStack.Free; + fBlockStack.Free; fElemStyleMap.Free; inherited; end; @@ -189,7 +232,6 @@ procedure TActiveTextRTF.Render(ActiveText: IActiveText; ActionElem: IActiveTextActionElem; begin Initialise(RTFBuilder); - fInBlock := False; for Elem in ActiveText do begin if Supports(Elem, IActiveTextTextElem, TextElem) then @@ -206,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; @@ -226,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: @@ -245,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; @@ -271,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/URTFBuilder.pas b/Src/URTFBuilder.pas index f25efaf82..64e8cf664 100644 --- a/Src/URTFBuilder.pas +++ b/Src/URTFBuilder.pas @@ -179,6 +179,12 @@ TRTFBuilder = class(TObject) /// Sets before and after spacing, in points, to be used for /// subsequent paragraphs. procedure SetParaSpacing(const Spacing: TRTFParaSpacing); + /// Sets left and first line indents, in twips to be used for + /// subsequent paragraphs. + procedure SetIndents(const LeftIndent, FirstLineOffset: SmallInt); + /// Sets tab stops, in twips, to be used for subsequent + /// paragraphs. + procedure SetTabStops(const TabStops: array of SmallInt); /// Sets paragraph and character styling for subsequent text /// according to given RTF style. procedure ApplyStyle(const Style: TRTFStyle); @@ -354,11 +360,30 @@ procedure TRTFBuilder.SetFontStyle(const Style: TFontStyles); AddControl(RTFControl(rcUnderline)); end; +procedure TRTFBuilder.SetIndents(const LeftIndent, FirstLineOffset: SmallInt); +begin + AddControl(RTFControl(rcLeftIndent, LeftIndent)); + AddControl(RTFControl(rcFirstLineOffset, FirstLineOffset)); +end; + procedure TRTFBuilder.SetParaSpacing(const Spacing: TRTFParaSpacing); +const + TwipsPerPoint = 20; // Note: 20 Twips in a point +begin + AddControl( + RTFControl(rcSpaceBefore, FloatToInt(TwipsPerPoint * Spacing.Before)) + ); + AddControl( + RTFControl(rcSpaceAfter, FloatToInt(TwipsPerPoint * Spacing.After)) + ); +end; + +procedure TRTFBuilder.SetTabStops(const TabStops: array of SmallInt); +var + Tab: SmallInt; begin - // Note: 20 Twips in a point - AddControl(RTFControl(rcSpaceBefore, FloatToInt(20 * Spacing.Before))); - AddControl(RTFControl(rcSpaceAfter, FloatToInt(20 * Spacing.After))); + for Tab in TabStops do + AddControl(RTFControl(rcTabStop, Tab)); end; { TRTFFontTable } diff --git a/Src/URTFSnippetDoc.pas b/Src/URTFSnippetDoc.pas index 375b10213..d947e4c01 100644 --- a/Src/URTFSnippetDoc.pas +++ b/Src/URTFSnippetDoc.pas @@ -39,8 +39,11 @@ TRTFSnippetDoc = class(TSnippetDoc) fBuilder: TRTFBuilder; /// Flag indicates whether to output in colour. fUseColour: Boolean; - + /// Styles to apply to snippet description active text. + /// fDescStyles: TActiveTextRTFStyleMap; + /// Styles to apply to snippet extra information active text. + /// fExtraStyles: TActiveTextRTFStyleMap; /// Styling applied to URLs. fURLStyle: TRTFStyle; @@ -49,14 +52,24 @@ TRTFSnippetDoc = class(TSnippetDoc) MainFontName = 'Tahoma'; /// Name of mono font. MonoFontName = 'Courier New'; - /// Size of heading font. - HeadingFontSize = 16; - /// Size of paragraph font. + /// Size of font used for database information in points. + /// + DBInfoFontSize = 9; // points + /// Size of heading font in points. + HeadingFontSize = 16; // points + /// Size of sub-heading font in points. + /// Used in descripton and extra active text. + SubHeadingFontSize = 12; + /// Size of paragraph font in points. ParaFontSize = 10; /// Paragraph spacing in points. - ParaSpacing = 12.0; - /// Size of font used for database information. - DBInfoFontSize = 9; + ParaSpacing = 6.0; + /// Spacing for non-paragrap blocks in points. + NoParaBlockSpacing = 0.0; + /// Spacing of list blocks in points. + ListSpacing = ParaSpacing; + /// Step size of indents and tabs in twips. + IndentDelta = TRTFStyle.DefaultIndentDelta; strict private /// Initialises RTF style used when rendering active text as RTF. /// @@ -167,43 +180,71 @@ procedure TRTFSnippetDoc.InitStyles; [scColour], TRTFFont.CreateNull, 0.0, [], clExternalLink ); - fExtraStyles.Add( - ekPara, - TRTFStyle.Create( - TRTFParaSpacing.Create(ParaSpacing, 0.0) - ) - ); + // Active text styles + + // -- Active text block styles + fDescStyles.Add( - ekPara, - TRTFStyle.Create( - TRTFParaSpacing.Create(0.0, ParaSpacing) - ) + ekHeading, + TRTFStyle.Create( + [scParaSpacing, scFontStyles, scFontSize], + TRTFParaSpacing.Create(0.0, 0.0), + TRTFFont.CreateNull, + SubHeadingFontSize, + [fsBold], + clNone + ) ); - fExtraStyles.Add( ekHeading, TRTFStyle.Create( - [scParaSpacing, scFontStyles], + [scParaSpacing, scFontStyles, scFontSize], TRTFParaSpacing.Create(ParaSpacing, 0.0), TRTFFont.CreateNull, - 0.0, + SubHeadingFontSize, [fsBold], clNone ) ); + fDescStyles.Add( - ekHeading, + ekPara, + TRTFStyle.Create(TRTFParaSpacing.Create(ParaSpacing, 0.0)) + ); + fExtraStyles.Add(ekPara, fDescStyles[ekPara]); + + fDescStyles.Add( + ekBlock, + TRTFStyle.Create(TRTFParaSpacing.Create(NoParaBlockSpacing, 0.0)) + ); + fExtraStyles.Add(ekBlock, fDescStyles[ekBlock]); + + fDescStyles.Add( + ekUnorderedList, + TRTFStyle.Create(TRTFParaSpacing.Create(ListSpacing, 0.0)) + ); + fExtraStyles.Add(ekUnorderedList, fDescStyles[ekUnorderedList]); + + fDescStyles.Add(ekOrderedList, fDescStyles[ekUnorderedList]); + fExtraStyles.Add(ekOrderedList, fDescStyles[ekOrderedList]); + + fDescStyles.Add( + ekListItem, TRTFStyle.Create( - [scParaSpacing, scFontStyles], - TRTFParaSpacing.Create(0.0, ParaSpacing), + [scIndentDelta], + TRTFParaSpacing.CreateNull, TRTFFont.CreateNull, 0.0, - [fsBold], - clNone + [], + clNone, + 360 ) ); + fExtraStyles.Add(ekListItem, fDescStyles[ekListItem]); - fExtraStyles.Add( + // -- Active text inline styles + + fDescStyles.Add( ekStrong, TRTFStyle.Create( [scFontStyles], @@ -213,9 +254,9 @@ procedure TRTFSnippetDoc.InitStyles; clNone ) ); - fDescStyles.Add(ekStrong, fExtraStyles[ekStrong]); + fExtraStyles.Add(ekStrong, fDescStyles[ekStrong]); - fExtraStyles.Add( + fDescStyles.Add( ekEm, TRTFStyle.Create( [scFontStyles], @@ -225,9 +266,9 @@ procedure TRTFSnippetDoc.InitStyles; clNone ) ); - fDescStyles.Add(ekEm, fExtraStyles[ekEm]); + fExtraStyles.Add(ekEm, fDescStyles[ekEm]); - fExtraStyles.Add( + fDescStyles.Add( ekVar, TRTFStyle.Create( [scFontStyles, scColour], @@ -237,9 +278,9 @@ procedure TRTFSnippetDoc.InitStyles; clVarText ) ); - fDescStyles.Add(ekVar, fExtraStyles[ekVar]); + fExtraStyles.Add(ekVar, fDescStyles[ekVar]); - fExtraStyles.Add( + fDescStyles.Add( ekWarning, TRTFStyle.Create( [scFontStyles, scColour], @@ -249,9 +290,9 @@ procedure TRTFSnippetDoc.InitStyles; clWarningText ) ); - fDescStyles.Add(ekWarning, fExtraStyles[ekWarning]); + fExtraStyles.Add(ekWarning, fDescStyles[ekWarning]); - fExtraStyles.Add( + fDescStyles.Add( ekMono, TRTFStyle.Create( [scFont], @@ -261,7 +302,9 @@ procedure TRTFSnippetDoc.InitStyles; clNone ) ); - fDescStyles.Add(ekMono, fExtraStyles[ekMono]); + fExtraStyles.Add(ekMono, fDescStyles[ekMono]); + + // Fixes for monochrome if not fUseColour then begin @@ -356,7 +399,17 @@ procedure TRTFSnippetDoc.RenderHeading(const Heading: string; procedure TRTFSnippetDoc.RenderSourceCode(const SourceCode: string); var Renderer: IHiliteRenderer; // renders highlighted source as RTF +resourcestring + sHeading = 'Source Code:'; begin + fBuilder.ResetCharStyle; + fBuilder.SetFont(MainFontName); + fBuilder.SetFontSize(ParaFontSize); + fBuilder.SetFontStyle([fsBold]); + fBuilder.SetParaSpacing(TRTFParaSpacing.Create(ParaSpacing, ParaSpacing)); + fBuilder.AddText(sHeading); + fBuilder.ResetCharStyle; + fBuilder.EndPara; fBuilder.ClearParaFormatting; Renderer := TRTFHiliteRenderer.Create(fBuilder, fHiliteAttrs); TSyntaxHiliter.Hilite(SourceCode, Renderer); diff --git a/Src/URTFStyles.pas b/Src/URTFStyles.pas index 6142e8762..60e5dd89a 100644 --- a/Src/URTFStyles.pas +++ b/Src/URTFStyles.pas @@ -88,7 +88,8 @@ TRTFParaSpacing = record scFont, scFontSize, scFontStyles, - scColour + scColour, + scIndentDelta ); type @@ -97,24 +98,30 @@ TRTFParaSpacing = record type TRTFStyle = record public + const + DefaultIndentDelta = 360; var ParaSpacing: TRTFParaSpacing; Font: TRTFFont; FontSize: Double; FontStyles: TFontStyles; Colour: TColor; + IndentDelta: SmallInt; Capabilities: TRTFStyleCaps; constructor Create(const ACapabilities: TRTFStyleCaps; const AParaSpacing: TRTFParaSpacing; const AFont: TRTFFont; const AFontSize: Double; const AFontStyles: TFontStyles; - const AColour: TColor); overload; + const AColour: TColor; const AIndentDelta: SmallInt = DefaultIndentDelta); + overload; constructor Create(const ACapabilities: TRTFStyleCaps; const AFont: TRTFFont; const AFontSize: - Double; const AFontStyles: TFontStyles; const AColour: TColor); overload; + Double; const AFontStyles: TFontStyles; const AColour: TColor; + const AIndentDelta: SmallInt = DefaultIndentDelta); overload; constructor Create(const AParaSpacing: TRTFParaSpacing); overload; class function CreateNull: TRTFStyle; static; function IsNull: Boolean; procedure MakeMonochrome; + function IndentLevelToTwips(const ALevel: Byte): SmallInt; class operator Equal(const Left, Right: TRTFStyle): Boolean; class operator NotEqual(const Left, Right: TRTFStyle): Boolean; end; @@ -186,7 +193,7 @@ class function TRTFParaSpacing.CreateNull: TRTFParaSpacing; constructor TRTFStyle.Create(const ACapabilities: TRTFStyleCaps; const AParaSpacing: TRTFParaSpacing; const AFont: TRTFFont; const AFontSize: Double; const AFontStyles: TFontStyles; - const AColour: TColor); + const AColour: TColor; const AIndentDelta: SmallInt); begin Capabilities := ACapabilities; ParaSpacing := AParaSpacing; @@ -194,11 +201,13 @@ constructor TRTFStyle.Create(const ACapabilities: TRTFStyleCaps; FontSize := AFontSize; FontStyles := AFontStyles; Colour := AColour; + IndentDelta := AIndentDelta; end; constructor TRTFStyle.Create(const ACapabilities: TRTFStyleCaps; const AFont: TRTFFont; const AFontSize: Double; - const AFontStyles: TFontStyles; const AColour: TColor); + const AFontStyles: TFontStyles; const AColour: TColor; + const AIndentDelta: SmallInt); begin Create( ACapabilities - [scParaSpacing], @@ -206,7 +215,8 @@ constructor TRTFStyle.Create(const ACapabilities: TRTFStyleCaps; AFont, AFontSize, AFontStyles, - AColour + AColour, + AIndentDelta ); end; @@ -231,7 +241,19 @@ class function TRTFStyle.CreateNull: TRTFStyle; and StrSameText(Left.Font.Name, Right.Font.Name) and SameValue(Left.FontSize, Right.FontSize) and (Left.FontStyles = Right.FontStyles) - and (Left.Colour = Right.Colour); + and (Left.Colour = Right.Colour) + and (Left.IndentDelta = Right.IndentDelta); +end; + +function TRTFStyle.IndentLevelToTwips(const ALevel: Byte): SmallInt; +var + Delta: SmallInt; +begin + if scIndentDelta in Capabilities then + Delta := IndentDelta + else + Delta := DefaultIndentDelta; + Result := ALevel * Delta; end; function TRTFStyle.IsNull: Boolean; diff --git a/Src/URTFUtils.pas b/Src/URTFUtils.pas index 927014dde..2b1834bc9 100644 --- a/Src/URTFUtils.pas +++ b/Src/URTFUtils.pas @@ -3,7 +3,7 @@ * 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) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2022, Peter Johnson (gravatar.com/delphidabbler). * * Utility functions used when processing RTF. } @@ -66,7 +66,10 @@ interface rcUnicodeChar, // defines a Unicode character as signed 16bit value rcUnicodePair, // introduces pair of ANSI and Unicode destinations rcUnicodeDest, // introduces Unicode destination - rcIgnore // denotes following control can be ignored + rcIgnore, // denotes following control can be ignored + rcFirstLineOffset, // first line indent in twips (relative to \li) + rcLeftIndent, // left indent in twips + rcTabStop // sets a tab stop in twips ); type @@ -193,7 +196,8 @@ implementation 'rtf', 'ansi', 'ansicpg', 'deff', 'deflang', 'fonttbl', 'fprq', 'fcharset', 'fnil', 'froman', 'fswiss', 'fmodern', 'fscript', 'fdecor', 'ftech', 'colortbl', 'red', 'green', 'blue', 'info', 'title', 'pard', 'par', 'plain', - 'f', 'cf', 'b', 'i', 'ul', 'fs', 'sb', 'sa', 'u', 'upr', 'ud', '*' + 'f', 'cf', 'b', 'i', 'ul', 'fs', 'sb', 'sa', 'u', 'upr', 'ud', '*', + 'fi', 'li', 'tx' ); function RTFControl(const Ctrl: TRTFControl): ASCIIString; From 45900e47d3a806c52b73230a6114abafecc90812 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sat, 8 Apr 2023 00:47:12 +0100 Subject: [PATCH 168/330] Correct and revise REML documentation Updated REML design document and REML help topic --- Docs/Design/reml.html | 25 ++++++++++++++----------- Src/Help/HTML/reml.htm | 30 +++++++++++++++--------------- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/Docs/Design/reml.html b/Docs/Design/reml.html index 9762decaf..51fb91de4 100644 --- a/Docs/Design/reml.html +++ b/Docs/Design/reml.html @@ -279,19 +279,19 @@

                • - <p>...</p> – Renders the enclosed markup as a simple paragraph. + <p>...</p> – Renders the enclosed markup as a simple paragraph.
                • <heading>...</heading> – Renders the enclosed markup as a heading.
                • - <ol>...</ol> – Renders the enclosed HTML as an ordered list. Must contain <li>...</li> blocks and nothing else. + <ol>...</ol> – Renders the enclosed markup as an ordered list.
                • - <ul>...</ul> – Renders the enclosed HTML as an unordered list. Must contain <li>...</li> blocks and nothing else. + <ul>...</ul> – Renders the enclosed markup as an unordered list.
                • - <li>...</li> – Renders the enclosed HTML as a list item. Must only be used within <ol>...</ol> and <ul>...</ul> blocks. + <li>...</li> – Renders the enclosed markup as a list item.

                @@ -308,7 +308,7 @@

                <ol>...</ol> and <ul>...</ul> blocks must only contain one or more <li>...</li> blocks.

              • - <li>...</li> blocks must not contain <p>...</p>, <heading>...</heading> or other <li>...</li> blocks directly, but may contain <ol>...</ol> and <ul>...</ul> blocks. + <li>...</li> blocks must only be used within <ol>...</ol> and <ul>...</ul> blocks. May contain <p>...</p> and <heading>...</heading> blocks, but it is permitted to include text and inline tags directly without enclosing them one of the permitted blocks. Nested lists are permitted by including further <ul>...</ul> and <ol>...</ol> blocks.
              • All text should be embedded within <p>...</p>, <heading>...</heading> or <li>...</li> block level tags, e.g. <heading>heading</heading><p>text</p> or simply <p>text</p>. @@ -320,12 +320,15 @@

                Here is a valid example:

                -
                <p>Hello World</p>
                -<heading>Hello</heading>
                +  
                <heading>Hello</heading>
                 <p>Hello World</p>
                 <ol>
                   <li>one</li>
                -  <li>two</li>
                +  <li><p>two</p></li>
                +  <ul>
                +    <li>two A</li>
                +    <li>two B</li>
                +  <ul>
                   <li>three</li>
                 </ol>

                @@ -333,7 +336,7 @@

                wrong <heading>blah</heading> wrong <p>blah</p> wrong

                - However interpreting code may interpret this permissively. If this is done the text outside blocks must be interpreted as if it was enclosed in <p> and </p> tags. Therefore the above code would be interpreted as: + However interpreting code may interpret this permissively. If this is done the text outside blocks should be interpreted as if it was enclosed in <p> and </p> tags. Therefore the above code would be interpreted as:

                <p>wrong </p><heading>blah</heading><p>wrong </p><p>blah</p><p>wrong</p>

              • - <li>...</li> blocks must not - contain - <p>...</p>, - <heading>...</heading> or other - <li>...</li> blocks directly, - but may contain - <ol>...</ol> and - <ul>...</ul> blocks. + <li>...</li> blocks may contain + <p>...</p> and + <heading>...</heading> blocks, + but it is permitted to include text and inline tags directly without + enclosing them one of the permitted blocks. Nested lists are permitted + by including further <ul>...</ul> + and <ol>...</ol> blocks.
              • All text should be embedded within @@ -132,12 +131,15 @@

                Here is a valid example:

                -
                <p>Hello World</p>
                -<heading>Hello</heading>
                +    
                <heading>Hello</heading>
                 <p>Hello World</p>
                 <ol>
                   <li>one</li>
                -  <li>two</li>
                +  <li><p>two</p></li>
                +  <ul>
                +    <li>two A</li>
                +    <li>two B</li>
                +  <ul>
                   <li>three</li>
                 </ol>

                @@ -148,10 +150,8 @@

                blah<heading>blah</heading>blah<p>blah</p>blah

                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: + automatically adds block level tags for text that is not enclosed in block + level tags. The above code is interpreted similar ro:

                <p>blah </p>
                 <heading>blah</heading>
                
                From 3d9235dbe939c53c6eb8ac533fcd09a3ef8dfbdb Mon Sep 17 00:00:00 2001
                From: delphidabbler <5164283+delphidabbler@users.noreply.github.com>
                Date: Sat, 8 Apr 2023 01:17:38 +0100
                Subject: [PATCH 169/330] Remove unused param from TRTF ctor stream overload
                
                The TRTF constructor that took a stream, an encoding and a flag as
                parameters was not using the encoding parameter. So it was removed.
                
                Although this has been fixed, the constructor is not actually used and
                could be removed.
                ---
                 Src/URTFUtils.pas | 7 +++----
                 1 file changed, 3 insertions(+), 4 deletions(-)
                
                diff --git a/Src/URTFUtils.pas b/Src/URTFUtils.pas
                index 2b1834bc9..c7463e670 100644
                --- a/Src/URTFUtils.pas
                +++ b/Src/URTFUtils.pas
                @@ -92,8 +92,8 @@   TRTF = record
                     ///  Boolean [in] Flag that indicates if the whole
                     ///  stream is to be read (True) or stream is to be read from current
                     ///  position (False).
                -    constructor Create(const AStream: TStream; const AEncoding: TEncoding;
                -      const ReadAll: Boolean = False); overload;
                +    constructor Create(const AStream: TStream; const ReadAll: Boolean = False);
                +      overload;
                     ///  Initialises record from ASCII RTF code.
                     ///  ASCIIString [in] ASCII string containing RTF
                     ///  code.
                @@ -295,8 +295,7 @@ function RTFUnicodeSafeDestination(const DestCtrl: TRTFControl;
                 
                 { TRTF }
                 
                -constructor TRTF.Create(const AStream: TStream; const AEncoding: TEncoding;
                -  const ReadAll: Boolean);
                +constructor TRTF.Create(const AStream: TStream; const ReadAll: Boolean);
                 var
                   ByteCount: Integer;
                 begin
                
                From 21f6e7cca09a7dc17a117498962132197a3da6e9 Mon Sep 17 00:00:00 2001
                From: delphidabbler <5164283+delphidabbler@users.noreply.github.com>
                Date: Sat, 8 Apr 2023 02:01:22 +0100
                Subject: [PATCH 170/330] Fixed corrupted text snippet report
                
                Precalculate longest compiler name and use that value to ensure compiler
                table rows line up correctly.
                ---
                 Src/UTextSnippetDoc.pas | 15 ++++++++++++---
                 1 file changed, 12 insertions(+), 3 deletions(-)
                
                diff --git a/Src/UTextSnippetDoc.pas b/Src/UTextSnippetDoc.pas
                index d046c38c0..b5119eae8 100644
                --- a/Src/UTextSnippetDoc.pas
                +++ b/Src/UTextSnippetDoc.pas
                @@ -123,12 +123,21 @@ procedure TTextSnippetDoc.RenderActiveText(ActiveText: IActiveText);
                 procedure TTextSnippetDoc.RenderCompilerInfo(const Heading: string;
                   const Info: TCompileDocInfoArray);
                 var
                -  Idx: Integer; // loops compiler information table
                +  MaxNameLength: Integer;
                +  CompilerInfo: TCompileDocInfo;
                 begin
                +  // Calculate length of longest compiler name
                +  MaxNameLength := 0;
                +  for CompilerInfo in Info do
                +    if Length(CompilerInfo.Compiler) > MaxNameLength then
                +      MaxNameLength := Length(CompilerInfo.Compiler);
                +  // Write out compilers with results
                   fWriter.WriteLine;
                   fWriter.WriteLine(Heading);
                -  for Idx := Low(Info) to High(Info) do
                -    fWriter.WriteLine('%-20s%s', [Info[Idx].Compiler, Info[Idx].Result]);
                +  for CompilerInfo in Info do
                +    fWriter.WriteLine(
                +      '%-*s%s', [MaxNameLength + 4, CompilerInfo.Compiler, CompilerInfo.Result]
                +    );
                 end;
                 
                 procedure TTextSnippetDoc.RenderDBInfo(const Text: string);
                
                From be39085a6fd15d55a478234bb9e041e9ca9c820e Mon Sep 17 00:00:00 2001
                From: delphidabbler <5164283+delphidabbler@users.noreply.github.com>
                Date: Sat, 8 Apr 2023 17:10:09 +0100
                Subject: [PATCH 171/330] New code to find max width of strings when rendered
                
                Added new routines to return the maximum width of a list of strings when
                rendered in a given font.
                
                Two routines added: MaxStringWidthPx and MaxStringWidthTwips to return
                the maximum string width in pixels and twips respectively.
                ---
                 Src/UGraphicUtils.pas | 101 +++++++++++++++++++++++++++++++++++++++---
                 1 file changed, 96 insertions(+), 5 deletions(-)
                
                diff --git a/Src/UGraphicUtils.pas b/Src/UGraphicUtils.pas
                index 917324f07..cd4dc1c87 100644
                --- a/Src/UGraphicUtils.pas
                +++ b/Src/UGraphicUtils.pas
                @@ -17,10 +17,7 @@ interface
                 
                 uses
                   // Delphi
                -  Windows, Graphics,
                -  // Project
                -  UStructs;
                -
                +  Windows, Graphics;
                 
                 function CreateDisplayDC: HDC;
                   {Creates a display device context.
                @@ -45,6 +42,28 @@ function StringExtent(const S: string; const Font: TFont): TSize; overload;
                     @return Structure containing width and height of string in pixels.
                   }
                 
                +///  Returns width, in pixels, of the widest of the given strings when
                +///  rendered a specified font.
                +///  array of string [in] Strings whose rendered
                +///  width is to be measured.
                +///  TFont [in] Font in which strings are to be
                +///  rendered.
                +///  SmallInt. Width of widest string in array in pixels.
                +///  
                +function MaxStringWidthPx(const AStrings: array of string; const AFont: TFont):
                +  SmallInt;
                +
                +///  Returns width, in twips, of the widest of the given strings when
                +///  rendered a specified font.
                +///  array of string [in] Strings whose rendered
                +///  width is to be measured.
                +///  TFont [in] Font in which strings are to be
                +///  rendered.
                +///  SmallInt. Width of widest string in array in twips.
                +///  
                +function MaxStringWidthTwips(const AStrings: array of string;
                +  const AFont: TFont): SmallInt;
                +
                 function GetTextRect(const Text: string; const Canvas: TCanvas;
                   const Rect: TRect; const Flags: Longint): TRect;
                   {Gets rectangle of size required to display text in a specified canvas.
                @@ -59,8 +78,10 @@ implementation
                 
                 
                 uses
                +  // Delphi
                +  SysUtils,
                   // Project
                -  SysUtils;
                +  UStructs;
                 
                 
                 { Helper routines }
                @@ -91,6 +112,43 @@ procedure FreeDisplayCanvas(var Canvas: TCanvas);
                     end;
                 end;
                 
                +///  Returns width of the widest of the given strings when rendered a
                +///  specified font.
                +///  Width is calculated in pixels, but is converted to returned value
                +///  by closure passed as a parameter.
                +///  array of string [in] Strings whose rendered
                +///  width is to be measured.
                +///  TFont [in] Font in which strings are to be
                +///  rendered.
                +///  TFunc<HDC, Integer, SmallInt> [in]
                +///  Converter function used to convert result to required units, using the
                +///  handle of the font canvas.
                +///  SmallInt. Width of widest string in array in twips.
                +///  
                +function InternalMaxStringWidth(const AStrings: array of string;
                +  const AFont: TFont; const AConverter: TFunc):
                +  SmallInt;
                +var
                +  Str: string;
                +  StrWidth: Integer;
                +  MaxStrWidth: Integer;
                +  Canvas: TCanvas; // canvas used to measure text extent
                +begin
                +  MaxStrWidth := 0;
                +  Canvas := CreateDisplayCanvas(AFont);
                +  try
                +    for Str in AStrings do
                +    begin
                +      StrWidth := Canvas.TextExtent(Str).cx;
                +      if StrWidth > MaxStrWidth then
                +        MaxStrWidth := StrWidth;
                +    end;
                +    Result := AConverter(Canvas.Handle, MaxStrWidth);
                +  finally
                +    FreeDisplayCanvas(Canvas);
                +  end;
                +end;
                +
                 { Public routines }
                 
                 function CreateDisplayDC: HDC;
                @@ -144,6 +202,39 @@ function StringExtent(const S: string; const Font: TFont): TSize; overload;
                   end;
                 end;
                 
                +function MaxStringWidthTwips(const AStrings: array of string;
                +  const AFont: TFont): SmallInt;
                +begin
                +  Result := InternalMaxStringWidth(
                +    AStrings,
                +    AFont,
                +    function (CanvasHandle: HDC; MaxStrWidthPx: Integer): SmallInt
                +    var
                +      PxPerInchX: Integer;
                +    const
                +      TwipsPerInch = 1440;
                +    begin
                +      // convert pixels to twips
                +      PxPerInchX := GetDeviceCaps(CanvasHandle, LOGPIXELSX);
                +      Result := SmallInt(Round(MaxStrWidthPx * TwipsPerInch / PxPerInchX));
                +    end
                +  );
                +end;
                +
                +function MaxStringWidthPx(const AStrings: array of string; const AFont: TFont):
                +  SmallInt;
                +begin
                +  Result := InternalMaxStringWidth(
                +    AStrings,
                +    AFont,
                +    function (CanvasHandle: HDC; StrWidthPx: Integer): SmallInt
                +    begin
                +      // no conversion
                +      Result := SmallInt(StrWidthPx);
                +    end
                +  );
                +end;
                +
                 function GetTextRect(const Text: string; const Canvas: TCanvas;
                   const Rect: TRect; const Flags: Longint): TRect;
                   {Gets rectangle of size required to display text in a specified canvas.
                
                From 20714abdf4ce223880b9380f7f87dcdfa57f5916 Mon Sep 17 00:00:00 2001
                From: delphidabbler <5164283+delphidabbler@users.noreply.github.com>
                Date: Sat, 8 Apr 2023 17:15:39 +0100
                Subject: [PATCH 172/330] Fixed corrupted rich text formatted snippet report
                
                Precalculate longest compiler name and use that value to set a tab to
                ensure compiler table rows line up correctly. This value calculated
                using new routine added to UGraphUtil in previous commit.
                ---
                 Src/URTFSnippetDoc.pas | 44 +++++++++++++++++++++++++++++++++---------
                 1 file changed, 35 insertions(+), 9 deletions(-)
                
                diff --git a/Src/URTFSnippetDoc.pas b/Src/URTFSnippetDoc.pas
                index d947e4c01..d857a15be 100644
                --- a/Src/URTFSnippetDoc.pas
                +++ b/Src/URTFSnippetDoc.pas
                @@ -17,11 +17,9 @@ interface
                 
                 
                 uses
                -  // Delphi
                -  Graphics,
                   // Project
                   ActiveText.UMain, ActiveText.URTFRenderer, Hiliter.UGlobals, UEncodings,
                -  UIStringList, USnippetDoc, URTFBuilder, URTFStyles, URTFUtils;
                +  UIStringList, USnippetDoc, URTFBuilder, URTFStyles;
                 
                 
                 type
                @@ -128,9 +126,9 @@ implementation
                 
                 uses
                   // Delphi
                -  SysUtils,
                +  Graphics,
                   // Project
                -  Hiliter.UHiliters, UColours, UConsts, UPreferences, UStrUtils;
                +  Hiliter.UHiliters, UColours, UConsts, UGraphicUtils, UPreferences;
                 
                 
                 { TRTFSnippetDoc }
                @@ -316,9 +314,35 @@ procedure TRTFSnippetDoc.InitStyles;
                 
                 procedure TRTFSnippetDoc.RenderCompilerInfo(const Heading: string;
                   const Info: TCompileDocInfoArray);
                +
                +  // Calculate length of longest compiler name, in twips, when rendered on font
                +  // to be used to display them
                +  function MaxCompilerNameLenInTwips: SmallInt;
                +  var
                +    CompilerInfo: TCompileDocInfo;  // info about each compiler
                +    CompilerNames: IStringList;     // list of all compiler names
                +    Font: TFont;                    // font in which compile info displayed
                +  begin
                +    Font := TFont.Create;
                +    try
                +      Font.Name := MainFontName;
                +      Font.Size := ParaFontSize;
                +      CompilerNames := TIStringList.Create;
                +      for CompilerInfo in Info do
                +        CompilerNames.Add(CompilerInfo.Compiler);
                +      Result := MaxStringWidthTwips(CompilerNames.ToArray, Font);
                +    finally
                +      Font.Free;
                +    end;
                +  end;
                +
                 var
                -  Idx: Integer; // loops compiler information table
                +  CompilerInfo: TCompileDocInfo;  // info about each compiler
                +  TabStop: SmallInt;              // tab stop where compile result displayed
                 begin
                +  // Calculate tab stop where compile results are displayed
                +  TabStop := (MaxCompilerNameLenInTwips div IndentDelta) * IndentDelta + IndentDelta;
                +  // Display heading
                   fBuilder.SetFontStyle([fsBold]);
                   fBuilder.SetParaSpacing(
                     TRTFParaSpacing.Create(ParaSpacing, ParaSpacing / 3)
                @@ -328,13 +352,15 @@ procedure TRTFSnippetDoc.RenderCompilerInfo(const Heading: string;
                   fBuilder.EndPara;
                   fBuilder.ClearParaFormatting;
                   fBuilder.SetFontSize(ParaFontSize);
                -  for Idx := Low(Info) to High(Info) do
                +  // Display compiler table
                +  fBuilder.SetTabStops([TabStop]);
                +  for CompilerInfo in Info do
                   begin
                -    fBuilder.AddText(Info[Idx].Compiler);
                +    fBuilder.AddText(CompilerInfo.Compiler);
                     fBuilder.AddText(TAB);
                     fBuilder.BeginGroup;
                     fBuilder.SetFontStyle([fsItalic]);
                -    fBuilder.AddText(Info[Idx].Result);
                +    fBuilder.AddText(CompilerInfo.Result);
                     fBuilder.EndGroup;
                     fBuilder.EndPara;
                   end;
                
                From 6fbc69c44731ff3691b7da3d63d6ea6f993c091e Mon Sep 17 00:00:00 2001
                From: delphidabbler <5164283+delphidabbler@users.noreply.github.com>
                Date: Sat, 8 Apr 2023 22:04:20 +0100
                Subject: [PATCH 173/330] Add new IActiveText.HasContent method
                
                This method checks whether active text contains any text content. Added
                following changes to the active text document format because the
                existing IsEmpty returns false when a document contains only ekDocument
                elements or only empty block elements.
                ---
                 Src/ActiveText.UMain.pas | 17 +++++++++++++++++
                 1 file changed, 17 insertions(+)
                
                diff --git a/Src/ActiveText.UMain.pas b/Src/ActiveText.UMain.pas
                index 648420fee..19487745d 100644
                --- a/Src/ActiveText.UMain.pas
                +++ b/Src/ActiveText.UMain.pas
                @@ -190,6 +190,8 @@   TActiveTextAttrNames = record
                     ///  Checks if the active text object contains any elements.
                     ///  
                     function IsEmpty: Boolean;
                +    ///  Checks if the active text object has text content.
                +    function HasContent: Boolean;
                     ///  Checks if the active text object contains only plain text.
                     ///  
                     ///  Plain text is considered to be active text with no action
                @@ -504,6 +506,9 @@   TActiveText = class(TInterfacedObject,
                     ///  Checks if the element list is empty.
                     ///  Method of IActiveText.
                     function IsEmpty: Boolean;
                +    ///  Checks if the active text object has text content.
                +    ///  Method of IActiveText.
                +    function HasContent: Boolean;
                     ///  Checks if the active text object contains only plain text.
                     ///  
                     ///  
                @@ -869,6 +874,18 @@ function TActiveText.GetEnumerator: TEnumerator;
                   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;
                
                From 8e41ea9767b37438aa43b5a25731623ebf46a46b Mon Sep 17 00:00:00 2001
                From: delphidabbler <5164283+delphidabbler@users.noreply.github.com>
                Date: Sat, 8 Apr 2023 22:10:33 +0100
                Subject: [PATCH 174/330] Replace IActiveText.IsEmpty calls with HasContent
                
                Replaced the IsEmpty method with HasContent (with negated logic) where
                the IsEmpty method is no longer suitable following changes to active
                text document layout resulting with some truly empty code that should
                have been skipped being output.
                ---
                 Src/ActiveText.UHTMLRenderer.pas               | 2 +-
                 Src/DBIO.UXMLDataIO.pas                        | 2 +-
                 Src/FmSnippetsEditorDlg.FrActiveTextEditor.pas | 2 +-
                 Src/UCodeImportExport.pas                      | 2 +-
                 Src/UREMLDataIO.pas                            | 2 +-
                 Src/URTFSnippetDoc.pas                         | 3 ++-
                 Src/USnippetDoc.pas                            | 2 +-
                 Src/UTextSnippetDoc.pas                        | 3 ++-
                 8 files changed, 10 insertions(+), 8 deletions(-)
                
                diff --git a/Src/ActiveText.UHTMLRenderer.pas b/Src/ActiveText.UHTMLRenderer.pas
                index 8a00367b4..b246dde09 100644
                --- a/Src/ActiveText.UHTMLRenderer.pas
                +++ b/Src/ActiveText.UHTMLRenderer.pas
                @@ -174,7 +174,7 @@ function TActiveTextHTML.Render(ActiveText: IActiveText): string;
                   DestLines: IStringList;
                   DestLine: string;
                 begin
                -  if ActiveText.IsEmpty then
                +  if not ActiveText.HasContent then
                     Exit('');
                   Text := '';
                   fLevel := 0;
                diff --git a/Src/DBIO.UXMLDataIO.pas b/Src/DBIO.UXMLDataIO.pas
                index 2b1f1ebdf..ec5655760 100644
                --- a/Src/DBIO.UXMLDataIO.pas
                +++ b/Src/DBIO.UXMLDataIO.pas
                @@ -950,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/FmSnippetsEditorDlg.FrActiveTextEditor.pas b/Src/FmSnippetsEditorDlg.FrActiveTextEditor.pas
                index 13282fa4a..107430504 100644
                --- a/Src/FmSnippetsEditorDlg.FrActiveTextEditor.pas
                +++ b/Src/FmSnippetsEditorDlg.FrActiveTextEditor.pas
                @@ -257,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/UCodeImportExport.pas b/Src/UCodeImportExport.pas
                index 72d2d8efd..7ba46f2d0 100644
                --- a/Src/UCodeImportExport.pas
                +++ b/Src/UCodeImportExport.pas
                @@ -300,7 +300,7 @@ procedure TCodeExporter.WriteSnippet(const ParentNode: IXMLNode;
                     SnippetNode, cHighlightSource, IntToStr(Ord(Snippet.HiliteSource))
                   );
                   // extra info is written only if present
                -  if not Snippet.Extra.IsEmpty then
                +  if Snippet.Extra.HasContent then
                     fXMLDoc.CreateElement(
                       SnippetNode,
                       cExtraNode,
                diff --git a/Src/UREMLDataIO.pas b/Src/UREMLDataIO.pas
                index 526369a89..5e698383a 100644
                --- a/Src/UREMLDataIO.pas
                +++ b/Src/UREMLDataIO.pas
                @@ -735,7 +735,7 @@ class function TREMLWriter.Render(const ActiveText: IActiveText): string;
                   DestLine: string;
                   RW: TREMLWriter;
                 begin
                -  if ActiveText.IsEmpty then
                +  if not ActiveText.HasContent then
                     Exit('');
                   RW := TREMLWriter.InternalCreate;
                   try
                diff --git a/Src/URTFSnippetDoc.pas b/Src/URTFSnippetDoc.pas
                index d857a15be..fd37204f5 100644
                --- a/Src/URTFSnippetDoc.pas
                +++ b/Src/URTFSnippetDoc.pas
                @@ -398,7 +398,8 @@ procedure TRTFSnippetDoc.RenderExtra(const ExtraText: IActiveText);
                 var
                   RTFWriter: TActiveTextRTF;  // Object that generates RTF from active text
                 begin
                -  Assert(not ExtraText.IsEmpty, ClassName + '.RenderExtra: ExtraText is empty');
                +  Assert(ExtraText.HasContent,
                +    ClassName + '.RenderExtra: ExtraText has no content');
                   RTFWriter := TActiveTextRTF.Create;
                   try
                     RTFWriter.ElemStyleMap := fExtraStyles;
                diff --git a/Src/USnippetDoc.pas b/Src/USnippetDoc.pas
                index 0e293ec24..1167c6754 100644
                --- a/Src/USnippetDoc.pas
                +++ b/Src/USnippetDoc.pas
                @@ -177,7 +177,7 @@ function TSnippetDoc.Generate(const Snippet: TSnippet): TEncodedData;
                   RenderTitledList(sXRefListTitle, SnippetsToStrings(Snippet.XRef));
                   if Snippet.Kind <> skFreeform then
                     RenderCompilerInfo(sCompilers, CompilerInfo(Snippet));
                -  if not Snippet.Extra.IsEmpty then
                +  if Snippet.Extra.HasContent then
                     RenderExtra(Snippet.Extra);
                   if not Snippet.UserDefined then
                     // database info written only if snippet is from main database
                diff --git a/Src/UTextSnippetDoc.pas b/Src/UTextSnippetDoc.pas
                index b5119eae8..999619114 100644
                --- a/Src/UTextSnippetDoc.pas
                +++ b/Src/UTextSnippetDoc.pas
                @@ -154,7 +154,8 @@ procedure TTextSnippetDoc.RenderDescription(const Desc: IActiveText);
                 
                 procedure TTextSnippetDoc.RenderExtra(const ExtraText: IActiveText);
                 begin
                -  Assert(not ExtraText.IsEmpty, ClassName + '.RenderExtra: ExtraText is empty');
                +  Assert(ExtraText.HasContent,
                +    ClassName + '.RenderExtra: ExtraText has no content');
                   fWriter.WriteLine;
                   RenderActiveText(ExtraText);
                 end;
                
                From d802d649c67334596685a9106b5e08f58a0bf5e0 Mon Sep 17 00:00:00 2001
                From: delphidabbler <5164283+delphidabbler@users.noreply.github.com>
                Date: Sat, 8 Apr 2023 22:49:32 +0100
                Subject: [PATCH 175/330] Prevent snippets editor from stripping REML p tags
                
                If a single paragraph was entered in the snippet's editor's REML editor
                in form 

                content here

                , with no inline tags the

                tags would be stripped. This is undesirable because text outside of

                tags may be rendered differently to text in

                tags, so stripping the tags could change the rendering. This behaviour is changed by this commit and

                tags are now preserved. --- Src/ActiveText.UMain.pas | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Src/ActiveText.UMain.pas b/Src/ActiveText.UMain.pas index 19487745d..2edf2a7f6 100644 --- a/Src/ActiveText.UMain.pas +++ b/Src/ActiveText.UMain.pas @@ -195,8 +195,8 @@ TActiveTextAttrNames = record ///

                Checks if the active text object contains only plain text. /// /// 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. + /// elements except for "document" or "block". This can rendered in plain + /// text with no loss of formatting.
                function IsPlainText: Boolean; /// Checks if the active text object is a valid active text /// document. @@ -513,8 +513,8 @@ TActiveText = class(TInterfacedObject, /// /// /// 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. + /// elements except for "document" or "block". This can rendered in plain + /// text with no loss of formatting. /// Method of IActiveText. /// function IsPlainText: Boolean; @@ -899,7 +899,7 @@ function TActiveText.IsPlainText: Boolean; for Elem in fElems do begin if Supports(Elem, IActiveTextActionElem, ActionElem) - and not (ActionElem.Kind in [ekPara, ekDocument]) then + and not (ActionElem.Kind in [ekBlock, ekDocument]) then Exit(False); end; Result := True; From 222063429ccfd95d5cf60b9d9fb7de68b6cc1ec4 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 9 Apr 2023 12:40:47 +0100 Subject: [PATCH 176/330] Updated copyright dates to include 2023 Updated for all files that were updated to date in 2023 that had not already had the date updated. --- Docs/Design/reml.html | 2 +- Src/ActiveText.UHTMLRenderer.pas | 2 +- Src/ActiveText.UMain.pas | 2 +- Src/ActiveText.URTFRenderer.pas | 2 +- Src/ActiveText.UTextRenderer.pas | 2 +- Src/ActiveText.UValidator.pas | 2 +- Src/CodeSnip.dpr | 2 +- Src/Compilers.USettings.pas | 2 +- Src/DBIO.UXMLDataIO.pas | 2 +- Src/FmSnippetsEditorDlg.FrActiveTextEditor.pas | 2 +- Src/FrOverview.pas | 2 +- Src/Help/HTML/reml.htm | 2 +- Src/UCodeImportExport.pas | 2 +- Src/UConsts.pas | 2 +- Src/UEncodings.pas | 2 +- Src/UGraphicUtils.pas | 2 +- Src/UREMLDataIO.pas | 2 +- Src/URTFBuilder.pas | 2 +- Src/URTFSnippetDoc.pas | 2 +- Src/URTFStyles.pas | 2 +- Src/URTFUtils.pas | 2 +- Src/USnippetDoc.pas | 2 +- Src/USnippetExtraHelper.pas | 2 +- Src/USourceGen.pas | 2 +- Src/UStrUtils.pas | 2 +- Src/UTextSnippetDoc.pas | 2 +- 26 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Docs/Design/reml.html b/Docs/Design/reml.html index 51fb91de4..a84de371a 100644 --- a/Docs/Design/reml.html +++ b/Docs/Design/reml.html @@ -1,7 +1,7 @@ diff --git a/Src/UCodeImportExport.pas b/Src/UCodeImportExport.pas index 7ba46f2d0..7a197459e 100644 --- a/Src/UCodeImportExport.pas +++ b/Src/UCodeImportExport.pas @@ -3,7 +3,7 @@ * 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) 2008-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2008-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements classes that can import and export user defined snippets from and * to XML. diff --git a/Src/UConsts.pas b/Src/UConsts.pas index 449090022..5c658e071 100644 --- a/Src/UConsts.pas +++ b/Src/UConsts.pas @@ -3,7 +3,7 @@ * 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) 2009-2022, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2009-2023, Peter Johnson (gravatar.com/delphidabbler). * * Defines various character, string and resource id constants. } diff --git a/Src/UEncodings.pas b/Src/UEncodings.pas index f33dad412..78fc4aa54 100644 --- a/Src/UEncodings.pas +++ b/Src/UEncodings.pas @@ -3,7 +3,7 @@ * 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) 2009-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2009-2023, Peter Johnson (gravatar.com/delphidabbler). * * Provides support for certain character encodings used by the program. } diff --git a/Src/UGraphicUtils.pas b/Src/UGraphicUtils.pas index cd4dc1c87..00a6bb6ec 100644 --- a/Src/UGraphicUtils.pas +++ b/Src/UGraphicUtils.pas @@ -3,7 +3,7 @@ * 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) 2006-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2006-2023, Peter Johnson (gravatar.com/delphidabbler). * * Utility routines used for working with graphics. } diff --git a/Src/UREMLDataIO.pas b/Src/UREMLDataIO.pas index 5e698383a..6924ab76c 100644 --- a/Src/UREMLDataIO.pas +++ b/Src/UREMLDataIO.pas @@ -3,7 +3,7 @@ * 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) 2008-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2008-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements classes that render and parse Routine Extra Markup Language (REML) * code. This markup is used to read and store active text objects as used by diff --git a/Src/URTFBuilder.pas b/Src/URTFBuilder.pas index 64e8cf664..c0be9ede7 100644 --- a/Src/URTFBuilder.pas +++ b/Src/URTFBuilder.pas @@ -3,7 +3,7 @@ * 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) 2006-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2006-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements various classes used to create content of a rich text document. } diff --git a/Src/URTFSnippetDoc.pas b/Src/URTFSnippetDoc.pas index fd37204f5..b4f41d63a 100644 --- a/Src/URTFSnippetDoc.pas +++ b/Src/URTFSnippetDoc.pas @@ -3,7 +3,7 @@ * 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) 2008-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2008-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a class that renders a document that describes a snippet as rich * text. diff --git a/Src/URTFStyles.pas b/Src/URTFStyles.pas index 60e5dd89a..aa7d2fd2c 100644 --- a/Src/URTFStyles.pas +++ b/Src/URTFStyles.pas @@ -3,7 +3,7 @@ * 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-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2012-2023, Peter Johnson (gravatar.com/delphidabbler). * * Defines structures that encapsulate RTF styling elements. } diff --git a/Src/URTFUtils.pas b/Src/URTFUtils.pas index c7463e670..e567184cc 100644 --- a/Src/URTFUtils.pas +++ b/Src/URTFUtils.pas @@ -3,7 +3,7 @@ * 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) 2005-2022, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2023, Peter Johnson (gravatar.com/delphidabbler). * * Utility functions used when processing RTF. } diff --git a/Src/USnippetDoc.pas b/Src/USnippetDoc.pas index 1167c6754..17fbe309b 100644 --- a/Src/USnippetDoc.pas +++ b/Src/USnippetDoc.pas @@ -3,7 +3,7 @@ * 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) 2008-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2008-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements an abstract base class that renders a text document that describes * a snippet. Should be overridden by classes that generate actual documents in diff --git a/Src/USnippetExtraHelper.pas b/Src/USnippetExtraHelper.pas index f813d543a..03764bfc3 100644 --- a/Src/USnippetExtraHelper.pas +++ b/Src/USnippetExtraHelper.pas @@ -3,7 +3,7 @@ * 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) 2008-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2008-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a class that helps with parsing of a snippet's extra property as * active text and vice versa. diff --git a/Src/USourceGen.pas b/Src/USourceGen.pas index ccf94698a..3d9edf2a7 100644 --- a/Src/USourceGen.pas +++ b/Src/USourceGen.pas @@ -3,7 +3,7 @@ * 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) 2005-2022, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a class that is used to generate Pascal source code containing * specified database snippets. diff --git a/Src/UStrUtils.pas b/Src/UStrUtils.pas index b85d180d5..4e0e29584 100644 --- a/Src/UStrUtils.pas +++ b/Src/UStrUtils.pas @@ -3,7 +3,7 @@ * 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) 2011-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2011-2023, Peter Johnson (gravatar.com/delphidabbler). * * Unicode string utility routines. * diff --git a/Src/UTextSnippetDoc.pas b/Src/UTextSnippetDoc.pas index 999619114..5b80fd32b 100644 --- a/Src/UTextSnippetDoc.pas +++ b/Src/UTextSnippetDoc.pas @@ -3,7 +3,7 @@ * 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) 2009-2022, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2009-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a class that renders a document that describes a snippet as plain * text. From 52aaada83ea5278a799d88148b95f4f2d2f170f3 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 9 Apr 2023 12:42:33 +0100 Subject: [PATCH 177/330] Bump version number to v4.21.1 build 268 --- Src/VersionInfo.vi-inc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Src/VersionInfo.vi-inc b/Src/VersionInfo.vi-inc index f255c1655..ee01e5c42 100644 --- a/Src/VersionInfo.vi-inc +++ b/Src/VersionInfo.vi-inc @@ -1,8 +1,8 @@ # CodeSnip Version Information Macros for Including in .vi files # Version & build numbers -version=4.21.0 -build=267 +version=4.21.1 +build=268 # String file information copyright=Copyright © P.D.Johnson, 2005-. From 5b45ed275af0252b9ff71216b590a89650c87037 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 9 Apr 2023 12:50:25 +0100 Subject: [PATCH 178/330] Update "What's new" help topic --- Src/Help/HTML/new.htm | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/Src/Help/HTML/new.htm b/Src/Help/HTML/new.htm index daa6de623..274304981 100644 --- a/Src/Help/HTML/new.htm +++ b/Src/Help/HTML/new.htm @@ -81,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.

              @@ -186,6 +193,11 @@

              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] @@ -232,6 +244,9 @@

            required namespaces in the Namespaces tab of the Configure Compilers dialogue box. +
          1. + [v4.21.0] CodeSnip now detects newly installed Delphi compilers at start up. +

        Other Features From d4af4f3aa64c98109448fdb1d839998dcc1339bd Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 9 Apr 2023 12:50:57 +0100 Subject: [PATCH 179/330] Update change log with details of release v4.21.1 --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a922e7060..afae53dcf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,32 @@ 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.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]: From 57d70dbc7832e8aebbf3a1c5fdedb0a38767f042 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sat, 29 Apr 2023 08:33:57 +0100 Subject: [PATCH 180/330] Fix vulnerability in jQuery per dependabot alert Uses patch suggested at in GitHub Dependabot alert 1 https://github.com/delphidabbler/codesnip/security/dependabot/1 Fixes #107 --- Src/Res/Scripts/easteregg.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Src/Res/Scripts/easteregg.js b/Src/Res/Scripts/easteregg.js index 103bd4ef4..b74a93bcd 100644 --- a/Src/Res/Scripts/easteregg.js +++ b/Src/Res/Scripts/easteregg.js @@ -13,6 +13,12 @@ // Main function called when DOM has loaded. Runs prelinary animations up to // when introductory page is shown on unfolded screen $(document).ready(function(){ + + //! Fix vulnerability using patch suggested in dependabot alert + //! https://github.com/delphidabbler/codesnip/security/dependabot/1 + jQuery.htmlPrefilter = function( html ) { + return html; + }; var spt = null; // showPrompt timeout From f4d174805fcf7a1f4c2dd2532a0e9e3ac0ede4b3 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 30 Apr 2023 01:57:18 +0100 Subject: [PATCH 181/330] Add Src/Res/Img/AltBranding directory & images This directory provides public domain placeholder images that can be used in derived programs in place of CodeSnip's branding images that are prohibited from use in derived programs. There's also README.txt that explains the purpose of the directory. --- Src/Res/Img/AltBranding/CodeSnip.ico | Bin 0 -> 16572 bytes Src/Res/Img/AltBranding/README.txt | 48 +++++++++++++++++++++++++++ Src/Res/Img/AltBranding/Splash.gif | Bin 0 -> 3053 bytes Src/Res/Img/AltBranding/icon.gif | Bin 0 -> 157 bytes 4 files changed, 48 insertions(+) create mode 100644 Src/Res/Img/AltBranding/CodeSnip.ico create mode 100644 Src/Res/Img/AltBranding/README.txt create mode 100644 Src/Res/Img/AltBranding/Splash.gif create mode 100644 Src/Res/Img/AltBranding/icon.gif diff --git a/Src/Res/Img/AltBranding/CodeSnip.ico b/Src/Res/Img/AltBranding/CodeSnip.ico new file mode 100644 index 0000000000000000000000000000000000000000..a5a72e8e9309b3df6b49265c602d70b9b8f2c759 GIT binary patch literal 16572 zcmeI34@{J09LIk*@L(o!iGPSBj*`l`RAdYWu|v5C+EftEl^T$#Ac#PvDcZ>h5kxr{ zo6ShdRSVC8H8N!eZyIq~F==HsCm!9kU@*lOArCq4?Rm4?&fEQY-}m0XTkrPlc@N&- z@B8^Yz`OD8c>!eL59A>OC3MdKNJZz%mfio{8U^4l2T&^i`>X)?Y%zcW&E)k3s5JXI zfZB}JF9gjDn~6$;($myy$?uXQ5Y@uQ*ITy&%s7y)UXi`~>W$xxb$xpl81T68u!}nN zww|`1XLV@jWiA}u(S9#@$)B5=n!A+hnUS55Q;o0S!5JG1@;7{`t=isL`bp>b;^jwL zJKmjhI^o5kpGI*@$n{T$PTiiWFW*zPzM~>E<=eiiwY`xa5e8{Tu%c_b{oo`%Er;{?V-Q$xgyd4I94gImpjd#(Dh4e<;BP1aWxUq! z!Ala7Y%j-bAPWKvs;}~Q9iP6Z{SoyT9<*F)`TEW(n(Zhg(!V5EbNN=&g)0WkGH5OT zd+3{*8lwsdqHS&6*XwnN{2^R7I@TUz*zwjrB)z_d&^;NKs`l!yw2{kV3P$ptPxC>6 z$pEbc)#=Q+>X80L_|k-1SxFJ7NV&vzVEV7$>M&w9_B0&Bb5H8^c-_21Mra5ns-o&f zC;lih#lgbf{UiNzQBIFemTV36KFOh{1d@@cyGenOhVwC3?bhrqh?f!biOQs|$#Ffp zt_;6q9LQ?7bv~tC1Ix`=jz-gT^x3%xKiYP2@zE=__x$cd?i<@e1JbJpf{aFxDtWRs z4|;+hoIWUwUoqWl#BLufe>=+>T5;AgRNS0da`W13RSQ~4CdlLx2?>pdlvjbIru{zM<7o!7Rez7?);TMwwQ+}~H_`)v+2VePxc3^{FNC&p~ zg>qn%UkC@b`T2I>f}c+ZuK4+K;F6yY2d??KcHo1bO9#IAxpLr>p9=@R`Pp_5f}c$X zq4?Qy5R#t_2ch|Wbr1u;FAieiXX+p(ekKlLZ$bbR`0C` dm3mJ-sMfpdL8snT54!cvJ($!x_VD#4^$%uTM$-TQ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ea67c501cd09e253a14577155e012e53018d88a9 GIT binary patch literal 3053 zcmV zSRQRz9dBA4a$OyCUmbT~9eH9Mdt@AaWgLHI9A8o=T2nh)Q#@Q%JYQEmVNoGrQXpYc zBVtk_VpAt#QY2tfCt_7CVOc(7T0eDQBzR&Vd1NMJTR>)AL1PqXGw2oOmSvLacDiQ9EEEfhin{+ZyShh9E)%pj&U21a~qO% z8I7ss3z$( zpBKTG7{Zts&!HF5qZiYp7S^T~+Nl=Ys}|j?7T>KF;jb0ruodO873Z@R>$Vl?v=!>L z73{Ya@3|H3xft)fAo0RA@xwNZgJO(>WQ2iogo1X6gm!{~dWC~|g@k^IhkA*JevF8D zjf#AQhJ%HMg^7oNiiv}bi-C@egNcfUj*f?kiinMliiwMiiHweoj*O9zhLVztk&%y< zmXC^!kdBX$j**s)lA4i{kd>B`k&~5@m6w&5m64U2m6w>2mY$ZFo0XWInVX%Oou84H zp^=xSmYSiKo2HtbqM4qim7A%RovoRlshOXymY%VlqpF#pte&K}>xtOE9ouupsUEFugj*g&8V}_wYtQkvC^cm(xbB0r?u9ov(Tut(xS51rnTCjvfrh( z-Kw|Qs<_*!x!$0&;G?$Xq_^g&x#Fz6jG?W?}wM| zdR#zd768~FG^T_e)1-_4w@$wN(w;vzFi$%n2s`oc&C8#!at|1(8dh_l&?qZ>@&3>! z;D8v3Re)K|RWM0g|NTecgAg`k7ha29pq3|pMc3eX5q9XIAcrNkSWF|B^4NwOeyHMr zawtLJY7um{4~jIdXk&S&rRI?U9Hj6XjX>TgWN~o}5x@nD@%TbA{Q&9YkWi9_oNkHr z_MDSgZWZO0g<-dVVnGQI!2~;mmnD{5ra2dSW?4{Gd*_^arkZqmWgh|;P!&L06e!}K zoLSZ>=u!rn1%OZyRNx3bfC4(`qd47hmtq1ir&cF_HR>p&oT3yWrHMsQB0idG+Ucny zwV0Vu7M4KEXQeiN8X&5)R@8r1S#wSTdbwlDl9~674>75LaN0UXH4fD zE`l19tFVF`vdCMyfva-eMR0dhgA5b#*jB;jSZ;@^RsYA5PpHOX!?ftFNRCdE}DM<^~eE ze#8L^y3Dva&3J~G`=Sf zJYiicKxuCi{18s^)Gtq0+6VxE0dH1q0XOy4TMz%29$p-X!J%nY(1^m|D=)rRegn>( zr5+64{{8QBmG}VKGw%5P0L&j#C>Oe_l%N%w10eVWn3T^2U?{sn!7WUuzyLB3Cu<IRF5X3&c!Cv5ELILj?(TGUQ2>HwhfJ1RWee!tX6QgJmzZF0L2BV)P2&Y9T za#136Z~}!MD5ep7B2#7D;u#-8z^{dh1g=P69FI6hhP(lE1JG3mO^^!;?oo?!A8 zff`uDtZ;=^L|dnt2Wo%^+<>cIhqX}z{IEB=45>@?`cPWw;S0fZSEwk!Qo=eGRbl~L z!~k$bNkr4JgzeN$@nWzCW)l-Z#q3@mRn!8Ci3F%1r)bYAs5cxTS1~g|EU3EL%+je1 z^8}TJPQZ)7)|RPh3IrS+V7aQez!<*$EpVfh*3|6Et;tpHa)tDVmeBQqtl_~nq|05B zeyFcMtda(*Adc>K*G79ltoHcFL@m6-yyjJrOy%d#D@Nk7@a3+DHft!)egL%j&2NN? znixHu78Oek|8RT>d#E$F$k0WXr;rOFZt%M4k;Dn7}^+M}P#ONHCbgJQ_K?Hjtt#K_1sk zXF^m4w~X9X3UJX?IuqFtlfg|{GQdB8x_}rj_Op2bJ(v_5O>{M2MxqsM-a-h5CC=N8 zcy*8sN_+YrmX-^fa47(aR)HL!_Ow4j4Ho-IMX?sRViua5>Q6J;D^aEjqNr@;S}Xe2 zPSG+{*wf;HON7?flm_-v4pV0Rj3CX(Mz);?u@pCh(}1j)aI`7S6g+>eDktFcwzsVm z-0Uf0tZ6|oCOvK}3aLyCg{-(wt7Q_arpb-G;wu! zqlyJ}9Q@%aiL$QfhX{B){Ng8>5L9FG0ZAPD;`S!V#{MZPP<$EW2p7p^3cRlpq(a*$ z4>?A1^ldSnU>1Fj`M)n>?iKe&-R#ymw<&@gdR9dS%LuyAhaM3x=?Ewqu)#I{q4cFI zIwD>BNeX#Hg>G2=>xAA-$ek49u6gD2MrLsl}%+f40^wK0jOhr|qtY{hzB-%*q;F>dISMT{s!6 E0aQpnz5oCK literal 0 HcmV?d00001 From f32fa6c20aab8592b133aa9a22e28095c3074294 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 30 Apr 2023 02:26:26 +0100 Subject: [PATCH 182/330] Overhaul and correct main license file. Rewrite Source Code section to include all information from redundant LICENSE files. Modifications to some licenses. Add Public Domain CC0 license to Open Source Licenses section. Add DelphiDabbler Exclusive Use License to Proprietary Source Code section & license branding files and Docs/License.html using it. License files in new Src/Res/Img/AltBranding directory as Public Domain. Delete MPL2.0 boilerplate comments from license file: it is no longer MPL'd Some other corrections and minor edits. --- Docs/License.html | 402 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 282 insertions(+), 120 deletions(-) diff --git a/Docs/License.html b/Docs/License.html index f5cae9b16..131c7a549 100644 --- a/Docs/License.html +++ b/Docs/License.html @@ -1,20 +1,20 @@  - + CodeSnip License @@ -221,6 +221,12 @@ <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> @@ -237,159 +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 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, including third party code, - is available from the <a - href="https://github.com/delphidabbler/codesnip" - ><em>CodeSnip</em> GitHub repository</a>. + 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"> - <a href="#md5">MD5 License</a> + <a href="#tlistviewex">Vadim Crit's TListViewEx License</a> + </div> + <div class="applies-to"> + <kbd>Src/3rdParty/LVEx.pas</kbd>. </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/3rdParty/LVEx.res</kbd>. </div> </li> <li> <div class="license-name"> - <a href="#tlistviewex">Vadim Crit's TListViewEx License</a> + <a href="#jquery">jQuery License</a> </div> <div class="applies-to"> - Used by <kbd>Src/3rdParty/LVEx.pas</kbd> and - <kbd>Src/3rdParty/LVEx.res</kbd>. + <kbd>Src/Res/Scripts/3rdParty/jquery-1.12.4.min.js</kbd>. </div> </li> <li> <div class="license-name"> - <a href="#jquery">jQuery License</a> + <a href="#jquery-cycle">jQuery Cycle Lite Plugin MIT License</a> </div> <div class="applies-to"> - Used by <kbd>Src/Res/Scripts/3rdParty/jquery-1.8.0.min.js</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="#jquery-cycle">jQuery Cycle Lite Plugin License</a> + <a href="#CC-BY-SA-3.0">Creative Commons Attribution Share Alike 3.0 License</a> + </div> + <div class="applies-to"> + 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"> - Used by <kbd>Src/Res/Scripts/3rdParty/jquery.cycle.lite.js</kbd> + 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="#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"> + All files in the <kbd>Tests/Src/DUnit</kbd> directory. + </div> + </li> + <li> + <div class="license-name"> + <a href="#ddab-exclusive">DelphiDabbler Exclusive Use License</a> + </div> + <div class="applies-to"> + <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="https://github.com/delphidabbler/codesnip" - ><em>CodeSnip</em> GitHub 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-2023 by <a - href="https://gravatar.com/delphidabbler" - >Peter D Johnson</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> - This 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="https://delphidabbler.com/software/codesnip" - >https://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"> @@ -832,7 +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 /> + <hr> <h2 id="md5"> MD5 License @@ -859,7 +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 /> + <hr> <h2 id="tlistviewex"> Vadim Crit's TListViewEx License @@ -879,7 +932,7 @@ <h2 id="tlistviewex"> the reference to the original author.</li> </ol> - <hr /> + <hr> <h2 id="jquery"> jQuery License @@ -908,10 +961,10 @@ <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 @@ -937,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 @@ -1333,7 +1386,7 @@ <h3>Creative Commons Notice</h3> </aside> <!-- END CC NOTICE --> - <hr /> + <hr> <h2 id="CC-BY-2.5"> Creative Commons Attribution 2.5 License @@ -1606,7 +1659,7 @@ <h2 id="CC-BY-2.5"> >https://creativecommons.org/</a>.</p> </aside> - <hr /> + <hr> <h2 id="toolbar-icons-mit"> Toolbar Icons MIT License @@ -1638,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-2023, <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"> @@ -1706,11 +1868,11 @@ <h1> >https://www.jrsoftware.org/isinfo.php</a>. </li> <li> - Some program icons are based on the public domain PixelBox icon collection: + 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 + Some images are based on Florian Haag's Toolbar Icons set at <a href="https://toolbaricons.sourceforge.net/" >https://toolbaricons.sourceforge.net/</a>. </li> From 1446d998634fdffcae8ff1fceffdee2877f8bc4b Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 30 Apr 2023 02:16:46 +0100 Subject: [PATCH 183/330] Rename two LICENSE files Rename LICENSE file in repo root as LICENSE.md Rename Src/Res/Img/Branding/LICENSE as README.txt --- LICENSE => LICENSE.md | 0 Src/Res/Img/Branding/{LICENSE => README.txt} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename LICENSE => LICENSE.md (100%) rename Src/Res/Img/Branding/{LICENSE => README.txt} (100%) diff --git a/LICENSE b/LICENSE.md similarity index 100% rename from LICENSE rename to LICENSE.md diff --git a/Src/Res/Img/Branding/LICENSE b/Src/Res/Img/Branding/README.txt similarity index 100% rename from Src/Res/Img/Branding/LICENSE rename to Src/Res/Img/Branding/README.txt From 91e63e43d18e8cb84cab740b8576b2cab807c923 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 30 Apr 2023 02:35:37 +0100 Subject: [PATCH 184/330] Delete redundant LICENSE files. --- Docs/LICENSE | 9 --------- Src/3rdParty/LICENSE | 12 ------------ Src/AutoGen/LICENSE | 7 ------- Src/Help/Images/LICENSE | 5 ----- Src/Install/Assets/LICENSE | 9 --------- Src/LICENSE | 20 -------------------- Src/Res/Img/LICENSE | 16 ---------------- Src/Res/Scripts/3rdParty/LICENSE | 10 ---------- Tests/Src/DUnit/LICENSE | 3 --- 9 files changed, 91 deletions(-) delete mode 100644 Docs/LICENSE delete mode 100644 Src/3rdParty/LICENSE delete mode 100644 Src/AutoGen/LICENSE delete mode 100644 Src/Help/Images/LICENSE delete mode 100644 Src/Install/Assets/LICENSE delete mode 100644 Src/LICENSE delete mode 100644 Src/Res/Img/LICENSE delete mode 100644 Src/Res/Scripts/3rdParty/LICENSE delete mode 100644 Tests/Src/DUnit/LICENSE diff --git a/Docs/LICENSE b/Docs/LICENSE deleted file mode 100644 index 6fd5af54f..000000000 --- a/Docs/LICENSE +++ /dev/null @@ -1,9 +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 https://mozilla.org/MPL/2.0/. - -All files are Copyright (C) 2012-2021, Peter Johnson -(gravatar.com/delphidabbler). diff --git a/Src/3rdParty/LICENSE b/Src/3rdParty/LICENSE deleted file mode 100644 index e93ffd769..000000000 --- a/Src/3rdParty/LICENSE +++ /dev/null @@ -1,12 +0,0 @@ -Files in the Src/3rdParty directory are licensed as follows: - -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/AutoGen/LICENSE b/Src/AutoGen/LICENSE deleted file mode 100644 index db6ff5e35..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. -https://creativecommons.org/publicdomain/zero/1.0/ \ No newline at end of file diff --git a/Src/Help/Images/LICENSE b/Src/Help/Images/LICENSE deleted file mode 100644 index 731dda904..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 -(https://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/Install/Assets/LICENSE b/Src/Install/Assets/LICENSE deleted file mode 100644 index fe6bd5582..000000000 --- a/Src/Install/Assets/LICENSE +++ /dev/null @@ -1,9 +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 https://mozilla.org/MPL/2.0/. - -All files are copyright (C) 2012-2021, Peter Johnson -(gravatar.com/delphidabbler). diff --git a/Src/LICENSE b/Src/LICENSE deleted file mode 100644 index 29f642273..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 - https://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/Res/Img/LICENSE b/Src/Res/Img/LICENSE deleted file mode 100644 index 687e07ab3..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 -(https://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/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/Tests/Src/DUnit/LICENSE b/Tests/Src/DUnit/LICENSE deleted file mode 100644 index 349a54e77..000000000 --- a/Tests/Src/DUnit/LICENSE +++ /dev/null @@ -1,3 +0,0 @@ -All files in the Tests/Src/DUnit directory have any copyright dedicated to the -Public Domain. -https://creativecommons.org/publicdomain/zero/1.0/ \ No newline at end of file From c7c36ad6d1de2f7eb7ad24e00d6997319f88b4da Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 30 Apr 2023 03:21:03 +0100 Subject: [PATCH 185/330] Update Build.html Most changes relate to revised CodeSnip license. Add new "Conditions For Release of Modified Code" section to explain changes to code required to remove CodeSnip branding etc. Revise "Get the Source Code" sub-section and add note linking to above new section. Update source code tree to include new Src/Res/Img/AltBranding directory. Fix size of mono fonts used by code and pre tags. Fix minor typo --- Build.html | 160 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 115 insertions(+), 45 deletions(-) diff --git a/Build.html b/Build.html index b918791bf..f7c5479f7 100644 --- a/Build.html +++ b/Build.html @@ -21,6 +21,9 @@ body { font-family: sans-serif; } + code, pre { + font-size: 1rem; + } dt.spaced { margin-top: 0.5em; } @@ -157,7 +160,7 @@ <h3> </dt> <dd> Type library importer tool. Used to create a Pascal unit that describes - code contained in <code>ExternalObj.idl</code>. + code contained in <code>ExternalObj.ridl</code>. </dd> </dl> @@ -376,30 +379,34 @@ <h3> </h3> <p> - The source code is maintained in the <code><a href="https://github.com/delphidabbler/codesnip">delphidabbler/codesnip</a></code> Git respository on GitHub. + The source code is maintained in the <code><a href="https://github.com/delphidabbler/codesnip">delphidabbler/codesnip</a></code> Git respository on GitHub. Source code can be obtained in three ways: </p> -<p> - If you are intending to contribute code to the project you need to: -</p> - -<ol class="spaced"> - <li> - Fork the project on GitHub. - </li> +<ol> <li> - Create a new branch off the <code>development</code> branch. + <p> + Fork the project from GitHub and then clone your forked repository. + </p> </li> <li> - Make your changes on the branch you created. + <p> + Clone the existing repository using: + </p> + <pre class="cmd"><span class="prompt">></span> git clone https://github.com/delphidabbler/codesnip.git</pre> </li> <li> - Once finished raise a pull request for your code on the <code>delphidabbler/codesnip</code> repo. + <p> + Download the source of a specific release from the project's <a href="https://github.com/delphidabbler/codesnip/releases">Releases</a> section on GitHub – just choose the version you want. + </p> </li> </ol> <p> - If you only intend to use the code for your own purposes you can still fork the repository as above. Alternatively you can download the source code from the project's <a href="https://github.com/delphidabbler/codesnip/releases">Releases</a> section on GitHub – just choose the version you want. + If you are intending to contribute code to the project please read the most up to date version of the project's <a href="https://github.com/delphidabbler/codesnip/blob/develop/README.md">read-me file</a> before doing so. +</p> + +<p class="note"> + <strong>Important:</strong> If you are planning to fork <em>CodeSnip</em> 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 <em>CodeSnip</em> license. See the <a href="#conditions">Conditions For Release of Modified Code</a> section below for details. </p> <h3> @@ -412,53 +419,55 @@ <h3> <pre>./ | - +-- Docs - documentation + +-- Docs - documentation | | - | +-- Design - documents concerning program design + | +-- Design - documents concerning program design | | - | +-- FileFormats - documentation of CodeSnip's file formats + | +-- FileFormats - documentation of CodeSnip's file formats | - +-- Src - main CodeSnip source code + +-- Src - main CodeSnip source code | | - | +-- 3rdParty - third party & DelphiDabbler library source code + | +-- 3rdParty - third party & DelphiDabbler library source code | | - | +-- AutoGen - receives automatically generated code + | +-- AutoGen - receives automatically generated code | | - | +-- Help - help source files + | +-- Help - help source files | | | - | | +-- CSS - CSS code for help files + | | +-- CSS - CSS code for help files | | | - | | +-- HTML - HTML files included in help file + | | +-- HTML - HTML files included in help file | | | - | | +-- Images - images included in help file + | | +-- Images - images included in help file | | - | +-- Install - setup script and support code + | +-- Install - setup script and support code | | | - | | +-- Assets - files required for inclusion in install program + | | +-- Assets - files required for inclusion in install program | | - | +-- Res - container for files that are embedded in resources + | +-- Res - container for files that are embedded in resources | | - | +-- CSS - CSS files + | +-- CSS - CSS files | | - | +-- HTML - HTML files + | +-- HTML - HTML files | | - | +-- Img - image files + | +-- Img - image files + | | | + | | +-- AltBranding - image files used for 3rd party branding | | | - | | +-- Branding - image files used for CodeSnip branding + | | +-- Branding - image files used for CodeSnip branding only | | | - | | +-- Egg - image files for 'Easter Egg' + | | +-- Egg - image files for 'Easter Egg' | | - | +-- Misc - other resources + | +-- Misc - other resources | | - | +-- Scripts - scripting files + | +-- Scripts - scripting files | | - | +-- 3rdParty - 3rd party scripting files + | +-- 3rdParty - 3rd party scripting files | - +-- Tests - contains test code + +-- Tests - contains test code | - +-- Src - test source code + +-- Src - test source code | - +-- DUnit - test source code that uses the DUnit framework</pre> + +-- DUnit - test source code that uses the DUnit framework</pre> <p> If, by chance you also have a <code>_build</code> directory don't worry - all will become clear. Git users may also see the usual <code>.git</code> hidden @@ -497,13 +506,13 @@ <h3> <pre>./ | - +-- _build - contains all the build files + +-- _build - contains all the build files | | - | +-- bin - receives object files for CodeSnip + | +-- bin - receives object files for CodeSnip | | - | +-- exe - receives executable code and compiled help file + | +-- exe - receives executable code and compiled help file | | - | +-- release - receives release files + | +-- release - receives release files | ...</pre> @@ -861,14 +870,14 @@ <h2> </p> <p> - To compile the tests, open the <code>.\Src\CodeSnip.groupproj</code> group + To compile the tests, open the <code>Src\CodeSnip.groupproj</code> group project file in the Delphi XE IDE. Now select the <em>CodeSnipTests.exe</em> target in the project manager and compile. </p> <p> If they were not already present <code>Bin</code> and <code>Exe</code> - sub-directories will have been created in the <code>.\Tests</code> directory. + sub-directories will have been created in the <code>Tests</code> directory. The <code>Exe</code> directory contains the <em>DUnit</em> test program while <code>Bin</code> contains intermediate binaries. </p> @@ -876,7 +885,7 @@ <h2> <p> You can compile the tests as either a GUI application (default) or as a console application. For details please see the comments in - <code>.\Tests\Src\DUnit\CodeSnipTests.dpr</code>. + <code>Tests\Src\DUnit\CodeSnipTests.dpr</code>. </p> <h2> @@ -893,6 +902,67 @@ <h2> directory. </p> +<h2 id="conditions"> + Conditions For Release of Modified Code +</h2> + +<p> + If you are intending to release your own application based on the <em>CodeSnip</em> source code you <strong>must</strong> <em>either</em> change the source code as described below <em>or</em> seek written permission to use the DelphiDabbler CodeSnip branding. To seek such permission please use the <em>CodeSnip</em> <a href="https://github.com/delphidabbler/codesnip/issues">Issue Tracker</a> on GitHub. +</p> + +<h3> + Required Changes +</h3> + +<p> + 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 <strong>must</strong>: +</p> + +<ol> + <li> + <p> + Replace the files in the <code>Src\Res\Img\Branding</code> directory with copies of the identically named placeholder files in the <code>Src\Res\Img\AltBranding</code> 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 <code>Src\Res\Img\Branding</code> without copying the placeholder files across then <em>CodeSnip</em> will fail to build. + </p> + </li> + <li> + <p> + 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: + </p> + <ul> + <li> + Wherever the names may be displayed in the GUI. + </li> + <li> + Wherever the names may be displayed by the installer. + </li> + <li> + Wherever the names occur in directory names. In particular do not use the names in the program's <code>%ProgramData%</code> and <code>%AppData%</code> sub-directories. + </li> + <li> + Anywhere else that use of the names could imply endorsement of the modified code. + </li> + </ul> + </li> + <li> + <p> + Provide your own license file with content compatible with the requirements of the <em>CodeSnip</em> license as it relates to the code reused from the <em>CodeSnip</em> source tree. <strong>Do not</strong> edit or re-use <code>Docs/License.html</code>. + </p> + </li> + <li> + <p> + Modify source code and documentation where necessary to acknowledge the origins of the program's source code, documentation and images, in accordance with the <em>CodeSnip</em> license. + </p> + </li> +</ol> + +<p> + Note that the <em>CodeSnip</em> license can be found in <code>Docs\License.html</code>. +</p> + +<p> + If you are unsure about whether your changes meet the license requirements then you can seek clarification by creating an issue on the aforementioned <a href="https://github.com/delphidabbler/codesnip/issues">Issue Tracker</a>. +</p> + </body> </html> From e3d2540b898dfb43487fb21d64d3534424e03462 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 30 Apr 2023 03:56:35 +0100 Subject: [PATCH 186/330] Rewrite content of Src/Res/Img/Branding/README.txt Summarises why file in Src/Res/Img/Branding can't be used in forked versions of CodeSnip. --- Src/Res/Img/Branding/README.txt | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/Src/Res/Img/Branding/README.txt b/Src/Res/Img/Branding/README.txt index 96a458c5e..080124e97 100644 --- a/Src/Res/Img/Branding/README.txt +++ b/Src/Res/Img/Branding/README.txt @@ -1,8 +1,19 @@ -All image files in the Src/Res/Img/Branding directory are copyright -(C) 2012-2021, Peter Johnson (gravatar.com/delphidabbler). +About the Src/Res/Img/Branding directory +======================================== -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. +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. -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 +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. From 01ecc986ca597628ad0fb30b0f44e7c31cc44439 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 30 Apr 2023 04:12:06 +0100 Subject: [PATCH 187/330] Rewrite LICENSE.md This document provides a summary of the license and links to the full license text in Docs/License.html. --- LICENSE.md | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index 9074c751d..94996ab7c 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,20 +1,11 @@ -Licensing of CodeSnip's source and image files is on a per file basis. +# CodeSnip License -There are two ways that license information can be found: +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). -1) By examining comments within source files. License information will be at or - near the beginning of the file. +Much of CodeSnip's source code is released under the same license, although other open source licenses are also used. -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. +There are restrictions on using CodeSnip's branding in any independent forks of the program. - 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. +For definitive details see the [full license text](https://htmlpreview.github.io/?https://github.com/delphidabbler/codesnip/blob/master/Docs/License.html). -If any information is missing or incorrect please inform the author by filling -in a bug report at https://github.com/delphidabbler/codesnip/issues - -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 +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. From a199fcb042090d49cc89e997a9ffcaa520ba15d5 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 30 Apr 2023 04:23:42 +0100 Subject: [PATCH 188/330] Revise License section of README.md Rewrote section & added link to LICENSE.md. --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 61d3680c7..306676c40 100644 --- a/README.md +++ b/README.md @@ -93,9 +93,11 @@ The program's current change log can be found in the file [`CHANGELOG.md`](https ## License -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`](https://htmlpreview.github.io/?https://github.com/delphidabbler/codesnip/blob/master/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. +A summary of CodeSnip's license can be found in [`LICENSE.md`](https://github.com/delphidabbler/codesnip/blob/master/LICENSE.md). -Most of the original code is made available under the [Mozilla Public License v2](https://www.mozilla.org/MPL/2.0/). +The complete license text is in [`Docs\License.html`](https://htmlpreview.github.io/?https://github.com/delphidabbler/codesnip/blob/master/Docs/License.html). + +> The linked files are in the `master` branch and relate to the latest release. However, the license has changed between releases, so if you need to see an older version, select the appropriate `version-x.x.x` tag to find the appropriate file. 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 CodeSnip source in other projects. From c4b787162c405de5a6790f2fa47d8bf0b5528c9b Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 30 Apr 2023 09:51:09 +0100 Subject: [PATCH 189/330] Fix macro naming error in .vi files Fixed typo <%ver.license> macro name (was <%var.license>). Fixes #106 --- Src/VCodeSnip.vi | 4 ++-- Src/VCodeSnipPortable.vi | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Src/VCodeSnip.vi b/Src/VCodeSnip.vi index 8dc6fb9de..ef0029546 100644 --- a/Src/VCodeSnip.vi +++ b/Src/VCodeSnip.vi @@ -2,7 +2,7 @@ ; 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) 2008-2022, Peter Johnson (gravatar.com/delphidabbler). +; Copyright (C) 2008-2023, Peter Johnson (gravatar.com/delphidabbler). ; ; Version information description file for CodeSnip. @@ -24,7 +24,7 @@ Language=2057 Character Set=1252 [String File Info] -Comments=<%var.license> +Comments=<%ver.license> Company Name=<%ver.company> File Description=<%ver.description> (Standard Edition) File Version=<#F1>.<#F2>.<#F3> build <#F4> diff --git a/Src/VCodeSnipPortable.vi b/Src/VCodeSnipPortable.vi index 90646ad38..7cb4de423 100644 --- a/Src/VCodeSnipPortable.vi +++ b/Src/VCodeSnipPortable.vi @@ -2,7 +2,7 @@ ; 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-2022, Peter Johnson (gravatar.com/delphidabbler). +; Copyright (C) 2012-2023, Peter Johnson (gravatar.com/delphidabbler). ; ; Version information description file for the portable edition of CodeSnip @@ -24,7 +24,7 @@ Language=2057 Character Set=1252 [String File Info] -Comments=<%var.license> +Comments=<%ver.license> Company Name=<%ver.company> File Description=<%ver.description> (Portable Edition) File Version=<#F1>.<#F2>.<#F3> build <#F4> From 31fd34f197d50aa3e59291aca969a7e1786fee9e Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 30 Apr 2023 10:15:20 +0100 Subject: [PATCH 190/330] Remove broken web links from About Box Removed broken links for "Silk Icon Set" and "16x16 Free Application Icons" attributions. Added <span> tags with title "broken link removed" to relevant attributions. --- Src/Res/HTML/dlg-about-program-tplt.html | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/Src/Res/HTML/dlg-about-program-tplt.html b/Src/Res/HTML/dlg-about-program-tplt.html index e50a6a249..63a3a2cd4 100644 --- a/Src/Res/HTML/dlg-about-program-tplt.html +++ b/Src/Res/HTML/dlg-about-program-tplt.html @@ -77,30 +77,27 @@ </li> <li> <div> - <em>CodeSnip</em> makes use of images from the following icon + <em>CodeSnip</em> makes use of images from the following image collections: </div> <ul> <li> - <a - href="http://www.famfamfam.com/lab/icons/silk/" - class="external-link" - >Silk Icon Set 1.3</a> by Mark James. + <span title="broken link removed">Silk Icon Set 1.3</span> by Mark + James. </li> <li> - Silk Companion 1 by Damien Guard. + <span title="broken link removed">Silk Companion 1</span> by Damien + Guard. </li> <li> - Led Icon Set v1.0. + <span title="broken link removed">Led Icon Set v1.0</span>. </li> <li> - 16x16-free-application-icons by <a - href="https://www.aha-soft.com" - class="external-link" - >Aha-Soft</a>. + <span title="broken link removed">16×16 Free Application + Icons</span> by Aha-Soft. </li> <li> - PixelBox icon collection. + <span title="broken link removed">PixelBox icon collection</span>. </li> <li> <a From 8cdcd0e7bccc12484a6baad475bc246d24772c54 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Mon, 1 May 2023 20:29:05 +0100 Subject: [PATCH 191/330] Add CONTRIBUTING.md Provides information on how to contribute to the project. Includes a code of conduct. --- CONTRIBUTING.md | 195 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..8531ff7e8 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,195 @@ +# Contributing + +Thanks for you interest in contributing to the CodeSnip project. + +## Contents + +* [Overview](#overview) +* [Issues](#issues) +* [Documentation](#documentation) +* [Coding](#coding) +* [About Pull Requests](#about-pull-requests) +* [Licensing of Contributions](#licensing-of-contributions) +* [Code of Conduct](#code-of-conduct) +* [Attributions](#attributions) + +## Overview + +Contributions of all kinds are more than welcome. + +> 💡 You will need a GitHub account to be able to make contributions. [Sign up here](https://docs.github.com/en/get-started/signing-up-for-github/signing-up-for-a-new-github-account). + +There three main ways in which you can contribute, each of which is explained in its own section below. From easiest to hardest, they are: + +* [Issues](#issues) - report bugs and request new features. +* [Documentation](#documentation) - improve the project's documentation. +* [Coding](#coding) - modify the project's source code. + +Regardless of how you choose to contribute, please respect the project's [code of conduct](#code-of-conduct). + +## Issues + +The easiest way to make a contribution is to [create an issue](https://github.com/delphidabbler/codesnip/issues/new). + +You can use issues to: + +1. [Report a bug](#reporting-a-bug) +2. [Request a new program feature](#requesting-a-new-feature) +3. [Suggest changes to documentation](#suggesting-documentation-changes) + +It is helpful if you perform a cursory search of [existing issues](https://github.com/delphidabbler/codesnip/issues?q=is%3Aissue) to see if there is already a similar issue. If so then please add your thoughts as comments on that issue rather than open a new one. + +### Reporting a bug + +Before reporting a bug please make sure the bug exists in the [latest release](https://github.com/delphidabbler/codesnip/releases) of CodeSnip 4. + +To report a bug please provide as much information about the bug as possible, including the program's version number, what happened, what you expected to happen and what you were doing at the time. If possible, explain how to reproduce the bug. + +> 💡 You can find CodeSnip's version number in the program's About box (_Help | About_ menu option). + +If you have resolved an issue yourself please consider contributing your fix so that others can benefit from your work. Please read the [Coding](#coding) section before doing so. + +### Requesting a new feature + +If you think of a feature that you would like to see added to CodeSnip you can open an issue to request it. + +Please be as clear as possible about what you expect the feature to do and why you want it. + +Ideas about how to implement the feature are welcome. Even better, if you have implemented the feature yourself, you may be able to contribute the code. But please read the [Coding](#coding) section before doing so. + +### Suggesting documentation changes + +The third reason to create an issue is if you want to suggest new documentation or ammendments to existing documentation. + +There are two types of documentation within the CodeSnip repository. Firstly, there is general documentation that can be found in the [repository root](https://github.com/delphidabbler/codesnip/tree/develop) and in the [`Docs`](https://github.com/delphidabbler/codesnip/tree/develop/Docs) directory. Secondly, there are numerous HTML help files in the [`Src/Help/HTML`](https://github.com/delphidabbler/codesnip/tree/develop/Src/Help/HTML) directory. + +If you have written or corrected some documentation yourself please consider submitting it. See the [Documentation](#documentation) section below to find out how to do this. + +## Documentation + +Writing and editing documentation is a relatively easy way to start contributing. Providing simple clear or helpful documentation for users is critical. Things that *you* found hard to understand as a user, or difficult to work out, are excellent places to begin. + +There are two ways to contribute documentation. The preferred method is by means of a [pull request](#about-pull-requests). But, you can simply create an issue and attach documentation files to the issue comments. This approach is perfectly acceptable for a small number of files, and saves you having to install Git! + +## Coding + +Code contributions to the [CodeSnip 4 development tree](https://github.com/delphidabbler/codesnip/tree/develop) are always welcome, from fixing bugs to developing new features. + +> 😠Before going any further, let's address the elephant in the room. CodeSnip has to be compiled with the now rather ancient Delphi XE. It would be much better if development could move to a more recent version of Delphi, but that's proving to be extremely problematic. See [this FAQ](https://github.com/delphidabbler/codesnip-faq/blob/master/SourceCode.md#faq-11) for an explanation. + +If you're still here, you will need to set up a valid build environment in order to compile CodeSnip from source. [`Build.html`](https://htmlpreview.github.io/?https://github.com/delphidabbler/codesnip/blob/develop/Build.html) tells you everything you need to know about configuring the environment, the tools you will need, and how compile CodeSnip using the provided `Makefile`. + +> 💡 Need ideas? You can [browse open issues](https://github.com/delphidabbler/codesnip/issues) and see if you can help with any of them. + +Contributions should normally be made using [pull requests](#about-pull-requests). + +But, if you have just a few lines of code to suggest it _may_ be possible to accept the code within (or attached to) a comment on an issue. If in doubt, ask first by means of a comment. When including the code in a comment, please use [GitHub code fencing](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks) to format the code and make it easier to read. For example: + + ```pascal + procedure TFoo.CoolNewMethod; + begin + // cool code here + end; + ``` + +## About Pull Requests + +All pull requests *must* relate to an [open _accepted_ issue](https://github.com/delphidabbler/codesnip/issues?q=is%3Aissue+label%3Aaccepted++is%3Aopen+) on GitHub. + +If you wish to contribute code for which there is no existing issue you can create one. Please wait for the issue to be accepted by the project owner before submitting your pull request. Acceptance is indicated by attaching an "accepted" label to the issue and is signed off by means of a comment on the issue from the project owner. + +> 💡 Should you get stuck at any point please ask for help by leaving a comment on the relevant issue. + +Before you can start you need to get the source code. Here's how: + +1. Fork the CodeSnip project into your own GitHub repository. + + â­***Important***â­ _Ensure that all remote branches are included in the fork_, not just `master`. On GitHub this means clearing the "Copy the `master` branch only" check box before creating the fork. + +2. Clone the forked repository onto your own system using: + + $ git clone https://github.com/<account>/codesnip.git + + where `<account>` is your GitHub user account ID. + +CodeSnip uses the [Git Flow](https://nvie.com/posts/a-successful-git-branching-model/) methodology. This means that you need to create your source code on a "feature" branch off the `develop` branch. Here's how: + +1. Pull the `develop` branch from your forked repository, and switch into it: + + $ git pull origin develop + $ git switch develop + +2. Create and switch to a new feature branch, branched off `develop`. + + Name the branch `feature/` followed by the issue number to which it relates. Appending a hyphen followed by a brief description is useful too. Example branch names are `feature/42-fix-ui-bug` or `feature/56`: + + $ git branch feature/42-fix-ui-bug + $ git switch feature/42-fix-ui-bug + +3. Make your changes, whether to source code or documentation, and commit them to your feature branch. Clarity is prefered to terseness in commit messages. Please don't squash logically unrelated commits. + +4. If you are editing source code you need to ensure that it compiles and works as expected. + + > 💡 Having trouble? Please make sure you have read [`Build.html`](https://htmlpreview.github.io/?https://github.com/delphidabbler/codesnip/blob/develop/Build.html). If that doesn't solve your problem then ask for help by creating a comment on the relevant issue. + +Once you have finished writing the code and are ready to submit it you need to prepare your forked repository: + +1. Use GitHub to ensure that the `develop` branch of your fork is up to date with the parent `delphidabbler/codesnip` repository's `develop` branch. + +2. Rebase your changes onto your forked repository's remote `develop` branch: + + $ git rebase origin/develop feature/42-fix-ui-bug + +3. Resolve any conflicts arising from the rebase then push your changes to your forked repository: + + $ git push origin feature/42-fix-ui-bug + +You are finally ready to open that pull request. Proceed as follows: + +1. Open your forked repository on GitHub. You should see a banner saying something like "feature/42-fix-ui-bug had recent pushes ..." along with a button that says "Compare and Pull Request". Click that button. + +2. You will be taken the "Comparing Changes" page on `delphidabbler/codesnip`. + + â­***Important***â­ Just below the page title is a line of drop-down buttons where you choose what to compare. Check the caption of the 2nd button from the left. If it reads anything other than "base: `develop`" (and it probably doesn't) then click the button and select `develop` from the list of branches that is displayed. + + > âš ï¸ Pull requests based on `master`, or any branch other than `develop`, will not be accepted. + +3. Now complete the form and click the "Create Pull Request" button. + +That's it. + +Sit back and wait. Either your pull request will be accepted or you will receive comments requesting clarification or changes. + +Once your pull request is accepted you can delete the feature branch from both your local and forked remote fork. + +## Licensing of Contributions + +The license that applies to any existing file you edit will continue to apply to the edited file. Any existing license text or copyright statement **must not** be altered or removed, but you can add your own copyright line if desired. + +Any new file you contribute **must** either be licensed under the [Mozilla Public License v2.0](https://www.mozilla.org/MPL/2.0/) (MPL2) or have a license compatible with the MPL2. If a license is not specified then MPL2 will be assumed and will be applied to the file. You should insert a suitable copyright statement in the file. + +Any third party code used by your contributed code **must** also have a license compatible with the MPL2. + +> 💡 MPL2 boilerplate text, in several programming language's comment formats, can be found in the file [`Docs/MPL-2.0-Boilerplate.txt`](https://raw.githubusercontent.com/delphidabbler/codesnip/master/Docs/MPL-2.0-Boilerplate.txt). You will need to change the name of the copyright holder. + +## Code of Conduct + +This is a simple and straightforward code of conduct. What it boils down to is that if you're a decent person you're more than welcome here. Hateful, abusive and just plain rude people are not. + +To be more precise, anyone who contributes to the CodeSnip project is expected to display respect, empathy and politeness towards others. + +Certain behaviours will not be tolerated, and will result in an immediate ban. They are: + +* Racism and hate speech in any form. +* The use of sexualized language or imagery, and sexual attention or advances of any kind. +* Intolerance towards people of any religion or none. +* Trolling, insulting or derogatory comments, and personal or political attacks. +* Public or private harassment. +* Publishing others' private information, such as a physical or email address, without their explicit permission. +* Other conduct which could reasonably be considered inappropriate in a professional setting. + +## Attributions + +1. The list of unacceptable behaviours in the Code of Conduct was taken, in part, from the "Our Standards" section of the [Contributor Covenant Code of Conduct v2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct/). + +2. Some of the content of this document was inspired by and/or copied from the [Stumpy Contributing Guide](https://stumpy.readthedocs.io/en/latest/Contribute.html). \ No newline at end of file From 9d1311be95a4bed9d2005ae27c82e22dd907eb0f Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Tue, 2 May 2023 07:54:43 +0100 Subject: [PATCH 192/330] Heavy overhaul of README.md Much content revised, rewritten and restructured. Some content replaced by references to CONTRIBUTING.md. Footnotes revised to use GitHub Markdown's footnote feature to place them at the end of the document. --- README.md | 85 +++++++++++++++++++++---------------------------------- 1 file changed, 33 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 306676c40..8672ddf64 100644 --- a/README.md +++ b/README.md @@ -6,103 +6,84 @@ A code bank designed with Pascal in mind. * [Installation](#installation) * [Support](#support) * [Source Code](#source-code) +* [Compiling](#compiling) +* [Contributing](#contributing) * [Change Log](#change-log) * [License](#license) -* [Bug Reports and Features](#bug-reports-and-features) +* [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 focused mainly on Pascal and Delphi code for which additional features are available. -CodeSnip can import code from the DelphiDabbler [Code Snippets Database](https://github.com/delphidabbler/code-snippets). - The program is available in both standard and portable editions. -CodeSnip requires Windows 2000 or later and Internet Explorer 6 or later, although XP and IE 8 and later are recommended. +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 The standard edition of CodeSnip is installed and removed using a Windows installer. Administrator privileges are required for installation. -The portable edition has no installer. Simply follow the instructions in the [read me file](https://raw.githubusercontent.com/delphidabbler/codesnip/master/Docs/ReadMe.txt) that is included in the download zip file. +The portable edition has no installer. Simply follow the instructions in the [read me file](https://raw.githubusercontent.com/delphidabbler/codesnip/master/Docs/ReadMe.txt) that is included in the download. + +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 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 [Using CodeSnip FAQ](https://github.com/delphidabbler/codesnip-faq/blob/master/UsingCodeSnip.md). -* A [Blog](https://codesnip-app.blogspot.co.uk/). +* A [read-me file](https://raw.githubusercontent.com/delphidabbler/codesnip/master/Docs/ReadMe.txt) that discusses installation, configuration, updating and known issues. [^1] +* The [Using CodeSnip FAQ](https://github.com/delphidabbler/codesnip-faq/blob/master/UsingCodeSnip.md). +* The [CodeSnip Blog](https://codesnip-app.blogspot.co.uk/). +* 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> This link takes you to the most recent version of the read-me file -- it 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 -CodeSnip's source code is maintained in the [`delphidabbler/codesnip`](https://github.com/delphidabbler/codesnip) Git repository on GitHub†. - -The [Git Flow](https://nvie.com/posts/a-successful-git-branching-model/) methodology has been adopted, with the exception of some experimental branches. - -The following branches existed as of 2022/12/03: - -* [`master`](https://github.com/delphidabbler/codesnip/tree/master): Always reflects the state of the source code as of the latest release.‡ -* [`develop`](https://github.com/delphidabbler/codesnip/tree/develop): Main development branch. The head of this branch contains the latest v4 development code. Normal development of CodeSnip 4 takes place in `feature/xxx` branches off `develop`. -* [`caboli`](https://github.com/delphidabbler/codesnip/tree/caboli): Experimental branch where an attempt is being made to (a) modernise the UI and (b) get the code to work properly when compiled with Delphi 11. -* Abandoned branches: - * [`pagoda`](https://github.com/delphidabbler/codesnip/tree/pagoda): An abortive attempt at developing CodeSnip 5. - * [`pavilion`](https://github.com/delphidabbler/codesnip/tree/pavilion): Another attempt at working on CodeSnip 5 that branched off `pagoda`. - * [`belvedere`](https://github.com/delphidabbler/codesnip/tree/belvedere): A thiird, failed attempt to develop CodeSnip 5 as a ground up rewrite. Not related to `pagoda` & `pavilion`. - -> † 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/). +CodeSnip's source code is maintained in the [`delphidabbler/codesnip`](https://github.com/delphidabbler/codesnip) Git repository on GitHub. [^2] -> ‡ 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. +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: -### Contributions +* [`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. -To contribute to CodeSnip 4 development please fork the repository on GitHub. Create a feature branch off the `develop` branch. Make your changes to your feature branch then submit a pull request via GitHub. +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. -:warning: **Do not create branches off `master`, always branch from `develop`.** +> [^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/). -:no_entry: Contributions to experimental branches are not being excepted just now. +> [^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. -#### Licensing of contributions +## Compiling -The license that applies to any existing file you edit will continue to apply to the edited file. Any existing license text or copyright statement **must not** be altered or removed. +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. -Any new file you contribute **must** either be licensed under the [Mozilla Public License v2.0](https://www.mozilla.org/MPL/2.0/) (MPL2) or have a license compatible with the MPL2. If a license is not specified then MPL2 will be assumed and will be applied to the file. You should insert a suitable copyright statement in the file. +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). -Any third party code used by your contributed code **must** also have a license compatible with the MPL2. +## Contributing -> MPL2 boilerplate text, in several programming language's comment formats, can be found in the file [`Docs/MPL-2.0-Boilerplate.txt`](https://raw.githubusercontent.com/delphidabbler/codesnip/master/Docs/MPL-2.0-Boilerplate.txt). You will need to change the name of the copyright holder. +Please see [`CONTRIBUTING.md`](https://github.com/delphidabbler/codesnip/blob/develop/CONTRIBUTING.md) for details of how to contribute to the CodeSnip project. -### Compiling - -`master` has a file in the root directory named [`Build.html`](https://htmlpreview.github.io/?https://github.com/delphidabbler/codesnip/blob/master/Build.html) that gives detailed information about how to compile the current release of CodeSnip 4. - -There is also a [Compiling & Source Code FAQ](https://github.com/delphidabbler/codesnip-faq/blob/master/SourceCode.md). - -CodeSnip 4 **must** be compiled with Delphi XE. See [Compiling & Source Code FAQ 11](https://github.com/delphidabbler/codesnip-faq/blob/master/SourceCode.md#faq-11) for the reason why. +â›” Contributions to experimental and abandoned branches are not accepted. ## Change Log -The program's current change log can be found in the file [`CHANGELOG.md`](https://github.com/delphidabbler/codesnip/blob/master/CHANGELOG.md) in the root of the `master` branch. +The change log can be found in the file [`CHANGELOG.md`](https://github.com/delphidabbler/codesnip/blob/master/CHANGELOG.md). [^4] -> Note that 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. +> [^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. ## License -A summary of CodeSnip's license can be found in [`LICENSE.md`](https://github.com/delphidabbler/codesnip/blob/master/LICENSE.md). - -The complete license text is in [`Docs\License.html`](https://htmlpreview.github.io/?https://github.com/delphidabbler/codesnip/blob/master/Docs/License.html). - -> The linked files are in the `master` branch and relate to the latest release. However, the license has changed between releases, so if you need to see an older version, select the appropriate `version-x.x.x` tag to find the appropriate file. +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] -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 CodeSnip source in other projects. +> [^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. -## Bug Reports and Features +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. -You can report bugs or request new features using the [Issues section](https://github.com/delphidabbler/codesnip/issues) of the CodeSnip GitHub project. You will need a GitHub account to do this. +## Bug Reports and Feature Requests -Please do not report bugs unless you have checked whether the bug exists in the latest version of the program. +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. From b1cf6f3f1ce5b521a59d1210753cce26c7ccd7a6 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Tue, 2 May 2023 22:52:22 +0100 Subject: [PATCH 193/330] Correct, update and edit Docs/ReadMe.txt --- Docs/ReadMe.txt | 62 +++++++++++++++++++++++++------------------------ 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/Docs/ReadMe.txt b/Docs/ReadMe.txt index 3346f91af..a2020254e 100644 --- a/Docs/ReadMe.txt +++ b/Docs/ReadMe.txt @@ -48,14 +48,15 @@ Installation ================================================================================ CodeSnip requires Windows 2000 or later. It also requires MS Internet Explorer 6 -or later, but IE 8, 9 or 10 are strongly recommended. +or later, although IE 8, 9 or 10 are strongly recommended. But note that recent +releases have only been tested on Windows 10/11. 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 to Windows 10 will +should run setup as administrator. By default Windows Vista to Windows 11 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. @@ -66,8 +67,8 @@ 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. +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. @@ -80,9 +81,9 @@ The installer makes the following changes to your system: + Files required by the uninstaller are stored in the main installation's Uninst sub-folder. -+ The program's uninstall information is registered with the "Apps and Features" - (a.k.a. "Programs and Features", a.k.a. "Add / Remove Programs") control panel - applet. ++ 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). @@ -113,9 +114,10 @@ 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. +1) Mount any storage medium on which you want to install CodeSnip. -2) Create a folder on the storage medium in which to copy the required files. +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. @@ -143,12 +145,13 @@ Uninstallation Uninstalling the Standard Edition --------------------------------- -CodeSnip can be uninstalled via "Apps and Features" (a.k.a. "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. +CodeSnip can be uninstalled via "Installed Apps" (a.k.a. "Apps and Features", +a.k.a. "Programs and Features", a.k.a. "Add / Remove Programs") accessed 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 -to Windows 10 with UAC prompts enabled will prompt for an admin password if +to Windows 11 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 @@ -205,12 +208,15 @@ Windows 32 version of Delphi (from Delphi 2 to Delphi 11.x Alexandria) 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. 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. +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 @@ -221,7 +227,7 @@ 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-1 +https://github.com/delphidabbler/codesnip-faq/blob/master/UsingCodeSnip.md#faq-7 Any type of snippet other than "freeform" can be test compiled. @@ -229,11 +235,8 @@ Any type of snippet other than "freeform" can be test compiled. Updating the Program ================================================================================ -Updates are published on: - -+ GitHub: https://github.com/delphidabbler/codesnip/releases - -+ SourceForge: https://sourceforge.net/projects/codesnip/files/ +Updates are published on GitHub. See +https://github.com/delphidabbler/codesnip/releases News of new updates is published on the CodeSnip Blog: https://codesnip-app.blogspot.com/. @@ -324,9 +327,8 @@ 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 version 4.15.1 and earlier 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. +Please ensure that you have installed the latest version of CodeSnip and checked +if the bug is still present before reporting it. Feedback @@ -378,8 +380,8 @@ Thanks to: + 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). If the list is empty - then updating the Code Snippets Database will download the details. + menu option then select the "About the Database" tab). The list will be empty + if the Code Snippets Database has not been installed. ================================================================================ From 55496f2b225f01be5f055993ad4caf55b97b5318 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 13 Jul 2023 10:06:10 +0100 Subject: [PATCH 194/330] Update copyright date in header comments. Date changed 2023 since these files were updated in the year. --- Build.html | 2 +- Src/Help/HTML/new.htm | 2 +- Src/Res/Scripts/easteregg.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Build.html b/Build.html index f7c5479f7..7176db3ff 100644 --- a/Build.html +++ b/Build.html @@ -6,7 +6,7 @@ * 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) 2009-2022, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2009-2023, Peter Johnson (gravatar.com/delphidabbler). * * Instructions for building CodeSnip from source. --> diff --git a/Src/Help/HTML/new.htm b/Src/Help/HTML/new.htm index 274304981..336bcd45c 100644 --- a/Src/Help/HTML/new.htm +++ b/Src/Help/HTML/new.htm @@ -4,7 +4,7 @@ * 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-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2012-2023, Peter Johnson (gravatar.com/delphidabbler). * * Help topic listing key new features of CodeSnip 4. --> diff --git a/Src/Res/Scripts/easteregg.js b/Src/Res/Scripts/easteregg.js index b74a93bcd..f15128421 100644 --- a/Src/Res/Scripts/easteregg.js +++ b/Src/Res/Scripts/easteregg.js @@ -3,7 +3,7 @@ * 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) 2009-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2009-2023, Peter Johnson (gravatar.com/delphidabbler). * * JavaScript code used to perform animations in program's easter egg. * Requires jQuery and lite version of jQuery Cycle plug-in. From 7cef4dc2289e0945ea59e1b70d2195e0b4b973ac Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 13 Jul 2023 10:09:32 +0100 Subject: [PATCH 195/330] Update minimum required .vi file & VIEd version FileVersion bumped to 2 in .vi files. Change of file version requires VIEd v2.15.0 or later, so Build.html changed reflect this. --- Build.html | 2 +- Src/VCodeSnip.vi | 2 +- Src/VCodeSnipPortable.vi | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Build.html b/Build.html index 7176db3ff..fc6947166 100644 --- a/Build.html +++ b/Build.html @@ -196,7 +196,7 @@ <h3> <p> This tool is used to compile version information (<code>.vi</code>) files and any associated macro file(s) into intermediate resource source - (<code>.rc</code>) files. Version 2.14.0 or later is required. Version + (<code>.rc</code>) files. Version 2.15.0 or later is required. Version Information Editor can be obtained from <a href="https://github.com/delphidabbler/vied/releases" diff --git a/Src/VCodeSnip.vi b/Src/VCodeSnip.vi index ef0029546..51acab7f3 100644 --- a/Src/VCodeSnip.vi +++ b/Src/VCodeSnip.vi @@ -41,4 +41,4 @@ Special Build= Identifier= NumRCComments=0 ResOutputDir= -FileVersion=1 +FileVersion=2 diff --git a/Src/VCodeSnipPortable.vi b/Src/VCodeSnipPortable.vi index 7cb4de423..744d7a432 100644 --- a/Src/VCodeSnipPortable.vi +++ b/Src/VCodeSnipPortable.vi @@ -41,4 +41,4 @@ Special Build=Portable Identifier= NumRCComments=0 ResOutputDir= -FileVersion=1 +FileVersion=2 From a5f8139f8bffc8f6f0db018d416ddd6fb99d2376 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 13 Jul 2023 10:14:09 +0100 Subject: [PATCH 196/330] Bump version number to v4.21.2 build 269 --- Src/VersionInfo.vi-inc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Src/VersionInfo.vi-inc b/Src/VersionInfo.vi-inc index ee01e5c42..78ab7ae12 100644 --- a/Src/VersionInfo.vi-inc +++ b/Src/VersionInfo.vi-inc @@ -1,8 +1,8 @@ # CodeSnip Version Information Macros for Including in .vi files # Version & build numbers -version=4.21.1 -build=268 +version=4.21.2 +build=269 # String file information copyright=Copyright © P.D.Johnson, 2005-<YEAR>. From ea20cb940fc563e28eccac09fe606170819cb645 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 13 Jul 2023 10:34:50 +0100 Subject: [PATCH 197/330] Update change log with details of release v4.21.2 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index afae53dcf..9297a5729 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,15 @@ 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.21.2 of 13 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: From 7b8edaeade91a96d907c01ce4d89baed120ad2e9 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 14 Jul 2023 09:07:04 +0100 Subject: [PATCH 198/330] Fix errors in CHANGELOG.md re release v4.21.2 Fixes #120 --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9297a5729..7ed3fc709 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,10 @@ 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.21.2 of 13 July 2023 +## 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 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]. From b127989131f15f56ea4e824a8ef275e53f12e120 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 14 Jul 2023 10:26:32 +0100 Subject: [PATCH 199/330] Refactor out all `with` statements. Fixes #118 --- Src/DB.UMain.pas | 11 +-- Src/FirstRun.FmV4ConfigDlg.pas | 18 ++-- Src/FirstRun.FmWhatsNew.pas | 16 +-- Src/FmAboutDlg.pas | 16 +-- Src/FmActiveTextPreviewDlg.pas | 31 +++--- Src/FmAddCategoryDlg.pas | 16 +-- Src/FmBugReportBaseDlg.pas | 18 ++-- Src/FmCodeExportDlg.pas | 18 ++-- Src/FmCodeImportDlg.pas | 18 ++-- Src/FmCompErrorDlg.pas | 23 ++--- Src/FmCompilersDlg.FrLog.pas | 75 +++++++------- Src/FmDBUpdateDlg.pas | 18 ++-- Src/FmDeleteCategoryDlg.pas | 18 ++-- Src/FmDeleteUserDBDlg.pas | 18 ++-- Src/FmDependenciesDlg.pas | 60 +++++------ Src/FmDuplicateSnippetDlg.pas | 20 ++-- Src/FmEasterEgg.pas | 16 +-- Src/FmFavouritesDlg.pas | 57 ++++++----- Src/FmFindCompilerDlg.pas | 20 ++-- Src/FmFindTextDlg.pas | 20 ++-- Src/FmFindXRefsDlg.pas | 20 ++-- Src/FmMain.pas | 176 ++++++++++++++++----------------- Src/FmNewHiliterNameDlg.pas | 24 ++--- Src/FmPreferencesDlg.pas | 28 +++--- Src/FmPreviewDlg.pas | 22 +++-- Src/FmPrintDlg.pas | 16 +-- Src/FmRenameCategoryDlg.pas | 18 ++-- Src/FmSWAGImportDlg.pas | 16 +-- Src/FmSelectionSearchDlg.pas | 20 ++-- Src/FmSnippetsEditorDlg.pas | 69 ++++++------- Src/FmSplash.pas | 17 ++-- Src/FmTestCompileDlg.pas | 20 ++-- Src/FmTrappedBugReportDlg.pas | 18 ++-- Src/FmUserBugReportDlg.pas | 16 +-- Src/FmUserDataPathDlg.pas | 19 ++-- Src/FmUserHiliterMgrDlg.pas | 22 +++-- Src/FrCodeGenPrefs.pas | 57 +++++------ Src/FrDetailView.pas | 15 +-- Src/FrMemoPreview.pas | 16 +-- Src/Hiliter.UAttrs.pas | 25 +++-- Src/Hiliter.UHiliters.pas | 16 +-- Src/SWAG.UVersion.pas | 30 +++--- Src/UBrowseProtocol.pas | 20 ++-- Src/UCodeImportExport.pas | 113 ++++++++++----------- Src/UConsoleApp.pas | 23 +++-- Src/UDataBackupMgr.pas | 30 +++--- Src/UDlgHelper.pas | 18 ++-- Src/UEncodings.pas | 97 ++++++++---------- Src/UHiddenWindow.pas | 5 +- Src/UPrintMgr.pas | 16 +-- Src/USaveSnippetMgr.pas | 16 +-- Src/USaveSourceMgr.pas | 71 +++++++------ Src/USaveUnitMgr.pas | 16 +-- Src/USettings.pas | 41 ++++---- Src/USnippetSourceGen.pas | 16 +-- Src/UTestCompile.pas | 30 +++--- Src/UTestUnit.pas | 22 +++-- Src/UTestUnitDlgMgr.pas | 15 +-- Src/UVersionInfo.pas | 76 +++++++------- Src/UWBCommandBars.pas | 15 +-- Src/UWaitForThreadUI.pas | 18 ++-- 61 files changed, 967 insertions(+), 878 deletions(-) diff --git a/Src/DB.UMain.pas b/Src/DB.UMain.pas index f80860e59..6b61183e1 100644 --- a/Src/DB.UMain.pas +++ b/Src/DB.UMain.pas @@ -3,7 +3,7 @@ * 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) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). + * 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. @@ -954,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 @@ -984,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/FirstRun.FmV4ConfigDlg.pas b/Src/FirstRun.FmV4ConfigDlg.pas index abc588a59..122d67563 100644 --- a/Src/FirstRun.FmV4ConfigDlg.pas +++ b/Src/FirstRun.FmV4ConfigDlg.pas @@ -3,7 +3,7 @@ * 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-2021, Peter Johnson (gravatar.com/delphidabbler). + * 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 @@ -289,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); diff --git a/Src/FirstRun.FmWhatsNew.pas b/Src/FirstRun.FmWhatsNew.pas index 4c8f0e3ee..4cc6e9778 100644 --- a/Src/FirstRun.FmWhatsNew.pas +++ b/Src/FirstRun.FmWhatsNew.pas @@ -3,7 +3,7 @@ * 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). + * 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 @@ -114,13 +114,15 @@ procedure TWhatsNewDlg.CreateParams(var Params: TCreateParams); end; class procedure TWhatsNewDlg.Execute(AOwner: TComponent); +var + Dlg: TWhatsNewDlg; begin - with InternalCreate(AOwner) do - try - ShowModal; - finally - Free; - end; + Dlg := InternalCreate(AOwner); + try + Dlg.ShowModal; + finally + Dlg.Free; + end; end; function TWhatsNewDlg.GetAligner: IFormAligner; diff --git a/Src/FmAboutDlg.pas b/Src/FmAboutDlg.pas index 421495c56..6584a4ffd 100644 --- a/Src/FmAboutDlg.pas +++ b/Src/FmAboutDlg.pas @@ -3,7 +3,7 @@ * 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) 2005-2022, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements the program's About dialogue box. } @@ -326,13 +326,15 @@ function TAboutDlg.ContribListHTML(ContribList: IStringList): end; class procedure TAboutDlg.Execute(AOwner: TComponent); +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); diff --git a/Src/FmActiveTextPreviewDlg.pas b/Src/FmActiveTextPreviewDlg.pas index d35db88b1..747b40f20 100644 --- a/Src/FmActiveTextPreviewDlg.pas +++ b/Src/FmActiveTextPreviewDlg.pas @@ -3,7 +3,7 @@ * 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) 2009-2022, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2009-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a dialogue box that displays active text rendered from REML markup * or plain text. @@ -143,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; @@ -162,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'; @@ -195,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; diff --git a/Src/FmAddCategoryDlg.pas b/Src/FmAddCategoryDlg.pas index 791d24acd..27ebb3258 100644 --- a/Src/FmAddCategoryDlg.pas +++ b/Src/FmAddCategoryDlg.pas @@ -3,7 +3,7 @@ * 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) 2009-2021, Peter Johnson (gravatar.com/delphidabbler). + * 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. @@ -133,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/FmBugReportBaseDlg.pas b/Src/FmBugReportBaseDlg.pas index f57d03eed..9d2a1161b 100644 --- a/Src/FmBugReportBaseDlg.pas +++ b/Src/FmBugReportBaseDlg.pas @@ -3,7 +3,7 @@ * 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) 2009-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2009-2023, Peter Johnson (gravatar.com/delphidabbler). * * Provides a base class and common functionality for bug report dialogue boxes. } @@ -95,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 := TURL.CodeSnipBugTracker; - 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/FmCodeExportDlg.pas b/Src/FmCodeExportDlg.pas index 74c84943a..39cfdae8e 100644 --- a/Src/FmCodeExportDlg.pas +++ b/Src/FmCodeExportDlg.pas @@ -3,7 +3,7 @@ * 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) 2008-2021, Peter Johnson (gravatar.com/delphidabbler). + * 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. @@ -189,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); diff --git a/Src/FmCodeImportDlg.pas b/Src/FmCodeImportDlg.pas index 175ec525c..7315b29e8 100644 --- a/Src/FmCodeImportDlg.pas +++ b/Src/FmCodeImportDlg.pas @@ -1,9 +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 https://mozilla.org/MPL/2.0/ * - * Copyright (C) 2011-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2011-2023, 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 @@ -296,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; diff --git a/Src/FmCompErrorDlg.pas b/Src/FmCompErrorDlg.pas index 81bf7901d..ed1285957 100644 --- a/Src/FmCompErrorDlg.pas +++ b/Src/FmCompErrorDlg.pas @@ -3,7 +3,7 @@ * 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) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a dialogue box that displays compiler error and warning logs. } @@ -180,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); diff --git a/Src/FmCompilersDlg.FrLog.pas b/Src/FmCompilersDlg.FrLog.pas index 0048f48c1..581cd2bb7 100644 --- a/Src/FmCompilersDlg.FrLog.pas +++ b/Src/FmCompilersDlg.FrLog.pas @@ -3,7 +3,7 @@ * 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) 2011-2021, Peter Johnson (gravatar.com/delphidabbler). + * 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. @@ -126,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 - 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] + // 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 + // 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/FmDBUpdateDlg.pas b/Src/FmDBUpdateDlg.pas index 80cd905b2..1ffc7347f 100644 --- a/Src/FmDBUpdateDlg.pas +++ b/Src/FmDBUpdateDlg.pas @@ -3,7 +3,7 @@ * 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) 2005-2022, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a wizard dialogue box that handles the updating of the main * DelphiDabbler Code Snippets database. @@ -345,14 +345,16 @@ procedure TDBUpdateDlg.DoUpdate; end; class function TDBUpdateDlg.Execute(AOwner: TComponent): Boolean; +var + Dlg: TDBUpdateDlg; begin - with InternalCreate(AOwner) do - try - ShowModal; - Result := fDataUpdated; - finally - Free; - end; + Dlg := InternalCreate(AOwner); + try + Dlg.ShowModal; + Result := Dlg.fDataUpdated; + finally + Dlg.Free; + end; end; procedure TDBUpdateDlg.FormCreate(Sender: TObject); diff --git a/Src/FmDeleteCategoryDlg.pas b/Src/FmDeleteCategoryDlg.pas index 06f150109..3f6a65b0f 100644 --- a/Src/FmDeleteCategoryDlg.pas +++ b/Src/FmDeleteCategoryDlg.pas @@ -3,7 +3,7 @@ * 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) 2009-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2009-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a dialogue box that permits user to select and delete a user * defined category. @@ -137,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.pas b/Src/FmDeleteUserDBDlg.pas index d6c04b056..d51be0681 100644 --- a/Src/FmDeleteUserDBDlg.pas +++ b/Src/FmDeleteUserDBDlg.pas @@ -3,7 +3,7 @@ * 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). + * Copyright (C) 2022-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a dialogue box that asks user to confirm deletion of user-defined * snippets database. @@ -84,14 +84,16 @@ procedure TDeleteUserDBDlg.ConfigForm; end; class function TDeleteUserDBDlg.Execute(AOwner: TComponent): Boolean; +var + Dlg: TDeleteUserDBDlg; begin - with InternalCreate(AOwner) do - try - ShowModal; - Result := fPermissionGranted; - finally - Free; - end; + Dlg := InternalCreate(AOwner); + try + Dlg.ShowModal; + Result := Dlg.fPermissionGranted; + finally + Dlg.Free; + end; end; constructor TDeleteUserDBDlg.InternalCreate(AOwner: TComponent); diff --git a/Src/FmDependenciesDlg.pas b/Src/FmDependenciesDlg.pas index 0ee913675..e1705a01e 100644 --- a/Src/FmDependenciesDlg.pas +++ b/Src/FmDependenciesDlg.pas @@ -3,7 +3,7 @@ * 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) 2009-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2009-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a dialogue box that displays all the dependencies and dependents * of a snippet. @@ -321,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/FmDuplicateSnippetDlg.pas b/Src/FmDuplicateSnippetDlg.pas index b2e1fc13f..4c207e683 100644 --- a/Src/FmDuplicateSnippetDlg.pas +++ b/Src/FmDuplicateSnippetDlg.pas @@ -3,7 +3,7 @@ * 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-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2012-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a dialogue box which can create a duplicate copy of asnippet. } @@ -137,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 54022636f..31bdf2b38 100644 --- a/Src/FmEasterEgg.pas +++ b/Src/FmEasterEgg.pas @@ -3,7 +3,7 @@ * 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) 2009-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2009-2023, Peter Johnson (gravatar.com/delphidabbler). * * Defines a form that hosts the program's easter egg. } @@ -107,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 865a1df9c..7213d1852 100644 --- a/Src/FmFavouritesDlg.pas +++ b/Src/FmFavouritesDlg.pas @@ -3,7 +3,7 @@ * 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) 2013-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2013-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a dialogue box that displays and manages the user's favourite * snippets. @@ -406,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.pas b/Src/FmFindCompilerDlg.pas index 562d03396..277c4ae9c 100644 --- a/Src/FmFindCompilerDlg.pas +++ b/Src/FmFindCompilerDlg.pas @@ -3,7 +3,7 @@ * 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) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). + * 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. @@ -321,15 +321,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); diff --git a/Src/FmFindTextDlg.pas b/Src/FmFindTextDlg.pas index e436dba08..383234cef 100644 --- a/Src/FmFindTextDlg.pas +++ b/Src/FmFindTextDlg.pas @@ -3,7 +3,7 @@ * 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) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a dialogue box that is used to select criteria for text searches. * @@ -250,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 2df4f6db5..0d301ef98 100644 --- a/Src/FmFindXRefsDlg.pas +++ b/Src/FmFindXRefsDlg.pas @@ -3,7 +3,7 @@ * 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) 2006-2021, Peter Johnson (gravatar.com/delphidabbler). + * 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. @@ -250,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/FmMain.pas b/Src/FmMain.pas index 54ee4a6bf..59c28851a 100644 --- a/Src/FmMain.pas +++ b/Src/FmMain.pas @@ -3,7 +3,7 @@ * 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) 2005-2022, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2023, Peter Johnson (gravatar.com/delphidabbler). * * Application's main form. Handles the program's main window display and user * interaction. @@ -948,13 +948,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); @@ -1299,14 +1299,15 @@ procedure TMainForm.FormCreate(Sender: TObject); end; procedure TMainForm.FormDestroy(Sender: TObject); +var + EditableDB: IDatabaseEdit; begin inherited; // 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 @@ -1344,6 +1345,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; @@ -1387,38 +1390,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 - ) - ); - SetNewSnippetAction(actAddSnippet); - SetNewsAction(actBlog); - SetAboutBoxAction(actAbout); - 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); @@ -1456,58 +1458,56 @@ 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 diff --git a/Src/FmNewHiliterNameDlg.pas b/Src/FmNewHiliterNameDlg.pas index 7ff40df15..a7fcca8e5 100644 --- a/Src/FmNewHiliterNameDlg.pas +++ b/Src/FmNewHiliterNameDlg.pas @@ -3,7 +3,7 @@ * 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) 2013-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2013-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a dialogue box that enables the user to enter a syntax highlighter * name. @@ -122,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/FmPreferencesDlg.pas b/Src/FmPreferencesDlg.pas index 7a2e0fb7d..cceb5b6ce 100644 --- a/Src/FmPreferencesDlg.pas +++ b/Src/FmPreferencesDlg.pas @@ -3,7 +3,7 @@ * 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) 2006-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2006-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a dialogue box that is used to set user preferences. } @@ -265,19 +265,21 @@ function TPreferencesDlg.CustomHelpKeyword: string; class function TPreferencesDlg.Execute(AOwner: TComponent; const Pages: array of TPrefsFrameClass; out UpdateUI: Boolean; const Flags: UInt64): Boolean; +var + Dlg: TPreferencesDlg; begin - with InternalCreate(AOwner) do - try - fFrameFlags := Flags; - 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; diff --git a/Src/FmPreviewDlg.pas b/Src/FmPreviewDlg.pas index f5c987ffe..e9f924046 100644 --- a/Src/FmPreviewDlg.pas +++ b/Src/FmPreviewDlg.pas @@ -3,7 +3,7 @@ * 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) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). + * 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. @@ -172,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 101f9fa33..5ef588f1c 100644 --- a/Src/FmPrintDlg.pas +++ b/Src/FmPrintDlg.pas @@ -3,7 +3,7 @@ * 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) 2007-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2007-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a print dialogue box. } @@ -264,13 +264,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/FmRenameCategoryDlg.pas b/Src/FmRenameCategoryDlg.pas index 50f8ea135..039d6905a 100644 --- a/Src/FmRenameCategoryDlg.pas +++ b/Src/FmRenameCategoryDlg.pas @@ -3,7 +3,7 @@ * 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) 2009-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2009-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a dialogue box that permits user to select and rename a user * defined category. @@ -182,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.pas b/Src/FmSWAGImportDlg.pas index ceb931628..ffe252edc 100644 --- a/Src/FmSWAGImportDlg.pas +++ b/Src/FmSWAGImportDlg.pas @@ -3,7 +3,7 @@ * 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) 2013-2022, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2013-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a wizard dialogue box that lets the user select and import * packets from the DelphiDabbler implementation of the SWAG Pascal archive as @@ -568,13 +568,15 @@ procedure TSWAGImportDlg.DisplayPacketsForCategory; 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; diff --git a/Src/FmSelectionSearchDlg.pas b/Src/FmSelectionSearchDlg.pas index 6e19a454a..a1dcf49bc 100644 --- a/Src/FmSelectionSearchDlg.pas +++ b/Src/FmSelectionSearchDlg.pas @@ -3,7 +3,7 @@ * 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) 2006-2021, Peter Johnson (gravatar.com/delphidabbler). + * 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. @@ -194,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.pas b/Src/FmSnippetsEditorDlg.pas index d212194c0..45e9bc7d7 100644 --- a/Src/FmSnippetsEditorDlg.pas +++ b/Src/FmSnippetsEditorDlg.pas @@ -3,7 +3,7 @@ * 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) 2008-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2008-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a dialogue box that enables the user to create or edit user * defined snippets. @@ -531,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; @@ -710,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); @@ -944,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 bdf8d156e..de4b7bd34 100644 --- a/Src/FmSplash.pas +++ b/Src/FmSplash.pas @@ -3,7 +3,7 @@ * 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) 2007-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2007-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements the program's splash screen. } @@ -225,19 +225,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 + Free; + end; end; { TOwnerWindowSettings } diff --git a/Src/FmTestCompileDlg.pas b/Src/FmTestCompileDlg.pas index b7a01db0d..5066e2bff 100644 --- a/Src/FmTestCompileDlg.pas +++ b/Src/FmTestCompileDlg.pas @@ -3,7 +3,7 @@ * 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) 2011-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2011-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a dialogue box which test compiles a snippet and displays the * results. @@ -314,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.pas b/Src/FmTrappedBugReportDlg.pas index 40403a497..6c1412954 100644 --- a/Src/FmTrappedBugReportDlg.pas +++ b/Src/FmTrappedBugReportDlg.pas @@ -3,7 +3,7 @@ * 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) 2009-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2009-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a bug report dialogue box that is displayed when unexpected * exceptions are detected. @@ -166,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.pas b/Src/FmUserBugReportDlg.pas index 570bb65bc..bf180386d 100644 --- a/Src/FmUserBugReportDlg.pas +++ b/Src/FmUserBugReportDlg.pas @@ -3,7 +3,7 @@ * 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) 2009-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2009-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a dialogue box that informs users how to report bugs. } @@ -75,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.pas b/Src/FmUserDataPathDlg.pas index 745577a3d..9c6bbbbbe 100644 --- a/Src/FmUserDataPathDlg.pas +++ b/Src/FmUserDataPathDlg.pas @@ -3,7 +3,7 @@ * 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) 2013-2021, Peter Johnson (gravatar.com/delphidabbler). + * 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. @@ -272,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 2ee6db8a4..b71130b03 100644 --- a/Src/FmUserHiliterMgrDlg.pas +++ b/Src/FmUserHiliterMgrDlg.pas @@ -3,7 +3,7 @@ * 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) 2013-2021, Peter Johnson (gravatar.com/delphidabbler). + * 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 @@ -152,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/FrCodeGenPrefs.pas b/Src/FrCodeGenPrefs.pas index 7814ad14b..be4d50516 100644 --- a/Src/FrCodeGenPrefs.pas +++ b/Src/FrCodeGenPrefs.pas @@ -3,7 +3,7 @@ * 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) 2010-2022, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2010-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a frame that allows user to set source code generation * preferences. @@ -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); diff --git a/Src/FrDetailView.pas b/Src/FrDetailView.pas index bb68e32d5..8aa41a592 100644 --- a/Src/FrDetailView.pas +++ b/Src/FrDetailView.pas @@ -3,7 +3,7 @@ * 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) 2005-2022, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a frame that can display detailed views. } @@ -129,6 +129,7 @@ procedure TDetailViewFrame.BuildCSS(const CSSBuilder: TCSSBuilder); 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 @@ -239,12 +240,12 @@ procedure TDetailViewFrame.BuildCSS(const CSSBuilder: TCSSBuilder); // Sets text styles and colours used by syntax highlighter HiliteAttrs := THiliteAttrsFactory.CreateUserAttrs; - with THiliterCSS.Create(HiliteAttrs) do - try - BuildCSS(CSSBuilder); - finally - Free; - end; + 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] diff --git a/Src/FrMemoPreview.pas b/Src/FrMemoPreview.pas index 97c24e34a..6a8864b1d 100644 --- a/Src/FrMemoPreview.pas +++ b/Src/FrMemoPreview.pas @@ -3,7 +3,7 @@ * 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) 2007-2021, Peter Johnson (gravatar.com/delphidabbler). + * 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. @@ -141,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/Hiliter.UAttrs.pas b/Src/Hiliter.UAttrs.pas index 42ab29f1d..67a970123 100644 --- a/Src/Hiliter.UAttrs.pas +++ b/Src/Hiliter.UAttrs.pas @@ -3,7 +3,7 @@ * 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) 2005-2022, Peter Johnson (gravatar.com/delphidabbler). + * 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. @@ -232,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 @@ -240,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 @@ -320,6 +319,8 @@ procedure THiliteAttrs.SetFontSize(const AFontSize: Integer); { THiliteElemAttrs } procedure THiliteElemAttrs.Assign(const Src: IInterface); +var + ElemAttrs: IHiliteElemAttrs; begin if Assigned(Src) then begin @@ -328,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.UHiliters.pas b/Src/Hiliter.UHiliters.pas index 348fc58b1..f0a998300 100644 --- a/Src/Hiliter.UHiliters.pas +++ b/Src/Hiliter.UHiliters.pas @@ -3,7 +3,7 @@ * 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) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2023, 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 @@ -336,14 +336,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); diff --git a/Src/SWAG.UVersion.pas b/Src/SWAG.UVersion.pas index ed77c0d08..b4383ea95 100644 --- a/Src/SWAG.UVersion.pas +++ b/Src/SWAG.UVersion.pas @@ -3,7 +3,7 @@ * 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). + * Copyright (C) 2020-2023, Peter Johnson (gravatar.com/delphidabbler). * * Provides a class that reads and validates the SWAG collection's version * information from file. @@ -107,13 +107,15 @@ implementation class function TSWAGVersion.GetVersion(const SWAGDir: TFileName): TVersionNumber; +var + Instance: TSWAGVersion; begin - with InternalCreate(SWAGDir) do - try - Result := ReadAndValidateVersionFile; - finally - Free; - end; + Instance := InternalCreate(SWAGDir); + try + Result := Instance.ReadAndValidateVersionFile; + finally + Instance.Free; + end; end; constructor TSWAGVersion.InternalCreate(const SWAGDir: TFileName); @@ -158,13 +160,15 @@ function TSWAGVersion.ReadVersionStr: string; end; class procedure TSWAGVersion.ValidateVersionFile(const SWAGDir: TFileName); +var + Instance: TSWAGVersion; begin - with InternalCreate(SWAGDir) do - try - ReadAndValidateVersionFile; - finally - Free; - end; + Instance := InternalCreate(SWAGDir); + try + Instance.ReadAndValidateVersionFile; + finally + Instance.Free; + end; end; end. diff --git a/Src/UBrowseProtocol.pas b/Src/UBrowseProtocol.pas index 7878af4ce..e1ae9e3e5 100644 --- a/Src/UBrowseProtocol.pas +++ b/Src/UBrowseProtocol.pas @@ -3,7 +3,7 @@ * 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) 2009-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2009-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a abstract base class for protocol handlers that access a URL * using a TBrowseURL action. @@ -67,17 +67,19 @@ function TBrowseProtocol.Execute: Boolean; @return True. @except EProtocol raised if an exception occurs in browse action. } +var + BrowseAction: TBrowseURL; begin // We execute the resource using an action try - with TBrowseURL.Create(nil) do - try - URL := NormaliseURL(Self.URL); - Execute; - Result := True; - finally - Free; - end; + BrowseAction := TBrowseURL.Create(nil); + try + BrowseAction.URL := NormaliseURL(Self.URL); + BrowseAction.Execute; + Result := True; + finally + BrowseAction.Free; + end; except // any exceptions converted to EProtocol on E: Exception do diff --git a/Src/UCodeImportExport.pas b/Src/UCodeImportExport.pas index 7a197459e..e43346daa 100644 --- a/Src/UCodeImportExport.pas +++ b/Src/UCodeImportExport.pas @@ -224,13 +224,15 @@ function TCodeExporter.Execute: TEncodedData; class function TCodeExporter.ExportSnippets(const SnipList: TSnippetList): TEncodedData; +var + Instance: TCodeExporter; begin - with InternalCreate(SnipList) do - try - Result := Execute; - finally - Free; - end; + Instance := InternalCreate(SnipList); + try + Result := Instance.Execute; + finally + Instance.Free; + end; end; procedure TCodeExporter.HandleException(const EObj: TObject); @@ -418,54 +420,54 @@ procedure TCodeImporter.Execute(const Data: TBytes); fSnippetInfo[Idx].Name := SnippetNode.Attributes[cSnippetNameAttr]; fSnippetInfo[Idx].Data := (Database as IDatabaseEdit).GetEditableSnippetInfo; - with fSnippetInfo[Idx].Data do - begin - Props.Cat := TReservedCategories.ImportsCatID; - Props.Desc := GetDescription(SnippetNode); - Props.DisplayName := TXMLDocHelper.GetSubTagText( - fXMLDoc, SnippetNode, cDisplayNameNode - ); - Props.SourceCode := TXMLDocHelper.GetSubTagText( - fXMLDoc, SnippetNode, cSourceCodeTextNode - ); - Props.HiliteSource := TXMLDocHelper.GetHiliteSource( - fXMLDoc, SnippetNode, True - ); - // how we read extra property depends on version of file - case fVersion of - 1: - Props.Extra := TSnippetExtraHelper.BuildActiveText( + fSnippetInfo[Idx].Data.Props.Cat := TReservedCategories.ImportsCatID; + fSnippetInfo[Idx].Data.Props.Desc := GetDescription(SnippetNode); + fSnippetInfo[Idx].Data.Props.DisplayName := TXMLDocHelper.GetSubTagText( + fXMLDoc, SnippetNode, cDisplayNameNode + ); + fSnippetInfo[Idx].Data.Props.SourceCode := TXMLDocHelper.GetSubTagText( + fXMLDoc, SnippetNode, cSourceCodeTextNode + ); + fSnippetInfo[Idx].Data.Props.HiliteSource := TXMLDocHelper.GetHiliteSource( + fXMLDoc, SnippetNode, True + ); + // how we read extra property depends on version of file + case fVersion of + 1: + fSnippetInfo[Idx].Data.Props.Extra := + TSnippetExtraHelper.BuildActiveText( TXMLDocHelper.GetSubTagText(fXMLDoc, SnippetNode, cCommentsNode), TXMLDocHelper.GetSubTagText(fXMLDoc, SnippetNode, cCreditsNode), TXMLDocHelper.GetSubTagText(fXMLDoc, SnippetNode, cCreditsUrlNode) ); - else // later versions - Props.Extra := TSnippetExtraHelper.BuildActiveText( + else // later versions + fSnippetInfo[Idx].Data.Props.Extra := + TSnippetExtraHelper.BuildActiveText( TXMLDocHelper.GetSubTagText(fXMLDoc, SnippetNode, cExtraNode) ); - end; - // how we read kind property depends on version of file - case fVersion of - 1, 2: - // for version 1 and 2, we have StandardFormat instead of Kind: - // map standard format value onto a kind - if TXMLDocHelper.GetStandardFormat(fXMLDoc, SnippetNode, False) then - Props.Kind := skRoutine - else - Props.Kind := skFreeform; - else // later versions - // for later versions we have Kind value: use Freeform if missing - Props.Kind := TXMLDocHelper.GetSnippetKind( - fXMLDoc, SnippetNode, skFreeForm - ); - end; - Props.CompilerResults := TXMLDocHelper.GetCompilerResults( + end; + // how we read kind property depends on version of file + case fVersion of + 1, 2: + // for version 1 and 2, we have StandardFormat instead of Kind: + // map standard format value onto a kind + if TXMLDocHelper.GetStandardFormat(fXMLDoc, SnippetNode, False) then + fSnippetInfo[Idx].Data.Props.Kind := skRoutine + else + fSnippetInfo[Idx].Data.Props.Kind := skFreeform; + else // later versions + // for later versions we have Kind value: use Freeform if missing + fSnippetInfo[Idx].Data.Props.Kind := TXMLDocHelper.GetSnippetKind( + fXMLDoc, SnippetNode, skFreeForm + ); + end; + fSnippetInfo[Idx].Data.Props.CompilerResults := + TXMLDocHelper.GetCompilerResults( fXMLDoc, SnippetNode ); - GetUnits(SnippetNode, Refs.Units); - GetDepends(SnippetNode, Refs.Depends); - Refs.XRef.Clear; - end; + GetUnits(SnippetNode, fSnippetInfo[Idx].Data.Refs.Units); + GetDepends(SnippetNode, fSnippetInfo[Idx].Data.Refs.Depends); + fSnippetInfo[Idx].Data.Refs.XRef.Clear; end; except on E: EDOMParseError do @@ -489,16 +491,17 @@ class procedure TCodeImporter.ImportData(out SnippetInfo: TSnippetInfoList; const Data: TBytes); var Idx: Integer; // loops through all imported snippets + Instance: TCodeImporter; begin - with InternalCreate do - try - Execute(Data); - SetLength(SnippetInfo, Length(fSnippetInfo)); - for Idx := Low(fSnippetInfo) to High(fSnippetInfo) do - SnippetInfo[Idx].Assign(fSnippetInfo[Idx]); - finally - Free; - end; + Instance := InternalCreate; + try + Instance.Execute(Data); + SetLength(SnippetInfo, Length(Instance.fSnippetInfo)); + for Idx := Low(Instance.fSnippetInfo) to High(Instance.fSnippetInfo) do + SnippetInfo[Idx].Assign(Instance.fSnippetInfo[Idx]); + finally + Instance.Free; + end; end; constructor TCodeImporter.InternalCreate; diff --git a/Src/UConsoleApp.pas b/Src/UConsoleApp.pas index c56b85db4..2c0b67f2d 100644 --- a/Src/UConsoleApp.pas +++ b/Src/UConsoleApp.pas @@ -3,7 +3,7 @@ * 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) 2006-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2006-2023, Peter Johnson (gravatar.com/delphidabbler). * * A class that encapsulates and executes a command line application and * optionally redirects the application's standard input, output and error. @@ -349,17 +349,16 @@ function TConsoleApp.StartProcess(const CmdLine, CurrentDir: string; begin // Set up startup information structure FillChar(StartInfo, Sizeof(StartInfo),#0); - with StartInfo do - begin - cb := SizeOf(StartInfo); - dwFlags := STARTF_USESHOWWINDOW; - if (fStdIn <> 0) or (fStdOut <> 0) or (fStdErr <> 0) then - dwFlags := dwFlags or STARTF_USESTDHANDLES; // we are redirecting - hStdInput := fStdIn; // std handles (non-zero => redirect) - hStdOutput := fStdOut; - hStdError := fStdErr; - wShowWindow := cShowFlags[fVisible]; // show or hide window - end; + StartInfo.cb := SizeOf(StartInfo); + StartInfo.dwFlags := STARTF_USESHOWWINDOW; + if (fStdIn <> 0) or (fStdOut <> 0) or (fStdErr <> 0) then + // we are redirecting (at least one std handle is non zero) + StartInfo.dwFlags := StartInfo.dwFlags or STARTF_USESTDHANDLES; + // std handles (non-zero => redirect) + StartInfo.hStdInput := fStdIn; + StartInfo.hStdOutput := fStdOut; + StartInfo.hStdError := fStdErr; + StartInfo.wShowWindow := cShowFlags[fVisible]; // show or hide window // Make CmdLine parameter safe for passing to CreateProcess (Delphi 2009 // and later). Need to ensure memory space is writeable because of issue with // CreateProcessW. Problem does not exist with CreateProcessA. diff --git a/Src/UDataBackupMgr.pas b/Src/UDataBackupMgr.pas index f9177e4b0..44046ed7c 100644 --- a/Src/UDataBackupMgr.pas +++ b/Src/UDataBackupMgr.pas @@ -3,7 +3,7 @@ * 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) 2006-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2006-2023, Peter Johnson (gravatar.com/delphidabbler). * * Static class that manages backups of data files. It can back up the local * database directory, restore the backup and delete it. @@ -81,16 +81,18 @@ implementation class procedure TDataBackupMgr.Backup; {Backs up CodeSnip data files into a single file. } +var + FolderBackup: TFolderBackup; begin EnsureFolders(BackupDir); SysUtils.DeleteFile(BackupFileName); EnsureFolders(DataDir); - with TFolderBackup.Create(DataDir, BackupFileName, cBakFileID) do - try - Backup; - finally - Free; - end; + FolderBackup := TFolderBackup.Create(DataDir, BackupFileName, cBakFileID); + try + FolderBackup.Backup; + finally + FolderBackup.Free; + end; end; class function TDataBackupMgr.BackupDir: string; @@ -147,17 +149,19 @@ class procedure TDataBackupMgr.RestoreBackup; {Restores back up, replacing current data files. If no backup exists the database directory is cleared. } +var + FolderBackup: TFolderBackup; begin EnsureFolders(DataDir); DeleteFilesFromDir(DataDir); if BackupExists then begin - with TFolderBackup.Create(DataDir, BackupFileName, cBakFileID) do - try - Restore; - finally - Free; - end; + FolderBackup := TFolderBackup.Create(DataDir, BackupFileName, cBakFileID); + try + FolderBackup.Restore; + finally + FolderBackup.Free; + end; end; end; diff --git a/Src/UDlgHelper.pas b/Src/UDlgHelper.pas index 5e29e6705..3ea91e43c 100644 --- a/Src/UDlgHelper.pas +++ b/Src/UDlgHelper.pas @@ -3,7 +3,7 @@ * 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) 2007-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2007-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements "static" classes that help to manipulate dialogue boxes: * + TDlgHelper sets a dialogue box's parent window. @@ -380,25 +380,29 @@ procedure TDlgAligner.AdjustWindowPosition(const DlgBounds: TRectEx); end; class procedure TDlgAligner.Align(const Dlg, Host: TComponent); +var + Instance: TDlgAligner; begin Assert(Assigned(Dlg), ClassName + '.Align: Dlg is nil'); - with InternalCreate(Dlg, Host) do + Instance := InternalCreate(Dlg, Host); try - PerformAlignment; + Instance.PerformAlignment; finally - Free; + Instance.Free; end; end; class procedure TDlgAligner.Align(const DlgHandle: THandle; const Host: TComponent); +var + Instance: TDlgAligner; begin Assert(IsWindow(DlgHandle), ClassName + '.Align: DlgHandle is not a window'); - with InternalCreate(DlgHandle, Host) do + Instance := InternalCreate(DlgHandle, Host); try - PerformAlignment; + Instance.PerformAlignment; finally - Free; + Instance.Free; end; end; diff --git a/Src/UEncodings.pas b/Src/UEncodings.pas index 78fc4aa54..b8f6878bc 100644 --- a/Src/UEncodings.pas +++ b/Src/UEncodings.pas @@ -496,6 +496,7 @@ class function TEncodingHelper.CharSets: TStringDynArray; UTF16BEFactoryFn: TEncodingFactoryFn; begin // Set references to appropriate encoding factory functions + DefaultFactoryFn := function: TEncoding begin Result := TEncoding.Default; end; ASCIIFactoryFn := @@ -508,62 +509,46 @@ class function TEncodingHelper.CharSets: TStringDynArray; function: TEncoding begin Result := TEncoding.BigEndianUnicode; end; // Populate map for all encodings - with fMap[etSysDefault] do - begin - CharSet := ''; - IsAnsi := True; - CodePage := ULocales.DefaultAnsiCodePage; - FactoryFn := DefaultFactoryFn; - end; - with fMap[etASCII] do - begin - CharSet := ASCIICharSetName; - IsAnsi := True; - CodePage := ASCIICodePage; - FactoryFn := ASCIIFactoryFn; - end; - with fMap[etISO88591] do - begin - CharSet := ISO88591CharSetName; - IsAnsi := True; - CodePage := ISO88591CodePage; - FactoryFn := MBCSFactoryFn(ISO88591CodePage); - end; - with fMap[etUTF8] do - begin - CharSet := UTF8CharSetName; - IsAnsi := True; - CodePage := UTF8CodePage; - FactoryFn := UTF8FactoryFn; - end; - with fMap[etUnicode] do - begin - CharSet := UTF16CharSetName; - IsAnsi := False; - CodePage := 0; - FactoryFn := UTF16FactoryFn; - end; - with fMap[etUTF16BE] do - begin - CharSet := UTF16BECharSetName; - IsAnsi := False; - CodePage := 0; - FactoryFn := UTF16BEFactoryFn; - end; - with fMap[etUTF16LE] do - begin - CharSet := UTF16LECharSetName; - IsAnsi := False; - CodePage := 0; - FactoryFn := UTF16FactoryFn; - end; - with fMap[etWindows1252] do - begin - CharSet := Windows1252CharSetName; - IsAnsi := True; - CodePage := Windows1252CodePage; - FactoryFn := MBCSFactoryFn(Windows1252CodePage); - end; + + fMap[etSysDefault].CharSet := ''; + fMap[etSysDefault].IsAnsi := True; + fMap[etSysDefault].CodePage := ULocales.DefaultAnsiCodePage; + fMap[etSysDefault].FactoryFn := DefaultFactoryFn; + + fMap[etASCII].CharSet := ASCIICharSetName; + fMap[etASCII].IsAnsi := True; + fMap[etASCII].CodePage := ASCIICodePage; + fMap[etASCII].FactoryFn := ASCIIFactoryFn; + + fMap[etISO88591].CharSet := ISO88591CharSetName; + fMap[etISO88591].IsAnsi := True; + fMap[etISO88591].CodePage := ISO88591CodePage; + fMap[etISO88591].FactoryFn := MBCSFactoryFn(ISO88591CodePage); + + fMap[etUTF8].CharSet := UTF8CharSetName; + fMap[etUTF8].IsAnsi := True; + fMap[etUTF8].CodePage := UTF8CodePage; + fMap[etUTF8].FactoryFn := UTF8FactoryFn; + + fMap[etUnicode].CharSet := UTF16CharSetName; + fMap[etUnicode].IsAnsi := False; + fMap[etUnicode].CodePage := 0; + fMap[etUnicode].FactoryFn := UTF16FactoryFn; + + fMap[etUTF16BE].CharSet := UTF16BECharSetName; + fMap[etUTF16BE].IsAnsi := False; + fMap[etUTF16BE].CodePage := 0; + fMap[etUTF16BE].FactoryFn := UTF16BEFactoryFn; + + fMap[etUTF16LE].CharSet := UTF16LECharSetName; + fMap[etUTF16LE].IsAnsi := False; + fMap[etUTF16LE].CodePage := 0; + fMap[etUTF16LE].FactoryFn := UTF16FactoryFn; + + fMap[etWindows1252].CharSet := Windows1252CharSetName; + fMap[etWindows1252].IsAnsi := True; + fMap[etWindows1252].CodePage := Windows1252CodePage; + fMap[etWindows1252].FactoryFn := MBCSFactoryFn(Windows1252CodePage); end; class function TEncodingHelper.DefaultCharSet: string; diff --git a/Src/UHiddenWindow.pas b/Src/UHiddenWindow.pas index ed0d52494..7a39f1d8b 100644 --- a/Src/UHiddenWindow.pas +++ b/Src/UHiddenWindow.pas @@ -3,7 +3,7 @@ * 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) 2007-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2007-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a class that provides a hidden window. } @@ -82,8 +82,7 @@ procedure THiddenWindow.WndProc(var Msg: TMessage); processing. } begin - with Msg do - Result := DefWindowProc(Handle, Msg, WParam, LParam); + Msg.Result := DefWindowProc(Handle, Msg.Msg, Msg.WParam, Msg.LParam); end; end. diff --git a/Src/UPrintMgr.pas b/Src/UPrintMgr.pas index da15bf150..4c72fc589 100644 --- a/Src/UPrintMgr.pas +++ b/Src/UPrintMgr.pas @@ -3,7 +3,7 @@ * 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) 2007-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2007-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a class that manages printing of a document providing information * about certain view items. @@ -117,15 +117,17 @@ constructor TPrintMgr.InternalCreate(ViewItem: IView); end; class procedure TPrintMgr.Print(ViewItem: IView); +var + PrintMgr: TPrintMgr; begin Assert(Assigned(ViewItem), ClassName + '.Print: ViewItem is nil'); Assert(CanPrint(ViewItem), ClassName + '.Print: ViewItem can''t be printed'); - with InternalCreate(ViewItem) do - try - DoPrint; - finally - Free; - end; + PrintMgr := InternalCreate(ViewItem); + try + PrintMgr.DoPrint; + finally + PrintMgr.Free; + end; end; end. diff --git a/Src/USaveSnippetMgr.pas b/Src/USaveSnippetMgr.pas index c8df2e451..63dcb9b0e 100644 --- a/Src/USaveSnippetMgr.pas +++ b/Src/USaveSnippetMgr.pas @@ -3,7 +3,7 @@ * 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) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2023, Peter Johnson (gravatar.com/delphidabbler). * * Defines a class that manages generation, previewing and saving of a code * snippet. @@ -119,13 +119,15 @@ procedure TSaveSnippetMgr.CheckFileName(const FileName: string; end; class procedure TSaveSnippetMgr.Execute(View: IView); +var + Instance: TSaveSnippetMgr; begin - with InternalCreate(View) do - try - DoExecute; - finally - Free; - end; + Instance := InternalCreate(View); + try + Instance.DoExecute; + finally + Instance.Free; + end; end; function TSaveSnippetMgr.GenerateSource(const CommentStyle: TCommentStyle; diff --git a/Src/USaveSourceMgr.pas b/Src/USaveSourceMgr.pas index 21fbd40e6..9c7c8efca 100644 --- a/Src/USaveSourceMgr.pas +++ b/Src/USaveSourceMgr.pas @@ -3,7 +3,7 @@ * 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) 2006-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2006-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements abstract base class for classes that manage generation, previewing * and saving to disk of a source code files in various formats and encodings. @@ -244,42 +244,39 @@ constructor TSaveSourceMgr.InternalCreate; begin inherited InternalCreate; fSourceFileInfo := TSourceFileInfo.Create; - with fSourceFileInfo do - begin - FileTypeInfo[sfText] := TSourceFileTypeInfo.Create( - '.txt', - GetFileTypeDesc(sfText), - [ - TSourceFileEncoding.Create(etSysDefault, sANSIDefaultEncoding), - TSourceFileEncoding.Create(etUTF8, sUTF8Encoding), - TSourceFileEncoding.Create(etUTF16LE, sUTF16LEEncoding), - TSourceFileEncoding.Create(etUTF16BE, sUTF16BEEncoding) - ] - ); - FileTypeInfo[sfPascal] := TSourceFileTypeInfo.Create( - '.pas', - GetFileTypeDesc(sfPascal), - [ - TSourceFileEncoding.Create(etSysDefault, sANSIDefaultEncoding), - TSourceFileEncoding.Create(etUTF8, sUTF8Encoding) - ] - ); - FileTypeInfo[sfHTML] := TSourceFileTypeInfo.Create( - '.html', - GetFileTypeDesc(sfHTML), - [ - TSourceFileEncoding.Create(etUTF8, sUTF8Encoding) - ] - ); - FileTypeInfo[sfRTF] := TSourceFileTypeInfo.Create( - '.rtf', - GetFileTypeDesc(sfRTF), - [ - TSourceFileEncoding.Create(etSysDefault, sANSIDefaultEncoding) - ] - ); - DefaultFileName := GetDefaultFileName; - end; + fSourceFileInfo.FileTypeInfo[sfText] := TSourceFileTypeInfo.Create( + '.txt', + GetFileTypeDesc(sfText), + [ + TSourceFileEncoding.Create(etSysDefault, sANSIDefaultEncoding), + TSourceFileEncoding.Create(etUTF8, sUTF8Encoding), + TSourceFileEncoding.Create(etUTF16LE, sUTF16LEEncoding), + TSourceFileEncoding.Create(etUTF16BE, sUTF16BEEncoding) + ] + ); + fSourceFileInfo.FileTypeInfo[sfPascal] := TSourceFileTypeInfo.Create( + '.pas', + GetFileTypeDesc(sfPascal), + [ + TSourceFileEncoding.Create(etSysDefault, sANSIDefaultEncoding), + TSourceFileEncoding.Create(etUTF8, sUTF8Encoding) + ] + ); + fSourceFileInfo.FileTypeInfo[sfHTML] := TSourceFileTypeInfo.Create( + '.html', + GetFileTypeDesc(sfHTML), + [ + TSourceFileEncoding.Create(etUTF8, sUTF8Encoding) + ] + ); + fSourceFileInfo.FileTypeInfo[sfRTF] := TSourceFileTypeInfo.Create( + '.rtf', + GetFileTypeDesc(sfRTF), + [ + TSourceFileEncoding.Create(etSysDefault, sANSIDefaultEncoding) + ] + ); + fSourceFileInfo.DefaultFileName := GetDefaultFileName; fSaveDlg := TSaveSourceDlg.Create(nil); fSaveDlg.Title := GetDlgTitle; diff --git a/Src/USaveUnitMgr.pas b/Src/USaveUnitMgr.pas index d1d16a05d..1cd7841d3 100644 --- a/Src/USaveUnitMgr.pas +++ b/Src/USaveUnitMgr.pas @@ -3,7 +3,7 @@ * 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) 2006-2022, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2006-2023, Peter Johnson (gravatar.com/delphidabbler). * * Defines a class that manages generation, previewing and saving of a pascal * unit. @@ -199,13 +199,15 @@ destructor TSaveUnitMgr.Destroy; end; class procedure TSaveUnitMgr.Execute(const Snips: TSnippetList); +var + Instance: TSaveUnitMgr; begin - with InternalCreate(Snips) do - try - DoExecute; - finally - Free; - end; + Instance := InternalCreate(Snips); + try + Instance.DoExecute; + finally + Instance.Free; + end; end; function TSaveUnitMgr.GenerateSource(const CommentStyle: TCommentStyle; diff --git a/Src/USettings.pas b/Src/USettings.pas index 7de87b900..5e38e3227 100644 --- a/Src/USettings.pas +++ b/Src/USettings.pas @@ -3,7 +3,7 @@ * 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) 2006-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2006-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements class that can store application settings in application wide and * per user persistent storage. @@ -645,14 +645,16 @@ function TIniSettingsSection.ItemExists(const Name: string): Boolean; end; procedure TIniSettingsSection.Load; +var + Ini: TIniFile; begin // Read all values from section in app's ini file to data item storage - with CreateIniFile do - try - ReadSectionValues(fSectionName, fValues); - finally - Free; - end; + Ini := CreateIniFile; + try + Ini.ReadSectionValues(fSectionName, fValues); + finally + Ini.Free; + end; end; function TIniSettingsSection.ParseConfigDate(const S: string): TDateTime; @@ -674,20 +676,21 @@ function TIniSettingsSection.ParseConfigDate(const S: string): TDateTime; procedure TIniSettingsSection.Save; var Idx: Integer; // loops thru all data items in section + Ini: TIniFile; begin // Open application's ini file - with CreateIniFile do - try - // Delete any existing section with same name - EraseSection(fSectionName); - // Write all data items to ini file section - for Idx := 0 to Pred(fValues.Count) do - WriteString( - fSectionName, fValues.Names[Idx], fValues.ValueFromIndex[Idx] - ); - finally - Free; - end; + Ini := CreateIniFile; + try + // Delete any existing section with same name + Ini.EraseSection(fSectionName); + // Write all data items to ini file section + for Idx := 0 to Pred(fValues.Count) do + Ini.WriteString( + fSectionName, fValues.Names[Idx], fValues.ValueFromIndex[Idx] + ); + finally + Ini.Free; + end; end; procedure TIniSettingsSection.SetBoolean(const Name: string; diff --git a/Src/USnippetSourceGen.pas b/Src/USnippetSourceGen.pas index 952b5a1ee..e7739df85 100644 --- a/Src/USnippetSourceGen.pas +++ b/Src/USnippetSourceGen.pas @@ -3,7 +3,7 @@ * 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) 2006-2022, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2006-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a static class that generates source code for code snippet(s) * contained in a routine snippet or category view. @@ -216,13 +216,15 @@ class function TSnippetSourceGen.Generate(View: IView; description to first paragraph in comments. @return Required source code. } +var + Instance: TSnippetSourceGen; begin - with InternalCreate(View) do - try - Result := DoGenerate(CommentStyle, TruncateComments); - finally - Free; - end; + Instance := InternalCreate(View); + try + Result := Instance.DoGenerate(CommentStyle, TruncateComments); + finally + Instance.Free; + end; end; procedure TSnippetSourceGen.Initialize(View: IView); diff --git a/Src/UTestCompile.pas b/Src/UTestCompile.pas index 2fdeceeec..210a74b6e 100644 --- a/Src/UTestCompile.pas +++ b/Src/UTestCompile.pas @@ -3,7 +3,7 @@ * 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) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2023, Peter Johnson (gravatar.com/delphidabbler). * * Class that performs a test compilation of a snippet using all supported and * installed versions of Delphi and returns details of success or failure. @@ -112,13 +112,15 @@ class function TTestCompile.Compile(const ACompilers: ICompilers; @return Compilation results for each supported compiler (crQuery is returned for each supported compiler that is not installed). } +var + Instance: TTestCompile; begin - with InternalCreate(ACompilers, ASnippet) do - try - Result := DoCompile; - finally - Free; - end; + Instance := InternalCreate(ACompilers, ASnippet); + try + Result := Instance.DoCompile; + finally + Instance.Free; + end; end; class function TTestCompile.CompileSourceFile(const SrcFile: string; @@ -162,13 +164,15 @@ procedure TTestCompile.GenerateSourceFile(out FileName: string); {Generates a source file for snippet under test. @param FileName [out] Name of the generated file. } +var + TestUnit: TTestUnit; begin - with TTestUnit.Create(fSnippet) do - try - SaveUnit(FileName); - finally - Free; - end; + TestUnit := TTestUnit.Create(fSnippet); + try + TestUnit.SaveUnit(FileName); + finally + TestUnit.Free; + end; end; constructor TTestCompile.InternalCreate(const ACompilers: ICompilers; diff --git a/Src/UTestUnit.pas b/Src/UTestUnit.pas index 86506597c..eef7d44c5 100644 --- a/Src/UTestUnit.pas +++ b/Src/UTestUnit.pas @@ -3,7 +3,7 @@ * 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) 2006-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2006-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a class that generates Pascal units for use in test compiling * snippets. @@ -79,18 +79,20 @@ constructor TTestUnit.Create(const Snippet: TSnippet); end; function TTestUnit.GenerateUnitSource: string; +var + Generator: TSourceGen; begin if fSnippet.Kind <> skUnit then begin - with TSourceGen.Create do - try - IncludeSnippet(fSnippet); - // Must use Self.UnitName below for Delphis that defined TObject.UnitName - // otherwise the TObject version is used. - Result := UnitAsString(Self.UnitName); - finally - Free; - end; + Generator := TSourceGen.Create; + try + Generator.IncludeSnippet(fSnippet); + // Must use Self.UnitName below for Delphis that defined TObject.UnitName + // otherwise the TObject version is used. + Result := Generator.UnitAsString(Self.UnitName); + finally + Generator.Free; + end; end else Result := fSnippet.SourceCode; diff --git a/Src/UTestUnitDlgMgr.pas b/Src/UTestUnitDlgMgr.pas index 34a0ce3a6..05025efe5 100644 --- a/Src/UTestUnitDlgMgr.pas +++ b/Src/UTestUnitDlgMgr.pas @@ -3,7 +3,7 @@ * 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) 2009-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2009-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a static class that manages and displays a test unit in a dialog * box. @@ -60,16 +60,17 @@ class procedure TTestUnitDlgMgr.DisplayTestUnit(const Owner: TComponent; } var TestUnitSource: string; // source code of test unit + TestUnit: TTestUnit; resourcestring sDlgTitle = 'Test Unit for %s'; // caption of dialog box begin // Generate unit source code - with TTestUnit.Create(Snippet) do - try - TestUnitSource := GenerateUnitSource; - finally - Free; - end; + TestUnit := TTestUnit.Create(Snippet); + try + TestUnitSource := TestUnit.GenerateUnitSource; + finally + TestUnit.Free; + end; // Convert source to higlighted XHTML document and display it TPreviewDlg.Execute( Owner, diff --git a/Src/UVersionInfo.pas b/Src/UVersionInfo.pas index 163f02361..c74fbdc01 100644 --- a/Src/UVersionInfo.pas +++ b/Src/UVersionInfo.pas @@ -3,7 +3,7 @@ * 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) 2006-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2006-2023, Peter Johnson (gravatar.com/delphidabbler). * * Provides details of the application's version information and provides a * record used to manipulate version numbers. @@ -200,27 +200,31 @@ class function TVersionInfo.FileVersionNumberStr: string; information. @return Version number string in form 9.9.9.9. } +var + VI: TPJVersionInfo; begin - with TPJVersionInfo.Create(nil) do - try - // casts TPJVersionNumber directly to string - Result := FileVersionNumber; - finally - Free; - end; + VI := TPJVersionInfo.Create(nil); + try + // casts TPJVersionNumber directly to string + Result := VI.FileVersionNumber; + finally + VI.Free; + end; end; class function TVersionInfo.ProductVerNum: TVersionNumber; {Product version number from fixed file information. @return Required version number record. } +var + VI: TPJVersionInfo; begin - with TPJVersionInfo.Create(nil) do - try - Result := ProductVersionNumber; // implicit type cast - finally - Free; - end; + VI := TPJVersionInfo.Create(nil); + try + Result := VI.ProductVersionNumber; // implicit type cast + finally + VI.Free; + end; end; class function TVersionInfo.ProductVersionNumberStr: string; @@ -228,14 +232,16 @@ class function TVersionInfo.ProductVersionNumberStr: string; information. @return Version number string in form 9.9.9.9. } +var + VI: TPJVersionInfo; begin - with TPJVersionInfo.Create(nil) do - try - // casts TPJVersionNumber directly to string - Result := ProductVersionNumber; - finally - Free; - end; + VI := TPJVersionInfo.Create(nil); + try + // casts TPJVersionNumber directly to string + Result := VI.ProductVersionNumber; + finally + VI.Free; + end; end; class function TVersionInfo.ProductVersionStr: string; @@ -243,26 +249,30 @@ class function TVersionInfo.ProductVersionStr: string; ProductVersionNumberStr. @return Product version string. } +var + VI: TPJVersionInfo; begin - with TPJVersionInfo.Create(nil) do - try - Result := ProductVersion; - finally - Free; - end; + VI := TPJVersionInfo.Create(nil); + try + Result := VI.ProductVersion; + finally + VI.Free; + end; end; class function TVersionInfo.SpecialBuildStr: string; {Gets special build information from string table. @return Required copyright information. } +var + VI: TPJVersionInfo; begin - with TPJVersionInfo.Create(nil) do - try - Result := SpecialBuild; - finally - Free; - end; + VI := TPJVersionInfo.Create(nil); + try + Result := VI.SpecialBuild; + finally + VI.Free; + end; end; { TVersionNumber } diff --git a/Src/UWBCommandBars.pas b/Src/UWBCommandBars.pas index 0e79ac616..ba5d27c09 100644 --- a/Src/UWBCommandBars.pas +++ b/Src/UWBCommandBars.pas @@ -3,7 +3,7 @@ * 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) 2007-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2007-2023, Peter Johnson (gravatar.com/delphidabbler). * * Defines various classes used to configure one or more command bars owned by * a web browser container. Command bars are UI elements used to issue commands, @@ -246,6 +246,7 @@ function TWBDefaultPopupMenuWrapper.GetImageIndex( ImgTags: IDispatchList; // all <img> children of parent ImgTag: IDispatch; // <img> child of parent that contains required GIF Src: string; // resource URL of GIF file + MenuImages: TGIFImageList; begin Result := -1; // Check if parent elem is a <div> or <span> with class "option" @@ -267,12 +268,12 @@ function TWBDefaultPopupMenuWrapper.GetImageIndex( // Get matching bitmap from image list: add one from GIF file if not found Result := -1; if Menu.Images is TGIFImageList then - with Menu.Images as TGIFImageList do - begin - Result := ImageIndex(Src); - if Result = -1 then - Result := AddGIFImage(Src); - end; + begin + MenuImages := Menu.Images as TGIFImageList; + Result := MenuImages.ImageIndex(Src); + if Result = -1 then + Result := MenuImages.AddGIFImage(Src); + end; end; procedure TWBDefaultPopupMenuWrapper.GetLinkMenuItems(const Doc: IDispatch; diff --git a/Src/UWaitForThreadUI.pas b/Src/UWaitForThreadUI.pas index d6d20e13d..16d1738ab 100644 --- a/Src/UWaitForThreadUI.pas +++ b/Src/UWaitForThreadUI.pas @@ -3,7 +3,7 @@ * 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) 2006-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2006-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a class that executes a thread and displays a dialog box if thread * takes more than a specified time to complete. @@ -314,14 +314,18 @@ class procedure TWaitForThreadUI.Run(const AThread: TThread; displayed (optional) @param AMinDisplayTime [in] Minimum time to display form (optional). } +var + Instance: TWaitForThreadUI; begin Assert(Assigned(AThread), ClassName + '.Run: AThread is nil'); - with InternalCreate(AThread, AForm, APauseBeforeDisplay, AMinDisplayTime) do - try - Execute; - finally - Free; - end; + Instance := InternalCreate( + AThread, AForm, APauseBeforeDisplay, AMinDisplayTime + ); + try + Instance.Execute; + finally + Instance.Free; + end; end; procedure TWaitForThreadUI.ShowForm; From 54f88392085486699408bfbd29d715d6318755f7 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 14 Jul 2023 16:58:07 +0100 Subject: [PATCH 200/330] Fix splash screen bug due to with statement removal There was a memory access bug caused when with statement was removed. --- Src/FmSplash.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Src/FmSplash.pas b/Src/FmSplash.pas index de4b7bd34..1b44eaf04 100644 --- a/Src/FmSplash.pas +++ b/Src/FmSplash.pas @@ -237,7 +237,7 @@ function TSplashAligner.GetMainFormBounds(const AForm: TCustomForm): TRectEx; if not Settings.GetWdwState(Result, State) or (State = wsMaximized) then Result := Screen.WorkAreaRect; // we use workarea of primary monitor finally - Free; + Settings.Free; end; end; From 4edaefed72a3225ba86e34245133cbf53544c771 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 14 Jul 2023 19:32:25 +0100 Subject: [PATCH 201/330] Reverse order of compiler list in compiler setup dlg --- Src/FmCompilersDlg.UCompilerListMgr.pas | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/Src/FmCompilersDlg.UCompilerListMgr.pas b/Src/FmCompilersDlg.UCompilerListMgr.pas index 9359d0897..63ffefe0f 100644 --- a/Src/FmCompilersDlg.UCompilerListMgr.pas +++ b/Src/FmCompilersDlg.UCompilerListMgr.pas @@ -3,7 +3,7 @@ * 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) 2011-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2011-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements a class that manages display of compiler names in an owner draw * list box. @@ -34,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> @@ -87,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; @@ -103,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; @@ -139,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 @@ -208,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; From a2c5f68c1b39567304e869ddd0733454b4ca9100 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 14 Jul 2023 19:33:25 +0100 Subject: [PATCH 202/330] Reverse order of compiler list in find compilers dlg --- Src/FmFindCompilerDlg.pas | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Src/FmFindCompilerDlg.pas b/Src/FmFindCompilerDlg.pas index 277c4ae9c..958827bf5 100644 --- a/Src/FmFindCompilerDlg.pas +++ b/Src/FmFindCompilerDlg.pas @@ -55,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 @@ -363,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; From db7e3b35902df44e8f9318e4ac7de96661a9cadc Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 14 Jul 2023 19:34:10 +0100 Subject: [PATCH 203/330] Remove redundant compiler settings keys In an earlier commit list ordering keys were added to TCompilerSettings, (either speculatively or by mistake). These keys were removed as they are not used. --- Src/Compilers.USettings.pas | 2 -- 1 file changed, 2 deletions(-) diff --git a/Src/Compilers.USettings.pas b/Src/Compilers.USettings.pas index 3468c3693..2a1fe77ae 100644 --- a/Src/Compilers.USettings.pas +++ b/Src/Compilers.USettings.pas @@ -27,8 +27,6 @@ TCompilerSettings = class(TNoConstructObject) const AllCompilersConfigSection = ssCompilers; PermitStartupDetectionKey = 'PermitStartupDetection'; - ListFPCAtTopKey = 'Lists:FPCAtTop'; - ListDelphiOldestFirstKey = 'Lists:DelphiOldestFirst'; class function ReadStorage: ISettingsSection; class procedure DoSaveProperty(const WriteProp: TProc<ISettingsSection>); class procedure SaveProperty(const Key: string; const Value: Boolean); From 6fa0d4c0961b54c12ead4a1dce5ca56b08d52f0a Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Tue, 7 Nov 2023 11:21:43 +0000 Subject: [PATCH 204/330] Add support for test compiling with Delphi 12 Added compiler to relevant Src/Compilers/* files. Updated .ini and .xml file support code to persist compiler results. Added Delphi 12 CompilerVersion const to codegen preferences page. Fixes #121 --- Src/Compilers.UBDS.pas | 8 +++++++- Src/Compilers.UGlobals.pas | 3 ++- Src/DBIO.UIniDataReader.pas | 1 + Src/FrCodeGenPrefs.pas | 1 + Src/UXMLDocConsts.pas | 2 +- 5 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Src/Compilers.UBDS.pas b/Src/Compilers.UBDS.pas index 668916c19..a7c871984 100644 --- a/Src/Compilers.UBDS.pas +++ b/Src/Compilers.UBDS.pas @@ -152,6 +152,8 @@ function TBDSCompiler.GetIDString: string; Result := 'D104S'; ciD11A: Result := 'D11A'; + ciD12A: + Result := 'D12Y'; else raise EBug.Create(ClassName + '.GetIDString: Invalid ID'); end; @@ -175,7 +177,8 @@ function TBDSCompiler.GetName: string; sDelphi102T = 'Delphi 10.2 Tokyo'; sDelphi103R = 'Delphi 10.3 Rio'; sDelphi104S = 'Delphi 10.4 Sydney'; - sDelphi11A = 'Delphi 11.x Alexandria'; + sDelphi11A = 'Delphi 11.x Alexandria'; + sDelphi12A = 'Delphi 12 Athens'; begin case GetID of ciDXE: @@ -206,6 +209,8 @@ function TBDSCompiler.GetName: string; Result := sDelphi104S; ciD11A: Result := sDelphi11A; + ciD12A: + Result := sDelphi12A; else Result := Format(sCompilerName, [ProductVersion]); end; @@ -240,6 +245,7 @@ function TBDSCompiler.InstallationRegKey: string; 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.UGlobals.pas b/Src/Compilers.UGlobals.pas index 3600176bf..5fd63023d 100644 --- a/Src/Compilers.UGlobals.pas +++ b/Src/Compilers.UGlobals.pas @@ -44,6 +44,7 @@ interface ciD103R, // Delphi 10.3 Rio ciD104S, // Delphi 10.4 Sydney, ciD11A, // Delphi 11.x Alexandria + ciD12A, // Delphi 12 Athens ciFPC // Free Pascal ); @@ -57,7 +58,7 @@ interface cBDSCompilers = [ ciD2005w32, ciD2006w32, ciD2007, ciD2009w32, ciD2010, ciDXE, ciDXE2, ciDXE3, ciDXE4, ciDXE5, ciDXE6, ciDXE7, ciDXE8, ciD10S, ciD101B, ciD102T, - ciD103R, ciD104S, ciD11A + ciD103R, ciD104S, ciD11A, ciD12A ]; const diff --git a/Src/DBIO.UIniDataReader.pas b/Src/DBIO.UIniDataReader.pas index 989ab9fef..4d831156c 100644 --- a/Src/DBIO.UIniDataReader.pas +++ b/Src/DBIO.UIniDataReader.pas @@ -236,6 +236,7 @@ implementation 'Delphi2010', 'DelphiXE', 'DelphiXE2', 'DelphiXE3', 'DelphiXE4', 'DelphiXE5', 'DelphiXE6', 'DelphiXE7', 'DelphiXE8', 'Delphi10S', 'Delphi101B', 'Delphi102T', 'Delphi103R', 'Delphi104S', 'Delphi11A', + 'Delphi12A', 'FPC' ); diff --git a/Src/FrCodeGenPrefs.pas b/Src/FrCodeGenPrefs.pas index be4d50516..cff57d325 100644 --- a/Src/FrCodeGenPrefs.pas +++ b/Src/FrCodeGenPrefs.pas @@ -681,6 +681,7 @@ procedure TCodeGenPrefsFrame.PopulatePreDefCompilerMenu; 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/UXMLDocConsts.pas b/Src/UXMLDocConsts.pas index e84fe3455..53eb02cc7 100644 --- a/Src/UXMLDocConsts.pas +++ b/Src/UXMLDocConsts.pas @@ -68,7 +68,7 @@ interface 'd2005', 'd2006', 'd2007', 'd2009', 'd2010', 'dXE', 'dXE2', 'dXE3', 'dDX4' {error, but in use so can't fix}, 'dXE5', 'dXE6', 'dXE7', 'dXE8', - 'd10s', 'd101b', 'd102t', 'd103r', 'd104s', 'd11a', + 'd10s', 'd101b', 'd102t', 'd103r', 'd104s', 'd11a', 'd12y', 'fpc' ); From 212fbaa14b44ebb0363dfae538a6012f6586c704 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Tue, 7 Nov 2023 11:47:34 +0000 Subject: [PATCH 205/330] Update file format docs re Delphi 12 support --- Docs/Design/FileFormats/config.html | 9 +++++++++ Docs/Design/FileFormats/export.html | 9 +++++++++ Docs/Design/FileFormats/main-db.html | 3 +++ Docs/Design/FileFormats/user-db.html | 9 +++++++++ 4 files changed, 30 insertions(+) diff --git a/Docs/Design/FileFormats/config.html b/Docs/Design/FileFormats/config.html index 8c0061093..09f7d7114 100644 --- a/Docs/Design/FileFormats/config.html +++ b/Docs/Design/FileFormats/config.html @@ -259,6 +259,9 @@ <h4> <li> <em>D11A</em> – Delphi 11.x Alexandria </li> + <li> + <em>D12Y</em> – Delphi 12 Athens + </li> <li> <em>FPC</em> – Free Pascal </li> @@ -613,6 +616,12 @@ <h4> <dd> Indicates whether Delphi 11.x Alexandria was included in the search. </dd> + <dt> + <code class="key">D12Y</code> (Boolean) + </dt> + <dd> + Indicates whether Delphi 12 Athens was included in the search. + </dd> <dt> <code class="key">FPC</code> (Boolean) </dt> diff --git a/Docs/Design/FileFormats/export.html b/Docs/Design/FileFormats/export.html index 5f57b1c60..81e956f14 100644 --- a/Docs/Design/FileFormats/export.html +++ b/Docs/Design/FileFormats/export.html @@ -606,6 +606,9 @@ <h2> <li> <em>d11a</em> – Delphi 11.x Alexandria compiler <span class="highlight">(v7.2 & later)</span> </li> + <li> + <em>d12y</em> – Delphi 12 Athens compiler <span class="highlight">(v7.4 & later)</span> + </li> <li> <em>fpc</em> – Free Pascal compiler <span class="highlight">(all versions)</span> </li> @@ -984,6 +987,12 @@ <h2> <dd> Updated with CodeSnip v4.21.0 to add support for REML v5, which is backward compatible with REML v4. </dd> + <dt> + <em>Version 7.4 - 7 November 2023</em> + </dt> + <dd> + Updated in time for CodeSnip v4.22.0 to add support for Delphi 12 Athens. + </dd> </dl> </dd> </dl> diff --git a/Docs/Design/FileFormats/main-db.html b/Docs/Design/FileFormats/main-db.html index 867438356..b1d19aa47 100644 --- a/Docs/Design/FileFormats/main-db.html +++ b/Docs/Design/FileFormats/main-db.html @@ -419,6 +419,9 @@ <h4> <li> <code class="key">Delphi11A</code> – Delphi 11.x Alexandria compiler * </li> + <li> + <code class="key">Delphi12A</code> – Delphi 12 Athens compiler * + </li> <li> <code class="key">FPC</code> – Free Pascal compiler </li> diff --git a/Docs/Design/FileFormats/user-db.html b/Docs/Design/FileFormats/user-db.html index a5e03cfca..9ca2fe261 100644 --- a/Docs/Design/FileFormats/user-db.html +++ b/Docs/Design/FileFormats/user-db.html @@ -631,6 +631,9 @@ <h3 id="xml-file"> <li> <em>d11a</em> – Delphi 11.x Alexandria compiler <span class="highlight">(v6.10 & later)</span> </li> + <li> + <em>d12y</em> – Delphi 12 Athens compiler <span class="highlight">(v6.12 & later)</span> + </li> <li> <em>fpc</em> – Free Pascal compiler <span class="highlight">(all versions)</span> </li> @@ -1020,6 +1023,12 @@ <h2> <dd> Updated with CodeSnip v4.21.0 to add support for REML v5, which is backwards compatible with REML v4. </dd> + <dt> + <em>Version 6.12 - 7 November 2023</em> + </dt> + <dd> + Updated in time for CodeSnip v4.22.0 to add support for Delphi 12 Athens. + </dd> </dl> </dd> </dl> From a048d1f9ecf6b86b14537fdea669d1e1c9069e91 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Tue, 7 Nov 2023 11:48:11 +0000 Subject: [PATCH 206/330] Update ReadMe.txt to note Delphi 12 support --- Docs/ReadMe.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Docs/ReadMe.txt b/Docs/ReadMe.txt index a2020254e..b7806db53 100644 --- a/Docs/ReadMe.txt +++ b/Docs/ReadMe.txt @@ -14,8 +14,8 @@ 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 11.x -Alexandria and Free Pascal. +with each installed Win32 version of Delphi from Delphi 2 to Delphi 12 Athens +and Free Pascal. Compilable Pascal units can be created that contain selected snippets. @@ -204,8 +204,8 @@ 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 11.x Alexandria) and -FreePascal, providing some simple rules are followed. +Windows 32 version of Delphi (from Delphi 2 to Delphi 12 Athens) 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 From 07405a21764172ee25e6c9977741a44291c157e7 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Tue, 7 Nov 2023 11:49:08 +0000 Subject: [PATCH 207/330] Update help topics re support for Delphi 12 --- Src/Help/HTML/about_compiler_checks.htm | 2 +- Src/Help/HTML/dlg_configcompilers.htm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Src/Help/HTML/about_compiler_checks.htm b/Src/Help/HTML/about_compiler_checks.htm index 54ab897b0..5921eb7d8 100644 --- a/Src/Help/HTML/about_compiler_checks.htm +++ b/Src/Help/HTML/about_compiler_checks.htm @@ -34,7 +34,7 @@ <h1> </p> <p> The supported compilers are the Win32 Delphi compilers from Delphi 2 to - Delphi 11.x Alexandria and Free Pascal. + Delphi 12 Athens and Free Pascal. </p> <h2> Configuring CodeSnip diff --git a/Src/Help/HTML/dlg_configcompilers.htm b/Src/Help/HTML/dlg_configcompilers.htm index 73e6ad0be..f2faa3ee8 100644 --- a/Src/Help/HTML/dlg_configcompilers.htm +++ b/Src/Help/HTML/dlg_configcompilers.htm @@ -312,7 +312,7 @@ <h2> </h2> <p> <em>CodeSnip</em> can automatically detect the presence of Win 32 Delphi - compilers from Delphi 2 to Delphi 11.x Alexandria. Click the <em>Detect + 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. From ead4e069a207db6d85c9ac72d23085057e703ca7 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 8 Nov 2023 02:10:40 +0000 Subject: [PATCH 208/330] Update license copyright date range to include 2023 --- Docs/Design/FileFormats/config.html | 2 +- Docs/Design/FileFormats/export.html | 2 +- Docs/Design/FileFormats/main-db.html | 2 +- Docs/Design/FileFormats/user-db.html | 2 +- Src/Compilers.UBDS.pas | 2 +- Src/Compilers.UGlobals.pas | 2 +- Src/DBIO.UIniDataReader.pas | 2 +- Src/Help/HTML/about_compiler_checks.htm | 2 +- Src/Help/HTML/dlg_configcompilers.htm | 2 +- Src/UXMLDocConsts.pas | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Docs/Design/FileFormats/config.html b/Docs/Design/FileFormats/config.html index 09f7d7114..d6a57c49a 100644 --- a/Docs/Design/FileFormats/config.html +++ b/Docs/Design/FileFormats/config.html @@ -5,7 +5,7 @@ * 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-2022, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2012-2023, Peter Johnson (gravatar.com/delphidabbler). * * CodeSnip File Format Documentation: Configuration Files --> diff --git a/Docs/Design/FileFormats/export.html b/Docs/Design/FileFormats/export.html index 81e956f14..e60bfe00c 100644 --- a/Docs/Design/FileFormats/export.html +++ b/Docs/Design/FileFormats/export.html @@ -5,7 +5,7 @@ * 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-2022, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2012-2023, Peter Johnson (gravatar.com/delphidabbler). * * CodeSnip File Format Documentation: Export --> diff --git a/Docs/Design/FileFormats/main-db.html b/Docs/Design/FileFormats/main-db.html index b1d19aa47..25dc2834a 100644 --- a/Docs/Design/FileFormats/main-db.html +++ b/Docs/Design/FileFormats/main-db.html @@ -5,7 +5,7 @@ * 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-2022, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2012-2023, Peter Johnson (gravatar.com/delphidabbler). * * CodeSnip File Format Documentation: Main Database --> diff --git a/Docs/Design/FileFormats/user-db.html b/Docs/Design/FileFormats/user-db.html index 9ca2fe261..8a0e6945c 100644 --- a/Docs/Design/FileFormats/user-db.html +++ b/Docs/Design/FileFormats/user-db.html @@ -5,7 +5,7 @@ * 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-2022, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2012-2023, Peter Johnson (gravatar.com/delphidabbler). * * CodeSnip File Format Documentation: User Database --> diff --git a/Src/Compilers.UBDS.pas b/Src/Compilers.UBDS.pas index a7c871984..3ef1fde4c 100644 --- a/Src/Compilers.UBDS.pas +++ b/Src/Compilers.UBDS.pas @@ -3,7 +3,7 @@ * 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) 2006-2022, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2006-2023, Peter Johnson (gravatar.com/delphidabbler). * * Class that controls and provides information about Borland CodeGear and * Embarcadero "BDS" Win32 compilers. diff --git a/Src/Compilers.UGlobals.pas b/Src/Compilers.UGlobals.pas index 5fd63023d..7e660a166 100644 --- a/Src/Compilers.UGlobals.pas +++ b/Src/Compilers.UGlobals.pas @@ -3,7 +3,7 @@ * 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) 2005-2022, Peter Johnson (gravatar.com/delphidabbler). + * 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. diff --git a/Src/DBIO.UIniDataReader.pas b/Src/DBIO.UIniDataReader.pas index 4d831156c..90b0c9657 100644 --- a/Src/DBIO.UIniDataReader.pas +++ b/Src/DBIO.UIniDataReader.pas @@ -3,7 +3,7 @@ * 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) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2023, Peter Johnson (gravatar.com/delphidabbler). * * Implements code that reads the main CodeSnip database from .ini and .dat * files. diff --git a/Src/Help/HTML/about_compiler_checks.htm b/Src/Help/HTML/about_compiler_checks.htm index 5921eb7d8..ab4a5784f 100644 --- a/Src/Help/HTML/about_compiler_checks.htm +++ b/Src/Help/HTML/about_compiler_checks.htm @@ -4,7 +4,7 @@ * 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) 2005-2022, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2023, Peter Johnson (gravatar.com/delphidabbler). * * Help topic explaining compiler checks. --> diff --git a/Src/Help/HTML/dlg_configcompilers.htm b/Src/Help/HTML/dlg_configcompilers.htm index f2faa3ee8..787f3bec5 100644 --- a/Src/Help/HTML/dlg_configcompilers.htm +++ b/Src/Help/HTML/dlg_configcompilers.htm @@ -4,7 +4,7 @@ * 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) 2005-2022, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2023, Peter Johnson (gravatar.com/delphidabbler). * * Help topic for Configure Compilers dialogue box. --> diff --git a/Src/UXMLDocConsts.pas b/Src/UXMLDocConsts.pas index 53eb02cc7..122d13322 100644 --- a/Src/UXMLDocConsts.pas +++ b/Src/UXMLDocConsts.pas @@ -3,7 +3,7 @@ * 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) 2008-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2008-2023, Peter Johnson (gravatar.com/delphidabbler). * * Constants defined node names and attributes used in the various XML documents * used by CodeSnip. From 464cc5656a6d719845dbcc2b8d95cf92a15a2367 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 8 Nov 2023 02:31:53 +0000 Subject: [PATCH 209/330] Bump version number to v4.22.0 build 270 --- Src/VersionInfo.vi-inc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Src/VersionInfo.vi-inc b/Src/VersionInfo.vi-inc index 78ab7ae12..7fdf24930 100644 --- a/Src/VersionInfo.vi-inc +++ b/Src/VersionInfo.vi-inc @@ -1,8 +1,8 @@ # CodeSnip Version Information Macros for Including in .vi files # Version & build numbers -version=4.21.2 -build=269 +version=4.22.0 +build=270 # String file information copyright=Copyright © P.D.Johnson, 2005-<YEAR>. From 16f0951b9d64e0da9de3a45ec6a31748b8f9f46f Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 8 Nov 2023 02:32:33 +0000 Subject: [PATCH 210/330] Update change log with details of release v4.22.0 --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ed3fc709..afb4a535e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,17 @@ 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.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]. From 8e83b561d1b7d12c236e6883b9b01bf086b5b278 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Tue, 2 Apr 2024 12:23:59 +0100 Subject: [PATCH 211/330] Remove marketing names from later Delpji compilers Fixes #125 --- Src/Compilers.UBDS.pas | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Src/Compilers.UBDS.pas b/Src/Compilers.UBDS.pas index 3ef1fde4c..509e0f993 100644 --- a/Src/Compilers.UBDS.pas +++ b/Src/Compilers.UBDS.pas @@ -3,7 +3,7 @@ * 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) 2006-2023, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2006-2024, Peter Johnson (gravatar.com/delphidabbler). * * Class that controls and provides information about Borland CodeGear and * Embarcadero "BDS" Win32 compilers. @@ -172,13 +172,13 @@ function TBDSCompiler.GetName: string; 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 Athens'; + 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: From adb92d22ea7763172a72755f1d7f8f37910354cc Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Tue, 2 Apr 2024 12:34:04 +0100 Subject: [PATCH 212/330] Add new ' REML entity Fixes #99 --- Src/UREMLDataIO.pas | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Src/UREMLDataIO.pas b/Src/UREMLDataIO.pas index 6924ab76c..76974afc8 100644 --- a/Src/UREMLDataIO.pas +++ b/Src/UREMLDataIO.pas @@ -3,7 +3,7 @@ * 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) 2008-2023, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2008-2024, Peter Johnson (gravatar.com/delphidabbler). * * Implements classes that render and parse Routine Extra Markup Language (REML) * code. This markup is used to read and store active text objects as used by @@ -1035,7 +1035,7 @@ class function TREMLEntities.CharToMnemonicEntity(const Ch: Char): string; {Class constructor. Creates map of mnemonic entities to equivalent characters. } begin - SetLength(fEntityMap, 34); + SetLength(fEntityMap, 35); // Supported character entities. All are optional unless otherwise stated // REML v1 fEntityMap[0] := TREMLEntity.Create('amp', '&'); // required in REML @@ -1074,6 +1074,8 @@ class function TREMLEntities.CharToMnemonicEntity(const Ch: Char): string; fEntityMap[31] := TREMLEntity.Create('laquo', '«'); fEntityMap[32] := TREMLEntity.Create('raquo', '»'); fEntityMap[33] := TREMLEntity.Create('iquest', '¿'); + // REML v6 + fEntityMap[34] := TREMLEntity.Create('apos', SINGLEQUOTE); end; class destructor TREMLEntities.Destroy; From dfa315ba8548aaef718535b456e249018f74f1d1 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Tue, 2 Apr 2024 12:46:36 +0100 Subject: [PATCH 213/330] Update REML docs & help topic re REML v6 * Updated affected file format documentation * Updated REML documentation (reml.html) * Updated help topic (reml.htm) Also corrected omission in list of REML entities in reml.html --- Docs/Design/FileFormats/export.html | 21 +++++++++++++++++---- Docs/Design/FileFormats/main-db.html | 4 ++-- Docs/Design/FileFormats/user-db.html | 19 ++++++++++++++++--- Docs/Design/reml.html | 27 ++++++++++++++++++++++++--- Src/Help/HTML/reml.htm | 6 +++--- 5 files changed, 62 insertions(+), 15 deletions(-) diff --git a/Docs/Design/FileFormats/export.html b/Docs/Design/FileFormats/export.html index e60bfe00c..29ca8a849 100644 --- a/Docs/Design/FileFormats/export.html +++ b/Docs/Design/FileFormats/export.html @@ -5,7 +5,7 @@ * 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-2023, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2012-2024, Peter Johnson (gravatar.com/delphidabbler). * * CodeSnip File Format Documentation: Export --> @@ -300,9 +300,13 @@ <h2> encoded in REML markup. REML v4 is supported. </li> <li> - <span class="highlight">version 7.3 and later:</span> Content is formatted text + <span class="highlight">version 7.3 and 7.4:</span> Content is formatted text encoded in REML markup. REML v5 is supported. </li> + <li> + <span class="highlight">version 7.5 and later:</span> Content is formatted text + encoded in REML markup. REML v6 is supported. + </li> </ul> </dd> @@ -447,7 +451,10 @@ <h2> <span class="highlight">versions 5 to 7.2:</span> supports REML v4. </li> <li> - <span class="highlight">version 7.3 & later:</span> supports REML v5. + <span class="highlight">version 7.3 & 7.4:</span> supports REML v5. + </li> + <li> + <span class="highlight">version 7.5 & later:</span> supports REML v6. </li> </ul> </li> @@ -993,6 +1000,12 @@ <h2> <dd> Updated in time for CodeSnip v4.22.0 to add support for Delphi 12 Athens. </dd> + <dt> + <em>Version 7.5 - 2 April 2014</em> + </dt> + <dd> + Added support for REML v6, which is backward compatible with REML v4. + </dd> </dl> </dd> </dl> @@ -1042,7 +1055,7 @@ <h2> </p> <p> - Readers of v2 files and later can parse REML as v5, since all versions of REML up to v5 are backwards compatible. + Readers of v2 files and later can parse REML as v6, since all versions of REML up to v6 are backwards compatible. </p> </section> diff --git a/Docs/Design/FileFormats/main-db.html b/Docs/Design/FileFormats/main-db.html index 25dc2834a..1b122069c 100644 --- a/Docs/Design/FileFormats/main-db.html +++ b/Docs/Design/FileFormats/main-db.html @@ -5,7 +5,7 @@ * 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-2023, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2012-2024, Peter Johnson (gravatar.com/delphidabbler). * * CodeSnip File Format Documentation: Main Database --> @@ -928,7 +928,7 @@ <h2> <ol> <li id="footnote-1"> <p> - REML is a text markup language used to format text. REML version 5 is supported. The REML format is documented <a href="../reml.html">here</a>. + REML is a text markup language used to format text. REML version 6 is supported. The REML format is documented <a href="../reml.html">here</a>. </p> </li> <li id="footnote-2"> diff --git a/Docs/Design/FileFormats/user-db.html b/Docs/Design/FileFormats/user-db.html index 8a0e6945c..4756169b2 100644 --- a/Docs/Design/FileFormats/user-db.html +++ b/Docs/Design/FileFormats/user-db.html @@ -5,7 +5,7 @@ * 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-2023, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2012-2024, Peter Johnson (gravatar.com/delphidabbler). * * CodeSnip File Format Documentation: User Database --> @@ -325,9 +325,13 @@ <h3 id="xml-file"> encoded in REML markup. REML v4 is supported. </li> <li> - <span class="highlight">version 6.11 & later:</span> Content is formatted text + <span class="highlight">version 6.11 & 6.12:</span> Content is formatted text encoded in REML markup. REML v5 is supported. </li> + <li> + <span class="highlight">version 6.13 & later:</span> Content is formatted text + encoded in REML markup. REML v6 is supported. + </li> </ul> </dd> @@ -472,7 +476,10 @@ <h3 id="xml-file"> <span class="highlight">versions 5 & 6.10:</span> supports REML v4. </li> <li> - <span class="highlight">version 6.11 & later:</span> supports REML v5. + <span class="highlight">version 6.11 & 6.12:</span> supports REML v5. + </li> + <li> + <span class="highlight">version 6.13 & later:</span> supports REML v6. </li> </ul> </li> @@ -1029,6 +1036,12 @@ <h2> <dd> Updated in time for CodeSnip v4.22.0 to add support for Delphi 12 Athens. </dd> + <dt> + <em>Version 6.13 - 2 April 2024</em> + </dt> + <dd> + Updated with CodeSnip v4.23.0 to add support for REML v6, which is backwards compatible with REML v4. + </dd> </dl> </dd> </dl> diff --git a/Docs/Design/reml.html b/Docs/Design/reml.html index a84de371a..b945f4332 100644 --- a/Docs/Design/reml.html +++ b/Docs/Design/reml.html @@ -1,7 +1,7 @@ <!DOCTYPE HTML> <!-- - * This file copyright (C) 2020-2023, Peter Johnson (gravatar.com/delphidabbler) and + * This file copyright (C) 2020-2024, Peter Johnson (gravatar.com/delphidabbler) and * is licensed under the MIT License: https://opensource.org/licenses/MIT * * DelphiDabbler Code Snippets Database Documentation: REML markup language @@ -251,7 +251,7 @@ <h1> The REML language is a SGML language similar to a greatly simplified XHTML. The are a small number of tags and character entities that can be used. </p> <aside> - <strong>Note:</strong> The language described here is REML v5. v4 is still in regular use in CodeSnip up to v4.20.x. Earlier versions are obsolete. + <strong>Note:</strong> The language described here is REML v6. v4 is still in regular use in CodeSnip up to v4.20.x. Earlier versions are obsolete. </aside> </section> @@ -524,6 +524,10 @@ <h1> <td><code>&deg;</code></td> <td>°</td> </tr> + <tr> + <td><code>&cent;</code></td> + <td>¢</td> + </tr> <tr> <td><code>&laquo;</code></td> <td>«</td> @@ -536,6 +540,10 @@ <h1> <td><code>&iquest;</code></td> <td>¿</td> </tr> + <tr> + <td><code>&apos;</code></td> + <td>'</td> + </tr> </tbody> </table> @@ -644,8 +652,21 @@ <h1>Change Log</h1> </li> </ul> -</section> + <p> + <strong>v6 of 2024-04-02</strong> + </p> + + <p> + Introduced in CodeSnip v4.23.0 + </p> + <ul> + <li> + Added entity: <code class="value">&apos;</code>. + </li> + </ul> + + </section> </body> diff --git a/Src/Help/HTML/reml.htm b/Src/Help/HTML/reml.htm index 4eedac417..f66afea9d 100644 --- a/Src/Help/HTML/reml.htm +++ b/Src/Help/HTML/reml.htm @@ -4,7 +4,7 @@ * 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) 2009-2023, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2009-2024, Peter Johnson (gravatar.com/delphidabbler). * * Help topic describing REML markup language. --> @@ -43,8 +43,8 @@ <h1> <p> <em>REML</em> is <em>CodeSnip</em>'s own little markup language that can be used to style the text of a snippet's description and / or extra - information. The latest version is v5, which is backwards compatible with - all other versions. + information. The latest version is v6, which is backwards compatible with + all other versions. </p> <h2> Language Details From 258a196f5ad400b32cb6c21fee89aaf5c5e62f1e Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Tue, 2 Apr 2024 13:33:49 +0100 Subject: [PATCH 214/330] Split UClassHelpers unit into three separate units New units are: * ClassHelpers.UActions * ClassHelpers.UControls * ClassHelpers.UGraphics --- Src/ClassHelpers.UActions.pas | 43 ++++++++++ Src/ClassHelpers.UControls.pas | 68 +++++++++++++++ ...Helpers.pas => ClassHelpers.UGraphics.pas} | 84 ++----------------- Src/CodeSnip.dpr | 8 +- Src/CodeSnip.dproj | 4 +- 5 files changed, 125 insertions(+), 82 deletions(-) create mode 100644 Src/ClassHelpers.UActions.pas create mode 100644 Src/ClassHelpers.UControls.pas rename Src/{UClassHelpers.pas => ClassHelpers.UGraphics.pas} (66%) 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 66% rename from Src/UClassHelpers.pas rename to Src/ClassHelpers.UGraphics.pas index d18138ccc..d63866cc6 100644 --- a/Src/UClassHelpers.pas +++ b/Src/ClassHelpers.UGraphics.pas @@ -3,41 +3,20 @@ * 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-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2012-2024, Peter Johnson (gravatar.com/delphidabbler). * - * Provides various class helpers for VCL classes. + * Provides class helpers for VCL image classes. + * + * Extracted from in 2024 original UClassHelpers unit (2012-2021). } - -unit UClassHelpers; - -{ TODO: Separate different helpers into their own units, within a ClassHelpers - scope. E.g. ClassHelpers.Controls, ClassHelper.Graphics } +unit ClassHelpers.UGraphics; interface - uses // Delphi - Controls, Menus, ImgList, Graphics, ActnList, GIFImg; - - -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 @@ -62,16 +41,6 @@ TImageListHelper = class helper for TCustomImageList Size: Integer; MaskColour: TColor); 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 - public - /// <summary>Updates all actions in the action list by calling their Update - /// methods.</summary> - procedure Update; - end; - type /// <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 @@ -87,42 +56,12 @@ TGIFImageHelper = class helper for TGIFImage 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; @@ -181,16 +120,6 @@ procedure TImageListHelper.LoadFromResource(ResType: PChar; end; end; -{ TActionListHelper } - -procedure TActionListHelper.Update; -var - Action: TContainedAction; // each action in list -begin - for Action in Self do - Action.Update; -end; - { TGIFImageHelper } procedure TGIFImageHelper.LoadFromResource(const Module: HMODULE; @@ -207,4 +136,3 @@ procedure TGIFImageHelper.LoadFromResource(const Module: HMODULE; end; end. - diff --git a/Src/CodeSnip.dpr b/Src/CodeSnip.dpr index 9b900845b..1fe24aca3 100644 --- a/Src/CodeSnip.dpr +++ b/Src/CodeSnip.dpr @@ -3,7 +3,7 @@ * 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) 2005-2023, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2024, Peter Johnson (gravatar.com/delphidabbler). * * CodeSnip application project file. } @@ -192,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', @@ -372,7 +372,9 @@ uses FmDeleteUserDBDlg in 'FmDeleteUserDBDlg.pas' {DeleteUserDBDlg}, Compilers.UAutoDetect in 'Compilers.UAutoDetect.pas', Compilers.USettings in 'Compilers.USettings.pas', - FmRegisterCompilersDlg in 'FmRegisterCompilersDlg.pas' {RegisterCompilersDlg}; + FmRegisterCompilersDlg in 'FmRegisterCompilersDlg.pas' {RegisterCompilersDlg}, + ClassHelpers.UGraphics in 'ClassHelpers.UGraphics.pas', + ClassHelpers.UActions in 'ClassHelpers.UActions.pas'; // Include resources {$Resource ExternalObj.tlb} // Type library file diff --git a/Src/CodeSnip.dproj b/Src/CodeSnip.dproj index af19fb220..dc6c27915 100644 --- a/Src/CodeSnip.dproj +++ b/Src/CodeSnip.dproj @@ -394,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"/> @@ -579,6 +579,8 @@ <DCCReference Include="FmRegisterCompilersDlg.pas"> <Form>RegisterCompilersDlg</Form> </DCCReference> + <DCCReference Include="ClassHelpers.UGraphics.pas"/> + <DCCReference Include="ClassHelpers.UActions.pas"/> <None Include="CodeSnip.todo"/> <BuildConfiguration Include="Base"> <Key>Base</Key> From af6208082e0e6c40af3c3a2da8d474cf7534534a Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Tue, 2 Apr 2024 13:49:04 +0100 Subject: [PATCH 215/330] Update all units affected by split of UClassHelpers All units that referenced the now-removed UClassHelpers unit were modified to use one or more of the new, rafactored ClassHelpers.xxx units instead. --- Src/FmBase.pas | 5 +++-- Src/FmCompilersDlg.FrSearchDirs.pas | 7 +++++-- Src/FmMain.pas | 6 ++++-- Src/FmPrintDlg.pas | 5 +++-- Src/FmSplash.pas | 5 +++-- Src/FrSnippetLayoutPrefs.pas | 6 ++++-- Src/UGIFImageList.pas | 4 ++-- Src/ULEDImageList.pas | 4 ++-- Src/USnipKindListAdapter.pas | 2 +- 9 files changed, 27 insertions(+), 17 deletions(-) diff --git a/Src/FmBase.pas b/Src/FmBase.pas index 79574c9e6..a20163b2c 100644 --- a/Src/FmBase.pas +++ b/Src/FmBase.pas @@ -3,7 +3,7 @@ * 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) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). + * 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 @@ -136,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/FmCompilersDlg.FrSearchDirs.pas b/Src/FmCompilersDlg.FrSearchDirs.pas index 8774a4177..57b2195d3 100644 --- a/Src/FmCompilersDlg.FrSearchDirs.pas +++ b/Src/FmCompilersDlg.FrSearchDirs.pas @@ -3,7 +3,7 @@ * 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) 2011-2021, Peter Johnson (gravatar.com/delphidabbler). + * 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. @@ -109,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/FmMain.pas b/Src/FmMain.pas index 59c28851a..725d241aa 100644 --- a/Src/FmMain.pas +++ b/Src/FmMain.pas @@ -3,7 +3,7 @@ * 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) 2005-2023, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2024, Peter Johnson (gravatar.com/delphidabbler). * * Application's main form. Handles the program's main window display and user * interaction. @@ -583,9 +583,11 @@ implementation // Delphi Windows, Graphics, // Project + ClassHelpers.UControls, + ClassHelpers.UGraphics, DB.UCategory, DB.UMain, DB.USnippet, FmSplash, FmTrappedBugReportDlg, FmWaitDlg, IntfFrameMgrs, UActionFactory, UAppInfo, - UClassHelpers, UCodeShareMgr, UCommandBars, UConsts, UCopyInfoMgr, + UCodeShareMgr, UCommandBars, UConsts, UCopyInfoMgr, UCopySourceMgr, UDatabaseLoader, UDatabaseLoaderUI, UDetailTabAction, UEditSnippetAction, UExceptions, UHelpMgr, UHistoryMenus, UKeysHelper, UMessageBox, UNotifier, UNulDropTarget, UPrintMgr, UQuery, USaveSnippetMgr, diff --git a/Src/FmPrintDlg.pas b/Src/FmPrintDlg.pas index 5ef588f1c..b34cdcab0 100644 --- a/Src/FmPrintDlg.pas +++ b/Src/FmPrintDlg.pas @@ -3,7 +3,7 @@ * 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) 2007-2023, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2007-2024, Peter Johnson (gravatar.com/delphidabbler). * * Implements a print dialogue box. } @@ -76,7 +76,8 @@ implementation // Delphi Printers, Graphics, // Project - FmPreferencesDlg, FrPrintingPrefs, UClassHelpers, UConsts, UMessageBox, + ClassHelpers.UGraphics, + FmPreferencesDlg, FrPrintingPrefs, UConsts, UMessageBox, UPageSetupDlgMgr, UPrintInfo, UStructs, UStrUtils; diff --git a/Src/FmSplash.pas b/Src/FmSplash.pas index 1b44eaf04..eb2e1c034 100644 --- a/Src/FmSplash.pas +++ b/Src/FmSplash.pas @@ -3,7 +3,7 @@ * 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) 2007-2023, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2007-2024, Peter Johnson (gravatar.com/delphidabbler). * * Implements the program's splash screen. } @@ -68,7 +68,8 @@ implementation // Delphi Windows, Graphics, GIFImg, // Project - UAppInfo, UClassHelpers, UColours, UStructs, UWindowSettings; + ClassHelpers.UGraphics, + UAppInfo, UColours, UStructs, UWindowSettings; {$R *.dfm} diff --git a/Src/FrSnippetLayoutPrefs.pas b/Src/FrSnippetLayoutPrefs.pas index f02ef779e..a16cc1da4 100644 --- a/Src/FrSnippetLayoutPrefs.pas +++ b/Src/FrSnippetLayoutPrefs.pas @@ -3,7 +3,7 @@ * 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-2021, Peter Johnson (gravatar.com/delphidabbler). + * 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. @@ -84,7 +84,9 @@ implementation // Delphi Windows, Graphics, // Project - FmPreferencesDlg, UClassHelpers, UCtrlArranger; + ClassHelpers.UControls, + ClassHelpers.UGraphics, + FmPreferencesDlg, UCtrlArranger; {$R *.dfm} diff --git a/Src/UGIFImageList.pas b/Src/UGIFImageList.pas index 3ba34c7c0..f3e26a199 100644 --- a/Src/UGIFImageList.pas +++ b/Src/UGIFImageList.pas @@ -3,7 +3,7 @@ * 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) 2008-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2008-2024, Peter Johnson (gravatar.com/delphidabbler). * * Image list descendant that enables representations of GIF images loaded from * HTML resource to be added. Resource names are mapped to image indices. @@ -70,7 +70,7 @@ implementation uses // Delphi - GIFImg, UClassHelpers, + GIFImg, ClassHelpers.UGraphics, // Project UComparers; diff --git a/Src/ULEDImageList.pas b/Src/ULEDImageList.pas index c5a7b9663..839b064fa 100644 --- a/Src/ULEDImageList.pas +++ b/Src/ULEDImageList.pas @@ -3,7 +3,7 @@ * 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) 2009-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2009-2024, Peter Johnson (gravatar.com/delphidabbler). * * Defines a custom image list that provides a list of LED images. Image list is * automatically loaded from resources when class is instantiated. @@ -55,7 +55,7 @@ implementation uses // Project - UClassHelpers; + ClassHelpers.UGraphics; { diff --git a/Src/USnipKindListAdapter.pas b/Src/USnipKindListAdapter.pas index 47d03a6d6..745e819e1 100644 --- a/Src/USnipKindListAdapter.pas +++ b/Src/USnipKindListAdapter.pas @@ -3,7 +3,7 @@ * 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) 2009-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2009-2024, Peter Johnson (gravatar.com/delphidabbler). * * Implements a class that adapts a list of snippet kinds by providing an * alternative interface to the list, sorted by the name of the snippet kind. From bdf2de81e70ba17faea5b1e8772dcefadc3433ff Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Tue, 2 Apr 2024 13:59:46 +0100 Subject: [PATCH 216/330] Correct user DB format docs re REML v6 Description of REML format that may be assumed by readers of DB format v2 was change to from REML v5 to v6. --- Docs/Design/FileFormats/user-db.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Docs/Design/FileFormats/user-db.html b/Docs/Design/FileFormats/user-db.html index 4756169b2..bc761983e 100644 --- a/Docs/Design/FileFormats/user-db.html +++ b/Docs/Design/FileFormats/user-db.html @@ -1100,7 +1100,7 @@ <h3> </ul> <p> - Readers of v2 and later files may parse REML from any file version as if it were REML v5, since all versions of REML up to v5 are compatible. + 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. </p> <h3> From d7f14b28c3607552ef908e3378d3cb28e420e0d8 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Tue, 2 Apr 2024 14:06:36 +0100 Subject: [PATCH 217/330] Bump version number to v4.23.0 build 271 --- Src/VersionInfo.vi-inc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Src/VersionInfo.vi-inc b/Src/VersionInfo.vi-inc index 7fdf24930..c9ae9dae3 100644 --- a/Src/VersionInfo.vi-inc +++ b/Src/VersionInfo.vi-inc @@ -1,8 +1,8 @@ # CodeSnip Version Information Macros for Including in .vi files # Version & build numbers -version=4.22.0 -build=270 +version=4.23.0 +build=271 # String file information copyright=Copyright © P.D.Johnson, 2005-<YEAR>. From 656e9ddeee7f6421192e7d02c90042b76edc7246 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Tue, 2 Apr 2024 14:22:46 +0100 Subject: [PATCH 218/330] Update license copyright date to 2024 --- Docs/License.html | 6 +++--- Src/Help/HTML/license.htm | 4 ++-- Src/Install/Assets/License.rtf | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Docs/License.html b/Docs/License.html index 131c7a549..458ab8f79 100644 --- a/Docs/License.html +++ b/Docs/License.html @@ -1,7 +1,7 @@ <!DOCTYPE HTML> <!-- - * Copyright (C) 2012-2023, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2012-2024, Peter Johnson (gravatar.com/delphidabbler). * * CodeSnip license. * @@ -231,7 +231,7 @@ <h2> Executable Program </h2> <p> - DelphiDabbler <em>CodeSnip</em> is copyright © 2005-2023 by <a + DelphiDabbler <em>CodeSnip</em> is copyright © 2005-2024 by <a href="https://gravatar.com/delphidabbler" >Peter D Johnson</a>. </p> @@ -1801,7 +1801,7 @@ <h2 id="ddab-exclusive"> </h2> <p> - Files covered by this license are original work, copyright © 2012-2023, <a href="https://gravatar.com/delphidabbler">Peter D Johnson</a>. + Files covered by this license are original work, copyright © 2012-2024, <a href="https://gravatar.com/delphidabbler">Peter D Johnson</a>. </p> <p> diff --git a/Src/Help/HTML/license.htm b/Src/Help/HTML/license.htm index d9a261f15..fc310ae74 100644 --- a/Src/Help/HTML/license.htm +++ b/Src/Help/HTML/license.htm @@ -4,7 +4,7 @@ * 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-2023, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2012-2024, Peter Johnson (gravatar.com/delphidabbler). * * Help topic containing summary of CodeSnip license. --> @@ -27,7 +27,7 @@ <h1> <a name="license"></a>Summary of End User License Agreement </h1> <p> - DelphiDabbler <em>CodeSnip</em> is copyright © 2005-2023 by Peter D + DelphiDabbler <em>CodeSnip</em> is copyright © 2005-2024 by Peter D Johnson, <a href="https://gravatar.com/delphidabbler" class="weblink" diff --git a/Src/Install/Assets/License.rtf b/Src/Install/Assets/License.rtf index 7d2008d00..5ef3433ff 100644 --- a/Src/Install/Assets/License.rtf +++ b/Src/Install/Assets/License.rtf @@ -1,7 +1,7 @@ {\rtf1\ansi\ansicpg1252\deff0\nouicompat\deftab709{\fonttbl{\f0\fswiss\fprq2\fcharset0 Arial;}} {\colortbl ;\red0\green0\blue255;} {\*\generator Riched20 10.0.18362}\viewkind4\uc1 -\pard\sa113\f0\fs18\lang1033 DelphiDabbler CodeSnip is copyright \'a9 2005-2023 by Peter D Johnson, {{\field{\*\fldinst{HYPERLINK https://en.gravatar.com/delphidabbler }}{\fldrslt{https://en.gravatar.com/delphidabbler\ul0\cf0}}}}\f0\fs18 . \par +\pard\sa113\f0\fs18\lang1033 DelphiDabbler CodeSnip is copyright \'a9 2005-2024 by Peter D Johnson, {{\field{\*\fldinst{HYPERLINK https://en.gravatar.com/delphidabbler }}{\fldrslt{https://en.gravatar.com/delphidabbler\ul0\cf0}}}}\f0\fs18 . \par The executable version of CodeSnip is made available under the terms of the Mozilla Public License 2.0 ({{\field{\*\fldinst{HYPERLINK https://www.mozilla.org/MPL/2.0/ }}{\fldrslt{https://www.mozilla.org/MPL/2.0/\ul0\cf0}}}}\f0\fs18 ). This means you can use, copy and distribute CodeSnip as you wish.\par 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) in any modification you distribute unless you have the explicit permission of the copyright holder. \par For full information see the file \i License.html\i0 installed with this program.\fs24\lang2057\par From 9c2e46a145b6ecc2f78b7e13db9528750a7c1a27 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Tue, 2 Apr 2024 14:23:30 +0100 Subject: [PATCH 219/330] Update change log with details of release v4.23.0 --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index afb4a535e..5aeb246be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ 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.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]. From 848d284bdc4450c811333bdc875213e658a54156 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 3 Apr 2024 11:54:58 +0100 Subject: [PATCH 220/330] Split Docs/ReadMe.txt into two different versions Split into ReadMe-portable.txt relating to Portable edition and ReadMe-standard.txt for Standard edition. Both files were edited according to new purpose and to update content where necessary. --- Docs/ReadMe-portable.txt | 257 +++++++++++++++++++++++ Docs/{ReadMe.txt => ReadMe-standard.txt} | 174 ++++----------- 2 files changed, 303 insertions(+), 128 deletions(-) create mode 100644 Docs/ReadMe-portable.txt rename Docs/{ReadMe.txt => ReadMe-standard.txt} (64%) diff --git a/Docs/ReadMe-portable.txt b/Docs/ReadMe-portable.txt new file mode 100644 index 000000000..e0883fa5c --- /dev/null +++ b/Docs/ReadMe-portable.txt @@ -0,0 +1,257 @@ +================================================================================ + +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 CodeSnip Blog: +https://codesnip-app.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. + ++ 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-standard.txt similarity index 64% rename from Docs/ReadMe.txt rename to Docs/ReadMe-standard.txt index b7806db53..5f5ea703f 100644 --- a/Docs/ReadMe.txt +++ b/Docs/ReadMe-standard.txt @@ -1,6 +1,6 @@ ================================================================================ -DELPHIDABBLER CODESNIP v4 README +DELPHIDABBLER CODESNIP v4 STANDARD EDITION README ================================================================================ @@ -14,30 +14,26 @@ 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 Athens -and Free Pascal. +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. -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. +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. -+ The portable edition that can be run from any writeable removable storage - medium (e.g. a USB memory stick) and that makes no changes to the host - computer. This edition has no installer and is simply copied onto the required - medium. +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 standard and portable editions together on the same +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 @@ -48,18 +44,14 @@ 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. But note that recent -releases have only been tested on Windows 10/11. +or later, although IE 8, 9 or 10 are strongly recommended. Note that recent +releases have only been tested on Windows 11. -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 to Windows 11 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. +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 @@ -106,53 +98,15 @@ 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 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. 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 ================================================================================ -Uninstalling the Standard Edition ---------------------------------- - -CodeSnip can be uninstalled via "Installed Apps" (a.k.a. "Apps and Features", -a.k.a. "Programs and Features", a.k.a. "Add / Remove Programs") accessed from the -Windows Control Panel or by choosing "Uninstall DelphiDabbler CodeSnip" from the -program's start menu group. +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. Windows Vista -to Windows 11 with UAC prompts enabled will prompt for an admin password if -necessary. +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 @@ -161,16 +115,6 @@ 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, 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 ================================================================================ @@ -179,22 +123,19 @@ 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. This link opens the "Install or Update DelphiDabbler Snippets -Database" wizard style dialogue box. The dialogue box explains how to download -and install the database. +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. -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. +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. @@ -204,7 +145,7 @@ 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 12 Athens) and FreePascal, +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 @@ -235,7 +176,7 @@ Any type of snippet other than "freeform" can be test compiled. Updating the Program ================================================================================ -Updates are published on GitHub. See +Updates are published on GitHub. See https://github.com/delphidabbler/codesnip/releases News of new updates is published on the CodeSnip Blog: @@ -288,7 +229,7 @@ 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 is open source. See +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.) @@ -303,7 +244,8 @@ 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 and portable editions of CodeSnip share the same source code. +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 @@ -311,36 +253,15 @@ file "License.html" in the "Docs" directory of the repository for full licensing information. -Bugs +Bugs & Feature Requests ================================================================================ -Please do report any bugs you find. - -Bugs are recorded in tracker software. View the reported and fixed bugs via -https://github.com/delphidabbler/codesnip/issues (GitHub account required). - -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 do report any bugs you find. Suggestions for new features are also +welcomed. -Please ensure that you have installed the latest version of CodeSnip and checked -if the bug is still present before reporting it. - - -Feedback -================================================================================ - -If you want to suggest new features please use the feature request tracker -accessed from https://github.com/delphidabbler/codesnip/issues (GitHub account -required). Please check whether anyone else has requested something similar and -add a comment to their request if so. - -Always check the latest version of CodeSnip before requesting a feature just in -case it has already been implemented! +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 @@ -353,15 +274,12 @@ https://github.com/delphidabbler/codesnip-faq/blob/master/README.md Privacy ================================================================================ -As of v4.16.0 CodeSnip no longer stores or transmits any personally identifiable +From v4.16.0 CodeSnip neither stores nor transmits any personally identifiable data. -Because of this change the privacy statement that used to be provided with the -program has been removed. - -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. +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 From 7f2f758efc9ef2785c51a92a0cee0c266ac330fe Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 3 Apr 2024 13:09:29 +0100 Subject: [PATCH 221/330] Release different ReadMe.txt for standard & portable Makefile updated to copy appropriate ReadMe-portable.txt or ReadMe-standard.txt as ReadMe.txt to release zip files for portable and standard edition builds respectively. Made similar changes to Inno Setup script to copy ReadMe-standard.txt as ReadMe.txt into the install program. Both versions of ReadMe.txt were temporarily stored in new _build\release\~tmp~ directory. --- Src/Install/CodeSnip.iss | 3 ++- Src/Makefile | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Src/Install/CodeSnip.iss b/Src/Install/CodeSnip.iss index 47a85f471..3e154e1d0 100644 --- a/Src/Install/CodeSnip.iss +++ b/Src/Install/CodeSnip.iss @@ -29,6 +29,7 @@ #define SrcDocsPath SourcePath + "..\..\Docs\" #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)) @@ -89,7 +90,7 @@ 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: {#TmpPath}{#ReadMeFile}; DestDir: {app}; Flags: ignoreversion Source: {#SrcAssetsPath}UpdatingPreview.rtf; Flags: dontcopy [Icons] diff --git a/Src/Makefile b/Src/Makefile index 17b443abf..ce6cb8cf8 100644 --- a/Src/Makefile +++ b/Src/Makefile @@ -12,12 +12,15 @@ 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 @@ -115,6 +118,7 @@ config: @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 @@ -160,8 +164,10 @@ typelib: # Builds setup program setup: !ifndef PORTABLE - @del $(EXE_REL)\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 @@ -195,12 +201,16 @@ release: @cd .. -@if exist $(OUTFILE) del $(OUTFILE) !ifndef PORTABLE - @$(ZIP) -j -9 $(OUTFILE) $(EXE_ROOT)\CodeSnip-Setup-*.exe $(DOCS_ROOT)\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 + 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) $(DOCS_ROOT)\ReadMe.txt + @$(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_ROOT) From dc7cb79e3c82c94a6e8526949c73fec5957c01e3 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 3 Apr 2024 14:49:25 +0100 Subject: [PATCH 222/330] Update copyright date in header comments --- Src/Install/CodeSnip.iss | 2 +- Src/Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Src/Install/CodeSnip.iss b/Src/Install/CodeSnip.iss index 3e154e1d0..229db969a 100644 --- a/Src/Install/CodeSnip.iss +++ b/Src/Install/CodeSnip.iss @@ -2,7 +2,7 @@ ; 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) 2006-2022, Peter Johnson (gravatar.com/delphidabbler). +; Copyright (C) 2006-2024, Peter Johnson (gravatar.com/delphidabbler). ; ; Install file generation script for use with Inno Setup. diff --git a/Src/Makefile b/Src/Makefile index ce6cb8cf8..b8b69e3b5 100644 --- a/Src/Makefile +++ b/Src/Makefile @@ -2,7 +2,7 @@ # 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) 2009-2022, Peter Johnson (gravatar.com/delphidabbler). +# Copyright (C) 2009-2024, Peter Johnson (gravatar.com/delphidabbler). # # Makefile for the CodeSnip project. From bcaf6cc90084ff19a45480904d4227dccab4ea90 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 3 Apr 2024 15:08:59 +0100 Subject: [PATCH 223/330] Update Build.html re changes per issue #127 Updated re change from using single ReadMe.txt to using a version of the files for each edition. Made some other changes for clarity. --- Build.html | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/Build.html b/Build.html index fc6947166..0f96ec05e 100644 --- a/Build.html +++ b/Build.html @@ -723,9 +723,13 @@ <h4> </h4> <p> - The release file for the standard edition of <em>CodeSnip</em> includes the - setup file along with <code>ReadMe.txt</code> from the <code>Docs</code> - directory. Both files must exist. + The release zip file for the standard edition requires that the setup files is already + present in the <code>_build/exe</code> directory. +</p> + +<p> + The release file includes the setup file along with <code>ReadMe.txt</code> + that is automatically generated from <code>Docs/ReadMe-standard.txt</code>. </p> <p> @@ -752,9 +756,16 @@ <h4> </h4> <p> - The release file for the portable edition includes the portable executable - file, <code>CodeSnip-p.exe</code>, the help file <code>CodeSnip.chm</code> and - several files from the <code>Docs</code> directory. All must be present. + The release zip file for the portable edition cannot be created until the + <em>CodeSnip</em> excutable and the compiled help file are already present in the + <code>_build\exe</code> directory. +</p> + +<p> + The release file includes the portable executable file, <code>CodeSnip-p.exe</code>, + the help file <code>CodeSnip.chm</code>, <code>Docs/License.html</code> and + <code>ReadMe.txt</code> that is automatically generated from + <code>Docs/ReadMe-portable.txt</code>. </p> <p> From 4e120754407cde6a4124e6ff0a85b979e933c3b3 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 3 Apr 2024 15:46:38 +0100 Subject: [PATCH 224/330] Add Deploy.bat Deployment script that creates release zip files for both editions of CodeSnip and includes the version number passed on command line in zip file names. Fixes #128 --- Deploy.bat | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 Deploy.bat diff --git a/Deploy.bat b/Deploy.bat new file mode 100644 index 000000000..23d95a425 --- /dev/null +++ b/Deploy.bat @@ -0,0 +1,99 @@ +:: 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) 2024, Peter Johnson (gravatar.com/delphidabbler). +:: +:: Deploy script for CodeSnip. +:: +:: This script compiles release versions of the standard and portable editions +:: of CodeSnip and places them into two different zip files ready for release. +:: +:: This script uses Embarcadero Make. Various other programs are required to +:: run Make. See Src/Makefile for details. +:: +:: To use the script: +:: 1) Set the environment variables required for Make to succeed. See +:: Src/Makefile for details +:: 2) Change directory to that where this script is located. +:: 3) Run the script. +:: +:: Usage: +:: Deploy <version> +:: where +:: <version> is the version number of the release, e.g. 0.5.3-beta or 1.2.0. + +@echo off + +setlocal + +:: Check for required parameter +if "%1"=="" goto paramerror + +:: Store parameter +set Version=%1 + +:: Store common make parameters +set CommonParams=-DVERSION=%Version% + +:: Store standard edition make parameters +set StandardParams=%CommonParams% + +:: Store portable edition make parameters +set PortableParams=-DPORTABLE %CommonParams% + +:: Set command line +set MakeCmd=Make +set StandardMakeCmd=%MakeCmd% %StandardParams% +set PortableMakeCmd=%MakeCmd% %PortableParams% + +echo ---------------------------------------------- +echo Deploying CodeSnip Standard And Portable Builds +echo ----------------------------------------------- +echo. +echo Standard edition Make command: %StandardMakeCmd% +echo Portable edition Make command: %PortableMakeCmd% + +cd Src + +echo. +echo. +echo. +echo ========================= +echo Building Standard edition +echo ========================= +echo. +echo. +%StandardMakeCmd% + +echo. +echo. +echo. +echo ========================= +echo Building Portable edition +echo ========================= +echo. +echo. +%PortableMakeCmd% + +echo. +echo. +echo. +echo ==================== +echo Deployment completed +echo ==================== + +goto end + +:: Error messages + +:paramerror +echo. +echo ***ERROR: Please specify a version number as a parameter +echo. +goto end + +:: End +:end + +endlocal From b52c7853ee4baed649c3c43875db84bbbabc5b80 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 3 Apr 2024 23:47:51 +0100 Subject: [PATCH 225/330] Change README.md re use per-edition ReadMe.txt files --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8672ddf64..3787b2439 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ CodeSnip can import code from the DelphiDabbler [Code Snippets Database](https:/ The standard edition of CodeSnip is installed and removed using a Windows installer. Administrator privileges are required for installation. -The portable edition has no installer. Simply follow the instructions in the [read me file](https://raw.githubusercontent.com/delphidabbler/codesnip/master/Docs/ReadMe.txt) that is included in the download. +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. 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. @@ -33,14 +33,14 @@ The program _should_ run on Windows 2000, with Internet Explorer 6 or later, alt 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) that discusses installation, configuration, updating and known issues. [^1] +* 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 [CodeSnip Blog](https://codesnip-app.blogspot.co.uk/). * 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. -> [^1]: The linked read-me file is the most recent version. It 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 From 101592fb8af64b1be9fa85b8d93d2c597fd28a1c Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 4 Apr 2024 00:41:32 +0100 Subject: [PATCH 226/330] Update Build.html re addition of Deploy.bat script Also fixed some errors and inconsistencies and tweaked some content. --- Build.html | 58 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/Build.html b/Build.html index 0f96ec05e..ba8a9086f 100644 --- a/Build.html +++ b/Build.html @@ -6,7 +6,7 @@ * 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) 2009-2023, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2009-2024, Peter Johnson (gravatar.com/delphidabbler). * * Instructions for building CodeSnip from source. --> @@ -513,12 +513,14 @@ <h3> | +-- exe - receives executable code and compiled help file | | | +-- release - receives release files + | | + | +-- ~tmp~ - store for temp files ceated in release process | ...</pre> <p> If the <code>_build/bin</code> folder already existed, it will have been emptied. - In addition, <code>Make</code> will have created a <code>.cfg</code> file from + In addition, <code>Make</code> will have created a <code>.cfg</code> file from a template in the <code>Src</code> folder. This <code>.cfg</code> file is needed for DCC32 to run correctly. The file will be ignored by Git. </p> @@ -580,7 +582,7 @@ <h2> You have several options: </p> -<ul class="spaced"> +<ol class="spaced"> <li> Build the <em>CodeSnip</em> Executable </li> @@ -599,10 +601,10 @@ <h2> <li> Clean Up. </li> -</ul> +</ol> <p> - Each of these options is described below. All except the last assume that + Each of these options is described below. All except options 5 and 6 assume that <code>Make config</code> has been run. </p> @@ -648,7 +650,7 @@ <h4> <pre class="cmd"><span class="prompt">></span> Make -DPORTABLE codesnip</pre> <p> - Again the executable is placed in the <code>_build/exe</code> folder, but this time + Again the executable is placed in the <code>_build\exe</code> folder, but this time it is named <code>CodeSnip-p.exe</code> </p> @@ -665,12 +667,17 @@ <h3> <p> The compiled help file will be written to the <code>_build\exe</code> folder. </p> + +<p> + The same help file is used for the standard and portable editions. +</p> + <h3> Build the Setup Program </h3> <p> - The setup program requires that the <em>CodeSnip</em> excutable and the + The setup program requires that the <em>CodeSnip</em> executable and the compiled help file are already present in the <code>_build\exe</code> directory. </p> @@ -690,7 +697,7 @@ <h3> <p> The setup program is named <code>CodeSnip-Setup-x.x.x.exe</code>, where x.x.x is the version number extracted from <em>CodeSnip</em>'s version - information. It is placed in the <code>_build/exe</code> directory. + information. It is placed in the <code>_build\exe</code> directory. </p> <p> @@ -715,7 +722,7 @@ <h3> <p> Make can create zip files containing all the files that are included in a release. - Zip files are written to the <code>_build/release</code> directory. + Zip files are written to the <code>_build\release</code> directory. </p> <h4> @@ -724,12 +731,12 @@ <h4> <p> The release zip file for the standard edition requires that the setup files is already - present in the <code>_build/exe</code> directory. + present in the <code>_build\exe</code> directory. </p> <p> The release file includes the setup file along with <code>ReadMe.txt</code> - that is automatically generated from <code>Docs/ReadMe-standard.txt</code>. + that is automatically generated from <code>Docs\ReadMe-standard.txt</code>. </p> <p> @@ -763,9 +770,9 @@ <h4> <p> The release file includes the portable executable file, <code>CodeSnip-p.exe</code>, - the help file <code>CodeSnip.chm</code>, <code>Docs/License.html</code> and + the help file <code>CodeSnip.chm</code>, <code>Docs\License.html</code> and <code>ReadMe.txt</code> that is automatically generated from - <code>Docs/ReadMe-portable.txt</code>. + <code>Docs\ReadMe-portable.txt</code>. </p> <p> @@ -855,6 +862,31 @@ <h3> zip file names can be used here too. </p> +<p> + 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 +</p> + +<pre class="cmd"><span class="prompt">></span> Deploy 9.9.9</pre> + +<p> + where <code>9.9.9</code> is the release version number. +</p> + +<p> + 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. +</p> + +<p> + Using <code>Deploy 9.9.9</code> is the equivalent of doing: +</p> + +<pre class="cmd"><span class="prompt">></span> cd Src +<span class="prompt">></span> Make -DVERSION=9.9.9 +<span class="prompt">></span> Make -DPORTABLE -DVERSION=9.9.9 +<span class="prompt">></span> cd ..</pre> + <h3> Clean Up </h3> From b38d5fe1ff81e7bc7a767e22bc7141b1c641efa3 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 4 Apr 2024 01:01:05 +0100 Subject: [PATCH 227/330] Update 3rd party PJSysInfo unit to v5.27.0 Fixes #126 --- Src/3rdParty/PJSysInfo.pas | 348 +++++++++++++++++++++++++------------ 1 file changed, 241 insertions(+), 107 deletions(-) diff --git a/Src/3rdParty/PJSysInfo.pas b/Src/3rdParty/PJSysInfo.pas index efd2c4de6..88f726505 100644 --- a/Src/3rdParty/PJSysInfo.pas +++ b/Src/3rdParty/PJSysInfo.pas @@ -3,7 +3,7 @@ * 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) 2001-2023, Peter Johnson (https://gravatar.com/delphidabbler). + * 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 @@ -21,8 +21,12 @@ * 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. + * OS. When run on Windows 8.1 details of the actual host operating system + * are always returned and the emulated OS is ignored. + * + * 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. * * ACKNOWLEDGEMENTS * @@ -1226,11 +1230,12 @@ TBuildNameMap = record 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://tinyurl.com/usupsz4a (Win 11 Version Numbers & Build Versions) 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) @@ -1239,6 +1244,10 @@ TBuildNameMap = record 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 } { @@ -1267,24 +1276,30 @@ TBuildNameMap = record // Windows 10 ---------------------------------------------------------------- - // Version 1507 previews - // Preview builds with major/minor version number 6.4 - Win10_6point4Builds: array[0..2] of Integer = (9841, 9860, 9879); - // Preview builds with major/minor version number 10.0 - Win10_1507_Preview_Builds: array[0..10] of Integer = ( - 9926, 10041, 10049, 10061, 10074, 10122, 10130, 10158, 10159, 10162, 10166 - ); + // 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 previews - Win10_1511_Preview_Builds: array[0..4] of Integer = ( - 10525, 10532, 10547, 10565, 10576 - ); + // Version 1511 preview builds + // Expired by 2016-07-30 [^1]: + // 10525, 10532, 10547, 10565, 10576 // Version 1607 previews - Win10_1607_Preview_Builds: array[0..24] of Integer = ( - 11082, 11099, 11102, 14251, 14257, 14271, 14279, 14291, 14295, 14316, - 14328, 14332, 14342, 14352, 14361, 14366, 14367, 14371, 14372, 14376, - 14379, 14383, 14385, 14388, 14390 + 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 @@ -1349,7 +1364,7 @@ TBuildNameMap = record ); { - End of support information for Windows 10 versions (as of 2022-12-31). + End of support information for Windows 10 versions (as of 2023-05-01). GAC = General Availablity Channel. LTSC = Long Term Support Channel. @@ -1365,10 +1380,10 @@ TBuildNameMap = record 1903 | ended | N/a 1909 | ended | N/a 2004 | ended | N/a - 20H2 | 2023-09-05 | N/a + 20H2 | ended | N/a 21H1 | ended | N/a 21H2 | 2024-06-11 | 2032-01-13 - 22H2 | 2025-05-13 | N/a + 22H2 | 2025-10-14 | N/a } // Map of Win 10 builds from 1st release (version 1507) to version 20H2 @@ -1397,7 +1412,7 @@ TBuildNameMap = record Name: 'Version 1909: November 2019 Update'), (Build: 19041; LoRev: 264; HiRev: 1415; Name: 'Version 2004: May 2020 Update'), - (Build: 19042; LoRev: 572; HiRev: MaxInt; + (Build: 19042; LoRev: 572; HiRev: 2965; Name: 'Version 20H2: October 2020 Update') ); @@ -1430,6 +1445,7 @@ TBuildNameMap = record --------|------------|------------ 21H2 | 2023-10-10 | 2024-10-08 22H2 | 2024-10-08 | 2025-10-14 + 23H2 | 2025-11-11 | 2026-11-10 } // 1st build released branded as Windows 11 @@ -1445,50 +1461,93 @@ TBuildNameMap = record // various other channels. // See **REF1** in implementation Win11v22H2Build = 22621; - // Build 22632 was added as an alternative Beta channel build as of rev 290. + + // Windows 11 version 22H3 + // See **REF10** in implementation + Win11v23H2Build = 22631; + + // "Preview Builds of October 2022 component update in Beta Channel" // See **REF2** in implementation - Win11v22H2BuildAlt = 22622; - - // Windows 11 Dev channel releases (with version string "Dev"). - // For details see https://en.wikipedia.org/wiki/Windows_11_version_history - Win11DevChannelDevBuilds: array[0..25] of Integer = ( - // 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, - // post Win 11 22H2 beta release (expired 2022/09/15): + Win11Oct22ComponentBetaChannelBuild = 22622; + + // "Preview Builds of February 2023 component update in Beta Channel" + // See **REF7** in implementation + Win11Feb23ComponentBetaChannelBuild = 22623; + + // "Preview builds of May 2023 component update in Beta Channel" + // See **REF8** in implementation + Win11May23ComponentBetaChannelBuild = 22624; + + // "Preview builds of future component update in Beta Channel" + // See **REF9** in implementation + Win11FutureComponentBetaChannelBuild = 22635; + + // 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] + Win1122H2DevChannelDevBuilds: array[0..20] of Integer = ( + // expired 2022/09/15 (pre Win 11 22H2 beta release): + // 22567, 22572, 22579 + // expired 2022/09/15 (post Win 11 22H2 beta release): // 25115, 25120, 25126, 25131, 25136, 25140, 25145, 25151, 25158, 25163, // 25169, 25174, 25179, - // post Win 11 22H2 beta release (expiring 2023/09/15): - 25182, 25188, 25193, 25197, 25201, 25206, 25211, - // post Win 11 22H2 release (expiring 2023/09/15): - 25217, 25227, 25231, 25236, 25247, 25252, 25262, 25267, 25272, 25276, 25281, - 25284, 25290, 25295, 25300, 25309, 23403, 23419, 23424 + // expired 2023/09/15 (post Win 11 22H2 beta release): + // 25182, 25188, 25193, 25197, 25201, 25206, 25211, + // expired 2023/09/15 (post Win 11 22H2 release): + // 25217, 25227, 25231, 25236, 25247, 25252, 25262, 25267, 25272, 25276, + // 25281, 25284, 25290, 25295, 25300, 25309, 23403, 23419, 23424, 23430, + // 23435, 23440, 23451, 23466, 23471, 23475, 23481, 23486, 23493, 23506, + // 23511, 23516, 23521, + // expiring 2024-09-15: + 23526, 23531, 23536, 23541, 23545, 23550, 23555, 23560, 23565, 23570, 23575, + 23580, 23585, 23590, 23595, 23601, 23606, 23612, 23615, 23619, 23620 + ); + + // Win 11 Dev channel releases with version string "24H2" [^2] + Win1124H2DevChannelDevBuilds: array[0..4] of Integer = ( + // expiring 2024-09-15: + 26052, 26058, 26080, 26085, 26090 ); - // Preview builds of Windows 11 in the Canary Channel - // For details see https://en.wikipedia.org/wiki/Windows_11_version_history - Win11CanaryPreviewBuilds: array[0..2] of Integer = ( - // expiring 2023/09/15: - 25314, 25324, 25330 + // 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] + Win11Canary23H2PreviewBuilds: array[0..15] of Integer = ( + // expired 2023-09-15: + // 25375, 25381, 25387, 25393, 25905, 25915, 25921, 25926, + // expires 2024-09-15: + 25931, 25936, 25941, 25947, 25951, 25967, 25977, 25982, 25987, 25992, 25997, + 26002, 26010, 26016, 26020, 26040 ); - // Windows 11 Dev channel builds with version string "22H2" - // expired 2022/09/15): - // 22567, 22572, 22579 + // Preview builds of Windows 11 in the Canary Channel with version string + // "24H2" [^2] + Win11Canary24H2PreviewBuilds: array[0..5] of Integer = ( + // expires 2024-09-15: + 26052, 26058, 26063, 26080, 26085, + // expiry date unknown + 26090 + ); - // Windows 11 Dev & Beta channel builds with version string "22H2" + // Windows 11 Dev & Beta channel builds with version string "22H2" [^2] Win11DevBetaChannels22H2Builds: array[0..1] of Integer = ( - // expired 2022/09/15: 22581, 22593, 22598, + // Expired 2022/09/15: + // 22581, 22593, 22598 + // Unknown expiry date: 22610, 22616 ); - Win11Feb23ComponentBetaChannelBuild = 22623; - Win11FutureComponentBetaChannelBuild = 22624; - Win11FirstBuild = Win11DevBuild; // First build number of Windows 11 // Windows server v10.0 version ---------------------------------------------- @@ -2006,6 +2065,13 @@ procedure InitPlatformIdEx; ); 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'); @@ -2073,17 +2139,6 @@ procedure InitPlatformIdEx; // Windows 2016 Server tech preview 1 InternalBuildNumber := Win2016TP1Build; InternalExtraUpdateInfo := 'Technical Preview 6'; - end - else - begin - if FindBuildNumberFrom( - Win10_6point4Builds, InternalBuildNumber - ) then - // Early Win 10 preview builds report v6.4, not v10.0 - InternalExtraUpdateInfo := Format( - 'Version 1507 Preview v6.4.%d.%d', - [InternalBuildNumber, InternalRevisionNumber] - ) end; end; if Win32ServicePackMajor > 0 then @@ -2150,11 +2205,13 @@ procedure InitPlatformIdEx; 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 .. MaxInt: + 2311, 2364, 2486, 2546, 2604, 2673, 2728, 2788, 2846, 2965, + 3086, 3208, 3324, 3448, 3570, 3693, 3803, 3930, 4046, + 4170 .. 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: + 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] @@ -2172,16 +2229,19 @@ procedure InitPlatformIdEx; InternalBuildNumber := Win1022H2Build; case InternalBuildNumber of 2006, 2130, 2132, 2193, 2194, 2251, 2311, 2364, 2486, 2546, - 2604, 2673, 2728, 2788 .. MaxInt: + 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 .. MaxInt: InternalExtraUpdateInfo := 'Version 22H2'; - 1865, 1889, 1949, 2075, 2301, 2670, 2787: + 1865, 1889, 1949, 2075, 2301, 2670, 2787, 2908, 3030, 3154, + 3155, 3269, 3391, 3513, 3754, 3757, 3992, 4116, 4233, 4235: InternalExtraUpdateInfo := Format( 'Version 22H2 [Release Preview Channel v10.0.%d.%d]', [InternalBuildNumber, InternalRevisionNumber] ); else InternalExtraUpdateInfo := Format( - 'Version 22H1 [Unknown release v10.0.%d.%d]', + 'Version 22H2 [Unknown release v10.0.%d.%d]', [InternalBuildNumber, InternalRevisionNumber] ); end; @@ -2209,7 +2269,9 @@ procedure InitPlatformIdEx; 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 .. MaxInt: + 1696, 1761, 1817, 1880, 1936, 2003, 2057, 2124, 2176, 2245, + 2295, 2360, 2416, 2482, 2538, 2600, 2652, 2713, 2777, + 2836 .. MaxInt: // Public releases of Windows 11 InternalExtraUpdateInfo := 'Version 21H2'; 51, 65, 71: @@ -2228,9 +2290,10 @@ procedure InitPlatformIdEx; + '[Beta & Release Preview Channels v10.0.%d.%d]', [InternalBuildNumber, InternalRevisionNumber] ); - 651, 706, 776, 829, 917, 1041, 1163, 1279, 1515, 1639, 1757: + 651, 706, 776, 829, 917, 1041, 1163, 1279, 1515, 1639, 1757, + 1879, 2001, 2121, 2243, 2359, 2479: InternalExtraUpdateInfo := Format( - 'Version 21H1 Release Preview Channel v10.0.%d.%d]', + 'Version 21H2 Release Preview Channel v10.0.%d.%d]', [InternalBuildNumber, InternalRevisionNumber] ); else @@ -2246,21 +2309,35 @@ procedure InitPlatformIdEx; InternalBuildNumber := Win11v22H2Build; case InternalRevisionNumber of 382, 521, 525, 608, 674, 675, 755, 819, 900, 963, 1105, 1194, - 1265, 1344, 1413, 1485, {placeholder->}1538 .. MaxInt: + 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 .. 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: + 105, 169, 232, 317, 457, 607, 754, 898, 1192, 1343, 1483, 1631, + 1776, 2066, 2213, 2359, 2500, 2787, 3078, 3227, 3371: 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: + 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] @@ -2272,11 +2349,40 @@ procedure InitPlatformIdEx; ); end; end - else if IsBuildNumber(Win11v22H2BuildAlt) then + else if IsBuildNumber(Win11v23H2Build) then + begin + // **REF10** + InternalBuildNumber := Win11v23H2Build; + case InternalRevisionNumber of + 2428, 2506, 2715, 2792, 2861, 3007, 3085, 3155, 3235 {Moment 5}, 3296, 3374 .. MaxInt: + InternalExtraUpdateInfo := 'Version 23H2'; + 1825, 1830, 1835, 1900, 1906, 1972: + // revisions 1825..1972 had version string "22H2" + InternalExtraUpdateInfo := Format( + 'Version 22H2 [Beta v10.0.%d.%d]', + [InternalBuildNumber, InternalRevisionNumber] + ); + 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: + 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(Win11Oct22ComponentBetaChannelBuild) then begin // **REF2** - InternalBuildNumber := Win11v22H2BuildAlt; - // Set fallback update info for unknown revisions + InternalBuildNumber := Win11Oct22ComponentBetaChannelBuild; case InternalRevisionNumber of 290, 436, 440, 450, 575, 586, 590, 598, 601: InternalExtraUpdateInfo := Format( @@ -2291,22 +2397,42 @@ procedure InitPlatformIdEx; end; end else if FindBuildNumberFrom( - Win11DevChannelDevBuilds, InternalBuildNumber + Win1122H2DevChannelDevBuilds, InternalBuildNumber + ) then + begin + // Win11 Dev Channel builds with version string "22H2" + InternalExtraUpdateInfo := Format( + 'Dev Channel Version 22H2 v10.0.%d.%d', + [InternalBuildNumber, InternalRevisionNumber] + ); + end + else if FindBuildNumberFrom( + Win1124H2DevChannelDevBuilds, InternalBuildNumber + ) then + begin + // Win11 Dev Channel builds with version string "22H2" + InternalExtraUpdateInfo := Format( + 'Dev Channel Version 24H2 v10.0.%d.%d', + [InternalBuildNumber, InternalRevisionNumber] + ); + end + else if FindBuildNumberFrom( + Win11Canary23H2PreviewBuilds, InternalBuildNumber ) then begin - // Win11 Dev Channel builds with version string "Dev" + // Win11 Canary Channel builds with version string "23H2" InternalExtraUpdateInfo := Format( - 'Dev Channel v10.0.%d.%d (Dev)', + 'Canary Channel Version 23H2 v10.0.%d.%d', [InternalBuildNumber, InternalRevisionNumber] ); end else if FindBuildNumberFrom( - Win11CanaryPreviewBuilds, InternalBuildNumber + Win11Canary24H2PreviewBuilds, InternalBuildNumber ) then begin - // Win11 Canary Channel builds + // Win11 Canary Channel builds with version string "24H2" InternalExtraUpdateInfo := Format( - 'Canary Channel v10.0.%d.%d (Dev)', + 'Canary Channel Version 24H2 v10.0.%d.%d', [InternalBuildNumber, InternalRevisionNumber] ); end @@ -2314,7 +2440,7 @@ procedure InitPlatformIdEx; Win11DevBetaChannels22H2Builds, InternalBuildNumber ) then begin - // Win 11 Dev & Beta channel builds with verison string "22H2" + // Win 11 Dev & Beta channel builds with version string "22H2" InternalExtraUpdateInfo := Format( 'Dev & Beta Channels v10.0.%d.%d (22H2)', [InternalBuildNumber, InternalRevisionNumber] @@ -2322,6 +2448,7 @@ procedure InitPlatformIdEx; end else if IsBuildNumber(Win11Feb23ComponentBetaChannelBuild) then begin + // **REF7** InternalBuildNumber := Win11Feb23ComponentBetaChannelBuild; case InternalRevisionNumber of 730, 741, 746, 870, 875, 885, 891, 1020, 1028, 1037, 1095, @@ -2337,11 +2464,32 @@ procedure InitPlatformIdEx; ); end; end + else if IsBuildNumber(Win11May23ComponentBetaChannelBuild) then + begin + // **REF8** + InternalBuildNumber := Win11May23ComponentBetaChannelBuild; + 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(Win11FutureComponentBetaChannelBuild) then begin + // **REF9** InternalBuildNumber := Win11FutureComponentBetaChannelBuild; case InternalRevisionNumber of - 1391, 1465, 1470, 1537 .. MaxInt: + 2419, 2483, 2486, 2552, 2700, 2771, 2776, 2841, 2850, 2915, + 2921, 3061, 3066, 3130, 3139, 3140, 3209, 3212, 3276, 3286, + 3350, 3420 .. MaxInt: InternalExtraUpdateInfo := Format( 'Future Component Update Beta v10.0.%d.%d', [InternalBuildNumber, InternalRevisionNumber] @@ -2431,20 +2579,6 @@ procedure InitPlatformIdEx; begin // Nothing to do: required internal variables set in function call end - else if FindWin10PreviewBuildNameAndExtraFrom( - Win10_1511_Preview_Builds, '1511', - InternalBuildNumber, InternalExtraUpdateInfo - ) then - begin - // Nothing to do: required internal variables set in function call - end - else if FindWin10PreviewBuildNameAndExtraFrom( - Win10_1507_Preview_Builds, '1507', - InternalBuildNumber, InternalExtraUpdateInfo - ) then - begin - // Nothing to do: required internal variables set in function call - end end else // Win32ProductType in [VER_NT_DOMAIN_CONTROLLER, VER_NT_SERVER] begin @@ -3059,8 +3193,6 @@ class function TPJOSInfo.Platform: TPJOSPlatform; end; class function TPJOSInfo.Product: TPJOSProduct; -var - DummyBN: Integer; // dummy build number begin Result := osUnknown; case Platform of @@ -3150,8 +3282,10 @@ class function TPJOSInfo.Product: TPJOSProduct; // application is "manifested" for the correct Windows version. // See https://bit.ly/MJSO8Q. Result := osWin10Svr - else if FindBuildNumberFrom(Win10_6point4Builds, DummyBN) then - Result := osWin10; + // 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 From df62229ccefa0349445c08049261dd5233a19207 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 4 Apr 2024 01:19:30 +0100 Subject: [PATCH 228/330] Fix copyright date in About box Fixes #129 --- Src/Res/HTML/dlg-about-program-tplt.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Src/Res/HTML/dlg-about-program-tplt.html b/Src/Res/HTML/dlg-about-program-tplt.html index 63a3a2cd4..be93a30c3 100644 --- a/Src/Res/HTML/dlg-about-program-tplt.html +++ b/Src/Res/HTML/dlg-about-program-tplt.html @@ -9,7 +9,7 @@ * 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) 2005-2023, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2024, Peter Johnson (gravatar.com/delphidabbler). * * Template for content displayed in program tab of about dialog box. --> @@ -47,7 +47,7 @@ <body> <p> - DelphiDabbler <em>CodeSnip</em> is copyright © 2005-2023 by <a + DelphiDabbler <em>CodeSnip</em> is copyright © 2005-2024 by <a class="external-link" href="https://en.gravatar.com/delphidabbler" >Peter D Johnson</a>. From 6d33c8df7b5d3b97b7f3f55e61303a2b94fbcdca Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 4 Apr 2024 01:46:59 +0100 Subject: [PATCH 229/330] Add version number to program caption Program release version number now displayed in the main form caption. Fixes #122 --- Src/UAppInfo.pas | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/Src/UAppInfo.pas b/Src/UAppInfo.pas index eacc0d65d..37958f245 100644 --- a/Src/UAppInfo.pas +++ b/Src/UAppInfo.pas @@ -3,7 +3,7 @@ * 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) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2024, Peter Johnson (gravatar.com/delphidabbler). * * Class that provides information about the application. } @@ -36,12 +36,6 @@ TAppInfo = class(TNoConstructObject) const ProgramName = 'CodeSnip-p'; {$ENDIF} {Name of program} - {$IFNDEF PORTABLE} - const ProgramCaption = 'CodeSnip 4'; - {$ELSE} - const ProgramCaption = 'CodeSnip 4 (Portable Edition)'; - {$ENDIF} - {Name of program displayed in main window and task bar caption} const FullProgramName = CompanyName + ' ' + ProgramName; {Full name of program, including company name} const ProgramID = 'codesnip'; @@ -107,6 +101,10 @@ TAppInfo = class(TNoConstructObject) {Gets version number of program's executable file. @return Version number as dotted quad. } + class function ProgramCaption: string; + {Gets the program caption to be displayed in main window, + @return Required caption, + } end; @@ -214,6 +212,19 @@ class function TAppInfo.HelpFileName: string; Result := AppExeDir + '\CodeSnip.chm'; end; +class function TAppInfo.ProgramCaption: string; +var + ProductVer: TVersionNumber; +begin + ProductVer := TVersionInfo.ProductVerNum; + Result := Format( + 'CodeSnip v%d.%d.%d', [ProductVer.V1, ProductVer.V2, ProductVer.V3] + ); + {$IFDEF PORTABLE} + Result := Result + ' (Portable Edition)' + {$ENDIF} +end; + class function TAppInfo.ProgramFileVersion: string; {Gets version number of program's executable file. @return Version number as dotted quad. From b0d0726d6ae953adc42253a81d0be7f94f57b4a9 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 4 Apr 2024 01:57:02 +0100 Subject: [PATCH 230/330] Fix full program name used by portable edition The full program name is now always "DelphiDabbler CodeSnip", regardless of the edition. Previously the name was "DelphiDabbler CodeSnip-p" on the portable edition. Fixes #130 --- Src/UAppInfo.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Src/UAppInfo.pas b/Src/UAppInfo.pas index 37958f245..7bc91fd6e 100644 --- a/Src/UAppInfo.pas +++ b/Src/UAppInfo.pas @@ -36,7 +36,7 @@ TAppInfo = class(TNoConstructObject) const ProgramName = 'CodeSnip-p'; {$ENDIF} {Name of program} - const FullProgramName = CompanyName + ' ' + ProgramName; + const FullProgramName = CompanyName + ' CodeSnip'; {Full name of program, including company name} const ProgramID = 'codesnip'; {Machine readable identifier of program} From 6d7a9925160333c8f2d50cec412696c74b9bcf77 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 4 Apr 2024 02:23:33 +0100 Subject: [PATCH 231/330] Fix REML documentation and help topic * REML docs in Docs/Design/reml.html has duplicate ¢ character entity entry. * REML help topic was never updated for the new ' character entity. Fixes #131 --- Docs/Design/reml.html | 4 ---- Src/Help/HTML/reml.htm | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Docs/Design/reml.html b/Docs/Design/reml.html index b945f4332..43adc650f 100644 --- a/Docs/Design/reml.html +++ b/Docs/Design/reml.html @@ -524,10 +524,6 @@ <h1> <td><code>&deg;</code></td> <td>°</td> </tr> - <tr> - <td><code>&cent;</code></td> - <td>¢</td> - </tr> <tr> <td><code>&laquo;</code></td> <td>«</td> diff --git a/Src/Help/HTML/reml.htm b/Src/Help/HTML/reml.htm index f66afea9d..3b6753a59 100644 --- a/Src/Help/HTML/reml.htm +++ b/Src/Help/HTML/reml.htm @@ -353,6 +353,10 @@ <h3 id="entities"> <td><code>&iquest;</code></td> <td class="centre">¿</td> </tr> + <tr> + <td><code>&apos;</code></td> + <td class="centre">'</td> + </tr> </tbody> </table> <p> From 0fb7ce6de170769f3f7aa102375ce8d533fda714 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Mon, 8 Apr 2024 08:04:27 +0100 Subject: [PATCH 232/330] Rewrite Docs/Design/reml.html Docs/Design/reml.html now contains a brief overview of REML and links to documentation in the new delphidabbler/reml repository for a full language definition. --- Docs/Design/reml.html | 537 ++---------------------------------------- 1 file changed, 20 insertions(+), 517 deletions(-) diff --git a/Docs/Design/reml.html b/Docs/Design/reml.html index 43adc650f..fb8b0ce15 100644 --- a/Docs/Design/reml.html +++ b/Docs/Design/reml.html @@ -19,7 +19,7 @@ <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script> <![endif]--> <meta name="author" content="Peter Johnson - https://en.gravatar.com/delphidabbler"> -<meta name="description" content="DelphiDabbler Code Snippets collection documentation - REML markup language"> +<meta name="description" content="DelphiDabbler Code Snippets documentation - REML markup language"> <style> body { font-family: "Segoe UI", Tahoma, Geneva, Helvetica, Arial, sans-serif; @@ -44,28 +44,6 @@ text-align: center; margin: 0.5em; } -header nav { - display: block; - margin: 1em; - font-weight: normal; - font-size: 90%; -} -header nav ul { - list-style: none; - margin: 0; - text-align: center; -} -header nav li { - display: inline; -} -header nav a { - text-decoration: none; - white-space: nowrap; - padding: 0.5em; -} -header nav a:hover { - background-color: #eeeeee; -} h1 { font-weight: bold; font-size: 200%; @@ -79,47 +57,11 @@ padding: 0; margin: 0.75em 0; } -aside { - display: block; - xfont-size: 100%; - font-style: italic; - padding: 0.25em 0.5em; - margin: 0.75em 2em; - border-left: 2px solid silver; - border-right: 1px solid silver; - border-top: 1px solid silver; - border-bottom: 2px solid silver; - border-radius: 6px; - background-color: #f5f5f5; -} -aside code.value { - background-color: #ddd; -} p { font-size: 100%; padding: 0; margin: 0.75em 0; } -pre, code { - font-family: "Lucida Console", "Courier New", Courier, monospace; - font-size: 90%; -} -pre.sample { - margin: 0.75em 2em; - background-color: #f5f5f5; - padding: 0.5em; -} -code.key, code.value { - background-color: #f5f5f5; - padding: 1px 4px; -} -code.key { - font-weight: bold; - font-style: none; -} -code.value { - font-style: normal; -} ul { font-size: 100%; padding: 0; @@ -141,21 +83,6 @@ ul.unspaced li { margin: 0; } -.very-strong { - xtext-transform: uppercase; - font-variant: small-caps; - font-weight: bold; -} -dt { - margin: 0; - padding: 0; - font-weight: bold; -} -dd { - margin: 0 0 0 2em; - padding: 0; - font-weight: normal; -} a { color: rgb(46, 46, 192); } @@ -171,36 +98,6 @@ margin-top: 0; margin-bottom: 0.5em; } -table { - border-collapse: collapse; -} -tr { - border-bottom: 1px silver solid; -} -th { - text-align: center; - font-weight: bold; - margin: 0; - padding: 0; - border-bottom: 2px silver solid; -} -td { - border-bottom: 1px silver solid; - margin: 0; - padding: 0; -} -td, th { - padding: 0.5em; -} -td:first-child, th:first-child { - border-right: 1px silver solid; -} -td:last-child { - text-align: center; -} -tr:nth-child(even), th { - background-color: #eee; -} </style> <title> @@ -219,450 +116,56 @@ </p> </div> - <nav id="contents"> - <ul> - <li> - <a href="#intro">Introduction</a> - </li> - <li> - <a href="#tags">Tags</a> - </li> - <li> - <a href="#entities">Character Entities</a> - </li> - <li> - <a href="#changes">Change Log</a> - </li> - </ul> - </nav> - </header> -<section id="intro"> - - <h1> - Introduction - </h1> - - <p> - REML is a little markup language that can be used to style text. It is used in Code Snippets collection meta data for certain properties of a snippet. - </p> - <p> - The REML language is a SGML language similar to a greatly simplified XHTML. The are a small number of tags and character entities that can be used. - </p> - <aside> - <strong>Note:</strong> The language described here is REML v6. v4 is still in regular use in CodeSnip up to v4.20.x. Earlier versions are obsolete. - </aside> - -</section> +<main> -<section id="tags"> +<section id="intro"> <h1> - Tags + About REML </h1> <p> - There are two types of tags: block level and in-line. - </p> - - <p> - If an unrecognised tag is encountered an REML code the interpreter <em>should</em> report an error. However, providing start and end tags are matched, the interpreter <em>may</em> choose to simply ignore the tags. - </p> - - <h2> - Block Level Tags - </h2> - - <p> - Block level tags separate the enclosed text into paragraphs of some description. The supported tags are: - </p> - <ul class="half-spaced"> - <li> - <code class="value"><p>...</p></code> – Renders the enclosed markup as a simple paragraph. - </li> - <li> - <code class="value"><heading>...</heading></code> – Renders the enclosed markup as a heading. - </li> - <li> - <code class="value"><ol>...</ol></code> – Renders the enclosed markup as an ordered list. - </li> - <li> - <code class="value"><ul>...</ul></code> – Renders the enclosed markup as an unordered list. - </li> - <li> - <code class="value"><li>...</li></code> – Renders the enclosed markup as a list item. - </li> - </ul> - <p> - The following rules apply to the use of block level tags: - </p> - <ul class="unspaced"> - <li> - <span class="very-strong">Must</span> be matched, e.g. <code class="value"><p></code> <span class="very-strong">must</span> have a matching <code class="value"></p></code>. - </li> - <li> - <code class="value"><p>...</p></code> and <code class="value"><heading>...</heading></code> blocks <span class="very-strong">must not</span> contain other block level tags. - </li> - <li> - <code class="value"><ol>...</ol></code> and <code class="value"><ul>...</ul></code> blocks <span class="very-strong">must only</span> contain one or more <code class="value"><li>...</li></code> blocks. - </li> - <li> - <code class="value"><li>...</li></code> blocks <span class="very-strong">must</span> only be used within <code class="value"><ol>...</ol></code> and <code class="value"><ul>...</ul></code> blocks. <em>May</em> contain <code class="value"><p>...</p></code> and <code class="value"><heading>...</heading></code> blocks, but it is permitted to include text and inline tags directly without enclosing them one of the permitted blocks. Nested lists are permitted by including further <code class="value"><ul>...</ul></code> and <code class="value"><ol>...</ol></code> blocks. - </li> - <li> - All text <em>should</em> be embedded within <code class="value"><p>...</p></code>, <code class="value"><heading>...</heading></code> or <code class="value"><li>...</li></code> block level tags, e.g. <code class="value"><heading>heading</heading><p>text</p></code> or simply <code class="value"><p>text</p></code>. - </li> - <li> - White space between blocks <span class="very-strong">must</span> be ignored. - </li> - </ul> - <p> - Here is a valid example: - </p> - <pre class="sample"><heading>Hello</heading> -<p>Hello World</p> -<ol> - <li>one</li> - <li><p>two</p></li> - <ul> - <li>two A</li> - <li>two B</li> - <ul> - <li>three</li> -</ol></pre> - <p> - Strictly speaking, the following example is invalid code – all occurrences of <code class="value">wrong</code> are in error because they are not contained within block tags. - </p> - <pre class="sample">wrong <heading>blah</heading> wrong <p>blah</p> wrong</pre> - <p> - However interpreting code <em>may</em> interpret this permissively. If this is done the text outside blocks <em>should</em> be interpreted as if it was enclosed in <code class="value"><p></code> and <code class="value"></p></code> tags. Therefore the above code would be interpreted as: + 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. </p> - <pre class="sample"><p>wrong </p><heading>blah</heading><p>wrong </p><p>blah</p><p>wrong</p></pre> - <aside> - <strong>Note:</strong> Code Snippets Database collections <em>may</em> contain such non-conforming REML. Therefore interpreters of REML that need to accept such collections <span class="very-strong">must</span> be able to handle text without enclosing block tags. - </aside> - <h2> - Inline Tags - </h2> - - <p> - In-line tags format the text enclosed between the start and end tags. - </p> <p> - Here are the available in-line tags: + See the <a href="https://htmlpreview.github.io/?https://raw.githubusercontent.com/delphidabbler/reml/main/docs/reml-v6.html">REML v6 language definition</a> for full details. </p> - <ul class="half-spaced"> - <li> - <code class="value"><strong>...</strong></code> – Renders the enclosed markup with strong emphasis. - </li> - <li> - <code class="value"><em>...</em></code> – Emphasises the enclosed markup. - </li> - <li> - <code class="value"><var>...</var></code> – Used to indicate the enclosed markup is a variable. - </li> - <li> - <code class="value"><warning>...</warning></code> – Used for warning text. - </li> - <li> - <code class="value"><mono>...</mono></code> – Renders markup in a mono-spaced font. - </li> - <li> - <code class="value"><a href="url">...</a></code> – Creates a hyper-link. The <code class="value">href</code> attribute <span class="very-strong">must</span> specify the required URL, which <span class="very-strong">must</span> use one of the <code class="value">http</code>, <code class="value">https</code> or <code class="value">file</code> protocols; others are not permitted. If you use the <code class="value">file</code> protocol it <span class="very-strong">must</span> reference a valid local or network file. - </li> - </ul> - <p> - The following rules apply to the use of in-line tags: - </p> - <ul class="unspaced"> - <li> - In-line tags <span class="very-strong">must</span> be embedded inside a valid block level tag. E.g. <code class="value"><p>one<strong>two</strong>three</p></code>. - </li> - <li> - Tags <span class="very-strong">must</span> match. E.g. <code class="value"><em></code> must be matched with <code class="value"></em></code>. - </li> - <li> - Tags may be nested, providing the tags are balanced. E.g. <code class="value"><em>blah <var>blah</var></em></code> is valid but <code class="value"><em>blah <var>blah</em></var></code> is not. - </li> - </ul> - <p> - Examples: - </p> - <pre class="sample"><p>Make stuff <strong>stand out</strong>.</p> -<p><em>Emphasised <warning>warning!</warning></em></p> -<p>Refer to a function <var>parameter</var>.</p> -<p>Use the: <mono>Windows</mono> unit.</p> -<p>See this <a href="https://example.com">example</a>.</p></pre> </section> -<section id="entities"> +<section id="reml-in-codesnip"> <h1> - Character Entities + REML in CodeSnip </h1> <p> - Some symbolic character entities are supported in REML. Many symbols, but not all, have analogues in the list of supported character entities in XHTML or HTML 5. Some entities have alternate symbols. Here is the complete list. + 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. </p> - <table> - <thead> - <tr> - <th>Character Entity</th> - <th>Actual Character</th> - </tr> - </thead> - <tbody> - <tr> - <td><code>&amp;</code></td> - <td>&</td> - </tr> - <tr> - <td><code>&quot;</code></td> - <td>"</td> - </tr> - <tr> - <td><code>&gt;</code></td> - <td>></td> - </tr> - <tr> - <td><code>&lt;</code></td> - <td><</td> - </tr> - <tr> - <td><code>&copy;</code></td> - <td>©</td> - </tr> - <tr> - <td><code>&times;</code></td> - <td>×</td> - </tr> - <tr> - <td><code>&divide;</code> or <code>&div;</code></td> - <td>÷</td> - </tr> - <tr> - <td><code>&plusmn;</code></td> - <td>±</td> - </tr> - <tr> - <td><code>&ne;</code> or <code>&neq;</code></td> - <td>≠</td> - </tr> - <tr> - <td><code>&sum;</code></td> - <td>∑</td> - </tr> - <tr> - <td><code>&infin;</code></td> - <td>∞</td> - </tr> - <tr> - <td><code>&pound;</code></td> - <td>£</td> - </tr> - <tr> - <td><code>&curren;</code></td> - <td>¤</td> - </tr> - <tr> - <td><code>&yen;</code></td> - <td>Â¥</td> - </tr> - <tr> - <td><code>&euro;</code></td> - <td>€</td> - </tr> - <tr> - <td><code>&cent;</code></td> - <td>¢</td> - </tr> - <tr> - <td><code>&dagger;</code></td> - <td>†</td> - </tr> - <tr> - <td><code>&ddagger;</code> or <code>&Dagger;</code></td> - <td>‡</td> - </tr> - <tr> - <td><code>&hellip;</code></td> - <td>…</td> - </tr> - <tr> - <td><code>&para;</code></td> - <td>¶</td> - </tr> - <tr> - <td><code>&sect;</code></td> - <td>§</td> - </tr> - <tr> - <td><code>&reg;</code></td> - <td>®</td> - </tr> - <tr> - <td><code>&frac14;</code></td> - <td>¼</td> - </tr> - <tr> - <td><code>&frac12;</code> or <code>&half;</code></td> - <td>½</td> - </tr> - <tr> - <td><code>&frac34;</code></td> - <td>¾</td> - </tr> - <tr> - <td><code>&micro;</code></td> - <td>µ</td> - </tr> - <tr> - <td><code>&deg;</code></td> - <td>°</td> - </tr> - <tr> - <td><code>&laquo;</code></td> - <td>«</td> - </tr> - <tr> - <td><code>&raquo;</code></td> - <td>»</td> - </tr> - <tr> - <td><code>&iquest;</code></td> - <td>¿</td> - </tr> - <tr> - <td><code>&apos;</code></td> - <td>'</td> - </tr> - </tbody> - </table> - - <aside> - <strong>Note:</strong> the '<' and '&' characters are special within the markup and cannot be used literally, even when you are just entering plain text. You <span class="very-strong">must</span> use the <code class="value">&lt;</code> character entity in place of <code class="value"><</code> and <code class="value">&amp;</code> instead of <code class="value">&</code>. For example to write <code class="value">x<y</code> in REML use <code class="value">x&lt;y</code> and to write <code class="value">you & me</code> use <code class="value">you &amp; me</code>. - </aside> - <p> - To express other special symbols for which there is no symbolic character entity, numeric character entities can be used. For example to display the 'Ω' character (Unicode <em>Greek capital letter Omega</em>) use <code class="value">&#937;</code>. + CodeSnip currently supports REML v6. Earlier versions of CodeSnip supported different versions of REML: </p> - - <aside> - <strong>Note:</strong> Numeric entities should be used with caution because the characters they represent may vary across different text encodings, whereas symbolic entities are safe across encodings. - </aside> - -</section> - - -<section id="changes"> - - <h1>Change Log</h1> - - <p> - This section notes the changes in the various versions of REML. - </p> - - <p> - <strong>v1 of 2008-12-31</strong> - </p> - - <p> - Introduced in CodeSnip v2.2.5 - </p> - - <ul> - <li> - Supported tags: <code class="value"><strong></code> and <code class="value"><a></code>. - </li> - <li> - Supported entities: <code class="value">&gt;</code>, <code class="value">&lt;</code>, <code class="value">&quot;</code> and <code class="value">&amp;</code>. - </li> - <li> - Supported protocols for use in <code class="value"><a></code> tags: <code class="value">http</code>. - </li> - </ul> - - <p> - <strong>v2 of 2009-06-29</strong> - </p> - - <p> - Introduced in CodeSnip v3.0 - </p> - - <ul> - <li> - Added tags: <code class="value"><em></code>, <code class="value"><var></code>, <code class="value"><warning></code>, <code class="value"><mono></code>, <code class="value"><p></code> and <code class="value"><heading></code>. - </li> - <li> - Added entity: <code class="value">&copy;</code>. - </li> - </ul> - - <p> - <strong>v3 of 2009-07-06</strong> - </p> - - <p> - Introduced in CodeSnip v3.0.1 - </p> - - <ul> - <li> - Added protocol for use in <code class="value"><a></code> tags: <code class="value">file</code>. - </li> - </ul> - - <p> - <strong>v4 of 2011-12-31</strong> - </p> - - <p> - Introduced in CodeSnip v4.0 alpha 1 (preview) - </p> - - <ul> - <li> - Added protocol for use in <code class="value"><a></code> tags: <code class="value">https</code>. - </li> - </ul> - - <p> - <strong>v5 of 2022-12-16</strong> - </p> - - <p> - Introduced in CodeSnip v4.21.0 - </p> - + <ul> - <li> - Added support for lists with the <code class="value"><ol></code>, <code class="value"><ul></code> & <code class="value"><li></code> block tags. - </li> - <li> - Added entities: <code class="value">&times;</code>, <code class="value">&divide;</code>, <code class="value">&div;</code> <code class="value">&plusmn;</code>, <code class="value">&ne;</code>, <code class="value">&neq;</code>, <code class="value">&sum;</code>, <code class="value">&infin;</code>, <code class="value">&pound;</code>, <code class="value">&curren;</code>, <code class="value">&yen;</code>, <code class="value">&euro;</code>, <code class="value">&cent;</code>, <code class="value">&dagger;</code>, <code class="value">&ddagger;</code>, <code class="value">&Dagger;</code>, <code class="value">&hellip;</code>, <code class="value">&para;</code>, <code class="value">&sect;</code>, <code class="value">&reg;</code>, <code class="value">&frac14;</code>, <code class="value">frac12</code>, <code class="value">&half;</code>, <code class="value">&frac34;</code>, <code class="value">&micro;</code>, <code class="value">&deg;</code>, <code class="value">&laquo;</code>, <code class="value">&raquo;</code> & <code class="value">&iquest;</code>. - </li> + <li>REML v1 was first supported by CodeSnip v2.2.5</li> + <li>REML v2 was first supported by CodeSnip v3.0</li> + <li>REML v3 was first supported by CodeSnip v3.0.1</li> + <li>REML v4 was first supported by CodeSnip v4.0 alpha 1 (preview)</li> + <li>REML v5 was first supported by CodeSnip v4.21.0</li> + <li>REML v6 was first supported by CodeSnip v4.23.0</li> </ul> <p> - <strong>v6 of 2024-04-02</strong> + All CodeSnip versions are backward compatible with earlier versions of REML. </p> - <p> - Introduced in CodeSnip v4.23.0 - </p> +</section> - <ul> - <li> - Added entity: <code class="value">&apos;</code>. - </li> - </ul> - - </section> +</main> </body> From fca862c6d28a2b3fed1fa43a2e3852293791d6e0 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Mon, 8 Apr 2024 08:46:15 +0100 Subject: [PATCH 233/330] Rewite REML help topic REML language definition was replaced with a link to the definition on the delphidabbler/reml project. Added a brief preamble and an example that uses all supported tags. Added screens shot image to illustrate example. --- Src/Help/HTML/reml.htm | 387 ++++---------------------------- Src/Help/Images/REMLExample.png | Bin 0 -> 47280 bytes 2 files changed, 47 insertions(+), 340 deletions(-) create mode 100644 Src/Help/Images/REMLExample.png diff --git a/Src/Help/HTML/reml.htm b/Src/Help/HTML/reml.htm index 3b6753a59..0783817a5 100644 --- a/Src/Help/HTML/reml.htm +++ b/Src/Help/HTML/reml.htm @@ -15,20 +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. The latest version is v6, which is backwards compatible with - all other versions. + 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.

        -

        - 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. + CodeSnip currently supports REML v6. See the REML v6 language definition for full details.

        -

        - Block level tags -

        -
        -
        <p>...</p>
        -
        - Renders the enclosed markup as a simple paragraph. -
        -
        <heading>...</heading>
        -
        - Renders the enclosed markup as a heading. -
        -
        <ol>...</ol>
        -
        - Renders the enclosed HTML as an ordered list. Must contain - <li>...</li> blocks and nothing - else. -
        -
        <ul>...</ul>
        -
        - Renders the enclosed HTML as an unordered list. Must contain - <li>...</li> blocks and nothing - else. -
        -
        <l1>...</li>
        -
        - Renders the enclosed HTML as a list item. May only be used within - <ol>...</ol> and - <ul>...</ul> blocks. -
        -

        - The following rules apply to the use of block level tags: -

        -
          -
        1. - Must be matched, e.g. - <p> must have a matching - </p>. -
        2. -
        3. - <p>...</p> and - <heading>...</heading> blocks - must not contain other block level tags. -
        4. -
        5. - <ol>...</ol> and - <ul>...</ul> blocks must only - contain one or more - <li>...</li> blocks. -
        6. -
        7. - <li>...</li> blocks may contain - <p>...</p> and - <heading>...</heading> blocks, - but it is permitted to include text and inline tags directly without - enclosing them one of the permitted blocks. Nested lists are permitted - by including further <ul>...</ul> - and <ol>...</ol> blocks. -
        8. -
        9. - All text should be embedded within - <p>...</p>, - <heading>...</heading> or - <li>...</li> block level tags, - e.g. <heading>heading</heading><p>text</p> - or simply <p>text</p>. -
        10. -
        11. - White space between blocks must be ignored. -
        12. -
        -

        - Here is a valid example: -

        -
        <heading>Hello</heading>
        -<p>Hello World</p>
        +      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>one</li>
        -  <li><p>two</p></li>
        -  <ul>
        -    <li>two A</li>
        -    <li>two B</li>
        -  <ul>
        -  <li>three</li>
        -</ol>
        -

        - 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
        -

        - However, CodeSnip is quite permissive and, in many cases, - automatically adds block level tags for text that is not enclosed in block - level tags. The above code is interpreted similar ro: -

        -
        <p>blah </p>
        -<heading>blah</heading>
        -<p>blah </p>
        -<p>blah</p>
        -<p>blah</p>
        -

        - 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 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="https://example.com">Visit - example.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: -

        - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        Character EntityActual Character
        &amp;&
        &quot;"
        &gt;>
        &lt;<
        &copy;©
        &times;×
        &divide; or &div;÷
        &plusmn;±
        &ne; or &neq;
        &sum;
        &infin;
        &pound;£
        &curren;¤
        &yen;¥
        &euro;
        &cent;¢
        &dagger;
        &ddagger; or &Dagger;
        &hellip;
        &para;
        &sect;§
        &reg;®
        &frac14;¼
        &frac12; or &half;½
        &frac34;¾
        &micro;µ
        &deg;°
        &laquo;«
        &raquo;»
        &iquest;¿
        &apos;'
        -

        - By way of an example, if you want to display x ≠ y, use: -

        -

        - x &ne; y + <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/Images/REMLExample.png b/Src/Help/Images/REMLExample.png new file mode 100644 index 0000000000000000000000000000000000000000..6e7a49f5bee6ce5ebb8a6c54f70e776991471493 GIT binary patch literal 47280 zcma%?Q{e-9s|94XEb@yWnON*V! z>u!J6t_KKKPR_^yqaZ{=0YATdA^uPzC<`B-&WZ}@8jX*~gnzqx1?#o!q@>UIKGcIyPP3$?X;;*;^zi&9S@oug3tqJQXWE={rU^tZ3oZR2}-}omju~oWGO9ntk z;ie9rb$0aPhNy5Cs^z!D{WpT3z&htm1X<0wIm8{mqvGmT>rh-G4^_>%!k3@BHcdr+ z?3@-aRX0q6kHZ>7*6gxJ2FF@Q1T`YkR7=CxwPbL9n*+_>d{3RS^JzlAmlo5KuZ82c z_8$R<^iJxFQ^-1e>fX#h_j?D&hdZ`-aQlOk%{gyNgn81A%Q*J^=J^h^e9xDTKaYbm z3l|h@9E&`e)?@r{j}MeOEfkav0vm0>vIW#aM9_&}=i>i{)Y|oWIujUJYAxZIpV$TM zg!^7OANc%n5Z}a^tR~>MnX()3{gChJ?j9Z%RIb9@svT$Fw7}xm+g*h?=PTc|xUBQWT^(#J=JYBX@ zQqSb~FWDj9YW|Tjg|boun#%G@hZWX0IDnD!dPTA&4kaE`1}={Kt(l2H=%sR|!iSpw z=$Y#oHus1ott3*1+$6=iugpvyZ+e=#l8%PKS}A$#un5N7k}vs_pYg`4mA|vWa><~J z7f)djsLz8!i2+?ckt`VqhO%zHJHyvts&kgyWC^xTqpIY4ZJuVa^r1Q>1Oi- zcrGYEc{;4CPP~a0TIi{@J)yui=IPZ{_)owbA3`1x9Mqj^FfBWMawqnXSl~{%>Cw4Z zLwjF?zY4GYagQr;P(grhYpbZpCb6u@3W&7#A6Qla?4JbXa29% z1wwQLEa}%f*}&H~{Xnm);%)}oHKm`s2gL6;y`$L_?AUOWc3)Z^%UdxDf3kzOlOG?V zI&UfdwJi4ri{Y_H#DampfI#0;8?{&{BSv(Pexx^Q&=e5}=o{wPO$1fPyD`P@SEpmJ z-_*+O=dLy%5$7@Uheo0GwMGXpU7JYBC+dBkx3&z~F&#E(m zcj>N2psGzn=8^er5 z_ahsgTLC)tcn`V9mr=`9F~Q;%-&atJ6l>v;5|acOz)PWk3<3HZJ@ zYrYp>zMQu(hU1m^8O+Gkmq0)q4(;|Qxrh#WeXgn|UOUB}`27 zqs7Eh+@_0>+qN9!`$p;L`7|Si=ctkfyP5FK?X?J zO$I9X_pEXF5s)Z<6C*Dl6s-%@9sj42ov*u|*)Wcev-{CfV=41t!VxkG@PY$d790@;gT+7+xNK7T_TAvf zq%5NyG&-vu1188S8%x`UyyYNFlkLjE* zaf)&;>@AO;@O1`wqaf$!RhiSP>QyWDa!`l!_WhR<^;&evQ>FI{{obcXE^{i5CP+(A1eC8SPh4iSAz)?a*jPA zCmF~%)XUUH`E@WD066_$?L@s#j&(l=C2b5GT_A1Whi!^Hir-fwJKyJ?d3t>tf!;>r zK|`i^s4K2-w|9MG-IBHRx~)`bN^O1wZ42BBs!Pn3ZNLFSeql+9L)@jE7d(>97-1B# zF|a7C)aBzMdhdm4Y2VS@uy~)lP@nx_Q0Vy)bMPGOh~-U(2P9u_ zDIL?5*U*r(Tr@(bIJT!bURg{c6cWqr z^x5@$tuiYq7lwS%b#3+!u&e|esXcuUDi5zT4go&di<~U!b>?>GbfNVs5ET}jBs$^o z4AT?alY;RGGcvFV26RoYadS_Lr<|#C^2nYuu}gw`eW}0Ga!^1XC_7vlYCDmNF#BWM zqdZa3YdKKxO-p;?4}W7*(_Wup<$pSS?|^r(%DLB0?dX5Pon9dR;Gd=^MEHM*_PqtJ ze|F$?@75acpON@QfOE zuEnxveN@7fgN0Wj3RWI=3%U+}^3U^mW`KDNbeTt1Oq z`o%4n;~&*vwJ8rzJ(z_Eh&fOojK5ca2RXZyc5r6wAHqE`_X+A|y4Op3?wQuJNX@33c<|7`=pCLjH_!awzAH;>9d?ab$doO;l?u!vAt!?PHwpp9Ee?ZC5 zVmj=?`~k~OvtRF#jwh(bLXq^>`^TXNeP4sq+`wiIbxmYD{k$!3@a&YaG;^u5@q)_+tF-u<|c{s9}`;vd}^mr+cbX zAR!9S9Xaw&VMBq}4_BZ*aIy%HcfU(cVCQKL%X}`k+t={H`T6<%iEwc5j*e8*sMebD ziNERKkz>6U6;Tsxzju^;%Uh|-s%ZOsz2GK(HJ--(2n_|wZ%+TNY#!pa52F6+mdS*4 zhS$s)yBL)gDgMfMaHd*cu7*uzh5IQ-nrl;A+R0*k=7|~Dx0;{*bC^0NO=IhR2c}NY zVmDP8F3bl*;E%_pRNxVAvlZe87FIc4Hsfl^dbai#L$Rs%AWTuB_*i>tM9h_sx`>QWH%&_xTN z8?)`(RN(0|eLlv?$mZkMn?$9BCyRrJc?>8h7@xQbqvtdHRc55s)DfIDn3V?|--hPq zf7SA^sWj=X#EgfKy~eV-=3sQI1*azK=TTAH0uPuBRtkTM0p$L+dnO3ngpY(| z)K(mba6<(hYzeGD>BOb<0W}UJPmBc`ILLN-6$(C{x_boYqzf0MD4l^KANW-c>1E@X z`7kmVBV;>)dK*OkkfI!`QBs}oC$0?v`?OM=ntBWwhB!8EWNO)SmK-^od)zz{84W~j(O>5>8S({+;9SR|PKt?qYTV_`5mVd!^InNy%gyyIp+ zbPxNiFX;B%tivDZ)H!5eKZ;7OTK$NbdC@N9Sjt_dye@)KgDy=bEIfk5A)kD_dQE@f zo?gN4D^eU;BS?BJbg-OKfj_0rHcRjHw1eSV!_4}=-(EMHZhh-Av73#)$PF(l->J3K zv;>iqQ2T9{I_Q*TT|Tji>=)6f{;CT&_zZMM-|?_tZy62eagX!M%r*G3f5K`yY~j0p zObncj!($Ve*9d!j8y08X1iHFi2g4|nL^QTroTa0Faj6Cp_i6I3m9;7ja^6UnB5K0T z$Z7r3FaqRKx9Y*UUCD*X9;mcItW;Gu~mI}#;@6rH_A-|$g zW!L{^f9LKQli75&1y4IMi~Qt zruH_sH3f=vcllFxv9PVO#B(Y#{?jJFF>`9hA+D&3mH}<~*%pt;XsIVVJwlp4QJJf= zziSGEav^-6^D6YHVydYYdDVv%hsl4al3_OcJ6nk`Z}9Q z|DF@@gyT*-o6g;u)#_FKqXBoa>Nc~v*?j~4$ZJ@<8wGxY{Njlj+S2nB&mCF7<7h6m zbTEjhZopW*#sWJ#E5E%8{Z1D(x6XhQ_xrf&gUp3woLFFLegb}zy|WaPyTv9B*7eMR zDjsWm2WG5|kL8xv(fkGnV|+?w-Jc`<8tlabHCn>eUOKUR?+Kt#a6ADZm&K{#PJFbK zAOS2}U=LQMwh-D>qpf@)K2$V9V0F--AjoQ`dVhGRhEUiJBQT04pb-}&S%`S~{%Z(V zR-5`18lAuf@c;{tJn)a&=B)`D)YKjlD64ob&hB_jgtA6%%IjZGg zj7YPdC)UKxYfupiw3;ms?#0jnx`NFU)6>>la((Q|&a`3W!7#p!8+^*EbE+{sjba&} zyH~`OQW)FDSyVPRwD}TSmyNFjyds}N+5~|PwRc$$niN{buo}bTQJv2rvic6@?Kz98 z`dZsnqMlsF5!LJ69@Z%-)fWt1^YdH$1-VTznpbk#e?+P|AlHDq>V3NzSU+6E^C|gB z&5n*!4y~w>LkCE+A}$ZIOup(f(Hm@J$78VG+-;%Mxn1?my~9V~O+Cc2+HHr%#RvFq z8g5bo@zGGwPRoS+y6Y?g zwxRGD&(k-mA5u9%VlFd^5!=$>vQtf5T&w%{uo(0rxnNOBnv1V|(c;zEUCeVCa!xqc|*rYlbX3&mR_Wr5FK zCo_wQiq@9v4~akL>Ey+xJ?v|gJ0H4*(imtLjx`%E;}d`h9{>Q-hBeRkQc=@g?f0UN z=X(br(7kkO1j;Dh;`_++#*!1k29FC5 zImuJuA9=is5{D5CMv8{7ry^(lblZYbXP8J>7`aFXGt4cAxjfoTVh9R4om{g9?vJM> z=LRjNy;SM`op|J?+1A!0B%;;2~QKhC%JO$E)6r`EJ;K3V;DYC)KxMttqB7+4J;B-oE? zA!XCS(qG+nE4OmBzBf)*vK@LEg>n1NAlyz3t7;06l!_e_hSuUHa#tf4F+ul^hQ0kb zRgycy;SskNHUqqztc)GxBpd9iZl{h;&p{ZsEp+l0eH-3PfF_s(+3={Cc*Go~tI7kd zFGUU3gqH5Npa31~FA$9Iq6fLsz4gJyNI-L2LuqSoVb#s}5<~pJ;qY!$O8-{dPV2|?M(d8}hRQ<0a&_C> z?QlimvIHrq&0LmztY|o%_-ax{)NVOA?J`4bvW{;o`@n|hw#&+OF0VtNkF1fewdluV zPO=z!ZO_p%e%odvjl@{teuIX>J$>6{yZcH)icu4(-BE`$Mos2 z5fZD#(R$5>=f4m0aZ?PJwIQV3k!j^UhZaFd+ z`8Wgv>zgxXrXtW(lxO%*E4VbNhzJIp;r+XNe$TDei)UTWP5%p*A_+^E=mShK$uIze zPJ@Gc()^quGqFLJ|4UDs!;UjR(Q{NzZIL};stPY%bW2~uBB96rM*rnzyVY6Q>T=MD zINsrmrjMmWtLtN#_r-1};3CaD>Fl?3uCqkVTs_9+T7&ORZTtkrsFRbER^=59^)k3! z$J6!4i`@oNO`IOaxTSOXdj&IPq$K4e$4S2b$yD|Icvj~-g{M`!8r7hJ!XszGE=Uok zy&eI%@lV&7hL!)XdZS8Uy&e!QTOkz^s0?8JRH7=lFg^&BLEsf=i4P%v{!<*$uN?Wz zAaK|yvU)ef)m~gO2p|Mxqbnk7X01L@J8e)B7@-|FrhQ;sAQFUrW+Z6f2HE~~e4`7T zdXOyTg@Ql~x%vJWw0b%MDp6rKpk3zv#xCKfeBm%NgL+1w0~3ZW^OfnIwyW-w)+Lya z&B;SAO?7$c=cUr+(e=*faZ<_gj|hv}E7=I5QEgOeA)I~BU`Yf2c#PF_{Bb<%f-%2S_&c}f0TIVd6@*bXL&+k zW8e^9(w(F{uB>F??amWwJNKWlYY@)S@zRp&AJVScuJ!w<)TVb#k_=v2uum6SNO7%s$~N)Ad$<95j}RMYCTo7Ln*Ed!Cg zV5LYx#Nb|?4xGT@V31{t#*zrX-&B3ukZc6}Z&LdR7_e2cjY^au|2TDDc<9z>op@fSlL;PKZ!^xd8?$AfzlWsl? zw#@6ah3xqL`%(9Kv`}>6Yie@GWo>5mtG&3Ss--jm#p4721)>rX;(T0pbTgWkRGh9_ znpY0>{;VF5tW8I;F=NIugkmfo8iofEJxr7gZMF-L^yf$(6Kf0up3?B@C}6HWbj?7c zNglGO2eorOUv8XyM4XnF4K2nHhoIUM7ca}>dr+*O84!e{w75_uB-plN@IOIh>`m2S zciLik!_%tP#lj{jQH6p^9ywwvK)$3Nox(^HUl zlug$EjYfj0{429!%o;;cf{fcoH`^|06)8L@>bl9aLCcE|A-Fs<^UrW8S=d_x6i!PT z>0#1XLhUF;wxL{oU)X*g!6hMy3z!;=`ohXwc;WpRgXjyH<$|i`PK)Kix%-RY=Wf<8 zk?`O9kHC-gsp)x9!hEV5)t`&!p!$kcJ0gnUBsV2C=vI0#SP`Hlu-fAwJyI_-bg09i zPLkWvfPU5BkvXBGv5;+X2P6_mHwYal9e2)Ow?NTgQDQ=&LoApP9)WRKLV0Cc`a)^k^Eh$>i450F1$(`4) zFffC~ofXbXPoxHw7$7BhE)kkS{}v-xY+E8gFqsa9uC0G39M4x8SF153JHzLWoPoDk z7+=1(RIQLP#)ohS=;*=`z0roj9iOoA`f#4}$WEUuVZAY5tklX#8{kZl4_JsLIvt`Q zBqOA<Hz1tAbLOg-EtsIirq^ueB)Ob8HVSxoxi=qHwG0GKr{{t8tUlCzcK3G? z<{pI*w=FQQJDF#oCwKc?WvLhX(JhDbz&(=%OVF%O%~r5rd6?E?IqZg%RB_hoA~UKO z`mwfI+f2jp*5cCRxEoCFAue-1C(dBeM>J%OVt|GMO6G1)C-0uH*`a)FR-cq+RZc{i z;GT#iQ`_B^MxSU=$k$~ZkJ#iU6_-Hp^01qU1mp@GdQ}95HN+US@a)d$r)X&wCFo<( zA5uVg#OGNBDpsuHlD|NQM2E$ZQE);JJu>Dx)EwU!7OnYo2(g|gOGFAFu&r)z^K!5* zuLyCkFdMAjgYUK>9EOt)U#U`|5J>sP3N%L46cx^%Akt+8StZlN9Rohk;HQV`N;MOw zx7$sbd@5~&ZRsy7vfHp$s}-`B?A+Tk@V3oVU3dP54cHLWSv%yCHNL@l$$HQ0Hsegu zH4i*yM$*L!mON9%qY;cbqMz6{hsq(X!ls25Y zb*i#f<*HxAxt{0UH@jLUfB?tJ49UT}omufwq(a%bqKOXfOqrR{R67bq~%TN zwU2>B4537+SsIF%5HwBg_>FsqaRBe*z?z7ZM;k1JJG`Sfp7JJ4>$>&cLVcl09?@Sp zL%Cvo{Ekc)DHc`Z*s#DAw*07Ti;Sim8=NKfr)_)b5?XZxQ2qMxkM z`A?-hRsf1Y-_YpyMbKB-nfi}@gWAx*b&qrlzebMm7_Z1G_UWxxjiA(be(%B~N^w=& zI#M=pNhqo4d94%fOz1u?!%iaoAZ@?-N5(cIZlaR!4_Ur;_PlJ~&5y18sF4ml<7I1o zpF3(uNZ&SjT(ko(Bj(zp)s%cYu-^6?BBCzfvDIA{zL_pL@L{UcA1K#>p<39y~3LC0-}~k`nYSj@E#y ziKB$l9l?bSEbf6lw{2Au7EFGAHsp~AbnzW-Z2=|B@v5PaBx2KyYb5lc0zQ{gbivER z#ZC_MJ=yAmGdygB8>-FUJeWCTI$azl|MX1ke*c&V2w`Ton3|^aq*pV+Wt@WgLhfl45W%zx+{jCWRJIMXsd3~jOh=!xT!+8b;tX`myulKP66% zikjlG;uBs{$yL^9sK|8ToZUuAGjdp1WjXKrmsVw&oQiK5T8A0}^w-Ah?K!v*1BeF; z5!PJ3EoyKSEIca`KD-72c-=RAeW@@!5btcO&<@)`r!OwZ_eGZI&(rrnEN~q#9X7mI zqE8AGT>p#3r&zRW*XN`7 zVe8aZcPZvcwo5?ZvuvC%Fd-Dfa{?bXU0jAA0;Y%~y8up~L2)hCO&48f_=fv6R7F-Fuas|u%({lV~XN5lEAeM%K>!o(3t za`MzXf%g}|!zc^(tsYTg+7ncR^aW5F&UeN%%*^(J!^gY6A7;hsWw$4|2(sccBSqsq zT}J$Xy1PX;)li1B?|d;D?f#JCu+-sSpUEo}FMclfBbG8CuLv%Sts9(mF{yz++2#J3 zE6T%1ObTv@>)|IyFP}18^Q_;`eUx6Ow>Y?ef#ylW&Rs|iFYNjqm z^A5kq8ZvfVMezSD8AASUQ`| z@e>z`etM~WSXrJ#<&h!6BC*m9P5CD?I7mf>B<9-R;Bh3G1?Xd015 zjubBsqcf`nO@o6a`TZBFKzb+y%`e8epu#ZKRHmghf08i=hxCT!qNwi3-zk^!4<&Gdx~wKiqF(8e~*Qf*ZVMrZ5xe ze4FqZR>_p1X1frNf(lQVueF;Sg=h?$&JTIE3bje%i%1>c$Wpg@Ra31- zCGrVw0X{Z+HzQ^=V78~cCdH6xVWtxZ;6kAR$Y<$UcBp0s zBqWGKzMxR(QlS-~*?)gs+$bufS5;w0L?*ZRxGG_Ka7m~Mmwsy7zk1!x0 z-l4IXLIawMEuHjiM~txHAQI?bl5fL@JHCbS)2LIVGj8o>)3UI!X7>sYO(?dL117Wc z5-e#CQF@^=&vNFoPuvia2X;yBzC=M%>l2nUzlrf9sDFuyTV>;)o`v!Qppp0hf^1Ae zAmLx%w{P6f&Y%teJJsp%q*3A;)VVktaNL^!E7Oq6pJG@q(!bwO-EIhCbdV~Tf5$

        9gVJA2A1-@TOkttGQg{wQu)pMsf#^h@mW?oTkq~$u{pN?;>AF z@BCQy1jbfSm-0!x9iP>nTa#z;{p|)%5OEzM@9F->8&uC$p_$#W@%ZuXCzWx9Yx@S3 z_+OZN_DP@K4m+Dwe}U@(^5@OrQ!nKN5KnzfO7wu75803{Nju&BW?*x*ig)SrAk9Bn zCM0~B7Cbmq*Y}#;Htm@>%~&m}mo_j#x&1oiD){mC;AnVB(9yenjq@;4FbVL=NbDqb z`F_Ea@&e`j(@Fd)Zd5Ne|4)qxn0~iyaBVu3I6E!B&P?(Cpzgl`N2+4+g+sx1-nrI$ zZ%#)ysuF%5#j{RCq8{^c-@wz;! z*;ptTk1Z)<5@OTv=lk&Wm{_$2;{8qGySF;~Jp@qrV{X9E!4d-fD`wSfMNHmC=WeO{ zf%~nciLNBd+D?Yo?^Dgj<8H~AtY2}mUqMr!wyE3;>VPrX>Fi-gK{(ukHMj-_+iQ9<>$-fErc0E1Up{t|ocDC@KjPY2nS5IaAPw7O72nM(R{ zlnB`S^uD23GO^?&?cbYhGRb1VcuQ#>}t(jMg=6SziX3T^9OCx4TcF`{F<@D7vw z{uSNit}ozo__yIhU`nrfV>-HCuAU3Lg{C<70 zfN1Wi4O01^S*XP}(l}v$P$DckoDXe}MJ|z*bRsOccecR=uk&#oRTZ}=HMgtpc7syb zyR>MDfHXpo*(1vz1>*Nt!sA$fAdyWEQN;nsGkZ8~F+Dvop&*$Gw0Y*^x#a+r&(0%Q z5)h~!?nngimKZMQc4wDx6h!ACEJx6eH`M#gIumssbk0) ze|YMdi)ax4Mb#2y*!Box6T5mDD0T##Gm0^jWKFtw=(`n-H0z;Hb(FiOnO>x_Ejvgw zh{&No)64nxV*fJ8;KOtCpa2HkFBFUsaR{1J8tgsfb}(1X=L_!l;Zq^(i8=qHN}X;Z9;xBsRXGo#q6Tlhh|)ciCRKuJB<38|Tu><` zm;3!!rgf*W5?X!_2cv>TCC`8m94fQ5EJgh3l{ZOpF_@((&4m`=mzsp`+J9`bs_nqp zfn=fqUG;*~-z@IEL8eqSIs6Wvy~AkIG6sya6t@gC6M;w$m$$>a$D;G95pnPI##kEw zbbt$VWJJaWj3Pe2%cI|RYQjKoR^$Gt^=~2(%-;xJJ3IHwh&t{#aIY zuL7i?!(EI!?9iWpLTK2c(9nbAm1;v|+LlK%YZA}PP4}07kzU6@ld9_O<_1@h3z9i8 zT#udBgEU3ZFXCY=JMT1 z@>rDuJ7Uor&Gt9f|6rp(!_@6?eEmt$S82JnLpjU+ zhb_7<+x~##poTl^mgN}IdE-V~?Q*isCrE)$#7?@XDGFUn3Er$iwy!VE*sf-aYQ6wt zIWe!z{nU#UvWBvY30Ro(b>0nK9iK`?v&H8p&c(yFz2sKAo`dDwf3fOTJ90!%|K{-a zP1K{z6ULWhkn}ozbhcDq@+yZ(5c<@FO;t>rn(p?RY!wX%Ti8VNCAY*qGd$6+NXvEU z!P^w|?!v|@eN|QnOU1FHN@$h-nD~njIQX(18A8&zzi@7A!2qOA0m@?!Zl3$;Kk3s* zDHE9FqoMC{IT3lxs7^DBYMctl+{&=1W=;4#KEq)z6}W^yX^dF_;#o9W!k;C@n~rd$ z=jVw)ukVA;UTM=Kh>xhhsDV~)miA~1%=6fH1Z%}FOd~Fa0ya_A341a>y;6)SoUhIrB=EvIc z-!0!5@@aEda%^m{3V6M}QRW96mkEBIvCe!xTF&&QZ*e=^m%i*~?!IpA_h94xgt9N-itmQQtjF1Uk1S*sbHEFpGe%hdDF~(~|o-kPr z+iK4xds!&XHhC$8<)I4)#(b8@!Jx~MCH<`wqK;K4W?iGh6XO3p_{Rz(tk|!1 zKRp99iQc6T=&0FfNWPyT30J))wx_aN=@3bIH!)NdO~0D0jK`<9a5=WWXZ5>o*4p_D zM$8LIRU97-Y|Ywi8;dI&sp=|w3mY3ND;rF9y54GvlO^bKq3lTM-PzIA;ahs5gyPBq zTa}g~6LDWj;zI}aS7ZLZj{NhaH3&nCemd(c%$9@u^-)6>O(H`fW5@2PE6 zr(OqPO|Q!R^y>9?*O@$f+tdZ`o0}P{=Gz06*6l{u4?bN?NQ3-G;T!+5C6fkjJdUcV zm}yBOrXu2p+w2>|9BAD(N~In36-@`Zp3xW?y`*j;WeI03ISI^alGrKM z=O?%-%XkmocL@k1F1@^pDn~Pq_0#%|OM*IF2cY$JUiK!O2Qybu)r}0`bt)zt8c4OD zqIam=P9ai_@eJ1qi#xg9#i%$eO?&^c`dNWd>5Yj%*VVj z6F2&v1&8nuCUxNeE(%5_I-9fkl2{B%iC5@9dT9-1wp#oZoWjgFx)E^()kAyz_2GVenTPd5ura|DaoJ-< z#_~KvIRg%&%{D#DW*$W=NC<^Sq`LSBT}v?>EOr(+xg>E0$?Zhmck?A)@&4rtuKd-T%9csvq zrT2O|hP&g#M9?dy)kgI|qRny`hnOh_^z`y|)-2`fwrz~J>9N%7S*#V_=u1oJ=JF5; zeVc|OOPU7VT3R~l`s(XuNP|uH-nHM&sN5wj>hwdT#ZS$r{IDWq&v~-`?W?CsfwetV zT)rtyY}E7l7pDxpl@GEpFprw;AM=Wb7@F#>@o{l#N=4Ul2&qhXJxAy|XaCe5bFxiN zXHvSVmh+K~j{DVQHH*1vlFFbn+=_+aB+VJ5a60uHmRCZ$A|4nxl8?GvA>RQKk*Zr0sEm3HZE$=sgu>UXU5*_tM8?O|Hl3o;5P z16-=jxFY_STcC|bfP=ZGr=@y!7F8;BXvigM&lYH?PLGF+i{IpQJoGQyxw2l5gmRjgkO6PUzd!OB+l zCuR9^aL;=FR;t&uI?zhQ(%rN`nG`Ax@#$c`ck14&Z9!meYnHXN0hA9yK(qnQTO>>n zz&AfDSj)kyTqegA!$d`+70~0im?|3R|1n!V7d_O#tw#qCRGT(Vsy4l%?>TVAu)0$s zFH{zGVX&xdDRC4r4Pi$6hw=Uj6JLH>@r0Yk!AC0$Gbg6P=hrxyavQ9?H&-DYnH?Wp zexn<5$XXrsw(e~%P>FYi1VpyZU`}L^6hSTw8+V6o4_j|) zj)1_BHDQeuf41&gRo*qHn84%XS2pI~zOieqf5nFA|2qRzN8710#r^D%s65#bOcQt!zTu8Qxfer zZd7A5t=ToK_M$4f83KjKSp3v3U$xVlqFZi%T|CTuKe=t4YWnPb91|hPI^Gq+mf)M_ z(Np*3)SU%99z!|@*2MH5fNCllP*q+nOaWeB=kAYi0Op*nW&y8>=`Wbi3chLfUAdhD zX?*rpSY0RNdteja<3oJfQhrWzI$C!PePdDGU5;k8M$6Yr)R1RGwUAMa% zWehJ0TkXLwZ6il`<>QlA%@FiD805yk`XU=jCzv<1wU%l){LFpZbDQALC}C_qEc)_G zKL(t57pKyvkC)-))=o@&f#87Fd(SJMo#QPNbqED5daE6Y5gyFkM@y1m1HA?Mto7h; zAl%mA5~L_5Kl~rUoLADBinqq+0!yqyFzW3t5}xK(E5Fv@fnep8TnlFcgnsg|wskP(m*s@!D8tfM>hc55_a&;g-V(%Ff^maqpxd zsIn~*3Bo>0`6H+CUTtk(L!U| z>J(6Za64|k@F;g)J0&JI6#eu7)0*Rpa2|z~M~%ak$GEAiS@bh2gA`nqqK+QRM=th= zyJcJD>o*(2^fR2<&Gx*V4Wjx01-BAGpT#R^Uz;N*o~v!10uXPQVD+yRDaA*>=#?3d zP(FposUrMx>dN!X4CW&AU_k8z$Ia4T*Hk(U*5(LgzThApf{+;$-1X~iFo6I!zuzm$ zQ2R{r>UookqmFt))^q>dFUSa7wP|Zr^LT+C9(sRSd-tCBZ<_B~c!}cAMkRoNHzW_uAL0kznTTI*3Kv)N0zxoFpMWmpd_I zofA}55-c~W_zfZVw}wM;ujSuDs29`*Dy~TL%J<1L3Ch?Rb5kD|f$u+B^yyOVhaKk& zmf1OY9Ok8;e*uL4E}wo51_pvCLGQF9PltJxwG3Q&dCm;%O@vhRQ|T&+es1@b3Ale! z>Teg~!>>?T*~7)Ptt}astTSZ-*y2^UO|T5#S5yKAwGlb zEJxY(PKGtb^qquA9Wg1;^V+1qWiI6vb_9sn{w~go*MBjk8S@kQ7>A7xXW^({aW5Id z4v`IAyTzBp+QakeRcXT`5@6s^ry0FDoZI8gVRd>fCemb9Jj!Q}4<{y&O$zDRpQZ;S z>b1|4#xm_W#T5b`l@O>+7DJUI&d3A7-ykqWwcI{`B$JS6_H=L*@o70fMux>JekD{J zixm7_^tpB+GU$#Ic6o8sjSnX%LQ$}lJ~3g2M|(v~Z^Dke|4K=v z#gKv5RHda4*b9b14iz$qHvg6h479YB$kj(9dcjOS@sap@m;EpQPf&%@NCjpUWx}L| zmp7R9H$_G;v+2XHWlfMpXpo8hkN^F=x!KUMylt|`>veB8tfr+GGW1>HYOO42yHJT< zjL*F&-t+gqKPGR~H|%m58Gu|20#Yh-wSg9rVFn^B3_MEexpyCwJS(XG7m(}J@5BVp zhqP+#E*oO=U6qnF`gED8CBQa6JCQ-X&D6M;i=s|ua2b;Fzj7fw8W%vFexFV7s#2yw zA$sIrFe$_7fpphuTMh>qZL|_JRXS4B)eQM;)ikkLfNWzG#n}LKG;-K>cL32ta)-`> zqG6uO|B-c0!I8Crx=uPyI`+hvWMbR4J+W=u_QXym6FZsMwmq?J`}Du-?3;6L`=YD5 zs@GcIdhxup8!Z$QD?gLCNLIR-={1K28bX{vrqmxFyk-Euo!)R6q>R(DgjS#Du_Yt(pcpbJjoMiZDSZ#EI@$~u*( zy5lHQsD{st8c5a~@Bu_8@;Oj8vfUlUbQ6dxaT@~{3V164(Zy+r^>L7lY7nAxe79XD~G#}`6P ztz{J-Y;Q8|UNg-)|G^A-wNh{TG}MUazTQk#+Fsd8L=zPYj{p~|mC!*he0uX2{2J0r zR5--c$<55m#9*b?W&Y5ABcVl1g%ja?_S{ocN@&n(b~MO|f`x>+WZy7dG%168fY)TJk5SYUyNrQ}xy0JzR%%zqo4v_c&gNAd#E1h7?yHq|TP)iM3Ro$s z1J~h(Ya&Vv81@_<*5r-cUd0DQ_e)~c=(jtr4Y5(V+sgh`kd2wfarDSGnQg=Hh+VQ< zY5zXU$;8gQzTvJ$wpDL>cagPhv*LKwv{F{lc+o%aMw-}av-XIn-AosW6b}k6!U5qG zB=u79$2nXzFb>q&SbMD(lzlIKKDK3zJx#;q8m%^_s;u2Nx?C53!cB!5fXU!?lhXr! zmX&Jq`}z1J4moPac4|1+=*SD)zv}BhZ)_nS5^m+`@%&j@Wm@qcb=ph76Oz1b`A)NJ z_pjdcsL4S_?{zVbYU90~mRGIoyf=@}^GIHKu$`g%`uuHmV^4g=G(eD|7?lakaSm6f zR0tnBc9vYX)^DYvj)DiH4d3Td_lN8zRh|CMvqFh~2uK zch(@~0Ei=M=2Yh6W}TmlNcvv2ivMRhBg?Aocy`B$5YOH;0t&cbCmaC?4CpQFNhIq% zi%J&n-;OkKGKfePD^WSI!NRI*%SKwsdx3`|cn}odzbzmMU9sI`Yo!DkFyJ!HHqu&o z9n9%skfl#bKgN$j1EcD9b_?G5)mL^wYTgo^Htt9erAVx_AcEqx)J%dS*MwJaE>W}J zL)%4GGvE92FaPyKa&vD6tT^JI9!Glup67&q!)5Mz9;dOo_$N5a@>j<~A@)dkFpluJv4H9A0bWQQMJt)N0< zm*5^q8T}0V6?vWadC;22f47ew|9MJ8#A{<^N3b-tnxEqQF(r5eVc#OBqwef|s7=Yx z9D8g?)O#xT%Ec?(Yjiki3`81M8=}`~wOga})`$|HN@kC<2Er1vVyOY;uHXQYzHx6y&24>h=Mt)eY{dpja4TViJHk}8Li@S*;gNWBc7ntDD#`Vv9n3x~)R$1fRYWgZA1=pMy)Px^s z!%6$x9<-Exfku^IPI_f(f+-;B9Lb#>8&|l{R5&nV@R*_IA}77B?L0}%)pg#3B29j{ z-4Nz(>XEdx+i2Q{Dj7Q$d)LV@^LG5z+>CDNWayt)EJQqI>00KHj9-D8h|YH~oz?O?xU)lus-(1bsye*?kZ7>^?aa(?uMefsbOGDk(KK&E zl(Lr@r$b?e5OLB4E#OUsr5*k@>&NL;HPu9m=oUbxBpw~dB6q94(TC%KtVm7K7P@@@Y(7t2PZd+t<%)fg0OD&&A;hdFc+h>;rJwf z0d==E0+A}$Vnq-Cnn=__WW|QQzpNY$&NjH4Ld4sA#he{Nb(#w*)koOy@4Y(#6+|7$ zQajE+;)KHAkC1EP)oME>bRX-=8z>vf-&}FfdS@o}?$V`4C_x1^rhx#|yXN4+UW`3>O{pz=6v1BG_KC-Wqy+~9vE27bOz)!OP8x^n{*f+!;cM@`(qw}mYKaq*#3 z?GbN3B0Sm@$<%?xlCH0}Q}lHu-J0HxbhxBFP9c&BnG$`^0?TO@WIJj2ZPe{&r7n|? z-?!2B?@h_0ILp9eI)syD>F&q+gb`J`wVb)7Dd4Z5XIDg{r}aPpnvn`!_}Eg{b?*XA-w;gunbq+!J9Sl4|2fz@syrQHhDl>ozg8 z&gnG)kIeW!=W9Qf^xR`3eLs>C>wDOXc3Bpl9@ppX{Lw0@%zW~3ASGJx`|%LdJw=o0*Orl5DEWnp4{V$SU%jV)N+CD(KzTt z3r3p+l_`?G-()%=c8?O3xyH9Qs721WYVu>=D;go1>^c}bWE?m5gOJ#+#P3h1t%04Yz%61Wk>pXO$nK;KU2FyyQRObkBQ;N`Bpdo zaa6Ol*J(y^(o!oQJyQ8kk}teZ|1g%smA#hXZbCR zp`fTjT>uW&Fqz!p#Sb%w{WpMJLlV}?;%>ulH3?B6E=L|3?WyNA34Pn5%K@Qe=njnk zrlb&NCUJli2x0VyvGB=tBRCfZ#>?BuQ*nb%Tsput1*(JFACHl7OAh2hE#tN0+DOmfJGpLL4-8;fR}aif;rax zx1A7OF5(U6Scro^ee8BFp4DZ@a=$!kL^=|@0$g~wZ;5|y0TKdIWFqtK?6eD_$E?Gka=e?)JN$b6rlht`4r72I8o`J5r^0)}5EU~g{p0smaWM)m0W4zHn>*%?F^ z6AU}FV|Q*m0n^2vAmNL~4q9M%hR~d*FU2UYt(B?=6ycPr?Y?Jmh3GY?mDNg@KK)Mu zL-=g}pJ>aM_WzdY|2OLL{|jI~)5JZN5b|l}T0`zJYnESO)d19m*o4({#{?Fl2QHMf zjmvCzJ&67|>zyoQg^lifEPg+f?a6yvyM!;%J&&cYk8tb_L>>9<5dSTiy!R_(j^OO4 z-UGY*y!dBk6prU7gea7O3nxFq@cvod+AkZ!kL_iu)o9=P*K-~Q-iMhQWW5h}znAs6 z)l-#WdL7HWfZ^=ZQxypY9ZS6CNum!55B0*&apA@*pr)Pw$!tSw3ZL^G(-y;Bw6%XS z$8q=e&Ep5#QX>gjN||Mbm@Ug)cQ^f(@?J}om#go;cK^2zm{vZPt)+{Se#me10oeZF zg#PfnZ)l}Bkor40w_hZRc}S`OhGfs#49EU3svNE-#1&{0pU%ny{JL4Mm(kO_kNYgs z{7>u7)1UX0)4X>R(Ze)WG$7OBFh*bNmMtL=REi>*o71+_#hfsIACKaO6slCok=sE3 z$)fso92EI-%`td^-{Ny!r7-^ux|^eUFv1T+SMXoD?q6r@$28yK^C)im3ZJ$A(si+2 zHD`uD3$FtG2Ap|We~Iak#|m`6T>C;t9d=`hPfGi2`->{Mv#goSt`r`_#}7S5c^p3H>IN(jh2Y1CJ(t4$(0q)m0Z(4yoE3cKx9*g&Nt&SFg9xE6EgHM0VX>h3P zsFEjG10g8JpRqQ{U4*TsUnb2aR6ut$J47xYq+=HT5&D3FiBA#0PZ0F)8i*0ND=?^o#> zH)NWAix$gv%Yx7&PSrP2mSrSzZ9i_(wqcK64H-M;N-TNJy`7m;j5+>+94i(Gl~prp zT3;aEz6L(M?>G{5co)Wm!K^sHG$;Odt{3Aqpf_^f(h5|4rKWUy)gYAlRwXg0BJS?-nu7#rG|U*Jd= z5GmsMs$hheI9a`vZ#}~jLVn;lec+)OitEHrWQuF>$_#8rk|)3-fr^dW1D2o|{VkC58vJ>mlX^2B4>MjKEYw`clU}kNc1joEMsG0= z6D|O$fmjNLPiip&@_QU!m^r-x3)6vu{ylwxLBZSo$i1OH30cUjw8W?Z)h-V2j3f3C zwjVtVz=%}?KPXxoThd?#wNgNej)*n~z2RPQ>}Z`ds&#ckX`}z2>U*oDRdnn()FN4m z12o&D6mw*PmF6#5x19hQNvfwubk4&|hIRMdm2q6QwoBAz{fH2LZP(o-%=D{8SM*h?2>pE`ADk=JJBjb<2MDeGxaso^HjLT5wFv3XBzYDhE?TWOJMl99d3=}-NYH_b-wRTFA`84J1Idq z(a+B6yPZ45N7E+oyvgvqzJl&oPVz)jHJQax-pbKUG@ys;_XD6jlp3gzvs&m zbLWN%^9*=_)(FPc8P-EH*bhVgVNWZ(Ctk}?pdt#i~da5Pk>O5xleF25`xIb zgOsIto;+~`!zZ%@x)$s|A{@k(uLCLBlaW=s3RTirRqXKHT9s%QQQmGyD7K5H6n}t_{yF8*w$6vf6v4;y!$--T zQ@wU9AaOxoFvogwbYzk@Ei)@)tsuj$tfs|WK@aBP8$sH^o$)=t7*qfrSJ8y7Y9O8SenVQfkf8n*1N{!6=S?(7&lsCTy%zb-~5 z$F_4pL@ybI^lRwbFqEwB_AzFy7e@qIAw3Htd5X|7WN3i!Mg7tRHO7x(@JiwuHJ_eZT5pmBd48Igaki$02 zL!Xkv98f(J+=j!W0u1s-PzK9lJBa5gz=Z1u9~3KEK1Sea|Mor3N(aFYik>=_o^rq$ zRzTQZKswkT(wRi4+ew2puUdMXWJ2&Zu>2tAYas(xNlCCB$OJtABndGmn5G1nGt)`~ z!N4>4H^)ttDOZ#d70-dAzSf-0?$x2&*!btu->A56H=VyPXgArleizL1tA!JN9k(oOEwOfh z%WAe&vWSK+`9q_UDj{>?0jxCCpY5t7@p}(jbFb3wV5L~mALxnGz^BRm1sM`5`6I5N z9{sxtnyCe>-L}(Ek>QtXmfN{(4{Om8(mX^^kcmqrdJex6PY%R)n1;92I=su3RzE2x z+0yRxNW2Q}w^>TLzE~bNdu_V(k#^pYBx#4$)Chno5$r)+EmNOH7@=wXHHyOv~Um3MW$~8`6q&BnsUb@<-vy*bd z=S2+<^P&>T8^__zpjRFopdcR@WTLFU3xo6X16Ou8f86QLVflX5;(B%ZgQ-~H%I5`= z!*cvlt#7ANyfc7718&Ce3_AFGi4#w@(Ani?C%>nsPP%j-sr+&_^|u`=k$w}GVab<_ z-%7d6KQKz*sPK z1{gf{AWUjTb_f?NL8stkU4zwcwdANRD+U|z3H7M7$bbeFK^9o*b&Ei}v>-9--liWj zGw>7(0kY5v8!$^Y&D5Q80CTC>rISdh!6{Ak0yN4T@T3oQa7bE`%NugBOZgAG%Ca zcV-+yn4b_mr-D#_$EN}j2J`;_2uN-r6tg5TfxE^^Y_BQU(1BJ1RGZFghiqBul(t(f zlDoB5W<%T+730pgP7MryiVuzm`8HUqnLG|X88uURbq;E-k>R5wff!oi-w?qomMd4Y zv(sB1H|3<7mYN%AUQ_XnV?FV}PPt*=gVi)adAi?ura zEDQz+TWBYY(zCK~v(xvXpd&mmF{5qBu!e4 z)~^Sd-sT>u7) z1ybHEF!Ezd16l$9Uh$XZ1FP18oZ2noU9HB!N|pV!MxZ!;6fLm$cfNt(C6}J zW$LMFDX&y;q-S3{#uib6l=OqP#>7CUbx|2zPjNRtJ80^jVan6t;-e09GMpZB=UVi| zBRrTYRxOiSK%A12MEhNwk)euU1}}sG5KA)6{%5Pv#azfAx(c*a@yV4K!r;M(G+WI& zD_tj__s7LZoeD394);<=`4$JYv8jWb$ciUBDKlU*$3vW!1Ltc{PL`5qkg8BYpr2DH z^ABEbigaT_I~svJt%NL)`AYdTk}-616ZV=)PvGw8G#2%|sJ&dZi6JER1Sq)q=no41 zbKU#s`uz9}COXLLAc*aA$N)SJF$9{?6l{4bbcMFZQcMjxEsB2=m2}$)JS*rC@T@`s zjRD_-4i5DV7ARN*h6T}Ld%Qr4h1B&YKosBsAbtaf17$k{!09M>9JFQtT&#a{YXu2m z96F%dge*|3Bw8#3#sRr+zJ&NNT`5z-(aN@0{zeUx|!DxXw zTyCuact}g#n;tkpj(5Q+0B+oPMjTp-;3#7D!r2iiop!&ws^<0A1%WaC4jTFd_bNNW zCm+Q?&K+H&1}Y*ylrqcNLgb=vr-+$ z&yw@UJ+i)Z+o9j>V_k618`bzUQ^pi%Dai6BC3^hE5Fekz-RF-LCE<~xi)Oi6nv(qA z=I#Aoq*KY1*2}8bb z&yFgR?5xmY<%A3-h@r}`ue&|zHrwlBP^i(Zdwxylepz0NUiM<=`8??C1$@Io(iA4l zfoy1zG@%9$cg$oJNB{2h@Ncr+{nG0^Wsc)YA9av%S}PI*z9YqMU@ffHQ%+oZpz;2@ zJzp(13ANu{j=;;}ogoWU_~+}umb}JeDWBBrp*qJ=_LQ{?Roc~Nqvd=Q=8X+0Ep)E| z$}gRv!r9v6rXu_#bDdDO#Y`Vf{dK2|Sa(P9o=M5DxJv%z_p%zx~CziyGqCaX7u zOj)Yd(Lt6XX}rMa!ux@HOMZ{oE_~92HJ#QiYtV%GUyh>iz(MV1ZN_X`FgQ6?gnAGQ zc5e>~_XYPFvb+vpkA0g1sl2VX1!)2?@YGB22>csnOOAzrs2ugcJRK^lPp7%p8+D8j z8D9<-Vm}UkIlpMIMIf28AP3~w8F)(cDL|7rb1(^el35=O9tnYTw^x#Aq!lpWf>Hqx z$;s~l8vPNZ0DC7{(|ly@`@&-}$2l&I_)|3=iE>o1YH}j16d2QdEMeWff!$ zI;29uM@XfGWd2r*xAOxX>Ox!HfK}ve_MleFrdIc>mC3Nzg3Ln*#be9!SANa3uHw!O zKseb0nuD0r=LXcnz@iA1n7Kg4SQ2bWxch5pfN5@jrH;Jo@QTuz={}+%2K7)H$)f; z7d=7Eb>+YKk&ix7A1fhP29cdbn7SgtKleO*MfxKWdmf_^RA-)jx_hq0qOs|8l5s8> zlwn&&6g9pEYKM%NrWKI9MZ+(`4j>_aWB_tWiGjuXwVX`r3W9~sDFu$v8kJhzqIPsY zD$cUze&f!hW?$Ijxyq0wrP9mZ8lwj&o!#qEd)WVmfb_68Sp5Y5WF=f?AcoYd361_m z`VP6QM@mYMw#-28b`q5wJL=c8PUHGP)}RR}>n^l)G8tf<{r69Kxmqj>0N&C|lS1&} z`)yuS#MwCyt3N~+dOK{ag1g;&AvH3y_;6FJ$840-hhSo1zNjvJ==AiPx10-jo`fhv zv<4xDM<(&$pH->9iOCZ4>cZU5Mb_9&o#XfVi=Q&S z-@jfp->h`hjmJYdkU_Chb)EB)Ql4Av%a=j)3bnSj4@&VqqApk4gku@XgpPz4gS&ab zFA9$rE`>m+jGf`TwI?$0294?%KI0Am=~wPHyR z7=F$Un2irUVGkt^FXVhbV;rmn_4RYrcG2E;nMwdexP0WcIYK}X(&YXNIKfXOR1`b> zbr9+q4Kfe>sKCz<{0q!shm}|i0-UJUV!KOsyQpw`%(90STm#?3Y^TFR5%>cP0~sEy z-(rI8CckG_==!swhaCt#xNYx9oQ$ka8MtR3U<94sRJa^)A$39osjLvFzf3aCw^`Kb z*?=#dWtA41K*p7TfW$ATk8Fkb2Rse&SBYi5?JZWp)1L zbhewj>6qrTq_Ie%i6;w0?HeW4hr3Eb@F$6@Wz=`oG<4aRv*voIRAt#6v+Uxq1mDlptwlXD=px_Evaa?v$%9iTHQ$jeY$~rg7jgvzkZBYgOe# z`caEN5oIo7N}XATF4^*NwB`MMnqSoz&iAtHAX*r(F;ZUb{`B?UrNZ2l)Ajs0>`t%S z;wds{9j@ZYG?aC6(7_zt8+J)WX_;ND0qyaUEwxK1X5dJ=>F^lnpX|q|YOTumvXX0p>0F5(u^P-3zSY&{pKm1paBkeL5grOQ z+Ene%@B7h3E1yu|IdpYWlEwgMx@uLh%Eo|98(joMF}d`9F?aRtZt;J}eDE8N|2o`M zEgO-F?%bPQQ~zG6lcf5#8Lgnte^_%%wo8UKJKHa*c6#F^*B7SEA&S6PPkqIdZ4Tc3*8ZHQuE4aZ@Bg^qZ;RzhH3@@O2@Ph^@tu|PK&4P4x z+=H=I2W&G=@EVytd`Qxw#1dNT3NT)%$ zG4SiR(D+*UOSmx_Ocf7Is6T>+L5Skz*I*!{o!#RWY zJ5zkk96XN!r7ps5L$l&UTKR$N|WP;Kypi17_!%Obf z6(|j)3p8~+DEd7#zD8m;<#%r_rRQqobgqX}Vu>U0AM7H(j z7C6R^Lg6J8!m5tFJhv7ok;M%rU7v8KciE$C&|?zBQL75qn_AhZT8B{ru0@5YLyw`4 zgo`6DKWC|2H~`$iafXtn!t8nZM52>4xfXs&95A#F7Cs2g>8HQ07?m0oO zV%CPD90_RVL7K!2HPEr%+yInHd|%ONi=Ifm=6bKo?k;*@{xD91nU9fg<5WCgFhtd! z?o8ekxslPt6Q5tT+%-t)PDeaoR(N8*Jm?((6Yx;8Io(krOC80W*499}V65DZ ziKSO5`$K+ly?vYvJ|=E33Xu#?vdZ~SfRowQ0ga24J-2UnZbkTaQ;EXGHfUO+gJEOg zy88qIr93NM0N{!k&!LMBl z>_?0+32+63BWxGQ2TA1IN!YsG_v4a68R%MIaS+m>{6xv4Nnq9|4(cTFxrhq|^h)BZ zMl&)&_nJlxL{Gmemr#-l9T^q0ABDqPe{necWP_w}y(9*JaO zL9cJ?zrOXD+DrK|iFDhXRm(ATytEKOL{#kc(O^>utGHe1IZXBiOi#w$r!mMvR(F|R z8Qu8o`{#WX4>@uRdGoy9taUIQ?Lix4g@i*O;hVm53ZZ0~)ndE)Am=g6nEeVXHJRzj z6nOOwY(Z4<{f3=1+1AUWR%`d&T3ncu!Yg_bfAwc02l{_<5 zmubIFw)=?j)r_Z6nY=%)F}PsN+RleFgB7~^ELY87j+3#hO-R!jU2|ojYg{V6Mqe-W zkE29+^C(0dh#VzDfe_$@LNy#XpRssBHE_Fsryd{o(3`g2_7#Y>u(-Vo{@D5agG5X| z>q5B~KkIM@1JOTRuLIhE*WAX}m7{s_t#*X17pa%yB~C-3XM`TN86{ZSVd5@(4Q3z! zoZ4B>KqX-&U1<_u`l10N4&987!@t>i&+)(#5t`s$Be9*)ER6v;Ey#8$zPXHE% zBHe4b0&ui_muwjZJn7XuLDwzef;s3q?uOeBZUGbx+%~0L;X|vsNDERyM?Qpgb)2N& zdw+5Grtqp4ap_*d+yuuPv)KBp)aX9;RJa#rZuTEQDEGs)N`kLkN1Vl2Tp}NrZOxx< zj@>f!Nm5;*EWiN}Yff!pjZ9gcB2>oK_q$wbPfDH8`^nq~%p(D3j+UyXrlP>O>c}77 za_2wy`;>Wv{EqZw3=H>XHGno9~$zhT1idS zx~UlG#lIZ1nwk(Or|`M-W|)?^VtxRm$i8!Vg7V$;rfL>Y@YP*(A0xwg*&V8?njn6i zNYdyZ0QaUXerb6g#WiP0Z#LO*JpQ;nku8zSTlh=#3P$!kyNdgzNma(wf%A>;%Nze# zUb3`_PkGbpL@NbMv`}_6Z`0LYd|c7N!QnmkfpS^8*f9gAu9A`iGuhe1DuOS4S!>mH zU)@uDiq%S>G*vk7yicpEM}74lTAl}0vkh7~`n21V!lGfMi?3tRp@Y7T>~hOY*`9vj zAwHkqv82$j;x-(B;rc-C`F50j4Xd~43@_U$Sw=wk0){VgpGo7h{; z>HlsWefR6vr znrFj}lZdgKI-6Z@TuzpH>BM50sftdhuuUcZ{o|_C;%&0)XXZIs$sJw&!8n>`j(OG% z$Wxr`e8Ijw>T9-J`AfZ`l=hR84hdOlj7#;KLIDtn1mEkT*HOL)`3zH`8TRSuHTP(% ztv*QeBFo(SRF@oomGHHoJ@^zn&%UUhtFH!C+Ql$bS=k+_Zx&SFraO@i_H5x zeCmDyF|@Q15Guq=?pZs!+UprFCR*l9K57q#*pIVM6*8aA1+ zOtj~p*Z30Kner|`3?Ck|@T`hfQUJ87W#lMkHyE&KH99AUY_Dr! zYiCK2*N7g#YdRd(sJ&FFgCA2wQKMI9cGg5^P5Q7m&s~KZE`mYBQLWeL_fn7Oex((f zk)eDh>k{NxTg~}#zxwz7*J;cye<5N&0uVv*87gd;oB~>EC6BK99&~HiH*2q{^$644 zDG~H;aVI4a9UwvLdVwiB?lT33(Za>W$VAA?%4(sQSYKY`oX}5kEgK+gd0Z*vwtVH% zc>_QmN#KC)dr!^h^P7GcQl8%%M|_7@f%O=LeAm?xL&tU2OrS{3*JYx{TxQr|XBt_= znjs}TpwF0N{o#3DJE%R{s@;qgs}#S6A!P_qA2CesF=ViGlR1&pm^vc&#P>1q;eOTf zJ^7)P;l&W?s(j;Yyi)0!)9gOz$HnutsxyQXVW*MxpFs=j%G2NB(4!tY**QH1iV=hx z1Nrl#01C`-G609nNRjf0V#*F2DOH+yLBa?tCv|-!{#rK z4c{>i@JE(XLRsh24BU;0h3)6$6omGCvwU3S&!~A3u74T@gW@Mi-n>*{&qf-0=yJKmkCk$m}c_j5p4CsnAE$gKs zPzyH!^Cw&a<$}CN4y{ZCVjA4gFy;#oYHF@l9FZVAy{nvIkN4NElSwJR1$(A~0Qx-`PTkG3?vEFh zc>b4$BbAM;r|M{x!5VGCl4o>R~-H0g47dq|cne3<6>0F=V*^>EFS{x*V2PkkvF4ByU{vP7g zBkX|-uD3a7fBv6*zdH}ZQO4u6Q4FjIjcQkHgap(3G~J;X(BKA?f4QtyX^Vs_f4$C- z#q;0X8=Ge9o?#D+SioY5kQy)%y5m9uaIFNdKs%7UG2|$b(mRT;+W!8Jvn75`bGaQ9 z{HotTS2{rU#e16Xtf*f@6>rfF$A=Xk<9ym4l^N63Cxf=qB=ITOyNAEDq&t=^K_j2StSxA=#X(DoRS>P zA_r8M)WUbr3G1DM@&oOq?9n09E!k!h;%nelmA*-~_2`e4Bss8OtQ_BG;}*#5>r@II z5ev!n3g�L^zsR_^8L{#sTDLsTD?w=3$X824Zq1%oG2A#riWPNbG5-H&f_+mx7$qnbZ{A@%Rnu3|L1`7D66zn0wX zzd2)u%(cZgTI!1E_nD{r3{k$<&Y2hlb1d|KgwipCOv5FJzPfOBPRTbGK3nYaPws2S^(bRcltVb{KTf7Rsn^69`s+X+RKM|YV!Gdb)7SIUD}n(d>% z@Z19TQfCF4XUQn#cKy*{CmHRhxmz2fPUwB0DD?Gr)AWpf<>_{3y%)EEUs|}>;?^u1v_Sh^9c$(G z!E2_ummo1<+FfaYdtPgS)ux`)kzDNkuzi^lP6=l1e?~E@ez=v4UpTTG-t2x{E?xZ9 zcisr-VFYqtU~NdsxE_tfEbVJyZU;f-<9>Vn&PU3~F zoj7nC(0)T4R>)?$`70k2_65&9bA;E;^jgHt9Q4U!p{y5+{ljOr7|fQhr(I8*AKTZfm6ertS+ewLjFFvC zC1cVoNO?T|x1v_fp+&ZnGJMaEy}z}(y1OGJlsOJL;;qZ0;Tf`deY%`73vScuBSpmW z{BC#NB&Ls!4o%c#70HcAn%>?63t^B=IFQ+&Rxo!y^R|-b!{b5y6a2@gB5%XKpNIDlxqR2)L=YL39_S4SPHDoEBa!2Hz%26a4M$u23Z)+^oQ zsVb+i(&oH&s`))nsL%h7jT97#eOk}nq}FLViHqv{Esb;Uk{n0I=DW424aMj86dccY zx9%+9eR%Q)>K+vu&M-lPG6@OS$8%&jL+Bpb?lxK}_1x5&pvqh5P{D0BKc5^13IVyH zGAt~#1o<{^bO9K2bpKu?c`-RBtzZFcT#P~lB5CZRMyd&>9{t$#7=3$o>MS>x`8{yY zfzwJ=8AH7!BLnyJ)Sd1hs?E|_vG{Aa_`&RsO-#`!R)^=uKRTM!N=^Uaq370rhf}y! zIgo#7nk#i192d(&!9^c~Ovg1q!;q$GrEYHGeCEA7D4w@NY9NI0&#uqMjd9vSbne)| z7&as}^?ef)v!m2yEL9+mt)>u&OxO)FXw8I_6IPm)dxzzQCf!7KuVu@+yU{kR*LWaM z=?_sNxBh8|($caU3iV45j65`=UyvmG--jXXt)c-XAGj z_H!H7S6&mpKR%VbqD4{-L%CN=Tf5V59&Qg_v|)!n_t>w?`}}J!TOmUThSIYJ zhMNviik=OJ7iU^YjzL*-40FdUy;f9Zk64t61~va$rG;-3#_VNTp{f?z#a z(NKfK%jHN9W`B?~hsyUGW)qg6!zN9Wpc@kM1`q!Jg;HYKLUAUV^4;txsrzHGL1Dw! zz0!QmYnuPH)FZE^5}Aktf+K6FH=%{(jL`2nL(VWaZ|(E?h);^Tw>f<{(8C1+5}NNz z>_aHObEc5S08HrX=s|H$?y@I=3lQmlUjHzzTHV*yq?Lmz@%t}Za||N#^rL>O@)~2M z1V{bQZ+eP*XYQnw>KItpHXRpk5sD-!6DN}B(;Hm~&=azNseH6IsFDVYUJ0jc2C0!1 zQ>*{CC@*dv-M2Do>ywMTCHW^^s+w7)7M*X1-W#)!)CKNKxS&9*b8DR(m)HfLZ_@mS zhD^nwgA3Xq`;s+rlqtM_86?w`tlZS*wu>+ahxr3n&b4EM<2>#L4p0}XZl`WpVNqRG zeYxXt<<*00umR$Y0}^6b1mj!AHUTR5J$8=#?X#mGvLY-atSd~Sa8Z6gg-f-2`Qy5P z-_w47{Krn!9K!p7(8nrA9B~uM)WqXMLYz5UHo{(dTFTvI@_Q zV>|oKxWM~EFf#w`GrB*o63gvsHBgh5hL(L`fr|q5E*;;$XklGvT=C_`Q@G8fcaxx? z(_GK*J(4o_<-q>5sM$syh^fTL30T}a0Yr%v&y8}rcstW3ijtrkBnTFV(3DJFd7q^z z@V{jt{CNOP4hx|oq%k<8biIw|@xDGcU}xHplVDQ}IJ^f#iD2u$9q7kvAbWQqg5I8s zcO=@~)-USwFTYvv5{AnU5Yso9xeQ3AMzsKc%kZ&VmcV(~P;XY_W@Rcs=p3%@^ukrG z?ll31QDCS;fXq|2>E}c_b6Ps0NNHC64Fft@S*PmAeaRF(>d!JbQztu2jhm-KMxcYX za$li-9*<)?;yi>gH@*V>fl^{?Vu2KlE;Ni|={($?$<}r9ur_2W6_R3vrb(rt6__h` z?@rIHM3@{TfBY3(+*+9q7J@y_5_jDYSHqwcgh{pV_dON3+c0i@fBUmDLH+!MjuXjdFC2~W z^Vsb}xxSOh?*hjG=8ma_P?8V5YFm%#$~>NLHhACdq6qLW#X-;#jC;?4%D` ziFb+%7ThRW&?W~-VnuwIkP@au_@s`X3fAW8)Miq!ra?V$?2NK?2O$hz)c(w|yT3C7 z(&B%UW`v#gqr7HaAG+g$5SC07Nt{bh($YyNvD^!ln zpO1l6Qs7(4*xZv4*!n)EKMo?-mIlVI?Yx~&Y4Xe#5%D59@wXX#-&eJhf}y59gMx`l zQeCH346meH-iqFqLU3e|Sb+~Ie--5Aag3V;unuf0>1fW0ZC_ue?e*~|F591n3S!EI z(BbGzMnwc#jn#057u`;Bekp0Vj~_ojN6;NMZSno}RRSC50$r-_A=IXWeTsTpq3&_H z-Rs7`w*!+JpL_GpRwy=v3@d)Y}hvQlAf-R}hcozvqB4M5y_XdiZ?Rh?<8k7EP6V>ipi z#-9D=5WtVNtm*!>7NPqz(coSch2cW@yt+yyW?@$hgu0cvL>BmzRC^7Ukbxh_%gW2lOq;yoUp!F> zk9vFG_~uO6pJun(X};F>fYAQ|$vR;$TJm;#F4bnwT6I<5b8gTChEpO8B`=Bog>^-eu}u`mkG4HSpXc$aQ)KNb&u9 zs``pT$=pLUujNPeQAuIt`WLvI;?*)oYmS#yf9Lf9YKGu^ zs7Uveg-Gxh6MctuQ(+q?*@e$gQPj}wKf}Te@MoV;+h;5FWne0ava}~FDN^rtP824d z24#(28~G*?gXUkKlUug4Wd?-ooSa2GS&!znhWcZpf1xpzw{0|y{S?y%F%gKK`%{xN zOLY3RehRK2%dD)Kx*VwBz z|9b5dCx`MTZP;XQ!QuoLK}$&N#9B3)m3B^Jc2TU@%~cG0!%&I^N+WG99RoM&&0fL= z;iAzSG3j_|rZxnB`=SO(toX zR_dqg+LVcjoqEO69;%!|tVTIa!Jj|#$h})Pt7v#Ypa>S- z|LZ^e{&!)cXW#tAJuj!VjT$gG9EK<2Q;9@?ClHBZkJktil}^Xw9TvlIb=u(?*Mgl7 zJ_ifgzAcF1kqv`ExGFh^1V<-iDuYh@Y$O8p4dQZ7UcGpt+7!0wq33@3$OGGVhBFCX zh3e)gWgHp-@1)WgG%EQsa{@zPg~vx5Mh7Pws`288Wt*a%dTVW2J2NOyCKKarL>7~T zgUQ`E8iP*aGwC=S8G~@d0$3P=#o>u!nSf5llW0^b9_w_wTre#i=0R7s!z>Ic8Q9FD zbs5JiO)*;^`1wyCy?@uPNYoCz=8OX}FNzNz-yt`f#=(+#VH+QR?3o>L9?5q7^{=-r z3>1AkZxs;K*@MSb4nUl)ss1)I%P%lAROye?O-#?~9PkK2U`qS~!dnx34VL!DuG9AG#-XY%;Jn18eE$%OV{ zWV*k{Kve{UgaxQ@y6KrIt@lNcCzq#dqq*kYQh34P8DVijlf9YeODs&HFv?HUS67#9 zi42v=6auznxU8UUs83^@8@gInp`{4eY$odLV%|FiloSR%=f0*oTnM%~r?{iHcSb){ zR$MjhAd<48VhDpJQJ4%ew0S&1Dw)Ed2d>z*RCnpGPd@U% zBfrQSVsS)lG$Ec$qfyY;PpEZ5rpVP3h1==yV2_A(ru|6o-0X4#VSe*;K5{Fg>WlqG5_t zGdgLb@VGn%31gh>9iG&hJf~)0=pqS+Mt9Ei%)t|Jxiq}nFxfjFzV7f4b#~D=s>KWB zQZ56`4GoUl>3&iX6OIOfak6J%bWGHjCMUka_+}8J|H!Op|>>s8kp%9Gqe4VkzJ2>|$_iM(<|Hl~NIl z@|hC!;p0ER=dHqr4Vs3~k_lkr-1&rk2p*}Wv_vsOJbH8nLoGov#*&<7^!VoPtB z)6CA!Y7EA?*~x*Xs^-3tv1vWj5;zJ0vCPfRsb{9IrH4}53|jT{)HIB;p!NvjFwYrH zFb<~j#C`rbjKvXQAv*Q7LQo3^v75B&SyT>~Lu0g|p0%(zf;eKumgFcAi|jdx!JyO3 zs%O8Y66j8-04XpaBXEMzk!4ML%aS+Pp=ev33`5S)jDQFyQPXOC* zdZ?|oxqEPA$^cK~t)CAcH}Wmkj>9{U!dHbv#e@gRgd}xc>0I#gXcdqCjaGLf4(n)3 zK^rGJpt5349PcOPSi7@(X)BXL1VmF+>T7Sj`Tn7^IoVnLn2-Q}DIGU{_3)>u7f&9& zkXbQHn7Wc(c4*z-%Q9G1?U56%~tr=7ZR>gb`fJs?Qsui%obn)-|n-g)!& z{b#Z-rggi6{QTq`?C|Bo?|$(9{uAk!GA<0!7lg@p_{ox!@4frZfiszxdK^kWKLr9&}4}Xy z=?EnH?3It+eB<@~r*pC{b~*!8{&FUvFaKmw@%aO1&L25)s>{NjYt2jD_xE>mHG%-8 zT+Glk7PJlwmtQVDe`MdOl4-umPb#F26z>1vowp99=4NMSmXFi@lnP>h_OV0n{o_*e zP(`~_5$5M7^e#H{;RD6HcKq9D94wYZXHtncj7>LClb4~9ZC)NNLm&Cy0QkfoY0PcG zuDWj-y}VJ)Qt${soIBn|TfBAmmgSt* zPn$t7pJi`3_x2ygl74Z|?YD+n&wbcHULFx3WP0_1+c{j5o%ZQ;^t1Qhb9=ZcV}A>X z4-SUCU#g5)xM<0u#CXQQ@k`T^hybO4I9GS}OrIz=LMfrDYtlb{tvm9Ghws00WvI;2 zaUyj{5F6~rG1i<+oAggk2$j%*#cZ)b!_8&uFFXCwFS^5Cc=Ul=Bb-?W8j*OF3fFS( z-BSZWw>|y%y|*Nh8_r}6l0`B{+ldlgc*?C0-FNHet&78D9Nc8(nNy9V#7(<*tP}R1 zYqE-jT+5XM$9omK9(w4GnKjd^TcDv>e}+ zdZ^MQW9b?)vim~seCpA=x5ttjPp0?r!sJ3zN9uN0x-02J1BmBs)$Osuu zYv-@L`|*1>Cn>p9qR-QXkI(o|y;Zo3Go57_7i`OSELKRF^O4^K#O(qEu5|gPwfy?( zx}N?S3=ZdZ*=I+ZnM<}MCoPN!iCnyWeUi4ReL$mgk^ir~^MH@qNdNw-tXA85bML)S zY*S1R9a2b0=siGszm&V&U3&Sa5lZNU-f;npjg2uKj43wQ2KU~#_bR=kU6bVQpBzap z$$Rg6`X--bJQ~eBGb=rMM$-I*s~&&wfq{clgB7xX;6S-VstgW~j8I8Hh8j&Goc&z4*%F^=EV_ExmZQZyzyXbORv(937W6XhXESWrXY)1U(5gB5)$K||t zw*iFzFzk(9I%{IasPy;;MyIJr(m~A~tPK~B@87s)!{%+rFPtmyayVTC17g5TCYZxe zClidKikXj27@R&JC_tr3ADV_YHZ-)iHP_xYgr>!+kjA55y|HBOqNRV?TUOska9GKI zTRbI$vNJPH4U;yjtEZ_N8#FF+$cUuqm?4jkPbcad8?`KHG}KVM>q4c*g-b*&=<0Q0P~_z4nS+NUr^JWw z;>M57NEnt(;;fK;=Wxw)TMtT?sCXN|D zB1L!O>b07?HcG;QAPAhzX8H_+aSj`m@_7itgcvM^gojdLx__Oue@QpxE+B>|SS@4% zr-8Hi92{dXJTA8*c*25ZPrvs18=vkh`fT2iNEzyN2}1%zIE;E(Y#x^liv19n6a^}U zEClwjIb1H6O}bp2myhhKAO_A@`T8p>7EGHG!39SA5b72O16RuUOsXyz79>v?tI#&q z-z>SL35X7n%R*x_XFmPc_y7L(%JH!QA|~M$1gV7}HuM?5VS(DbUV=q^_?)T^#@RR` z;)^({%-Ju#{qB2d#*>@>JpY3um2JIVhBt8h!llo=`s!;RZ$19y;!#n2r^V&dHl_B% zahAUdKM=}~NlCJGv~=FMil}0TPfGDx?AI>bUK%knesjh9Q@91r~aH;Hii-{8c>IXpuaRZ_>jd@vT>^w}B z9Aav|QI>Csi>8z=#DU#cPF2+1>DG1Eoj-ro#8pT|0)+5*`qefF(m&F^-uXL9kkFk| zXDe&&bZF|&o+<4E9+TtlsyCwQpwNimK&I2E>CyV0WWgu`Im~*k&Psm_g62p0(B`08)poHuXY(ii{UGxqd)AgiuZRwJCrBAQnSete~;;W~J$lzBg~~+yyIMxD~zV z!69)-#m*03oHujMb8mmS?KuD8sVTv!DZ{5t6kJ~Q-0a7nm^JIgjb|G4iZQbuP0;Lr zW7(1=@0|4pBqoG%fACWxC@(f8$=;}o3KJ^=!o!2@6<6g^k&=KAj(X_Qc^R4`f15vN z&f;fYE013E*wEN8K8din{Yn>1mQSDq)CMzgztRO0=M0R|C3A zC`gQe%YZm6R@IUHx5ck)JawnLU(b++<_G+Wwc~`>rt9d? zn4KOfS|A|^<|@L~B9=?n(&OT)l`j|MuD&jC1gr&EoOV1Dn2THjZ zZ0T+4)L5Ke;Ei~SP>{uh9QrPeha(mWxfnPtNWx+0Z0|8TAc0cFC5K0YkKAyBCHyTpoiXRf@S>)NSbMb#cWaFdOkFr9uJ6f760+YEQ3yC5Q3Y^7+$J zmH#jbgx9L;FhigT0UPmJwZ60nq-}jIof@Of10o)tJWMU+!KSv>UZc%T321VV2XrIe4DJKA~-78i*LWCDW0mI=5#Fp~X{gjLhpZ9>FKrHIEcb+-4q zIBJEA&jB5_X_`Cxe3~6FR~`}|=d&QIrrpfo3q&FgVeIMzy_ZOYY|_}>=fwB|A!=zi zx=5SV?Ql3qOdJ@f5c4olO^dG@1`MX5Fb9O9z>Ry1T^&6-vzMm|RLW@4Qkow?t5y8w zcs<=$zWwC5_o4ZZjZKu_i=b#|XaKZY#cvLbh!dv2{KCw%ND1w3XlQ;6+T;9&KoCo; z4poEM#Qmq0G&KK}&>n||hUSms|9Ou?5-yv;V*gFG6#DD*tw#EP5Vy@}u~7@1ekXqK z`f2`u=GS=~((5!+>;5zvt&Um@_^S>+(rq{CskNC}gY}o4+$3nCJqN3G(YA-vY!n&v*c>%Mgo9_QWs zH6Lust!n>I5wCzDySBNd$727z#4AXfrWMrhxUY-#x_e9aY&dYfwv&D{hvpu?VkM{7 zVQ)B-uZw?V&g>ZzBHfp^*9gWYhs*ijy$kX)AYA5K$JQL_8Mx?~Wsgq}>}nuFN4E%=e<8*KX7s3`Ud1>H%3O0(tEwlg(~6nJqRC%tT3t!RDkSFiarF1F@L*UM~bE zmB-n6`&0uQA0iVlJ)lm8uTF;3qBl^+Su7T-gG4ZD7Y4-Q@YSJFA!-?y#jqRAHXCTd zYI8G4rx~2|FhUjbI*bPDjT^N89YgBGrTXG@SN5JOYl z)ZBJH??m>&-8l_VR76;y2({Fe?A@?o^e9hG*~6$_|FJ%iguJ2S2T27%rD3}xbq05ED;tUWY!mN-n{;s z-A9X073E#(;)ewXtAwb>P+4%gg{24xkl}q7wr|TmesJH3lb8F1Xw|`!cicjCh>GWJ zFZp&|Nv~8L5+L#~=OR7!n!MFn=dR}-&O5k!dsZz;nJvMrebo)O^S%fK#uOx-u>6CY&Ng^+_rUV z{%6a9q7%7Q?kF(QBCPS`p3~<~Y|A>fcjti`hs0Q!e`LpJ>kGA#i0}}VP*+)UyQb!H z$%SKkw(PmkBLwX$`5kArZQihESI+68qP+9%yl}M~ZY$oubHl2FipCptL{Lm-RaGxjFA~h@j=W^i+cKs zr87sx%Z!yp$9C%my|QTbgHiU}Z>#Z)@BmMF*4_#vdCcT->Ac(9E0IK%1ZyeW_*HZ2 zQ;X)$92q0Q+t2LyX0;~u={d6=kF#fOtiaNvBLn?{Il$wzUO$<=T{Gb2MROmHb>?lm zg=WM?2L>u7%Fv|b)Rg2Tc4KylUKJk^E@vCA9m;N!Cd5Vr@(g8Jo7Xo+FIu>0*2tJ3 zZ*|tuc0odPxZGO0=Sa6IZ9tqFcUw##03u1RtNHxC4ezxkKR4%z#}kQTn{Q&n|0s+P27-hcSZL39(CG73+6vIZTirJAQ94Ax_8fYG}hFz)pr|uv~DIBqZTnyt`0#l6h%1_ zb)Z_I2ndagiBd~>9Ml!NV$qbLqlYAp9W_+W@K`PFx31i{lyl_3p6#2q`Ski>CLu78!Y5###CX9IFo?19{*qDqYP^S`1 zk(^!vwlrNiws-xSjhnyCJD+!>lM=6>z7LEteT%Gsfe0dh}u@l3yunx!p>@B z(1cOL#tn#196odUaHy%aw!PQMBx9yNI%?SH!2^=QSTU2Q3`-w3X4KF`!l*Hr?GQ|a zkDD-T$cWU~;j^cXk-AJ;y%BP?T`fGg;p+{Xw;nr_bFR(lX7H5(;Ac{YL`6rdB|KWZ zf`;Ok9|j~rxU6Pa92gQ78Jn0sGF4o4?q;*mcwZIVA3}m@g~`iNhQ*AY^U|BM(ls|L z8?-tn;=6A_6@U>Y6OoGr7{(;LHgNco42W>r?UazGZ%wm*#kTKXh{01xc_=7=u{b!3 zN!jRiM?CV(OK*Mf@h9sKomsnNc(fcPJWPp@#KEZR{d$l;7|$Xb#W9j9*Y4JOpJ}=RBfP3z@t*D zD19SiBSTJ(b9b@pP&q^5Vf}y0-)lr&#pxl$b;H^(T zc=NeAONI!UBz2_tmivEi3JuLK_c(;pQd{`t_M=yBHTHG3cGh)tlWKt!Lt(d73$B1a z>%|z->+HFC@W{#1^7>vY!{vt9YN?DH5E|BT{#^N;>ei0d%EFQ|L?{KvKNDx&zFyi~ zd$;B8r3d2DJtgLGj`mRKSY$z&oP3xmBL z>LUa9j(yQCz4(DZm8kh?w}qg!o9cT!1tD>KRlU{{27*CsKs< zT|0B5s;afCwd%};G8DpsUR`|Vs*xp;iG>ibtzWT%AgE#UCw(2I5^!zF`HHIB&D~We&tL5(SbUzlx7CUW zBr>@`;MDhYclZVef`QYt>3X`lO*ZOP1{#`Q;c-A5q6i6VIrPEvOBO7C`sEMm(*N@0 zlmsPisVrFc;f~@K?az434WS4>B*<8>_RU4}7q5JEofgb3Qe#rbESZ(in)ShwMT?$# z?c?4NPfr?{5Fv@2JafGD#0M`v|IFu?P11-kDFQ5zMkLB>_PqP*M+Zuq%`BzFFBt(r z7+1t+v)F?02OgfnzVyv2^XJZAwD`|^N}IGEut~zle2NU9ASfxM0v`CgG>ESJ#!;J@ zT#=CNd$0t-U@KK8D`L{5Oz*{2&n;j4vDgYsDK5%GYV&zMU`8bvI0kZ=V z;`m*cbukHHawX57=?9#_7)JyQ@I?xg0U$KO_<=)aJw8}-?4zZNm#lc{?V7l`52Z(= z)d#-*^U}GCUj1;x{!@|}qrrqLdFVq^gr#d=oj-ftqQ!6SxYS~lkDfL?#gOy&r=MK@ z(G{j_z`zLp4{n+Omy<9k+1ca{Q;C!TVKI@e+c#7}YFS_~t{S#@?htp$7mF7yTK>YD z)$z-w4NVQ>!7Q$jgCGdl73a!8{78)r!r}>iy<>8CDRIU#->!aX(ef9MVPnT;42(z` zHDwg*_Qn@iEL!$*Q8P;xq@)5Sab#kk=GZ&Wy}h-hrk8$1P6PNgYsX2_WzqEMsm}m_ zV1q4E$%Gg}dae3igBz!04*UXs{~D688+x=Rhnt|3+Qmw6p=Lr1hrU~Dw!6Uf9^|Pd z0*ncf42Q8xV{&*1l+9&%V764i!BN7jr9MOeaYSOAVfDa#E{}~-Prx;HNWcSWf-=$K z@PO$P%9Y53T-2pEc-R6ihXv{+35T)Q1`GIH4h9o;D$VCn@-_Etqw3-DcpMD6mj)%> z#vZNF<^mS8a2#R@MLZlwJr=Fe!{UPWsieWf;(?@JEzo1_J-6w#9Ay5}bH~Ta{$Wsn z0j5411RA9b_&$v|{B2MpfD08;Ase#x^-`}$Qi_8RE>cJYU=NQ~+ov}>C`AjDBbJE; zY}lp)Id(UR@`N0cf%7>y`+L<8Fo>oeoek!RfJcUGU}(H7u}J9au+!M9HB#S(0o4mt zQX!5(4kNI~*JZ-0)7oKyfX~55tIp_Vvbj9OuCclahr{i5xk)BZA_dK(3>PrmWOH~x zEi4?gFXXW~Ot8BKSViC>saWI_8lm~Gg;uNhy&)X!S9X4p4?VWvvCJ5HH95_HIkZ~E z?+t{>;-|m(^o-OHA?f7Hx6PKy4yR+R-M@Nsr_B*R$ z0t+o#(=V}*hKA;TY86gXpGNBwh;k527WP|eDv^ZUtkqdP{en{e0O7V*W`C5|tyZa2 zegz2%5@Z`q4icfXrT$?LyT1MMkuTP5*_oAp=2TH(VL_>ut&l5~_rBHn+e5mnck)*k zv`Q2KY7whH^CuuZcB9cr!0`WdO)8f~r`2lpCbOMDFec_-GU~SIK(5|sHk)k(>em?q znV_H!WZJz5hV_^9K{yOLo!;u9M7Qp(=b)jvKlfLwuxr~c?0NU~r{^wQvizyPZa>@b zGe7YAvmjj7yQemNyt}wf`y;iIgr&RYpR`0tkE8R#wspBTJN)0czK7SQy`H;zLzQak z6VJW&{K}`ETCwu^5%$70JC6KbTE74gg;{#*oBMu7j6-;OFYQ>DeYvjh_t63U**J9- z`Rm_(e%8W;v!7j^ceBL|@<@l}X6}Y}o}0N~@v_BppZ&VvW~+rk+70D-YybZItOW}f zytw*AMXTkzpTT2oIlKF_mu9?k@J93fg^p=x{+XYQaopyfOZz|lvL@o0=U-Vdd&&q| z%c(YDRDfK>bQpZ1m^QoJ!(dW+o(z}OXm>fSW~;?&wYx|v1o1jd78fPG07Jl2fbAY| znRMH&b{i;Wb$DTvi3y~E(NU2BG7$@t zq*c>+<#1M`B#}}m#bLx3zmQH#+quJKyy=ri;2njB4<0&sxw1!;Ien0#>1rz;6A~il z`NRo*;+w!Eup7c~TTLdX7o`jZlOyU=!3aXyP4_JDK$NnlPkhtmw1Yg0#Rl>ipo?Y` zi1tv7g^(5ZmJ(9sur8gNgyaN+oo?S&C0IhLX} z07;jn;mq!hwFyh-PaPH~7fI#8QL!OP8PnEv>Ao4pT5oh{sDjI=I{fb5 zlIuD9a`x`rd8kyc4wG{<=htk!LPmrKOPIFWqAj1DHHE7fHHWwFKC)|j*0CGTsCYHs zTX*b84IUAq7LvLv2fz5AFTUD-IQQV5!}-)MaIMOd5E-JB zP}4Fnv+2ID^_vS$xmqECDvx~hey%nyGE|CnpI*Q9I;!B24TY;e|NQf92l8|FSG%G@ zLR11~P0sp);-j1PXYJm;v&t1{xR$+l)5mKIw5r6|AQ7vt;?&KCw)4kN9Nx8M*SQ{1 zRAjJ>+j?r#hP7XB&n_y+&nju-hpJ?ZhTJXRZrOCAyy zeOINK*H@8uaMMR$9_y8ZMwR@wqV?Z=vt~zDQ9(iW=_YPOaG+Ac`lksG1VLO`NK|ZW zc%WR$>bY5A4o->ozFj99DFtVN_DG;BWXH&txl&m z+I#MlHY$Qt94@43yLQ5uxNyZ=FHeb6@fg;=#_D#X%jtCVm7gu=j9U5bC!a3Pnf9k$KgO(h@q>= zhy}$Y42X=13R4K#91fdWTj0~50YDIg%OYw|-)QXVF=<-5wQa>!9XgG^r?%{JkHOo1 zDev$(*MNl|eg4%aZ_H+%-F@szWw+U^sXBZK9yWdHU!NS-ap0Zp-HI_w-0 zub!&B?k*#9-jcul&s%d<<=F-0ZCX!Q#*|0qJ-v9|oLNr{XI;uYTiNc4m^fqd#F?|7 zeCds6ro<4pa|@b~_$hPdK9*_E&AHlK*XK0%Rb=->&wBFDe|mUugeqe2htJ|L-`8hUUKf^ie~Q#6R731#YOj zv0OZKQfB(ll!TPb#~v7{t*&d-n+S|GaN>j^>FKFSNfR=Y`}!PiBqD7XR$E!$)7Esi z(j1-^8zALlFeHCy%DA-j0bxo$mjyE**e5|ll3p;;VsiN+u|yyc1%?MHgd808M9-T! zVfeUVNe_%l6F360R$tk+wJAc6sWDIqqR}f*QYhx-7c5! z4hwXzgDA#MNE?LoY5FwH^=*uy(-I9`hQ8LO_THG}7?Im_mzDZJ=FrTP_@v=8A4qf6 z)zxeC9wsAZ+@#DQBL)ph3_-&l7(H~zs8J&`qD{RTi^&FNXEEa@3`rZ3k}~wcDVdO7 zV>Fu?)|TrMcRAcHF9Sh5krSq7rVk%DATfj; zJ7($THIcWvFh_h9jr;_F>@C)nl3A#B>ZvpI)!>0^e(#>yb4 zi;~{z-v*8b3hEaDY0_b;znGWX7yrPpxNr%>)p0HV;0?P}&Su(7I-Sv=)fpZ1m#Aod zDL>(HU>K1qgY@04I&;6k7U8m(Z7!DwV*0+M0**+S$zmdMk%)<63=+kdKKJAS`?}pE z&JiU|oFr?yU0Z(of+-?4L?Y%u1Ovh-B^Tei#(u>;@Lw20cw7-ty8g}AUwr}BB`9P??3`cy3ad-Ve$h)||(tGjDdB#dI;-S^=OPd@$Zdwb5_ zD!X>ahT{mhIQN61yyPK+w9O4&<=1&psUxN)n+^8s7pqmtu_~zyp}xZ6e=_5SNi7Nm$!UuPi(Pb6Sb0%9<*L;{is%3|S6)a!7xUpTm{5+DD}hig}T z@W$dLgM=8R#QGz+Et8`eD-_9W^Kbg&2fUqjlCEUiZPP%NPB5UG9NRpRPJw(x~}?HZ%>*&)_FK4#dO* z;|CAc9Nbq}ezUf#t+D1>{>Gde-HWR7m0Wwg9mXMN={@~%cBCNA~q#14BW^)QBDBmLBJm%tFOlrH1hFfE1z2a z+~SE*G9E&eBK)eLR5`z@CvSV^fzKutO*VNl8vpV#nsJie6Afl&2%J&arVMK`J{8yYJLPhEC$1U$A&+e4yk zoW&%Gj`}-|ty&Lt?gO(Sb6+Qzim^hH2S65$-D;tN2^7aM6opApH`R)7M9hXR7~^3W zMtHlLYa3fMzG$U?2z*`(j0-gyRAEXC)k_%lJrKrWu`trx*;v!mtOIVJWDs_Jug+w5 zei!Yy&DW2vd2fGJ$k5>_BGS;QG1xrf*whT-=GklI*XrAvZxs|=G^(SbWL(?ztS|nu zr&gXeJWYYRA;y8-%Lx0POmq-MbGQIbL+?oGazgaTx=WquAXi4(|H)U~b8oi<}9s%z7X>fYWvT zSUUk#ojG^>$l){m>2J**n;b4dVHVnRabHe%%44&}r3CU}kE!ieohLLkHdM|9VUE{j z>Zq#qgr~&>F!kld`6buyHn%q1DZfxy14l)uFl~7+e{gbCfQaett-07EOizgnkm9=A zc?Wmr+(u#&!vZB7%3lIYC4mv~?E0*%f>Rf7)>PfPRbJK}Fm2_$=|f{={usm$%s>R* zuJh;H6q!>-CxwZO*9y);@zW+`#7U)!Addb__U_I5b59o)A){ZKJuxkeudlt(%^#E$ z86fbqR+JeO!v@9#2t92TcU@rvB2|d4!i>7hPo2r#f1p5OweR)MEZh9h+Pam$sw-P;c2o+{GAA^e1qW0RC(cUx%}D=|JQNaAU`byLS5oEje_CEF|R+Eq!h z$paMjD}@KP?#nq{XhMS6aihm1s$`bZZJ+1$1_glTDX&Wqx|=7nvUcpQFq-Qw7oW(< zZsVoIM8(A?3d~ne>|L`v`|PO#&xk)QoSG2@R~H^SwC`}W-dKCJppU>$i}QJ0oYLU+{cZ4a_!x0F`|VX%B;&>mi;yskl?9uP!{1#vIs!~y`b9Zy zh8{a2O1{{rz)9<%>;FJ+YkYd%f-BAO`n4)67abgVb&;4YP$}9*gC*^jaC92p6QZS_drP3%D%8q6Is0c{~nb z1iy*R0c^){HUsQ}6Sf<>-^gVwc=C}^F)H6Mk#0(;)$;udL@a?^D&pg))6k@qA~-BE8W0s35g?{dlYbKk#`rP`pB_h=|2XJF zl7@!nkAwC&G&D4S9JI%wp`rQX==V7EA{iPQn%@=3_vQ-qtSUP@d(4*W? zG&D57EycyfY&IL}>gqav{J2)D#c|yCracV}4b5+l)oRVm%!G70o!M-5yXhr)G&D57 fBVbm=;c)&B6yjse4^_Sm00000NkvXXu0mjfPx0$q literal 0 HcmV?d00001 From bbdbdcaa87d613eaa18272d68516fbc2894c9c43 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 23 Oct 2024 05:56:37 +0100 Subject: [PATCH 234/330] Reverse order of compilers in Snippets Editor Order of compilers on the Compile Results tab was reversed. Fixes #135 --- Src/UCompileResultsLBMgr.pas | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Src/UCompileResultsLBMgr.pas b/Src/UCompileResultsLBMgr.pas index 6d8fd29bd..c94b7fcab 100644 --- a/Src/UCompileResultsLBMgr.pas +++ b/Src/UCompileResultsLBMgr.pas @@ -3,7 +3,7 @@ * 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) 2009-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2009-2024, Peter Johnson (gravatar.com/delphidabbler). * * Defines classes that manages display and interaction with a list box that * displays compiler results. @@ -600,13 +600,18 @@ procedure TCompileResultsLBMgr.PopulateListBox; 'unknown'. } var - Compiler: ICompiler; // each supported compiler + Compiler: ICompiler; + CompilerId: TCompilerID; begin - for Compiler in fCompilers do + // Populate list box in reverse order of compiler ID + for CompilerId := High(TCompilerID) downto Low(TCompilerID) do + begin + Compiler := fCompilers[CompilerId]; fLB.Items.AddObject( Compiler.GetName, TCompilerInfo.Create(Compiler.GetID, crQuery) ); + end; end; procedure TCompileResultsLBMgr.SetCompileResult(const Index: Integer; From 1ab44ec348e90be5dbf8a5360677263240279333 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 23 Oct 2024 06:10:09 +0100 Subject: [PATCH 235/330] Fix bug in TFileIO.CheckBOM (TStream overload) Method was returning True for encodings with zero length BOMs instead of the False result per documentation. Fixes #139 --- Src/UIOUtils.pas | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Src/UIOUtils.pas b/Src/UIOUtils.pas index 88beb3afa..09b02879d 100644 --- a/Src/UIOUtils.pas +++ b/Src/UIOUtils.pas @@ -206,6 +206,8 @@ class function TFileIO.CheckBOM(const Stream: TStream; Assert(Assigned(Stream), 'TFileIO.CheckBOM: Stream is nil'); Assert(Assigned(Encoding), 'TFileIO.CheckBOM: Encoding is nil'); Preamble := Encoding.GetPreamble; + if Length(Preamble) = 0 then + Exit(False); if Stream.Size < Length(Preamble) then Exit(False); OldPos := Stream.Position; From 2a323b6c524ca1ac106b65d3a2f5a22cfb7a6d83 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 23 Oct 2024 12:40:24 +0100 Subject: [PATCH 236/330] Update Build.html with alternate zip download link The old link to InfoZip zip.exe is http only. Added an alternative https link that gets the file from delphidabbler.com. Fixes #137 --- Build.html | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Build.html b/Build.html index ba8a9086f..5dde59363 100644 --- a/Build.html +++ b/Build.html @@ -284,11 +284,19 @@

        - This program is used to create CodeSnip's release file. - You can get a Windows command line version at + 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. + >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.

        From 5217e273f4a81760a3b1ccd80bd63ab3e037a23a Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 23 Oct 2024 13:06:47 +0100 Subject: [PATCH 237/330] Update user-db.html with links to REML documentation Fixes #134 --- Docs/Design/FileFormats/user-db.html | 50 ++++++++++++++-------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/Docs/Design/FileFormats/user-db.html b/Docs/Design/FileFormats/user-db.html index bc761983e..d8d7773f0 100644 --- a/Docs/Design/FileFormats/user-db.html +++ b/Docs/Design/FileFormats/user-db.html @@ -322,15 +322,15 @@

      1. version 6.0 to 6.10: Content is formatted text - encoded in REML markup. REML v4 is supported. + encoded in REML markup. REML v4 is supported.
      2. version 6.11 & 6.12: Content is formatted text - encoded in REML markup. REML v5 is supported. + encoded in REML markup. REML v5 is supported.
      3. version 6.13 & later: Content is formatted text - encoded in REML markup. REML v6 is supported. + encoded in REML markup. REML v6 is supported.
      4. @@ -460,26 +460,26 @@

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

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

        - REML not supported. + 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. @@ -833,8 +833,8 @@

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

        @@ -862,8 +862,8 @@

        - 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.

        @@ -875,8 +875,8 @@

        Introduced with CodeSnip v3.0.1.

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

        @@ -937,8 +937,8 @@

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

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

        @@ -950,7 +950,7 @@

        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. + 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: @@ -1028,7 +1028,7 @@

        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. + 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 @@ -1040,7 +1040,7 @@

        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. + Updated with CodeSnip v4.23.0 to add support for REML v6, which is backwards compatible with REML v4.
        @@ -1073,7 +1073,7 @@

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

        @@ -1090,7 +1090,7 @@

        • Convert the plain text snippet description read from - codesnip-data/routines/routine/description into the REML + codesnip-data/routines/routine/description into the REML equivalent of a single paragraph containing the description.
        • @@ -1100,7 +1100,7 @@

        - 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. + 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.

        From 45b06d76452947e9ff3d193f9ed857f823faec4d Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 23 Oct 2024 17:02:40 +0100 Subject: [PATCH 238/330] Omit untested compilers from generated snippet info Modified TSnippetDoc base class to filter out compilers without any test information and to use a special no tests message where no test compilations have been recorded. Modified both TTextSnippetDoc & TRTFSnippetDoc to render new "no tests" message. Also modified TRTFSnippetDoc to widen spacing between Compiler name and result in compile results table. Fixes #143 --- Src/URTFSnippetDoc.pas | 28 ++++++++++++++++++++++--- Src/USnippetDoc.pas | 45 ++++++++++++++++++++++++++++++----------- Src/UTextSnippetDoc.pas | 15 +++++++++++++- 3 files changed, 72 insertions(+), 16 deletions(-) diff --git a/Src/URTFSnippetDoc.pas b/Src/URTFSnippetDoc.pas index b4f41d63a..0fe04c353 100644 --- a/Src/URTFSnippetDoc.pas +++ b/Src/URTFSnippetDoc.pas @@ -93,10 +93,14 @@ TRTFSnippetDoc = class(TSnippetDoc) /// to document. procedure RenderTitledList(const Title: string; List: IStringList); override; - /// Adds given compiler info, preceeded by given heading, to - /// document. + /// Output given compiler test info, preceded by given heading. + /// procedure RenderCompilerInfo(const Heading: string; const Info: TCompileDocInfoArray); override; + /// Output message stating that there is no compiler test info, + /// preceded by given heading. + procedure RenderNoCompilerInfo(const Heading, NoCompileTests: string); + override; /// Interprets and adds given extra information to document. /// /// Active text formatting is observed and styled to suit @@ -341,7 +345,8 @@ procedure TRTFSnippetDoc.RenderCompilerInfo(const Heading: string; TabStop: SmallInt; // tab stop where compile result displayed begin // Calculate tab stop where compile results are displayed - TabStop := (MaxCompilerNameLenInTwips div IndentDelta) * IndentDelta + IndentDelta; + TabStop := (MaxCompilerNameLenInTwips div IndentDelta) * IndentDelta + + 2 * IndentDelta; // Display heading fBuilder.SetFontStyle([fsBold]); fBuilder.SetParaSpacing( @@ -423,6 +428,23 @@ procedure TRTFSnippetDoc.RenderHeading(const Heading: string; fBuilder.EndPara; end; +procedure TRTFSnippetDoc.RenderNoCompilerInfo(const Heading, + NoCompileTests: string); +begin + // Display heading + fBuilder.SetFontStyle([fsBold]); + fBuilder.SetParaSpacing( + TRTFParaSpacing.Create(ParaSpacing, ParaSpacing / 3) + ); + fBuilder.AddText(Heading); + fBuilder.ResetCharStyle; + fBuilder.EndPara; + fBuilder.ClearParaFormatting; + fBuilder.SetFontSize(ParaFontSize); + fBuilder.AddText(NoCompileTests); + fBuilder.EndPara; +end; + procedure TRTFSnippetDoc.RenderSourceCode(const SourceCode: string); var Renderer: IHiliteRenderer; // renders highlighted source as RTF diff --git a/Src/USnippetDoc.pas b/Src/USnippetDoc.pas index 17fbe309b..35cd8e94a 100644 --- a/Src/USnippetDoc.pas +++ b/Src/USnippetDoc.pas @@ -39,7 +39,7 @@ TCompileDocInfo = record type /// Array of textual compiler result information. - TCompileDocInfoArray = array of TCompileDocInfo; + TCompileDocInfoArray = TArray; type /// Abstract base class for classes that render documents that @@ -76,10 +76,14 @@ TSnippetDoc = class(TObject) /// title. procedure RenderTitledList(const Title: string; List: IStringList); virtual; abstract; - /// Output given compiler info, preceeded by given heading. + /// Output given compiler test info, preceded by given heading. /// procedure RenderCompilerInfo(const Heading: string; const Info: TCompileDocInfoArray); virtual; abstract; + /// Output message stating that there is no compiler test info, + /// preceded by given heading. + procedure RenderNoCompilerInfo(const Heading, NoCompileTests: string); + virtual; abstract; /// Output given extra information to document. /// Active text must be interpreted in a manner that makes sense /// for document format. @@ -109,6 +113,7 @@ implementation uses // Delphi SysUtils, + Generics.Collections, // Project Compilers.UCompilers, DB.UMain, @@ -136,17 +141,24 @@ function TSnippetDoc.CompilerInfo(const Snippet: TSnippet): var Compilers: ICompilers; // provided info about compilers Compiler: ICompiler; // each supported compiler - InfoIdx: Integer; // index into output array + ResList: TList; begin Compilers := TCompilersFactory.CreateAndLoadCompilers; SetLength(Result, Compilers.Count); - InfoIdx := 0; - for Compiler in Compilers do - begin - Result[InfoIdx] := TCompileDocInfo.Create( - Compiler.GetName, Snippet.Compatibility[Compiler.GetID] - ); - Inc(InfoIdx); + ResList := TList.Create; + try + for Compiler in Compilers do + begin + if Snippet.Compatibility[Compiler.GetID] <> crQuery then + ResList.Add( + TCompileDocInfo.Create( + Compiler.GetName, Snippet.Compatibility[Compiler.GetID] + ) + ); + end; + Result := ResList.ToArray; + finally + ResList.Free; end; end; @@ -158,7 +170,10 @@ function TSnippetDoc.Generate(const Snippet: TSnippet): TEncodedData; sUnitListTitle = 'Required units:'; sDependListTitle = 'Required snippets:'; sXRefListTitle = 'See also:'; - sCompilers = 'Supported compilers:'; + sCompilers = 'Compiler test results:'; + sNoCompilerTests = 'No compiler tests were carried out.'; +var + CompileResults: TCompileDocInfoArray; begin Assert(Assigned(Snippet), ClassName + '.Create: Snippet is nil'); // generate document @@ -176,7 +191,13 @@ function TSnippetDoc.Generate(const Snippet: TSnippet): TEncodedData; RenderTitledList(sDependListTitle, SnippetsToStrings(Snippet.Depends)); RenderTitledList(sXRefListTitle, SnippetsToStrings(Snippet.XRef)); if Snippet.Kind <> skFreeform then - RenderCompilerInfo(sCompilers, CompilerInfo(Snippet)); + begin + CompileResults := CompilerInfo(Snippet); + if Length(CompileResults) > 0 then + RenderCompilerInfo(sCompilers, CompilerInfo(Snippet)) + else + RenderNoCompilerInfo(sCompilers, sNoCompilerTests); + end; if Snippet.Extra.HasContent then RenderExtra(Snippet.Extra); if not Snippet.UserDefined then diff --git a/Src/UTextSnippetDoc.pas b/Src/UTextSnippetDoc.pas index 5b80fd32b..4ea009d9d 100644 --- a/Src/UTextSnippetDoc.pas +++ b/Src/UTextSnippetDoc.pas @@ -63,10 +63,14 @@ TTextSnippetDoc = class(TSnippetDoc) /// to document. procedure RenderTitledList(const Title: string; List: IStringList); override; - /// Adds given compiler info, preceeded by given heading, to + /// Adds given compiler info, preceded by given heading, to /// document. procedure RenderCompilerInfo(const Heading: string; const Info: TCompileDocInfoArray); override; + /// Output message stating that there is no compiler test info, + /// preceded by given heading. + procedure RenderNoCompilerInfo(const Heading, NoCompileTests: string); + override; /// Interprets and adds given extra information to document. /// /// Active text is converted to word-wrapped plain text @@ -166,6 +170,15 @@ procedure TTextSnippetDoc.RenderHeading(const Heading: string; fWriter.WriteLine(Heading); end; +procedure TTextSnippetDoc.RenderNoCompilerInfo(const Heading, + NoCompileTests: string); +begin + // Write out compilers with results + fWriter.WriteLine; + fWriter.WriteLine(Heading); + fWriter.WriteLine(NoCompileTests); +end; + procedure TTextSnippetDoc.RenderSourceCode(const SourceCode: string); begin fWriter.WriteLine; From fa71b941198b8cf1048dbc533adc9d1ecd1b81c6 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 23 Oct 2024 19:07:53 +0100 Subject: [PATCH 239/330] Update pjsysinfo library unit to v5.30.0 Fixes #144 --- Src/3rdParty/PJSysInfo.pas | 1048 +++++++++++++++++++++++++----------- 1 file changed, 726 insertions(+), 322 deletions(-) diff --git a/Src/3rdParty/PJSysInfo.pas b/Src/3rdParty/PJSysInfo.pas index 88f726505..342fd7750 100644 --- a/Src/3rdParty/PJSysInfo.pas +++ b/Src/3rdParty/PJSysInfo.pas @@ -59,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 @@ -80,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} @@ -115,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 @@ -234,107 +243,190 @@ interface // These Windows-defined constants are required for use with the // GetProductInfo API call used with Windows Vista and later + // 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 - PRODUCT_BUSINESS = $00000006; - PRODUCT_BUSINESS_N = $00000010; - PRODUCT_CLUSTER_SERVER = $00000012; - PRODUCT_CLUSTER_SERVER_V = $00000040; - PRODUCT_CORE = $00000065; - PRODUCT_CORE_COUNTRYSPECIFIC = $00000063; - PRODUCT_CORE_N = $00000062; - PRODUCT_CORE_SINGLELANGUAGE = $00000064; - PRODUCT_DATACENTER_EVALUATION_SERVER = $00000050; - PRODUCT_DATACENTER_A_SERVER_CORE = $00000091; - PRODUCT_STANDARD_A_SERVER_CORE = $00000092; - 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_EVALUATION = $00000048; - PRODUCT_ENTERPRISE_N = $0000001B; - PRODUCT_ENTERPRISE_N_EVALUATION = $00000054; - PRODUCT_ENTERPRISE_S = $0000007D; - PRODUCT_ENTERPRISE_S_EVALUATION = $00000081; - PRODUCT_ENTERPRISE_S_N = $0000007E; - PRODUCT_ENTERPRISE_S_N_EVALUATION = $00000082; - 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_ADDL = $0000003C; - PRODUCT_ESSENTIALBUSINESS_SERVER_ADDLSVC = $0000003E; - PRODUCT_ESSENTIALBUSINESS_SERVER_MGMT = $0000003B; - PRODUCT_ESSENTIALBUSINESS_SERVER_MGMTSVC = $0000003D; - 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_IOTENTERPRISE = $000000BC; - PRODUCT_IOTENTERPRISE_S = $000000BF; - PRODUCT_IOTUAP = $0000007B; - PRODUCT_IOTUAPCOMMERCIAL = $00000083; - 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_PREMIUM_SERVER = $0000004D; - PRODUCT_MULTIPOINT_STANDARD_SERVER = $0000004C; - PRODUCT_PRO_WORKSTATION = $000000A1; - PRODUCT_PRO_WORKSTATION_N = $000000A2; - 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_CORE_V = $00000028; - PRODUCT_STANDARD_SERVER_V = $00000024; - 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_ULTIMATE = $00000001; - PRODUCT_ULTIMATE_E = $00000047; - PRODUCT_ULTIMATE_N = $0000001C; - PRODUCT_UNDEFINED = $00000000; - PRODUCT_WEB_SERVER = $00000011; - PRODUCT_WEB_SERVER_CORE = $0000001D; - PRODUCT_UNLICENSED = $ABCDABCD; + // ** 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 @@ -454,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 /// Class of exception raised by code in this unit. EPJSysInfo = class(Exception); @@ -473,10 +576,13 @@ TPJOSInfo = class(TObject) /// True if suite is installed, False if not installed or not an /// NT platform OS. class function CheckSuite(const Suite: Integer): Boolean; + {$IFDEF INLINEMETHODS}inline;{$ENDIF} + + /// Gets product edition from registry for NT4 pre SP6. + class function NTEditionFromReg: string; - /// Gets product edition from registry. - /// Needed to get edition for NT4 pre SP6. - class function EditionFromReg: string; + /// Gets edition ID from registry. + class function EditionIDFromReg: string; /// Checks registry to see if NT4 Service Pack 6a is installed. /// @@ -498,6 +604,18 @@ TPJOSInfo = class(TObject) class function IsReallyWindowsVersionOrGreater(MajorVersion, MinorVersion, ServicePackMajor: Word): Boolean; + /// Checks if the operating system is Windows 10 or later, with a + /// version identifier the same or later than the given version identifier. + /// + /// + /// 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. + /// AVersion must not be one of win10plusNA or + /// win10plusUnknown. + class function IsWindows10PlusVersionOrLater( + const AVersion: TPJWin10PlusVersion): Boolean; + public /// Checks if the OS can be "spoofed" by specifying a @@ -534,17 +652,21 @@ TPJOSInfo = class(TObject) /// Checks if Windows Media Center is installed. class function IsMediaCenter: Boolean; + {$IFDEF INLINEMETHODS}inline;{$ENDIF} /// Checks if the program is running on a tablet PC OS. class function IsTabletPC: Boolean; + {$IFDEF INLINEMETHODS}inline;{$ENDIF} /// Checks if the program is running under Windows Terminal Server /// as a client session. class function IsRemoteSession: Boolean; + {$IFDEF INLINEMETHODS}inline;{$ENDIF} /// Checks of the host operating system has pen extensions /// installed. class function HasPenExtensions: Boolean; + {$IFDEF INLINEMETHODS}inline;{$ENDIF} /// Returns the host OS platform identifier. class function Platform: TPJOSPlatform; @@ -605,6 +727,9 @@ TPJOSInfo = class(TObject) /// Returns the Windows product ID of the host OS. class function ProductID: string; + /// Returns the digital product ID of the host OS. + class function DigitalProductID: TBytes; + /// Organisation to which Windows is registered, if any. class function RegisteredOrganisation: string; @@ -740,6 +865,46 @@ TPJOSInfo = class(TObject) class function IsReallyWindows10OrGreater: Boolean; {$IFDEF INLINEMETHODS}inline;{$ENDIF} + /// Returns an identifier representing a Windows 10 or 11 + /// version. + /// If the OS is earlier than Windows 10 then win10plusNA + /// is returned. If the OS is Windows 10 or later but is a dev, beta etc. + /// build whose version can't be detected then win10plusUnknown is + /// returned. + class function Windows10PlusVersion: TPJWin10PlusVersion; + + /// Returns the version name of a the current operating system, if + /// it is Windows 10 or later. + /// + /// NOTE: some Windows 10 and 11 versions have the same string. + /// + /// 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. + /// + /// + class function Windows10PlusVersionName: string; + + /// Checks if the operating system is Windows 10 or later, with a + /// version identifier the same or later than AVersion. + /// + /// AVersion must be a valid Windows 10 version + /// identifier, with a name that begins with win10v. + /// EPJSysInfo raised if AVersion is not a valid + /// Windows 10 version identifier. + class function IsWindows10VersionOrLater( + const AVersion: TPJWin10PlusVersion): Boolean; + + /// Checks if the operating system is Windows 11 or later, with a + /// version identifier the same or later than AVersion. + /// + /// AVersion must be a valid Windows 11 version + /// identifier, with a name that begins with win11v. + /// EPJSysInfo raised if AVersion is not a valid + /// Windows 11 version identifier. + class function IsWindows11VersionOrLater( + const AVersion: TPJWin10PlusVersion): Boolean; + /// Checks if the OS is a server version. /// /// For Windows 2000 and later the result always relates to the @@ -758,6 +923,12 @@ TPJOSInfo = class(TObject) /// that this value could be spoofed. /// class function RevisionNumber: Integer; + + /// Returns the repository branch from which the OS release was] + /// built. + /// Returns the empty string if no build branch information is + /// available. + class function BuildBranch: string; end; type @@ -810,6 +981,7 @@ TPJComputerInfo = class(TObject) /// Checks if a network is present on host computer. class function IsNetworkPresent: Boolean; + {$IFDEF INLINEMETHODS}inline;{$ENDIF} /// Returns the OS mode used when host computer was last booted. /// @@ -984,6 +1156,7 @@ implementation 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'; @@ -994,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 - // https://msdn.microsoft.com/en-us/library/ms724358 - cProductMap: array[1..99] 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 = ( @@ -1200,6 +1374,22 @@ implementation 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';) ); @@ -1220,8 +1410,11 @@ TBuildNameMap = record LoRev: Integer; HiRev: Integer; Name: string; + Version: Word; end; + TWin10PlusVersionSet = set of TPJWin10PlusVersion; + const { Known windows build numbers. @@ -1265,14 +1458,14 @@ TBuildNameMap = record // Windows Vista ------------------------------------------------------------- - WinVistaBaseBuild = 6000; + WinVista_Base_Build = 6000; // Windows 7 ----------------------------------------------------------------- - Win7BaseBuild = 7600; + Win7_Base_Build = 7600; // Windows 8 ----------------------------------------------------------------- - Win8Build = 9200; // Build number used for all Win 8/Svr 2012 - Win8Point1Build = 9600; // Build number used for all Win 8.1/Svr 2012 R2 + 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 ---------------------------------------------------------------- @@ -1364,7 +1557,7 @@ TBuildNameMap = record ); { - End of support information for Windows 10 versions (as of 2023-05-01). + End of support information for Windows 10 versions (as of 2024-10-01). GAC = General Availablity Channel. LTSC = Long Term Support Channel. @@ -1382,51 +1575,63 @@ TBuildNameMap = record 2004 | ended | N/a 20H2 | ended | N/a 21H1 | ended | N/a - 21H2 | 2024-06-11 | 2032-01-13 + 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. - Win10BuildMap: array[0..10] of TBuildNameMap = ( - (Build: 10240; LoRev: 16484; HiRev: MaxInt; - Name: 'Version 1507'), - (Build: 10586; LoRev: 0; HiRev: 1540; - Name: 'Version 1511: November Update'), - (Build: 14393; LoRev: 0; HiRev: MaxInt; - Name: 'Version 1607: Anniversary Update'), - (Build: 15063; LoRev: 0; HiRev: 2679; - Name: 'Version 1703: Creators Update'), - (Build: 16299; LoRev: 15; HiRev: 2166; - Name: 'Version 1709: Fall Creators Update'), - (Build: 17134; LoRev: 1; HiRev: 2208; - Name: 'Version 1803: April 2018 Update'), - (Build: 17763; LoRev: 1; HiRev: MaxInt; - Name: 'Version 1809: October 2018 Update'), - (Build: Win10_19XX_Shared_Build; LoRev: 116; HiRev: 1256; - Name: 'Version 1903: May 2019 Update'), - (Build: 18363; LoRev: 327; HiRev: 2274; - Name: 'Version 1909: November 2019 Update'), - (Build: 19041; LoRev: 264; HiRev: 1415; - Name: 'Version 2004: May 2020 Update'), - (Build: 19042; LoRev: 572; HiRev: 2965; - Name: 'Version 20H2: October 2020 Update') + 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)) ); - // Additional information is available for Win 10 builds from version 21H1, - // as follows: - - // Windows 10 version 21H1 - see **REF3** in implementation for details - Win1021H1Build = 19043; // ** End of service 2022-12-13, rev 2364 - - // Windows 10 version 21H2 - see **REF4** in implementation for details - Win1021H2Build = 19044; - - // Windows 10 version 22H2 - see **REF5** in implementation for details - Win1022H2Build = 19045; + // 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 @@ -1437,50 +1642,59 @@ TBuildNameMap = record // NOTE: All releases of Windows 11 report version 10.0 { - End of support (EOS) information for Windows 11 versions (as of 2022-12-31). + End of support (EOS) information for Windows 11 versions (as of 2024-10-01). Version | Home, Pro | Education, | etc EOS | Enterprise | | etc EOS --------|------------|------------ - 21H2 | 2023-10-10 | 2024-10-08 + 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 - Win11DevBuild = 21996; + Win11_Dev_Build = 21996; // Windows 11 version 21H2 - see **REF6** in implementation for details - Win11v21H2Build = 22000; + 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 - Win11v22H2Build = 22621; + Win11_22H2_Build = 22621; // Windows 11 version 22H3 // See **REF10** in implementation - Win11v23H2Build = 22631; + 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 - Win11Oct22ComponentBetaChannelBuild = 22622; + Win11_Oct22Component_BetaChannel_Build = 22622; // "Preview Builds of February 2023 component update in Beta Channel" // See **REF7** in implementation - Win11Feb23ComponentBetaChannelBuild = 22623; + Win11_Feb23Component_BetaChannel_Build = 22623; // "Preview builds of May 2023 component update in Beta Channel" // See **REF8** in implementation - Win11May23ComponentBetaChannelBuild = 22624; + Win11_May23Component_BetaChannel_Build = 22624; // "Preview builds of future component update in Beta Channel" // See **REF9** in implementation - Win11FutureComponentBetaChannelBuild = 22635; + 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): @@ -1492,63 +1706,61 @@ TBuildNameMap = record // 22526, 22533, 22538, 22543, 22557, 22563, // Windows 11 Dev channel releases with version string "22H2" [^2] - Win1122H2DevChannelDevBuilds: array[0..20] of Integer = ( - // expired 2022/09/15 (pre Win 11 22H2 beta release): - // 22567, 22572, 22579 - // expired 2022/09/15 (post Win 11 22H2 beta release): - // 25115, 25120, 25126, 25131, 25136, 25140, 25145, 25151, 25158, 25163, - // 25169, 25174, 25179, - // expired 2023/09/15 (post Win 11 22H2 beta release): - // 25182, 25188, 25193, 25197, 25201, 25206, 25211, - // expired 2023/09/15 (post Win 11 22H2 release): - // 25217, 25227, 25231, 25236, 25247, 25252, 25262, 25267, 25272, 25276, - // 25281, 25284, 25290, 25295, 25300, 25309, 23403, 23419, 23424, 23430, - // 23435, 23440, 23451, 23466, 23471, 23475, 23481, 23486, 23493, 23506, - // 23511, 23516, 23521, - // expiring 2024-09-15: - 23526, 23531, 23536, 23541, 23545, 23550, 23555, 23560, 23565, 23570, 23575, - 23580, 23585, 23590, 23595, 23601, 23606, 23612, 23615, 23619, 23620 - ); - - // Win 11 Dev channel releases with version string "24H2" [^2] - Win1124H2DevChannelDevBuilds: array[0..4] of Integer = ( - // expiring 2024-09-15: - 26052, 26058, 26080, 26085, 26090 - ); + // 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): + // 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] - Win11Canary23H2PreviewBuilds: array[0..15] of Integer = ( - // expired 2023-09-15: - // 25375, 25381, 25387, 25393, 25905, 25915, 25921, 25926, - // expires 2024-09-15: - 25931, 25936, 25941, 25947, 25951, 25967, 25977, 25982, 25987, 25992, 25997, - 26002, 26010, 26016, 26020, 26040 - ); - - // Preview builds of Windows 11 in the Canary Channel with version string - // "24H2" [^2] - Win11Canary24H2PreviewBuilds: array[0..5] of Integer = ( - // expires 2024-09-15: - 26052, 26058, 26063, 26080, 26085, - // expiry date unknown - 26090 - ); + // 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] - Win11DevBetaChannels22H2Builds: array[0..1] of Integer = ( + Win11_22H2_DevAndBetaChannel_Builds: array[0..1] of Integer = ( // Expired 2022/09/15: // 22581, 22593, 22598 // Unknown expiry date: 22610, 22616 ); - Win11FirstBuild = Win11DevBuild; // First build number of Windows 11 + // 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 ---------------------------------------------- @@ -1556,9 +1768,14 @@ TBuildNameMap = record // version 10.0. There's always an exception with Windows versioning! // Last build numbers of each "major" release before moving on to the next - Win2016LastBuild = 17134; - Win2019LastBuild = 18363; - WinServerLastBuild = 19042; + 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. @@ -1590,31 +1807,38 @@ TBuildNameMap = record // 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'), - (Build: 10514; LoRev: 0; HiRev: MaxInt; Name: 'Technical Preview 3'), - (Build: 10586; LoRev: 0; HiRev: MaxInt; Name: 'Technical Preview 4'), - (Build: 14300; LoRev: 0; HiRev: MaxInt; Name: 'Technical Preview 5'), - (Build: 14393; LoRev: 0; HiRev: MaxInt; Name: 'Version 1607'), - (Build: 16299; LoRev: 0; HiRev: MaxInt; Name: 'Version 1709'), - (Build: Win2016LastBuild; LoRev: 0; HiRev: MaxInt; Name: 'Version 1803'), + (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'), - (Build: 18362; LoRev: 0; HiRev: MaxInt; Name: 'Version 1903'), - (Build: Win2019LastBuild; LoRev: 0; HiRev: MaxInt; Name: 'Version 1909'), + (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'), - (Build: WinServerLastBuild; LoRev: 0; HiRev: MaxInt; Name: 'Version 20H2'), + (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') + (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! - Win2016TP1Build = 9841; + Win2016_TP1_Build = 9841; // Server 2019 Insider Preview builds: require format strings in names - Win2019IPBuilds: array[0..9] of Integer = ( + Win2019_IP_Builds: array[0..9] of Integer = ( 17623, 17627, 17666, 17692, 17709, 17713, 17723, 17733, 17738, 17744 ); @@ -1663,6 +1887,8 @@ TBuildNameMap = record // ** 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 @@ -1781,7 +2007,8 @@ function FindBuildNumberFrom(const BNs: array of Integer; var FoundBN: Integer): // 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): Boolean; + var FoundBN: Integer; var FoundExtra: string; var FoundVersion: Word): + Boolean; var I: Integer; begin @@ -1795,6 +2022,7 @@ function FindBuildNameAndExtraFrom(const Infos: array of TBuildNameMap; begin FoundBN := Infos[I].Build; FoundExtra := Infos[I].Name; + FoundVersion := Infos[I].Version; Result := True; Break; end; @@ -2031,6 +2259,33 @@ function GetRegistryInt(const RootKey: HKEY; const SubKey, Name: string): 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; @@ -2056,6 +2311,7 @@ procedure InitPlatformIdEx; GetVersionEx: TGetVersionEx; // pointer to GetVersionEx API function GetProductInfo: TGetProductInfo; // pointer to GetProductInfo API function SI: TSystemInfo; // structure from GetSystemInfo API call + VersionEx: Word; // gets extra version info (Win 10/11) // Get OS's revision number from registry. function GetOSRevisionNumber(const IsNT: Boolean): Integer; @@ -2120,24 +2376,24 @@ procedure InitPlatformIdEx; 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 := Win2016TP1Build; + InternalBuildNumber := Win2016_TP1_Build; InternalExtraUpdateInfo := 'Technical Preview 6'; end; end; @@ -2162,15 +2418,18 @@ procedure InitPlatformIdEx; and (Win32ProductType <> VER_NT_SERVER) then begin if FindBuildNameAndExtraFrom( - Win10BuildMap, InternalBuildNumber, InternalExtraUpdateInfo + Win10_BuildMap, InternalBuildNumber, InternalExtraUpdateInfo, + VersionEx ) then begin - // Nothing to do: required internal variables set in function call + InternalWin1011Version := + TPJWin10PlusVersion(VersionEx); end - else if IsBuildNumber(Win1021H1Build) then + else if IsBuildNumber(Win10_21H1_Build) then begin // **REF3** - InternalBuildNumber := Win1021H1Build; + 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, @@ -2195,19 +2454,20 @@ procedure InitPlatformIdEx; ); end; end - else if IsBuildNumber(Win1021H2Build) then + 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 := Win1021H2Build; + 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 .. MaxInt: + 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, @@ -2223,22 +2483,31 @@ procedure InitPlatformIdEx; ); end; end - else if IsBuildNumber(Win1022H2Build) then + else if IsBuildNumber(Win10_22H2_Build) then begin // **REF5** - InternalBuildNumber := Win1022H2Build; + 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 .. MaxInt: + 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: + 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]', @@ -2248,15 +2517,16 @@ procedure InitPlatformIdEx; end // Win 11 releases are reporting v10.0 // Details taken from: https://tinyurl.com/usupsz4a - else if IsBuildNumber(Win11DevBuild) then + else if IsBuildNumber(Win11_Dev_Build) then begin - InternalBuildNumber := Win11DevBuild; + InternalBuildNumber := Win11_Dev_Build; + InternalWin1011Version := win10plusUnknown; InternalExtraUpdateInfo := Format( 'Dev [Insider v10.0.%d.%d]', [InternalBuildNumber, InternalRevisionNumber] ) end - else if IsBuildNumber(Win11v21H2Build) then + else if IsBuildNumber(Win11_21H2_Build) then begin // **REF6** // There are several Win 11 releases with this build number @@ -2264,14 +2534,15 @@ procedure InitPlatformIdEx; // number. // *** Amazingly one of them, revision 194, is the 1st public // release of Win 11 -- well hidden eh?! - InternalBuildNumber := Win11v21H2Build; + 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 .. MaxInt: + 2836, 2899, 2960, 3019, 3079, 3147, 3197 .. MaxInt: // Public releases of Windows 11 InternalExtraUpdateInfo := 'Version 21H2'; 51, 65, 71: @@ -2303,15 +2574,18 @@ procedure InitPlatformIdEx; ); end; end - else if IsBuildNumber(Win11v22H2Build) then + else if IsBuildNumber(Win11_22H2_Build) then begin // **REF1** - InternalBuildNumber := Win11v22H2Build; + 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 .. MaxInt: + 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 @@ -2328,7 +2602,8 @@ procedure InitPlatformIdEx; [InternalBuildNumber, InternalRevisionNumber] ); 105, 169, 232, 317, 457, 607, 754, 898, 1192, 1343, 1483, 1631, - 1776, 2066, 2213, 2359, 2500, 2787, 3078, 3227, 3371: + 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] @@ -2349,25 +2624,32 @@ procedure InitPlatformIdEx; ); end; end - else if IsBuildNumber(Win11v23H2Build) then + else if IsBuildNumber(Win11_23H2_Build) then begin // **REF10** - InternalBuildNumber := Win11v23H2Build; + InternalBuildNumber := Win11_23H2_Build; + InternalWin1011Version := win11v23H2; case InternalRevisionNumber of - 2428, 2506, 2715, 2792, 2861, 3007, 3085, 3155, 3235 {Moment 5}, 3296, 3374 .. MaxInt: + 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: + 2361, 2787, 3078, 3227, 3371, 3520, 3668, 3807, 3951, 4108, + 4247: InternalExtraUpdateInfo := Format( 'Version 23H2 [Release Preview v10.0.%d.%d]', [InternalBuildNumber, InternalRevisionNumber] @@ -2379,77 +2661,93 @@ procedure InitPlatformIdEx; ); end; end - else if IsBuildNumber(Win11Oct22ComponentBetaChannelBuild) then + else if IsBuildNumber(Win11_24H2_Build) then begin - // **REF2** - InternalBuildNumber := Win11Oct22ComponentBetaChannelBuild; + // **REF11** + InternalBuildNumber := Win11_24H2_Build; + InternalWin1011Version := win11v24H2; case InternalRevisionNumber of - 290, 436, 440, 450, 575, 586, 590, 598, 601: + 1742, 1882 .. MaxInt: + InternalExtraUpdateInfo := 'Version 24H2'; + 560, 712, 863, 994, 1000, 1150, 1297, 1301, 1457, 1586, 1591: InternalExtraUpdateInfo := Format( - 'Version 22H2 [October Component Update v10.0.%d.%d]', + '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 22H2 [Unknown release v10.0.%d.%d]', + 'Version 24H2 [Unknown release v10.0.%d.%d]', [InternalBuildNumber, InternalRevisionNumber] ); end; end else if FindBuildNumberFrom( - Win1122H2DevChannelDevBuilds, InternalBuildNumber + Win11_24H2_DevAndCanaryChannel_Builds, InternalBuildNumber ) then begin - // Win11 Dev Channel builds with version string "22H2" + // Win11 builds in Canary, Dev & Preview channels with version + // string "24H2" + InternalWin1011Version := win10plusUnknown; InternalExtraUpdateInfo := Format( - 'Dev Channel Version 22H2 v10.0.%d.%d', + 'Dev or Canary Channel Version 24H2 v10.0.%d.%d', [InternalBuildNumber, InternalRevisionNumber] ); end else if FindBuildNumberFrom( - Win1124H2DevChannelDevBuilds, InternalBuildNumber + Win11_24H2_CanaryChannel_Builds, InternalBuildNumber ) then begin - // Win11 Dev Channel builds with version string "22H2" + // Win11 builds in Canary channel with version string "24H2" + InternalWin1011Version := win10plusUnknown; InternalExtraUpdateInfo := Format( - 'Dev Channel Version 24H2 v10.0.%d.%d', - [InternalBuildNumber, InternalRevisionNumber] - ); - end - else if FindBuildNumberFrom( - Win11Canary23H2PreviewBuilds, InternalBuildNumber - ) then - begin - // Win11 Canary Channel builds with version string "23H2" - InternalExtraUpdateInfo := Format( - 'Canary Channel Version 23H2 v10.0.%d.%d', + 'Canary Channel Version 24H2 v10.0.%d.%d', [InternalBuildNumber, InternalRevisionNumber] ); end - else if FindBuildNumberFrom( - Win11Canary24H2PreviewBuilds, InternalBuildNumber - ) then + else if IsBuildNumber(Win11_Oct22Component_BetaChannel_Build) then begin - // Win11 Canary Channel builds with version string "24H2" - InternalExtraUpdateInfo := Format( - 'Canary Channel Version 24H2 v10.0.%d.%d', - [InternalBuildNumber, InternalRevisionNumber] - ); + // **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( - Win11DevBetaChannels22H2Builds, InternalBuildNumber + 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(Win11Feb23ComponentBetaChannelBuild) then + else if IsBuildNumber(Win11_Feb23Component_BetaChannel_Build) then begin // **REF7** - InternalBuildNumber := Win11Feb23ComponentBetaChannelBuild; + 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: @@ -2464,10 +2762,11 @@ procedure InitPlatformIdEx; ); end; end - else if IsBuildNumber(Win11May23ComponentBetaChannelBuild) then + else if IsBuildNumber(Win11_May23Component_BetaChannel_Build) then begin // **REF8** - InternalBuildNumber := Win11May23ComponentBetaChannelBuild; + InternalBuildNumber := Win11_May23Component_BetaChannel_Build; + InternalWin1011Version := win10plusUnknown; case InternalRevisionNumber of 1391, 1465, 1470, 1537, 1546, 1610, 1616, 1680, 1690, 1755 .. MaxInt: @@ -2482,14 +2781,17 @@ procedure InitPlatformIdEx; ); end; end - else if IsBuildNumber(Win11FutureComponentBetaChannelBuild) then + else if IsBuildNumber(Win11_FutureComponent_BetaChannel_Build) then begin // **REF9** - InternalBuildNumber := Win11FutureComponentBetaChannelBuild; + 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 .. MaxInt: + 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] @@ -2501,6 +2803,25 @@ procedure InitPlatformIdEx; ); 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 @@ -2512,14 +2833,14 @@ procedure InitPlatformIdEx; InternalBuildNumber, InternalExtraUpdateInfo ) then begin - // Nothing to do: required internal variables set in function call + InternalWin1011Version := win10v20H2; end else if FindWin10PreviewBuildNameAndExtraFrom( Win10_2004_Preview_Builds, '2004', InternalBuildNumber, InternalExtraUpdateInfo ) then begin - // Nothing to do: required internal variables set in function call + InternalWin1011Version := win10v2004; end else if IsBuildNumber(Win10_19XX_Shared_Build) then begin @@ -2527,57 +2848,63 @@ procedure InitPlatformIdEx; // 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 - // Nothing to do: required internal variables set in function call + InternalWin1011Version := win10v1903; end else if FindWin10PreviewBuildNameAndExtraFrom( Win10_1809_Preview_Builds, '1809', InternalBuildNumber, InternalExtraUpdateInfo ) then begin - // Nothing to do: required internal variables set in function call + InternalWin1011Version := win10v1809; end else if FindWin10PreviewBuildNameAndExtraFrom( Win10_1803_Preview_Builds, '1803', InternalBuildNumber, InternalExtraUpdateInfo ) then begin - // Nothing to do: required internal variables set in function call + InternalWin1011Version := win10v1803; end else if FindWin10PreviewBuildNameAndExtraFrom( Win10_1709_Preview_Builds, '1709', InternalBuildNumber, InternalExtraUpdateInfo ) then begin - // Nothing to do: required internal variables set in function call + InternalWin1011Version := win10v1709; end else if FindWin10PreviewBuildNameAndExtraFrom( Win10_1703_Preview_Builds, '1703', InternalBuildNumber, InternalExtraUpdateInfo ) then begin - // Nothing to do: required internal variables set in function call + InternalWin1011Version := win10v1703; end else if FindWin10PreviewBuildNameAndExtraFrom( Win10_1607_Preview_Builds, '1607', InternalBuildNumber, InternalExtraUpdateInfo ) then begin - // Nothing to do: required internal variables set in function call + InternalWin1011Version := win10v1607; end end else // Win32ProductType in [VER_NT_DOMAIN_CONTROLLER, VER_NT_SERVER] @@ -2587,13 +2914,14 @@ procedure InitPlatformIdEx; if FindBuildNameAndExtraFrom( WinServerSimpleBuildMap, InternalBuildNumber, - InternalExtraUpdateInfo + InternalExtraUpdateInfo, + VersionEx // unused ) then begin // Nothing to do: required internal variables set in function call end else if FindBuildNumberFrom( - Win2019IPBuilds, InternalBuildNumber + Win2019_IP_Builds, InternalBuildNumber ) then begin // Windows 2019 Insider preview builds require build number in @@ -2687,6 +3015,13 @@ procedure InitPlatformIdEx; { TPJOSInfo } +class function TPJOSInfo.BuildBranch: string; +begin + Result := GetRegistryString( + HKEY_LOCAL_MACHINE, CurrentVersionRegKeys[IsWinNT], 'BuildBranch' + ); +end; + class function TPJOSInfo.BuildNumber: Integer; begin Result := InternalBuildNumber; @@ -2752,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 @@ -2766,7 +3108,11 @@ class function TPJOSInfo.Edition: string; // 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)'; @@ -2870,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; @@ -2890,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; @@ -3125,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; @@ -3182,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 @@ -3303,7 +3679,7 @@ class function TPJOSInfo.Product: TPJOSProduct; 0: if not IsServer then begin - if TestBuildNumber(VER_LESS, Win11FirstBuild) then + if TestBuildNumber(VER_LESS, Win11_First_Build) then Result := osWin10 else // ** As of 2021-10-05 Win 11 is reporting version 10.0! @@ -3311,11 +3687,17 @@ class function TPJOSInfo.Product: TPJOSProduct; end else begin - if TestBuildNumber(VER_LESS_EQUAL, Win2016LastBuild) then + if TestBuildNumber( + VER_LESS_EQUAL, Win2016_Last_Build + ) then Result := osWin10Svr - else if TestBuildNumber(VER_LESS_EQUAL, Win2019LastBuild) then + else if TestBuildNumber( + VER_LESS_EQUAL, Win2019_Last_Build + ) then Result := osWinSvr2019 - else if TestBuildNumber(VER_LESS_EQUAL, WinServerLastBuild) then + else if TestBuildNumber( + VER_LESS_EQUAL, WinServer_Last_Build + ) then Result := osWinServer else Result := osWinSvr2022; @@ -3457,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; @@ -3642,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; From 82b16ca6917f6e84d4bedd879d6f1d3b31d1a90d Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 23 Oct 2024 19:37:29 +0100 Subject: [PATCH 240/330] Bump version number to v4.24.0 build 272 --- Src/VersionInfo.vi-inc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Src/VersionInfo.vi-inc b/Src/VersionInfo.vi-inc index c9ae9dae3..70615f76f 100644 --- a/Src/VersionInfo.vi-inc +++ b/Src/VersionInfo.vi-inc @@ -1,8 +1,8 @@ # CodeSnip Version Information Macros for Including in .vi files # Version & build numbers -version=4.23.0 -build=271 +version=4.24.0 +build=272 # String file information copyright=Copyright © P.D.Johnson, 2005-. From 7572fdad75cd510d5229ecd9c7b3971d34337e2b Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 23 Oct 2024 20:32:52 +0100 Subject: [PATCH 241/330] Update change log with details of release v4.24.0 --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5aeb246be..456f8baa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,21 @@ 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.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]. From 7a6ef7fb397c186b021759e3e56cebbcba7a7aa9 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 23 Oct 2024 19:51:15 +0100 Subject: [PATCH 242/330] Update copyright date in header comments to 2024 --- Src/UIOUtils.pas | 2 +- Src/URTFSnippetDoc.pas | 2 +- Src/USnippetDoc.pas | 2 +- Src/UTextSnippetDoc.pas | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Src/UIOUtils.pas b/Src/UIOUtils.pas index 09b02879d..8c6ab2154 100644 --- a/Src/UIOUtils.pas +++ b/Src/UIOUtils.pas @@ -3,7 +3,7 @@ * 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) 2009-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2009-2024, Peter Johnson (gravatar.com/delphidabbler). * * Provides a container for assisting with common file operations. } diff --git a/Src/URTFSnippetDoc.pas b/Src/URTFSnippetDoc.pas index 0fe04c353..4bb6399c1 100644 --- a/Src/URTFSnippetDoc.pas +++ b/Src/URTFSnippetDoc.pas @@ -3,7 +3,7 @@ * 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) 2008-2023, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2008-2024, Peter Johnson (gravatar.com/delphidabbler). * * Implements a class that renders a document that describes a snippet as rich * text. diff --git a/Src/USnippetDoc.pas b/Src/USnippetDoc.pas index 35cd8e94a..e11245322 100644 --- a/Src/USnippetDoc.pas +++ b/Src/USnippetDoc.pas @@ -3,7 +3,7 @@ * 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) 2008-2023, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2008-2024, Peter Johnson (gravatar.com/delphidabbler). * * Implements an abstract base class that renders a text document that describes * a snippet. Should be overridden by classes that generate actual documents in diff --git a/Src/UTextSnippetDoc.pas b/Src/UTextSnippetDoc.pas index 4ea009d9d..923637950 100644 --- a/Src/UTextSnippetDoc.pas +++ b/Src/UTextSnippetDoc.pas @@ -3,7 +3,7 @@ * 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) 2009-2023, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2009-2024, Peter Johnson (gravatar.com/delphidabbler). * * Implements a class that renders a document that describes a snippet as plain * text. From 58be37c7a9a31be5abab1ec2ae323552259f44d9 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 13 Apr 2025 17:43:18 +0100 Subject: [PATCH 243/330] Fix CodeSnip crash after resume from hiberanation. It seems that the problem is that, sometimes (not always) Windows recreates the tree view displayed in the overview pane and all its nodes. The tree view nodes are custom classes that have a property that reference an `IView` instance relating to the displayed items. Unfortunately when Windows recreates the nodes the `IView` property is set to `nil`. This explains the nil `IView` references that have been causing the access violation. The solution used in the fix is to handle the Windows messages sent when the computer hibernates and resumes. On hibernation the state of the tree view is recorded. On restoration we assume that the tree view is corrupted and so forcibly rebuild it and restore the saved state. There is a problem thought. The message we handle is issued twice after resuming from hibernation. There is no easy way to tell which message has been issued. Therefore the tree view is rebuilt twice. There is not much performance penalty to this, so we can let it go. The potential problem is that if the tree view is recreated it happens after the 1st message and before the 2nd. Should, for example, the message only be triggered once then the bug will be back! Even after all this it is possible that the program will redraw the tree view before the `IView` instances are restored. I've added code to the tree node custom drawing code to test if a node's `IView` instance is nil. This leads to some nodes not being drawn correctly. However, this doesn't matter because the tree view is forcibly redrawn again after the `IView` instances are restored. All in all, I've not totally happy with this solution, which is more of a work around than a fix, but it's the best I can come up with without completely revising the overview frame code. Fixes #70 --- Src/FmMain.pas | 23 ++++++++++++++++++++++- Src/FrOverview.pas | 16 +++++++++++++--- Src/UMainDisplayMgr.pas | 29 +++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 4 deletions(-) diff --git a/Src/FmMain.pas b/Src/FmMain.pas index 725d241aa..2bce3465e 100644 --- a/Src/FmMain.pas +++ b/Src/FmMain.pas @@ -522,6 +522,11 @@ TMainForm = class(THelpAwareForm) /// Object that manages favourites. fFavouritesMgr: TFavouritesManager; + /// Handles the WM_POWERBROADCAST messages to detect and + /// respond to hibernation messages. + /// This is necessary as part of the fix for an obscure bug. See + /// https://github.com/delphidabbler/codesnip/issues/70 + procedure WMPowerBroadcast(var Msg: TMessage); message WM_POWERBROADCAST; /// Displays view item given by TViewItemAction instance /// referenced by Sender and adds to history list. procedure ActViewItemExecute(Sender: TObject); @@ -1324,7 +1329,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); @@ -1582,5 +1586,22 @@ procedure TMainForm.splitVertCanResize(Sender: TObject; Accept := False; end; +procedure TMainForm.WMPowerBroadcast(var Msg: TMessage); +begin + // Sometimes when the computer is resumed from hibernation the tree view in + // the overview frame is destroyed and recreated by Windows. Unfortunately the + // IView instances associated with the recreated tree nodes are lost. + // Attempting to read those (now nil) IView instances was resulting in an + // access violation. + case Msg.WParam of + PBT_APMSUSPEND: + // Get ready for isolation + fMainDisplayMgr.PrepareForHibernate; + PBT_APMPOWERSTATUSCHANGE: + // Restore from hibernation: ensure the IView instances are recreeated + fMainDisplayMgr.RestoreFromHibernation; + end; +end; + end. diff --git a/Src/FrOverview.pas b/Src/FrOverview.pas index e668c3007..b96e4b63f 100644 --- a/Src/FrOverview.pas +++ b/Src/FrOverview.pas @@ -86,6 +86,7 @@ TTVDraw = class(TSnippetsTVDraw) @return True if node is a section header, False if not. } end; + var fTVDraw: TTVDraw; // Object that renders tree view nodes fNotifier: INotifier; // Notifies app of user initiated events @@ -966,7 +967,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( @@ -979,8 +985,12 @@ function TOverviewFrame.TTVDraw.IsUserDefinedNode( ViewItem: IView; // view item represented by node begin ViewItem := (Node as TViewItemTreeNode).ViewItem; - // TODO -cBug: Exception reported as issue #70 seems to be triggered here - 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/UMainDisplayMgr.pas b/Src/UMainDisplayMgr.pas index e9c1f5459..3df9a37fe 100644 --- a/Src/UMainDisplayMgr.pas +++ b/Src/UMainDisplayMgr.pas @@ -291,6 +291,23 @@ TMainDisplayMgr = class(TObject) /// Prepares display ready for database to be reloaded. procedure PrepareForDBReload; + + /// Gets the overview frame prepared for program hibernation. + /// + /// Saves the overview tree view state ready for restoring after + /// hibernation. + procedure PrepareForHibernate; + + /// Restores the overview's tree view to have the correct IView + /// instances after hibernation restores the previously saved state. + /// + /// Sometimes, Windows quietly recreates the node of the tree view + /// after resuming from hibernation, without restoring the associated IView + /// instances, leading to access violations. This method should be called + /// after resuming from hibernation to recreate the tree view with the + /// correct IView instances. + procedure RestoreFromHibernation; + end; @@ -566,6 +583,12 @@ procedure TMainDisplayMgr.PrepareForDBViewChange(View: IView); fPendingViewChange := True; end; +procedure TMainDisplayMgr.PrepareForHibernate; +begin + // simply save the state of the overview tree view ready for later restoration + (fOverviewMgr as IOverviewDisplayMgr).SaveTreeState; +end; + procedure TMainDisplayMgr.RedisplayOverview; begin (fOverviewMgr as IOverviewDisplayMgr).Display(Query.Selection, True); @@ -593,6 +616,12 @@ procedure TMainDisplayMgr.ReStart; (fOverviewMgr as IOverviewDisplayMgr).Display(Query.Selection, True); end; +procedure TMainDisplayMgr.RestoreFromHibernation; +begin + (fOverviewMgr as IOverviewDisplayMgr).Display(Query.Selection, True); + (fOverviewMgr as IOverviewDisplayMgr).RestoreTreeState; +end; + procedure TMainDisplayMgr.SelectAll; begin // Only details pane supports text selection From 689c4be2014bc2b3a54206bf9559a34e0c8a1bab Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 13 Apr 2025 18:07:41 +0100 Subject: [PATCH 244/330] Bump copyright dates for 2025 --- Docs/License.html | 6 +++--- Src/FmMain.pas | 3 +-- Src/FrOverview.pas | 2 +- Src/Help/HTML/license.htm | 4 ++-- Src/Install/Assets/License.rtf | 2 +- Src/UMainDisplayMgr.pas | 2 +- 6 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Docs/License.html b/Docs/License.html index 458ab8f79..c47a44323 100644 --- a/Docs/License.html +++ b/Docs/License.html @@ -1,7 +1,7 @@  @@ -27,7 +27,7 @@

        Summary of End User License Agreement

        - DelphiDabbler CodeSnip is copyright © 2005-2024 by Peter D + DelphiDabbler CodeSnip is copyright © 2005-2025 by Peter D Johnson, Date: Sun, 13 Apr 2025 18:51:06 +0100 Subject: [PATCH 245/330] Bump version number to v4.24.1 build 273 --- Src/VersionInfo.vi-inc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Src/VersionInfo.vi-inc b/Src/VersionInfo.vi-inc index 70615f76f..513eae126 100644 --- a/Src/VersionInfo.vi-inc +++ b/Src/VersionInfo.vi-inc @@ -1,8 +1,8 @@ # CodeSnip Version Information Macros for Including in .vi files # Version & build numbers -version=4.24.0 -build=272 +version=4.24.1 +build=273 # String file information copyright=Copyright © P.D.Johnson, 2005-. From c6274e0b06a74e5bbc728b4e3e0cd99829d4f669 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 13 Apr 2025 19:11:28 +0100 Subject: [PATCH 246/330] Update change log with details of release v4.24.1 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 456f8baa6..bafd5addf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ 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.24.1 of 13 April 2005 + +* Fixed bug where CodeSnip occasionally crashes after a computer resumes from hibernation [issue #70]. +* Bumped some 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]. From ca42523866c90e8bbeff5f5ce4490a311b076cc0 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Mon, 14 Apr 2025 14:13:33 +0100 Subject: [PATCH 247/330] Revise hibernate bug fix (issue #70) Following a discussion in the comments the DelphiDabbler Blog post at https://tinyurl.com/mrp76mdy it seems that was not a good idea to rely upon handling WM_POWERBROADCAST's PBT_APMPOWERSTATUSCHANGE event to restore the overview pane's tree view nodes to the expected state after Windows has recreated the tree view in an invalid state. So I've modified the code to only rely on the PBT_APMSUSPEND event of WM_POWERBROADCAST and not PBT_APMPOWERSTATUSCHANGE. PBT_APMSUSPEND is handled to prepare for hibernation by not only saving the tree view's state (as per the previous fix) but also setting an event handler that gets called only when the tree view's window gets recreated by Windows AND the treeview contains nodes with nil IView pointers. When called, the event handler rebuilds the tree view with nodes containing valid IView references. The problem is that the event needs to be triggered from the TTreeView.CreateWnd method that gets called when Windows recreates the tree view. Since TTreeView exposes no suitable events, the only way is to inject a suitable event using a nasty hack. Not good practise. Note that all the methods that depend on the hack have been given names beginning with "_HACK_" to make it obvious where the naughtiness lies. --- Src/FmMain.pas | 10 ++++---- Src/FrOverview.pas | 52 +++++++++++++++++++++++++++++++++++++++++ Src/IntfFrameMgrs.pas | 6 ++++- Src/UMainDisplayMgr.pas | 52 ++++++++++++++++++++--------------------- 4 files changed, 87 insertions(+), 33 deletions(-) diff --git a/Src/FmMain.pas b/Src/FmMain.pas index adfd77419..8bffbe2b8 100644 --- a/Src/FmMain.pas +++ b/Src/FmMain.pas @@ -524,9 +524,10 @@ TMainForm = class(THelpAwareForm) ///

        Handles the WM_POWERBROADCAST messages to detect and /// respond to hibernation messages. - /// This is necessary as part of the fix for an obscure bug. See + /// !! HACK necessary as part of the fix for an obscure bug. See /// https://github.com/delphidabbler/codesnip/issues/70 procedure WMPowerBroadcast(var Msg: TMessage); message WM_POWERBROADCAST; + /// Displays view item given by TViewItemAction instance /// referenced by Sender and adds to history list. procedure ActViewItemExecute(Sender: TObject); @@ -1587,6 +1588,7 @@ procedure TMainForm.splitVertCanResize(Sender: TObject; procedure TMainForm.WMPowerBroadcast(var Msg: TMessage); begin + // !! HACK // Sometimes when the computer is resumed from hibernation the tree view in // the overview frame is destroyed and recreated by Windows. Unfortunately the // IView instances associated with the recreated tree nodes are lost. @@ -1594,11 +1596,7 @@ procedure TMainForm.WMPowerBroadcast(var Msg: TMessage); // access violation. case Msg.WParam of PBT_APMSUSPEND: - // Get ready for isolation - fMainDisplayMgr.PrepareForHibernate; - PBT_APMPOWERSTATUSCHANGE: - // Restore from hibernation: ensure the IView instances are recreeated - fMainDisplayMgr.RestoreFromHibernation; + fMainDisplayMgr._HACK_PrepareForHibernate; end; end; diff --git a/Src/FrOverview.pas b/Src/FrOverview.pas index e608e6d4a..9cf76a2a8 100644 --- a/Src/FrOverview.pas +++ b/Src/FrOverview.pas @@ -26,6 +26,29 @@ interface type + // !! HACK + // Horrible hack to expose CreateWnd for overiding TTreeView.CreateWnd for the + // existing TTreeView component of TOverviewFrame. The hack avoids having to + // remove the component and replacing it with a descendant class that is + // manually constructed at run time. + // This is here to enable the tree view to be recreated with correctly + // instantiated TViewItemTreeNode nodes after Windows recreates the tree + // behind the scenes after resuming from hibernation. + // I am deeply ashamed of this hack. + TTreeView = class(ComCtrls.TTreeView) + strict private + var + _HACK_fOnAfterCreateNilViews: TNotifyEvent; + protected + procedure CreateWnd; override; + public + /// !! HACK. Event triggered after the inherited CreateWnd is + /// called. Only called if the tree view has nil references to IView + /// objects. + property _HACK_OnAfterCreateNilViews: TNotifyEvent + read _HACK_fOnAfterCreateNilViews write _HACK_fOnAfterCreateNilViews; + end; + { TOverviewFrame: Titled frame that displays lists of snippets grouped in various ways and @@ -214,6 +237,10 @@ TTVDraw = class(TSnippetsTVDraw) procedure RestoreTreeState; {Restores last saved treeview expansion state from memory. } + /// !! HACK: Sets an event handler on the tree view to work + /// around a bug that can occur after resuming from hibernation. + /// Method of IOverviewDisplayMgr. + procedure _HACK_SetHibernateHandler(const AHandler: TNotifyEvent); { IPaneInfo } function IsInteractive: Boolean; {Checks if the pane is currently interactive with user. @@ -955,6 +982,12 @@ procedure TOverviewFrame.UpdateTreeState(const State: TTreeNodeAction); end; end; +procedure TOverviewFrame._HACK_SetHibernateHandler( + const AHandler: TNotifyEvent); +begin + tvSnippets._HACK_OnAfterCreateNilViews := AHandler; +end; + { TOverviewFrame.TTVDraw } function TOverviewFrame.TTVDraw.IsSectionHeadNode( @@ -993,5 +1026,24 @@ function TOverviewFrame.TTVDraw.IsUserDefinedNode( Result := False; end; +{ TTreeView } + +procedure TTreeView.CreateWnd; +var + HasNilViews: Boolean; + Node: TTreeNode; +begin + inherited; + HasNilViews := False; + for Node in Items do + begin + HasNilViews := not Assigned((Node as TViewItemTreeNode).ViewItem); + if HasNilViews then + Break; + end; + if HasNilViews and Assigned(_HACK_fOnAfterCreateNilViews) then + _HACK_fOnAfterCreateNilViews(Self); +end; + end. diff --git a/Src/IntfFrameMgrs.pas b/Src/IntfFrameMgrs.pas index 0f409800c..b3cb76101 100644 --- a/Src/IntfFrameMgrs.pas +++ b/Src/IntfFrameMgrs.pas @@ -3,7 +3,7 @@ * 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) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2025, Peter Johnson (gravatar.com/delphidabbler). * * Declares interfaces, constants and enumerations required to manage various * parts of CodeSnip's UI. @@ -19,6 +19,7 @@ interface uses // Delphi SHDocVw, ActiveX, + Classes, // !! For HACK // Project Browser.IntfDocHostUI, DB.USnippet, Compilers.UGlobals, UCommandBars, UView; @@ -145,6 +146,9 @@ interface /// Restore expand / collapse state of treeview to last save /// state. procedure RestoreTreeState; + /// !! HACK: Sets an event handler on the tree view to work + /// around a bug that can occur after resuming from hibernation. + procedure _HACK_SetHibernateHandler(const AHandler: TNotifyEvent); end; type diff --git a/Src/UMainDisplayMgr.pas b/Src/UMainDisplayMgr.pas index 65e98e2fb..0c64a17d5 100644 --- a/Src/UMainDisplayMgr.pas +++ b/Src/UMainDisplayMgr.pas @@ -165,6 +165,11 @@ TMainDisplayMgr = class(TObject) procedure DisplayViewItem(ViewItem: IView; Mode: TDetailPageDisplayMode); overload; + /// !! HACK event handle to redisplay the overview pane treeview. + /// Called only if Windows has mysteriously recreated the treeview and lost + /// necessary object references. + procedure _HACK_HibernateHandler(Sender: TObject); + public /// Object contructor. Sets up object to work with given frame /// manager objects. @@ -292,21 +297,12 @@ TMainDisplayMgr = class(TObject) /// Prepares display ready for database to be reloaded. procedure PrepareForDBReload; - /// Gets the overview frame prepared for program hibernation. - /// + /// !!HACK: gets the overview frame prepared for program + /// hibernation. /// Saves the overview tree view state ready for restoring after - /// hibernation. - procedure PrepareForHibernate; - - /// Restores the overview's tree view to have the correct IView - /// instances after hibernation restores the previously saved state. - /// - /// Sometimes, Windows quietly recreates the node of the tree view - /// after resuming from hibernation, without restoring the associated IView - /// instances, leading to access violations. This method should be called - /// after resuming from hibernation to recreate the tree view with the - /// correct IView instances. - procedure RestoreFromHibernation; + /// hibernation if Windows has recreated the overview pane's treeview, + /// losing necessary IView object references.. + procedure _HACK_PrepareForHibernate; end; @@ -583,12 +579,6 @@ procedure TMainDisplayMgr.PrepareForDBViewChange(View: IView); fPendingViewChange := True; end; -procedure TMainDisplayMgr.PrepareForHibernate; -begin - // simply save the state of the overview tree view ready for later restoration - (fOverviewMgr as IOverviewDisplayMgr).SaveTreeState; -end; - procedure TMainDisplayMgr.RedisplayOverview; begin (fOverviewMgr as IOverviewDisplayMgr).Display(Query.Selection, True); @@ -616,12 +606,6 @@ procedure TMainDisplayMgr.ReStart; (fOverviewMgr as IOverviewDisplayMgr).Display(Query.Selection, True); end; -procedure TMainDisplayMgr.RestoreFromHibernation; -begin - (fOverviewMgr as IOverviewDisplayMgr).Display(Query.Selection, True); - (fOverviewMgr as IOverviewDisplayMgr).RestoreTreeState; -end; - procedure TMainDisplayMgr.SelectAll; begin // Only details pane supports text selection @@ -720,5 +704,21 @@ procedure TMainDisplayMgr.UpdateOverviewTreeState(const State: TTreeNodeAction); (fOverviewMgr as IOverviewDisplayMgr).UpdateTreeState(State); end; +procedure TMainDisplayMgr._HACK_HibernateHandler(Sender: TObject); +begin + (fOverviewMgr as IOverviewDisplayMgr).Display(Query.Selection, True); + (fOverviewMgr as IOverviewDisplayMgr).RestoreTreeState; + // disable this handler until next resume from hibernation + (fOverviewMgr as IOverviewDisplayMgr)._HACK_SetHibernateHandler(nil); +end; + +procedure TMainDisplayMgr._HACK_PrepareForHibernate; +begin + (fOverviewMgr as IOverviewDisplayMgr).SaveTreeState; + (fOverviewMgr as IOverviewDisplayMgr)._HACK_SetHibernateHandler( + _HACK_HibernateHandler + ); +end; + end. From ef80e27bbdbb30e895f2b73fbac5519ab2512119 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Mon, 14 Apr 2025 19:19:29 +0100 Subject: [PATCH 248/330] Bump version number to v4.24.2 build 274 --- Src/VersionInfo.vi-inc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Src/VersionInfo.vi-inc b/Src/VersionInfo.vi-inc index 513eae126..fbd558db1 100644 --- a/Src/VersionInfo.vi-inc +++ b/Src/VersionInfo.vi-inc @@ -1,8 +1,8 @@ # CodeSnip Version Information Macros for Including in .vi files # Version & build numbers -version=4.24.1 -build=273 +version=4.24.2 +build=274 # String file information copyright=Copyright © P.D.Johnson, 2005-. From 0ad4fbe75b4cb4bbdb6b27844057107c39bcaedb Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Mon, 14 Apr 2025 19:37:10 +0100 Subject: [PATCH 249/330] Update change log with details of release v4.24.2 --- CHANGELOG.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bafd5addf..16846323a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,14 @@ 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.24.1 of 13 April 2005 +## 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]. * Bumped some copyright dates for 2025. From efcef0f2453ff43d525bddabd78d88f2dbb780d6 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Tue, 15 Apr 2025 08:22:24 +0100 Subject: [PATCH 250/330] Fix malformed bullet char in snippet import wizard The last page of the Import Wizard dialogue box lists imported snippets as bullet points. The bullet point string literal had become corrupted. Replaced the string literal with a constant containing the Unicode character code point. Fixes #147 --- Src/FmCodeImportDlg.pas | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Src/FmCodeImportDlg.pas b/Src/FmCodeImportDlg.pas index 7315b29e8..86f0fbef2 100644 --- a/Src/FmCodeImportDlg.pas +++ b/Src/FmCodeImportDlg.pas @@ -3,7 +3,7 @@ * 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) 2011-2023, Peter Johnson (gravatar.com/delphidabbler). + * 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 @@ -419,6 +419,8 @@ 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 @@ -426,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; // --------------------------------------------------------------------------- From 0a0681841be323fdf1001794777fae820b8a790e Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Tue, 15 Apr 2025 08:29:22 +0100 Subject: [PATCH 251/330] Correct program copyright date in about box Updated copyright date to 2025. Fixes #149 --- Src/Res/HTML/dlg-about-program-tplt.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Src/Res/HTML/dlg-about-program-tplt.html b/Src/Res/HTML/dlg-about-program-tplt.html index be93a30c3..a337e8b80 100644 --- a/Src/Res/HTML/dlg-about-program-tplt.html +++ b/Src/Res/HTML/dlg-about-program-tplt.html @@ -9,7 +9,7 @@ * 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) 2005-2024, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2025, Peter Johnson (gravatar.com/delphidabbler). * * Template for content displayed in program tab of about dialog box. --> @@ -47,7 +47,7 @@

        - DelphiDabbler CodeSnip is copyright © 2005-2024 by CodeSnip is copyright © 2005-2025 by Peter D Johnson. From 46ae69be45e9904cf04f31f5f43fa01464bccdeb Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Tue, 15 Apr 2025 09:42:42 +0100 Subject: [PATCH 252/330] Fix error in export file format documentation Documentation of XML tags relating to storing snippet xrefs in export files was removed as erroneous. The error was noted and the file format bumped to v8 following this change. Updated the current file version number in the UCodeImportExport unit to v8. Fixes #151 Updated the export help topic to note that snippet categories and xrefs are not exported. --- Docs/Design/FileFormats/export.html | 75 ++++++++++++++++------------- Src/Help/HTML/dlg_export.htm | 6 ++- Src/UCodeImportExport.pas | 4 +- 3 files changed, 48 insertions(+), 37 deletions(-) diff --git a/Docs/Design/FileFormats/export.html b/Docs/Design/FileFormats/export.html index 29ca8a849..7f6e80653 100644 --- a/Docs/Design/FileFormats/export.html +++ b/Docs/Design/FileFormats/export.html @@ -5,7 +5,7 @@ * 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). + * Copyright (C) 2012-2025, Peter Johnson (gravatar.com/delphidabbler). * * CodeSnip File Format Documentation: Export --> @@ -57,6 +57,9 @@

      5. File Format
      6. +
      7. + Erratum +
      8. Change Log
      9. @@ -114,8 +117,8 @@

        - There have been seven different versions of the XML export file format – v1 to - v7. Tags used by all 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.

        @@ -176,7 +179,7 @@

        Identifies major version of file. Determines which tags are valid and - establishes rules concerning content. Valid versions are 1 to 7. + establishes rules concerning content. Valid versions are 1 to 8.
        @@ -198,7 +201,7 @@

        versions 1 to 6: Contains information about user who created the file   used for submissions to the online database, omitted for other exports.
      10. - version 7: Not supported. Ignored if present. + version 7 and later: Not supported. Ignored if present.
      11. @@ -212,7 +215,7 @@

        versions 1 to 6: User's name or nickname.
      12. - version 7: Not supported. Ignored if present. + version 7 and later: Not supported. Ignored if present.
      13. @@ -226,7 +229,7 @@

        versions 1 to 6: User's email address.
      14. - version 7: Not supported. Ignored if present. + version 7 and later: Not supported. Ignored if present.
      15. @@ -240,7 +243,7 @@

        versions 1 to 6: Any comments provided by user.
      16. - version 7: Not supported. Ignored if present. + version 7 and later: Not supported. Ignored if present.
      17. @@ -687,33 +690,21 @@

        -
        - 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 to 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. +

        @@ -1008,6 +999,18 @@

        + +
        + 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. +

        +
        @@ -1058,6 +1061,10 @@

        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/Src/Help/HTML/dlg_export.htm b/Src/Help/HTML/dlg_export.htm index a17b626a4..b8e1db3ff 100644 --- a/Src/Help/HTML/dlg_export.htm +++ b/Src/Help/HTML/dlg_export.htm @@ -4,7 +4,7 @@ * 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) 2008-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2008-2025, Peter Johnson (gravatar.com/delphidabbler). * * Help topic for Export Snippets dialogue box. --> @@ -57,6 +57,10 @@

        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/UCodeImportExport.pas b/Src/UCodeImportExport.pas index e43346daa..b4dfffd29 100644 --- a/Src/UCodeImportExport.pas +++ b/Src/UCodeImportExport.pas @@ -3,7 +3,7 @@ * 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) 2008-2023, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2008-2025. Peter Johnson (gravatar.com/delphidabbler). * * Implements classes that can import and export user defined snippets from and * to XML. @@ -181,7 +181,7 @@ implementation cWatermark = 'B46969D4-D367-4F5F-833E-F165FBA78631'; // file version numbers cEarliestVersion = 1; // earliest file version supported by importer - cLatestVersion = 7; // current file version written by exporter + cLatestVersion = 8; // current file version written by exporter { TCodeExporter } From 9998f4e22fb02339d4b4080535364d93a13b5087 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Tue, 15 Apr 2025 11:03:43 +0100 Subject: [PATCH 253/330] Corrected comments for some methods of TREMLEntities The comments for both TREMLEntities.MapToEntity and the private TREMLEntities.CharToMnemonicEntity methods were changed to reflect their actual functions. Fixes #84 --- Src/UREMLDataIO.pas | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/Src/UREMLDataIO.pas b/Src/UREMLDataIO.pas index 76974afc8..249990248 100644 --- a/Src/UREMLDataIO.pas +++ b/Src/UREMLDataIO.pas @@ -281,12 +281,15 @@ TREMLEntity = record } end; class var fEntityMap: array of TREMLEntity; // Entity <=> character map + + /// Attempts to map a character to an associated mnemonic + /// character entity, without the surrounding & and ; + /// characters. + /// Char [in] Character to be mapped. + /// string. The associated mnemonic entity or an empty + /// string if not such entity exists. class function CharToMnemonicEntity(const Ch: Char): string; - {Gets the mnemonic character entity that represents a character. - @param Entity [in] Character for which equivalent entity is required. - @return Required entity or '' if character has no matching mnemonic - entity. - } + class function GetCount: Integer; static; {Read accessor for Count property. @return Number of supported tags. @@ -309,13 +312,16 @@ TREMLEntity = record class destructor Destroy; {Class destructor. Clears entity map } + + /// Attempts to map a character to a character enitity, without + /// the surrounding & and ; characters. + /// Char [in] Character to be mapped. + /// string. A mnemonic entity if one exists for Ch. + /// Otherwise if Ch is not a printable ASCII character a numeric + /// entity is returned. If Ch is a printable ASCII character an + /// empty string is returned. class function MapToEntity(const Ch: Char): string; - {Maps a character to a character entity if appropriate. - @param Ch [in] Character to be mapped. - @return Mnemonic entity if one exists, character itself if it is - printable and has ascii value less than 127, or a numeric character - otherwise. - } + class property Count: Integer read GetCount; {Number of supported tags} class property Entities[Idx: Integer]: string read GetEntity; @@ -1013,10 +1019,6 @@ constructor TREMLTags.TREMLTag.Create(const AId: TActiveTextActionElemKind; { TREMLEntities } class function TREMLEntities.CharToMnemonicEntity(const Ch: Char): string; - {Gets the mnemonic character entity that represents a character. - @param Entity [in] Character for which equivalent entity is required. - @return Required entity or '' if character has no matching mnemonic entity. - } var Idx: Integer; // loops thru table of entity / characters begin @@ -1112,11 +1114,6 @@ class function TREMLEntities.GetEntity(Idx: Integer): string; end; class function TREMLEntities.MapToEntity(const Ch: Char): string; - {Maps a character to a character entity if appropriate. - @param Ch [in] Character to be mapped. - @return Mnemonic entity if one exists, character itself if it is printable - and has ascii value less than 127, or a numeric character otherwise. - } begin Result := CharToMnemonicEntity(Ch); if (Result = '') and ( (Ord(Ch) <= 31) or (Ord(Ch) >= 127) ) then From 0265ec083e05e49f533e17db50e9a0ee30a09ddf Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 16 Apr 2025 07:52:47 +0100 Subject: [PATCH 254/330] Add feature to save snippet info to RTF file Added new USaveInfoMgr unit to handle getting file name from user, generating the RTF from a given snippet's information and saving to file. Added new File | Save Snippet Information menu option and associated action to the main form. --- Src/CodeSnip.dpr | 3 +- Src/CodeSnip.dproj | 1 + Src/FmMain.dfm | 13 +++++ Src/FmMain.pas | 21 ++++++- Src/USaveInfoMgr.pas | 132 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 166 insertions(+), 4 deletions(-) create mode 100644 Src/USaveInfoMgr.pas diff --git a/Src/CodeSnip.dpr b/Src/CodeSnip.dpr index 1fe24aca3..babbd49e2 100644 --- a/Src/CodeSnip.dpr +++ b/Src/CodeSnip.dpr @@ -374,7 +374,8 @@ uses Compilers.USettings in 'Compilers.USettings.pas', FmRegisterCompilersDlg in 'FmRegisterCompilersDlg.pas' {RegisterCompilersDlg}, ClassHelpers.UGraphics in 'ClassHelpers.UGraphics.pas', - ClassHelpers.UActions in 'ClassHelpers.UActions.pas'; + ClassHelpers.UActions in 'ClassHelpers.UActions.pas', + USaveInfoMgr in 'USaveInfoMgr.pas'; // Include resources {$Resource ExternalObj.tlb} // Type library file diff --git a/Src/CodeSnip.dproj b/Src/CodeSnip.dproj index dc6c27915..41e93eb81 100644 --- a/Src/CodeSnip.dproj +++ b/Src/CodeSnip.dproj @@ -581,6 +581,7 @@ + Base diff --git a/Src/FmMain.dfm b/Src/FmMain.dfm index 902e71720..6f460ff96 100644 --- a/Src/FmMain.dfm +++ b/Src/FmMain.dfm @@ -877,6 +877,16 @@ inherited MainForm: TMainForm 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 @@ -887,6 +897,9 @@ inherited MainForm: TMainForm object miSaveSnippet: TMenuItem Action = actSaveSnippet end + object miSaveInfo: TMenuItem + Action = actSaveInfo + end object miSaveUnit: TMenuItem Action = actSaveUnit end diff --git a/Src/FmMain.pas b/Src/FmMain.pas index 8bffbe2b8..60556605c 100644 --- a/Src/FmMain.pas +++ b/Src/FmMain.pas @@ -241,6 +241,8 @@ TMainForm = class(THelpAwareForm) tbSpacer7: TToolButton; tbSpacer8: TToolButton; tbTestCompile: TToolButton; + miSaveInfo: TMenuItem; + actSaveInfo: TAction; /// Displays About Box. procedure actAboutExecute(Sender: TObject); /// Gets a new category from user and adds to database. @@ -501,6 +503,8 @@ TMainForm = class(THelpAwareForm) 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 /// Object that notifies user-initiated events by triggering @@ -596,9 +600,9 @@ implementation UCodeShareMgr, UCommandBars, UConsts, UCopyInfoMgr, UCopySourceMgr, UDatabaseLoader, UDatabaseLoaderUI, UDetailTabAction, UEditSnippetAction, UExceptions, UHelpMgr, UHistoryMenus, UKeysHelper, - UMessageBox, UNotifier, UNulDropTarget, UPrintMgr, UQuery, USaveSnippetMgr, - USaveUnitMgr, USelectionIOMgr, UUrl, UUserDBMgr, UView, UViewItemAction, - UWBExternal; + UMessageBox, UNotifier, UNulDropTarget, UPrintMgr, UQuery, USaveInfoMgr, + USaveSnippetMgr, USaveUnitMgr, USelectionIOMgr, UUrl, UUserDBMgr, UView, + UViewItemAction, UWBExternal; {$R *.dfm} @@ -1025,6 +1029,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; diff --git a/Src/USaveInfoMgr.pas b/Src/USaveInfoMgr.pas new file mode 100644 index 000000000..123f04a99 --- /dev/null +++ b/Src/USaveInfoMgr.pas @@ -0,0 +1,132 @@ +{ + * 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). + * + * Saves information about a snippet to disk in rich text format. Only routine + * snippet kinds are supported. +} + + +unit USaveInfoMgr; + +interface + +uses + // Project + UEncodings, + UView; + + +type + /// Method-only record that saves information about a snippet to + /// file in rich text format. The snippet is obtained from a view. Only + /// snippet views are supported. + TSaveInfoMgr = record + strict private + /// Attempts to name of the file to be written from the user. + /// + /// string [out] Set to the name of the file + /// entered by the user. Undefined if the user cancelled. + /// Boolean. True if the user entered and accepted a + /// file name of False if the user cancelled. + class function TryGetFileNameFromUser(out AFileName: string): Boolean; + static; + /// Returns encoded data containing a RTF representation of + /// information about the snippet represented by the given view. + class function GenerateRichText(View: IView): TEncodedData; static; + public + /// Saves information about the snippet referenced by the a given + /// view to file. + /// The view must be a snippet view. + class procedure Execute(View: IView); static; + /// Checks if a given view can be saved to the clipboard. Returns + /// True only if the view represents a snippet. + class function CanHandleView(View: IView): Boolean; static; + + end; + +implementation + +uses + // Delphi + SysUtils, + Dialogs, + // Project + Hiliter.UAttrs, + Hiliter.UGlobals, + UIOUtils, + UOpenDialogHelper, + URTFSnippetDoc, + URTFUtils, + USaveDialogEx; + +{ TSaveInfoMgr } + +class function TSaveInfoMgr.CanHandleView(View: IView): Boolean; +begin + Result := Supports(View, ISnippetView); +end; + +class procedure TSaveInfoMgr.Execute(View: IView); +var + FileName: string; + RTF: TRTF; +begin + Assert(Assigned(View), 'TSaveInfoMgr.Execute: View is nil'); + Assert(CanHandleView(View), 'TSaveInfoMgr.Execute: View not supported'); + if not TryGetFileNameFromUser(FileName) then + Exit; + RTF := TRTF.Create(GenerateRichText(View)); + TFileIO.WriteAllBytes(FileName, RTF.ToBytes); +end; + +class function TSaveInfoMgr.GenerateRichText(View: IView): TEncodedData; +var + Doc: TRTFSnippetDoc; // object that generates RTF document + HiliteAttrs: IHiliteAttrs; // syntax highlighter formatting attributes +begin + Assert(Supports(View, ISnippetView), + 'TSaveInfoMgr.GenerateRichText: View is not a snippet view'); + if (View as ISnippetView).Snippet.HiliteSource then + HiliteAttrs := THiliteAttrsFactory.CreateUserAttrs + else + HiliteAttrs := THiliteAttrsFactory.CreateNulAttrs; + Doc := TRTFSnippetDoc.Create(HiliteAttrs); + try + // TRTFSnippetDoc generates stream of ASCII bytes + Result := Doc.Generate((View as ISnippetView).Snippet); + Assert(Result.EncodingType = etASCII, + 'TSaveInfoMgr.GenerateRichText: ASCII encoded data expected'); + finally + Doc.Free; + end; +end; + +class function TSaveInfoMgr.TryGetFileNameFromUser( + out AFileName: string): Boolean; +var + Dlg: TSaveDialogEx; +resourcestring + sCaption = 'Save Snippet Information'; // dialogue box caption + sFilter = 'Rich Text File (*.rtf)|*.rtf|' // file filter + + 'All files (*.*)|*.*'; +begin + Dlg := TSaveDialogEx.Create(nil); + try + Dlg.Title := sCaption; + Dlg.Options := [ofShowHelp, ofNoTestFileCreate, ofEnableSizing]; + Dlg.Filter := sFilter; + Dlg.FilterIndex := 1; + Dlg.HelpKeyword := 'SnippetInfoFileDlg'; + Result := Dlg.Execute; + if Result then + AFileName := FileOpenFileNameWithExt(Dlg) + finally + Dlg.Free; + end; +end; + +end. From afa37e47a5be5907bd5cd2a3e6e14fe27679df09 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 16 Apr 2025 08:19:33 +0100 Subject: [PATCH 255/330] Update help file re save snippet information feature Added new help topic for the Save Snippet Information file save dialogue box. Updated help project file and help index file re the new topic. Updated File menu help topic to add the new Save Snippet Information menu option. --- Src/Help/CodeSnip.hhp | 1 + Src/Help/HTML/dlg_saveinfo.htm | 48 ++++++++++++++++++++++++++++++++++ Src/Help/HTML/menu_file.htm | 16 ++++++++++++ Src/Help/Index.hhk | 4 +++ 4 files changed, 69 insertions(+) create mode 100644 Src/Help/HTML/dlg_saveinfo.htm diff --git a/Src/Help/CodeSnip.hhp b/Src/Help/CodeSnip.hhp index 48d4ec0f2..73163ca7e 100644 --- a/Src/Help/CodeSnip.hhp +++ b/Src/Help/CodeSnip.hhp @@ -57,6 +57,7 @@ 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 diff --git a/Src/Help/HTML/dlg_saveinfo.htm b/Src/Help/HTML/dlg_saveinfo.htm new file mode 100644 index 000000000..53abcca9d --- /dev/null +++ b/Src/Help/HTML/dlg_saveinfo.htm @@ -0,0 +1,48 @@ + + + + + + + + + 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 + name of the file into which information about the currently selected + snippet is to be saved. +

        +

        + The saved snippet information is written in rich text format. +

        +

        + This dialogue is a standard Windows save dialogue box. You specify the + name and folder for the file in the usual way. +

        +

        + Use the Save button to write the file to disk or press + Cancel to abort. +

        + + + \ No newline at end of file diff --git a/Src/Help/HTML/menu_file.htm b/Src/Help/HTML/menu_file.htm index 706315a9c..dfb1a4963 100644 --- a/Src/Help/HTML/menu_file.htm +++ b/Src/Help/HTML/menu_file.htm @@ -44,6 +44,22 @@

        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. + +

        +

      18. + + +
      19. From b5511d95409116936e73eb2d1f4cef2825d2495c Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 16 Apr 2025 08:32:54 +0100 Subject: [PATCH 256/330] Update Saved Files documentation page Updated with details of the file format used for the new Save Snippet Information feature. --- Docs/Design/FileFormats/saved.html | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/Docs/Design/FileFormats/saved.html b/Docs/Design/FileFormats/saved.html index 8e68c073d..69dbbefd4 100644 --- a/Docs/Design/FileFormats/saved.html +++ b/Docs/Design/FileFormats/saved.html @@ -5,7 +5,7 @@ * 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-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2012-2025, Peter Johnson (gravatar.com/delphidabbler). * * CodeSnip File Format Documentation: Saved Files --> @@ -46,20 +46,27 @@

        - 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 to file from the File | Save Snippet Information menu option.
        2. - By saving units to file from the File | Save Unit menu. + By saving snippets to file from the File | Save Snippet menu option. +
        3. +
        4. + By saving units to file from 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 is always saved in rich text format. +

        + +

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

          @@ -87,7 +94,11 @@

          - The encodings used depend on the file type and user choice. Different file + In the first case the RTF is always saved in ASCII format. +

          + +

          + In the 2nd and 3rd cases the encodings used depend on the file type and user choice. Different file types have different encoding choices, as follows:

          @@ -140,7 +151,7 @@

          • - ANSI (system default code page) + ANSI (system default code page). ASCII format is actually used.
          From fc1b87aee77056ccea36154477856f0e6cf539a6 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 17 Apr 2025 17:16:25 +0100 Subject: [PATCH 257/330] Add new THML5 & TXHTML classes & reimplement THTML THTML was re-implemented as a static abstract class instead of a record. It now acts as an abstract base template class for two new static classes - TXHTML and THML5. TXHTML was added as drop-in replacement for the original implementation of THTML. Since the original THTML generated XML compliant tags, this new name is more accurate. All code that called THML was changed to call TXHTML instead. TXHTML was implemented without any change of interface to THTML, despite the fact that the original THML was a record and TXHTML is a class. THTML5 was added as another static class that descends from THTML. This class generates HTML 5 compliant tags. A new class reference to THTML derived types was added, named THTMLClass. --- Src/ActiveText.UHTMLRenderer.pas | 6 ++-- Src/Browser.UHighlighter.pas | 2 +- Src/FmAboutDlg.pas | 12 +++---- Src/FmCompErrorDlg.pas | 2 +- Src/UCompResHTML.pas | 34 +++++++++--------- Src/UDetailPageHTML.pas | 18 +++++----- Src/UHTMLBuilder.pas | 20 +++++------ Src/UHTMLTemplate.pas | 2 +- Src/UHTMLUtils.pas | 61 ++++++++++++++++++++++++-------- Src/USnippetHTML.pas | 14 ++++---- Src/USnippetPageHTML.pas | 16 ++++----- 11 files changed, 109 insertions(+), 78 deletions(-) diff --git a/Src/ActiveText.UHTMLRenderer.pas b/Src/ActiveText.UHTMLRenderer.pas index bf4dfc7c1..ec9c19d23 100644 --- a/Src/ActiveText.UHTMLRenderer.pas +++ b/Src/ActiveText.UHTMLRenderer.pas @@ -145,7 +145,7 @@ procedure TActiveTextHTML.InitialiseTagInfoMap; function TActiveTextHTML.MakeClosingTag(const Elem: IActiveTextActionElem): string; begin - Result := THTML.ClosingTag(fTagInfoMap[Elem.Kind].Name); + Result := TXHTML.ClosingTag(fTagInfoMap[Elem.Kind].Name); end; function TActiveTextHTML.MakeOpeningTag(const Elem: IActiveTextActionElem): @@ -160,7 +160,7 @@ 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 := TXHTML.OpeningTag(fTagInfoMap[Elem.Kind].Name, Attrs); end; function TActiveTextHTML.Render(ActiveText: IActiveText): string; @@ -242,7 +242,7 @@ function TActiveTextHTML.RenderText(const TextElem: IActiveTextTextElem): end else Result := ''; - Result := Result + THTML.Entities(TextElem.Text); + Result := Result + TXHTML.Entities(TextElem.Text); end; { TActiveTextHTML.TCSSStyles } diff --git a/Src/Browser.UHighlighter.pas b/Src/Browser.UHighlighter.pas index 9231502b4..5405008cd 100644 --- a/Src/Browser.UHighlighter.pas +++ b/Src/Browser.UHighlighter.pas @@ -194,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/FmAboutDlg.pas b/Src/FmAboutDlg.pas index 6584a4ffd..dafdfb627 100644 --- a/Src/FmAboutDlg.pas +++ b/Src/FmAboutDlg.pas @@ -312,15 +312,15 @@ function TAboutDlg.ContribListHTML(ContribList: IStringList): begin for Contributor in ContribList do Result := Result - + THTML.CompoundTag('div', THTML.Entities(Contributor)) + + TXHTML.CompoundTag('div', TXHTML.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) + Result := TXHTML.CompoundTag( + 'div', DivAttrs, TXHTML.Entities(sNoContributors) ); end; end; @@ -484,15 +484,15 @@ procedure TAboutDlg.InitHTMLFrames; 'DBLicense', StrIf( fMetaData.GetLicenseInfo.URL <> '', - THTML.CompoundTag( + TXHTML.CompoundTag( 'a', THTMLAttributes.Create([ THTMLAttribute.Create('href', fMetaData.GetLicenseInfo.URL), THTMLAttribute.Create('class', 'external-link') ]), - THTML.Entities(fMetaData.GetLicenseInfo.Name) + TXHTML.Entities(fMetaData.GetLicenseInfo.Name) ), - THTML.Entities(fMetaData.GetLicenseInfo.Name) + TXHTML.Entities(fMetaData.GetLicenseInfo.Name) ) ); Tplt.ResolvePlaceholderHTML( diff --git a/Src/FmCompErrorDlg.pas b/Src/FmCompErrorDlg.pas index ed1285957..b9cdf0a3e 100644 --- a/Src/FmCompErrorDlg.pas +++ b/Src/FmCompErrorDlg.pas @@ -341,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/UCompResHTML.pas b/Src/UCompResHTML.pas index a3248e55c..7ed706983 100644 --- a/Src/UCompResHTML.pas +++ b/Src/UCompResHTML.pas @@ -99,8 +99,8 @@ class function TCompResHTML.CompileResultsTableRows(Compilers: ICompilers; Compiler: ICompiler; // each supported compiler begin // Initialise HTML for two rows of table and resulting table HTML - Row1 := THTML.OpeningTag('tr'); - Row2 := THTML.OpeningTag('tr'); + Row1 := TXHTML.OpeningTag('tr'); + Row2 := TXHTML.OpeningTag('tr'); // Add to each table row for each compiler: compiler name in row 1 and LED // image representing compile result in row 2 for Compiler in Compilers do @@ -111,8 +111,8 @@ class function TCompResHTML.CompileResultsTableRows(Compilers: ICompilers; Row2 := Row2 + ResultCell(CompileResults[Compiler.GetID]) + EOL; end; // Close the two rows - Row1 := Row1 + THTML.ClosingTag('tr'); - Row2 := Row2 + THTML.ClosingTag('tr'); + Row1 := Row1 + TXHTML.ClosingTag('tr'); + Row2 := Row2 + TXHTML.ClosingTag('tr'); // Return HTML of two rows Result := Row1 + Row2; end; @@ -123,30 +123,30 @@ class function TCompResHTML.EmptyTableRows: string; sMessage = 'Results for all compilers have been hidden.'; sHelpText = 'More information'; begin - Result := THTML.CompoundTag( + Result := TXHTML.CompoundTag( 'tr', - THTML.CompoundTag( + TXHTML.CompoundTag( 'th', - THTML.CompoundTag( + TXHTML.CompoundTag( 'span', THTMLAttributes.Create('class', 'warning'), - THTML.Entities(sHeading) + TXHTML.Entities(sHeading) ) ) ) + - THTML.CompoundTag( + TXHTML.CompoundTag( 'tr', - THTML.CompoundTag( + TXHTML.CompoundTag( 'td', - THTML.Entities(sMessage) + TXHTML.Entities(sMessage) + ' ' + - THTML.CompoundTag( + TXHTML.CompoundTag( 'a', THTMLAttributes.Create([ THTMLAttribute.Create('href', 'help:AllCompilersHidden'), THTMLAttribute.Create('class', 'help-link') ]), - THTML.Entities(sHelpText) + TXHTML.Entities(sHelpText) ) + '.' ) @@ -172,7 +172,7 @@ class function TCompResHTML.ImageTag(const CompRes: TCompileResult): string; ); Attrs.Add('title', CompResImgInfo[CompRes].Title); // Create tag - Result := THTML.SimpleTag('img', Attrs); + Result := TXHTML.SimpleTag('img', Attrs); end; class function TCompResHTML.NameCell(const Compiler: ICompiler): string; @@ -181,14 +181,14 @@ class function TCompResHTML.NameCell(const Compiler: ICompiler): string; begin // Any spaces in compiler name replaced by
          tags CompilerNameHTML := StrReplace( - THTML.Entities(Compiler.GetName), ' ', THTML.SimpleTag('br') + TXHTML.Entities(Compiler.GetName), ' ', TXHTML.SimpleTag('br') ); - Result := THTML.CompoundTag('th', CompilerNameHTML); + Result := TXHTML.CompoundTag('th', CompilerNameHTML); end; class function TCompResHTML.ResultCell(const CompRes: TCompileResult): string; begin - Result := THTML.CompoundTag('td', ImageTag(CompRes)); + Result := TXHTML.CompoundTag('td', ImageTag(CompRes)); end; class function TCompResHTML.TableRows(const CompileResults: TCompileResults): diff --git a/Src/UDetailPageHTML.pas b/Src/UDetailPageHTML.pas index 718aa7032..d278ab931 100644 --- a/Src/UDetailPageHTML.pas +++ b/Src/UDetailPageHTML.pas @@ -391,10 +391,10 @@ function TNulPageHTML.Generate: string; function TNewTabPageHTML.GetBodyHTML: string; begin - Result := THTML.CompoundTag( + Result := TXHTML.CompoundTag( 'div', THTMLAttributes.Create('id', 'newtab'), - THTML.Entities(View.Description) + TXHTML.Entities(View.Description) ); end; @@ -452,9 +452,9 @@ procedure TWelcomePageHTML.ResolvePlaceholders(const Tplt: THTMLTemplate); for Compiler in Compilers do if Compiler.IsAvailable then CompilerList.AppendLine( - THTML.CompoundTag( + TXHTML.CompoundTag( 'li', - THTML.Entities(Compiler.GetName) + TXHTML.Entities(Compiler.GetName) ) ); Tplt.ResolvePlaceholderHTML('CompilerList', CompilerList.ToString); @@ -470,9 +470,9 @@ function TDBUpdatedPageHTML.GetBodyHTML: string; sBody = 'The database has been updated successfully.'; begin Result := - THTML.CompoundTag('h1', View.Description) + TXHTML.CompoundTag('h1', View.Description) + - THTML.CompoundTag('p', sBody); + TXHTML.CompoundTag('p', sBody); end; { TSnippetInfoPageHTML } @@ -623,14 +623,14 @@ function TSnippetListPageHTML.SnippetTableRow(const Snippet: TSnippet): string; DescCellAttrs := THTMLAttributes.Create('class', 'desc'); SnippetHTML := TSnippetHTML.Create(Snippet); try - Result := THTML.CompoundTag( + Result := TXHTML.CompoundTag( 'tr', - THTML.CompoundTag( + TXHTML.CompoundTag( 'td', NameCellAttrs, SnippetHTML.SnippetALink ) - + THTML.CompoundTag('td', DescCellAttrs, SnippetHTML.Description) + + TXHTML.CompoundTag('td', DescCellAttrs, SnippetHTML.Description) ); finally SnippetHTML.Free; diff --git a/Src/UHTMLBuilder.pas b/Src/UHTMLBuilder.pas index 8a5a2038a..87a11a00c 100644 --- a/Src/UHTMLBuilder.pas +++ b/Src/UHTMLBuilder.pas @@ -142,22 +142,22 @@ implementation procedure THTMLBuilder.AddText(const Text: string); begin - fBodyInner.Append(THTML.Entities(Text)); + fBodyInner.Append(TXHTML.Entities(Text)); end; function THTMLBuilder.BodyTag: string; begin - Result := THTML.CompoundTag(cBodyTag, EOL + HTMLFragment + EOL); + Result := TXHTML.CompoundTag(cBodyTag, EOL + HTMLFragment + EOL); end; procedure THTMLBuilder.ClosePre; begin - fBodyInner.Append(THTML.ClosingTag(cPreTag)); + fBodyInner.Append(TXHTML.ClosingTag(cPreTag)); end; procedure THTMLBuilder.CloseSpan; begin - fBodyInner.Append(THTML.ClosingTag(cSpanTag)); + fBodyInner.Append(TXHTML.ClosingTag(cSpanTag)); end; constructor THTMLBuilder.Create; @@ -182,10 +182,10 @@ function THTMLBuilder.GetTitle: string; function THTMLBuilder.HeadTag: string; begin - Result := THTML.CompoundTag( + Result := TXHTML.CompoundTag( cHeadTag, EOL - + THTML.CompoundTag(cTitleTag, THTML.Entities(Title)) + + TXHTML.CompoundTag(cTitleTag, TXHTML.Entities(Title)) + EOL + InlineStyleSheet ); @@ -222,7 +222,7 @@ function THTMLBuilder.HTMLTag: string; // --------------------------------------------------------------------------- begin - Result := THTML.CompoundTag( + Result := TXHTML.CompoundTag( cHTMLTag, HTMLAttrs, EOL + HeadTag + EOL + BodyTag + EOL @@ -237,7 +237,7 @@ function THTMLBuilder.InlineStyleSheet: string; begin Attrs := THTMLAttributes.Create('type', 'text/css'); Result := EOL - + THTML.CompoundTag(cStyleTag, Attrs, EOL + fCSS + EOL) + + TXHTML.CompoundTag(cStyleTag, Attrs, EOL + fCSS + EOL) + EOL; end else @@ -258,12 +258,12 @@ procedure THTMLBuilder.NewLine; procedure THTMLBuilder.OpenPre(const ClassName: string); begin - fBodyInner.Append(THTML.OpeningTag(cPreTag, MakeClassAttr(ClassName))); + fBodyInner.Append(TXHTML.OpeningTag(cPreTag, MakeClassAttr(ClassName))); end; procedure THTMLBuilder.OpenSpan(const ClassName: string); begin - fBodyInner.Append(THTML.OpeningTag(cSpanTag, MakeClassAttr(ClassName))); + fBodyInner.Append(TXHTML.OpeningTag(cSpanTag, MakeClassAttr(ClassName))); end; end. diff --git a/Src/UHTMLTemplate.pas b/Src/UHTMLTemplate.pas index 7a9088aef..54ae6a876 100644 --- a/Src/UHTMLTemplate.pas +++ b/Src/UHTMLTemplate.pas @@ -105,7 +105,7 @@ procedure THTMLTemplate.ResolvePlaceholderText(const Placeholder, Text: string); @param Text [in] Plain text to replace placeholder. } begin - ResolvePlaceholderHTML(Placeholder, THTML.Entities(Text)); + ResolvePlaceholderHTML(Placeholder, TXHTML.Entities(Text)); end; end. diff --git a/Src/UHTMLUtils.pas b/Src/UHTMLUtils.pas index 5ec7ccebb..ddc441e88 100644 --- a/Src/UHTMLUtils.pas +++ b/Src/UHTMLUtils.pas @@ -19,6 +19,7 @@ interface // Delphi Classes, Graphics, Generics.Collections, // Project + UBaseObjects, UIStringList; @@ -123,10 +124,12 @@ THTMLAttributes = class(TInterfacedObject, IHTMLAttributes) end; type - /// - /// Container for static methods that generate HTML tags and entities. - /// - THTML = record + + THTMLClass = class of THTML; + + /// Abstract base classe for static classes that return valid tags + /// for different flavours of HTML. + THTML = class abstract(TNoConstructObject) strict private /// Generates either an HTML start tag or a simple tag with given /// name and attributes. @@ -137,8 +140,9 @@ THTML = record /// be simple (True) or the start of a compound tag (False). /// string. Required tag. class function TagWithAttrs(const Name: string; Attrs: IHTMLAttributes; - const IsSimple: Boolean): string; static; - + const IsSimple: Boolean): string; + strict protected + class function GetSimpleTagCloser: string; virtual; abstract; public /// Generates an opening HTML tag. /// string [in] Name of tag. @@ -147,13 +151,13 @@ THTML = record /// String. Required tag. /// Example tag: <p class="ident"> class function OpeningTag(const Name: string; Attrs: IHTMLAttributes = nil): - string; static; + string; /// Generates a closing HTML tag. /// string [in] Name of tag. /// String. Required tag. /// Example tag: </p> - class function ClosingTag(const Name: string): string; static; + class function ClosingTag(const Name: string): string; /// Generates a simple HTML tag. /// string [in] Name of tag. @@ -162,7 +166,7 @@ THTML = record /// String. Required tag. /// Example tag: <img class="glyph" /> class function SimpleTag(const Name: string; Attrs: IHTMLAttributes = nil): - string; static; + string; /// Surrounds the given HTML in a HTML tag pair. /// string [in] Name of tag. @@ -172,7 +176,7 @@ THTML = record /// the tag pair. /// String. Required tag. class function CompoundTag(const Name: string; Attrs: IHTMLAttributes; - const InnerHTML: string): string; overload; static; + const InnerHTML: string): string; overload; /// Surrounds the given HTML in a HTML tag pair. The opening tag /// has no attributes. @@ -181,14 +185,27 @@ THTML = record /// the tag pair. /// String. Required tag. class function CompoundTag(const Name, InnerHTML: string): string; overload; - static; /// Encodes the given string replacing any HTML-incompatible /// characters with character entities. - class function Entities(const Text: string): string; static; + class function Entities(const Text: string): string; end; + /// Contains static methods that generate XHTML tags and entities. + /// + TXHTML = class sealed (THTML) + strict protected + class function GetSimpleTagCloser: string; override; + end; + + /// Contains static methods that generate HTML5 tags and entities. + /// + THTML5 = class sealed (THTML) + strict protected + class function GetSimpleTagCloser: string; override; + end; + implementation @@ -260,11 +277,25 @@ class function THTML.TagWithAttrs(const Name: string; Attrs: IHTMLAttributes; if Assigned(Attrs) and (not Attrs.IsEmpty) then Result := Result + ' ' + Attrs.RenderSafe; if IsSimple then - Result := Result + ' />' + Result := Result + GetSimpleTagCloser else Result := Result + '>'; end; +{ TXHTML } + +class function TXHTML.GetSimpleTagCloser: string; +begin + Result := ' />'; +end; + +{ THTML5 } + +class function THTML5.GetSimpleTagCloser: string; +begin + Result := '>'; +end; + { THTMLAttributes } procedure THTMLAttributes.Add(const Name, Value: string); @@ -376,8 +407,8 @@ function THTMLAttributes.RenderSafe: string; Result := Result + Format( ' %0:s="%1:s"', [ - THTML.Entities(fAttrs.Names[Idx]), - THTML.Entities(fAttrs.ValueFromIndex[Idx]) + TXHTML.Entities(fAttrs.Names[Idx]), + TXHTML.Entities(fAttrs.ValueFromIndex[Idx]) ] ); Result := StrTrimLeft(Result); diff --git a/Src/USnippetHTML.pas b/Src/USnippetHTML.pas index a853e74f6..3703830dd 100644 --- a/Src/USnippetHTML.pas +++ b/Src/USnippetHTML.pas @@ -143,7 +143,7 @@ function TSnippetHTML.EmptyListSentence: string; resourcestring sEmpty = 'None'; begin - Result := THTML.Entities(StrMakeSentence(sEmpty)); + Result := TXHTML.Entities(StrMakeSentence(sEmpty)); end; function TSnippetHTML.Extra: string; @@ -161,7 +161,7 @@ class function TSnippetHTML.JSALink(const JSFn, CSSClass, Text: string): THTMLAttribute.Create('onclick', JSFn + '; return false;'), THTMLAttribute.Create('class', CSSClass) ]); - Result := THTML.CompoundTag('a', Attrs, THTML.Entities(Text)); + Result := TXHTML.CompoundTag('a', Attrs, TXHTML.Entities(Text)); end; function TSnippetHTML.RenderActiveText(ActiveText: IActiveText): string; @@ -199,7 +199,7 @@ function TSnippetHTML.SnippetList(const Snippets: TSnippetList): string; function TSnippetHTML.SnippetName: string; begin - Result := THTML.Entities(fSnippet.DisplayName); + Result := TXHTML.Entities(fSnippet.DisplayName); end; class function TSnippetHTML.SnippetALink(const Snippet: TSnippet): string; @@ -221,7 +221,7 @@ function TSnippetHTML.SnippetALink: string; function TSnippetHTML.SnippetKind: string; begin - Result := THTML.Entities( + Result := TXHTML.Entities( StrMakeSentence(TSnippetKindInfoList.Items[fSnippet.Kind].DisplayName) ); end; @@ -267,9 +267,9 @@ function TSnippetHTML.TestingImage: string; begin Attrs := THTMLAttributes.Create; Attrs.Add('src', MakeResourceURL(ImgSrcs[fSnippet.TestInfo].ResName)); - Attrs.Add('title', THTML.Entities(ImgSrcs[fSnippet.TestInfo].Title)); + Attrs.Add('title', TXHTML.Entities(ImgSrcs[fSnippet.TestInfo].Title)); Attrs.Add('class', 'testing-img'); - Result := THTML.SimpleTag('img', Attrs); + Result := TXHTML.SimpleTag('img', Attrs); end; function TSnippetHTML.Units: string; @@ -277,7 +277,7 @@ function TSnippetHTML.Units: string; if fSnippet.Units.Count = 0 then Result := EmptyListSentence else - Result := THTML.Entities(StrJoin(fSnippet.Units, ', ', False) + '.'); + Result := TXHTML.Entities(StrJoin(fSnippet.Units, ', ', False) + '.'); end; function TSnippetHTML.XRefs: string; diff --git a/Src/USnippetPageHTML.pas b/Src/USnippetPageHTML.pas index a4871cc06..9ad1f082a 100644 --- a/Src/USnippetPageHTML.pas +++ b/Src/USnippetPageHTML.pas @@ -205,10 +205,10 @@ destructor TSnippetHTMLFragment.Destroy; class function TPrefixedSnippetHTMLFragment.Render(const Prefix, Id, Content: string): string; begin - Result := THTML.CompoundTag( + Result := TXHTML.CompoundTag( 'p', - THTML.CompoundTag('strong', Prefix) + ' ' + - THTML.CompoundTag('span', THTMLAttributes.Create('id', Id), Content) + TXHTML.CompoundTag('strong', Prefix) + ' ' + + TXHTML.CompoundTag('span', THTMLAttributes.Create('id', Id), Content) ); end; @@ -216,7 +216,7 @@ class function TPrefixedSnippetHTMLFragment.Render(const Prefix, Id, function TSnippetDescHTMLFragment.ToString: string; begin - Result := THTML.CompoundTag( + Result := TXHTML.CompoundTag( 'div', THTMLAttributes.Create('id', 'description'), SnippetHTML.Description ); end; @@ -225,7 +225,7 @@ function TSnippetDescHTMLFragment.ToString: string; function TSnippetSourceCodeHTMLFragment.ToString: string; begin - Result := THTML.CompoundTag( + Result := TXHTML.CompoundTag( 'div', THTMLAttributes.Create('id', 'sourcecode'), SnippetHTML.SourceCode ); end; @@ -279,10 +279,10 @@ function TSnippetXRefsHTMLFragment.ToString: string; function TSnippetCompileResultsHTMLFragment.ToString: string; begin - Result := THTML.CompoundTag( + Result := TXHTML.CompoundTag( 'div', THTMLAttributes.Create('id', 'compile-results'), - THTML.CompoundTag( + TXHTML.CompoundTag( 'table', THTMLAttributes.Create( [ @@ -300,7 +300,7 @@ function TSnippetCompileResultsHTMLFragment.ToString: string; function TSnippetExtraHTMLFragment.ToString: string; begin - Result := THTML.CompoundTag( + Result := TXHTML.CompoundTag( 'div', THTMLAttributes.Create('id', 'extra'), SnippetHTML.Extra ); end; From 050bc587dc4cbc93ccb65e588222e8f4c9c886d4 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 17 Apr 2025 09:41:00 +0100 Subject: [PATCH 258/330] Rename TSourceFileType.sfHTML as sfXHTML This was done to better decribe the file type. --- Src/Hiliter.UFileHiliter.pas | 4 ++-- Src/USaveSourceMgr.pas | 4 ++-- Src/USourceFileInfo.pas | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Src/Hiliter.UFileHiliter.pas b/Src/Hiliter.UFileHiliter.pas index 0c60d9372..18d1bf4d3 100644 --- a/Src/Hiliter.UFileHiliter.pas +++ b/Src/Hiliter.UFileHiliter.pas @@ -99,7 +99,7 @@ function TFileHiliter.Hilite(const SourceCode, DocTitle: string): TEncodedData; begin case fFileType of sfRTF: HilitedDocCls := TRTFDocumentHiliter; - sfHTML: HilitedDocCls := TXHTMLDocumentHiliter; + sfXHTML: HilitedDocCls := TXHTMLDocumentHiliter; else HilitedDocCls := TNulDocumentHiliter; end; if fWantHiliting and IsHilitingSupported(fFileType) then @@ -116,7 +116,7 @@ class function TFileHiliter.IsHilitingSupported( @return True if file type supports highlighting, false if not. } begin - Result := FileType in [sfHTML, sfRTF]; + Result := FileType in [sfXHTML, sfRTF]; end; end. diff --git a/Src/USaveSourceMgr.pas b/Src/USaveSourceMgr.pas index 9c7c8efca..69eb4e763 100644 --- a/Src/USaveSourceMgr.pas +++ b/Src/USaveSourceMgr.pas @@ -262,9 +262,9 @@ constructor TSaveSourceMgr.InternalCreate; TSourceFileEncoding.Create(etUTF8, sUTF8Encoding) ] ); - fSourceFileInfo.FileTypeInfo[sfHTML] := TSourceFileTypeInfo.Create( + fSourceFileInfo.FileTypeInfo[sfXHTML] := TSourceFileTypeInfo.Create( '.html', - GetFileTypeDesc(sfHTML), + GetFileTypeDesc(sfXHTML), [ TSourceFileEncoding.Create(etUTF8, sUTF8Encoding) ] diff --git a/Src/USourceFileInfo.pas b/Src/USourceFileInfo.pas index 4c641622e..67c7e3fff 100644 --- a/Src/USourceFileInfo.pas +++ b/Src/USourceFileInfo.pas @@ -28,7 +28,7 @@ interface TSourceFileType = ( sfText, // plain text files sfPascal, // pascal files (either .pas for units or .inc for include files - sfHTML, // HTML files + sfXHTML, // XHTML files sfRTF // rich text files ); From 78cf622bd4606142656310f0df451cde3bea2d01 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 17 Apr 2025 09:45:49 +0100 Subject: [PATCH 259/330] Change name of XHTML files in save dialogue boxes Name is dialogue box drop down lists change from HTML to XHTML --- Src/USaveSnippetMgr.pas | 4 ++-- Src/USaveUnitMgr.pas | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Src/USaveSnippetMgr.pas b/Src/USaveSnippetMgr.pas index 63dcb9b0e..1182fed92 100644 --- a/Src/USaveSnippetMgr.pas +++ b/Src/USaveSnippetMgr.pas @@ -99,7 +99,7 @@ implementation sCategory = 'category'; sSnippet = 'routine'; // File filter strings - sHtmExtDesc = 'HTML file'; + sXHtmExtDesc = 'XHTML file'; sRtfExtDesc = 'Rich text file'; sIncExtDesc = 'Pascal include file'; sTxtExtDesc = 'Plain text file'; @@ -170,7 +170,7 @@ function TSaveSnippetMgr.GetFileTypeDesc( const FileType: TSourceFileType): string; const Descriptions: array[TSourceFileType] of string = ( - sTxtExtDesc, sIncExtDesc, sHtmExtDesc, sRtfExtDesc + sTxtExtDesc, sIncExtDesc, sXHtmExtDesc, sRtfExtDesc ); begin Result := Descriptions[FileType]; diff --git a/Src/USaveUnitMgr.pas b/Src/USaveUnitMgr.pas index 1cd7841d3..3a9c446af 100644 --- a/Src/USaveUnitMgr.pas +++ b/Src/USaveUnitMgr.pas @@ -107,7 +107,7 @@ implementation // Dialog box title sSaveDlgTitle = 'Save Unit'; // File filter strings - sHTMLDesc = 'HTML file'; + sXHTMLDesc = 'XHTML file'; sRTFDesc = 'Rich text file'; sPascalDesc = 'Pascal unit'; sTextDesc = 'Plain text file'; @@ -241,7 +241,7 @@ function TSaveUnitMgr.GetDocTitle: string; function TSaveUnitMgr.GetFileTypeDesc(const FileType: TSourceFileType): string; const Descriptions: array[TSourceFileType] of string = ( - sTextDesc, sPascalDesc, sHTMLDesc, sRTFDesc + sTextDesc, sPascalDesc, sXHTMLDesc, sRTFDesc ); begin Result := Descriptions[FileType]; From 182d9c82daddf8dd54a90ebcc4b92d24c28da254 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 17 Apr 2025 11:57:05 +0100 Subject: [PATCH 260/330] Revise handling of file extensions in save dialogues This commit applies to the custom file save dialogues used when saving units or annotated source code files. The code to detect file types and to look up filter indexes depended on file extensions being different for each file type, which cannot be guaranteed. Relevant code was updated to remove this dependency. NOTE: This change was made because HTML 5 documents, when added, will use the same .html file extension and XTHML documents. --- Src/UOpenDialogHelper.pas | 71 +++++++++++++++++++-------------------- Src/USaveSourceDlg.pas | 8 ++--- Src/USaveSourceMgr.pas | 47 ++++++++++++++++++++------ Src/USourceFileInfo.pas | 19 ----------- 4 files changed, 74 insertions(+), 71 deletions(-) diff --git a/Src/UOpenDialogHelper.pas b/Src/UOpenDialogHelper.pas index 084b2b284..68382f1af 100644 --- a/Src/UOpenDialogHelper.pas +++ b/Src/UOpenDialogHelper.pas @@ -44,18 +44,20 @@ function FilterIndexToExt(const Dlg: TOpenDialog): string; prepended '.'. } -function ExtToFilterIndex(const FilterStr, Ext: string; - const DefValue: Integer): Integer; - {Calculates index of a file extension in a "|" delimited file filter string as - used in standard file dialog boxes. - @param FilterStr [in] List of file types and extensions. Has format - "file desc 1|ext 1|file desc 2|ext 2 etc...". - @param Ext [in] Extension to be found. - @param DefValue [in] Default 1 based index to use if Ext is not in - FilterStr. - @return 1 based index of extension in filter string or -1 if extension not - in list. - } +/// Calculates the index of a file type description in a "|" +/// delimited string, as used in Windows standard file dialogue boxes. +/// +/// string [in] List of file types and +/// extensions. Must have format +/// file desc 1|(*.ext1)|file desc 2|(*.ext2) etc... +/// string [in] File type description to be found. +/// +/// Integer [in] Default 1 based index to use if +/// Desc is not in FilterStr. +/// Integer. 1 based index of the file type description in the +/// filter string, or DefIdx if the description is not found. +function FilterDescToIndex(const FilterStr, Desc: string; + const DefIdx: Integer): Integer; function FileOpenEditedFileNameWithExt(const Dlg: TOpenDialog): string; {Gets full path to the file that is currently entered in a file open dialog @@ -96,47 +98,42 @@ function FilterIndexToExt(const Dlg: TOpenDialog): string; end; end; -function ExtToFilterIndex(const FilterStr, Ext: string; - const DefValue: Integer): Integer; - {Calculates index of a file extension in a "|" delimited file filter string as - used in standard file dialog boxes. - @param FilterStr [in] List of file types and extensions. Has format - "file desc 1|ext 1|file desc 2|ext 2 etc...". - @param Ext [in] Extension to be found. - @param DefValue [in] Default 1 based index to use if Ext is not in - FilterStr. - @return 1 based index of extension in filter string or -1 if extension not - in list. - } +function FilterDescToIndex(const FilterStr, Desc: string; + const DefIdx: Integer): Integer; var FilterParts: TStringList; // stores filter split into component parts - Extensions: TStringList; // list of extensions in filter string - Idx: Integer; // loops thru extensions in filter string + Descs: TStringList; // list of file type descriptions in filter string + Idx: Integer; // loops thru Descs in filter string + DescStr: string; + DescEnd: Integer; begin - Extensions := nil; + Descs := nil; FilterParts := TStringList.Create; try // Split filter string into parts (divided by | chars): - // even number indexes are descriptions and odd indexes are extensions + // even number indexes are descriptions and odd indexes are Descs StrExplode(FilterStr, '|', FilterParts); - // Record only extensions (every 2nd entry starting at index 1) - Extensions := TStringList.Create; - Idx := 1; + // Record only Descs (every 2nd entry starting at index 1) + Descs := TStringList.Create; + Idx := 0; while Idx < FilterParts.Count do begin - Extensions.Add(ExtractFileExt(FilterParts[Idx])); + DescStr := FilterParts[Idx]; + DescEnd := StrPos('(', DescStr) - 2; + DescStr := Copy(DescStr, 1, DescEnd); + Descs.Add(DescStr); Inc(Idx, 2); end; // Check if required extension in list - Result := Extensions.IndexOf(Ext); + Result := Descs.IndexOf(Desc); if Result >= 0 then - // extension in list, increment by 1 since filter indexes are 1 based + // description in list, increment by 1 since filter indexes are 1 based Inc(Result) else - Result := DefValue; + Result := DefIdx; finally - FreeAndNil(Extensions); - FreeAndNil(FilterParts); + Descs.Free; + FilterParts.Free; end; end; diff --git a/Src/USaveSourceDlg.pas b/Src/USaveSourceDlg.pas index fad8094d2..8a5aeaa5b 100644 --- a/Src/USaveSourceDlg.pas +++ b/Src/USaveSourceDlg.pas @@ -38,11 +38,11 @@ interface /// Type of handler for event triggered by TSaveSourceDlg to get /// list of encodings supported for a file type. /// TObject [in] Object triggering event. - /// string [in] Extension that defines type of file being - /// queried. + /// string [in] Filter index that specifies the type + /// of file being queried. /// TSourceFileEncodings [in/out] Assigned an array /// of records that specify supported encodings. - TEncodingQuery = procedure(Sender: TObject; const Ext: string; + TEncodingQuery = procedure(Sender: TObject; const FilterIdx: Integer; var Encodings: TSourceFileEncodings) of object; type @@ -475,7 +475,7 @@ procedure TSaveSourceDlg.DoTypeChange; // handle OnEncodingQuery) SetLength(Encodings, 0); if Assigned(fOnEncodingQuery) then - fOnEncodingQuery(Self, SelectedExt, Encodings); + fOnEncodingQuery(Self, FilterIndex, Encodings); if Length(Encodings) = 0 then Encodings := TSourceFileEncodings.Create( TSourceFileEncoding.Create(etSysDefault, sANSIEncoding) diff --git a/Src/USaveSourceMgr.pas b/Src/USaveSourceMgr.pas index 69eb4e763..dd5305682 100644 --- a/Src/USaveSourceMgr.pas +++ b/Src/USaveSourceMgr.pas @@ -49,10 +49,11 @@ TSaveSourceMgr = class abstract(TNoPublicConstructObject) /// Provides array of encodings supported for a file extension. /// TObject [in] Reference to object that triggered /// event. - /// string [in] Name of extension to check. + /// string [in] Index of file type withing dialog's + /// filter string to check. /// TSourceFileEncodings [in/out] Receives array of /// supported encodings. - procedure EncodingQueryHandler(Sender: TObject; const Ext: string; + procedure EncodingQueryHandler(Sender: TObject; const FilterIdx: Integer; var Encodings: TSourceFileEncodings); /// Handles custom save dialog's OnPreview event. Displays source /// code appropriately formatted in preview dialog box. @@ -81,6 +82,12 @@ TSaveSourceMgr = class abstract(TNoPublicConstructObject) /// TEncodedData - Formatted source code, syntax highlighted if /// required. function GenerateOutput(const FileType: TSourceFileType): TEncodedData; + /// Returns the source file type associated with the selected + /// index in the save dialogue box. + /// This method assumes that the filter string entries are in the + /// same order as elements of the TSourceFileType enumeration. + /// + function FileTypeFromFilterIdx: TSourceFileType; strict protected /// Internal constructor. Initialises managed save source dialog /// box and records information about supported file types. @@ -178,18 +185,16 @@ procedure TSaveSourceMgr.DoExecute; begin // Set up dialog box fSaveDlg.Filter := fSourceFileInfo.FilterString; - fSaveDlg.FilterIndex := ExtToFilterIndex( + fSaveDlg.FilterIndex := FilterDescToIndex( fSaveDlg.Filter, - fSourceFileInfo.FileTypeInfo[Preferences.SourceDefaultFileType].Extension, + fSourceFileInfo.FileTypeInfo[Preferences.SourceDefaultFileType].DisplayName, 1 ); fSaveDlg.FileName := fSourceFileInfo.DefaultFileName; // Display dialog box and save file if user OKs if fSaveDlg.Execute then begin - FileType := fSourceFileInfo.FileTypeFromExt( - ExtractFileExt(fSaveDlg.FileName) - ); + FileType := FileTypeFromFilterIdx; FileContent := GenerateOutput(FileType).ToString; Encoding := TEncodingHelper.GetEncoding(fSaveDlg.SelectedEncoding); try @@ -201,14 +206,27 @@ procedure TSaveSourceMgr.DoExecute; end; procedure TSaveSourceMgr.EncodingQueryHandler(Sender: TObject; - const Ext: string; var Encodings: TSourceFileEncodings); + const FilterIdx: Integer; var Encodings: TSourceFileEncodings); var FileType: TSourceFileType; // type of file that has given extension begin - FileType := fSourceFileInfo.FileTypeFromExt(Ext); + FileType := FileTypeFromFilterIdx; Encodings := fSourceFileInfo.FileTypeInfo[FileType].Encodings; end; +function TSaveSourceMgr.FileTypeFromFilterIdx: TSourceFileType; +var + FilterIdx: Integer; // dlg FilterIndex adjusted to be 0 based +begin + FilterIdx := fSaveDlg.FilterIndex - 1; + Assert( + (FilterIdx >= Ord(Low(TSourceFileType))) + and (FilterIdx <= Ord(High(TSourceFileType))), + ClassName + '.FileTypeFromFilterIdx: FilerIdx out of range' + ); + Result := TSourceFileType(FilterIdx) +end; + function TSaveSourceMgr.GenerateOutput(const FileType: TSourceFileType): TEncodedData; var @@ -231,7 +249,7 @@ function TSaveSourceMgr.GenerateOutput(const FileType: TSourceFileType): procedure TSaveSourceMgr.HiliteQueryHandler(Sender: TObject; const Ext: string; var CanHilite: Boolean); begin - CanHilite := IsHilitingSupported(fSourceFileInfo.FileTypeFromExt(Ext)); + CanHilite := IsHilitingSupported(FileTypeFromFilterIdx); end; constructor TSaveSourceMgr.InternalCreate; @@ -262,6 +280,13 @@ constructor TSaveSourceMgr.InternalCreate; TSourceFileEncoding.Create(etUTF8, sUTF8Encoding) ] ); + fSourceFileInfo.FileTypeInfo[sfHTML5] := TSourceFileTypeInfo.Create( + '.html', + GetFileTypeDesc(sfHTML5), + [ + TSourceFileEncoding.Create(etUTF8, sUTF8Encoding) + ] + ); fSourceFileInfo.FileTypeInfo[sfXHTML] := TSourceFileTypeInfo.Create( '.html', GetFileTypeDesc(sfXHTML), @@ -305,7 +330,7 @@ procedure TSaveSourceMgr.PreviewHandler(Sender: TObject); var FileType: TSourceFileType; // type of source file to preview begin - FileType := fSourceFileInfo.FileTypeFromExt(fSaveDlg.SelectedExt); + FileType := FileTypeFromFilterIdx; // Display preview dialog box. We use save dialog as owner to ensure preview // dialog box is aligned over save dialog box TPreviewDlg.Execute( diff --git a/Src/USourceFileInfo.pas b/Src/USourceFileInfo.pas index 67c7e3fff..d2a4a2b39 100644 --- a/Src/USourceFileInfo.pas +++ b/Src/USourceFileInfo.pas @@ -105,9 +105,6 @@ TSourceFileInfo = class(TObject) /// Builds filter string for use in open / save dialog boxes from /// descriptions and file extensions of each supported file type. function FilterString: string; - /// Finds source file type associated with a file extension. - /// - function FileTypeFromExt(const Ext: string): TSourceFileType; /// Array of information about each supported file type that is /// of use to save source dialog boxes. property FileTypeInfo[const FileType: TSourceFileType]: TSourceFileTypeInfo @@ -132,22 +129,6 @@ implementation { TSourceFileInfo } -function TSourceFileInfo.FileTypeFromExt(const Ext: string): TSourceFileType; -var - FT: TSourceFileType; // loops thru all source file types -begin - // Assume text file type if extension not recognised - Result := sfText; - for FT := Low(TSourceFileType) to High(TSourceFileType) do - begin - if StrSameText(Ext, fFileTypeInfo[FT].Extension) then - begin - Result := FT; - Break; - end; - end; -end; - function TSourceFileInfo.FilterString: string; const cFilterFmt = '%0:s (*%1:s)|*%1:s'; // format string for creating file filter From b2f2bfc921488ccefbc10c3225744244d08155da Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 17 Apr 2025 11:57:47 +0100 Subject: [PATCH 261/330] Add support for HTML 5 as save dialogue file type Added support for listing HTML 5 as a file type option, and detecting its selection in the custom save file dialogue boxes used when saving units and annotated source code. NOTE: this commit does not implement the correct rendering of HTML 5 files: at present selecting HTML 5 has the same effect as selecting plain text. --- Src/FrSourcePrefs.pas | 5 +++-- Src/Hiliter.UFileHiliter.pas | 2 +- Src/USaveSnippetMgr.pas | 3 ++- Src/USaveSourceMgr.pas | 6 +++++- Src/USaveUnitMgr.pas | 3 ++- Src/USourceFileInfo.pas | 1 + 6 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Src/FrSourcePrefs.pas b/Src/FrSourcePrefs.pas index 297cac1f4..2fbe5c77f 100644 --- a/Src/FrSourcePrefs.pas +++ b/Src/FrSourcePrefs.pas @@ -121,7 +121,8 @@ implementation resourcestring // File type descriptions - sHTMLFileDesc = 'HTML'; + sHTML5FileDesc = 'HTML 5'; + sXHTMLFileDesc = 'XHTML'; sRTFFileDesc = 'Rich text'; sPascalFileDesc = 'Pascal'; sTextFileDesc = 'Plain text'; @@ -130,7 +131,7 @@ implementation const // Maps source code file types to descriptions cFileDescs: array[TSourceFileType] of string = ( - sTextFileDesc, sPascalFileDesc, sHTMLFileDesc, sRTFFileDesc + sTextFileDesc, sPascalFileDesc, sHTML5FileDesc, sXHTMLFileDesc, sRTFFileDesc ); diff --git a/Src/Hiliter.UFileHiliter.pas b/Src/Hiliter.UFileHiliter.pas index 18d1bf4d3..3bb87cf8b 100644 --- a/Src/Hiliter.UFileHiliter.pas +++ b/Src/Hiliter.UFileHiliter.pas @@ -116,7 +116,7 @@ class function TFileHiliter.IsHilitingSupported( @return True if file type supports highlighting, false if not. } begin - Result := FileType in [sfXHTML, sfRTF]; + Result := FileType in [sfHTML5, sfXHTML, sfRTF]; end; end. diff --git a/Src/USaveSnippetMgr.pas b/Src/USaveSnippetMgr.pas index 1182fed92..61f37bef6 100644 --- a/Src/USaveSnippetMgr.pas +++ b/Src/USaveSnippetMgr.pas @@ -99,6 +99,7 @@ implementation sCategory = 'category'; sSnippet = 'routine'; // File filter strings + sHtml5ExtDesc = 'HTML 5 file'; sXHtmExtDesc = 'XHTML file'; sRtfExtDesc = 'Rich text file'; sIncExtDesc = 'Pascal include file'; @@ -170,7 +171,7 @@ function TSaveSnippetMgr.GetFileTypeDesc( const FileType: TSourceFileType): string; const Descriptions: array[TSourceFileType] of string = ( - sTxtExtDesc, sIncExtDesc, sXHtmExtDesc, sRtfExtDesc + sTxtExtDesc, sIncExtDesc, sHtml5ExtDesc, sXHtmExtDesc, sRtfExtDesc ); begin Result := Descriptions[FileType]; diff --git a/Src/USaveSourceMgr.pas b/Src/USaveSourceMgr.pas index dd5305682..29058b105 100644 --- a/Src/USaveSourceMgr.pas +++ b/Src/USaveSourceMgr.pas @@ -325,7 +325,11 @@ procedure TSaveSourceMgr.PreviewHandler(Sender: TObject); const // Map of source file type to preview document types PreviewDocTypeMap: array[TSourceFileType] of TPreviewDocType = ( - dtPlainText, dtPlainText, dtHTML, dtRTF + dtPlainText, // sfText + dtPlainText, // sfPascal + dtHTML, // sfHTML5 + dtHTML, // sfXHTML + dtRTF // sfRTF ); var FileType: TSourceFileType; // type of source file to preview diff --git a/Src/USaveUnitMgr.pas b/Src/USaveUnitMgr.pas index 3a9c446af..45d2b4529 100644 --- a/Src/USaveUnitMgr.pas +++ b/Src/USaveUnitMgr.pas @@ -107,6 +107,7 @@ implementation // Dialog box title sSaveDlgTitle = 'Save Unit'; // File filter strings + sHTML5Desc = 'HTML 5 file'; sXHTMLDesc = 'XHTML file'; sRTFDesc = 'Rich text file'; sPascalDesc = 'Pascal unit'; @@ -241,7 +242,7 @@ function TSaveUnitMgr.GetDocTitle: string; function TSaveUnitMgr.GetFileTypeDesc(const FileType: TSourceFileType): string; const Descriptions: array[TSourceFileType] of string = ( - sTextDesc, sPascalDesc, sXHTMLDesc, sRTFDesc + sTextDesc, sPascalDesc, sHTML5Desc, sXHTMLDesc, sRTFDesc ); begin Result := Descriptions[FileType]; diff --git a/Src/USourceFileInfo.pas b/Src/USourceFileInfo.pas index d2a4a2b39..a4b4f49a5 100644 --- a/Src/USourceFileInfo.pas +++ b/Src/USourceFileInfo.pas @@ -28,6 +28,7 @@ interface TSourceFileType = ( sfText, // plain text files sfPascal, // pascal files (either .pas for units or .inc for include files + sfHTML5, // HTML 5 files sfXHTML, // XHTML files sfRTF // rich text files ); From 28ac886326bffd8fa4f132a255543b38e800b14d Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 17 Apr 2025 12:57:02 +0100 Subject: [PATCH 262/330] Fix TSaveSourceMgr to preview HTML5 files correctly HTML 5 files are actually previewed as XHTML because support for HTML5 might not be properly implemented by the IE based MS web browser control. TSaveSourceMgr was also changed to explicitly render pascal file as plain text for previewing. Although this change is not strictly necessary since pascal and plain text file types render the same, it was done for consitency and clarity. --- Src/USaveSourceMgr.pas | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Src/USaveSourceMgr.pas b/Src/USaveSourceMgr.pas index 29058b105..bf35fc824 100644 --- a/Src/USaveSourceMgr.pas +++ b/Src/USaveSourceMgr.pas @@ -331,16 +331,23 @@ procedure TSaveSourceMgr.PreviewHandler(Sender: TObject); dtHTML, // sfXHTML dtRTF // sfRTF ); + PreviewFileTypeMap: array[TPreviewDocType] of TSourceFileType = ( + sfText, // dtPlainText + sfXHTML, // dtHTML + sfRTF // dtRTF + ); var - FileType: TSourceFileType; // type of source file to preview + PreviewFileType: TSourceFileType; // type of source file to preview + PreviewDocType: TPreviewDocType; // type of file to be generated for preview begin - FileType := FileTypeFromFilterIdx; + PreviewDocType := PreviewDocTypeMap[FileTypeFromFilterIdx]; + PreviewFileType := PreviewFileTypeMap[PreviewDocType]; // Display preview dialog box. We use save dialog as owner to ensure preview // dialog box is aligned over save dialog box TPreviewDlg.Execute( fSaveDlg, - GenerateOutput(FileType), - PreviewDocTypeMap[FileType], + GenerateOutput(PreviewFileType), + PreviewDocType, GetDocTitle ); end; From b165f85300fbe5d7b76037dc7c09ff70f7103658 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 17 Apr 2025 15:49:09 +0100 Subject: [PATCH 263/330] Add support for hiliting source code as HTML 5 Selecting the HTML 5 file type in custom save dialogue boxes now renders highlighted source code in correctly formatted HTML 5 documents. Revised the UHTMLBuilder unit so that the original THTMLBuilder class was refactored into an abstract template class containing HTML flavour-agnostic code. Added new TXHTMLBuilder and THTML5Builder concrete classes that can build XHTML and HTML 5 documents, respectively. XHTML output was modified to emit the correct data tag to describe the document content type. Similarly revised TXHTMLDocumentHiliter as a concrete sub class of a new THTMLDocumentHiliter abstract template class. The functionality of TXHTMLDocumentHiliter is unchanged. Added THTML5DocumentHiliter as another concrete subclass of THTMLDocumentHiliter that outputs HTML 5. Revised code in USnippetHTML that constructed a THTMLBuilder object to construct a TXHTMLBuilder object instead. Changed TFileHiliter.Hilite to highlight HTML 5 documents when the source file type is sfHTML5. --- Src/Hiliter.UFileHiliter.pas | 1 + Src/Hiliter.UHiliters.pas | 101 ++++++++++------ Src/UHTMLBuilder.pas | 220 +++++++++++++++++++++++++---------- Src/USnippetHTML.pas | 2 +- 4 files changed, 229 insertions(+), 95 deletions(-) diff --git a/Src/Hiliter.UFileHiliter.pas b/Src/Hiliter.UFileHiliter.pas index 3bb87cf8b..43838cb87 100644 --- a/Src/Hiliter.UFileHiliter.pas +++ b/Src/Hiliter.UFileHiliter.pas @@ -100,6 +100,7 @@ function TFileHiliter.Hilite(const SourceCode, DocTitle: string): TEncodedData; case fFileType of sfRTF: HilitedDocCls := TRTFDocumentHiliter; sfXHTML: HilitedDocCls := TXHTMLDocumentHiliter; + sfHTML5: HilitedDocCls := THTML5DocumentHiliter; else HilitedDocCls := TNulDocumentHiliter; end; if fWantHiliting and IsHilitingSupported(fFileType) then diff --git a/Src/Hiliter.UHiliters.pas b/Src/Hiliter.UHiliters.pas index f0a998300..8433ab199 100644 --- a/Src/Hiliter.UHiliters.pas +++ b/Src/Hiliter.UHiliters.pas @@ -132,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 @@ -140,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. /// @@ -154,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. @@ -242,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; @@ -372,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 @@ -396,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 @@ -405,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 @@ -420,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/UHTMLBuilder.pas b/Src/UHTMLBuilder.pas index 87a11a00c..1c9afdab0 100644 --- a/Src/UHTMLBuilder.pas +++ b/Src/UHTMLBuilder.pas @@ -23,10 +23,12 @@ interface type - /// - /// Class used to create content of a XHTML strict document. - /// - THTMLBuilder = class(TObject) + + THTMLBuilderClass = class of THTMLBuilder; + + /// Abstract base class for classes that create the content of + /// different types of HTML documents. + THTMLBuilder = class abstract (TObject) strict private var /// Value of CSS property. @@ -48,6 +50,9 @@ THTMLBuilder = class(TObject) /// function HeadTag: string; + /// Build document's <title> tag and its content. + function TitleTag: string; + /// Builds document's compound <body> tag and its content. /// function BodyTag: string; @@ -60,6 +65,30 @@ THTMLBuilder = class(TObject) /// Returns default title if title is empty string. function GetTitle: string; + strict protected + const + // Various HTML tag names + HTMLTagName = 'html'; + HeadTagName = 'head'; + TitleTagName = 'title'; + MetaTagName = 'meta'; + StyleTagName = 'style'; + BodyTagName = 'body'; + PreTagName = 'pre'; + SpanTagName = 'span'; + strict protected + /// Returns the class used to generate tags for the appropriate + /// type of HTML. + function TagGenerator: THTMLClass; virtual; abstract; + /// Returns any preamble to be written to the HTML before the + /// opening <html> tag. + function Preamble: string; virtual; abstract; + /// Returns the attributes of the document's <html> tag. + /// + function HTMLTagAttrs: IHTMLAttributes; virtual; abstract; + /// Returns any <meta> tags to be included within the + /// document's <head> tag. + function MetaTags: string; virtual; abstract; public /// Object constructor. Initialises object with empty body. /// @@ -107,6 +136,51 @@ THTMLBuilder = class(TObject) property CSS: string read fCSS write fCSS; end; + /// Class used to create the content of a XHTML strict document. + /// + TXHTMLBuilder = class sealed(THTMLBuilder) + strict private + const + // XML processor instruction + XMLProcInstruction = ''; + // XML document type + XHTMLDocType = ''; + strict protected + /// Returns the class used to generate XHTML compliant tags. + /// + function TagGenerator: THTMLClass; override; + /// Returns the XML processing instruction followed by the XHTML + /// doctype. + function Preamble: string; override; + /// Returns the attributes required for an XHTML <html> tag. + /// + function HTMLTagAttrs: IHTMLAttributes; override; + /// Returns a <meta> tag that specifies the text/html + /// content type and UTF-8 encodiing. + function MetaTags: string; override; + end; + + /// Class used to create the content of a HTML 5 document. + THTML5Builder = class sealed(THTMLBuilder) + strict private + const + // HTML 5 document type + HTML5DocType = ''; + strict protected + /// Returns the class used to generate HTML 5 compliant tags. + /// + function TagGenerator: THTMLClass; override; + /// Returns the HTML 5 doctype. + function Preamble: string; override; + /// Returns the attributes required for an HTML 5 <html> + /// tag. + function HTMLTagAttrs: IHTMLAttributes; override; + /// Returns a <meta> tag that specifies that the document + /// uses UTF-8 encoding. + function MetaTags: string; override; + end; + implementation @@ -116,23 +190,6 @@ implementation UConsts; -const - // XHTML document elements - // XML processor instruction - cXMLProcInstruction = ''; - // XML document type - cDocType = ''; - // Various tag names - cHTMLTag = 'html'; - cHeadTag = 'head'; - cTitleTag = 'title'; - cStyleTag = 'style'; - cBodyTag = 'body'; - cPreTag = 'pre'; - cSpanTag = 'span'; - - resourcestring // Default document title used if none provided sUntitled = 'Untitled'; @@ -142,22 +199,22 @@ implementation procedure THTMLBuilder.AddText(const Text: string); begin - fBodyInner.Append(TXHTML.Entities(Text)); + fBodyInner.Append(TagGenerator.Entities(Text)); end; function THTMLBuilder.BodyTag: string; begin - Result := TXHTML.CompoundTag(cBodyTag, EOL + HTMLFragment + EOL); + Result := TagGenerator.CompoundTag(BodyTagName, EOL + HTMLFragment + EOL); end; procedure THTMLBuilder.ClosePre; begin - fBodyInner.Append(TXHTML.ClosingTag(cPreTag)); + fBodyInner.Append(TagGenerator.ClosingTag(PreTagName)); end; procedure THTMLBuilder.CloseSpan; begin - fBodyInner.Append(TXHTML.ClosingTag(cSpanTag)); + fBodyInner.Append(TagGenerator.ClosingTag(SpanTagName)); end; constructor THTMLBuilder.Create; @@ -182,23 +239,15 @@ function THTMLBuilder.GetTitle: string; function THTMLBuilder.HeadTag: string; begin - Result := TXHTML.CompoundTag( - cHeadTag, - EOL - + TXHTML.CompoundTag(cTitleTag, TXHTML.Entities(Title)) - + EOL - + InlineStyleSheet + Result := TagGenerator.CompoundTag( + HeadTagName, + EOL + MetaTags + EOL + TitleTag + EOL + InlineStyleSheet ); end; function THTMLBuilder.HTMLDocument: string; begin - Result := cXMLProcInstruction - + EOL - + cDocType - + EOL - + HTMLTag - + EOL; + Result := Preamble + EOL + HTMLTag + EOL; end; function THTMLBuilder.HTMLFragment: string; @@ -207,24 +256,10 @@ function THTMLBuilder.HTMLFragment: string; end; function THTMLBuilder.HTMLTag: string; - - // --------------------------------------------------------------------------- - /// Builds object describing attributes of <html> tag. - /// - function HTMLAttrs: IHTMLAttributes; - begin - Result := THTMLAttributes.Create( - [THTMLAttribute.Create('xmlns', 'https://www.w3.org/1999/xhtml'), - THTMLAttribute.Create('xml:lang', 'en'), - THTMLAttribute.Create('lang', 'en')] - ); - end; - // --------------------------------------------------------------------------- - begin - Result := TXHTML.CompoundTag( - cHTMLTag, - HTMLAttrs, + Result := TagGenerator.CompoundTag( + HTMLTagName, + HTMLTagAttrs, EOL + HeadTag + EOL + BodyTag + EOL ); end; @@ -236,9 +271,7 @@ function THTMLBuilder.InlineStyleSheet: string; if fCSS <> '' then begin Attrs := THTMLAttributes.Create('type', 'text/css'); - Result := EOL - + TXHTML.CompoundTag(cStyleTag, Attrs, EOL + fCSS + EOL) - + EOL; + Result := TagGenerator.CompoundTag(StyleTagName, Attrs, EOL + fCSS) + EOL; end else Result := ''; @@ -258,12 +291,81 @@ procedure THTMLBuilder.NewLine; procedure THTMLBuilder.OpenPre(const ClassName: string); begin - fBodyInner.Append(TXHTML.OpeningTag(cPreTag, MakeClassAttr(ClassName))); + fBodyInner.Append( + TagGenerator.OpeningTag(PreTagName, MakeClassAttr(ClassName)) + ); end; procedure THTMLBuilder.OpenSpan(const ClassName: string); begin - fBodyInner.Append(TXHTML.OpeningTag(cSpanTag, MakeClassAttr(ClassName))); + fBodyInner.Append( + TagGenerator.OpeningTag(SpanTagName, MakeClassAttr(ClassName)) + ); +end; + +function THTMLBuilder.TitleTag: string; +begin + Result := TagGenerator.CompoundTag( + TitleTagName, TagGenerator.Entities(Title) + ); +end; + +{ TXHTMLBuilder } + +function TXHTMLBuilder.HTMLTagAttrs: IHTMLAttributes; +begin + Result := THTMLAttributes.Create( + [THTMLAttribute.Create('xmlns', 'https://www.w3.org/1999/xhtml'), + THTMLAttribute.Create('xml:lang', 'en'), + THTMLAttribute.Create('lang', 'en')] + ); +end; + +function TXHTMLBuilder.MetaTags: string; +begin + Result := TagGenerator.SimpleTag( + MetaTagName, + THTMLAttributes.Create([ + THTMLAttribute.Create('http-equiv', 'content-type'), + THTMLAttribute.Create('content', 'text/html; UTF-8') + ]) + ); +end; + +function TXHTMLBuilder.Preamble: string; +begin + Result := XMLProcInstruction + EOL + XHTMLDocType; +end; + +function TXHTMLBuilder.TagGenerator: THTMLClass; +begin + Result := TXHTML; +end; + +{ THTML5Builder } + +function THTML5Builder.HTMLTagAttrs: IHTMLAttributes; +begin + Result := THTMLAttributes.Create('lang', 'en'); +end; + +function THTML5Builder.MetaTags: string; +begin + // + Result := TagGenerator.SimpleTag( + MetaTagName, + THTMLAttributes.Create('charset', 'UTF-8') + ); +end; + +function THTML5Builder.Preamble: string; +begin + Result := HTML5DocType; +end; + +function THTML5Builder.TagGenerator: THTMLClass; +begin + Result := THTML5; end; end. diff --git a/Src/USnippetHTML.pas b/Src/USnippetHTML.pas index 3703830dd..8035a7474 100644 --- a/Src/USnippetHTML.pas +++ b/Src/USnippetHTML.pas @@ -236,7 +236,7 @@ function TSnippetHTML.SourceCode: string; Attrs := THiliteAttrsFactory.CreateUserAttrs else Attrs := THiliteAttrsFactory.CreateNulAttrs; - Builder := THTMLBuilder.Create; + Builder := TXHTMLBuilder.Create; try Renderer := THTMLHiliteRenderer.Create(Builder, Attrs); TSyntaxHiliter.Hilite(fSnippet.SourceCode, Renderer); From bd3b3ee14e2c245e431aed49c6a3e6105215a9af Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 17 Apr 2025 17:35:18 +0100 Subject: [PATCH 264/330] Update docs re support for HTML 5 in save dialogues Noted the HTML 5 output option in: * the save snippet & save unit dialogue boxes and file menu help topics. * the saved.html file format document XX --- Docs/Design/FileFormats/saved.html | 15 ++++++++++++++- Src/Help/HTML/dlg_savesnippet.htm | 8 +++++++- Src/Help/HTML/dlg_saveunit.htm | 8 +++++++- Src/Help/HTML/menu_file.htm | 6 +++--- 4 files changed, 31 insertions(+), 6 deletions(-) diff --git a/Docs/Design/FileFormats/saved.html b/Docs/Design/FileFormats/saved.html index 69dbbefd4..f464bd621 100644 --- a/Docs/Design/FileFormats/saved.html +++ b/Docs/Design/FileFormats/saved.html @@ -76,6 +76,9 @@

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

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

        @@ -135,6 +138,16 @@

        +
        + HTML 5 files +
        +
        +
          +
        • + UTF-8 +
        • +
        +
        XHTML files
        diff --git a/Src/Help/HTML/dlg_savesnippet.htm b/Src/Help/HTML/dlg_savesnippet.htm index 10e613980..bdddfe9b1 100644 --- a/Src/Help/HTML/dlg_savesnippet.htm +++ b/Src/Help/HTML/dlg_savesnippet.htm @@ -75,7 +75,13 @@

        file except that the extension is .txt rather than .inc.
      20. - An HTML file (.html) – This option writes the source code out as a + 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. +
      21. +
      22. + 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. diff --git a/Src/Help/HTML/dlg_saveunit.htm b/Src/Help/HTML/dlg_saveunit.htm index 928c4ebe6..9dfb25358 100644 --- a/Src/Help/HTML/dlg_saveunit.htm +++ b/Src/Help/HTML/dlg_saveunit.htm @@ -60,7 +60,13 @@

        file except that the extension is .txt rather than .pas.

      23. - An HTML file (.html) – This option writes the source code out as a + 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. +
      24. +
      25. + 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. diff --git a/Src/Help/HTML/menu_file.htm b/Src/Help/HTML/menu_file.htm index dfb1a4963..badf54294 100644 --- a/Src/Help/HTML/menu_file.htm +++ b/Src/Help/HTML/menu_file.htm @@ -37,8 +37,8 @@

        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. @@ -74,7 +74,7 @@

        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. From 426ec5502f236505b332c18cb15e912e6ae2b41e Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 18 Apr 2025 07:27:14 +0100 Subject: [PATCH 265/330] Refactor TRichEditHelper as a class helper Added new ClassHelpers.RichEdit unit to the project. Converted TRichEditHelper into a "class helper for TRichEdit" and moved it from URTFUtils into ClassHelpers.RichEdit. Updated all affected code. --- Src/ClassHelpers.RichEdit.pas | 53 +++++++++++++++++++++++++++++++++++ Src/CodeSnip.dpr | 3 +- Src/CodeSnip.dproj | 1 + Src/FrHiliterPrefs.pas | 3 +- Src/FrPrintingPrefs.pas | 5 ++-- Src/FrRTFPreview.pas | 3 +- Src/FrSourcePrefs.pas | 4 +-- Src/UPrintEngine.pas | 3 +- Src/URTFUtils.pas | 37 +----------------------- 9 files changed, 68 insertions(+), 44 deletions(-) create mode 100644 Src/ClassHelpers.RichEdit.pas diff --git a/Src/ClassHelpers.RichEdit.pas b/Src/ClassHelpers.RichEdit.pas new file mode 100644 index 000000000..fc6f1f70b --- /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 ARTF: TRTF); + end; + +implementation + +uses + // Delphi + SysUtils, + Classes; + +{ TRichEditHelper } + +procedure TRichEditHelper.Load(const ARTF: TRTF); +var + Stream: TStream; +begin + PlainText := False; + Stream := TMemoryStream.Create; + try + ARTF.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/CodeSnip.dpr b/Src/CodeSnip.dpr index babbd49e2..8e5662dfe 100644 --- a/Src/CodeSnip.dpr +++ b/Src/CodeSnip.dpr @@ -375,7 +375,8 @@ uses FmRegisterCompilersDlg in 'FmRegisterCompilersDlg.pas' {RegisterCompilersDlg}, ClassHelpers.UGraphics in 'ClassHelpers.UGraphics.pas', ClassHelpers.UActions in 'ClassHelpers.UActions.pas', - USaveInfoMgr in 'USaveInfoMgr.pas'; + USaveInfoMgr in 'USaveInfoMgr.pas', + ClassHelpers.RichEdit in 'ClassHelpers.RichEdit.pas'; // Include resources {$Resource ExternalObj.tlb} // Type library file diff --git a/Src/CodeSnip.dproj b/Src/CodeSnip.dproj index 41e93eb81..e430334ce 100644 --- a/Src/CodeSnip.dproj +++ b/Src/CodeSnip.dproj @@ -582,6 +582,7 @@ + Base diff --git a/Src/FrHiliterPrefs.pas b/Src/FrHiliterPrefs.pas index e92e82fa1..a34bfff14 100644 --- a/Src/FrHiliterPrefs.pas +++ b/Src/FrHiliterPrefs.pas @@ -178,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; @@ -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/FrPrintingPrefs.pas b/Src/FrPrintingPrefs.pas index f0fbfc9eb..f825f511e 100644 --- a/Src/FrPrintingPrefs.pas +++ b/Src/FrPrintingPrefs.pas @@ -101,9 +101,10 @@ implementation // Delphi SysUtils, Windows, Graphics, Math, ComCtrls, // Project + ClassHelpers.RichEdit, FmPreferencesDlg, Hiliter.UAttrs, Hiliter.UHiliters, IntfCommon, UColours, UConsts, UEncodings, UFontHelper, UKeysHelper, UPrintInfo, URTFBuilder, - URTFStyles, URTFUtils, UStrUtils, UUtils; + URTFStyles, UStrUtils, UUtils; {$R *.dfm} @@ -379,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/FrRTFPreview.pas b/Src/FrRTFPreview.pas index 05edcd01e..a542a75a1 100644 --- a/Src/FrRTFPreview.pas +++ b/Src/FrRTFPreview.pas @@ -59,6 +59,7 @@ implementation uses // Project + ClassHelpers.RichEdit, URTFUtils; @@ -80,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(TRTF.Create(DocContent)); end; procedure TRTFPreviewFrame.SetPopupMenu(const Menu: TPopupMenu); diff --git a/Src/FrSourcePrefs.pas b/Src/FrSourcePrefs.pas index 2fbe5c77f..48ccb606d 100644 --- a/Src/FrSourcePrefs.pas +++ b/Src/FrSourcePrefs.pas @@ -112,6 +112,7 @@ implementation // Delphi SysUtils, Math, // Project + ClassHelpers.RichEdit, FmPreferencesDlg, Hiliter.UAttrs, Hiliter.UFileHiliter, Hiliter.UHiliters, IntfCommon, UConsts, UCtrlArranger, URTFUtils; @@ -358,8 +359,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; diff --git a/Src/UPrintEngine.pas b/Src/UPrintEngine.pas index 30e1a3983..7c5e60876 100644 --- a/Src/UPrintEngine.pas +++ b/Src/UPrintEngine.pas @@ -63,6 +63,7 @@ implementation // Delphi Printers, // Project + ClassHelpers.RichEdit, UMeasurement, UPrintInfo; @@ -102,7 +103,7 @@ procedure TPrintEngine.Print(const Document: TRTF); sDefTitle = 'CodeSnip document'; // default document title begin // Load document into engine - TRichEditHelper.Load(RichEdit, Document); + RichEdit.Load(Document); // Set up page margins PrintMargins := GetPrintMargins; RichEdit.PageRect := Rect( diff --git a/Src/URTFUtils.pas b/Src/URTFUtils.pas index e567184cc..6dffc5b76 100644 --- a/Src/URTFUtils.pas +++ b/Src/URTFUtils.pas @@ -17,7 +17,7 @@ interface uses // Delphi - SysUtils, Classes, ComCtrls, + SysUtils, Classes, // Project UEncodings; @@ -130,20 +130,6 @@ TRTF = record /// Class of exception raised by TRTF ERTF = class(Exception); -type - /// Static method record that assists in working with rich edit - /// VCL controls. - TRichEditHelper = record - public - /// Loads RTF code into a rich edit control, replacing existing - /// content. - /// TRichEdit [in] Rich edit control. - /// TRTF [in] Contains rich text code to be loaded. - /// - class procedure Load(const RE: TRichEdit; const RTF: TRTF); static; - end; - - /// Returns a parameterless RTF control word of given kind. function RTFControl(const Ctrl: TRTFControl): ASCIIString; overload; @@ -184,8 +170,6 @@ implementation uses - // Delphi - Windows, RichEdit, // Project UExceptions; @@ -372,24 +356,5 @@ function TRTF.ToString: UnicodeString; Result := TEncoding.ASCII.GetString(fData); end; -{ TRichEditHelper } - -class procedure TRichEditHelper.Load(const RE: TRichEdit; const RTF: TRTF); -var - Stream: TStream; -begin - RE.PlainText := False; - Stream := TMemoryStream.Create; - try - RTF.ToStream(Stream); - Stream.Position := 0; - // must set MaxLength or long documents may not display - RE.MaxLength := Stream.Size; - RE.Lines.LoadFromStream(Stream, TEncoding.ASCII); - finally - Stream.Free; - end; -end; - end. From 26a7b998a1dcf7b1ce1c5d479ab728650b671e6c Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 18 Apr 2025 08:18:46 +0100 Subject: [PATCH 266/330] Rename TRTF as TRTFMarkup Also renamed some related variables etc. Updated all affected code. --- Src/ClassHelpers.RichEdit.pas | 6 +++--- Src/FrHiliterPrefs.pas | 4 ++-- Src/FrRTFPreview.pas | 2 +- Src/FrSourcePrefs.pas | 8 +++++--- Src/UCopyViewMgr.pas | 6 +++--- Src/UPrintDocuments.pas | 14 +++++++------- Src/UPrintEngine.pas | 4 ++-- Src/UPrintMgr.pas | 2 +- Src/URTFBuilder.pas | 6 +++--- Src/URTFUtils.pas | 32 ++++++++++++++++---------------- Src/USaveInfoMgr.pas | 6 +++--- 11 files changed, 46 insertions(+), 44 deletions(-) diff --git a/Src/ClassHelpers.RichEdit.pas b/Src/ClassHelpers.RichEdit.pas index fc6f1f70b..f82600e6f 100644 --- a/Src/ClassHelpers.RichEdit.pas +++ b/Src/ClassHelpers.RichEdit.pas @@ -21,7 +21,7 @@ interface type TRichEditHelper = class helper for TRichEdit public - procedure Load(const ARTF: TRTF); + procedure Load(const ARTFMarkup: TRTFMarkup); end; implementation @@ -33,14 +33,14 @@ implementation { TRichEditHelper } -procedure TRichEditHelper.Load(const ARTF: TRTF); +procedure TRichEditHelper.Load(const ARTFMarkup: TRTFMarkup); var Stream: TStream; begin PlainText := False; Stream := TMemoryStream.Create; try - ARTF.ToStream(Stream); + ARTFMarkup.ToStream(Stream); Stream.Position := 0; // must set MaxLength or long documents may not display MaxLength := Stream.Size; diff --git a/Src/FrHiliterPrefs.pas b/Src/FrHiliterPrefs.pas index a34bfff14..0910a505f 100644 --- a/Src/FrHiliterPrefs.pas +++ b/Src/FrHiliterPrefs.pas @@ -131,7 +131,7 @@ THiliterPrefsFrame = class(TPrefsBaseFrame) /// Generates and returns RTF representation of currently selected /// highlighter element. /// This RTF is used to display elememt in preview pane. - function GenerateRTF: TRTF; + function GenerateRTF: TRTFMarkup; public /// Constructs frame instance and initialises controls. /// TComponent [in] Component that owns the frame. @@ -479,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 diff --git a/Src/FrRTFPreview.pas b/Src/FrRTFPreview.pas index a542a75a1..16eb4f70b 100644 --- a/Src/FrRTFPreview.pas +++ b/Src/FrRTFPreview.pas @@ -81,7 +81,7 @@ procedure TRTFPreviewFrame.LoadContent(const DocContent: TEncodedData); @param DocContent [in] Valid RTF document to be displayed. } begin - reView.Load(TRTF.Create(DocContent)); + reView.Load(TRTFMarkup.Create(DocContent)); end; procedure TRTFPreviewFrame.SetPopupMenu(const Menu: TPopupMenu); diff --git a/Src/FrSourcePrefs.pas b/Src/FrSourcePrefs.pas index 48ccb606d..6b925973b 100644 --- a/Src/FrSourcePrefs.pas +++ b/Src/FrSourcePrefs.pas @@ -160,7 +160,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. } @@ -400,12 +400,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/UCopyViewMgr.pas b/Src/UCopyViewMgr.pas index 4db32bd2c..27c329634 100644 --- a/Src/UCopyViewMgr.pas +++ b/Src/UCopyViewMgr.pas @@ -66,20 +66,20 @@ class procedure TCopyViewMgr.Execute(View: IView); var Clip: TClipboardHelper; // object used to update clipboard UnicodeText: UnicodeString; // Unicode plain text representation of view - RTF: TRTF; // rich text representation of view + RTFMarkup: TRTFMarkup; // rich text representation of view begin Assert(Assigned(View), ClassName + '.Execute: View is nil'); Assert(CanHandleView(View), ClassName + '.Execute: View not supported'); // Generate plain text and rich text representation of view UnicodeText := GeneratePlainText(View).ToString; - RTF := TRTF.Create(GenerateRichText(View)); + RTFMarkup := TRTFMarkup.Create(GenerateRichText(View)); // Open clipboard and add both plain and rich text representations of snippet Clip := TClipboardHelper.Create; try Clip.Open; try Clip.AddUnicodeText(UnicodeText); - Clip.AddRTF(RTF.ToRTFCode); + Clip.AddRTF(RTFMarkup.ToRTFCode); finally Clip.Close; end; diff --git a/Src/UPrintDocuments.pas b/Src/UPrintDocuments.pas index b98950def..402971a5c 100644 --- a/Src/UPrintDocuments.pas +++ b/Src/UPrintDocuments.pas @@ -31,7 +31,7 @@ interface IPrintDocument = interface(IInterface) ['{56E4CA97-7F04-427A-A95F-03CE55910DC0}'] /// Generates and returns print document. - function Generate: TRTF; + function Generate: TRTFMarkup; end; type @@ -53,7 +53,7 @@ TSnippetPrintDocument = class(TInterfacedObject, constructor Create(const Snippet: TSnippet); /// Generates and returns print document. /// Method of IPrintDocument. - function Generate: TRTF; + function Generate: TRTFMarkup; end; type @@ -72,7 +72,7 @@ TCategoryPrintDocument = class(TInterfacedObject, constructor Create(const Category: TCategory); /// Generates and returns print document. /// Method of IPrintDocument. - function Generate: TRTF; + function Generate: TRTFMarkup; end; implementation @@ -91,7 +91,7 @@ constructor TSnippetPrintDocument.Create(const Snippet: TSnippet); fSnippet := Snippet; end; -function TSnippetPrintDocument.Generate: TRTF; +function TSnippetPrintDocument.Generate: TRTFMarkup; var Doc: TRTFSnippetDoc; // object that renders snippet document in RTF begin @@ -99,7 +99,7 @@ function TSnippetPrintDocument.Generate: TRTF; GetHiliteAttrs, poUseColor in PrintInfo.PrintOptions ); try - Result := TRTF.Create(Doc.Generate(fSnippet)); + Result := TRTFMarkup.Create(Doc.Generate(fSnippet)); finally Doc.Free; end; @@ -127,13 +127,13 @@ constructor TCategoryPrintDocument.Create(const Category: TCategory); fCategory := Category; end; -function TCategoryPrintDocument.Generate: TRTF; +function TCategoryPrintDocument.Generate: TRTFMarkup; var Doc: TRTFCategoryDoc; // object that renders category document in RTF begin Doc := TRTFCategoryDoc.Create(poUseColor in PrintInfo.PrintOptions); try - Result := TRTF.Create(Doc.Generate(fCategory)); + Result := TRTFMarkup.Create(Doc.Generate(fCategory)); finally Doc.Free; end; diff --git a/Src/UPrintEngine.pas b/Src/UPrintEngine.pas index 7c5e60876..318940e4c 100644 --- a/Src/UPrintEngine.pas +++ b/Src/UPrintEngine.pas @@ -48,7 +48,7 @@ TPrintEngine = class(THiddenRichEdit) function GetPrintMargins: TPrintMargins; public /// Prints given RTF document. - procedure Print(const Document: TRTF); + procedure Print(const Document: TRTFMarkup); /// Title of document that appears in print spooler. /// A default title is used if Title is not set or is set to /// empty string. @@ -95,7 +95,7 @@ function TPrintEngine.GetPrintMargins: TPrintMargins; Result.Bottom := InchesToPixelsY(MMToInches(PrintInfo.PageMargins.Bottom)); end; -procedure TPrintEngine.Print(const Document: TRTF); +procedure TPrintEngine.Print(const Document: TRTFMarkup); var PrintMargins: TPrintMargins; // page margins DocTitle: string; // document title for print spooler diff --git a/Src/UPrintMgr.pas b/Src/UPrintMgr.pas index 4c72fc589..c361d2ae8 100644 --- a/Src/UPrintMgr.pas +++ b/Src/UPrintMgr.pas @@ -85,7 +85,7 @@ class function TPrintMgr.CanPrint(ViewItem: IView): Boolean; procedure TPrintMgr.DoPrint; var PrintEngine: TPrintEngine; // object that prints the print document - Document: TRTF; // generated print document + Document: TRTFMarkup; // generated print document begin PrintEngine := TPrintEngine.Create; try diff --git a/Src/URTFBuilder.pas b/Src/URTFBuilder.pas index c0be9ede7..200e80fa3 100644 --- a/Src/URTFBuilder.pas +++ b/Src/URTFBuilder.pas @@ -189,7 +189,7 @@ TRTFBuilder = class(TObject) /// according to given RTF style. procedure ApplyStyle(const Style: TRTFStyle); /// Generates RTF code for whole document. - function Render: TRTF; + function Render: TRTFMarkup; /// Table of colours used in document. property ColourTable: TRTFColourTable read fColourTable write fColourTable; @@ -320,9 +320,9 @@ procedure TRTFBuilder.EndPara; fInControls := False; end; -function TRTFBuilder.Render: TRTF; +function TRTFBuilder.Render: TRTFMarkup; begin - Result := TRTF.Create(AsString); + Result := TRTFMarkup.Create(AsString); end; procedure TRTFBuilder.ResetCharStyle; diff --git a/Src/URTFUtils.pas b/Src/URTFUtils.pas index 6dffc5b76..8d5778fb0 100644 --- a/Src/URTFUtils.pas +++ b/Src/URTFUtils.pas @@ -75,7 +75,7 @@ interface type /// Encapsulate rich text markup code. /// Valid rich text markup contains only ASCII characters. - TRTF = record + TRTFMarkup = record strict private var /// Byte array that stores RTF code as bytes @@ -127,8 +127,8 @@ TRTF = record end; type - /// Class of exception raised by TRTF - ERTF = class(Exception); + /// Class of exception raised by TRTFMarkup + ERTFMarkup = class(Exception); /// Returns a parameterless RTF control word of given kind. function RTFControl(const Ctrl: TRTFControl): ASCIIString; overload; @@ -277,9 +277,9 @@ function RTFUnicodeSafeDestination(const DestCtrl: TRTFControl; end; end; -{ TRTF } +{ TRTFMarkup } -constructor TRTF.Create(const AStream: TStream; const ReadAll: Boolean); +constructor TRTFMarkup.Create(const AStream: TStream; const ReadAll: Boolean); var ByteCount: Integer; begin @@ -290,12 +290,12 @@ constructor TRTF.Create(const AStream: TStream; const ReadAll: Boolean); AStream.ReadBuffer(Pointer(fData)^, ByteCount); end; -constructor TRTF.Create(const ABytes: TBytes); +constructor TRTFMarkup.Create(const ABytes: TBytes); begin fData := Copy(ABytes); end; -constructor TRTF.Create(const AData: TEncodedData); +constructor TRTFMarkup.Create(const AData: TEncodedData); resourcestring sErrorMsg = 'Encoded data must contain only valid ASCII characters'; var @@ -307,41 +307,41 @@ constructor TRTF.Create(const AData: TEncodedData); begin DataStr := AData.ToString; if not IsValidRTFCode(DataStr) then - raise ERTF.Create(sErrorMsg); + raise ERTFMarkup.Create(sErrorMsg); fData := TEncoding.ASCII.GetBytes(DataStr); end; end; -constructor TRTF.Create(const ARTFCode: ASCIIString); +constructor TRTFMarkup.Create(const ARTFCode: ASCIIString); begin fData := BytesOf(ARTFCode); end; -constructor TRTF.Create(const AStr: UnicodeString); +constructor TRTFMarkup.Create(const AStr: UnicodeString); resourcestring sErrorMsg = 'String "%s" must contain only valid ASCII characters'; begin if not IsValidRTFCode(AStr) then - raise ERTF.CreateFmt(sErrorMsg, [AStr]); + raise ERTFMarkup.CreateFmt(sErrorMsg, [AStr]); fData := TEncoding.ASCII.GetBytes(AStr); end; -function TRTF.IsValidRTFCode(const AStr: UnicodeString): Boolean; +function TRTFMarkup.IsValidRTFCode(const AStr: UnicodeString): Boolean; begin Result := EncodingSupportsString(AStr, TEncoding.ASCII); end; -function TRTF.ToBytes: TBytes; +function TRTFMarkup.ToBytes: TBytes; begin Result := Copy(fData); end; -function TRTF.ToRTFCode: ASCIIString; +function TRTFMarkup.ToRTFCode: ASCIIString; begin Result := BytesToASCIIString(fData); end; -procedure TRTF.ToStream(const Stream: TStream; const Overwrite: Boolean); +procedure TRTFMarkup.ToStream(const Stream: TStream; const Overwrite: Boolean); begin if Overwrite then begin @@ -351,7 +351,7 @@ procedure TRTF.ToStream(const Stream: TStream; const Overwrite: Boolean); Stream.WriteBuffer(Pointer(fData)^, Length(fData)); end; -function TRTF.ToString: UnicodeString; +function TRTFMarkup.ToString: UnicodeString; begin Result := TEncoding.ASCII.GetString(fData); end; diff --git a/Src/USaveInfoMgr.pas b/Src/USaveInfoMgr.pas index 123f04a99..133b7cbce 100644 --- a/Src/USaveInfoMgr.pas +++ b/Src/USaveInfoMgr.pas @@ -73,14 +73,14 @@ class function TSaveInfoMgr.CanHandleView(View: IView): Boolean; class procedure TSaveInfoMgr.Execute(View: IView); var FileName: string; - RTF: TRTF; + RTFMarkup: TRTFMarkup; begin Assert(Assigned(View), 'TSaveInfoMgr.Execute: View is nil'); Assert(CanHandleView(View), 'TSaveInfoMgr.Execute: View not supported'); if not TryGetFileNameFromUser(FileName) then Exit; - RTF := TRTF.Create(GenerateRichText(View)); - TFileIO.WriteAllBytes(FileName, RTF.ToBytes); + RTFMarkup := TRTFMarkup.Create(GenerateRichText(View)); + TFileIO.WriteAllBytes(FileName, RTFMarkup.ToBytes); end; class function TSaveInfoMgr.GenerateRichText(View: IView): TEncodedData; From 4e0c6210fb2ea085b77340c1e28846bc95f50a4b Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 18 Apr 2025 10:02:43 +0100 Subject: [PATCH 267/330] Group all RTF*** methods into TRTF record All methods with names beginning with RTF in URTFUtils were replaced with similarly named static class methods of the new TRTF record. The functionality of these replacement methods is identical to the original routines. The constant array mapping RTF control IDs to their names was also moved inside TRTF and renamed. Updated affected code in URTFBuilder. --- Src/URTFBuilder.pas | 62 +++++++------- Src/URTFUtils.pas | 203 +++++++++++++++++++++++++++----------------- 2 files changed, 158 insertions(+), 107 deletions(-) diff --git a/Src/URTFBuilder.pas b/Src/URTFBuilder.pas index 200e80fa3..3c8a07bb1 100644 --- a/Src/URTFBuilder.pas +++ b/Src/URTFBuilder.pas @@ -234,7 +234,7 @@ procedure TRTFBuilder.AddText(const Text: string); fInControls := False; end; // Add text, escaping disallowed characters - AppendBody(RTFMakeSafeText(Text, fCodePage)); + AppendBody(TRTF.MakeSafeText(Text, fCodePage)); end; procedure TRTFBuilder.AppendBody(const S: ASCIIString); @@ -269,7 +269,7 @@ procedure TRTFBuilder.BeginGroup; procedure TRTFBuilder.ClearParaFormatting; begin - AddControl(RTFControl(rcPard)); + AddControl(TRTF.ControlWord(rcPard)); end; constructor TRTFBuilder.Create(const CodePage: Integer); @@ -296,11 +296,11 @@ destructor TRTFBuilder.Destroy; function TRTFBuilder.DocHeader: ASCIIString; begin - Result := RTFControl(rcRTF, cRTFVersion) - + RTFControl(rcAnsi) - + RTFControl(rcAnsiCodePage, fCodePage) - + RTFControl(rcDefFontNum, DefaultFontIdx) - + RTFControl(rcDefLanguage, DefaultLanguageID) + Result := TRTF.ControlWord(rcRTF, cRTFVersion) + + TRTF.ControlWord(rcAnsi) + + TRTF.ControlWord(rcAnsiCodePage, fCodePage) + + TRTF.ControlWord(rcDefFontNum, DefaultFontIdx) + + TRTF.ControlWord(rcDefLanguage, DefaultLanguageID) + fFontTable.AsString + fColourTable.AsString + fDocProperties.AsString @@ -315,7 +315,7 @@ procedure TRTFBuilder.EndGroup; procedure TRTFBuilder.EndPara; begin - AddControl(RTFControl(rcPar)); + AddControl(TRTF.ControlWord(rcPar)); AppendBody(EOL); fInControls := False; end; @@ -327,12 +327,12 @@ function TRTFBuilder.Render: TRTFMarkup; procedure TRTFBuilder.ResetCharStyle; begin - AddControl(RTFControl(rcPlain)); + AddControl(TRTF.ControlWord(rcPlain)); end; procedure TRTFBuilder.SetColour(const Colour: TColor); begin - AddControl(RTFControl(rcForeColorNum, fColourTable.ColourRef(Colour))); + AddControl(TRTF.ControlWord(rcForeColorNum, fColourTable.ColourRef(Colour))); end; procedure TRTFBuilder.SetFont(const FontName: string); @@ -342,28 +342,28 @@ procedure TRTFBuilder.SetFont(const FontName: string); // We don't emit control if this is default font FontIdx := fFontTable.FontRef(FontName); if FontIdx <> DefaultFontIdx then - AddControl(RTFControl(rcFontNum, FontIdx)); + AddControl(TRTF.ControlWord(rcFontNum, FontIdx)); end; procedure TRTFBuilder.SetFontSize(const Points: Double); begin - AddControl(RTFControl(rcFontSize, FloatToInt(2 * Points))); + AddControl(TRTF.ControlWord(rcFontSize, FloatToInt(2 * Points))); end; procedure TRTFBuilder.SetFontStyle(const Style: TFontStyles); begin if fsBold in Style then - AddControl(RTFControl(rcBold)); + AddControl(TRTF.ControlWord(rcBold)); if fsItalic in Style then - AddControl(RTFControl(rcItalic)); + AddControl(TRTF.ControlWord(rcItalic)); if fsUnderline in Style then - AddControl(RTFControl(rcUnderline)); + AddControl(TRTF.ControlWord(rcUnderline)); end; procedure TRTFBuilder.SetIndents(const LeftIndent, FirstLineOffset: SmallInt); begin - AddControl(RTFControl(rcLeftIndent, LeftIndent)); - AddControl(RTFControl(rcFirstLineOffset, FirstLineOffset)); + AddControl(TRTF.ControlWord(rcLeftIndent, LeftIndent)); + AddControl(TRTF.ControlWord(rcFirstLineOffset, FirstLineOffset)); end; procedure TRTFBuilder.SetParaSpacing(const Spacing: TRTFParaSpacing); @@ -371,10 +371,10 @@ procedure TRTFBuilder.SetParaSpacing(const Spacing: TRTFParaSpacing); TwipsPerPoint = 20; // Note: 20 Twips in a point begin AddControl( - RTFControl(rcSpaceBefore, FloatToInt(TwipsPerPoint * Spacing.Before)) + TRTF.ControlWord(rcSpaceBefore, FloatToInt(TwipsPerPoint * Spacing.Before)) ); AddControl( - RTFControl(rcSpaceAfter, FloatToInt(TwipsPerPoint * Spacing.After)) + TRTF.ControlWord(rcSpaceAfter, FloatToInt(TwipsPerPoint * Spacing.After)) ); end; @@ -383,7 +383,7 @@ procedure TRTFBuilder.SetTabStops(const TabStops: array of SmallInt); Tab: SmallInt; begin for Tab in TabStops do - AddControl(RTFControl(rcTabStop, Tab)); + AddControl(TRTF.ControlWord(rcTabStop, Tab)); end; { TRTFFontTable } @@ -420,15 +420,15 @@ function TRTFFontTable.AsString: ASCIIString; Idx: Integer; // loops thru fonts in table Font: TRTFFont; // reference to a font in table begin - Result := '{' + RTFControl(rcFontTable); + Result := '{' + TRTF.ControlWord(rcFontTable); for Idx := 0 to Pred(fFonts.Count) do begin Font := fFonts[Idx]; Result := Result + '{' - + RTFControl(rcFontNum, Idx) - + RTFControl(rcFontPitch, 1) - + RTFControl(cGenericFonts[Font.Generic]) - + RTFControl(rcFontCharset, Font.Charset) + + TRTF.ControlWord(rcFontNum, Idx) + + TRTF.ControlWord(rcFontPitch, 1) + + TRTF.ControlWord(cGenericFonts[Font.Generic]) + + TRTF.ControlWord(rcFontCharset, Font.Charset) + ' ' + StringToASCIIString(Font.Name) + '}'; @@ -488,7 +488,7 @@ function TRTFColourTable.AsString: ASCIIString; begin // Begin table Result := '{' - + RTFControl(rcColorTable) + + TRTF.ControlWord(rcColorTable) + ' '; // Add entry for each colour for Colour in fColours do @@ -497,9 +497,9 @@ function TRTFColourTable.AsString: ASCIIString; begin RGB := ColorToRGB(Colour); Result := Result - + RTFControl(rcRed, GetRValue(RGB)) - + RTFControl(rcGreen, GetGValue(RGB)) - + RTFControl(rcBlue, GetBValue(RGB)) + + TRTF.ControlWord(rcRed, GetRValue(RGB)) + + TRTF.ControlWord(rcGreen, GetGValue(RGB)) + + TRTF.ControlWord(rcBlue, GetBValue(RGB)) + ';' end else @@ -540,9 +540,9 @@ function TRTFDocProperties.AsString: ASCIIString; Exit; end; // Start with \info control word in group - Result := '{' + RTFControl(rcInfo); + Result := '{' + TRTF.ControlWord(rcInfo); if fTitle <> '' then - Result := Result + RTFUnicodeSafeDestination(rcTitle, fTitle, fCodePage); + Result := Result + TRTF.UnicodeSafeDestination(rcTitle, fTitle, fCodePage); // Close \info group Result := Result + '}'; end; diff --git a/Src/URTFUtils.pas b/Src/URTFUtils.pas index 8d5778fb0..66ca9c8e6 100644 --- a/Src/URTFUtils.pas +++ b/Src/URTFUtils.pas @@ -72,6 +72,90 @@ interface rcTabStop // sets a tab stop in twips ); +type + /// Container for related methods for generating valid RTF control + /// words and destinations. + TRTF = record + strict private + const + /// Map of RTF control ids to control words. + Controls: array[TRTFControl] of ASCIIString = ( + 'rtf', 'ansi', 'ansicpg', 'deff', 'deflang', 'fonttbl', 'fprq', + 'fcharset', 'fnil', 'froman', 'fswiss', 'fmodern', 'fscript', 'fdecor', + 'ftech', 'colortbl', 'red', 'green', 'blue', 'info', 'title', 'pard', + 'par', 'plain', 'f', 'cf', 'b', 'i', 'ul', 'fs', 'sb', 'sa', 'u', 'upr', + 'ud', '*', 'fi', 'li', 'tx' + ); + strict private + + /// Returns an RTF escape sequence for an ASCII character. + /// + /// AnsiChar [in] Character to be escaped. + /// ASCIIString. The required escape sequence. + /// ACh should be a valid ASCII character, but this is not + /// checked. + class function Escape(const ACh: AnsiChar): ASCIIString; static; + + /// Returns an RTF hex escape sequence for a single byte + /// character. + /// AnsiChar [in] Character to be escaped. + /// ASCIIString. The required hex escape sequence. + /// + class function HexEscape(const Ch: AnsiChar): ASCIIString; static; + + public + + /// Returns a parameterless RTF control word. + /// TRTFControl [in] Identifies the required + /// control. + /// ASCIIString. The required control word. + class function ControlWord(const ACtrlID: TRTFControl): ASCIIString; + overload; static; + + /// Returns a parameterised RTF control word. + /// TRTFControl [in] Identifies the required + /// control. + /// Int16 [in] The control's parameter value. + /// + /// ASCIIString. The required control word. + /// control word identified by Ctrl with the parameter specified + class function ControlWord(const ACtrlID: TRTFControl; const AParam: Int16): + ASCIIString; overload; static; + + /// Converts Unicode text into valid RTF when encoded in a given + /// ANSI code page. + /// string [in] The Unicode text to be + /// processed. + /// Integer [in] ANSI code to be used for + /// encoding the Unicode text. + /// ASCIIString. Valid RTF code for the given code page. + /// + /// Converted characters are escaped if necessary. Any characters + /// that are not valid in the required code page are encoded in a Unicode + /// RTF control word with ? as the non-Unicode fallback. + class function MakeSafeText(const AText: string; const ACodePage: Integer): + ASCIIString; static; + + /// Creates an RTF destination in a Unicode safe way. + /// TRTFControl [in] Required destination + /// control. + /// string [in] Unicode text to be included + /// in the destination. + /// Integer [in] ANSI Code page to use for + /// encoding the Unicode text. + /// ASCIIString. Destination RTF, containing ANSI and + /// Unicode sub-destinations if necessary. + /// If ADestText contains only characters supported by + /// ACodePage then a single, normal destination is returned, + /// containing the encoded text, escaped as necessary. Should any + /// characters in ADestText be incompatible with the code page then + /// two sub-destinations are created, one containing Unicode characters and + /// the other containing ANSI text, with unknown characters flagged with + /// "error" characters such as ?. + class function UnicodeSafeDestination(const ADestCtrl: TRTFControl; + const ADestText: string; const ACodePage: Integer): ASCIIString; static; + end; + type /// Encapsulate rich text markup code. /// Valid rich text markup contains only ASCII characters. @@ -130,41 +214,6 @@ TRTFMarkup = record /// Class of exception raised by TRTFMarkup ERTFMarkup = class(Exception); -/// Returns a parameterless RTF control word of given kind. -function RTFControl(const Ctrl: TRTFControl): ASCIIString; overload; - -/// Returns a parameterised RTF control word of given kind with given -/// parameter value. -function RTFControl(const Ctrl: TRTFControl; - const Param: SmallInt): ASCIIString; overload; - -/// Returns an RTF escape sequence for the given ANSI character. -/// -function RTFEscape(const Ch: AnsiChar): ASCIIString; - -/// returns an RTF hexadecimal escape sequence for given ANSI -/// character. -function RTFHexEscape(const Ch: AnsiChar): ASCIIString; - -/// Encodes given text for given code page so that any incompatible -/// characters are replaced by suitable control words. -function RTFMakeSafeText(const TheText: string; const CodePage: Integer): - ASCIIString; - -/// Creates an RTF destination in a Unicode safe way. -/// TRTFControl [in] Destination control. -/// string [in] Text of destination. -/// Integer [in] Code page to use for encoding. -/// ASCIIString. Destination RTF, containing ANSI and Unicode -/// sub-destinations if necessary. -/// If DestText contains only characters supported by the given code -/// page then a normal destination is returned, containing only the given text. -/// Should any characters in DestText be incompatible with the code page then -/// two sub-destinations are created, one ANSI only and the other containing -/// Unicode characters. -function RTFUnicodeSafeDestination(const DestCtrl: TRTFControl; - const DestText: string; const CodePage: Integer): ASCIIString; - implementation @@ -174,104 +223,106 @@ implementation UExceptions; -const - // Map of RTF control ids to control word - cControls: array[TRTFControl] of ASCIIString = ( - 'rtf', 'ansi', 'ansicpg', 'deff', 'deflang', 'fonttbl', 'fprq', 'fcharset', - 'fnil', 'froman', 'fswiss', 'fmodern', 'fscript', 'fdecor', 'ftech', - 'colortbl', 'red', 'green', 'blue', 'info', 'title', 'pard', 'par', 'plain', - 'f', 'cf', 'b', 'i', 'ul', 'fs', 'sb', 'sa', 'u', 'upr', 'ud', '*', - 'fi', 'li', 'tx' - ); +{ TRTF } -function RTFControl(const Ctrl: TRTFControl): ASCIIString; +class function TRTF.ControlWord(const ACtrlID: TRTFControl): ASCIIString; begin - Result := '\' + cControls[Ctrl]; + Result := '\' + Controls[ACtrlID]; end; -function RTFControl(const Ctrl: TRTFControl; - const Param: SmallInt): ASCIIString; +class function TRTF.ControlWord(const ACtrlID: TRTFControl; + const AParam: Int16): ASCIIString; begin - Result := RTFControl(Ctrl) + StringToASCIIString(IntToStr(Param)); + Result := ControlWord(ACtrlID) + StringToASCIIString(IntToStr(AParam)); end; -function RTFEscape(const Ch: AnsiChar): ASCIIString; +class function TRTF.Escape(const ACh: AnsiChar): ASCIIString; begin - Result := AnsiChar('\') + Ch; + Result := AnsiChar('\') + ACh; end; -function RTFHexEscape(const Ch: AnsiChar): ASCIIString; +class function TRTF.HexEscape(const Ch: AnsiChar): ASCIIString; begin Result := StringToASCIIString('\''' + IntToHex(Ord(Ch), 2)); end; -function RTFMakeSafeText(const TheText: string; const CodePage: Integer): +class function TRTF.MakeSafeText(const AText: string; const ACodePage: Integer): ASCIIString; var Ch: Char; // each Unicode character in TheText - AnsiChars: TArray; // translation of a Ch into ANSI code page + AnsiChars: TArray; // translation of a Ch into the ANSI code page AnsiCh: AnsiChar; // each ANSI char in AnsiChars begin Result := ''; - for Ch in TheText do + // Process each Unicode character in turn + for Ch in AText do begin - if WideCharToChar(Ch, CodePage, AnsiChars) then + // Convert Unicode char into one or more ANSI chars in required code page + if WideCharToChar(Ch, ACodePage, AnsiChars) then begin + // Conversion succeeded: check process each ANSI char for AnsiCh in AnsiChars do begin if (AnsiCh < #$20) or ((AnsiCh >= #$7F) and (AnsiCh <= #$FF)) then - Result := Result + RTFHexEscape(AnsiCh) + // Not an ASCII character + Result := Result + HexEscape(AnsiCh) else if (Ch = '{') or (Ch = '\') or (Ch = '}') then - Result := Result + RTFEscape(AnsiCh) + // Reserved RTF character: must be escaped + Result := Result + Escape(AnsiCh) else + // Valid character, use as is Result := Result + ASCIIString(AnsiCh); end; end else - Result := Result + RTFControl(rcUnicodeChar, SmallInt(Ord(Ch))) + ' ?'; + // Conversion failed: we store Unicode char in a Unicode control word + Result := Result + + ControlWord(rcUnicodeChar, SmallInt(Ord(Ch))) + + ' ?'; // fallback "unprintable" value end; end; -function RTFUnicodeSafeDestination(const DestCtrl: TRTFControl; - const DestText: string; const CodePage: Integer): ASCIIString; +class function TRTF.UnicodeSafeDestination(const ADestCtrl: TRTFControl; + const ADestText: string; const ACodePage: Integer): ASCIIString; - /// Makes a destination for DestCtrl using given text. + // Makes a destination for ADestCtrl using given text. function MakeDestination(const S: string): ASCIIString; begin Result := '{' - + RTFControl(DestCtrl) + ' ' - + RTFMakeSafeText(S, CodePage) + + ControlWord(ADestCtrl) + + ' ' + + MakeSafeText(S, ACodePage) + '}' end; var - Encoding: TEncoding; // encoding for CodePage - AnsiStr: string; // Unicode string containing only characters of CodePage + Encoding: TEncoding; // encoding for ACodePage + AnsiStr: string; // Unicode string containing only chars from ACodePage begin - if CodePageSupportsString(DestText, CodePage) then - // All chars of DestText supported in code page => RTF text won't have any + if CodePageSupportsString(ADestText, ACodePage) then + // All chars of ADestText supported in code page => RTF text won't have any // \u characters => we can just output destination as normal - Result := MakeDestination(DestText) + Result := MakeDestination(ADestText) else begin - // DestText contains characters not supported by code page. We create twin + // ADestText contains characters not supported by code page. We create twin // destinations, one ANSI only and the other that includes Unicode // characters. - Encoding := TMBCSEncoding.Create(CodePage); + Encoding := TMBCSEncoding.Create(ACodePage); try // Create a Unicode string that contains only characters supported in // given code page (+ some "error" characters (e.g. "?") - AnsiStr := Encoding.GetString(Encoding.GetBytes(DestText)); + AnsiStr := Encoding.GetString(Encoding.GetBytes(ADestText)); finally Encoding.Free; end; Result := '{' - + RTFControl(rcUnicodePair) + + ControlWord(rcUnicodePair) + MakeDestination(AnsiStr) // ANSI only destination + '{' - + RTFControl(rcIgnore) - + RTFControl(rcUnicodeDest) - + MakeDestination(DestText) // Unicode destinatation + + ControlWord(rcIgnore) + + ControlWord(rcUnicodeDest) + + MakeDestination(ADestText) // Unicode destinatation + '}' + '}'; end; From 36b0342b3d758d1e6142278a5cad75b381525bb9 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 18 Apr 2025 10:08:11 +0100 Subject: [PATCH 268/330] Moved RTF version const inside TRTF. The global cRTFVersion const exposed by URTFUtils was moved inside TRTF as a public const named Version. Updated code that uses this constance in URTFBuilder. --- Src/URTFBuilder.pas | 2 +- Src/URTFUtils.pas | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Src/URTFBuilder.pas b/Src/URTFBuilder.pas index 3c8a07bb1..6e03bec93 100644 --- a/Src/URTFBuilder.pas +++ b/Src/URTFBuilder.pas @@ -296,7 +296,7 @@ destructor TRTFBuilder.Destroy; function TRTFBuilder.DocHeader: ASCIIString; begin - Result := TRTF.ControlWord(rcRTF, cRTFVersion) + Result := TRTF.ControlWord(rcRTF, TRTF.Version) + TRTF.ControlWord(rcAnsi) + TRTF.ControlWord(rcAnsiCodePage, fCodePage) + TRTF.ControlWord(rcDefFontNum, DefaultFontIdx) diff --git a/Src/URTFUtils.pas b/Src/URTFUtils.pas index 66ca9c8e6..e75818c2a 100644 --- a/Src/URTFUtils.pas +++ b/Src/URTFUtils.pas @@ -22,11 +22,6 @@ interface UEncodings; -const - /// RTF version. - cRTFVersion = 1; - - type /// Enumeration containing identifiers for each supported RTF /// control word. @@ -103,6 +98,11 @@ TRTF = record /// class function HexEscape(const Ch: AnsiChar): ASCIIString; static; + public + const + /// RTF major version number. + Version = 1; + public /// Returns a parameterless RTF control word. From 57e4d74f4987ed2d4ea539e6b41133365a3dcdb9 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 18 Apr 2025 10:19:56 +0100 Subject: [PATCH 269/330] Turn on scoped enums for TRTFControls & rename elems The elements of TRTFControls in URTFUtils were stripped of their leading "rc" characters. The scoped enumeration compiler directive was switched on for TRTFControls so that the elements now have to be prefixed with the type name. Modified all affected code in URTFBuilder and URTFUtils. --- Src/URTFBuilder.pas | 74 ++++++++++++++++++++++---------------- Src/URTFUtils.pas | 88 +++++++++++++++++++++++---------------------- 2 files changed, 88 insertions(+), 74 deletions(-) diff --git a/Src/URTFBuilder.pas b/Src/URTFBuilder.pas index 6e03bec93..739b82a84 100644 --- a/Src/URTFBuilder.pas +++ b/Src/URTFBuilder.pas @@ -269,7 +269,7 @@ procedure TRTFBuilder.BeginGroup; procedure TRTFBuilder.ClearParaFormatting; begin - AddControl(TRTF.ControlWord(rcPard)); + AddControl(TRTF.ControlWord(TRTFControl.Pard)); end; constructor TRTFBuilder.Create(const CodePage: Integer); @@ -296,11 +296,11 @@ destructor TRTFBuilder.Destroy; function TRTFBuilder.DocHeader: ASCIIString; begin - Result := TRTF.ControlWord(rcRTF, TRTF.Version) - + TRTF.ControlWord(rcAnsi) - + TRTF.ControlWord(rcAnsiCodePage, fCodePage) - + TRTF.ControlWord(rcDefFontNum, DefaultFontIdx) - + TRTF.ControlWord(rcDefLanguage, DefaultLanguageID) + Result := TRTF.ControlWord(TRTFControl.RTF, TRTF.Version) + + TRTF.ControlWord(TRTFControl.Ansi) + + TRTF.ControlWord(TRTFControl.AnsiCodePage, fCodePage) + + TRTF.ControlWord(TRTFControl.DefFontNum, DefaultFontIdx) + + TRTF.ControlWord(TRTFControl.DefLanguage, DefaultLanguageID) + fFontTable.AsString + fColourTable.AsString + fDocProperties.AsString @@ -315,7 +315,7 @@ procedure TRTFBuilder.EndGroup; procedure TRTFBuilder.EndPara; begin - AddControl(TRTF.ControlWord(rcPar)); + AddControl(TRTF.ControlWord(TRTFControl.Par)); AppendBody(EOL); fInControls := False; end; @@ -327,12 +327,14 @@ function TRTFBuilder.Render: TRTFMarkup; procedure TRTFBuilder.ResetCharStyle; begin - AddControl(TRTF.ControlWord(rcPlain)); + AddControl(TRTF.ControlWord(TRTFControl.Plain)); end; procedure TRTFBuilder.SetColour(const Colour: TColor); begin - AddControl(TRTF.ControlWord(rcForeColorNum, fColourTable.ColourRef(Colour))); + AddControl( + TRTF.ControlWord(TRTFControl.ForeColorNum, fColourTable.ColourRef(Colour)) + ); end; procedure TRTFBuilder.SetFont(const FontName: string); @@ -342,28 +344,28 @@ procedure TRTFBuilder.SetFont(const FontName: string); // We don't emit control if this is default font FontIdx := fFontTable.FontRef(FontName); if FontIdx <> DefaultFontIdx then - AddControl(TRTF.ControlWord(rcFontNum, FontIdx)); + AddControl(TRTF.ControlWord(TRTFControl.FontNum, FontIdx)); end; procedure TRTFBuilder.SetFontSize(const Points: Double); begin - AddControl(TRTF.ControlWord(rcFontSize, FloatToInt(2 * Points))); + AddControl(TRTF.ControlWord(TRTFControl.FontSize, FloatToInt(2 * Points))); end; procedure TRTFBuilder.SetFontStyle(const Style: TFontStyles); begin if fsBold in Style then - AddControl(TRTF.ControlWord(rcBold)); + AddControl(TRTF.ControlWord(TRTFControl.Bold)); if fsItalic in Style then - AddControl(TRTF.ControlWord(rcItalic)); + AddControl(TRTF.ControlWord(TRTFControl.Italic)); if fsUnderline in Style then - AddControl(TRTF.ControlWord(rcUnderline)); + AddControl(TRTF.ControlWord(TRTFControl.Underline)); end; procedure TRTFBuilder.SetIndents(const LeftIndent, FirstLineOffset: SmallInt); begin - AddControl(TRTF.ControlWord(rcLeftIndent, LeftIndent)); - AddControl(TRTF.ControlWord(rcFirstLineOffset, FirstLineOffset)); + AddControl(TRTF.ControlWord(TRTFControl.LeftIndent, LeftIndent)); + AddControl(TRTF.ControlWord(TRTFControl.FirstLineOffset, FirstLineOffset)); end; procedure TRTFBuilder.SetParaSpacing(const Spacing: TRTFParaSpacing); @@ -371,10 +373,14 @@ procedure TRTFBuilder.SetParaSpacing(const Spacing: TRTFParaSpacing); TwipsPerPoint = 20; // Note: 20 Twips in a point begin AddControl( - TRTF.ControlWord(rcSpaceBefore, FloatToInt(TwipsPerPoint * Spacing.Before)) + TRTF.ControlWord( + TRTFControl.SpaceBefore, FloatToInt(TwipsPerPoint * Spacing.Before) + ) ); AddControl( - TRTF.ControlWord(rcSpaceAfter, FloatToInt(TwipsPerPoint * Spacing.After)) + TRTF.ControlWord( + TRTFControl.SpaceAfter, FloatToInt(TwipsPerPoint * Spacing.After) + ) ); end; @@ -383,7 +389,7 @@ procedure TRTFBuilder.SetTabStops(const TabStops: array of SmallInt); Tab: SmallInt; begin for Tab in TabStops do - AddControl(TRTF.ControlWord(rcTabStop, Tab)); + AddControl(TRTF.ControlWord(TRTFControl.TabStop, Tab)); end; { TRTFFontTable } @@ -413,22 +419,27 @@ function TRTFFontTable.AsString: ASCIIString; const // Map of generic font families to RTF controls cGenericFonts: array[TRTFGenericFont] of TRTFControl = ( - rcFontFamilyNil, rcFontFamilyRoman, rcFontFamilySwiss, rcFontFamilyModern, - rcFontFamilyScript, rcFontFamilyDecor, rcFontFamilyTech + TRTFControl.FontFamilyNil, // rgfDontCare + TRTFControl.FontFamilyRoman, // rgfRoman + TRTFControl.FontFamilySwiss, // rgfSwiss + TRTFControl.FontFamilyModern, // rgfModern + TRTFControl.FontFamilyScript, // rgfScript + TRTFControl.FontFamilyDecor, // rgfDecorative + TRTFControl.FontFamilyTech // rgfTechnical ); var Idx: Integer; // loops thru fonts in table Font: TRTFFont; // reference to a font in table begin - Result := '{' + TRTF.ControlWord(rcFontTable); + Result := '{' + TRTF.ControlWord(TRTFControl.FontTable); for Idx := 0 to Pred(fFonts.Count) do begin Font := fFonts[Idx]; Result := Result + '{' - + TRTF.ControlWord(rcFontNum, Idx) - + TRTF.ControlWord(rcFontPitch, 1) + + TRTF.ControlWord(TRTFControl.FontNum, Idx) + + TRTF.ControlWord(TRTFControl.FontPitch, 1) + TRTF.ControlWord(cGenericFonts[Font.Generic]) - + TRTF.ControlWord(rcFontCharset, Font.Charset) + + TRTF.ControlWord(TRTFControl.FontCharset, Font.Charset) + ' ' + StringToASCIIString(Font.Name) + '}'; @@ -488,7 +499,7 @@ function TRTFColourTable.AsString: ASCIIString; begin // Begin table Result := '{' - + TRTF.ControlWord(rcColorTable) + + TRTF.ControlWord(TRTFControl.ColorTable) + ' '; // Add entry for each colour for Colour in fColours do @@ -497,9 +508,9 @@ function TRTFColourTable.AsString: ASCIIString; begin RGB := ColorToRGB(Colour); Result := Result - + TRTF.ControlWord(rcRed, GetRValue(RGB)) - + TRTF.ControlWord(rcGreen, GetGValue(RGB)) - + TRTF.ControlWord(rcBlue, GetBValue(RGB)) + + TRTF.ControlWord(TRTFControl.Red, GetRValue(RGB)) + + TRTF.ControlWord(TRTFControl.Green, GetGValue(RGB)) + + TRTF.ControlWord(TRTFControl.Blue, GetBValue(RGB)) + ';' end else @@ -540,9 +551,10 @@ function TRTFDocProperties.AsString: ASCIIString; Exit; end; // Start with \info control word in group - Result := '{' + TRTF.ControlWord(rcInfo); + Result := '{' + TRTF.ControlWord(TRTFControl.Info); if fTitle <> '' then - Result := Result + TRTF.UnicodeSafeDestination(rcTitle, fTitle, fCodePage); + Result := Result + + TRTF.UnicodeSafeDestination(TRTFControl.Title, fTitle, fCodePage); // Close \info group Result := Result + '}'; end; diff --git a/Src/URTFUtils.pas b/Src/URTFUtils.pas index e75818c2a..dff0e59cd 100644 --- a/Src/URTFUtils.pas +++ b/Src/URTFUtils.pas @@ -23,49 +23,51 @@ interface type + {$ScopedEnums On} /// Enumeration containing identifiers for each supported RTF /// control word. TRTFControl = ( - rcRTF, // RTF version - rcAnsi, // use ANSI character set - rcAnsiCodePage, // specifies ANSI code page - rcDefFontNum, // default font number - rcDefLanguage, // default language - rcFontTable, // introduces font table - rcFontPitch, // font pitch - rcFontCharset, // font character set - rcFontFamilyNil, // unknown font family - rcFontFamilyRoman, // serif, proportional fonts - rcFontFamilySwiss, // sans-serif, proportional fonts - rcFontFamilyModern, // fixed pitch serif and sans-serif fonts - rcFontFamilyScript, // script fonts - rcFontFamilyDecor, // decorative fonts - rcFontFamilyTech, // technical, symbol and maths fonts - rcColorTable, // introduces colour table - rcRed, // defines red colour component - rcGreen, // defines gree colour component - rcBlue, // defines blue colour component - rcInfo, // introduces information group - rcTitle, // sets document title - rcPard, // resets to default paragraph format - rcPar, // begins new paragraph - rcPlain, // reset font (character) formatting properties - rcFontNum, // font number (index to font table) - rcForeColorNum, // foreground colour number (index to colour table) - rcBold, // sets or toggles bold style - rcItalic, // sets or toggles italic style - rcUnderline, // sets or toggles underline style - rcFontSize, // font size in 1/2 points - rcSpaceBefore, // space before paragraphs in twips - rcSpaceAfter, // space after paragraph in twips - rcUnicodeChar, // defines a Unicode character as signed 16bit value - rcUnicodePair, // introduces pair of ANSI and Unicode destinations - rcUnicodeDest, // introduces Unicode destination - rcIgnore, // denotes following control can be ignored - rcFirstLineOffset, // first line indent in twips (relative to \li) - rcLeftIndent, // left indent in twips - rcTabStop // sets a tab stop in twips + RTF, // RTF version + Ansi, // use ANSI character set + AnsiCodePage, // specifies ANSI code page + DefFontNum, // default font number + DefLanguage, // default language + FontTable, // introduces font table + FontPitch, // font pitch + FontCharset, // font character set + FontFamilyNil, // unknown font family + FontFamilyRoman, // serif, proportional fonts + FontFamilySwiss, // sans-serif, proportional fonts + FontFamilyModern, // fixed pitch serif and sans-serif fonts + FontFamilyScript, // script fonts + FontFamilyDecor, // decorative fonts + FontFamilyTech, // technical, symbol and maths fonts + ColorTable, // introduces colour table + Red, // defines red colour component + Green, // defines gree colour component + Blue, // defines blue colour component + Info, // introduces information group + Title, // sets document title + Pard, // resets to default paragraph format + Par, // begins new paragraph + Plain, // reset font (character) formatting properties + FontNum, // font number (index to font table) + ForeColorNum, // foreground colour number (index to colour table) + Bold, // sets or toggles bold style + Italic, // sets or toggles italic style + Underline, // sets or toggles underline style + FontSize, // font size in 1/2 points + SpaceBefore, // space before paragraphs in twips + SpaceAfter, // space after paragraph in twips + UnicodeChar, // defines a Unicode character as signed 16bit value + UnicodePair, // introduces pair of ANSI and Unicode destinations + UnicodeDest, // introduces Unicode destination + Ignore, // denotes following control can be ignored + FirstLineOffset, // first line indent in twips (relative to \li) + LeftIndent, // left indent in twips + TabStop // sets a tab stop in twips ); + {$ScopedEnums off} type /// Container for related methods for generating valid RTF control @@ -277,7 +279,7 @@ class function TRTF.MakeSafeText(const AText: string; const ACodePage: Integer): else // Conversion failed: we store Unicode char in a Unicode control word Result := Result - + ControlWord(rcUnicodeChar, SmallInt(Ord(Ch))) + + ControlWord(TRTFControl.UnicodeChar, SmallInt(Ord(Ch))) + ' ?'; // fallback "unprintable" value end; end; @@ -317,11 +319,11 @@ class function TRTF.UnicodeSafeDestination(const ADestCtrl: TRTFControl; Encoding.Free; end; Result := '{' - + ControlWord(rcUnicodePair) + + ControlWord(TRTFControl.UnicodePair) + MakeDestination(AnsiStr) // ANSI only destination + '{' - + ControlWord(rcIgnore) - + ControlWord(rcUnicodeDest) + + ControlWord(TRTFControl.Ignore) + + ControlWord(TRTFControl.UnicodeDest) + MakeDestination(ADestText) // Unicode destinatation + '}' + '}'; From ada3a44d71d410c0927c8323d398c242761f6149 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 18 Apr 2025 19:00:15 +0100 Subject: [PATCH 270/330] Fix handling of Unicode chars in RTF generation. Modified check in WideCharToChar in UEncodings unit to detect failed conversions from WideChar to a given ANSI code page. Added support for \uc RTF control word to TRTF in URTFUtils unit. Fixed problem in TRTF.MakeSafeText that was not handling Unicode correctly when a Unicode character cannot be represented in a given code page. Fixes #157 Also fixed error in detecting ASCII characters that need to be escaped# in RTF (per issue 159) Fixes #159 --- Src/UEncodings.pas | 17 ++++++++++++++- Src/URTFUtils.pas | 52 +++++++++++++++++++++++++++++----------------- 2 files changed, 49 insertions(+), 20 deletions(-) diff --git a/Src/UEncodings.pas b/Src/UEncodings.pas index b8f6878bc..f85b5169e 100644 --- a/Src/UEncodings.pas +++ b/Src/UEncodings.pas @@ -437,7 +437,12 @@ function WideCharToChar(const Source: WideChar; const CodePage: Integer; var UsedDefChar: BOOL; BufSize: Integer; + Encoding: TEncoding; + TestStr: string; + TestBytes: TBytes; + Idx: Integer; begin + // Attempt to convert the Unicode char to ANSI char(s) BufSize := WideCharToMultiByte( CodePage, 0, @Source, 1, nil, 0, nil, nil ); @@ -447,7 +452,17 @@ function WideCharToChar(const Source: WideChar; const CodePage: Integer; ) = 0 then RaiseLastOSError; SetLength(Dest, Length(Dest) - 1); - Result := not UsedDefChar; + // Check if the conversion succeeded + Encoding := TMBCSEncoding.Create; + try + SetLength(TestBytes, Length(Dest)); + for Idx := 0 to Pred(Length(Dest)) do + TestBytes[Idx] := Ord(Dest[Idx]); + TestStr := Encoding.GetString(TestBytes); + Result := (TestStr = Source) and not UsedDefChar; + finally + Encoding.Free; + end; end; { TEncodingHelper } diff --git a/Src/URTFUtils.pas b/Src/URTFUtils.pas index dff0e59cd..57f1c2512 100644 --- a/Src/URTFUtils.pas +++ b/Src/URTFUtils.pas @@ -65,7 +65,8 @@ interface Ignore, // denotes following control can be ignored FirstLineOffset, // first line indent in twips (relative to \li) LeftIndent, // left indent in twips - TabStop // sets a tab stop in twips + TabStop, // sets a tab stop in twips + UnicodeCharSize // number of bytes of a given \uN Unicode character ); {$ScopedEnums off} @@ -81,7 +82,7 @@ TRTF = record 'fcharset', 'fnil', 'froman', 'fswiss', 'fmodern', 'fscript', 'fdecor', 'ftech', 'colortbl', 'red', 'green', 'blue', 'info', 'title', 'pard', 'par', 'plain', 'f', 'cf', 'b', 'i', 'ul', 'fs', 'sb', 'sa', 'u', 'upr', - 'ud', '*', 'fi', 'li', 'tx' + 'ud', '*', 'fi', 'li', 'tx', 'uc' ); strict private @@ -134,13 +135,13 @@ TRTF = record /// /// Converted characters are escaped if necessary. Any characters /// that are not valid in the required code page are encoded in a Unicode - /// RTF control word with ? as the non-Unicode fallback. + /// RTF control word with a non-Unicode fallback. class function MakeSafeText(const AText: string; const ACodePage: Integer): ASCIIString; static; /// Creates an RTF destination in a Unicode safe way. /// TRTFControl [in] Required destination - /// control. + /// control. /// string [in] Unicode text to be included /// in the destination. /// Integer [in] ANSI Code page to use for @@ -152,8 +153,7 @@ TRTF = record /// containing the encoded text, escaped as necessary. Should any /// characters in ADestText be incompatible with the code page then /// two sub-destinations are created, one containing Unicode characters and - /// the other containing ANSI text, with unknown characters flagged with - /// "error" characters such as ?. + /// the other containing ANSI text. class function UnicodeSafeDestination(const ADestCtrl: TRTFControl; const ADestText: string; const ACodePage: Integer): ASCIIString; static; end; @@ -250,6 +250,20 @@ class function TRTF.HexEscape(const Ch: AnsiChar): ASCIIString; class function TRTF.MakeSafeText(const AText: string; const ACodePage: Integer): ASCIIString; + + function MakeSafeChar(const AChar: AnsiChar): ASCIIString; + begin + if (AChar < #$20) or ((AChar >= #$7F) and (AChar <= #$FF)) then + // Not an ASCII character + Result := HexEscape(AChar) + else if (AChar = '{') or (AChar = '\') or (AChar = '}') then + // Reserved RTF character: must be escaped + Result := Escape(AChar) + else + // Valid character, use as is + Result := ASCIIString(AChar); + end; + var Ch: Char; // each Unicode character in TheText AnsiChars: TArray; // translation of a Ch into the ANSI code page @@ -264,23 +278,23 @@ class function TRTF.MakeSafeText(const AText: string; const ACodePage: Integer): begin // Conversion succeeded: check process each ANSI char for AnsiCh in AnsiChars do - begin - if (AnsiCh < #$20) or ((AnsiCh >= #$7F) and (AnsiCh <= #$FF)) then - // Not an ASCII character - Result := Result + HexEscape(AnsiCh) - else if (Ch = '{') or (Ch = '\') or (Ch = '}') then - // Reserved RTF character: must be escaped - Result := Result + Escape(AnsiCh) - else - // Valid character, use as is - Result := Result + ASCIIString(AnsiCh); - end; + Result := Result + MakeSafeChar(AnsiCh) end else - // Conversion failed: we store Unicode char in a Unicode control word + begin + // Conversion failed: create a Unicode character followed by fallback + // ANSI character Result := Result + + ControlWord(TRTFControl.UnicodeCharSize, 1) + ControlWord(TRTFControl.UnicodeChar, SmallInt(Ord(Ch))) - + ' ?'; // fallback "unprintable" value + + ' '; + if Length(AnsiChars) = 1 then + // Single alternate character: output it + Result := Result + MakeSafeChar(AnsiChars[0]) + else + // Can't get alternate: use '?' + Result := Result + '?'; + end; end; end; From d5c7dce9b87a95e6ae7cd4d435ac741336b0a083 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sat, 19 Apr 2025 08:05:31 +0100 Subject: [PATCH 271/330] Remove all hibernation bug fix hacks per v4.24.2 This is a merge of two commits proposed by @SirRufo per PR#160, with trivial FrOverview.dfm changes removed. --- Src/FmMain.pas | 20 ---------------- Src/FrOverview.pas | 52 ----------------------------------------- Src/IntfFrameMgrs.pas | 4 ---- Src/UMainDisplayMgr.pas | 28 ---------------------- 4 files changed, 104 deletions(-) diff --git a/Src/FmMain.pas b/Src/FmMain.pas index 60556605c..6fc09ef54 100644 --- a/Src/FmMain.pas +++ b/Src/FmMain.pas @@ -526,12 +526,6 @@ TMainForm = class(THelpAwareForm) /// Object that manages favourites. fFavouritesMgr: TFavouritesManager; - /// Handles the WM_POWERBROADCAST messages to detect and - /// respond to hibernation messages. - /// !! HACK necessary as part of the fix for an obscure bug. See - /// https://github.com/delphidabbler/codesnip/issues/70 - procedure WMPowerBroadcast(var Msg: TMessage); message WM_POWERBROADCAST; - /// Displays view item given by TViewItemAction instance /// referenced by Sender and adds to history list. procedure ActViewItemExecute(Sender: TObject); @@ -1601,19 +1595,5 @@ procedure TMainForm.splitVertCanResize(Sender: TObject; Accept := False; end; -procedure TMainForm.WMPowerBroadcast(var Msg: TMessage); -begin - // !! HACK - // Sometimes when the computer is resumed from hibernation the tree view in - // the overview frame is destroyed and recreated by Windows. Unfortunately the - // IView instances associated with the recreated tree nodes are lost. - // Attempting to read those (now nil) IView instances was resulting in an - // access violation. - case Msg.WParam of - PBT_APMSUSPEND: - fMainDisplayMgr._HACK_PrepareForHibernate; - end; -end; - end. diff --git a/Src/FrOverview.pas b/Src/FrOverview.pas index 9cf76a2a8..e608e6d4a 100644 --- a/Src/FrOverview.pas +++ b/Src/FrOverview.pas @@ -26,29 +26,6 @@ interface type - // !! HACK - // Horrible hack to expose CreateWnd for overiding TTreeView.CreateWnd for the - // existing TTreeView component of TOverviewFrame. The hack avoids having to - // remove the component and replacing it with a descendant class that is - // manually constructed at run time. - // This is here to enable the tree view to be recreated with correctly - // instantiated TViewItemTreeNode nodes after Windows recreates the tree - // behind the scenes after resuming from hibernation. - // I am deeply ashamed of this hack. - TTreeView = class(ComCtrls.TTreeView) - strict private - var - _HACK_fOnAfterCreateNilViews: TNotifyEvent; - protected - procedure CreateWnd; override; - public - /// !! HACK. Event triggered after the inherited CreateWnd is - /// called. Only called if the tree view has nil references to IView - /// objects. - property _HACK_OnAfterCreateNilViews: TNotifyEvent - read _HACK_fOnAfterCreateNilViews write _HACK_fOnAfterCreateNilViews; - end; - { TOverviewFrame: Titled frame that displays lists of snippets grouped in various ways and @@ -237,10 +214,6 @@ TTVDraw = class(TSnippetsTVDraw) procedure RestoreTreeState; {Restores last saved treeview expansion state from memory. } - /// !! HACK: Sets an event handler on the tree view to work - /// around a bug that can occur after resuming from hibernation. - /// Method of IOverviewDisplayMgr. - procedure _HACK_SetHibernateHandler(const AHandler: TNotifyEvent); { IPaneInfo } function IsInteractive: Boolean; {Checks if the pane is currently interactive with user. @@ -982,12 +955,6 @@ procedure TOverviewFrame.UpdateTreeState(const State: TTreeNodeAction); end; end; -procedure TOverviewFrame._HACK_SetHibernateHandler( - const AHandler: TNotifyEvent); -begin - tvSnippets._HACK_OnAfterCreateNilViews := AHandler; -end; - { TOverviewFrame.TTVDraw } function TOverviewFrame.TTVDraw.IsSectionHeadNode( @@ -1026,24 +993,5 @@ function TOverviewFrame.TTVDraw.IsUserDefinedNode( Result := False; end; -{ TTreeView } - -procedure TTreeView.CreateWnd; -var - HasNilViews: Boolean; - Node: TTreeNode; -begin - inherited; - HasNilViews := False; - for Node in Items do - begin - HasNilViews := not Assigned((Node as TViewItemTreeNode).ViewItem); - if HasNilViews then - Break; - end; - if HasNilViews and Assigned(_HACK_fOnAfterCreateNilViews) then - _HACK_fOnAfterCreateNilViews(Self); -end; - end. diff --git a/Src/IntfFrameMgrs.pas b/Src/IntfFrameMgrs.pas index b3cb76101..813d320ad 100644 --- a/Src/IntfFrameMgrs.pas +++ b/Src/IntfFrameMgrs.pas @@ -19,7 +19,6 @@ interface uses // Delphi SHDocVw, ActiveX, - Classes, // !! For HACK // Project Browser.IntfDocHostUI, DB.USnippet, Compilers.UGlobals, UCommandBars, UView; @@ -146,9 +145,6 @@ interface /// Restore expand / collapse state of treeview to last save /// state. procedure RestoreTreeState; - /// !! HACK: Sets an event handler on the tree view to work - /// around a bug that can occur after resuming from hibernation. - procedure _HACK_SetHibernateHandler(const AHandler: TNotifyEvent); end; type diff --git a/Src/UMainDisplayMgr.pas b/Src/UMainDisplayMgr.pas index 0c64a17d5..15020fb34 100644 --- a/Src/UMainDisplayMgr.pas +++ b/Src/UMainDisplayMgr.pas @@ -165,11 +165,6 @@ TMainDisplayMgr = class(TObject) procedure DisplayViewItem(ViewItem: IView; Mode: TDetailPageDisplayMode); overload; - /// !! HACK event handle to redisplay the overview pane treeview. - /// Called only if Windows has mysteriously recreated the treeview and lost - /// necessary object references. - procedure _HACK_HibernateHandler(Sender: TObject); - public /// Object contructor. Sets up object to work with given frame /// manager objects. @@ -297,13 +292,6 @@ TMainDisplayMgr = class(TObject) /// Prepares display ready for database to be reloaded. procedure PrepareForDBReload; - /// !!HACK: gets the overview frame prepared for program - /// hibernation. - /// Saves the overview tree view state ready for restoring after - /// hibernation if Windows has recreated the overview pane's treeview, - /// losing necessary IView object references.. - procedure _HACK_PrepareForHibernate; - end; @@ -704,21 +692,5 @@ procedure TMainDisplayMgr.UpdateOverviewTreeState(const State: TTreeNodeAction); (fOverviewMgr as IOverviewDisplayMgr).UpdateTreeState(State); end; -procedure TMainDisplayMgr._HACK_HibernateHandler(Sender: TObject); -begin - (fOverviewMgr as IOverviewDisplayMgr).Display(Query.Selection, True); - (fOverviewMgr as IOverviewDisplayMgr).RestoreTreeState; - // disable this handler until next resume from hibernation - (fOverviewMgr as IOverviewDisplayMgr)._HACK_SetHibernateHandler(nil); -end; - -procedure TMainDisplayMgr._HACK_PrepareForHibernate; -begin - (fOverviewMgr as IOverviewDisplayMgr).SaveTreeState; - (fOverviewMgr as IOverviewDisplayMgr)._HACK_SetHibernateHandler( - _HACK_HibernateHandler - ); -end; - end. From 54c3b5fc2f9be467e37286e50ddc80d2b73a88e8 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sat, 19 Apr 2025 08:30:25 +0100 Subject: [PATCH 272/330] Manage IViews references in a list Implements a list of IView instances that are referenced by tree nodes. This list is necessary to maintain IView reference counts since the tree nodes only maintain weak references via pointers. Original commit by @SirRufo per PR#160 modified only by adding / updating comments. --- Src/FrOverview.pas | 7 ++++- Src/UOverviewTreeBuilder.pas | 51 +++++++++++++++++++++++++----------- Src/UViewItemTreeNode.pas | 28 ++++++++++++++------ 3 files changed, 61 insertions(+), 25 deletions(-) diff --git a/Src/FrOverview.pas b/Src/FrOverview.pas index e608e6d4a..29ce4cd08 100644 --- a/Src/FrOverview.pas +++ b/Src/FrOverview.pas @@ -18,6 +18,7 @@ interface uses // Delphi + Generics.Collections, ComCtrls, Controls, Classes, Windows, ExtCtrls, StdCtrls, ToolWin, Menus, // Project DB.USnippet, FrTitled, IntfFrameMgrs, IntfNotifier, UCommandBars, @@ -88,6 +89,8 @@ TTVDraw = class(TSnippetsTVDraw) end; var + fViewStore : TList; // 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 @@ -284,6 +287,7 @@ constructor TOverviewFrame.Create(AOwner: TComponent); TabIdx: Integer; // loops through tabs begin inherited; + fViewStore := TList.Create; // Create delegated (contained) command bar manager for toolbar and popup menu fCommandBars := TCommandBarMgr.Create(Self); fCommandBars.AddCommandBar( @@ -319,6 +323,7 @@ destructor TOverviewFrame.Destroy; fSelectedItem := nil; fSnippetList.Free; // does not free referenced snippets fCommandBars.Free; + fViewStore.Free; inherited; end; @@ -520,7 +525,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 diff --git a/Src/UOverviewTreeBuilder.pas b/Src/UOverviewTreeBuilder.pas index 87a32f23c..af5c9c924 100644 --- a/Src/UOverviewTreeBuilder.pas +++ b/Src/UOverviewTreeBuilder.pas @@ -18,6 +18,7 @@ interface uses // Delphu + Generics.Collections, ComCtrls, // Project DB.USnippet, UGroups, UView, UViewItemTreeNode; @@ -32,13 +33,23 @@ interface TOverviewTreeBuilder = class abstract(TObject) strict private var - fTreeView: TTreeView; // Value of TreeView property - fSnippetList: TSnippetList; // Value of SnippetList property + // Property values + fTreeView: TTreeView; + fSnippetList: TSnippetList; + fViewStore: TList; strict protected property TreeView: TTreeView read fTreeView; {Reference to treeview populated by class} property SnippetList: TSnippetList read fSnippetList; {List of snippets to be displayed in treeview} + /// List of IView instances referenced by treeview nodes. + /// + /// This list is required to maintain reference counting of + /// IViews because the tree nodes only store weak references. + /// + property ViewStore : TList read fViewStore; + {List of IView instances referenced (weakly) by treeview nodes. This list + maintains maintains reference counting} function AddViewItemNode(const ParentNode: TViewItemTreeNode; ViewItem: IView): TViewItemTreeNode; {Adds a new node to the tree view that represents a view item. @@ -57,12 +68,16 @@ TOverviewTreeBuilder = class abstract(TObject) @return Required view item object. } public - constructor Create(const TV: TTreeView; const SnippetList: TSnippetList); - {Class constructor. Sets up object to populate a treeview with a list of - snippets. - @param TV [in] Treeview control to be populated. - @param SnippetList [in] List of snippets to be added to TV. - } + /// Constructs an object to populate a tree view with a list of + /// snippets. + /// TTreeView [in] Treeview control to be + /// populated. + /// TSnippetList [in] List of snippets to + /// be added to the treeview. + /// TList<IView> [in] Receives a list + /// of view items, one per tree node. + constructor Create(const TV: TTreeView; const SnippetList: TSnippetList; + const ViewStore: TList); procedure Build; {Populates the treeview. } @@ -177,7 +192,9 @@ procedure TOverviewTreeBuilder.Build; ParentNode: TViewItemTreeNode; // each section node in tree Grouping: TGrouping; // groups snippets Group: TGroupItem; // each group of snippets + View: IView; begin + ViewStore.Clear; // Create required grouping of snippets Grouping := CreateGrouping; try @@ -186,11 +203,17 @@ procedure TOverviewTreeBuilder.Build; begin if not Group.IsEmpty or Preferences.ShowEmptySections then begin - ParentNode := AddViewItemNode(nil, CreateViewItemForGroup(Group)); + View := CreateViewItemForGroup(Group); + ParentNode := AddViewItemNode(nil, View); + ViewStore.Add(View); for Snippet in Group.SnippetList do + begin + View := TViewFactory.CreateSnippetView(Snippet); AddViewItemNode( - ParentNode, TViewFactory.CreateSnippetView(Snippet) + ParentNode, View ); + ViewStore.Add(View); + end; end; end; finally @@ -199,16 +222,12 @@ procedure TOverviewTreeBuilder.Build; end; constructor TOverviewTreeBuilder.Create(const TV: TTreeView; - const SnippetList: TSnippetList); - {Class constructor. Sets up object to populate a treeview with a list of - snippets. - @param TV [in] Treeview control to be populated. - @param SnippetList [in] List of snippets to be added to TV. - } + const SnippetList: TSnippetList; const ViewStore: TList); begin inherited Create; fTreeView := TV; fSnippetList := SnippetList; + fViewStore := ViewStore; end; { TOverviewCategorisedTreeBuilder } diff --git a/Src/UViewItemTreeNode.pas b/Src/UViewItemTreeNode.pas index 44e258c24..ca2f969e4 100644 --- a/Src/UViewItemTreeNode.pas +++ b/Src/UViewItemTreeNode.pas @@ -24,21 +24,33 @@ interface type - { - TViewItemTreeNode: - Custom tree node class that adds ability to store reference to a view item - in a tree node. - } + /// Custom tree node class that adds a property to store a weak + /// reference to an IView instance in a tree node. TViewItemTreeNode = class(TTreeNode) strict private - var fViewItem: IView; // Value of ViewItem property + function GetViewItem: IView; + procedure SetViewItem(const Value: IView); public - property ViewItem: IView read fViewItem write fViewItem; - {View item associated with tree node} + /// View item associated with tree node. + /// NOTE: This view item is stored as a weak reference via a + /// pointer so the reference count is not updated. + property ViewItem: IView read GetViewItem write SetViewItem; end; implementation +{ TViewItemTreeNode } + +function TViewItemTreeNode.GetViewItem: IView; +begin + Result := IView(Data); +end; + +procedure TViewItemTreeNode.SetViewItem(const Value: IView); +begin + Data := Pointer(Value); +end; + end. From 472567ae9f16b52de70bf61c707c9f7d12d255c3 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sat, 19 Apr 2025 09:12:12 +0100 Subject: [PATCH 273/330] Add acknowledgement of contribution by @SirRufo See issue 158 & PR 160 --- Src/FrOverview.pas | 3 +++ Src/UOverviewTreeBuilder.pas | 3 +++ Src/UViewItemTreeNode.pas | 3 +++ 3 files changed, 9 insertions(+) diff --git a/Src/FrOverview.pas b/Src/FrOverview.pas index 29ce4cd08..17f912f81 100644 --- a/Src/FrOverview.pas +++ b/Src/FrOverview.pas @@ -7,6 +7,9 @@ * * 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). } diff --git a/Src/UOverviewTreeBuilder.pas b/Src/UOverviewTreeBuilder.pas index af5c9c924..465a5e293 100644 --- a/Src/UOverviewTreeBuilder.pas +++ b/Src/UOverviewTreeBuilder.pas @@ -7,6 +7,9 @@ * * Implements a set of classes that populate the overview treeview with a list * of snippets. Each class groups the snippets in different ways. + * + * ACKNOWLEDGEMENT: ViewStore property and its use implemented by @SirRufo + * (GitHub PR #160 & Issue #158). } diff --git a/Src/UViewItemTreeNode.pas b/Src/UViewItemTreeNode.pas index ca2f969e4..23869c1f2 100644 --- a/Src/UViewItemTreeNode.pas +++ b/Src/UViewItemTreeNode.pas @@ -7,6 +7,9 @@ * * Implements class that extends TTreeNode by adding a property that references * a view item. + * + * ACKNOWLEDGEMENT: GetViewItem & SetViewItem property accessors implemented by + * @SirRufo (GitHub PR #160 & Issue #158). } From 6329b76045af9e6315c68050de046521c399a87d Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sat, 19 Apr 2025 10:54:03 +0100 Subject: [PATCH 274/330] Clarified change log entry for v4.24.1 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16846323a..ff7d2c58e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ Hotfix release. ## Release v4.24.1 of 13 April 2025 * Fixed bug where CodeSnip occasionally crashes after a computer resumes from hibernation [issue #70]. -* Bumped some copyright dates for 2025. +* Updated license copyright dates for 2025. ## Release v4.24.0 of 23 October 2024 From dbcf3c81cd5de97e7ff88016ee579fe340f4008d Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sat, 19 Apr 2025 09:44:09 +0100 Subject: [PATCH 275/330] Bump copyright dates for 2025 --- Src/ActiveText.UHTMLRenderer.pas | 2 +- Src/Browser.UHighlighter.pas | 2 +- Src/CodeSnip.dpr | 2 +- Src/FmAboutDlg.pas | 2 +- Src/FmCompErrorDlg.pas | 2 +- Src/FrHiliterPrefs.pas | 2 +- Src/FrPrintingPrefs.pas | 2 +- Src/FrRTFPreview.pas | 2 +- Src/FrSourcePrefs.pas | 2 +- Src/Help/CodeSnip.hhp | 2 +- Src/Help/HTML/dlg_savesnippet.htm | 2 +- Src/Help/HTML/dlg_saveunit.htm | 2 +- Src/Help/HTML/menu_file.htm | 2 +- Src/Help/Index.hhk | 2 +- Src/Hiliter.UFileHiliter.pas | 2 +- Src/Hiliter.UHiliters.pas | 2 +- Src/UCompResHTML.pas | 2 +- Src/UCopyViewMgr.pas | 2 +- Src/UDetailPageHTML.pas | 2 +- Src/UEncodings.pas | 2 +- Src/UHTMLBuilder.pas | 2 +- Src/UHTMLTemplate.pas | 2 +- Src/UHTMLUtils.pas | 2 +- Src/UOpenDialogHelper.pas | 2 +- Src/UOverviewTreeBuilder.pas | 2 +- Src/UPrintDocuments.pas | 2 +- Src/UPrintEngine.pas | 2 +- Src/UPrintMgr.pas | 2 +- Src/UREMLDataIO.pas | 2 +- Src/URTFBuilder.pas | 2 +- Src/URTFUtils.pas | 2 +- Src/USaveSnippetMgr.pas | 2 +- Src/USaveSourceDlg.pas | 2 +- Src/USaveSourceMgr.pas | 2 +- Src/USaveUnitMgr.pas | 2 +- Src/USnippetHTML.pas | 2 +- Src/USnippetPageHTML.pas | 2 +- Src/USourceFileInfo.pas | 2 +- Src/UViewItemTreeNode.pas | 2 +- 39 files changed, 39 insertions(+), 39 deletions(-) diff --git a/Src/ActiveText.UHTMLRenderer.pas b/Src/ActiveText.UHTMLRenderer.pas index ec9c19d23..e58ad92f2 100644 --- a/Src/ActiveText.UHTMLRenderer.pas +++ b/Src/ActiveText.UHTMLRenderer.pas @@ -3,7 +3,7 @@ * 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) 2009-2023, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2009-2025, Peter Johnson (gravatar.com/delphidabbler). * * Provides a class that renders active text as HTML. } diff --git a/Src/Browser.UHighlighter.pas b/Src/Browser.UHighlighter.pas index 5405008cd..68f2ac0e2 100644 --- a/Src/Browser.UHighlighter.pas +++ b/Src/Browser.UHighlighter.pas @@ -3,7 +3,7 @@ * 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) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2025, Peter Johnson (gravatar.com/delphidabbler). * * Class that highlights text in web browser that match a search criteria. } diff --git a/Src/CodeSnip.dpr b/Src/CodeSnip.dpr index 8e5662dfe..719053105 100644 --- a/Src/CodeSnip.dpr +++ b/Src/CodeSnip.dpr @@ -3,7 +3,7 @@ * 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) 2005-2024, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2025, Peter Johnson (gravatar.com/delphidabbler). * * CodeSnip application project file. } diff --git a/Src/FmAboutDlg.pas b/Src/FmAboutDlg.pas index dafdfb627..a26397685 100644 --- a/Src/FmAboutDlg.pas +++ b/Src/FmAboutDlg.pas @@ -3,7 +3,7 @@ * 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) 2005-2023, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2025, Peter Johnson (gravatar.com/delphidabbler). * * Implements the program's About dialogue box. } diff --git a/Src/FmCompErrorDlg.pas b/Src/FmCompErrorDlg.pas index b9cdf0a3e..56744cc6a 100644 --- a/Src/FmCompErrorDlg.pas +++ b/Src/FmCompErrorDlg.pas @@ -3,7 +3,7 @@ * 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) 2005-2023, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2025, Peter Johnson (gravatar.com/delphidabbler). * * Implements a dialogue box that displays compiler error and warning logs. } diff --git a/Src/FrHiliterPrefs.pas b/Src/FrHiliterPrefs.pas index 0910a505f..61f6816f2 100644 --- a/Src/FrHiliterPrefs.pas +++ b/Src/FrHiliterPrefs.pas @@ -3,7 +3,7 @@ * 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) 2006-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2006-2025, Peter Johnson (gravatar.com/delphidabbler). * * Implements a frame that allows the user to set syntax highlighter * preferences. diff --git a/Src/FrPrintingPrefs.pas b/Src/FrPrintingPrefs.pas index f825f511e..d5a2b3034 100644 --- a/Src/FrPrintingPrefs.pas +++ b/Src/FrPrintingPrefs.pas @@ -3,7 +3,7 @@ * 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) 2007-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2007-2025, Peter Johnson (gravatar.com/delphidabbler). * * Implements a frame that allows user to set printing preferences. * diff --git a/Src/FrRTFPreview.pas b/Src/FrRTFPreview.pas index 16eb4f70b..294602dab 100644 --- a/Src/FrRTFPreview.pas +++ b/Src/FrRTFPreview.pas @@ -3,7 +3,7 @@ * 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) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2025, Peter Johnson (gravatar.com/delphidabbler). * * Implements a frame used to display previews of RTF documents. } diff --git a/Src/FrSourcePrefs.pas b/Src/FrSourcePrefs.pas index 6b925973b..da40b5e00 100644 --- a/Src/FrSourcePrefs.pas +++ b/Src/FrSourcePrefs.pas @@ -3,7 +3,7 @@ * 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) 2006-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2006-2025, Peter Johnson (gravatar.com/delphidabbler). * * Implements a frame that allows user to set source code preferences. * diff --git a/Src/Help/CodeSnip.hhp b/Src/Help/CodeSnip.hhp index 73163ca7e..c7bb1a367 100644 --- a/Src/Help/CodeSnip.hhp +++ b/Src/Help/CodeSnip.hhp @@ -2,7 +2,7 @@ ; 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) 2005-2022, Peter Johnson (gravatar.com/delphidabbler). +; Copyright (C) 2005-2025, Peter Johnson (gravatar.com/delphidabbler). ; ; CodeSnip help project file. diff --git a/Src/Help/HTML/dlg_savesnippet.htm b/Src/Help/HTML/dlg_savesnippet.htm index bdddfe9b1..7ef34cb1b 100644 --- a/Src/Help/HTML/dlg_savesnippet.htm +++ b/Src/Help/HTML/dlg_savesnippet.htm @@ -4,7 +4,7 @@ * 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) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2025, Peter Johnson (gravatar.com/delphidabbler). * * Help topic for Save Annotated Source dialogue box. --> diff --git a/Src/Help/HTML/dlg_saveunit.htm b/Src/Help/HTML/dlg_saveunit.htm index 9dfb25358..3691a8e44 100644 --- a/Src/Help/HTML/dlg_saveunit.htm +++ b/Src/Help/HTML/dlg_saveunit.htm @@ -4,7 +4,7 @@ * 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) 2006-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2006-2025, Peter Johnson (gravatar.com/delphidabbler). * * Help topic for Save Unit dialogue box. --> diff --git a/Src/Help/HTML/menu_file.htm b/Src/Help/HTML/menu_file.htm index badf54294..a7beabcd4 100644 --- a/Src/Help/HTML/menu_file.htm +++ b/Src/Help/HTML/menu_file.htm @@ -4,7 +4,7 @@ * 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) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2025, Peter Johnson (gravatar.com/delphidabbler). * * Help topic describing File menu. --> diff --git a/Src/Help/Index.hhk b/Src/Help/Index.hhk index 5a74ad8ed..dbc1a4d8c 100644 --- a/Src/Help/Index.hhk +++ b/Src/Help/Index.hhk @@ -4,7 +4,7 @@ * 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) 2005-2022, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2025, Peter Johnson (gravatar.com/delphidabbler). * * CodeSnip help index file. --> diff --git a/Src/Hiliter.UFileHiliter.pas b/Src/Hiliter.UFileHiliter.pas index 43838cb87..609a4cd74 100644 --- a/Src/Hiliter.UFileHiliter.pas +++ b/Src/Hiliter.UFileHiliter.pas @@ -3,7 +3,7 @@ * 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) 2006-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2006-2025, Peter Johnson (gravatar.com/delphidabbler). * * Implements a class that generates hilighted and formatted source code for a * specified file type. diff --git a/Src/Hiliter.UHiliters.pas b/Src/Hiliter.UHiliters.pas index 8433ab199..45267ca81 100644 --- a/Src/Hiliter.UHiliters.pas +++ b/Src/Hiliter.UHiliters.pas @@ -3,7 +3,7 @@ * 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) 2005-2023, Peter Johnson (gravatar.com/delphidabbler). + * 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 diff --git a/Src/UCompResHTML.pas b/Src/UCompResHTML.pas index 7ed706983..829f15bd0 100644 --- a/Src/UCompResHTML.pas +++ b/Src/UCompResHTML.pas @@ -3,7 +3,7 @@ * 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) 2006-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2006-2025, Peter Johnson (gravatar.com/delphidabbler). * * Static class that generate HTML of parts of tables used to display compiler * results in details pane. diff --git a/Src/UCopyViewMgr.pas b/Src/UCopyViewMgr.pas index 27c329634..885c838c2 100644 --- a/Src/UCopyViewMgr.pas +++ b/Src/UCopyViewMgr.pas @@ -3,7 +3,7 @@ * 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) 2009-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2009-2025, Peter Johnson (gravatar.com/delphidabbler). * * Implements an abstract base class for objects that copy a representation of a * view to the clipboard. diff --git a/Src/UDetailPageHTML.pas b/Src/UDetailPageHTML.pas index d278ab931..fe7946c5b 100644 --- a/Src/UDetailPageHTML.pas +++ b/Src/UDetailPageHTML.pas @@ -3,7 +3,7 @@ * 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) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2025, Peter Johnson (gravatar.com/delphidabbler). * * Heirachy of classes that render views as HTML. The HTML is used to display * the view item in a tab in the detail pane. A factory is provided that can diff --git a/Src/UEncodings.pas b/Src/UEncodings.pas index f85b5169e..ea3e5a870 100644 --- a/Src/UEncodings.pas +++ b/Src/UEncodings.pas @@ -3,7 +3,7 @@ * 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) 2009-2023, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2009-2025, Peter Johnson (gravatar.com/delphidabbler). * * Provides support for certain character encodings used by the program. } diff --git a/Src/UHTMLBuilder.pas b/Src/UHTMLBuilder.pas index 1c9afdab0..1b224c0f3 100644 --- a/Src/UHTMLBuilder.pas +++ b/Src/UHTMLBuilder.pas @@ -3,7 +3,7 @@ * 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) 2007-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2007-2025, Peter Johnson (gravatar.com/delphidabbler). * * Implements a class used to create content of an XHTML strict document. } diff --git a/Src/UHTMLTemplate.pas b/Src/UHTMLTemplate.pas index 54ae6a876..61c35806e 100644 --- a/Src/UHTMLTemplate.pas +++ b/Src/UHTMLTemplate.pas @@ -3,7 +3,7 @@ * 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) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2025, Peter Johnson (gravatar.com/delphidabbler). * * Implements a class that loads a HTML template from resources and permits * replacing of placeholders with values. diff --git a/Src/UHTMLUtils.pas b/Src/UHTMLUtils.pas index ddc441e88..eda6160fc 100644 --- a/Src/UHTMLUtils.pas +++ b/Src/UHTMLUtils.pas @@ -3,7 +3,7 @@ * 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) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2025, Peter Johnson (gravatar.com/delphidabbler). * * Helper interfaces and classes used to generate HTML. } diff --git a/Src/UOpenDialogHelper.pas b/Src/UOpenDialogHelper.pas index 68382f1af..73cb008ef 100644 --- a/Src/UOpenDialogHelper.pas +++ b/Src/UOpenDialogHelper.pas @@ -3,7 +3,7 @@ * 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) 2008-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2008-2025, Peter Johnson (gravatar.com/delphidabbler). * * Helper routines for use when working with standard windows open and save file * dialog boxes. diff --git a/Src/UOverviewTreeBuilder.pas b/Src/UOverviewTreeBuilder.pas index 465a5e293..d9b61beb5 100644 --- a/Src/UOverviewTreeBuilder.pas +++ b/Src/UOverviewTreeBuilder.pas @@ -3,7 +3,7 @@ * 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) 2009-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2009-2025, Peter Johnson (gravatar.com/delphidabbler). * * Implements a set of classes that populate the overview treeview with a list * of snippets. Each class groups the snippets in different ways. diff --git a/Src/UPrintDocuments.pas b/Src/UPrintDocuments.pas index 402971a5c..51b6600a1 100644 --- a/Src/UPrintDocuments.pas +++ b/Src/UPrintDocuments.pas @@ -3,7 +3,7 @@ * 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) 2007-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2007-2025, Peter Johnson (gravatar.com/delphidabbler). * * Provides interface and classes that can generate output suitable for printing * using print engine. diff --git a/Src/UPrintEngine.pas b/Src/UPrintEngine.pas index 318940e4c..f0329bb72 100644 --- a/Src/UPrintEngine.pas +++ b/Src/UPrintEngine.pas @@ -3,7 +3,7 @@ * 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) 2007-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2007-2025, Peter Johnson (gravatar.com/delphidabbler). * * Implements a class that uses a rich edit control to print a rich text format * document. diff --git a/Src/UPrintMgr.pas b/Src/UPrintMgr.pas index c361d2ae8..825753c5a 100644 --- a/Src/UPrintMgr.pas +++ b/Src/UPrintMgr.pas @@ -3,7 +3,7 @@ * 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) 2007-2023, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2007-2025, Peter Johnson (gravatar.com/delphidabbler). * * Implements a class that manages printing of a document providing information * about certain view items. diff --git a/Src/UREMLDataIO.pas b/Src/UREMLDataIO.pas index 249990248..f96e95c65 100644 --- a/Src/UREMLDataIO.pas +++ b/Src/UREMLDataIO.pas @@ -3,7 +3,7 @@ * 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) 2008-2024, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2008-2025, Peter Johnson (gravatar.com/delphidabbler). * * Implements classes that render and parse Routine Extra Markup Language (REML) * code. This markup is used to read and store active text objects as used by diff --git a/Src/URTFBuilder.pas b/Src/URTFBuilder.pas index 739b82a84..2de1ec99d 100644 --- a/Src/URTFBuilder.pas +++ b/Src/URTFBuilder.pas @@ -3,7 +3,7 @@ * 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) 2006-2023, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2006-2025, Peter Johnson (gravatar.com/delphidabbler). * * Implements various classes used to create content of a rich text document. } diff --git a/Src/URTFUtils.pas b/Src/URTFUtils.pas index 57f1c2512..810448358 100644 --- a/Src/URTFUtils.pas +++ b/Src/URTFUtils.pas @@ -3,7 +3,7 @@ * 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) 2005-2023, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2025, Peter Johnson (gravatar.com/delphidabbler). * * Utility functions used when processing RTF. } diff --git a/Src/USaveSnippetMgr.pas b/Src/USaveSnippetMgr.pas index 61f37bef6..9426baa94 100644 --- a/Src/USaveSnippetMgr.pas +++ b/Src/USaveSnippetMgr.pas @@ -3,7 +3,7 @@ * 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) 2005-2023, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2025, Peter Johnson (gravatar.com/delphidabbler). * * Defines a class that manages generation, previewing and saving of a code * snippet. diff --git a/Src/USaveSourceDlg.pas b/Src/USaveSourceDlg.pas index 8a5aeaa5b..c089147f7 100644 --- a/Src/USaveSourceDlg.pas +++ b/Src/USaveSourceDlg.pas @@ -3,7 +3,7 @@ * 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) 2005-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2005-2025, Peter Johnson (gravatar.com/delphidabbler). * * Implements customised Save dialog box for source code. Dialog has additional * controls to allow user to choose output file format, commenting style and diff --git a/Src/USaveSourceMgr.pas b/Src/USaveSourceMgr.pas index bf35fc824..4739ac596 100644 --- a/Src/USaveSourceMgr.pas +++ b/Src/USaveSourceMgr.pas @@ -3,7 +3,7 @@ * 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) 2006-2023, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2006-2025, Peter Johnson (gravatar.com/delphidabbler). * * Implements abstract base class for classes that manage generation, previewing * and saving to disk of a source code files in various formats and encodings. diff --git a/Src/USaveUnitMgr.pas b/Src/USaveUnitMgr.pas index 45d2b4529..7015767dc 100644 --- a/Src/USaveUnitMgr.pas +++ b/Src/USaveUnitMgr.pas @@ -3,7 +3,7 @@ * 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) 2006-2023, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2006-2025, Peter Johnson (gravatar.com/delphidabbler). * * Defines a class that manages generation, previewing and saving of a pascal * unit. diff --git a/Src/USnippetHTML.pas b/Src/USnippetHTML.pas index 8035a7474..3f53d12fd 100644 --- a/Src/USnippetHTML.pas +++ b/Src/USnippetHTML.pas @@ -3,7 +3,7 @@ * 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) 2006-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2006-2025, Peter Johnson (gravatar.com/delphidabbler). * * Classes that generates HTML used to display snippets in detail pane. } diff --git a/Src/USnippetPageHTML.pas b/Src/USnippetPageHTML.pas index 9ad1f082a..90715e256 100644 --- a/Src/USnippetPageHTML.pas +++ b/Src/USnippetPageHTML.pas @@ -3,7 +3,7 @@ * 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-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2012-2025, Peter Johnson (gravatar.com/delphidabbler). * * Defines classes etc that render different fragments of information about a * snippet as HTML for display in the detail pane. Page content is flexible and diff --git a/Src/USourceFileInfo.pas b/Src/USourceFileInfo.pas index a4b4f49a5..8f721679f 100644 --- a/Src/USourceFileInfo.pas +++ b/Src/USourceFileInfo.pas @@ -3,7 +3,7 @@ * 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) 2006-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2006-2025, Peter Johnson (gravatar.com/delphidabbler). * * Implements class that provides information about types of source code output * that are supported. diff --git a/Src/UViewItemTreeNode.pas b/Src/UViewItemTreeNode.pas index 23869c1f2..e0e4139a0 100644 --- a/Src/UViewItemTreeNode.pas +++ b/Src/UViewItemTreeNode.pas @@ -3,7 +3,7 @@ * 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) 2009-2021, Peter Johnson (gravatar.com/delphidabbler). + * Copyright (C) 2009-2025, Peter Johnson (gravatar.com/delphidabbler). * * Implements class that extends TTreeNode by adding a property that references * a view item. From 5c64b2941f90d46248467981a79a7b2c57a6ce87 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sat, 19 Apr 2025 09:51:07 +0100 Subject: [PATCH 276/330] Bump version number to v4.25.0 build 275 --- Src/VersionInfo.vi-inc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Src/VersionInfo.vi-inc b/Src/VersionInfo.vi-inc index fbd558db1..e23088aa2 100644 --- a/Src/VersionInfo.vi-inc +++ b/Src/VersionInfo.vi-inc @@ -1,8 +1,8 @@ # CodeSnip Version Information Macros for Including in .vi files # Version & build numbers -version=4.24.2 -build=274 +version=4.25.0 +build=275 # String file information copyright=Copyright © P.D.Johnson, 2005-. From 1c0566e24e73d4f44d7d37e66cb77f9d5c2beae5 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sat, 19 Apr 2025 10:53:10 +0100 Subject: [PATCH 277/330] Update change log with details of release v4.25.0 --- CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff7d2c58e..15b4636c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,23 @@ 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.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. From 5b174d795f0815866ba8ce35ae12912a8429b01d Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 20 Apr 2025 08:54:24 +0100 Subject: [PATCH 278/330] Reimplement TSourceFileInfo.FileTypeInfo[] property Changed the field and read/write accessor for this property to store the property values in a dictionary instead of in a fixed size array. Functionality was changed only in as much that attempts to access an "array" value will now fail with an exception if an attempt is made to get a value that has not been previously set. Before garbage results would be returned. This change was made to enable fewer file types than the maximum to be supported in the filter string created by TSourceFileInfo.FilterString. --- Src/USourceFileInfo.pas | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/Src/USourceFileInfo.pas b/Src/USourceFileInfo.pas index 8f721679f..776eb5af3 100644 --- a/Src/USourceFileInfo.pas +++ b/Src/USourceFileInfo.pas @@ -17,6 +17,8 @@ interface uses + // Delphi + Generics.Collections, // Project UEncodings; @@ -89,10 +91,12 @@ TSourceFileInfo = class(TObject) var /// Stores information about the different source code output // types required by save source dialog boxes. - fFileTypeInfo: array[TSourceFileType] of TSourceFileTypeInfo; + fFileTypeInfo: TDictionary; // Value of DefaultFileName property. fDefaultFileName: string; /// Read accessor for FileTypeInfo property. + /// Raises EListError if FileType is not contained + /// in the property. function GetFileTypeInfo(const FileType: TSourceFileType): TSourceFileTypeInfo; /// Write accessor for FileTypeInfo property. @@ -103,11 +107,17 @@ TSourceFileInfo = class(TObject) /// necessary. procedure SetDefaultFileName(const Value: string); public + constructor Create; + destructor Destroy; override; + /// Builds filter string for use in open / save dialog boxes from /// descriptions and file extensions of each supported file type. function FilterString: string; - /// Array of information about each supported file type that is - /// of use to save source dialog boxes. + /// Information about each supported file type that is of use to + /// save source dialog boxes. + /// A EListError exception is raised if no information + /// relating to FileType has been stored in this property. + /// property FileTypeInfo[const FileType: TSourceFileType]: TSourceFileTypeInfo read GetFileTypeInfo write SetFileTypeInfo; /// Default source code file name. @@ -130,6 +140,18 @@ implementation { TSourceFileInfo } +constructor TSourceFileInfo.Create; +begin + inherited Create; + fFileTypeInfo := TDictionary.Create; +end; + +destructor TSourceFileInfo.Destroy; +begin + fFileTypeInfo.Free; + inherited; +end; + function TSourceFileInfo.FilterString: string; const cFilterFmt = '%0:s (*%1:s)|*%1:s'; // format string for creating file filter @@ -139,6 +161,8 @@ function TSourceFileInfo.FilterString: string; Result := ''; for FT := Low(TSourceFileType) to High(TSourceFileType) do begin + if not fFileTypeInfo.ContainsKey(FT) then + Continue; if Result <> '' then Result := Result + '|'; Result := Result + Format( @@ -175,7 +199,10 @@ procedure TSourceFileInfo.SetDefaultFileName(const Value: string); procedure TSourceFileInfo.SetFileTypeInfo(const FileType: TSourceFileType; const Info: TSourceFileTypeInfo); begin - fFileTypeInfo[FileType] := Info; + if fFileTypeInfo.ContainsKey(FileType) then + fFileTypeInfo[FileType] := Info + else + fFileTypeInfo.Add(FileType, Info); end; { TSourceFileTypeInfo } From 5d8cb55902db4dfb81fa4ae30361442b0b28cade Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 20 Apr 2025 10:01:36 +0100 Subject: [PATCH 279/330] Add new TSourceFileInfo.FileTypeFromFilterIdx method New method to get the file type associated with a given index within a filter string. To implement this many changes were made to the internals of TSourceFileInfo, the main one of which was that the filter string is now generated every time the FileTypeInfo property is updated instead of the filter string being built on request. --- Src/USourceFileInfo.pas | 52 +++++++++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/Src/USourceFileInfo.pas b/Src/USourceFileInfo.pas index 776eb5af3..d0e318f01 100644 --- a/Src/USourceFileInfo.pas +++ b/Src/USourceFileInfo.pas @@ -90,10 +90,24 @@ TSourceFileInfo = class(TObject) strict private var /// Stores information about the different source code output - // types required by save source dialog boxes. + /// types required by save source dialog boxes. fFileTypeInfo: TDictionary; - // Value of DefaultFileName property. + /// Maps a one-based index of a file filter within the current + /// filter string to the corresponding TSourceFileType that was + /// used to create the filter string entry. + fFilterIdxToFileTypeMap: TDictionary; + /// Value of DefaultFileName property. fDefaultFileName: string; + /// Filter string for use in open / save dialog boxes from + /// descriptions and file extensions of each supported file type. + /// + fFilterString: string; + /// Generates a new filter string and filter index to file type + /// map from the current state of the FileTypeInfo property. + /// + /// This method MUST be called every time the FileTypeInfo + /// property is updated. + procedure GenerateFilterInfo; /// Read accessor for FileTypeInfo property. /// Raises EListError if FileType is not contained /// in the property. @@ -110,9 +124,14 @@ TSourceFileInfo = class(TObject) constructor Create; destructor Destroy; override; - /// Builds filter string for use in open / save dialog boxes from + /// Returns filter string for use in open / save dialog boxes from /// descriptions and file extensions of each supported file type. function FilterString: string; + + /// Returns the file type associated with a file filter at the + /// given one-based index within the current filter string. + function FileTypeFromFilterIdx(const Idx: Integer): TSourceFileType; + /// Information about each supported file type that is of use to /// save source dialog boxes. /// A EListError exception is raised if no information @@ -144,30 +163,48 @@ constructor TSourceFileInfo.Create; begin inherited Create; fFileTypeInfo := TDictionary.Create; + fFilterIdxToFileTypeMap := TDictionary.Create; end; destructor TSourceFileInfo.Destroy; begin + fFilterIdxToFileTypeMap.Free; fFileTypeInfo.Free; inherited; end; +function TSourceFileInfo.FileTypeFromFilterIdx( + const Idx: Integer): TSourceFileType; +begin + Result := fFilterIdxToFileTypeMap[Idx]; +end; + function TSourceFileInfo.FilterString: string; +begin + Result := fFilterString; +end; + +procedure TSourceFileInfo.GenerateFilterInfo; const cFilterFmt = '%0:s (*%1:s)|*%1:s'; // format string for creating file filter var FT: TSourceFileType; // loops thru all source file types + FilterIdx: Integer; // current index in filter string begin - Result := ''; + fFilterIdxToFileTypeMap.Clear; + FilterIdx := 1; // filter index is one based + fFilterString := ''; for FT := Low(TSourceFileType) to High(TSourceFileType) do begin if not fFileTypeInfo.ContainsKey(FT) then Continue; - if Result <> '' then - Result := Result + '|'; - Result := Result + Format( + if fFilterString <> '' then + fFilterString := fFilterString + '|'; + fFilterString := fFilterString + Format( cFilterFmt, [fFileTypeInfo[FT].DisplayName, fFileTypeInfo[FT].Extension] ); + fFilterIdxToFileTypeMap.Add(FilterIdx, FT); + Inc(FilterIdx); end; end; @@ -203,6 +240,7 @@ procedure TSourceFileInfo.SetFileTypeInfo(const FileType: TSourceFileType; fFileTypeInfo[FileType] := Info else fFileTypeInfo.Add(FileType, Info); + GenerateFilterInfo; end; { TSourceFileTypeInfo } From c574ce2d5b386739fffb118ab7716c1b04ed3aa7 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 20 Apr 2025 10:04:32 +0100 Subject: [PATCH 280/330] Reimplement TSaveSourceMgr.FileTypeFromFilterIdx This method was changed to simply call TSourceFileInfo.FileTypeFromFilterIdx for the currently selected filter in the associated dialogue's filter string, instead of calculating the value locally. Note that the new method is much more resilient to future changes than the original implementation which made assumptions about a one to one relationship between filter indexes and file types. --- Src/USaveSourceMgr.pas | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/Src/USaveSourceMgr.pas b/Src/USaveSourceMgr.pas index 4739ac596..2811d7a75 100644 --- a/Src/USaveSourceMgr.pas +++ b/Src/USaveSourceMgr.pas @@ -215,16 +215,8 @@ procedure TSaveSourceMgr.EncodingQueryHandler(Sender: TObject; end; function TSaveSourceMgr.FileTypeFromFilterIdx: TSourceFileType; -var - FilterIdx: Integer; // dlg FilterIndex adjusted to be 0 based begin - FilterIdx := fSaveDlg.FilterIndex - 1; - Assert( - (FilterIdx >= Ord(Low(TSourceFileType))) - and (FilterIdx <= Ord(High(TSourceFileType))), - ClassName + '.FileTypeFromFilterIdx: FilerIdx out of range' - ); - Result := TSourceFileType(FilterIdx) + Result := fSourceFileInfo.FileTypeFromFilterIdx(fSaveDlg.FilterIndex); end; function TSaveSourceMgr.GenerateOutput(const FileType: TSourceFileType): From 300739c8364b0e0c20f390211557c264f16d0165 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 20 Apr 2025 13:08:38 +0100 Subject: [PATCH 281/330] Add EnableCommentStyles to TSaveSourceDlg This property disables the comment style selection combo, and associated controls when False. The default is True. --- Src/USaveSourceDlg.pas | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Src/USaveSourceDlg.pas b/Src/USaveSourceDlg.pas index c089147f7..6d0c44940 100644 --- a/Src/USaveSourceDlg.pas +++ b/Src/USaveSourceDlg.pas @@ -93,6 +93,9 @@ TSaveSourceDlg = class(TSaveDialogEx) fSelectedFilterIdx: Integer; /// Stores type of selected encoding. fSelectedEncoding: TEncodingType; + /// Value of EnableCommentStyles property. + fEnableCommentStyles: Boolean; + /// Handles click on Help button. /// Calls help with required keyword. procedure HelpClickHandler(Sender: TObject); @@ -201,6 +204,10 @@ TSaveSourceDlg = class(TSaveDialogEx) /// encodings supported for the file type. property OnEncodingQuery: TEncodingQuery read fOnEncodingQuery write fOnEncodingQuery; + /// Determines whether the comment styles combo and associated + /// controls are enabled, and so can be changed, or are disabled. + property EnableCommentStyles: Boolean + read fEnableCommentStyles write fEnableCommentStyles default True; /// Re-implementation of inherited property to overcome apparent /// bug where property forgets selected filter when dialog box is closed. /// @@ -317,6 +324,9 @@ constructor TSaveSourceDlg.Create(AOwner: TComponent); // set dialog options Options := [ofPathMustExist, ofEnableIncludeNotify]; + // enable comment style selection + fEnableCommentStyles := True; + // inhibit default help processing: we provide own help button and handling WantDefaultHelpSupport := False; end; @@ -579,6 +589,9 @@ procedure TSaveSourceDlg.UpdateCommentStyle; if TCommentStyle(fCmbCommentStyle.Items.Objects[Idx]) = fCommentStyle then fCmbCommentStyle.ItemIndex := Idx; end; + fCmbCommentStyle.Enabled := fEnableCommentStyles; + fLblCommentStyle.Enabled := fEnableCommentStyles; + fChkTruncateComment.Enabled := fEnableCommentStyles; end; procedure TSaveSourceDlg.UpdateCommentTruncation; From a8da07d8dcffa74f176c4a6471a3a663ca07509e Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 20 Apr 2025 14:50:58 +0100 Subject: [PATCH 282/330] Remove unnecessary params from TSaveSourceDlg events The OnHiliteQuery and OnEncodingQuery both had parameters that were not being used, so they were removed. USaveSourceMgr was modified re these changes. --- Src/USaveSourceDlg.pas | 13 ++++--------- Src/USaveSourceMgr.pas | 12 ++++-------- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/Src/USaveSourceDlg.pas b/Src/USaveSourceDlg.pas index 6d0c44940..78b301487 100644 --- a/Src/USaveSourceDlg.pas +++ b/Src/USaveSourceDlg.pas @@ -27,22 +27,17 @@ interface /// Type of handler for events triggered by TSaveSourceDlg to check /// if a file type supports syntax highlighting. /// TObject [in] Object triggering event. - /// string [in] Extension that defines type of file being - /// queried. /// Boolean [in/out] Set to true if file type /// supports syntax highlighting. - THiliteQuery = procedure(Sender: TObject; const Ext: string; - var CanHilite: Boolean) of object; + THiliteQuery = procedure(Sender: TObject; var CanHilite: Boolean) of object; type /// Type of handler for event triggered by TSaveSourceDlg to get /// list of encodings supported for a file type. /// TObject [in] Object triggering event. - /// string [in] Filter index that specifies the type - /// of file being queried. /// TSourceFileEncodings [in/out] Assigned an array /// of records that specify supported encodings. - TEncodingQuery = procedure(Sender: TObject; const FilterIdx: Integer; + TEncodingQuery = procedure(Sender: TObject; var Encodings: TSourceFileEncodings) of object; type @@ -475,7 +470,7 @@ procedure TSaveSourceDlg.DoTypeChange; // Update enabled state of syntax highlighter checkbox CanHilite := False; if Assigned(fOnHiliteQuery) then - fOnHiliteQuery(Self, SelectedExt, CanHilite); + fOnHiliteQuery(Self, CanHilite); fChkSyntaxHilite.Enabled := CanHilite; // Store selected type @@ -485,7 +480,7 @@ procedure TSaveSourceDlg.DoTypeChange; // handle OnEncodingQuery) SetLength(Encodings, 0); if Assigned(fOnEncodingQuery) then - fOnEncodingQuery(Self, FilterIndex, Encodings); + fOnEncodingQuery(Self, Encodings); if Length(Encodings) = 0 then Encodings := TSourceFileEncodings.Create( TSourceFileEncoding.Create(etSysDefault, sANSIEncoding) diff --git a/Src/USaveSourceMgr.pas b/Src/USaveSourceMgr.pas index 2811d7a75..4be7c6fcc 100644 --- a/Src/USaveSourceMgr.pas +++ b/Src/USaveSourceMgr.pas @@ -40,20 +40,16 @@ TSaveSourceMgr = class abstract(TNoPublicConstructObject) /// extension. /// TObject [in] Reference to object that triggered /// event. - /// string [in] Name of extension to check. /// Boolean [in/out] Set to True if highlighting /// supported for extension or False if not. - procedure HiliteQueryHandler(Sender: TObject; const Ext: string; - var CanHilite: Boolean); + procedure HiliteQueryHandler(Sender: TObject; var CanHilite: Boolean); /// Handles custom save dialog box's OnEncodingQuery event. /// Provides array of encodings supported for a file extension. /// TObject [in] Reference to object that triggered /// event. - /// string [in] Index of file type withing dialog's - /// filter string to check. /// TSourceFileEncodings [in/out] Receives array of /// supported encodings. - procedure EncodingQueryHandler(Sender: TObject; const FilterIdx: Integer; + procedure EncodingQueryHandler(Sender: TObject; var Encodings: TSourceFileEncodings); /// Handles custom save dialog's OnPreview event. Displays source /// code appropriately formatted in preview dialog box. @@ -206,7 +202,7 @@ procedure TSaveSourceMgr.DoExecute; end; procedure TSaveSourceMgr.EncodingQueryHandler(Sender: TObject; - const FilterIdx: Integer; var Encodings: TSourceFileEncodings); + var Encodings: TSourceFileEncodings); var FileType: TSourceFileType; // type of file that has given extension begin @@ -238,7 +234,7 @@ function TSaveSourceMgr.GenerateOutput(const FileType: TSourceFileType): end; end; -procedure TSaveSourceMgr.HiliteQueryHandler(Sender: TObject; const Ext: string; +procedure TSaveSourceMgr.HiliteQueryHandler(Sender: TObject; var CanHilite: Boolean); begin CanHilite := IsHilitingSupported(FileTypeFromFilterIdx); From 567e1c3c2847b52ce24b089e39b6eff87e4de275 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 20 Apr 2025 15:12:25 +0100 Subject: [PATCH 283/330] Rewrite of USaveInfoMgr ready for extra file types The unit was rewritten to use the TSaveSourceDlg dialogue box instead of the simple TSaveDialogEx. Nearly all code was rewritten, although, despite using a different dialogue box, only RTF files are supported for output. The only difference being that highlighting the source code in the output can now be switched off. The code was rewritten to make it easier to add support for other file formats. --- Src/USaveInfoMgr.pas | 241 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 197 insertions(+), 44 deletions(-) diff --git a/Src/USaveInfoMgr.pas b/Src/USaveInfoMgr.pas index 133b7cbce..12192672e 100644 --- a/Src/USaveInfoMgr.pas +++ b/Src/USaveInfoMgr.pas @@ -16,34 +16,90 @@ interface uses // Project + UBaseObjects, UEncodings, + USaveSourceDlg, + USourceFileInfo, UView; type - /// Method-only record that saves information about a snippet to - /// file in rich text format. The snippet is obtained from a view. Only - /// snippet views are supported. - TSaveInfoMgr = record + /// Class that saves information about a snippet to file in rich + /// text format. The snippet is obtained from a view. Only snippet views are + /// supported. + TSaveInfoMgr = class(TNoPublicConstructObject) strict private - /// Attempts to name of the file to be written from the user. - /// - /// string [out] Set to the name of the file - /// entered by the user. Undefined if the user cancelled. - /// Boolean. True if the user entered and accepted a - /// file name of False if the user cancelled. - class function TryGetFileNameFromUser(out AFileName: string): Boolean; - static; + var + fView: IView; + fSaveDlg: TSaveSourceDlg; + fSourceFileInfo: TSourceFileInfo; + /// Returns encoded data containing a RTF representation of /// information about the snippet represented by the given view. - class function GenerateRichText(View: IView): TEncodedData; static; + class function GenerateRichText(View: IView; const AUseHiliting: Boolean): + TEncodedData; static; + + /// Returns type of file selected in the associated save dialogue + /// box. + function SelectedFileType: TSourceFileType; + + /// Handles the custom save dialogue's OnPreview event. + /// Displays the required snippet information, appropriately formatted, in + /// a preview dialogues box. + /// TObject [in] Reference to the object that + /// triggered the event. + procedure PreviewHandler(Sender: TObject); + + /// Handles the custom save dialogue's OnHiliteQuery event. + /// Determines whether syntax highlighting is supported for the source code + /// section of the required snippet information.. + /// TObject [in] Reference to the object that + /// triggered the event. + /// Boolean [in/out] Set to False + /// when called. Should be set to True iff highlighting is + /// supported. + procedure HighlightQueryHandler(Sender: TObject; var CanHilite: Boolean); + + /// Handles the custom save dialogue's OnEncodingQuery + /// event. + /// TObject [in] Reference to the object that + /// triggered the event. + /// TSourceFileEncodings [in/out] Called + /// with an empty array which the event handler must be set to contain the + /// encodings supported by the currently selected file type. + procedure EncodingQueryHandler(Sender: TObject; + var Encodings: TSourceFileEncodings); + + /// Generates the required snippet information in the requested + /// format. + /// TSourceFileType [in] Type of file to be + /// generated. + /// TEncodedData. The formatted snippet information, syntax + /// highlighted if required. + function GenerateOutput(const FileType: TSourceFileType): TEncodedData; + + /// Displays the save dialogue box and creates required type of + /// snippet information file if the user OKs. + procedure DoExecute; + + strict protected + + /// Internal constructor. Initialises managed save source dialogue + /// box and records information about supported file types. + constructor InternalCreate(AView: IView); + public + + /// Object descructor. Tears down object. + destructor Destroy; override; + /// Saves information about the snippet referenced by the a given /// view to file. /// The view must be a snippet view. class procedure Execute(View: IView); static; - /// Checks if a given view can be saved to the clipboard. Returns - /// True only if the view represents a snippet. + + /// Checks if the given view can be saved to file. Returns + /// True if the view represents a snippet. class function CanHandleView(View: IView): Boolean; static; end; @@ -55,13 +111,16 @@ implementation SysUtils, Dialogs, // Project + FmPreviewDlg, Hiliter.UAttrs, + Hiliter.UFileHiliter, Hiliter.UGlobals, UIOUtils, UOpenDialogHelper, + UPreferences, URTFSnippetDoc, URTFUtils, - USaveDialogEx; + USourceGen; { TSaveInfoMgr } @@ -70,27 +129,84 @@ class function TSaveInfoMgr.CanHandleView(View: IView): Boolean; Result := Supports(View, ISnippetView); end; +destructor TSaveInfoMgr.Destroy; +begin + fSourceFileInfo.Free; + fSaveDlg.Free; + inherited; +end; + +procedure TSaveInfoMgr.DoExecute; +var + Encoding: TEncoding; // encoding to use for output file + FileContent: string; // output file content before encoding + FileType: TSourceFileType; // type of source file +begin + // Set up dialog box + fSaveDlg.Filter := fSourceFileInfo.FilterString; + fSaveDlg.FilterIndex := FilterDescToIndex( + fSaveDlg.Filter, + fSourceFileInfo.FileTypeInfo[Preferences.SourceDefaultFileType].DisplayName, + 1 + ); + fSaveDlg.FileName := fSourceFileInfo.DefaultFileName; + // Display dialog box and save file if user OKs + if fSaveDlg.Execute then + begin + FileType := SelectedFileType; + FileContent := GenerateOutput(FileType).ToString; + Encoding := TEncodingHelper.GetEncoding(fSaveDlg.SelectedEncoding); + try + FileContent := GenerateOutput(FileType).ToString; + TFileIO.WriteAllText(fSaveDlg.FileName, FileContent, Encoding, True); + finally + TEncodingHelper.FreeEncoding(Encoding); + end; + end; +end; + +procedure TSaveInfoMgr.EncodingQueryHandler(Sender: TObject; + var Encodings: TSourceFileEncodings); +begin + Encodings := fSourceFileInfo.FileTypeInfo[SelectedFileType].Encodings; +end; + class procedure TSaveInfoMgr.Execute(View: IView); var - FileName: string; - RTFMarkup: TRTFMarkup; + Instance: TSaveInfoMgr; begin Assert(Assigned(View), 'TSaveInfoMgr.Execute: View is nil'); Assert(CanHandleView(View), 'TSaveInfoMgr.Execute: View not supported'); - if not TryGetFileNameFromUser(FileName) then - Exit; - RTFMarkup := TRTFMarkup.Create(GenerateRichText(View)); - TFileIO.WriteAllBytes(FileName, RTFMarkup.ToBytes); + + Instance := TSaveInfoMgr.InternalCreate(View); + try + Instance.DoExecute; + finally + Instance.Free; + end; +end; + +function TSaveInfoMgr.GenerateOutput(const FileType: TSourceFileType): + TEncodedData; +var + UseHiliting: Boolean; +begin + UseHiliting := fSaveDlg.UseSyntaxHiliting and + TFileHiliter.IsHilitingSupported(FileType); + case FileType of + sfRTF: Result := GenerateRichText(fView, UseHiliting); + end; end; -class function TSaveInfoMgr.GenerateRichText(View: IView): TEncodedData; +class function TSaveInfoMgr.GenerateRichText(View: IView; + const AUseHiliting: Boolean): TEncodedData; var Doc: TRTFSnippetDoc; // object that generates RTF document HiliteAttrs: IHiliteAttrs; // syntax highlighter formatting attributes begin Assert(Supports(View, ISnippetView), 'TSaveInfoMgr.GenerateRichText: View is not a snippet view'); - if (View as ISnippetView).Snippet.HiliteSource then + if (View as ISnippetView).Snippet.HiliteSource and AUseHiliting then HiliteAttrs := THiliteAttrsFactory.CreateUserAttrs else HiliteAttrs := THiliteAttrsFactory.CreateNulAttrs; @@ -105,28 +221,65 @@ class function TSaveInfoMgr.GenerateRichText(View: IView): TEncodedData; end; end; -class function TSaveInfoMgr.TryGetFileNameFromUser( - out AFileName: string): Boolean; -var - Dlg: TSaveDialogEx; +procedure TSaveInfoMgr.HighlightQueryHandler(Sender: TObject; + var CanHilite: Boolean); +begin + CanHilite := TFileHiliter.IsHilitingSupported(SelectedFileType); +end; + +constructor TSaveInfoMgr.InternalCreate(AView: IView); +const + DlgHelpKeyword = 'SnippetInfoFileDlg'; resourcestring - sCaption = 'Save Snippet Information'; // dialogue box caption - sFilter = 'Rich Text File (*.rtf)|*.rtf|' // file filter - + 'All files (*.*)|*.*'; + sDefFileName = 'SnippetInfo'; + sDlgCaption = 'Save Snippet Information'; + // descriptions of supported encodings + sASCIIEncoding = 'ASCII'; + // descriptions of supported file filter strings + sRTFDesc = 'Rich text file'; begin - Dlg := TSaveDialogEx.Create(nil); - try - Dlg.Title := sCaption; - Dlg.Options := [ofShowHelp, ofNoTestFileCreate, ofEnableSizing]; - Dlg.Filter := sFilter; - Dlg.FilterIndex := 1; - Dlg.HelpKeyword := 'SnippetInfoFileDlg'; - Result := Dlg.Execute; - if Result then - AFileName := FileOpenFileNameWithExt(Dlg) - finally - Dlg.Free; - end; + inherited InternalCreate; + fView := AView; + fSourceFileInfo := TSourceFileInfo.Create; + // only RTF file type supported at present + fSourceFileInfo.FileTypeInfo[sfRTF] := TSourceFileTypeInfo.Create( + '.rtf', + sRTFDesc, + [ + TSourceFileEncoding.Create(etASCII, sASCIIEncoding) + ] + ); + fSourceFileInfo.DefaultFileName := sDefFileName; + + fSaveDlg := TSaveSourceDlg.Create(nil); + fSaveDlg.Title := sDlgCaption; + fSaveDlg.HelpKeyword := DlgHelpKeyword; + fSaveDlg.CommentStyle := TCommentStyle.csNone; + fSaveDlg.EnableCommentStyles := False; + fSaveDlg.TruncateComments := Preferences.TruncateSourceComments; + fSaveDlg.UseSyntaxHiliting := Preferences.SourceSyntaxHilited; + fSaveDlg.OnPreview := PreviewHandler; + fSaveDlg.OnHiliteQuery := HighlightQueryHandler; + fSaveDlg.OnEncodingQuery := EncodingQueryHandler; +end; + +procedure TSaveInfoMgr.PreviewHandler(Sender: TObject); +resourcestring + sDocTitle = '"%0:s" snippet'; +begin + // Display preview dialog box. We use save dialog as owner to ensure preview + // dialog box is aligned over save dialog box + TPreviewDlg.Execute( + fSaveDlg, + GenerateOutput(sfRTF), + dtRTF, + Format(sDocTitle, [fView.Description]) + ); +end; + +function TSaveInfoMgr.SelectedFileType: TSourceFileType; +begin + Result := fSourceFileInfo.FileTypeFromFilterIdx(fSaveDlg.FilterIndex); end; end. From 7f2f9d9204b7ba7fc491841f955470d979f7d5f0 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 20 Apr 2025 17:14:18 +0100 Subject: [PATCH 284/330] Added support for plain text snippet information TSaveInfoMgr was adapted to offer plain text output of snippet information in Unicode LE & BE, UTF-8 or ANSI format. --- Src/USaveInfoMgr.pas | 76 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 68 insertions(+), 8 deletions(-) diff --git a/Src/USaveInfoMgr.pas b/Src/USaveInfoMgr.pas index 12192672e..853c6e24f 100644 --- a/Src/USaveInfoMgr.pas +++ b/Src/USaveInfoMgr.pas @@ -24,9 +24,9 @@ interface type - /// Class that saves information about a snippet to file in rich - /// text format. The snippet is obtained from a view. Only snippet views are - /// supported. + /// Class that saves information about a snippet to file a user + /// specified format. The snippet is obtained from a view. Only snippet views + /// are supported. TSaveInfoMgr = class(TNoPublicConstructObject) strict private var @@ -39,6 +39,10 @@ TSaveInfoMgr = class(TNoPublicConstructObject) class function GenerateRichText(View: IView; const AUseHiliting: Boolean): TEncodedData; static; + /// Returns encoded data containing a plain text representation of + /// information about the snippet represented by the given view. + function GeneratePlainText: TEncodedData; + /// Returns type of file selected in the associated save dialogue /// box. function SelectedFileType: TSourceFileType; @@ -120,7 +124,8 @@ implementation UPreferences, URTFSnippetDoc, URTFUtils, - USourceGen; + USourceGen, + UTextSnippetDoc; { TSaveInfoMgr } @@ -195,6 +200,23 @@ function TSaveInfoMgr.GenerateOutput(const FileType: TSourceFileType): TFileHiliter.IsHilitingSupported(FileType); case FileType of sfRTF: Result := GenerateRichText(fView, UseHiliting); + sfText: Result := GeneratePlainText; + end; +end; + +function TSaveInfoMgr.GeneratePlainText: TEncodedData; +var + Doc: TTextSnippetDoc; // object that generates RTF document + HiliteAttrs: IHiliteAttrs; // syntax highlighter formatting attributes +begin + Assert(Supports(fView, ISnippetView), + ClassName + '.GeneratePlainText: View is not a snippet view'); + HiliteAttrs := THiliteAttrsFactory.CreateNulAttrs; + Doc := TTextSnippetDoc.Create; + try + Result := Doc.Generate((fView as ISnippetView).Snippet); + finally + Doc.Free; end; end; @@ -235,20 +257,35 @@ constructor TSaveInfoMgr.InternalCreate(AView: IView); sDlgCaption = 'Save Snippet Information'; // descriptions of supported encodings sASCIIEncoding = 'ASCII'; + sANSIDefaultEncoding = 'ANSI (Default)'; + sUTF8Encoding = 'UTF-8'; + sUTF16LEEncoding = 'Unicode (Little Endian)'; + sUTF16BEEncoding = 'Unicode (Big Endian)'; // descriptions of supported file filter strings sRTFDesc = 'Rich text file'; + sTextDesc = 'Plain text file'; begin inherited InternalCreate; fView := AView; fSourceFileInfo := TSourceFileInfo.Create; - // only RTF file type supported at present + // RTF and plain text files supported at present fSourceFileInfo.FileTypeInfo[sfRTF] := TSourceFileTypeInfo.Create( '.rtf', sRTFDesc, [ TSourceFileEncoding.Create(etASCII, sASCIIEncoding) ] - ); + ); + fSourceFileInfo.FileTypeInfo[sfText] := TSourceFileTypeInfo.Create( + '.txt', + sTextDesc, + [ + TSourceFileEncoding.Create(etUTF8, sUTF8Encoding), + TSourceFileEncoding.Create(etUTF16LE, sUTF16LEEncoding), + TSourceFileEncoding.Create(etUTF16BE, sUTF16BEEncoding), + TSourceFileEncoding.Create(etSysDefault, sANSIDefaultEncoding) + ] + ); fSourceFileInfo.DefaultFileName := sDefFileName; fSaveDlg := TSaveSourceDlg.Create(nil); @@ -266,13 +303,36 @@ constructor TSaveInfoMgr.InternalCreate(AView: IView); procedure TSaveInfoMgr.PreviewHandler(Sender: TObject); resourcestring sDocTitle = '"%0:s" snippet'; +var + // Type of snippet information document to preview: this is not always the + // same as the selected file type, because preview dialogue box doesn't + // support some types & we have to use an alternate. + PreviewFileType: TSourceFileType; + // Type of preview document supported by preview dialogue box + PreviewDocType: TPreviewDocType; begin + case SelectedFileType of + sfRTF: + begin + PreviewDocType := dtRTF; + PreviewFileType := sfRTF; + end; + sfText: + begin + PreviewDocType := dtPlainText; + PreviewFileType := sfText; + end; + else + raise Exception.Create( + ClassName + '.PreviewHandler: unsupported file type' + ); + end; // Display preview dialog box. We use save dialog as owner to ensure preview // dialog box is aligned over save dialog box TPreviewDlg.Execute( fSaveDlg, - GenerateOutput(sfRTF), - dtRTF, + GenerateOutput(PreviewFileType), + PreviewDocType, Format(sDocTitle, [fView.Description]) ); end; From 2d6dff051bd74e01ecdc983e054f285bde5457a2 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Tue, 22 Apr 2025 08:21:47 +0100 Subject: [PATCH 285/330] Change how TCSSBuilder generates CSS The order the selectors were generated by TCSSBuilder.AsString was indeterminate (it was the order a dictionary enumerated them). This was changed so the the selectors are now rendered in the order they were created. This was done for cases where the ordering of the CSS selectors matters. --- Src/UCSSBuilder.pas | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/Src/UCSSBuilder.pas b/Src/UCSSBuilder.pas index 172f8b07d..3be1b266a 100644 --- a/Src/UCSSBuilder.pas +++ b/Src/UCSSBuilder.pas @@ -77,7 +77,8 @@ TCSSBuilder = class(TObject) // Class that maps CSS selector names to selector objects TCSSSelectorMap = TObjectDictionary; var - fSelectors: TCSSSelectorMap; // Maps selector names to selector objects + fSelectors: TCSSSelectorMap; // Maps selector names to selector objects + fSelectorNames: TList; // Lists selector names in order created function GetSelector(const Selector: string): TCSSSelector; {Read access method for Selectors property. Returns selector object with given name. @@ -105,10 +106,13 @@ TCSSBuilder = class(TObject) procedure Clear; {Clears all selectors from style sheet and frees selector objects. } + + /// Generates CSS code representing the style sheet. + /// string. The required CSS. + /// The selectors are returned in the order they were created. + /// function AsString: string; - {Generates CSS code representing the style sheet. - @return Required CSS code. - } + property Selectors[const Selector: string]: TCSSSelector read GetSelector; {Array of CSS selectors in style sheet, indexed by selector name} @@ -189,26 +193,29 @@ function TCSSBuilder.AddSelector(const Selector: string): TCSSSelector; begin Result := TCSSSelector.Create(Selector); fSelectors.Add(Selector, Result); + fSelectorNames.Add(Selector); end; function TCSSBuilder.AsString: string; - {Generates CSS code representing the style sheet. - @return Required CSS code. - } var + SelectorName: string; // name of each selector Selector: TCSSSelector; // reference to each selector in map begin Result := ''; - for Selector in fSelectors.Values do + for SelectorName in fSelectorNames do + begin + Selector := fSelectors[SelectorName]; if not Selector.IsEmpty then Result := Result + Selector.AsString; + end; end; procedure TCSSBuilder.Clear; {Clears all selectors from style sheet and frees selector objects. } begin - fSelectors.Clear; // frees selector objects in .Values[] + fSelectorNames.Clear; + fSelectors.Clear; // frees owened selector objects in dictionary end; constructor TCSSBuilder.Create; @@ -221,13 +228,15 @@ constructor TCSSBuilder.Create; fSelectors := TCSSSelectorMap.Create( [doOwnsValues], TTextEqualityComparer.Create ); + fSelectorNames := TList.Create; end; destructor TCSSBuilder.Destroy; {Destructor. Tears down object. } begin - fSelectors.Free; // frees selector objects in fSelectors.Values[] + fSelectorNames.Free; + fSelectors.Free; // frees owened selector objects in dictionary inherited; end; From b7943ec3d58c6412c724e4a71e626030d5931c0b Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Mon, 21 Apr 2025 11:17:18 +0100 Subject: [PATCH 286/330] Add support to TCSS for margin & padding units Modified all TCSS.PaddingProp and TCSS.MarginProp overloaded methods to add a new optional parameter to specify which length units to use. Previously only px was permitted. In order to permit fractional lengths to be specified for use with relative units, length parameters were changed to floating point from Integer. Non-Integer values are truncated to a maximum of 2 decimal places. --- Src/UCSSUtils.pas | 207 +++++++++++++++++++++++++++++----------------- 1 file changed, 133 insertions(+), 74 deletions(-) diff --git a/Src/UCSSUtils.pas b/Src/UCSSUtils.pas index 53d6bb4f0..4d0a9c818 100644 --- a/Src/UCSSUtils.pas +++ b/Src/UCSSUtils.pas @@ -200,28 +200,38 @@ TCSS = record /// string. Required length unit as text. class function LengthUnit(const LU: TCSSLengthUnit): string; static; - /// Builds a space separated list of lengths using specified - /// units. - /// array of Integer [in] List of lengths. - /// TCSSLengthUnit [in] Specifies length unit to apply tp - /// each length. - /// string. Required spaced separated list. - class function LengthList(const List: array of Integer; + /// Builds a space separated list of lengths using the specified + /// unit. + /// array of Single [in] List of lengths. + /// TCSSLengthUnit [in] Specifies length unit to + /// apply to each length. + /// string. Required spaced separated list. + /// Note that lengths are rounded to a maximum of 2 decimal + /// places. + class function LengthList(const List: array of Single; const LU: TCSSLengthUnit = cluPixels): string; static; /// Creates a CSS "margin" property. - /// array of Integer [in] Array of margin widths. Must - /// contain either 1, 2 or 4 values. - /// string. Required CSS property. - class function MarginProp(const Margin: array of Integer): string; - overload; static; + /// array of Single [in] Array of margin + /// widths. Must contain either 1, 2 or 4 values. + /// TCSSLengthUnit [in] Optional length unit to use + /// for each margin width. Defaults to cluPixels. + /// string. Required CSS property. + /// Note that margin values are rounded to a maximum of 2 decimal + /// places. + class function MarginProp(const Margin: array of Single; + const LU: TCSSLengthUnit = cluPixels): string; overload; static; /// Creates a CSS "padding" property. - /// array of Integer [in] Array of padding widths. - /// Must contain either 1, 2 or 4 values. - /// string. Required CSS property. - class function PaddingProp(const Padding: array of Integer): string; - overload; static; + /// array of Single [in] Array of padding + /// widths. Must contain either 1, 2 or 4 values. + /// TCSSLengthUnit [in] Optional length unit to use + /// for each padding width. Defaults to cluPixels. + /// string. Required CSS property. + /// Note that padding values are rounded to a maximum of 2 decimal + /// places. + class function PaddingProp(const Padding: array of Single; + const LU: TCSSLengthUnit = cluPixels): string; overload; static; public /// Creates a CSS "color" property. @@ -312,54 +322,77 @@ TCSS = record /// Creates CSS "margin" property with same width on all edges. /// - /// Integer [in] Margin width in pixels. - /// string. Required CSS property. - class function MarginProp(const Margin: Integer): string; overload; static; + /// Single [in] Margin width. + /// TCSSLengthUnit [in] Optional length unit to use + /// for the margin width. Defaults to cluPixels. + /// string. Required CSS property. + /// Note that the margin value is rounded to a maximum of 2 + /// decimal places. + class function MarginProp(const Margin: Single; + const LU: TCSSLengthUnit = cluPixels): string; overload; static; /// Creates CSS "margin" property with potentially different /// margin widths on each side. - /// Integer [in] Top margin in pixels. - /// Integer [in] Right margin in pixels. - /// Integer [in] Bottom margin in pixels. - /// Integer [in] Left margin in pixels. - /// string. Required CSS property. - class function MarginProp(const Top, Right, Bottom, Left: Integer): string; - overload; static; + /// Single [in] Top margin. + /// Single [in] Right margin. + /// Single [in] Bottom margin. + /// Single [in] Left margin. + /// TCSSLengthUnit [in] Optional length unit to use + /// for each margin width. Defaults to cluPixels. + /// string. Required CSS property. + /// Note that margin values are rounded to a maximum of 2 decimal + /// places. + class function MarginProp(const Top, Right, Bottom, Left: Single; + const LU: TCSSLengthUnit = cluPixels): string; overload; static; /// Creates CSS "margin" or "margin-xxx" property (where "xxx" is /// a side). - /// TCSSSide [in] Specifies side(s) of element whose - /// margin is to be set. - /// Integer [in] Width of margin in pixels. - /// string. Required CSS property. - class function MarginProp(const Side: TCSSSide; const Margin: Integer): - string; overload; static; + /// TCSSSide [in] Specifies the side(s) of the + /// element whose margin is to be set. + /// Single [in] Width of margin in pixels. + /// string. Required CSS property. + /// Note that the margin is rounded to a maximum of 2 decimal + /// places. + class function MarginProp(const Side: TCSSSide; const Margin: Single; + const LU: TCSSLengthUnit = cluPixels): string; overload; static; /// Creates CSS "padding" property with same width on all sides. /// - /// Integer [in] Padding width in pixels. - /// string. Required CSS property. - class function PaddingProp(const Padding: Integer): string; overload; - static; + /// Single [in] Padding width. + /// TCSSLengthUnit [in] Optional length unit to use + /// for the padding width. Defaults to cluPixels. + /// string. Required CSS property. + /// Note that the padding value is rounded to a maximum of 2 + /// decimal places. + class function PaddingProp(const Padding: Single; + const LU: TCSSLengthUnit = cluPixels): string; overload; static; /// Creates CSS "padding" property with potentially different /// padding widths on each side. - /// Integer [in] Top margin in pixels. - /// Integer [in] Right margin in pixels. - /// Integer [in] Bottom margin in pixels. - /// Integer [in] Left margin in pixels. - /// string. Required CSS property. - class function PaddingProp(const Top, Right, Bottom, Left: Integer): - string; overload; static; + /// Single [in] Top margin. + /// Single [in] Right margin. + /// Single [in] Bottom margin. + /// Single [in] Left margin. + /// TCSSLengthUnit [in] Optional length unit to use + /// for each padding width. Defaults to cluPixels. + /// string. Required CSS property. + /// Note that padding values are rounded to a maximum of 2 decimal + /// places. + class function PaddingProp(const Top, Right, Bottom, Left: Single; + const LU: TCSSLengthUnit = cluPixels): string; overload; static; /// Creates CSS "padding" or "padding-xxx" property (where "xxx" /// is a side). - /// TCSSSide [in] Specifies side(s) of element whose - /// padding is to be set. - /// Integer [in] Width of padding in pixels. - /// string. Required CSS property. - class function PaddingProp(const Side: TCSSSide; const Padding: Integer): - string; overload; static; + /// TCSSSide [in] Specifies side(s) of element + /// whose padding is to be set. + /// Single [in] Width of padding. + /// TCSSLengthUnit [in] Optional length unit to use + /// for the padding width. Defaults to cluPixels. + /// string. Required CSS property. + /// Note that the padding value is rounded to a maximum of 2 + /// decimal places. + class function PaddingProp(const Side: TCSSSide; const Padding: Single; + const LU: TCSSLengthUnit = cluPixels): string; overload; static; /// Creates a CSS "text-decoration" property. /// string. Required CSS property. @@ -477,7 +510,7 @@ implementation uses // Delphi - SysUtils, Windows, + SysUtils, Windows, Math, // Project UIStringList, UStrUtils; @@ -519,7 +552,7 @@ class function TCSS.BorderProp(const Side: TCSSSide; const WidthPx: Cardinal; ) else // Hiding border - Result := Format('%s: %s;', [BorderSides[Side], LengthList([Cardinal(0)])]); + Result := Format('%s: %s;', [BorderSides[Side], LengthList([0])]); end; class function TCSS.ColorProp(const Color: TColor): string; @@ -641,11 +674,32 @@ class function TCSS.InlineDisplayProp(const Show: Boolean): string; Result := DisplayProp(BlockDisplayStyles[Show]); end; -class function TCSS.LengthList(const List: array of Integer; +class function TCSS.LengthList(const List: array of Single; const LU: TCSSLengthUnit): string; + + function FmtLength(const L: Single): string; + var + NumX100: Int64; + WholePart, DecPart: Int64; + begin + Assert(not (L < 0), 'TCSS.LengthList: Length < 0'); // avoiding using >= + NumX100 := Round(Abs(L) * 100); + WholePart := NumX100 div 100; + DecPart := NumX100 mod 100; + Result := IntToStr(WholePart); + if DecPart <> 0 then + begin + Result := Result + '.'; // TODO: check CSS spec re localisation of '.' + if DecPart mod 10 = 0 then + Result := Result + IntToStr(DecPart div 10) + else + Result := Result + IntToStr(DecPart); + end; + end; + var Idx: Integer; // loops thru list of values - ALength: Integer; // a length from list + ALength: Single; // a length from list begin Assert((LU <> cluAuto) or (Length(List) = 1), 'TCSS.LengthList: List size may only be 1 when length type is cltAuto'); @@ -659,7 +713,7 @@ class function TCSS.LengthList(const List: array of Integer; ALength := List[Idx]; if Result <> '' then Result := Result + ' '; - Result := Result + IntToStr(ALength); + Result := Result + FmtLength(ALength); if ALength <> 0 then Result := Result + LengthUnit(LU); // only add unit if length not 0 end; @@ -701,32 +755,35 @@ class function TCSS.ListStyleTypeProp(const Value: TCSSListStyleType): string; Result := 'list-style-type: ' + Types[Value] + ';'; end; -class function TCSS.MarginProp(const Margin: array of Integer): string; +class function TCSS.MarginProp(const Margin: array of Single; + const LU: TCSSLengthUnit): string; begin Assert(Length(Margin) in [1,2,4], 'TCSS.MarginProp: Invalid margin parameters'); - Result := 'margin: ' + LengthList(Margin) + ';'; + Result := 'margin: ' + LengthList(Margin, LU) + ';'; end; -class function TCSS.MarginProp(const Top, Right, Bottom, Left: Integer): string; +class function TCSS.MarginProp(const Top, Right, Bottom, Left: Single; + const LU: TCSSLengthUnit): string; begin - Result := MarginProp([Top, Right, Bottom, Left]); + Result := MarginProp([Top, Right, Bottom, Left], LU); end; -class function TCSS.MarginProp(const Margin: Integer): string; +class function TCSS.MarginProp(const Margin: Single; const LU: TCSSLengthUnit): + string; begin - Result := MarginProp([Margin]); + Result := MarginProp([Margin], LU); end; -class function TCSS.MarginProp(const Side: TCSSSide; const Margin: Integer): - string; +class function TCSS.MarginProp(const Side: TCSSSide; const Margin: Single; + const LU: TCSSLengthUnit): string; const // Map of element sides to associated margin properties MarginSides: array[TCSSSide] of string = ( 'margin', 'margin-top', 'margin-left', 'margin-bottom', 'margin-right' ); begin - Result := Format('%s: %s;', [MarginSides[Side], LengthList([Margin])]); + Result := Format('%s: %s;', [MarginSides[Side], LengthList([Margin], LU)]); end; class function TCSS.MaxHeightProp(const HeightPx: Integer): string; @@ -747,33 +804,35 @@ class function TCSS.OverflowProp(const Value: TCSSOverflowValue; Result := Format('%0:s: %1:s;', [Props[Direction], Values[Value]]); end; -class function TCSS.PaddingProp(const Padding: array of Integer): string; +class function TCSS.PaddingProp(const Padding: array of Single; + const LU: TCSSLengthUnit): string; begin Assert(Length(Padding) in [1,2,4], 'TCSS.PaddingProp: Invalid padding parameters'); - Result := 'padding: ' + LengthList(Padding) + ';'; + Result := 'padding: ' + LengthList(Padding, LU) + ';'; end; -class function TCSS.PaddingProp(const Top, Right, Bottom, Left: Integer): - string; +class function TCSS.PaddingProp(const Top, Right, Bottom, Left: Single; + const LU: TCSSLengthUnit): string; begin - Result := PaddingProp([Top, Right, Bottom, Left]); + Result := PaddingProp([Top, Right, Bottom, Left], LU); end; -class function TCSS.PaddingProp(const Padding: Integer): string; +class function TCSS.PaddingProp(const Padding: Single; + const LU: TCSSLengthUnit): string; begin - Result := PaddingProp([Padding]); + Result := PaddingProp([Padding], LU); end; -class function TCSS.PaddingProp(const Side: TCSSSide; - const Padding: Integer): string; +class function TCSS.PaddingProp(const Side: TCSSSide; const Padding: Single; + const LU: TCSSLengthUnit): string; const // Map of element sides to associated padding properties PaddingSides: array[TCSSSide] of string = ( 'padding', 'padding-top', 'padding-left', 'padding-bottom', 'padding-right' ); begin - Result := Format('%s: %s;', [PaddingSides[Side], LengthList([Padding])]); + Result := Format('%s: %s;', [PaddingSides[Side], LengthList([Padding], LU)]); end; class function TCSS.TextAlignProp(const TA: TCSSTextAlign): string; From 616a85da22cd06999573d64004587b0a98abdc08 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Mon, 21 Apr 2025 20:23:14 +0100 Subject: [PATCH 287/330] Modify active text HTML renderer to support HTML 5 Added support for rendering active text as HTML 5 in addition to XHTML. Implmented in such a way that existing code that expects the original behaviour in rendering XHTML does not need to be modified. --- Src/ActiveText.UHTMLRenderer.pas | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Src/ActiveText.UHTMLRenderer.pas b/Src/ActiveText.UHTMLRenderer.pas index e58ad92f2..14ad5a3bb 100644 --- a/Src/ActiveText.UHTMLRenderer.pas +++ b/Src/ActiveText.UHTMLRenderer.pas @@ -65,6 +65,7 @@ TCSSStyles = class(TObject) fTagInfoMap: TTagInfoMap; fIsStartOfTextLine: Boolean; fLINestingDepth: Cardinal; + fTagGen: THTMLClass; const IndentMult = 2; procedure InitialiseTagInfoMap; @@ -73,7 +74,7 @@ TCSSStyles = class(TObject) 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; end; @@ -87,13 +88,18 @@ implementation { 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; @@ -145,7 +151,7 @@ procedure TActiveTextHTML.InitialiseTagInfoMap; function TActiveTextHTML.MakeClosingTag(const Elem: IActiveTextActionElem): string; begin - Result := TXHTML.ClosingTag(fTagInfoMap[Elem.Kind].Name); + Result := fTagGen.ClosingTag(fTagInfoMap[Elem.Kind].Name); end; function TActiveTextHTML.MakeOpeningTag(const Elem: IActiveTextActionElem): @@ -160,7 +166,7 @@ function TActiveTextHTML.MakeOpeningTag(const Elem: IActiveTextActionElem): Attrs := THTMLAttributes.Create; Attrs.Add('class', fCSSStyles.ElemClasses[Elem.Kind]) end; - Result := TXHTML.OpeningTag(fTagInfoMap[Elem.Kind].Name, Attrs); + Result := fTagGen.OpeningTag(fTagInfoMap[Elem.Kind].Name, Attrs); end; function TActiveTextHTML.Render(ActiveText: IActiveText): string; @@ -242,7 +248,7 @@ function TActiveTextHTML.RenderText(const TextElem: IActiveTextTextElem): end else Result := ''; - Result := Result + TXHTML.Entities(TextElem.Text); + Result := Result + fTagGen.Entities(TextElem.Text); end; { TActiveTextHTML.TCSSStyles } From d49f268e94bcf18adbc9d46938d76a7e0c5fa02d Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Mon, 21 Apr 2025 20:27:01 +0100 Subject: [PATCH 288/330] Modify HTML builder to expose some protected methods All the formaer virtual abstract protected instance were made public and changed to class methods. This is so that the information they provide is made available to calling code without instantiating a THTMLBuilder derivative object. --- Src/UHTMLBuilder.pas | 46 ++++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/Src/UHTMLBuilder.pas b/Src/UHTMLBuilder.pas index 1b224c0f3..a3a0418bf 100644 --- a/Src/UHTMLBuilder.pas +++ b/Src/UHTMLBuilder.pas @@ -76,19 +76,19 @@ THTMLBuilder = class abstract (TObject) BodyTagName = 'body'; PreTagName = 'pre'; SpanTagName = 'span'; - strict protected + public /// Returns the class used to generate tags for the appropriate /// type of HTML. - function TagGenerator: THTMLClass; virtual; abstract; + class function TagGenerator: THTMLClass; virtual; abstract; /// Returns any preamble to be written to the HTML before the /// opening <html> tag. - function Preamble: string; virtual; abstract; + class function Preamble: string; virtual; abstract; /// Returns the attributes of the document's <html> tag. /// - function HTMLTagAttrs: IHTMLAttributes; virtual; abstract; + class function HTMLTagAttrs: IHTMLAttributes; virtual; abstract; /// Returns any <meta> tags to be included within the /// document's <head> tag. - function MetaTags: string; virtual; abstract; + class function MetaTags: string; virtual; abstract; public /// Object constructor. Initialises object with empty body. /// @@ -146,19 +146,19 @@ TXHTMLBuilder = class sealed(THTMLBuilder) // XML document type XHTMLDocType = ''; - strict protected + public /// Returns the class used to generate XHTML compliant tags. /// - function TagGenerator: THTMLClass; override; + class function TagGenerator: THTMLClass; override; /// Returns the XML processing instruction followed by the XHTML /// doctype. - function Preamble: string; override; + class function Preamble: string; override; /// Returns the attributes required for an XHTML <html> tag. /// - function HTMLTagAttrs: IHTMLAttributes; override; + class function HTMLTagAttrs: IHTMLAttributes; override; /// Returns a <meta> tag that specifies the text/html /// content type and UTF-8 encodiing. - function MetaTags: string; override; + class function MetaTags: string; override; end; /// Class used to create the content of a HTML 5 document. @@ -167,18 +167,18 @@ THTML5Builder = class sealed(THTMLBuilder) const // HTML 5 document type HTML5DocType = ''; - strict protected + public /// Returns the class used to generate HTML 5 compliant tags. /// - function TagGenerator: THTMLClass; override; + class function TagGenerator: THTMLClass; override; /// Returns the HTML 5 doctype. - function Preamble: string; override; + class function Preamble: string; override; /// Returns the attributes required for an HTML 5 <html> /// tag. - function HTMLTagAttrs: IHTMLAttributes; override; + class function HTMLTagAttrs: IHTMLAttributes; override; /// Returns a <meta> tag that specifies that the document /// uses UTF-8 encoding. - function MetaTags: string; override; + class function MetaTags: string; override; end; @@ -312,7 +312,7 @@ function THTMLBuilder.TitleTag: string; { TXHTMLBuilder } -function TXHTMLBuilder.HTMLTagAttrs: IHTMLAttributes; +class function TXHTMLBuilder.HTMLTagAttrs: IHTMLAttributes; begin Result := THTMLAttributes.Create( [THTMLAttribute.Create('xmlns', 'https://www.w3.org/1999/xhtml'), @@ -321,7 +321,7 @@ function TXHTMLBuilder.HTMLTagAttrs: IHTMLAttributes; ); end; -function TXHTMLBuilder.MetaTags: string; +class function TXHTMLBuilder.MetaTags: string; begin Result := TagGenerator.SimpleTag( MetaTagName, @@ -332,24 +332,24 @@ function TXHTMLBuilder.MetaTags: string; ); end; -function TXHTMLBuilder.Preamble: string; +class function TXHTMLBuilder.Preamble: string; begin Result := XMLProcInstruction + EOL + XHTMLDocType; end; -function TXHTMLBuilder.TagGenerator: THTMLClass; +class function TXHTMLBuilder.TagGenerator: THTMLClass; begin Result := TXHTML; end; { THTML5Builder } -function THTML5Builder.HTMLTagAttrs: IHTMLAttributes; +class function THTML5Builder.HTMLTagAttrs: IHTMLAttributes; begin Result := THTMLAttributes.Create('lang', 'en'); end; -function THTML5Builder.MetaTags: string; +class function THTML5Builder.MetaTags: string; begin // Result := TagGenerator.SimpleTag( @@ -358,12 +358,12 @@ function THTML5Builder.MetaTags: string; ); end; -function THTML5Builder.Preamble: string; +class function THTML5Builder.Preamble: string; begin Result := HTML5DocType; end; -function THTML5Builder.TagGenerator: THTMLClass; +class function THTML5Builder.TagGenerator: THTMLClass; begin Result := THTML5; end; From 4f9214ff82863b4d2da600bf6298192681f85d6f Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Mon, 21 Apr 2025 20:20:57 +0100 Subject: [PATCH 289/330] Add support for outputting snippet info as HTML Added new UHTMLSnippetDoc unit to project that contains classes to render snippet information in either HTML 5 or XHTML. --- Src/CodeSnip.dpr | 3 +- Src/CodeSnip.dproj | 1 + Src/UHTMLSnippetDoc.pas | 528 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 531 insertions(+), 1 deletion(-) create mode 100644 Src/UHTMLSnippetDoc.pas diff --git a/Src/CodeSnip.dpr b/Src/CodeSnip.dpr index 719053105..522a95b04 100644 --- a/Src/CodeSnip.dpr +++ b/Src/CodeSnip.dpr @@ -376,7 +376,8 @@ uses ClassHelpers.UGraphics in 'ClassHelpers.UGraphics.pas', ClassHelpers.UActions in 'ClassHelpers.UActions.pas', USaveInfoMgr in 'USaveInfoMgr.pas', - ClassHelpers.RichEdit in 'ClassHelpers.RichEdit.pas'; + ClassHelpers.RichEdit in 'ClassHelpers.RichEdit.pas', + UHTMLSnippetDoc in 'UHTMLSnippetDoc.pas'; // Include resources {$Resource ExternalObj.tlb} // Type library file diff --git a/Src/CodeSnip.dproj b/Src/CodeSnip.dproj index e430334ce..19c55d1ec 100644 --- a/Src/CodeSnip.dproj +++ b/Src/CodeSnip.dproj @@ -583,6 +583,7 @@ + Base diff --git a/Src/UHTMLSnippetDoc.pas b/Src/UHTMLSnippetDoc.pas new file mode 100644 index 000000000..27ca5d861 --- /dev/null +++ b/Src/UHTMLSnippetDoc.pas @@ -0,0 +1,528 @@ +{ + * 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 a class that renders a HTML document that describes a snippet. +} + + +unit UHTMLSnippetDoc; + +interface + +uses + // Delphi + SysUtils, + Graphics, + // Project + ActiveText.UHTMLRenderer, + ActiveText.UMain, + Hiliter.UGlobals, + UColours, + UEncodings, + UHTMLBuilder, + UHTMLUtils, + UIStringList, + USnippetDoc; + +type + THTMLSnippetDocClass = class of THTMLSnippetDoc; + + /// Abstract base class for classes that render a document that + /// describes a snippet using HTML. + THTMLSnippetDoc = class abstract (TSnippetDoc) + strict private + var + /// Attributes that determine the formatting of highlighted + /// source code. + fHiliteAttrs: IHiliteAttrs; + /// Flag indicates whether to output in colour. + fUseColour: Boolean; + /// Object used to build HTML source code document. + fDocument: TStringBuilder; + /// Type of class used to generate the HTML of the snippet's + /// source code and to provide addition HTML information. + fBuilderClass: THTMLBuilderClass; + /// Static class used to generate HTML tags. + fTagGen: THTMLClass; + const + /// Colour of plain text in the HTML document. + TextColour = clBlack; + /// Colour of HTML links in the document. + LinkColour = clExternalLink; + /// Colour of warning text in the HTML document. + WarningColour = clWarningText; + /// Colour used for <var> tags in the HTML document. + /// + VarColour = clVarText; + + // Names of various HTML tags used in the document + HTMLTag = 'html'; + HeadTag = 'head'; + TitleTag = 'title'; + BodyTag = 'body'; + H1Tag = 'h1'; + H2Tag = 'h2'; + DivTag = 'div'; + ParaTag = 'p'; + StrongTag = 'strong'; + EmphasisTag = 'em'; + CodeTag = 'code'; + LinkTag = 'a'; + StyleTag = 'style'; + TableTag = 'table'; + TableBodyTag = 'tbody'; + TableRowTag = 'tr'; + TableColTag = 'td'; + + // Names of HTML attributes used in the document + ClassAttr = 'class'; + + // Names of HTML classes used in the document + DBInfoClass = 'db-info'; + MainDBClass = 'main-db'; + UserDBClass = 'user-db'; + IndentClass = 'indent'; + WarningClass = 'warning'; + + /// Name of document body font. + BodyFontName = 'Tahoma'; + /// Size of paragraph font, in points. + BodyFontSize = 10; // points + /// Size of H1 heading font, in points. + H1FontSize = 14; // points + /// Size of H2 heading font, in points. + H2FontSize = 12; // points + /// Size of font used for database information, in points. + /// + DBInfoFontSize = 9; // points + + strict private + /// Creates and returns the inline CSS used in the HTML document. + /// + function BuildCSS: string; + /// Renders the given active text as HTML. + function ActiveTextToHTML(ActiveText: IActiveText): string; + strict protected + /// Returns a reference to the builder class used to create the + /// required flavour of HTML. + function BuilderClass: THTMLBuilderClass; virtual; abstract; + /// Initialises the HTML document. + procedure InitialiseDoc; override; + /// Adds the given heading (i.e. snippet name) to the document. + /// Can be user defined or from main database. + /// The heading is coloured according to whether user defined or + /// not iff coloured output is required. + procedure RenderHeading(const Heading: string; const UserDefined: Boolean); + override; + /// Adds the given snippet description to the document. + /// Active text formatting is observed and styled to suit the + /// document. + procedure RenderDescription(const Desc: IActiveText); override; + /// Highlights the given source code and adds it to the document. + /// + procedure RenderSourceCode(const SourceCode: string); override; + /// Adds the given title, followed by the given text, to the + /// document. + procedure RenderTitledText(const Title, Text: string); override; + /// Adds a comma-separated list of text, preceded by the given + /// title, to the document. + procedure RenderTitledList(const Title: string; List: IStringList); + override; + /// Outputs the given compiler test info, preceded by the given + /// heading. + procedure RenderCompilerInfo(const Heading: string; + const Info: TCompileDocInfoArray); override; + /// Outputs the given message stating that there is no compiler + /// test info, preceded by the given heading. + procedure RenderNoCompilerInfo(const Heading, NoCompileTests: string); + override; + /// Adds the given extra information about the snippet to the + /// document. + /// Active text formatting is observed and styled to suit the + /// document. + procedure RenderExtra(const ExtraText: IActiveText); override; + /// Adds the given information about a code snippets database to + /// the document. + procedure RenderDBInfo(const Text: string); override; + /// Finalises the document and returns its content as encoded + /// data. + function FinaliseDoc: TEncodedData; override; + public + /// Constructs an object to render snippet information. + /// IHiliteAttrs [in] Defines the style of + /// syntax highlighting to be used for the source code. + /// Boolean [in] Set True to render + /// the document in colour or False for black and white. + constructor Create(const HiliteAttrs: IHiliteAttrs; + const UseColour: Boolean = True); + /// Destroys the object. + destructor Destroy; override; + end; + + /// Class that renders a document that describes a snippet using + /// XHTML. + TXHTMLSnippetDoc = class sealed (THTMLSnippetDoc) + strict protected + /// Returns a reference to the builder class used to create valid + /// XHTML. + function BuilderClass: THTMLBuilderClass; override; + end; + + /// Class that renders a document that describes a snippet using + /// HTML 5. + THTML5SnippetDoc = class sealed (THTMLSnippetDoc) + strict protected + /// Returns a reference to the builder class used to create valid + /// HTML 5. + function BuilderClass: THTMLBuilderClass; override; + end; + +implementation + +uses + // Project + Hiliter.UCSS, + Hiliter.UHiliters, + UCSSBuilder, + UCSSUtils, + UFontHelper, + UPreferences; + +{ THTMLSnippetDoc } + +function THTMLSnippetDoc.ActiveTextToHTML(ActiveText: IActiveText): string; +var + HTMLWriter: TActiveTextHTML; // Object that generates HTML from active text +begin + HTMLWriter := TActiveTextHTML.Create(fTagGen); + try + Result := HTMLWriter.Render(ActiveText); + finally + HTMLWriter.Free; + end; +end; + +function THTMLSnippetDoc.BuildCSS: string; +var + CSS: TCSSBuilder; + HiliterCSS: THiliterCSS; + BodyFont: TFont; // default content font sized per preferences + MonoFont: TFont; // default mono font sized per preferences +begin + BodyFont := nil; + MonoFont := nil; + CSS := TCSSBuilder.Create; + try + MonoFont := TFont.Create; + TFontHelper.SetDefaultMonoFont(MonoFont); + BodyFont := TFont.Create; + BodyFont.Name := BodyFontName; + BodyFont.Size := BodyFontSize; + MonoFont.Size := BodyFontSize; + + // tag style + CSS.AddSelector(BodyTag) + .AddProperty(TCSS.FontProps(BodyFont)) + .AddProperty(TCSS.ColorProp(TextColour)); + //

        tag style + CSS.AddSelector(H1Tag) + .AddProperty(TCSS.FontSizeProp(H1FontSize)) + .AddProperty(TCSS.FontWeightProp(cfwBold)) + .AddProperty(TCSS.MarginProp(0.75, 0, 0.75, 0, cluEm)); + //

        tag + CSS.AddSelector(H2Tag) + .AddProperty(TCSS.FontSizeProp(H2FontSize)); + //

        tag style + CSS.AddSelector(ParaTag) + .AddProperty(TCSS.MarginProp(0.5, 0, 0.5, 0, cluEm)); + // tag style + // note: wanted to use :last-child to style right column, but not supported + // by TWebBrowser that is used for the preview + CSS.AddSelector(TableTag) + .AddProperty(TCSS.MarginProp(0.5, 0, 0.5, 0, cluEm)); + CSS.AddSelector(TableColTag) + .AddProperty(TCSS.PaddingProp(cssRight, 0.5, cluEm)) + .AddProperty(TCSS.PaddingProp(cssLeft, 0)); + // tag style + CSS.AddSelector(CodeTag) + .AddProperty(TCSS.FontProps(MonoFont)); + // tag style + CSS.AddSelector(LinkTag) + .AddProperty(TCSS.ColorProp(LinkColour)) + .AddProperty(TCSS.TextDecorationProp([ctdUnderline])); + // tag style + CSS.AddSelector('var') + .AddProperty(TCSS.ColorProp(VarColour)) + .AddProperty(TCSS.FontStyleProp(cfsItalic)); + + // Set active text list classes + + // list styling + CSS.AddSelector('ul, ol') + .AddProperty(TCSS.MarginProp(0.5, 0, 0.5, 0, cluEm)) + .AddProperty(TCSS.PaddingProp(cssAll, 0)) + .AddProperty(TCSS.PaddingProp(cssLeft, 1.5, cluEm)) + .AddProperty(TCSS.ListStylePositionProp(clspOutside)) + .AddProperty(TCSS.ListStyleTypeProp(clstDisc)); + CSS.AddSelector('ul') + .AddProperty(TCSS.ListStyleTypeProp(clstDisc)); + CSS.AddSelector('ol') + .AddProperty(TCSS.ListStyleTypeProp(clstDecimal)); + CSS.AddSelector('li') + .AddProperty(TCSS.PaddingProp(cssAll, 0)) + .AddProperty(TCSS.MarginProp(0.25, 0, 0.25, 0, cluEm)); + CSS.AddSelector('li ol, li ul') + .AddProperty(TCSS.MarginProp(0.25, 0, 0.25, 0, cluEm)); + CSS.AddSelector('li li') + .AddProperty(TCSS.PaddingProp(cssLeft, 0)) + .AddProperty(TCSS.MarginProp(0)); + + // class used to denote snippet is user defined + CSS.AddSelector('.' + UserDBClass) + .AddProperty(TCSS.ColorProp(Preferences.DBHeadingColours[True])); + // class used for smaller text describing database + CSS.AddSelector('.' + DBInfoClass) + .AddProperty(TCSS.FontSizeProp(DBInfoFontSize)) + .AddProperty(TCSS.FontStyleProp(cfsItalic)); + // class used to indent tag content + CSS.AddSelector('.' + IndentClass) + .AddProperty(TCSS.MarginProp(cssLeft, 1.5, cluEm)); + + // default active text classes + CSS.AddSelector('.' + WarningClass) + .AddProperty(TCSS.ColorProp(WarningColour)) + .AddProperty(TCSS.FontWeightProp(cfwBold)); + + // CSS used by highlighters + fHiliteAttrs.FontSize := BodyFontSize; + HiliterCSS := THiliterCSS.Create(fHiliteAttrs); + try + HiliterCSS.BuildCSS(CSS); + finally + HiliterCSS.Free; + end; + + Result := CSS.AsString; + finally + BodyFont.Free; + MonoFont.Free; + CSS.Free; + end; +end; + +constructor THTMLSnippetDoc.Create(const HiliteAttrs: IHiliteAttrs; + const UseColour: Boolean); +begin + inherited Create; + fDocument := TStringBuilder.Create; + fBuilderClass := BuilderClass; + fTagGen := BuilderClass.TagGenerator; + fHiliteAttrs := HiliteAttrs; + fUseColour := UseColour; +end; + +destructor THTMLSnippetDoc.Destroy; +begin + fDocument.Free; + inherited; +end; + +function THTMLSnippetDoc.FinaliseDoc: TEncodedData; +begin + // + fDocument.AppendLine(fTagGen.ClosingTag(BodyTag)); + // + fDocument.AppendLine(fTagGen.ClosingTag(HTMLTag)); + + Result := TEncodedData.Create(fDocument.ToString, etUTF8); +end; + +procedure THTMLSnippetDoc.InitialiseDoc; +resourcestring + sTitle = 'Snippet Information'; +begin + // doc type etc + fDocument.AppendLine(BuilderClass.Preamble); + // + fDocument.AppendLine(fTagGen.OpeningTag(HTMLTag, BuilderClass.HTMLTagAttrs)); + // + fDocument.AppendLine(fTagGen.OpeningTag(HeadTag)); + // .. + fDocument.AppendLine(BuilderClass.MetaTags); + // + fDocument.AppendLine(fTagGen.CompoundTag(TitleTag, fTagGen.Entities(sTitle))); + // <style> + fDocument.AppendLine( + fTagGen.OpeningTag(StyleTag, THTMLAttributes.Create('type', 'text/css')) + ); + fDocument.Append(BuildCSS); + // </style> + fDocument.AppendLine(fTagGen.ClosingTag(StyleTag)); + // </head> + fDocument.AppendLine(fTagGen.ClosingTag(HeadTag)); + // <body> + fDocument.AppendLine(fTagGen.OpeningTag(BodyTag)); +end; + +procedure THTMLSnippetDoc.RenderCompilerInfo(const Heading: string; + const Info: TCompileDocInfoArray); +var + CompilerInfo: TCompileDocInfo; // info about each compiler +begin + fDocument.AppendLine( + fTagGen.CompoundTag( + ParaTag, fTagGen.CompoundTag(StrongTag, fTagGen.Entities(Heading)) + ) + ); + fDocument + .AppendLine( + fTagGen.OpeningTag( + TableTag, THTMLAttributes.Create(ClassAttr, IndentClass) + ) + ) + .AppendLine(fTagGen.OpeningTag(TableBodyTag)); + + for CompilerInfo in Info do + begin + fDocument + .AppendLine(fTagGen.OpeningTag(TableRowTag)) + .AppendLine( + fTagGen.CompoundTag( + TableColTag, fTagGen.Entities(CompilerInfo.Compiler) + ) + ) + .AppendLine( + fTagGen.CompoundTag( + TableColTag, + fTagGen.CompoundTag( + EmphasisTag, fTagGen.Entities(CompilerInfo.Result) + ) + ) + ) + .AppendLine(fTagGen.ClosingTag(TableRowTag)); + end; + + fDocument + .AppendLine(fTagGen.ClosingTag(TableBodyTag)) + .AppendLine(fTagGen.ClosingTag(TableTag)); +end; + +procedure THTMLSnippetDoc.RenderDBInfo(const Text: string); +begin + fDocument.AppendLine( + fTagGen.CompoundTag( + ParaTag, + THTMLAttributes.Create(ClassAttr, DBInfoClass), + fTagGen.Entities(Text) + ) + ); +end; + +procedure THTMLSnippetDoc.RenderDescription(const Desc: IActiveText); +begin + fDocument.AppendLine(ActiveTextToHTML(Desc)); +end; + +procedure THTMLSnippetDoc.RenderExtra(const ExtraText: IActiveText); +begin + fDocument.AppendLine(ActiveTextToHTML(ExtraText)); +end; + +procedure THTMLSnippetDoc.RenderHeading(const Heading: string; + const UserDefined: Boolean); +var + Attrs: IHTMLAttributes; +const + DBClasses: array[Boolean] of string = (MainDBClass, UserDBClass); +begin + Attrs := THTMLAttributes.Create(ClassAttr, DBClasses[UserDefined]); + fDocument.AppendLine( + fTagGen.CompoundTag(H1Tag, Attrs, fTagGen.Entities(Heading)) + ); +end; + +procedure THTMLSnippetDoc.RenderNoCompilerInfo(const Heading, + NoCompileTests: string); +begin + fDocument.AppendLine( + fTagGen.CompoundTag( + ParaTag, fTagGen.CompoundTag(StrongTag, fTagGen.Entities(Heading)) + ) + ); + fDocument.AppendLine( + fTagGen.CompoundTag( + ParaTag, + THTMLAttributes.Create(ClassAttr, IndentClass), + fTagGen.Entities(NoCompileTests) + ) + ); +end; + +procedure THTMLSnippetDoc.RenderSourceCode(const SourceCode: string); +var + Renderer: IHiliteRenderer; // renders highlighted source as RTF + HTMLBuilder: THTMLBuilder; // constructs the HTML of the highlighted source +resourcestring + sHeading = 'Source Code:'; +begin + fDocument.AppendLine( + fTagGen.CompoundTag( + ParaTag, + fTagGen.CompoundTag(StrongTag, fTagGen.Entities(sHeading)) + ) + ); + fDocument.AppendLine( + fTagGen.OpeningTag(DivTag, THTMLAttributes.Create(ClassAttr, IndentClass)) + ); + HTMLBuilder := THTML5Builder.Create; + try + Renderer := THTMLHiliteRenderer.Create(HTMLBuilder, fHiliteAttrs); + TSyntaxHiliter.Hilite(SourceCode, Renderer); + fDocument.AppendLine(HTMLBuilder.HTMLFragment); + finally + HTMLBuilder.Free; + end; + fDocument.AppendLine(fTagGen.ClosingTag(DivTag)); +end; + +procedure THTMLSnippetDoc.RenderTitledList(const Title: string; + List: IStringList); +begin + RenderTitledText(Title, CommaList(List)); +end; + +procedure THTMLSnippetDoc.RenderTitledText(const Title, Text: string); +begin + fDocument.AppendLine( + fTagGen.CompoundTag( + ParaTag, fTagGen.CompoundTag(StrongTag, fTagGen.Entities(Title)) + ) + ); + fDocument.AppendLine( + fTagGen.CompoundTag( + ParaTag, + THTMLAttributes.Create(ClassAttr, IndentClass), + fTagGen.Entities(Text) + ) + ); +end; + +{ TXHTMLSnippetDoc } + +function TXHTMLSnippetDoc.BuilderClass: THTMLBuilderClass; +begin + Result := TXHTMLBuilder; +end; + +{ THTML5SnippetDoc } + +function THTML5SnippetDoc.BuilderClass: THTMLBuilderClass; +begin + Result := THTML5Builder; +end; + +end. From b4efc1bf0a882679ef49ab845e05dbad29331efc Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Mon, 21 Apr 2025 20:29:43 +0100 Subject: [PATCH 290/330] Added support for HTML 5 & XHTML snippet information TSaveInfoMgr was adapted to offer HTML 5 and XML output of snippet information in UTF-8 format. --- Src/USaveInfoMgr.pas | 60 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/Src/USaveInfoMgr.pas b/Src/USaveInfoMgr.pas index 853c6e24f..8f5e1f7c0 100644 --- a/Src/USaveInfoMgr.pas +++ b/Src/USaveInfoMgr.pas @@ -18,6 +18,7 @@ interface // Project UBaseObjects, UEncodings, + UHTMLSnippetDoc, USaveSourceDlg, USourceFileInfo, UView; @@ -39,6 +40,17 @@ TSaveInfoMgr = class(TNoPublicConstructObject) class function GenerateRichText(View: IView; const AUseHiliting: Boolean): TEncodedData; static; + /// <summary>Returns encoded data containing a HTML representation of the + /// required snippet information.</summary> + /// <param name="AUseHiliting"><c>Boolean</c> [in] Determines whether + /// source code is syntax highlighted or not.</param> + /// <param name="GeneratorClass"><c>THTMLSnippetDocClass</c> [in] Class of + /// object used to generate the required flavour of HTML.</param> + /// <returns><c>TEncodedData</c>. Required HTML document, encoded as UTF-8. + /// </returns> + function GenerateHTML(const AUseHiliting: Boolean; + const GeneratorClass: THTMLSnippetDocClass): TEncodedData; + /// <summary>Returns encoded data containing a plain text representation of /// information about the snippet represented by the given view.</summary> function GeneratePlainText: TEncodedData; @@ -191,6 +203,24 @@ class procedure TSaveInfoMgr.Execute(View: IView); end; end; +function TSaveInfoMgr.GenerateHTML(const AUseHiliting: Boolean; + const GeneratorClass: THTMLSnippetDocClass): TEncodedData; +var + Doc: THTMLSnippetDoc; // object that generates RTF document + HiliteAttrs: IHiliteAttrs; // syntax highlighter formatting attributes +begin + if (fView as ISnippetView).Snippet.HiliteSource and AUseHiliting then + HiliteAttrs := THiliteAttrsFactory.CreateUserAttrs + else + HiliteAttrs := THiliteAttrsFactory.CreateNulAttrs; + Doc := GeneratorClass.Create(HiliteAttrs); + try + Result := Doc.Generate((fView as ISnippetView).Snippet); + finally + Doc.Free; + end; +end; + function TSaveInfoMgr.GenerateOutput(const FileType: TSourceFileType): TEncodedData; var @@ -201,6 +231,8 @@ function TSaveInfoMgr.GenerateOutput(const FileType: TSourceFileType): case FileType of sfRTF: Result := GenerateRichText(fView, UseHiliting); sfText: Result := GeneratePlainText; + sfHTML5: Result := GenerateHTML(UseHiliting, THTML5SnippetDoc); + sfXHTML: Result := GenerateHTML(UseHiliting, TXHTMLSnippetDoc); end; end; @@ -264,6 +296,8 @@ constructor TSaveInfoMgr.InternalCreate(AView: IView); // descriptions of supported file filter strings sRTFDesc = 'Rich text file'; sTextDesc = 'Plain text file'; + sHTML5Desc = 'HTML 5 file'; + sXHTMLDesc = 'XHTML file'; begin inherited InternalCreate; fView := AView; @@ -286,6 +320,21 @@ constructor TSaveInfoMgr.InternalCreate(AView: IView); TSourceFileEncoding.Create(etSysDefault, sANSIDefaultEncoding) ] ); + fSourceFileInfo.FileTypeInfo[sfHTML5] := TSourceFileTypeInfo.Create( + '.html', + sHTML5Desc, + [ + TSourceFileEncoding.Create(etUTF8, sUTF8Encoding) + ] + ); + fSourceFileInfo.DefaultFileName := sDefFileName; + fSourceFileInfo.FileTypeInfo[sfXHTML] := TSourceFileTypeInfo.Create( + '.html', + sXHTMLDesc, + [ + TSourceFileEncoding.Create(etUTF8, sUTF8Encoding) + ] + ); fSourceFileInfo.DefaultFileName := sDefFileName; fSaveDlg := TSaveSourceDlg.Create(nil); @@ -314,21 +363,28 @@ procedure TSaveInfoMgr.PreviewHandler(Sender: TObject); case SelectedFileType of sfRTF: begin + // RTF is previewed as is PreviewDocType := dtRTF; PreviewFileType := sfRTF; end; sfText: begin + // Plain text us previewed as is PreviewDocType := dtPlainText; PreviewFileType := sfText; end; + sfHTML5, sfXHTML: + begin + // Both HTML 5 and XHTML are previewed as XHTML + PreviewDocType := dtHTML; + PreviewFileType := sfXHTML; + end; else raise Exception.Create( ClassName + '.PreviewHandler: unsupported file type' ); end; - // Display preview dialog box. We use save dialog as owner to ensure preview - // dialog box is aligned over save dialog box + // Display preview dialogue box aligned over the save dialogue TPreviewDlg.Execute( fSaveDlg, GenerateOutput(PreviewFileType), From e394eb3ef7c2879eef1be9bdf859ba86125aca85 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Tue, 22 Apr 2025 20:40:55 +0100 Subject: [PATCH 291/330] Add new StrMaxSequenceLength routine & unit test Added the new function to the UStrUtils unit. Added unit test for the function to Tests/Src/DUnit/TestUStrUtils unit. --- Src/UStrUtils.pas | 32 +++++++++++++++++++++++++++++++ Tests/Src/DUnit/TestUStrUtils.pas | 18 +++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/Src/UStrUtils.pas b/Src/UStrUtils.pas index 4e0e29584..5e613eebc 100644 --- a/Src/UStrUtils.pas +++ b/Src/UStrUtils.pas @@ -289,6 +289,15 @@ function StrOfChar(const Ch: Char; const Count: Word): string; /// <remarks>If Count is zero then an empty string is returned.</remarks> function StrOfSpaces(const Count: Word): string; +/// <summary>Returns the length of the longest repeating sequence of a given +/// character in a given string.</summary> +/// <param name="Ch"><c>Char</c> [in] Character to search for.</param> +/// <param name="S"><c>string</c> [in] String to search within.</param> +/// <returns><c>Cardinal</c>. Length of the longest sequence of <c>Ch</c> in +/// <c>S</c>, or <c>0</c> if <c>Ch</c> is not in <c>S</c>.</returns> +function StrMaxSequenceLength(const Ch: Char; const S: UnicodeString): Cardinal; + + implementation @@ -944,5 +953,28 @@ function StrOfSpaces(const Count: Word): string; Result := StrOfChar(' ', Count); end; +function StrMaxSequenceLength(const Ch: Char; const S: UnicodeString): Cardinal; +var + StartPos: Integer; + Count: Cardinal; + Idx: Integer; +begin + Result := 0; + StartPos := StrPos(Ch, S); + while StartPos > 0 do + begin + Count := 1; + Idx := StartPos + 1; + while (Idx <= Length(S)) and (S[Idx] = Ch) do + begin + Inc(Idx); + Inc(Count); + end; + if Count > Result then + Result := Count; + StartPos := StrPos(Ch, S, Idx); + end; +end; + end. diff --git a/Tests/Src/DUnit/TestUStrUtils.pas b/Tests/Src/DUnit/TestUStrUtils.pas index b540f3171..caeed5503 100644 --- a/Tests/Src/DUnit/TestUStrUtils.pas +++ b/Tests/Src/DUnit/TestUStrUtils.pas @@ -70,6 +70,8 @@ TTestStrUtilsRoutines = class(TTestCase) procedure TestStrMakeSentence; procedure TestStrIf; procedure TestStrBackslashEscape; + procedure TestStrMaxSequenceLength; + end; @@ -672,6 +674,22 @@ procedure TTestStrUtilsRoutines.TestStrMatchText; ); end; +procedure TTestStrUtilsRoutines.TestStrMaxSequenceLength; +begin + CheckEquals(0, StrMaxSequenceLength('~', ''), 'Test 1'); + CheckEquals(0, StrMaxSequenceLength('~', 'freda'), 'Test 2'); + CheckEquals(1, StrMaxSequenceLength('~', 'fre~da'), 'Test 3'); + CheckEquals(1, StrMaxSequenceLength('|', '|fre~da'), 'Test 4'); + CheckEquals(1, StrMaxSequenceLength('|', 'fre~da|'), 'Test 5'); + CheckEquals(3, StrMaxSequenceLength('|', '|fre||da|||'), 'Test 6'); + CheckEquals(3, StrMaxSequenceLength('|', '|||fre||da|||'), 'Test 7'); + CheckEquals(4, StrMaxSequenceLength('|', '|||fre||||da|||'), 'Test 8'); + CheckEquals(4, StrMaxSequenceLength('|', '|||f||re||||da|||'), 'Test 9'); + CheckEquals(10, StrMaxSequenceLength('|', '||||||||||'), 'Test 10'); + CheckEquals(1, StrMaxSequenceLength('|', '|'), 'Test 11'); + CheckEquals(0, StrMaxSequenceLength('~', 'x'), 'Test 12'); +end; + procedure TTestStrUtilsRoutines.TestStrPos_overload1; begin CheckEquals(0, StrPos('Fo', 'Bar'), 'Test 1'); From 7410c9caddd3917ec25dd77298ac434348b710e4 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 27 Apr 2025 20:19:18 +0100 Subject: [PATCH 292/330] Add TMarkdown class to format Markdown code Added new UMarkdownUtils unit to the project that contains the new TMarkdown static class that creates correctly formatted and escaped Markdown code. --- Src/CodeSnip.dpr | 3 +- Src/CodeSnip.dproj | 1 + Src/UMarkdownUtils.pas | 478 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 481 insertions(+), 1 deletion(-) create mode 100644 Src/UMarkdownUtils.pas diff --git a/Src/CodeSnip.dpr b/Src/CodeSnip.dpr index 522a95b04..e8f354285 100644 --- a/Src/CodeSnip.dpr +++ b/Src/CodeSnip.dpr @@ -377,7 +377,8 @@ uses ClassHelpers.UActions in 'ClassHelpers.UActions.pas', USaveInfoMgr in 'USaveInfoMgr.pas', ClassHelpers.RichEdit in 'ClassHelpers.RichEdit.pas', - UHTMLSnippetDoc in 'UHTMLSnippetDoc.pas'; + UHTMLSnippetDoc in 'UHTMLSnippetDoc.pas', + UMarkdownUtils in 'UMarkdownUtils.pas'; // Include resources {$Resource ExternalObj.tlb} // Type library file diff --git a/Src/CodeSnip.dproj b/Src/CodeSnip.dproj index 19c55d1ec..b8d02d11e 100644 --- a/Src/CodeSnip.dproj +++ b/Src/CodeSnip.dproj @@ -584,6 +584,7 @@ <DCCReference Include="USaveInfoMgr.pas"/> <DCCReference Include="ClassHelpers.RichEdit.pas"/> <DCCReference Include="UHTMLSnippetDoc.pas"/> + <DCCReference Include="UMarkdownUtils.pas"/> <None Include="CodeSnip.todo"/> <BuildConfiguration Include="Base"> <Key>Base</Key> diff --git a/Src/UMarkdownUtils.pas b/Src/UMarkdownUtils.pas new file mode 100644 index 000000000..bbc49188b --- /dev/null +++ b/Src/UMarkdownUtils.pas @@ -0,0 +1,478 @@ +{ + * 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). + * + * Helper class used to generate Markdown formatted text. +} + +unit UMarkdownUtils; + +interface + +uses + // Project + UConsts; + +type + TMarkdown = class + strict private + const + /// <summary>Character used in multiples of 1 to 6 to introduce a + /// heading.</summary> + HeadingOpenerChar = Char('#'); + /// <summary>Character used to introduce a block quote. Sometimes used in + /// multiple for nested block quotes.</summary> + BlockquoteOpenerChar = Char('>'); + /// <summary>Character used to delimit inline code, sometimes in + /// multiple, or in multiples of at least three for code fences. + /// </summary> + CodeDelim = Char('`'); + /// <summary>Characters used to delimit strongly emphasised text (bold). + /// </summary> + StrongEmphasisDelim = '**'; + /// <summary>Character used to delimit weakly emphasised text (italic). + /// </summary> + WeakEmphasisDelim = Char('*'); + /// <summary>Format string used to render a link (description first, URL + /// second).</summary> + LinkFmtStr = '[%0:s](%1:s)'; + /// <summary>Character used to introduce a bare URL.</summary> + URLOpenerChar = Char('<'); + /// <summary>Character used to close a bare URL.</summary> + URLCloserChar = Char('>'); + /// <summary>Character used to delimit table columns.</summary> + TableColDelim = Char('|'); + /// <summary>Character used in multiple for the ruling that separates a + /// table head from the body.</summary> + TableRulingChar = Char('-'); + /// <summary>Character used to introduce a bullet list item.</summary> + ListItemBullet = Char('-'); + /// <summary>String used to format a number that introduces a number list + /// item.</summary> + ListItemNumberFmt = '%d.'; + /// <summary>String used to indicate a ruling.</summary> + Ruling = '----'; + /// <summary>Characters that are escaped by prepending a \ to the same + /// character.</summary> + EscapeChars = '\`*_{}[]<>()#+-!|'; + /// <summary>Escape sequence used to specify a non-breaking space. + /// </summary> + NonBreakingSpace = '\ '; + + /// <summary>Size of each level of indentation in spaces.</summary> + IndentSize = UInt8(4); + + /// <summary>Minimum length of a code fence delimiter.</summary> + MinCodeFenceLength = Cardinal(3); + + /// <summary>Prepends an indent to the lines of given text.</summary> + /// <param name="AText"><c>string</c> [in] Text to be indented. If the text + /// contains multiple lines then each line is indented.</param> + /// <param name="AIndentLevel"><c>UInt8</c> [in] The number of levels of + /// indentation to be applied. If zero then no indentation is performed. + /// </param> + /// <remarks>Empty lines are not indented.</remarks> + class function ApplyIndent(const AText: string; const AIndentLevel: UInt8): + string; + + public + + /// <summary>Replaces any escapable characters in given text with escaped + /// versions of the characters, to make the text suitable for inclusion in + /// Markdown code.</summary> + /// <param name="AText"><c>string</c> [in] Text to be escaped.</param> + /// <returns><c>string</c>. The escaped text.</returns> + /// <remarks> + /// <para>If <c>AText</c> includes any markdown code then it will be + /// escaped and will be rendered literally and have no effect. For example, + /// <c>**bold**</c> will be transformed to <c>\*\*bold\*\*</c>.</para> + /// <para>Sequences of N spaces, where N >= 2, will be replaced with a + /// single space followed by N-1 non-breaking spaces.</para> + /// </remarks> + class function EscapeText(const AText: string): string; + + /// <summary>Renders markdown as a heading, optionally indented.</summary> + /// <param name="AMarkdown"><c>string</c> [in] Valid Markdown to include in + /// the heading. Will not be escaped.</param> + /// <param name="AHeadingLevel"><c>UInt8</c> [in] The heading level. Must + /// be in the range <c>1</c> to <c>6</c>.</param> + /// <param name="AIndentLevel"><c>UInt8</c> [in] The number of levels of + /// indentation required. Set to <c>0</c> (the default) for no indentation. + /// </param> + /// <returns><c>string</c>. The required heading Markdown.</returns> + class function Heading(const AMarkdown: string; const AHeadingLevel: UInt8; + const AIndentLevel: UInt8 = 0): string; + + /// <summary>Renders markdown as a paragraph, optionally indented. + /// </summary> + /// <param name="AMarkdown"><c>string</c> [in] Valid Markdown to include in + /// the paragraph. Will not be escaped.</param> + /// <param name="AIndentLevel"><c>UInt8</c> [in] The number of levels of + /// indentation required. Set to <c>0</c> (the default) for no indentation. + /// </param> + /// <returns><c>string</c>. The required paragraph Markdown.</returns> + class function Paragraph(const AMarkdown: string; + const AIndentLevel: UInt8 = 0): string; + + /// <summary>Renders markdown as a block quote, optionally indented. + /// </summary> + /// <param name="AMarkdown"><c>string</c> [in] Valid Markdown to include in + /// the block quote. Will not be escaped.</param> + /// <param name="ANestLevel"><c>UInt8</c> [in] The nesting level of the + /// block quote.</param> + /// <param name="AIndentLevel"><c>UInt8</c> [in] The number of levels of + /// indentation required. Set to <c>0</c> (the default) for no indentation. + /// </param> + /// <returns><c>string</c>. The required block quote Markdown.</returns> + class function BlockQuote(const AMarkdown: string; + const ANestLevel: UInt8 = 0; const AIndentLevel: UInt8 = 0): string; + + /// <summary>Renders markdown as a bullet list item, optionally indented. + /// </summary> + /// <param name="AMarkdown"><c>string</c> [in] Valid Markdown to include in + /// the list item. Will not be escaped.</param> + /// <param name="AIndentLevel"><c>UInt8</c> [in] The number of levels of + /// indentation required. Set to <c>0</c> (the default) for no indentation. + /// </param> + /// <returns><c>string</c>. The required bullet list item Markdown. + /// </returns> + class function BulletListItem(const AMarkdown: string; + const AIndentLevel: UInt8 = 0): string; + + /// <summary>Renders markdown as a number list item, optionally indented. + /// </summary> + /// <param name="AMarkdown"><c>string</c> [in] Valid Markdown to include in + /// the list item. Will not be escaped.</param> + /// <param name="ANumber"><c>UInt8</c> [in] The number to be used in the + /// list item. Must be > <c>0</c>.</param> + /// <param name="AIndentLevel"><c>UInt8</c> [in] The number of levels of + /// indentation required. Set to <c>0</c> (the default) for no indentation. + /// </param> + /// <returns><c>string</c>. The required number list item Markdown. + /// </returns> + class function NumberListItem(const AMarkdown: string; + const ANumber: UInt8; const AIndentLevel: UInt8 = 0): string; + + /// <summary>Renders pre-formatted code within code fences, optionally + /// indented.</summary> + /// <param name="ACode"><c>string</c> [in] The text of the code, which may + /// contain more than one line. Any markdown formatting within <c>ACode</c> + /// will be rendered literally.</param> + /// <param name="ALanguage"><c>string</c> [in] The name of any programming + /// language associated with the code. Set to an empty string (the default) + /// if there is no such language.</param> + /// <param name="AIndentLevel"><c>UInt8</c> [in] The number of levels of + /// indentation required. Set to <c>0</c> (the default) for no indentation. + /// </param> + /// <returns><c>string</c>. The required fenced code.</returns> + class function FencedCode(const ACode: string; const ALanguage: string = ''; + const AIndentLevel: UInt8 = 0): string; + + /// <summary>Renders pre-formatted code using indentation, optionally + /// indented further.</summary> + /// <param name="ACode"><c>string</c> [in] The text of the code block, + /// which may contain more than one line. Any markdown formatting within + /// <c>ACode</c> will be rendered literally.</param> + /// <param name="AIndentLevel"><c>UInt8</c> [in] The number of levels of + /// indentation required in addition to that required for the code block. + /// Set to <c>0</c> (the default) for no additional indentation.</param> + /// <returns><c>string</c>. The required fenced code.</returns> + class function CodeBlock(const ACode: string; + const AIndentLevel: UInt8 = 0): string; + + /// <summary>Renders the headings to use at the top of a Markdown table. + /// Includes the ruling the is required below the table heading. + /// </summary> + /// <param name="AHeadings"><c>array of string</c> [in] An array of heading + /// text. There will be one table column per element. Each heading is + /// assumed to be valid Markdown and will not be escaped.</param> + /// <param name="AIndentLevel"><c>UInt8</c> [in] The number of levels of + /// indentation required before the table. Set to <c>0</c> (the default) + /// for no indentation.</param> + /// <returns><c>string</c>. The required Markdown formatted table heading. + /// </returns> + /// <remarks>This method MUST be called before the 1st call to + /// <c>TableRow</c>.</remarks> + class function TableHeading(const AHeadings: array of string; + const AIndentLevel: UInt8 = 0): string; + + /// <summary>Renders the columns of text to use for a row of a Markdown + /// table.</summary> + /// <param name="AEntries"><c>array of string</c> [in] An array of column + /// text. There will be one table column per element. Each element is + /// assumed to be valid Markdown and will not be escaped.</param> + /// <param name="AIndentLevel"><c>UInt8</c> [in] The number of levels of + /// indentation required before the table. Set to <c>0</c> (the default) + /// for no indentation.</param> + /// <returns><c>string</c>. The required Markdown formatted table row. + /// </returns> + /// <remarks> + /// <para>Call this method once per table row.</para> + /// <para>The 1st call to this method MUST follow a call to + /// <c>TableHeading</c>.</para> + /// <para>The number of elements of <c>AEntries</c> should be the same for + /// each call of the method in the same table, and should be the same as + /// the number of headings passed to <c>TableHeading</c>.</para> + /// </remarks> + class function TableRow(const AEntries: array of string; + const AIndentLevel: UInt8 = 0): string; + + /// <summary>Renders the Markdown representation of a ruling.</summary> + /// <param name="AIndentLevel"><c>UInt8</c> [in] The number of levels of + /// indentation required before the ruling. Set to <c>0</c> (the default) + /// for no indentation.</param> + /// <returns><c>string</c>. The required Markdown ruling.</returns> + class function Rule(const AIndentLevel: UInt8 = 0): string; + + /// <summary>Renders text as inline code.</summary> + /// <param name="ACode"><c>string</c> [in] The code. Any markdown + /// formatting within <c>ACode</c> will be rendered literally.</param> + /// <returns><c>string</c>. The required Markdown formatted code.</returns> + class function InlineCode(const ACode: string): string; + + /// <summary>Renders weakly formatted text.</summary> + /// <param name="AMarkdown"><c>string</c> [in] Text to be formatted. + /// May contain other inline Mardown formatting. Will not be escaped. + /// </param> + /// <returns><c>string</c>. The required Markdown formatted text.</returns> + /// <remarks>Usually rendered in italics.</remarks> + class function WeakEmphasis(const AMarkdown: string): string; + + /// <summary>Renders strongly formatted text.</summary> + /// <param name="AMarkdown"><c>string</c> [in] Text to be formatted. + /// May contain other inline Mardown formatting. Will not be escaped. + /// </param> + /// <returns><c>string</c>. The required Markdown formatted text.</returns> + /// <remarks>Usually rendered in bold.</remarks> + class function StrongEmphasis(const AMarkdown: string): string; + + /// <summary>Renders a link.</summary> + /// <param name="AMarkdown"><c>string</c> [in] The link's text, which may + /// include other inline Markdown formatting.</param> + /// <param name="AURL"><c>string</c> [in] The URL of the link. Must be + /// valid and correctly URL encoded.</param> + /// <returns><c>string</c>. The required Markdown formatted link.</returns> + class function Link(const AMarkdown, AURL: string): string; + + /// <summary>Renders a bare URL.</summary> + /// <param name="AURL"><c>string</c> [in] The required URL. Must be valid + /// and correctly URL encoded.</param> + /// <returns><c>string</c>. The required Markdown formatted URL.</returns> + class function BareURL(const AURL: string): string; + + end; + +implementation + +uses + // Delphi + SysUtils, + Classes, + Math, + // Project + UStrUtils; + +{ TMarkdown } + +class function TMarkdown.ApplyIndent(const AText: string; + const AIndentLevel: UInt8): string; +var + Line: string; + InLines, OutLines: TStrings; +begin + Result := ''; + OutLines := nil; + InLines := TStringList.Create; + try + OutLines := TStringList.Create; + StrExplode(StrWindowsLineBreaks(AText), EOL, InLines); + for Line in InLines do + if Line <> '' then + OutLines.Add(StrOfChar(' ', IndentSize * AIndentLevel) + Line) + else + OutLines.Add(''); + Result := StrJoin(OutLines, EOL); + finally + OutLines.Free; + InLines.Free; + end; +end; + +class function TMarkdown.BareURL(const AURL: string): string; +begin + Result := URLOpenerChar + AURL + URLCloserChar; +end; + +class function TMarkdown.BlockQuote(const AMarkdown: string; const ANestLevel, + AIndentLevel: UInt8): string; +begin + Result := ApplyIndent( + StrOfChar(BlockquoteOpenerChar, ANestLevel + 1) + ' ' + AMarkdown, + AIndentLevel + ) +end; + +class function TMarkdown.BulletListItem(const AMarkdown: string; + const AIndentLevel: UInt8): string; +begin + Result := ApplyIndent(ListItemBullet + ' ' + AMarkdown, AIndentLevel); +end; + +class function TMarkdown.CodeBlock(const ACode: string; + const AIndentLevel: UInt8): string; +var + NormalisedCode: string; +begin + if ACode = '' then + Exit(''); + // Ensure code uses windows line breaks and is trimmed of trailing white space + NormalisedCode := StrTrimRight(StrWindowsLineBreaks(ACode)); + // Indent each line by indent level + 1 since code blocks are identified by + // being indented from the normal flow + Result := ApplyIndent(NormalisedCode, AIndentLevel + 1); +end; + +class function TMarkdown.EscapeText(const AText: string): string; +var + MultipleSpaceLen: Cardinal; + Spaces: string; + EscapedSpaces: string; + Idx: Integer; +begin + // Escape non-space characters + Result := StrBackslashEscape(AText, EscapeChars, EscapeChars); + // Escape sequences of >= 2 spaces, with \ before each space except 1st one + MultipleSpaceLen := StrMaxSequenceLength(' ', Result); + while MultipleSpaceLen > 1 do + begin + Spaces := StrOfChar(' ', MultipleSpaceLen); + EscapedSpaces := ' '; + for Idx := 1 to Pred(MultipleSpaceLen) do + EscapedSpaces := EscapedSpaces + NonBreakingSpace; + Result := StrReplace(Result, Spaces, EscapedSpaces); + MultipleSpaceLen := StrMaxSequenceLength(' ', Result); + end; + // Escape list starter chars if at start of line +end; + +class function TMarkdown.FencedCode(const ACode, ALanguage: string; + const AIndentLevel: UInt8): string; +var + FenceLength: Cardinal; + Fence: string; + FencedCode: string; + NormalisedCode: string; +begin + if ACode = '' then + Exit(''); + // Ensure code ends in at least one line break + NormalisedCode := StrUnixLineBreaks(ACode); + if NormalisedCode[Length(NormalisedCode)] <> LF then + NormalisedCode := NormalisedCode + LF; + NormalisedCode := StrWindowsLineBreaks(NormalisedCode); + // Create fence that has correct length + // TODO: only need to detect max fence length at start of line (excl spaces) + FenceLength := Max( + StrMaxSequenceLength(CodeDelim, ACode) + 1, MinCodeFenceLength + ); + Fence := StrOfChar(CodeDelim, FenceLength); + // Build fenced code + FencedCode := Fence + ALanguage + EOL + NormalisedCode + Fence; + // Indent each line of fenced code + Result := ApplyIndent(FencedCode, AIndentLevel); +end; + +class function TMarkdown.Heading(const AMarkdown: string; + const AHeadingLevel, AIndentLevel: UInt8): string; +begin + Assert(AHeadingLevel in [1..6], + ClassName + '.Heading: AHeadingLevel must be in range 1..6'); + Result := ApplyIndent( + StrOfChar(HeadingOpenerChar, AHeadingLevel) + ' ' + AMarkdown, AIndentLevel + ); +end; + +class function TMarkdown.InlineCode(const ACode: string): string; +var + CodeDelimLength: Cardinal; + Delim: string; +begin + CodeDelimLength := StrMaxSequenceLength(CodeDelim, ACode) + 1; + Delim := StrOfChar(CodeDelim, CodeDelimLength); + Result := Delim + ACode + Delim; +end; + +class function TMarkdown.Link(const AMarkdown, AURL: string): string; +begin + // TODO: make URL safe + Result := Format(LinkFmtStr, [AMarkdown, AURL]); +end; + +class function TMarkdown.NumberListItem(const AMarkdown: string; const ANumber, + AIndentLevel: UInt8): string; +begin + Assert(ANumber > 0, ClassName + 'NumberListItem: ANumber = 0'); + Result := ApplyIndent( + Format(ListItemNumberFmt, [ANumber]) + ' ' + AMarkdown, AIndentLevel + ); +end; + +class function TMarkdown.Paragraph(const AMarkdown: string; + const AIndentLevel: UInt8): string; +begin + Result := ApplyIndent(AMarkdown, AIndentLevel); +end; + +class function TMarkdown.Rule(const AIndentLevel: UInt8): string; +begin + Result := ApplyIndent(Ruling, AIndentLevel); +end; + +class function TMarkdown.StrongEmphasis(const AMarkdown: string): string; +begin + Result := StrongEmphasisDelim + AMarkdown + StrongEmphasisDelim; +end; + +class function TMarkdown.TableHeading(const AHeadings: array of string; + const AIndentLevel: UInt8): string; +var + Heading: string; + Ruling: string; + HeadingRow: string; +begin + if Length(AHeadings) = 0 then + Exit(''); + Ruling := TableColDelim; + HeadingRow := TableColDelim; + for Heading in AHeadings do + begin + Ruling := Ruling + StrOfChar(TableRulingChar, Length(Heading) + 2) + + TableColDelim; + HeadingRow := HeadingRow + ' ' + Heading + ' ' + TableColDelim; + end; + Result := ApplyIndent(HeadingRow + EOL + Ruling, AIndentLevel); +end; + +class function TMarkdown.TableRow(const AEntries: array of string; + const AIndentLevel: UInt8): string; +var + Entry: string; + Row: string; +begin + if Length(AEntries) = 0 then + Exit(''); + Row := TableColDelim; + for Entry in AEntries do + Row := Row + ' ' + Entry + ' ' + TableColDelim; + Result := ApplyIndent(Row, AIndentLevel); +end; + +class function TMarkdown.WeakEmphasis(const AMarkdown: string): string; +begin + Result := WeakEmphasisDelim + AMarkdown + WeakEmphasisDelim; +end; + +end. From 0618a5a71c7d5208c8140bc9c586acc95186895a Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 27 Apr 2025 17:39:13 +0100 Subject: [PATCH 293/330] Add new Markdown active text renderer to project Added new ActiveText.UMarkdownRenderer unit to the project that converts active text into Markdown format. --- Src/ActiveText.UMarkdownRenderer.pas | 927 +++++++++++++++++++++++++++ Src/CodeSnip.dpr | 3 +- Src/CodeSnip.dproj | 1 + 3 files changed, 930 insertions(+), 1 deletion(-) create mode 100644 Src/ActiveText.UMarkdownRenderer.pas 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/CodeSnip.dpr b/Src/CodeSnip.dpr index e8f354285..3aa3d0f83 100644 --- a/Src/CodeSnip.dpr +++ b/Src/CodeSnip.dpr @@ -378,7 +378,8 @@ uses USaveInfoMgr in 'USaveInfoMgr.pas', ClassHelpers.RichEdit in 'ClassHelpers.RichEdit.pas', UHTMLSnippetDoc in 'UHTMLSnippetDoc.pas', - UMarkdownUtils in 'UMarkdownUtils.pas'; + UMarkdownUtils in 'UMarkdownUtils.pas', + ActiveText.UMarkdownRenderer in 'ActiveText.UMarkdownRenderer.pas'; // Include resources {$Resource ExternalObj.tlb} // Type library file diff --git a/Src/CodeSnip.dproj b/Src/CodeSnip.dproj index b8d02d11e..b7f2441bf 100644 --- a/Src/CodeSnip.dproj +++ b/Src/CodeSnip.dproj @@ -585,6 +585,7 @@ <DCCReference Include="ClassHelpers.RichEdit.pas"/> <DCCReference Include="UHTMLSnippetDoc.pas"/> <DCCReference Include="UMarkdownUtils.pas"/> + <DCCReference Include="ActiveText.UMarkdownRenderer.pas"/> <None Include="CodeSnip.todo"/> <BuildConfiguration Include="Base"> <Key>Base</Key> From 69346450267d79bc56983140ba3b3dbac6d7a03a Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 27 Apr 2025 20:21:48 +0100 Subject: [PATCH 294/330] Add unit to render snippet info in Markdown format Added new UMarkdownSnippetDoc to the project that descends from TSnippetDoc and adds support for rendering snippet information as Markdown. --- Src/CodeSnip.dpr | 3 +- Src/CodeSnip.dproj | 1 + Src/UMarkdownSnippetDoc.pas | 235 ++++++++++++++++++++++++++++++++++++ 3 files changed, 238 insertions(+), 1 deletion(-) create mode 100644 Src/UMarkdownSnippetDoc.pas diff --git a/Src/CodeSnip.dpr b/Src/CodeSnip.dpr index 3aa3d0f83..fa718dacc 100644 --- a/Src/CodeSnip.dpr +++ b/Src/CodeSnip.dpr @@ -379,7 +379,8 @@ uses ClassHelpers.RichEdit in 'ClassHelpers.RichEdit.pas', UHTMLSnippetDoc in 'UHTMLSnippetDoc.pas', UMarkdownUtils in 'UMarkdownUtils.pas', - ActiveText.UMarkdownRenderer in 'ActiveText.UMarkdownRenderer.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 b7f2441bf..5eaa734a3 100644 --- a/Src/CodeSnip.dproj +++ b/Src/CodeSnip.dproj @@ -586,6 +586,7 @@ <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/UMarkdownSnippetDoc.pas b/Src/UMarkdownSnippetDoc.pas new file mode 100644 index 000000000..aa931d2de --- /dev/null +++ b/Src/UMarkdownSnippetDoc.pas @@ -0,0 +1,235 @@ +{ + * 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 a class that renders a document that describes a snippet in + * Markdown format. +} + + +unit UMarkdownSnippetDoc; + +interface + +uses + // Delphi + SysUtils, + // Project + ActiveText.UMain, + Hiliter.UGlobals, + UEncodings, + UIStringList, + USnippetDoc; + +type + /// <summary>Renders a document that describes a snippet in Markdown format. + /// </summary> + TMarkdownSnippetDoc = class sealed (TSnippetDoc) + strict private + var + /// <summary>Object used to build Markdown source code document. + /// </summary> + fDocument: TStringBuilder; + /// <summary>Flag indicating if the snippet has Pascal code.</summary> + /// <remarks>When <c>False</c> plain text is assumed.</remarks> + fIsPascal: Boolean; + strict private + /// <summary>Renders a Markdown paragraph with all given text emboldened. + /// </summary> + procedure RenderStrongPara(const AText: string); + /// <summary>Renders the given active text as Markdown.</summary> + function ActiveTextToMarkdown(ActiveText: IActiveText): string; + strict protected + /// <summary>Initialises the Markdown document.</summary> + procedure InitialiseDoc; override; + /// <summary>Adds the given heading (i.e. snippet name) to the document. + /// Can be user defined or from main database.</summary> + procedure RenderHeading(const Heading: string; const UserDefined: Boolean); + override; + /// <summary>Adds the given snippet description to the document.</summary> + /// <remarks>Active text formatting is observed and styled to suit the + /// document.</remarks> + procedure RenderDescription(const Desc: IActiveText); override; + /// <summary>Highlights the given source code and adds it to the document. + /// </summary> + procedure RenderSourceCode(const SourceCode: string); override; + /// <summary>Adds the given title, followed by the given text, to the + /// document.</summary> + procedure RenderTitledText(const Title, Text: string); override; + /// <summary>Adds a comma-separated list of text, preceded by the given + /// title, to the document.</summary> + procedure RenderTitledList(const Title: string; List: IStringList); + override; + /// <summary>Outputs the given compiler test info, preceded by the given + /// heading.</summary> + procedure RenderCompilerInfo(const Heading: string; + const Info: TCompileDocInfoArray); override; + /// <summary>Outputs the given message stating that there is no compiler + /// test info, preceded by the given heading.</summary> + procedure RenderNoCompilerInfo(const Heading, NoCompileTests: string); + override; + /// <summary>Adds the given extra information about the snippet to the + /// document.</summary> + /// <remarks>Active text formatting is observed and styled to suit the + /// document.</remarks> + procedure RenderExtra(const ExtraText: IActiveText); override; + /// <summary>Adds the given information about a code snippets database to + /// the document.</summary> + procedure RenderDBInfo(const Text: string); override; + /// <summary>Finalises the document and returns its content as encoded + /// data.</summary> + function FinaliseDoc: TEncodedData; override; + public + /// <summary>Constructs an object to render Markdown information.</summary> + /// <param name="AIsPascal"><c>Boolean</c> [in] Flag indicating whether the + /// snippet contains Pascal code.</param> + constructor Create(const AIsPascal: Boolean); + /// <summary>Destroys the object.</summary> + destructor Destroy; override; + end; + +implementation + +uses + // Delphi + UStrUtils, + // Project + ActiveText.UMarkdownRenderer, + UMarkdownUtils; + +{ TMarkdownSnippetDoc } + +function TMarkdownSnippetDoc.ActiveTextToMarkdown( + ActiveText: IActiveText): string; +var + Renderer: TActiveTextMarkdown; +begin + Renderer := TActiveTextMarkdown.Create; + try + Result := Renderer.Render(ActiveText); + finally + Renderer.Free; + end; +end; + +constructor TMarkdownSnippetDoc.Create(const AIsPascal: Boolean); +begin + inherited Create; + fDocument := TStringBuilder.Create; + fIsPascal := AIsPascal; +end; + +destructor TMarkdownSnippetDoc.Destroy; +begin + fDocument.Free; + inherited; +end; + +function TMarkdownSnippetDoc.FinaliseDoc: TEncodedData; +begin + Result := TEncodedData.Create(fDocument.ToString, etUnicode); +end; + +procedure TMarkdownSnippetDoc.InitialiseDoc; +begin + // Do nowt +end; + +procedure TMarkdownSnippetDoc.RenderCompilerInfo(const Heading: string; + const Info: TCompileDocInfoArray); +resourcestring + sCompiler = 'Compiler'; + sResults = 'Results'; +var + CompilerInfo: TCompileDocInfo; // info about each compiler +begin + RenderStrongPara(Heading); + + fDocument.AppendLine(TMarkdown.TableHeading([sCompiler, sResults])); + for CompilerInfo in Info do + fDocument.AppendLine( + TMarkdown.TableRow([CompilerInfo.Compiler, CompilerInfo.Result]) + ); + fDocument.AppendLine; +end; + +procedure TMarkdownSnippetDoc.RenderDBInfo(const Text: string); +begin + fDocument + .AppendLine(TMarkdown.WeakEmphasis(TMarkdown.EscapeText(Text))) + .AppendLine; +end; + +procedure TMarkdownSnippetDoc.RenderDescription(const Desc: IActiveText); +var + DescStr: string; +begin + DescStr := ActiveTextToMarkdown(Desc); + if not StrIsEmpty(DescStr, True) then + fDocument.AppendLine(DescStr); +end; + +procedure TMarkdownSnippetDoc.RenderExtra(const ExtraText: IActiveText); +var + ExtraStr: string; +begin + ExtraStr := ActiveTextToMarkdown(ExtraText); + if not StrIsEmpty(ExtraStr, True) then + fDocument.AppendLine(ExtraStr); +end; + +procedure TMarkdownSnippetDoc.RenderHeading(const Heading: string; + const UserDefined: Boolean); +begin + fDocument + .AppendLine(TMarkdown.Heading(TMarkdown.EscapeText(Heading), 1)) + .AppendLine; +end; + +procedure TMarkdownSnippetDoc.RenderNoCompilerInfo(const Heading, + NoCompileTests: string); +begin + RenderStrongPara(Heading); + fDocument + .AppendLine(TMarkdown.Paragraph(TMarkdown.EscapeText(NoCompileTests))) + .AppendLine; +end; + +procedure TMarkdownSnippetDoc.RenderSourceCode(const SourceCode: string); +begin + fDocument + .AppendLine( + TMarkdown.FencedCode(SourceCode, StrIf(fIsPascal, 'pascal', '')) + ) + .AppendLine; +end; + +procedure TMarkdownSnippetDoc.RenderStrongPara(const AText: string); +begin + fDocument + .AppendLine( + TMarkdown.Paragraph( + TMarkdown.StrongEmphasis(TMarkdown.EscapeText(AText)) + ) + ) + .AppendLine; +end; + +procedure TMarkdownSnippetDoc.RenderTitledList(const Title: string; + List: IStringList); +begin + RenderTitledText(Title, CommaList(List)); +end; + +procedure TMarkdownSnippetDoc.RenderTitledText(const Title, Text: string); +begin + RenderStrongPara(Title); + fDocument + .AppendLine(TMarkdown.Paragraph(TMarkdown.EscapeText(Text))) + .AppendLine; +end; + +end. From 61d53b29802e5b5945f332471c8b79bab9344cf6 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 27 Apr 2025 20:23:17 +0100 Subject: [PATCH 295/330] Add new Markdown source file type. Added sfMarkdown element to TSourceFileType enumerated type. The new sfMarkdown element broke TSaveSnippetMgr.GetFileTypeDesc and TSaveUnitMgr.GetFileTypeDesc which assumed that all TSourceFileType values were supported, and so don't check for any file type that may not be supported. To enable such checks to be made a new TSourceFileInfo.SupportsFileType method was added to check if a file type is supported. Updated the Code Formatting tab of the Preferences dialogue box with the options to set Markdown as the default source file type. --- Src/FrSourcePrefs.pas | 4 +++- Src/USaveSnippetMgr.pas | 5 ++++- Src/USaveSourceMgr.pas | 16 ++++++++++------ Src/USaveUnitMgr.pas | 5 ++++- Src/USourceFileInfo.pas | 17 ++++++++++++++++- 5 files changed, 37 insertions(+), 10 deletions(-) diff --git a/Src/FrSourcePrefs.pas b/Src/FrSourcePrefs.pas index da40b5e00..ab6cc70e9 100644 --- a/Src/FrSourcePrefs.pas +++ b/Src/FrSourcePrefs.pas @@ -127,12 +127,14 @@ implementation 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, sHTML5FileDesc, sXHTMLFileDesc, sRTFFileDesc + sTextFileDesc, sPascalFileDesc, sHTML5FileDesc, sXHTMLFileDesc, + sRTFFileDesc, sMarkdownFileDesc ); diff --git a/Src/USaveSnippetMgr.pas b/Src/USaveSnippetMgr.pas index 9426baa94..25de4e1ba 100644 --- a/Src/USaveSnippetMgr.pas +++ b/Src/USaveSnippetMgr.pas @@ -171,9 +171,12 @@ function TSaveSnippetMgr.GetFileTypeDesc( const FileType: TSourceFileType): string; const Descriptions: array[TSourceFileType] of string = ( - sTxtExtDesc, sIncExtDesc, sHtml5ExtDesc, sXHtmExtDesc, sRtfExtDesc + sTxtExtDesc, sIncExtDesc, sHtml5ExtDesc, sXHtmExtDesc, sRtfExtDesc, + '' {Markdown not supported} ); begin + Assert(FileType <> sfMarkdown, + ClassName + '.GetFileTypeDesc: Markdown not supported'); Result := Descriptions[FileType]; end; diff --git a/Src/USaveSourceMgr.pas b/Src/USaveSourceMgr.pas index 4be7c6fcc..41581bcfa 100644 --- a/Src/USaveSourceMgr.pas +++ b/Src/USaveSourceMgr.pas @@ -181,11 +181,14 @@ procedure TSaveSourceMgr.DoExecute; begin // Set up dialog box fSaveDlg.Filter := fSourceFileInfo.FilterString; - fSaveDlg.FilterIndex := FilterDescToIndex( - fSaveDlg.Filter, - fSourceFileInfo.FileTypeInfo[Preferences.SourceDefaultFileType].DisplayName, - 1 - ); + if fSourceFileInfo.SupportsFileType(Preferences.SourceDefaultFileType) then + fSaveDlg.FilterIndex := FilterDescToIndex( + fSaveDlg.Filter, + fSourceFileInfo.FileTypeInfo[Preferences.SourceDefaultFileType].DisplayName, + 1 + ) + else + fSaveDlg.FilterIndex := 1; fSaveDlg.FileName := fSourceFileInfo.DefaultFileName; // Display dialog box and save file if user OKs if fSaveDlg.Execute then @@ -317,7 +320,8 @@ procedure TSaveSourceMgr.PreviewHandler(Sender: TObject); dtPlainText, // sfPascal dtHTML, // sfHTML5 dtHTML, // sfXHTML - dtRTF // sfRTF + dtRTF, // sfRTF + dtPlainText // sfMarkdown ); PreviewFileTypeMap: array[TPreviewDocType] of TSourceFileType = ( sfText, // dtPlainText diff --git a/Src/USaveUnitMgr.pas b/Src/USaveUnitMgr.pas index 7015767dc..1901952a4 100644 --- a/Src/USaveUnitMgr.pas +++ b/Src/USaveUnitMgr.pas @@ -242,9 +242,12 @@ function TSaveUnitMgr.GetDocTitle: string; function TSaveUnitMgr.GetFileTypeDesc(const FileType: TSourceFileType): string; const Descriptions: array[TSourceFileType] of string = ( - sTextDesc, sPascalDesc, sHTML5Desc, sXHTMLDesc, sRTFDesc + sTextDesc, sPascalDesc, sHTML5Desc, sXHTMLDesc, sRTFDesc, + '' {Markdown not supported} ); begin + Assert(FileType <> sfMarkdown, + ClassName + '.GetFileTypeDesc: Markdown not supported'); Result := Descriptions[FileType]; end; diff --git a/Src/USourceFileInfo.pas b/Src/USourceFileInfo.pas index d0e318f01..213f9041a 100644 --- a/Src/USourceFileInfo.pas +++ b/Src/USourceFileInfo.pas @@ -32,7 +32,8 @@ interface sfPascal, // pascal files (either .pas for units or .inc for include files sfHTML5, // HTML 5 files sfXHTML, // XHTML files - sfRTF // rich text files + sfRTF, // rich text files + sfMarkdown // Markdown files ); type @@ -132,6 +133,13 @@ TSourceFileInfo = class(TObject) /// given one-based index within the current filter string.</summary> function FileTypeFromFilterIdx(const Idx: Integer): TSourceFileType; + /// <summary>Checks if a file type is supported.</summary> + /// <param name="FileType"><c>TSourceFileType</c> [in] File type to check. + /// </param> + /// <returns><c>Boolean</c>. <c>True</c> if file type is supported, + /// <c>False</c> if not.</returns> + function SupportsFileType(const FileType: TSourceFileType): Boolean; + /// <summary>Information about each supported file type that is of use to /// save source dialog boxes.</summary> /// <exception>A <c>EListError</c> exception is raised if no information @@ -139,6 +147,7 @@ TSourceFileInfo = class(TObject) /// </exception> property FileTypeInfo[const FileType: TSourceFileType]: TSourceFileTypeInfo read GetFileTypeInfo write SetFileTypeInfo; + /// <summary>Default source code file name.</summary> /// <remarks>Must be a valid Pascal identifier. Invalid characters are /// replaced by underscores.</remarks> @@ -243,6 +252,12 @@ procedure TSourceFileInfo.SetFileTypeInfo(const FileType: TSourceFileType; GenerateFilterInfo; end; +function TSourceFileInfo.SupportsFileType(const FileType: TSourceFileType): + Boolean; +begin + Result := fFileTypeInfo.ContainsKey(FileType); +end; + { TSourceFileTypeInfo } constructor TSourceFileTypeInfo.Create(const AExtension, ADisplayName: string; From 9c67d384fdcf97be0439e49524301180013c51b2 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Sun, 27 Apr 2025 20:22:18 +0100 Subject: [PATCH 296/330] Add Markdown support to Save Snippet Information dlg Updated TSaveInfoMgr in USaveInfoMgr to add support for rendering, previewing and outputting snippet information in Markdown format. --- Src/USaveInfoMgr.pas | 48 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/Src/USaveInfoMgr.pas b/Src/USaveInfoMgr.pas index 8f5e1f7c0..c62f5275c 100644 --- a/Src/USaveInfoMgr.pas +++ b/Src/USaveInfoMgr.pas @@ -5,8 +5,8 @@ * * Copyright (C) 2025, Peter Johnson (gravatar.com/delphidabbler). * - * Saves information about a snippet to disk in rich text format. Only routine - * snippet kinds are supported. + * Saves information about a snippet to disk in various, user specifed, formats. + * Only routine snippet kinds are supported. } @@ -55,6 +55,12 @@ TSaveInfoMgr = class(TNoPublicConstructObject) /// information about the snippet represented by the given view.</summary> function GeneratePlainText: TEncodedData; + /// <summary>Returns encoded data containing a Markdown representation of + /// information about the snippet represented by the given view.</summary> + /// <returns><c>TEncodedData</c>. Required Markdown document, encoded as + /// UTF-16.</returns> + function GenerateMarkdown: TEncodedData; + /// <summary>Returns type of file selected in the associated save dialogue /// box.</summary> function SelectedFileType: TSourceFileType; @@ -127,11 +133,13 @@ implementation SysUtils, Dialogs, // Project + DB.USnippetKind, FmPreviewDlg, Hiliter.UAttrs, Hiliter.UFileHiliter, Hiliter.UGlobals, UIOUtils, + UMarkdownSnippetDoc, UOpenDialogHelper, UPreferences, URTFSnippetDoc, @@ -171,7 +179,6 @@ procedure TSaveInfoMgr.DoExecute; if fSaveDlg.Execute then begin FileType := SelectedFileType; - FileContent := GenerateOutput(FileType).ToString; Encoding := TEncodingHelper.GetEncoding(fSaveDlg.SelectedEncoding); try FileContent := GenerateOutput(FileType).ToString; @@ -221,6 +228,22 @@ function TSaveInfoMgr.GenerateHTML(const AUseHiliting: Boolean; end; end; +function TSaveInfoMgr.GenerateMarkdown: TEncodedData; +var + Doc: TMarkdownSnippetDoc; +begin + Assert(Supports(fView, ISnippetView), + ClassName + '.GeneratePlainText: View is not a snippet view'); + Doc := TMarkdownSnippetDoc.Create( + (fView as ISnippetView).Snippet.Kind <> skFreeform + ); + try + Result := Doc.Generate((fView as ISnippetView).Snippet); + finally + Doc.Free; + end; +end; + function TSaveInfoMgr.GenerateOutput(const FileType: TSourceFileType): TEncodedData; var @@ -233,6 +256,7 @@ function TSaveInfoMgr.GenerateOutput(const FileType: TSourceFileType): sfText: Result := GeneratePlainText; sfHTML5: Result := GenerateHTML(UseHiliting, THTML5SnippetDoc); sfXHTML: Result := GenerateHTML(UseHiliting, TXHTMLSnippetDoc); + sfMarkdown: Result := GenerateMarkdown; end; end; @@ -298,6 +322,7 @@ constructor TSaveInfoMgr.InternalCreate(AView: IView); sTextDesc = 'Plain text file'; sHTML5Desc = 'HTML 5 file'; sXHTMLDesc = 'XHTML file'; + sMarkdownDesc = 'Markdown file'; begin inherited InternalCreate; fView := AView; @@ -336,6 +361,17 @@ constructor TSaveInfoMgr.InternalCreate(AView: IView); ] ); fSourceFileInfo.DefaultFileName := sDefFileName; + fSourceFileInfo.FileTypeInfo[sfMarkdown] := TSourceFileTypeInfo.Create( + '.md', + sMarkdownDesc, + [ + TSourceFileEncoding.Create(etUTF8, sUTF8Encoding), + TSourceFileEncoding.Create(etUTF16LE, sUTF16LEEncoding), + TSourceFileEncoding.Create(etUTF16BE, sUTF16BEEncoding), + TSourceFileEncoding.Create(etSysDefault, sANSIDefaultEncoding) + ] + ); + fSourceFileInfo.DefaultFileName := sDefFileName; fSaveDlg := TSaveSourceDlg.Create(nil); fSaveDlg.Title := sDlgCaption; @@ -379,6 +415,12 @@ procedure TSaveInfoMgr.PreviewHandler(Sender: TObject); PreviewDocType := dtHTML; PreviewFileType := sfXHTML; end; + sfMarkdown: + begin + // Markdown is previewed as plain text + PreviewDocType := dtPlainText; + PreviewFileType := sfMarkdown; + end; else raise Exception.Create( ClassName + '.PreviewHandler: unsupported file type' From 6e8e0f77aa2a2fb2110dfbf516c70777c37dee15 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Mon, 28 Apr 2025 15:22:27 +0100 Subject: [PATCH 297/330] Fix snippet info preview bug for ANSI encodings When either the plain text or Markdown file types were selected in the Save Snippet Information dialogue box and the ANSI encoding was also selected, the snippet displayed when the output ws previewed could differ from that written to file. This occured when the snippet contained characters that couldn't be rendered correctly in ANSI: the preview would show the correct snippet (rendered in Unicode) while the snippet with incorrectly translated characters was written to file. This bug was fixed so that the snippet previewed was also in ANSI encoding, meaning that any encoding errors show up there exactly as they will be written to file. Fixes #164 --- Src/USaveInfoMgr.pas | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Src/USaveInfoMgr.pas b/Src/USaveInfoMgr.pas index c62f5275c..ede347d7f 100644 --- a/Src/USaveInfoMgr.pas +++ b/Src/USaveInfoMgr.pas @@ -231,6 +231,7 @@ function TSaveInfoMgr.GenerateHTML(const AUseHiliting: Boolean; function TSaveInfoMgr.GenerateMarkdown: TEncodedData; var Doc: TMarkdownSnippetDoc; + GeneratedData: TEncodedData; begin Assert(Supports(fView, ISnippetView), ClassName + '.GeneratePlainText: View is not a snippet view'); @@ -238,7 +239,10 @@ function TSaveInfoMgr.GenerateMarkdown: TEncodedData; (fView as ISnippetView).Snippet.Kind <> skFreeform ); try - Result := Doc.Generate((fView as ISnippetView).Snippet); + GeneratedData := Doc.Generate((fView as ISnippetView).Snippet); + Result := TEncodedData.Create( + GeneratedData.ToString, fSaveDlg.SelectedEncoding + ); finally Doc.Free; end; @@ -264,13 +268,17 @@ function TSaveInfoMgr.GeneratePlainText: TEncodedData; var Doc: TTextSnippetDoc; // object that generates RTF document HiliteAttrs: IHiliteAttrs; // syntax highlighter formatting attributes + GeneratedData: TEncodedData; begin Assert(Supports(fView, ISnippetView), ClassName + '.GeneratePlainText: View is not a snippet view'); HiliteAttrs := THiliteAttrsFactory.CreateNulAttrs; Doc := TTextSnippetDoc.Create; try - Result := Doc.Generate((fView as ISnippetView).Snippet); + GeneratedData := Doc.Generate((fView as ISnippetView).Snippet); + Result := TEncodedData.Create( + GeneratedData.ToString, fSaveDlg.SelectedEncoding + ); finally Doc.Free; end; From 0c0403efa2262e13fe8d5b7cd6c6aca0060c8efc Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Mon, 28 Apr 2025 17:43:20 +0100 Subject: [PATCH 298/330] Add new TMessageBox.Warning method --- Src/UMessageBox.pas | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Src/UMessageBox.pas b/Src/UMessageBox.pas index 62108b1af..9f25b7260 100644 --- a/Src/UMessageBox.pas +++ b/Src/UMessageBox.pas @@ -142,6 +142,16 @@ TMessageBox = class sealed(TNoConstructObject) /// breaks.</param> class procedure Error(const Parent: TComponent; const Msg: string); + /// <summary>Displays a message in a warning dialogue box aligned over the + /// parent control.</summary> + /// <param name="Parent">TComponent [in] Dialogue box's parent control, + /// over which dialogue box is aligned. May be nil, when active form is + /// used for alignment.</param> + /// <param name="Msg">string [in] Message displayed in dialogue box. + /// Separate lines with LF or CRLF. Separate paragraphs with two line + /// breaks.</param> + class procedure Warning(const Parent: TComponent; const Msg: string); + /// <summary>Displays a message in a confirmation dialogue box aligned over /// the parent control.</summary> /// <param name="Parent">TComponent [in] Dialogue box's parent control, @@ -397,6 +407,21 @@ class procedure TMessageBox.Information(const Parent: TComponent; ); end; +class procedure TMessageBox.Warning(const Parent: TComponent; + const Msg: string); +begin + MessageBeep(MB_ICONEXCLAMATION); + Display( + Parent, + Msg, + mtWarning, + [TMessageBoxButton.Create(sBtnOK, mrOK, True, True)], + DefaultTitle, + DefaultIcon, + False + ); +end; + { TMessageBoxButton } constructor TMessageBoxButton.Create(const ACaption: TCaption; From 4b77024c89dfe9e08b29019039e5b462f364de0e Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Mon, 28 Apr 2025 20:28:56 +0100 Subject: [PATCH 299/330] Warn when saving snippet information looses data When saving snippet information that contains characters not supported in the selected output encoding a warning message now appears to inform that the saved or previewed text differs from the original. This usually happens when saving Markdown or plain text in the default ANSI encoding. Fixes #165 --- Src/USaveInfoMgr.pas | 44 +++++++++++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/Src/USaveInfoMgr.pas b/Src/USaveInfoMgr.pas index ede347d7f..600f15830 100644 --- a/Src/USaveInfoMgr.pas +++ b/Src/USaveInfoMgr.pas @@ -35,6 +35,10 @@ TSaveInfoMgr = class(TNoPublicConstructObject) fSaveDlg: TSaveSourceDlg; fSourceFileInfo: TSourceFileInfo; + /// <summary>Displays a warning message about data loss if + /// <c>ExpectedStr</c> doesn't match <c>EncodedStr</c>.</summary> + class procedure WarnIfDataLoss(const ExpectedStr, EncodedStr: string); + /// <summary>Returns encoded data containing a RTF representation of /// information about the snippet represented by the given view.</summary> class function GenerateRichText(View: IView; const AUseHiliting: Boolean): @@ -140,6 +144,7 @@ implementation Hiliter.UGlobals, UIOUtils, UMarkdownSnippetDoc, + UMessageBox, UOpenDialogHelper, UPreferences, URTFSnippetDoc, @@ -231,18 +236,20 @@ function TSaveInfoMgr.GenerateHTML(const AUseHiliting: Boolean; function TSaveInfoMgr.GenerateMarkdown: TEncodedData; var Doc: TMarkdownSnippetDoc; - GeneratedData: TEncodedData; + ExpectedMarkown: string; begin Assert(Supports(fView, ISnippetView), - ClassName + '.GeneratePlainText: View is not a snippet view'); + ClassName + '.GenerateMarkdown: View is not a snippet view'); Doc := TMarkdownSnippetDoc.Create( (fView as ISnippetView).Snippet.Kind <> skFreeform ); try - GeneratedData := Doc.Generate((fView as ISnippetView).Snippet); - Result := TEncodedData.Create( - GeneratedData.ToString, fSaveDlg.SelectedEncoding - ); + // Generate Markdown using default UTF-16 encoding + ExpectedMarkown := Doc.Generate((fView as ISnippetView).Snippet).ToString; + // Convert Markdown to encoding to that selected in save dialogue box + Result := TEncodedData.Create(ExpectedMarkown, fSaveDlg.SelectedEncoding); + // Check for data loss in required encoding + WarnIfDataLoss(ExpectedMarkown, Result.ToString); finally Doc.Free; end; @@ -266,19 +273,23 @@ function TSaveInfoMgr.GenerateOutput(const FileType: TSourceFileType): function TSaveInfoMgr.GeneratePlainText: TEncodedData; var - Doc: TTextSnippetDoc; // object that generates RTF document - HiliteAttrs: IHiliteAttrs; // syntax highlighter formatting attributes - GeneratedData: TEncodedData; + Doc: TTextSnippetDoc; // object that generates plain text document + HiliteAttrs: IHiliteAttrs; // syntax highlighter formatting attributes + ExpectedText: string; // expected plain text begin Assert(Supports(fView, ISnippetView), ClassName + '.GeneratePlainText: View is not a snippet view'); HiliteAttrs := THiliteAttrsFactory.CreateNulAttrs; Doc := TTextSnippetDoc.Create; try - GeneratedData := Doc.Generate((fView as ISnippetView).Snippet); + // Generate text using default UTF-16 encoding + ExpectedText := Doc.Generate((fView as ISnippetView).Snippet).ToString; + // Convert encoding to that selected in save dialogue box Result := TEncodedData.Create( - GeneratedData.ToString, fSaveDlg.SelectedEncoding + ExpectedText, fSaveDlg.SelectedEncoding ); + // Check for data loss in required encoding + WarnIfDataLoss(ExpectedText, Result.ToString); finally Doc.Free; end; @@ -448,4 +459,15 @@ function TSaveInfoMgr.SelectedFileType: TSourceFileType; Result := fSourceFileInfo.FileTypeFromFilterIdx(fSaveDlg.FilterIndex); end; +class procedure TSaveInfoMgr.WarnIfDataLoss(const ExpectedStr, + EncodedStr: string); +resourcestring + sEncodingError = 'The selected snippet contains characters that can''t be ' + + 'represented in the chosen file encoding.' + sLineBreak + sLineBreak + + 'Please compare the output to the snippet displayed in the Details pane.'; +begin + if ExpectedStr <> EncodedStr then + TMessageBox.Warning(nil, sEncodingError); +end; + end. From 6cf961fa9181315e88c5fff42fd873fa273a88e6 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Mon, 28 Apr 2025 21:22:59 +0100 Subject: [PATCH 300/330] Update Save Snippet Information Dialogue Help topic Rewritten re the addition of HTML 5, XHTML, plain text and Markdown format options to the original rich text. Also revised re the change to using the more complex TSaveSourceDlg dialogue box that enables syntax highliting, previewing, and choice of file encodings. --- Src/Help/HTML/dlg_saveinfo.htm | 86 +++++++++++++++++++++++++++++++--- 1 file changed, 79 insertions(+), 7 deletions(-) diff --git a/Src/Help/HTML/dlg_saveinfo.htm b/Src/Help/HTML/dlg_saveinfo.htm index 53abcca9d..59cbda798 100644 --- a/Src/Help/HTML/dlg_saveinfo.htm +++ b/Src/Help/HTML/dlg_saveinfo.htm @@ -28,21 +28,93 @@ <h1> </h1> <p> This dialogue box is displayed when the <em>File | Save Snippet - Information</em> menu option is clicked. It is used to specify the - name of the file into which information about the currently selected - snippet is to be saved. + Information</em> 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. </p> <p> - The saved snippet information is written in rich text format. + The dialogue is a standard Windows save dialogue box with a few added + options. </p> <p> - This dialogue is a standard Windows save dialogue box. You specify the - name and folder for the file in the usual way. + You specify the name and folder for the file where the snippet information + is to be written in in the usual way. </p> <p> - Use the <em>Save</em> button to write the file to disk or press + Use the <em>Save as type</em> drop down list to specify the type of file + to be saved. Options are: + </p> + <ul> + <li>Plain text.</li> + <li>HTML</li> + <li>XHTML</li> + <li>Rich text format</li> + <li>Markdown</li> + </ul> + <p> + 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. + </p> + <p> + 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 <em>Use syntax highlighting</em> check box is checked. + </p> + <p> + The output file encoding can be be specified in the <em>File Encoding</em> + drop down list. Options vary depending on the file type. Some file types + support only a single encoding. The encodings are: + </p> + <ul> + <li> + <em>ANSI (Default)</em> – the system default ANSI encoding. + Available as an option for plain text and Markdown file formats. + </li> + <li> + <em>UTF-8</em> – UTF-8 encoding, with BOM<sup>†</sup>. + Available as an option for plain text and Markdown file formats and + as the only encoding available for HTML 5 and XHTML file formats. + </li> + <li> + <em>Unicode (Little Endian)</em> – UTF-16 LE encoding, with + BOM<sup>†</sup>. Available as an option for plain text files and Markdown + file formats. + </li> + <li> + <em>Unicode (Big Endian)</em> – UTF-16 BE encoding, with + BOM<sup>†</sup>. Available as an option for plain text files and Markdown + file formats. + </li> + <li> + <em>ASCII</em> – The only encoding available for the rich text file. + </li> + </ul> + <p> + The output can be previewed by clicking the <em>Preview</em> 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. + </p> + <p> + Use the <em>Save</em> button to write the snippet information to disk or choose <em>Cancel</em> to abort. </p> + <p> + <strong class="warning">Warning:</strong> 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. + </p> + <h3> + Footnote + </h3> + <p> + † BOM = Byte Order Mark or Preamble: a sequence of bytes at the + start of a text file that identifies its encoding. + </p> </body> </html> \ No newline at end of file From 7d81f661d96ada94c51fcf9b662bb9d2ee5f8596 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Mon, 28 Apr 2025 21:27:54 +0100 Subject: [PATCH 301/330] Update file format documentation Updated Docs/Design/FileFormats/saved.html re the addition of plain text, HTML 5, XHTML and Markdown support when saving snippet information, along with support for different file encodings. --- Docs/Design/FileFormats/saved.html | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/Docs/Design/FileFormats/saved.html b/Docs/Design/FileFormats/saved.html index f464bd621..3353269a3 100644 --- a/Docs/Design/FileFormats/saved.html +++ b/Docs/Design/FileFormats/saved.html @@ -62,9 +62,27 @@ <h2> </ol> <p> - In the first case the snippet is always saved in rich text format. + In the first case the snippet information can be saved as one of the following file types: </p> +<ul> + <li> + Plain text. + </li> + <li> + HTML 5 files. + </li> + <li> + XHTML files. + </li> + <li> + Rich text files. + </li> + <li> + Markdown files. + </li> +</ul> + <p> In the second two cases the following file types can be chosen by the user: </p> @@ -88,7 +106,7 @@ <h2> </ul> <p> - There is no specific file format for these files, except that HTML 5, 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. </p> @@ -97,11 +115,7 @@ <h2> </h2> <p> - In the first case the RTF is always saved in ASCII format. -</p> - -<p> - In the 2nd and 3rd cases the encodings used depend on the file type and user choice. Different file + The available encodings used depend on the file type and user choice. Different file types have different encoding choices, as follows: </p> @@ -164,7 +178,7 @@ <h2> <dd> <ul class="squashed"> <li> - ANSI (system default code page). ASCII format is actually used. + ASCII [for Snippet Information] or ANSI [otherwise]. Regardless of naming, ASCII format is always used. </li> </ul> </dd> From 122df99dc59004704b19068f2c51fa8d2471d5a4 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Tue, 29 Apr 2025 08:09:01 +0100 Subject: [PATCH 302/330] Refactor TSaveInfoMgr class Removed much duplicated code & rationalised snippet information document creation. --- Src/USaveInfoMgr.pas | 160 ++++++++++++++----------------------------- 1 file changed, 50 insertions(+), 110 deletions(-) diff --git a/Src/USaveInfoMgr.pas b/Src/USaveInfoMgr.pas index 600f15830..24aa24bec 100644 --- a/Src/USaveInfoMgr.pas +++ b/Src/USaveInfoMgr.pas @@ -20,6 +20,7 @@ interface UEncodings, UHTMLSnippetDoc, USaveSourceDlg, + USnippetDoc, USourceFileInfo, UView; @@ -39,32 +40,6 @@ TSaveInfoMgr = class(TNoPublicConstructObject) /// <c>ExpectedStr</c> doesn't match <c>EncodedStr</c>.</summary> class procedure WarnIfDataLoss(const ExpectedStr, EncodedStr: string); - /// <summary>Returns encoded data containing a RTF representation of - /// information about the snippet represented by the given view.</summary> - class function GenerateRichText(View: IView; const AUseHiliting: Boolean): - TEncodedData; static; - - /// <summary>Returns encoded data containing a HTML representation of the - /// required snippet information.</summary> - /// <param name="AUseHiliting"><c>Boolean</c> [in] Determines whether - /// source code is syntax highlighted or not.</param> - /// <param name="GeneratorClass"><c>THTMLSnippetDocClass</c> [in] Class of - /// object used to generate the required flavour of HTML.</param> - /// <returns><c>TEncodedData</c>. Required HTML document, encoded as UTF-8. - /// </returns> - function GenerateHTML(const AUseHiliting: Boolean; - const GeneratorClass: THTMLSnippetDocClass): TEncodedData; - - /// <summary>Returns encoded data containing a plain text representation of - /// information about the snippet represented by the given view.</summary> - function GeneratePlainText: TEncodedData; - - /// <summary>Returns encoded data containing a Markdown representation of - /// information about the snippet represented by the given view.</summary> - /// <returns><c>TEncodedData</c>. Required Markdown document, encoded as - /// UTF-16.</returns> - function GenerateMarkdown: TEncodedData; - /// <summary>Returns type of file selected in the associated save dialogue /// box.</summary> function SelectedFileType: TSourceFileType; @@ -96,6 +71,14 @@ TSaveInfoMgr = class(TNoPublicConstructObject) procedure EncodingQueryHandler(Sender: TObject; var Encodings: TSourceFileEncodings); + /// <summary>Returns an instance of the document generator object for the + /// desired file type.</summary> + /// <param name="FileType"><c>TSourceFileType</c> [in] The type of file to + /// be generated.</param> + /// <returns><c>TSnippetDoc</c>. The required document generator object. + /// The caller MUST free this object.</returns> + function GetDocGenerator(const FileType: TSourceFileType): TSnippetDoc; + /// <summary>Generates the required snippet information in the requested /// format.</summary> /// <param name="FileType"><c>TSourceFileType</c> [in] Type of file to be @@ -142,6 +125,7 @@ implementation Hiliter.UAttrs, Hiliter.UFileHiliter, Hiliter.UGlobals, + UExceptions, UIOUtils, UMarkdownSnippetDoc, UMessageBox, @@ -215,106 +199,62 @@ class procedure TSaveInfoMgr.Execute(View: IView); end; end; -function TSaveInfoMgr.GenerateHTML(const AUseHiliting: Boolean; - const GeneratorClass: THTMLSnippetDocClass): TEncodedData; -var - Doc: THTMLSnippetDoc; // object that generates RTF document - HiliteAttrs: IHiliteAttrs; // syntax highlighter formatting attributes -begin - if (fView as ISnippetView).Snippet.HiliteSource and AUseHiliting then - HiliteAttrs := THiliteAttrsFactory.CreateUserAttrs - else - HiliteAttrs := THiliteAttrsFactory.CreateNulAttrs; - Doc := GeneratorClass.Create(HiliteAttrs); - try - Result := Doc.Generate((fView as ISnippetView).Snippet); - finally - Doc.Free; - end; -end; - -function TSaveInfoMgr.GenerateMarkdown: TEncodedData; -var - Doc: TMarkdownSnippetDoc; - ExpectedMarkown: string; -begin - Assert(Supports(fView, ISnippetView), - ClassName + '.GenerateMarkdown: View is not a snippet view'); - Doc := TMarkdownSnippetDoc.Create( - (fView as ISnippetView).Snippet.Kind <> skFreeform - ); - try - // Generate Markdown using default UTF-16 encoding - ExpectedMarkown := Doc.Generate((fView as ISnippetView).Snippet).ToString; - // Convert Markdown to encoding to that selected in save dialogue box - Result := TEncodedData.Create(ExpectedMarkown, fSaveDlg.SelectedEncoding); - // Check for data loss in required encoding - WarnIfDataLoss(ExpectedMarkown, Result.ToString); - finally - Doc.Free; - end; -end; - function TSaveInfoMgr.GenerateOutput(const FileType: TSourceFileType): TEncodedData; var - UseHiliting: Boolean; + Doc: TSnippetDoc; + DocData: TEncodedData; + ExpectedText: string; begin - UseHiliting := fSaveDlg.UseSyntaxHiliting and - TFileHiliter.IsHilitingSupported(FileType); - case FileType of - sfRTF: Result := GenerateRichText(fView, UseHiliting); - sfText: Result := GeneratePlainText; - sfHTML5: Result := GenerateHTML(UseHiliting, THTML5SnippetDoc); - sfXHTML: Result := GenerateHTML(UseHiliting, TXHTMLSnippetDoc); - sfMarkdown: Result := GenerateMarkdown; - end; -end; - -function TSaveInfoMgr.GeneratePlainText: TEncodedData; -var - Doc: TTextSnippetDoc; // object that generates plain text document - HiliteAttrs: IHiliteAttrs; // syntax highlighter formatting attributes - ExpectedText: string; // expected plain text -begin - Assert(Supports(fView, ISnippetView), - ClassName + '.GeneratePlainText: View is not a snippet view'); - HiliteAttrs := THiliteAttrsFactory.CreateNulAttrs; - Doc := TTextSnippetDoc.Create; + // Create required type of document generator + Doc := GetDocGenerator(FileType); try - // Generate text using default UTF-16 encoding - ExpectedText := Doc.Generate((fView as ISnippetView).Snippet).ToString; - // Convert encoding to that selected in save dialogue box - Result := TEncodedData.Create( - ExpectedText, fSaveDlg.SelectedEncoding - ); - // Check for data loss in required encoding - WarnIfDataLoss(ExpectedText, Result.ToString); + Assert(Assigned(Doc), ClassName + '.GenerateOutput: unknown file type'); + // Generate text + DocData := Doc.Generate((fView as ISnippetView).Snippet); + if DocData.EncodingType <> fSaveDlg.SelectedEncoding then + begin + // Required encoding is different to that used to generate document, so + // we need to convert to the desired encoding + ExpectedText := DocData.ToString; + // Convert encoding to that selected in save dialogue box + Result := TEncodedData.Create( + ExpectedText, fSaveDlg.SelectedEncoding + ); + // Check for data loss in desired encoding + WarnIfDataLoss(ExpectedText, Result.ToString); + end + else + // Required encoding is same as that used to generate the document + Result := DocData; finally Doc.Free; end; end; -class function TSaveInfoMgr.GenerateRichText(View: IView; - const AUseHiliting: Boolean): TEncodedData; +function TSaveInfoMgr.GetDocGenerator(const FileType: TSourceFileType): + TSnippetDoc; var - Doc: TRTFSnippetDoc; // object that generates RTF document + UseHiliting: Boolean; + IsPascalSnippet: Boolean; HiliteAttrs: IHiliteAttrs; // syntax highlighter formatting attributes begin - Assert(Supports(View, ISnippetView), - 'TSaveInfoMgr.GenerateRichText: View is not a snippet view'); - if (View as ISnippetView).Snippet.HiliteSource and AUseHiliting then + IsPascalSnippet := (fView as ISnippetView).Snippet.Kind <> skFreeform; + UseHiliting := fSaveDlg.UseSyntaxHiliting + and TFileHiliter.IsHilitingSupported(FileType) + and (fView as ISnippetView).Snippet.HiliteSource; + if UseHiliting then HiliteAttrs := THiliteAttrsFactory.CreateUserAttrs else HiliteAttrs := THiliteAttrsFactory.CreateNulAttrs; - Doc := TRTFSnippetDoc.Create(HiliteAttrs); - try - // TRTFSnippetDoc generates stream of ASCII bytes - Result := Doc.Generate((View as ISnippetView).Snippet); - Assert(Result.EncodingType = etASCII, - 'TSaveInfoMgr.GenerateRichText: ASCII encoded data expected'); - finally - Doc.Free; + // Create required type of document generator + case FileType of + sfRTF: Result := TRTFSnippetDoc.Create(HiliteAttrs); + sfText: Result := TTextSnippetDoc.Create; + sfHTML5: Result := THTML5SnippetDoc.Create(HiliteAttrs); + sfXHTML: Result := TXHTMLSnippetDoc.Create(HiliteAttrs); + sfMarkdown: Result := TMarkdownSnippetDoc.Create(IsPascalSnippet); + else Result := nil; end; end; From 679ce0af78a92796218ef22793e2ea6cea0cb483 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Tue, 29 Apr 2025 09:07:40 +0100 Subject: [PATCH 303/330] Improve display of file encodings in TSaveSourceDlg The "File encoding" label and combo box in TSaveSourceDlg is now disabled when it only contains one item. --- Src/USaveSourceDlg.pas | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Src/USaveSourceDlg.pas b/Src/USaveSourceDlg.pas index 78b301487..aab88db3d 100644 --- a/Src/USaveSourceDlg.pas +++ b/Src/USaveSourceDlg.pas @@ -495,6 +495,8 @@ procedure TSaveSourceDlg.DoTypeChange; fCmbEncoding.ItemIndex := IndexOfEncodingType(fSelectedEncoding); if fCmbEncoding.ItemIndex = -1 then fCmbEncoding.ItemIndex := 0; + fCmbEncoding.Enabled := fCmbEncoding.Items.Count > 1; + fLblEncoding.Enabled := fCmbEncoding.Enabled; DoEncodingChange; inherited; From 543170e5732e4ca07cc2133aa5e2d5d2a098c8fc Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Tue, 29 Apr 2025 10:21:39 +0100 Subject: [PATCH 304/330] Standardise and improve file encoding names File encoding names are now determined by the encoding type and are no longer specified by the code that displays TSaveSourceDlg dialogue boxes. This not only standardises the encoding names but removes quite a bit of duplication and simplifies code that saves snippets, snippet information and units. The system default ANSI encoding was previously displayed as "ANSI (Default)". This has been changed to "ANSI Code Page 999", where 999 is the default system code page for the user's locale. --- Src/USaveInfoMgr.pas | 33 +++++------------------- Src/USaveSourceDlg.pas | 4 +-- Src/USaveSourceMgr.pas | 34 +++++------------------- Src/USourceFileInfo.pas | 57 +++++++++++++++++++++++++++++++++-------- 4 files changed, 60 insertions(+), 68 deletions(-) diff --git a/Src/USaveInfoMgr.pas b/Src/USaveInfoMgr.pas index 24aa24bec..7b0c4dca9 100644 --- a/Src/USaveInfoMgr.pas +++ b/Src/USaveInfoMgr.pas @@ -270,18 +270,13 @@ constructor TSaveInfoMgr.InternalCreate(AView: IView); resourcestring sDefFileName = 'SnippetInfo'; sDlgCaption = 'Save Snippet Information'; - // descriptions of supported encodings - sASCIIEncoding = 'ASCII'; - sANSIDefaultEncoding = 'ANSI (Default)'; - sUTF8Encoding = 'UTF-8'; - sUTF16LEEncoding = 'Unicode (Little Endian)'; - sUTF16BEEncoding = 'Unicode (Big Endian)'; // descriptions of supported file filter strings sRTFDesc = 'Rich text file'; sTextDesc = 'Plain text file'; sHTML5Desc = 'HTML 5 file'; sXHTMLDesc = 'XHTML file'; sMarkdownDesc = 'Markdown file'; + begin inherited InternalCreate; fView := AView; @@ -290,45 +285,29 @@ constructor TSaveInfoMgr.InternalCreate(AView: IView); fSourceFileInfo.FileTypeInfo[sfRTF] := TSourceFileTypeInfo.Create( '.rtf', sRTFDesc, - [ - TSourceFileEncoding.Create(etASCII, sASCIIEncoding) - ] + [etASCII] ); fSourceFileInfo.FileTypeInfo[sfText] := TSourceFileTypeInfo.Create( '.txt', sTextDesc, - [ - TSourceFileEncoding.Create(etUTF8, sUTF8Encoding), - TSourceFileEncoding.Create(etUTF16LE, sUTF16LEEncoding), - TSourceFileEncoding.Create(etUTF16BE, sUTF16BEEncoding), - TSourceFileEncoding.Create(etSysDefault, sANSIDefaultEncoding) - ] + [etUTF8, etUTF16LE, etUTF16BE, etSysDefault] ); fSourceFileInfo.FileTypeInfo[sfHTML5] := TSourceFileTypeInfo.Create( '.html', sHTML5Desc, - [ - TSourceFileEncoding.Create(etUTF8, sUTF8Encoding) - ] + [etUTF8] ); fSourceFileInfo.DefaultFileName := sDefFileName; fSourceFileInfo.FileTypeInfo[sfXHTML] := TSourceFileTypeInfo.Create( '.html', sXHTMLDesc, - [ - TSourceFileEncoding.Create(etUTF8, sUTF8Encoding) - ] + [etUTF8] ); fSourceFileInfo.DefaultFileName := sDefFileName; fSourceFileInfo.FileTypeInfo[sfMarkdown] := TSourceFileTypeInfo.Create( '.md', sMarkdownDesc, - [ - TSourceFileEncoding.Create(etUTF8, sUTF8Encoding), - TSourceFileEncoding.Create(etUTF16LE, sUTF16LEEncoding), - TSourceFileEncoding.Create(etUTF16BE, sUTF16BEEncoding), - TSourceFileEncoding.Create(etSysDefault, sANSIDefaultEncoding) - ] + [etUTF8, etUTF16LE, etUTF16BE, etSysDefault] ); fSourceFileInfo.DefaultFileName := sDefFileName; diff --git a/Src/USaveSourceDlg.pas b/Src/USaveSourceDlg.pas index aab88db3d..21debca51 100644 --- a/Src/USaveSourceDlg.pas +++ b/Src/USaveSourceDlg.pas @@ -228,8 +228,6 @@ implementation sChkTruncateComment = 'Truncate comments to 1st paragraph'; sBtnPreview = '&Preview...'; sBtnHelp = '&Help'; - // Default encoding name - sANSIEncoding = 'ANSI (Default)'; const @@ -483,7 +481,7 @@ procedure TSaveSourceDlg.DoTypeChange; fOnEncodingQuery(Self, Encodings); if Length(Encodings) = 0 then Encodings := TSourceFileEncodings.Create( - TSourceFileEncoding.Create(etSysDefault, sANSIEncoding) + TSourceFileEncoding.Create(etSysDefault) ); fCmbEncoding.Clear; for Encoding in Encodings do diff --git a/Src/USaveSourceMgr.pas b/Src/USaveSourceMgr.pas index 41581bcfa..9a43cdce2 100644 --- a/Src/USaveSourceMgr.pas +++ b/Src/USaveSourceMgr.pas @@ -134,8 +134,8 @@ implementation // Delphi SysUtils, // Project - FmPreviewDlg, Hiliter.UFileHiliter, UIOUtils, UMessageBox, UOpenDialogHelper, - UPreferences; + FmPreviewDlg, Hiliter.UFileHiliter, UIOUtils, UMessageBox, + UOpenDialogHelper, UPreferences; { TSaveSourceMgr } @@ -244,53 +244,33 @@ procedure TSaveSourceMgr.HiliteQueryHandler(Sender: TObject; end; constructor TSaveSourceMgr.InternalCreate; -resourcestring - // descriptions of supported encodings - sANSIDefaultEncoding = 'ANSI (Default)'; - sUTF8Encoding = 'UTF-8'; - sUTF16LEEncoding = 'Unicode (Little Endian)'; - sUTF16BEEncoding = 'Unicode (Big Endian)'; begin inherited InternalCreate; fSourceFileInfo := TSourceFileInfo.Create; fSourceFileInfo.FileTypeInfo[sfText] := TSourceFileTypeInfo.Create( '.txt', GetFileTypeDesc(sfText), - [ - TSourceFileEncoding.Create(etSysDefault, sANSIDefaultEncoding), - TSourceFileEncoding.Create(etUTF8, sUTF8Encoding), - TSourceFileEncoding.Create(etUTF16LE, sUTF16LEEncoding), - TSourceFileEncoding.Create(etUTF16BE, sUTF16BEEncoding) - ] + [etSysDefault, etUTF8, etUTF16LE, etUTF16BE] ); fSourceFileInfo.FileTypeInfo[sfPascal] := TSourceFileTypeInfo.Create( '.pas', GetFileTypeDesc(sfPascal), - [ - TSourceFileEncoding.Create(etSysDefault, sANSIDefaultEncoding), - TSourceFileEncoding.Create(etUTF8, sUTF8Encoding) - ] + [etSysDefault, etUTF8] ); fSourceFileInfo.FileTypeInfo[sfHTML5] := TSourceFileTypeInfo.Create( '.html', GetFileTypeDesc(sfHTML5), - [ - TSourceFileEncoding.Create(etUTF8, sUTF8Encoding) - ] + [etUTF8] ); fSourceFileInfo.FileTypeInfo[sfXHTML] := TSourceFileTypeInfo.Create( '.html', GetFileTypeDesc(sfXHTML), - [ - TSourceFileEncoding.Create(etUTF8, sUTF8Encoding) - ] + [etUTF8] ); fSourceFileInfo.FileTypeInfo[sfRTF] := TSourceFileTypeInfo.Create( '.rtf', GetFileTypeDesc(sfRTF), - [ - TSourceFileEncoding.Create(etSysDefault, sANSIDefaultEncoding) - ] + [etSysDefault] ); fSourceFileInfo.DefaultFileName := GetDefaultFileName; diff --git a/Src/USourceFileInfo.pas b/Src/USourceFileInfo.pas index 213f9041a..2a9948b81 100644 --- a/Src/USourceFileInfo.pas +++ b/Src/USourceFileInfo.pas @@ -46,11 +46,15 @@ TSourceFileEncoding = record fEncodingType: TEncodingType; // Value of EncodingType property fDisplayName: string; // Value of DisplayName property public - /// <summary>Sets values of properties.</summary> - constructor Create(const AEncodingType: TEncodingType; - const ADisplayName: string); + /// <summary>Sets the value of the <c>EncodingType</c> property.</summary> + /// <remarks>The <c>DisplayName</c> property is dependent on the value of + /// the <c>EncodingType</c> property and so can't be set explicitly. + /// </remarks> + constructor Create(const AEncodingType: TEncodingType); + /// <summary>Type of this encoding.</summary> property EncodingType: TEncodingType read fEncodingType; + /// <summary>Description of encoding for display in dialog box.</summary> property DisplayName: string read fDisplayName; end; @@ -72,7 +76,7 @@ TSourceFileTypeInfo = record public /// <summary>Sets values of properties.</summary> constructor Create(const AExtension, ADisplayName: string; - const AEncodings: array of TSourceFileEncoding); + const AEncodingTypes: array of TEncodingType); /// <summary>File extension associated with this file type.</summary> property Extension: string read fExtension; /// <summary>Name of file extension to display in save dialog box. @@ -163,6 +167,7 @@ implementation // Delphi SysUtils, Windows {for inlining}, Character, // Project + ULocales, UStrUtils; @@ -261,24 +266,54 @@ function TSourceFileInfo.SupportsFileType(const FileType: TSourceFileType): { TSourceFileTypeInfo } constructor TSourceFileTypeInfo.Create(const AExtension, ADisplayName: string; - const AEncodings: array of TSourceFileEncoding); + const AEncodingTypes: array of TEncodingType); var I: Integer; begin fExtension := AExtension; fDisplayName := ADisplayName; - SetLength(fEncodings, Length(AEncodings)); - for I := 0 to Pred(Length(AEncodings)) do - fEncodings[I] := AEncodings[I]; + SetLength(fEncodings, Length(AEncodingTypes)); + for I := 0 to Pred(Length(AEncodingTypes)) do + fEncodings[I] := TSourceFileEncoding.Create(AEncodingTypes[I]); end; { TSourceFileEncoding } -constructor TSourceFileEncoding.Create(const AEncodingType: TEncodingType; - const ADisplayName: string); +constructor TSourceFileEncoding.Create(const AEncodingType: TEncodingType); +resourcestring + // Display names associated with each TEncodingType value + sASCIIEncodingName = 'ASCII'; + sISO88591Name = 'ISO-8859-1'; + sUTF8Name = 'UTF-8'; + sUnicodeName = 'UTF-16'; + sUTF16BEName = 'UTF-16 Big Endian'; + sUTF16LEName = 'UTF-16 Little Endian'; + sWindows1252Name = 'Windows-1252'; + sSysDefaultName = 'ANSI Code Page %d'; begin fEncodingType := AEncodingType; - fDisplayName := ADisplayName; + case fEncodingType of + etASCII: + fDisplayName := sASCIIEncodingName; + etISO88591: + fDisplayName := sISO88591Name; + etUTF8: + fDisplayName := sUTF8Name; + etUnicode: + fDisplayName := sUnicodeName; + etUTF16BE: + fDisplayName := sUTF16BEName; + etUTF16LE: + fDisplayName := sUTF16LEName; + etWindows1252: + fDisplayName := sWindows1252Name; + etSysDefault: + fDisplayName := Format(sSysDefaultName, [ULocales.DefaultAnsiCodePage]); + else + fDisplayName := ''; + end; + Assert(fDisplayName <> '', + 'TSourceFileEncoding.Create: Unrecognised encoding type'); end; end. From ea14d1fd129e3131c48d60b479e1b2350fed7928 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Tue, 29 Apr 2025 10:43:11 +0100 Subject: [PATCH 305/330] Correct RTF encoding displayed in save dialogues The file encoding presented as the only option for RTF files in the save dialogue boxes displayed by the File | Save Unit and File | Save Annotated Source dialogue boxes was changed from ANSI to ASCII. RTF files are always saved in ASCII, regardless of the fact that the encoding was presented as ANSI. This now conforms with the ASCII encoding already presented by the File | Save Snippet Information dialogue box. --- Src/USaveSourceMgr.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Src/USaveSourceMgr.pas b/Src/USaveSourceMgr.pas index 9a43cdce2..995458c5d 100644 --- a/Src/USaveSourceMgr.pas +++ b/Src/USaveSourceMgr.pas @@ -270,7 +270,7 @@ constructor TSaveSourceMgr.InternalCreate; fSourceFileInfo.FileTypeInfo[sfRTF] := TSourceFileTypeInfo.Create( '.rtf', GetFileTypeDesc(sfRTF), - [etSysDefault] + [etASCII] ); fSourceFileInfo.DefaultFileName := GetDefaultFileName; From 485e0536cc43f22b939d31330ad0f0e63c6945f9 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Tue, 29 Apr 2025 11:03:38 +0100 Subject: [PATCH 306/330] Update title of Snippet Information dialogue box The title of the dialogue box was changed to display the name of the snippet for which information is being displayed. --- Src/USaveInfoMgr.pas | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Src/USaveInfoMgr.pas b/Src/USaveInfoMgr.pas index 7b0c4dca9..16cb5b344 100644 --- a/Src/USaveInfoMgr.pas +++ b/Src/USaveInfoMgr.pas @@ -151,6 +151,8 @@ destructor TSaveInfoMgr.Destroy; end; procedure TSaveInfoMgr.DoExecute; +resourcestring + sDlgCaption = 'Save Snippet Information for %s'; var Encoding: TEncoding; // encoding to use for output file FileContent: string; // output file content before encoding @@ -164,6 +166,9 @@ procedure TSaveInfoMgr.DoExecute; 1 ); fSaveDlg.FileName := fSourceFileInfo.DefaultFileName; + fSaveDlg.Title := Format(sDlgCaption, [ + (fView as ISnippetView).Snippet.DisplayName] + ); // Display dialog box and save file if user OKs if fSaveDlg.Execute then begin @@ -269,7 +274,6 @@ constructor TSaveInfoMgr.InternalCreate(AView: IView); DlgHelpKeyword = 'SnippetInfoFileDlg'; resourcestring sDefFileName = 'SnippetInfo'; - sDlgCaption = 'Save Snippet Information'; // descriptions of supported file filter strings sRTFDesc = 'Rich text file'; sTextDesc = 'Plain text file'; @@ -312,7 +316,6 @@ constructor TSaveInfoMgr.InternalCreate(AView: IView); fSourceFileInfo.DefaultFileName := sDefFileName; fSaveDlg := TSaveSourceDlg.Create(nil); - fSaveDlg.Title := sDlgCaption; fSaveDlg.HelpKeyword := DlgHelpKeyword; fSaveDlg.CommentStyle := TCommentStyle.csNone; fSaveDlg.EnableCommentStyles := False; From 82e5748fa55aee83cd1d75f1c26f106343c3b7b6 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Tue, 29 Apr 2025 11:10:06 +0100 Subject: [PATCH 307/330] Update title of Save Annotated Source dialogue box This dialogue's title did not reflect the name of the menu option that displays it, so it was updated to included "Save Annotated Source". When a snippet is being displayed the word "Snippet" was removed from the title. Conversely the word "Category" was retained when displaying a category. --- Src/USaveSnippetMgr.pas | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Src/USaveSnippetMgr.pas b/Src/USaveSnippetMgr.pas index 25de4e1ba..cb08bd8c6 100644 --- a/Src/USaveSnippetMgr.pas +++ b/Src/USaveSnippetMgr.pas @@ -92,8 +92,8 @@ implementation resourcestring // Dialog box title - sSaveSnippetDlgTitle = 'Save %0:s Snippet'; - sSaveCategoryDlgTitle = 'Save %0:s Category'; + sSaveSnippetDlgTitle = 'Save Annotated Source of %0:s'; + sSaveCategoryDlgTitle = 'Save Annotated Source of %0:s Category'; // Output document title for snippets and categories sDocTitle = '"%0:s" %1:s'; sCategory = 'category'; From 011629b86cb5d8fd0230216b97dc0459a09b90f7 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Tue, 29 Apr 2025 11:21:54 +0100 Subject: [PATCH 308/330] Change default filename in Save Snippet Info dlg The default file name was always "SnippetInfo", which could lead to accidental overwrites of previously save information. The default was changed to be a name based on the snippet display name. Modified TSourceFileInfo with new property to prevent default file names from being converted as necessary to make them safe Pascal identifiers: we don't want this to happen in the Save Snippet Information dialogue. --- Src/USaveInfoMgr.pas | 8 +++---- Src/USourceFileInfo.pas | 46 +++++++++++++++++++++++++++-------------- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/Src/USaveInfoMgr.pas b/Src/USaveInfoMgr.pas index 16cb5b344..6f71937d1 100644 --- a/Src/USaveInfoMgr.pas +++ b/Src/USaveInfoMgr.pas @@ -273,7 +273,6 @@ constructor TSaveInfoMgr.InternalCreate(AView: IView); const DlgHelpKeyword = 'SnippetInfoFileDlg'; resourcestring - sDefFileName = 'SnippetInfo'; // descriptions of supported file filter strings sRTFDesc = 'Rich text file'; sTextDesc = 'Plain text file'; @@ -301,19 +300,20 @@ constructor TSaveInfoMgr.InternalCreate(AView: IView); sHTML5Desc, [etUTF8] ); - fSourceFileInfo.DefaultFileName := sDefFileName; fSourceFileInfo.FileTypeInfo[sfXHTML] := TSourceFileTypeInfo.Create( '.html', sXHTMLDesc, [etUTF8] ); - fSourceFileInfo.DefaultFileName := sDefFileName; fSourceFileInfo.FileTypeInfo[sfMarkdown] := TSourceFileTypeInfo.Create( '.md', sMarkdownDesc, [etUTF8, etUTF16LE, etUTF16BE, etSysDefault] ); - fSourceFileInfo.DefaultFileName := sDefFileName; + + // set default file name without converting to valid Pascal identifier + fSourceFileInfo.RequirePascalDefFileName := False; + fSourceFileInfo.DefaultFileName := fView.Description; fSaveDlg := TSaveSourceDlg.Create(nil); fSaveDlg.HelpKeyword := DlgHelpKeyword; diff --git a/Src/USourceFileInfo.pas b/Src/USourceFileInfo.pas index 2a9948b81..863c19e12 100644 --- a/Src/USourceFileInfo.pas +++ b/Src/USourceFileInfo.pas @@ -103,6 +103,8 @@ TSourceFileInfo = class(TObject) fFilterIdxToFileTypeMap: TDictionary<Integer,TSourceFileType>; /// <summary>Value of DefaultFileName property.</summary> fDefaultFileName: string; + /// <summary>Value of <c>RequirePascalDefFileName</c> property.</summary> + fRequirePascalDefFileName: Boolean; /// <summary>Filter string for use in open / save dialog boxes from /// descriptions and file extensions of each supported file type. /// </summary> @@ -153,10 +155,18 @@ TSourceFileInfo = class(TObject) read GetFileTypeInfo write SetFileTypeInfo; /// <summary>Default source code file name.</summary> - /// <remarks>Must be a valid Pascal identifier. Invalid characters are - /// replaced by underscores.</remarks> + /// <remarks>If, and only if, <c>RequirePascalDefFileName</c> is + /// <c>True</c> the default file name is modified so that name is a valid + /// Pascal identifier.</remarks> property DefaultFileName: string read fDefaultFileName write SetDefaultFileName; + + /// <summary>Determines whether any value assigned to + /// <c>DefaultFileName</c> is converted to a valid Pascal identifier or + /// not.</summary> + property RequirePascalDefFileName: Boolean + read fRequirePascalDefFileName write fRequirePascalDefFileName + default True; end; @@ -178,6 +188,7 @@ constructor TSourceFileInfo.Create; inherited Create; fFileTypeInfo := TDictionary<TSourceFileType,TSourceFileTypeInfo>.Create; fFilterIdxToFileTypeMap := TDictionary<Integer,TSourceFileType>.Create; + fRequirePascalDefFileName := True; end; destructor TSourceFileInfo.Destroy; @@ -232,19 +243,24 @@ procedure TSourceFileInfo.SetDefaultFileName(const Value: string); var Idx: Integer; // loops through characters of filename begin - // convert to "camel" case - fDefaultFileName := StrStripWhiteSpace(StrCapitaliseWords(Value)); - // replaces invalid Pascal identifier characters with underscore - if (fDefaultFileName <> '') - and not TCharacter.IsLetter(fDefaultFileName[1]) - and (fDefaultFileName[1] <> '_') then - fDefaultFileName[1] := '_'; - for Idx := 2 to Length(fDefaultFileName) do - if not TCharacter.IsLetterOrDigit(fDefaultFileName[Idx]) - and (fDefaultFileName[Idx] <> '_') then - fDefaultFileName[Idx] := '_'; - Assert((fDefaultFileName <> '') and IsValidIdent(fDefaultFileName), - ClassName + '.SetFileName: Not a valid identifier'); + if fRequirePascalDefFileName then + begin + // convert to "camel" case + fDefaultFileName := StrStripWhiteSpace(StrCapitaliseWords(Value)); + // replaces invalid Pascal identifier characters with underscore + if (fDefaultFileName <> '') + and not TCharacter.IsLetter(fDefaultFileName[1]) + and (fDefaultFileName[1] <> '_') then + fDefaultFileName[1] := '_'; + for Idx := 2 to Length(fDefaultFileName) do + if not TCharacter.IsLetterOrDigit(fDefaultFileName[Idx]) + and (fDefaultFileName[Idx] <> '_') then + fDefaultFileName[Idx] := '_'; + Assert((fDefaultFileName <> '') and IsValidIdent(fDefaultFileName), + ClassName + '.SetFileName: Not a valid identifier'); + end + else + fDefaultFileName := Value; end; procedure TSourceFileInfo.SetFileTypeInfo(const FileType: TSourceFileType; From 1eb2d60298c9ce9a9d74800528d555730eb09186 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Tue, 29 Apr 2025 10:51:04 +0100 Subject: [PATCH 309/330] Update help and file format docs for snippet files. The help topics for the dialogue boxes displayed by the File menu options Save Annotated Source, Save Unit and Save Snippet Information were updated to reflect the changes made in issue 166. The documentation of saved file formats in Docs/Design/FileFormats/saved.html was similarly updated. --- Docs/Design/FileFormats/saved.html | 33 +++++++++++++++++++++++------- Src/Help/HTML/dlg_saveinfo.htm | 13 ++++++------ Src/Help/HTML/dlg_savesnippet.htm | 19 ++++++++++------- Src/Help/HTML/dlg_saveunit.htm | 19 ++++++++++------- 4 files changed, 56 insertions(+), 28 deletions(-) diff --git a/Docs/Design/FileFormats/saved.html b/Docs/Design/FileFormats/saved.html index 3353269a3..7f9e6b571 100644 --- a/Docs/Design/FileFormats/saved.html +++ b/Docs/Design/FileFormats/saved.html @@ -51,13 +51,13 @@ <h2> <ol> <li> - By saving snippet information to file from the <em>File | Save Snippet Information</em> menu option. + By saving snippet information using the <em>File | Save Snippet Information</em> menu option. </li> <li> - By saving snippets to file from the <em>File | Save Snippet</em> menu option. + By saving snippets using the <em>File | Save Snippet</em> menu option. </li> <li> - By saving units to file from the <em>File | Save Unit</em> menu option. + By saving units using the <em>File | Save Unit</em> menu option. </li> </ol> @@ -115,7 +115,7 @@ <h2> </h2> <p> - The available 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: </p> @@ -132,10 +132,10 @@ <h2> UTF-8 </li> <li> - Unicode little endian (UTF16-LE) + UTF-16LE </li> <li> - Unicode big endian (UTF16-BE) + UTF-16BE </li> </ul> </dd> @@ -178,7 +178,26 @@ <h2> <dd> <ul class="squashed"> <li> - ASCII [for Snippet Information] or ANSI [otherwise]. Regardless of naming, ASCII format is always used. + ASCII + </li> + </ul> + </dd> + <dt> + Markdown + </dt> + <dd> + <ul class="squashed"> + <li> + ANSI (system default code page) + </li> + <li> + UTF-8 + </li> + <li> + UTF-16LE + </li> + <li> + UTF-16BE </li> </ul> </dd> diff --git a/Src/Help/HTML/dlg_saveinfo.htm b/Src/Help/HTML/dlg_saveinfo.htm index 59cbda798..e35745cdb 100644 --- a/Src/Help/HTML/dlg_saveinfo.htm +++ b/Src/Help/HTML/dlg_saveinfo.htm @@ -68,7 +68,8 @@ <h1> </p> <ul> <li> - <em>ANSI (Default)</em> – the system default ANSI encoding. + <em>ANSI Code Page <code>nnn</code></em> – ANSI encoding for the system default code page, + where <code>nnn</code> is the code page for the user's locale. Available as an option for plain text and Markdown file formats. </li> <li> @@ -77,14 +78,12 @@ <h1> as the only encoding available for HTML 5 and XHTML file formats. </li> <li> - <em>Unicode (Little Endian)</em> – UTF-16 LE encoding, with - BOM<sup>†</sup>. Available as an option for plain text files and Markdown - file formats. + <em>UTF-16 Little Endian</em> – UTF-16 LE encoding, with + BOM<sup>†</sup>. Available as an option for plain text and Markdown file formats. </li> <li> - <em>Unicode (Big Endian)</em> – UTF-16 BE encoding, with - BOM<sup>†</sup>. Available as an option for plain text files and Markdown - file formats. + <em>UTF-18 Big Endian</em> – UTF-16 BE encoding, with + BOM<sup>†</sup>. Available as an option for plain text and Markdown file formats. </li> <li> <em>ASCII</em> – The only encoding available for the rich text file. diff --git a/Src/Help/HTML/dlg_savesnippet.htm b/Src/Help/HTML/dlg_savesnippet.htm index 7ef34cb1b..3e8eba30a 100644 --- a/Src/Help/HTML/dlg_savesnippet.htm +++ b/Src/Help/HTML/dlg_savesnippet.htm @@ -104,29 +104,34 @@ <h1> <p> The output file encoding can be be specified in the <em>File Encoding</em> drop down list. Options vary depending on the file type. Some file types - support only a single encoding. The encodings are: + support only a single encoding, in which case the drop down list will be + disabled. The encodings are: </p> <ul> <li> - <em>ANSI (Default)</em> – the system default ANSI encoding. - Available for both plain text and Pascal include files and as the only - option for rich text files. + <em>ANSI Code Page <code>nnn</code></em> – ANSI encoding for the system default code page, + where <code>nnn</code> is the code page for the user's locale. + Available for both plain text and Pascal include files. </li> <li> <em>UTF-8</em> – UTF-8 encoding, with BOM<sup>†</sup>. Available for both plain text and Pascal include files and as the only - option for XHTML files. If used for Pascal include files be warned that + 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. </li> <li> - <em>Unicode (Little Endian)</em> – UTF-16 LE encoding, with + <em>UTF-16 Little Endian</em> – UTF-16 LE encoding, with BOM<sup>†</sup>. Available for plain text files only. </li> <li> - <em>Unicode (Big Endian)</em> – UTF-16 BE encoding, with + <em>UTF-18 Big Endian</em> – UTF-16 BE encoding, with BOM<sup>†</sup>. Available for plain text files only. </li> + <li> + <em>ASCII</em> – ASCII encoding. Available as the only option for + rich text files. + </li> </ul> <p> The output can be previewed by clicking the <em>Preview</em> button. This diff --git a/Src/Help/HTML/dlg_saveunit.htm b/Src/Help/HTML/dlg_saveunit.htm index 3691a8e44..22c3c7253 100644 --- a/Src/Help/HTML/dlg_saveunit.htm +++ b/Src/Help/HTML/dlg_saveunit.htm @@ -89,29 +89,34 @@ <h1> <p> The output file encoding can be be specified in the <em>File Encoding</em> drop down list. Options vary depending on the file type. Some file types - support only a single encoding. The encodings are: + support only a single encoding, in which case the drop down list will be + disabled. The encodings are: </p> <ul> <li> - <em>ANSI (Default)</em> – the system default ANSI encoding. - Available for both plain text and Pascal unit files and as the only - option for rich text files. + <em>ANSI Code Page <code>nnn</code></em> – ANSI encoding for the system default code page, + where <code>nnn</code> is the code page for the user's locale. + Available for both plain text and Pascal unit files. </li> <li> <em>UTF-8</em> – UTF-8 encoding, with BOM<sup>†</sup>. Available for both plain text and Pascal unit files and as the only - option for XHTML files. If used for Pascal units be warned that the + 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. </li> <li> - <em>Unicode (Little Endian)</em> – UTF-16 LE encoding, with + <em>UTF-16 Little Endian</em> – UTF-16 LE encoding, with BOM<sup>†</sup>. Available for plain text files only. </li> <li> - <em>Unicode (Big Endian)</em> – UTF-16 BE encoding, with + <em>UTF-18 Big Endian</em> – UTF-16 BE encoding, with BOM<sup>†</sup>. Available for plain text files only. </li> + <li> + <em>ASCII</em> – ASCII encoding. Available as the only option for + rich text files. + </li> </ul> <p> The output can be previewed by clicking the <em>Preview</em> button. This From 8c5a17dc674eb42d59bd5dde083a6703a99d4bf9 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Tue, 29 Apr 2025 20:09:18 +0100 Subject: [PATCH 310/330] Change CodeSnip blog URL to DelphiDabbler blog Changed URL that used to address the CodeSnip Blog to address the DelphiDabbler blog instead. --- Src/UUrl.pas | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Src/UUrl.pas b/Src/UUrl.pas index 17c1eb90b..aca0b4400 100644 --- a/Src/UUrl.pas +++ b/Src/UUrl.pas @@ -53,8 +53,9 @@ TURL = record /// hosted.</summary> SWAGReleases = SWAGRepo + '/releases'; - /// <summary>URL of the the CodeSnip blog.</summary> - CodeSnipBlog = 'https://codesnip-app.blogspot.com/'; + /// <summary>URL of the DelphiDabbler blog containing CodeSnip news. + /// </summary> + CodeSnipBlog = 'https://delphidabbler.blogspot.com/'; end; From aa76c4a218ffe56b84f92162f9b4bebd363ac10e Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Tue, 29 Apr 2025 20:10:50 +0100 Subject: [PATCH 311/330] Change reference to CodeSnip blog in UI Changed Welcome page, Help menu and What's New dialogue box content to now link to the DelphiDabbler blog instead of the CodeSnip blog. Th text displayed was changed to suit. --- Src/FmMain.dfm | 6 +++--- Src/Res/HTML/dlg-whatsnew.html | 6 +++--- Src/Res/HTML/welcome-tplt.html | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Src/FmMain.dfm b/Src/FmMain.dfm index 6f460ff96..5b2eab657 100644 --- a/Src/FmMain.dfm +++ b/Src/FmMain.dfm @@ -862,10 +862,10 @@ inherited MainForm: TMainForm end object actBlog: TBrowseURL Category = 'Help' - Caption = 'CodeSnip News Blog' + Caption = 'CodeSnip News On DelphiDabbler Blog' Hint = - 'Display CodeSnip news blog|Display the CodeSnip News Blog in the' + - ' default web browser' + 'Display CodeSnip news|Display the DelphiDabbler blog, containing' + + ' CodeSnip news, in the default web browser' ImageIndex = 6 end object actDeleteUserDatabase: TAction diff --git a/Src/Res/HTML/dlg-whatsnew.html b/Src/Res/HTML/dlg-whatsnew.html index 9b06c251c..3667ce009 100644 --- a/Src/Res/HTML/dlg-whatsnew.html +++ b/Src/Res/HTML/dlg-whatsnew.html @@ -63,11 +63,11 @@ You can no longer submit snippets for inclusion in the DelphiDabbler Code Snippets Database. </li> <li> - The news feed has gone away. News will now be posted to the + The news feed has gone away. News will now be posted to the <strike>CodeSnip blog</strike> <a - href="https://codesnip-app.blogspot.com/" + href="https://delphidabbler.blogspot.com/" class="external-link" - >CodeSnip blog</a>. You can display the blog in your web browser from the <em>Help</em> menu. + >DelphiDabbler blog</a>. You can display the blog in your web browser from the <em>Help</em> menu. </li> </ul> <p> diff --git a/Src/Res/HTML/welcome-tplt.html b/Src/Res/HTML/welcome-tplt.html index 189d82951..55a23c116 100644 --- a/Src/Res/HTML/welcome-tplt.html +++ b/Src/Res/HTML/welcome-tplt.html @@ -189,7 +189,7 @@ <h1> href="#" class="command-link" onclick="showNews();return false;" - >News Blog</a> + >News On DelphiDabbler Blog</a> | <a href="#" From 41f0a54d69d80d8cef139057c37428473813d0e3 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Tue, 29 Apr 2025 20:21:50 +0100 Subject: [PATCH 312/330] Update help file re change of linked blog Changed the Help menu topic re the change of name and function of the menu's blog link. --- Src/Help/HTML/menu_help.htm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Src/Help/HTML/menu_help.htm b/Src/Help/HTML/menu_help.htm index d67348647..8ecd783f0 100644 --- a/Src/Help/HTML/menu_help.htm +++ b/Src/Help/HTML/menu_help.htm @@ -97,14 +97,14 @@ <h1> <img alt="Menu icon" src="../images/WebLink.png" class="glyph"> </td> <td class="item"> - CodeSnip News Blog + CodeSnip News On DelphiDabbler Blog </td> <td class="desc"> Displays the <a class="weblink" href="https://codesnip-app.blogspot.com/" target="_blank" - >CodeSnip Blog</a> in the default web browser. The latest news about <em>CodeSnip</em> is posted in the blog. + >DelphiDabbler Blog</a> in the default web browser. The latest news about <em>CodeSnip</em> is posted in this blog. </td> </tr> <tr> From 1b1f1b0fc29e18cf56f88562886e98756640c937 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Tue, 29 Apr 2025 20:23:17 +0100 Subject: [PATCH 313/330] Update docs re change of linked blog The main README.md along with Docs/ReadMe-portable.txt and Docs/ReadMe-standard.txt were updated re the change of the linked blog from the CodeSnip blog to the DelphiDabbler blog. --- Docs/ReadMe-portable.txt | 4 ++-- Docs/ReadMe-standard.txt | 4 ++-- README.md | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Docs/ReadMe-portable.txt b/Docs/ReadMe-portable.txt index e0883fa5c..de9019283 100644 --- a/Docs/ReadMe-portable.txt +++ b/Docs/ReadMe-portable.txt @@ -144,8 +144,8 @@ Updating the Program Updates are published on GitHub. See https://github.com/delphidabbler/codesnip/releases -News of new updates is published on the CodeSnip Blog: -https://codesnip-app.blogspot.com/. +News of new updates is published on the DelphiDabbler Blog: +https://delphidabbler.blogspot.com/. Known Installation and Upgrading Issues diff --git a/Docs/ReadMe-standard.txt b/Docs/ReadMe-standard.txt index 5f5ea703f..f1ec09250 100644 --- a/Docs/ReadMe-standard.txt +++ b/Docs/ReadMe-standard.txt @@ -179,8 +179,8 @@ Updating the Program Updates are published on GitHub. See https://github.com/delphidabbler/codesnip/releases -News of new updates is published on the CodeSnip Blog: -https://codesnip-app.blogspot.com/. +News of new updates is published on the DelphiDabbler Blog: +https://delphidabbler.blogspot.com/. Known Installation and Upgrading Issues diff --git a/README.md b/README.md index 3787b2439..4110004a2 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ The following support is available to CodeSnip users: * A comprehensive help file. * 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 [CodeSnip Blog](https://codesnip-app.blogspot.co.uk/). +* 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. From 67fe88c23b904ca2794cf38cb6b483b3b2c4beb8 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 30 Apr 2025 10:00:02 +0100 Subject: [PATCH 314/330] Add new CommentsInUnitImpl preferences property Added CommentsInUnitImpl property to IPreferences and its implementation in TPreferences. This preference determines whether descriptive comments are included in the implementation section of generated units. --- Src/UPreferences.pas | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/Src/UPreferences.pas b/Src/UPreferences.pas index 26a412804..8bb265ec7 100644 --- a/Src/UPreferences.pas +++ b/Src/UPreferences.pas @@ -76,6 +76,17 @@ interface property TruncateSourceComments: Boolean read GetTruncateSourceComments write SetTruncateSourceComments; + /// <summary>Gets flag that determines whether source code comments are + /// repeated in a generated unit's implementation section.</summary> + function GetCommentsInUnitImpl: Boolean; + /// <summary>Sets flag that determines whether source code comments are + /// repeated in a generated unit's implementation section.</summary> + procedure SetCommentsInUnitImpl(const Value: Boolean); + /// <summary>Flag deteminining whether source code comments are repeated in + /// a generated unit's implementation section.</summary> + property CommentsInUnitImpl: Boolean + read GetCommentsInUnitImpl write SetCommentsInUnitImpl; + /// <summary>Gets current default file extension / type used when writing /// code snippets to file.</summary> function GetSourceDefaultFileType: TSourceFileType; @@ -326,6 +337,9 @@ TPreferences = class(TInterfacedObject, /// <summary>Flag determining whether multi-paragraph source code is /// truncated to first paragraph in source code comments.</summary> fTruncateSourceComments: Boolean; + /// <summary>Flag deteminining whether source code comments are repeated + /// in a generated unit's implementation section.</summary> + fCommentsInUnitImpl: Boolean; /// <summary>Indicates whether generated source is highlighted by /// default.</summary> fSourceSyntaxHilited: Boolean; @@ -426,6 +440,16 @@ TPreferences = class(TInterfacedObject, /// <remarks>Method of IPreferences.</remarks> procedure SetTruncateSourceComments(const Value: Boolean); + /// <summary>Gets flag that determines whether source code comments are + /// repeated in a generated unit's implementation section.</summary> + /// <remarks>Method of IPreferences.</remarks> + function GetCommentsInUnitImpl: Boolean; + + /// <summary>Sets flag that determines whether source code comments are + /// repeated in a generated unit's implementation section.</summary> + /// <remarks>Method of IPreferences.</remarks> + procedure SetCommentsInUnitImpl(const Value: Boolean); + /// <summary>Gets current default file extension / type used when writing /// code snippets to file.</summary> /// <remarks>Method of IPreferences.</remarks> @@ -690,6 +714,7 @@ procedure TPreferences.Assign(const Src: IInterface); Self.fSourceDefaultFileType := SrcPref.SourceDefaultFileType; Self.fSourceCommentStyle := SrcPref.SourceCommentStyle; Self.fTruncateSourceComments := SrcPref.TruncateSourceComments; + Self.fCommentsInUnitImpl := SrcPref.CommentsInUnitImpl; Self.fSourceSyntaxHilited := SrcPref.SourceSyntaxHilited; Self.fMeasurementUnits := SrcPref.MeasurementUnits; Self.fOverviewStartState := SrcPref.OverviewStartState; @@ -741,6 +766,11 @@ destructor TPreferences.Destroy; inherited; end; +function TPreferences.GetCommentsInUnitImpl: Boolean; +begin + Result := fCommentsInUnitImpl; +end; + function TPreferences.GetCustomHiliteColours: IStringList; begin Result := fHiliteCustomColours; @@ -852,6 +882,11 @@ function TPreferences.GetWarnings: IWarnings; Result := fWarnings; end; +procedure TPreferences.SetCommentsInUnitImpl(const Value: Boolean); +begin + fCommentsInUnitImpl := Value; +end; + procedure TPreferences.SetCustomHiliteColours(const Colours: IStringList); begin fHiliteCustomColours := Colours; @@ -985,6 +1020,7 @@ function TPreferencesPersist.Clone: IInterface; NewPref.SourceDefaultFileType := Self.fSourceDefaultFileType; NewPref.SourceCommentStyle := Self.fSourceCommentStyle; NewPref.TruncateSourceComments := Self.fTruncateSourceComments; + NewPref.CommentsInUnitImpl := Self.fCommentsInUnitImpl; NewPref.SourceSyntaxHilited := Self.fSourceSyntaxHilited; NewPref.MeasurementUnits := Self.fMeasurementUnits; NewPref.OverviewStartState := Self.fOverviewStartState; @@ -1069,6 +1105,7 @@ constructor TPreferencesPersist.Create; Storage.GetInteger('CommentStyle', Ord(csAfter)) ); fTruncateSourceComments := Storage.GetBoolean('TruncateComments', False); + fCommentsInUnitImpl := Storage.GetBoolean('UseCommentsInUnitImpl', True); fSourceSyntaxHilited := Storage.GetBoolean('UseSyntaxHiliting', False); // Read printing section @@ -1151,6 +1188,7 @@ destructor TPreferencesPersist.Destroy; Storage.SetInteger('FileType', Ord(fSourceDefaultFileType)); Storage.SetInteger('CommentStyle', Ord(fSourceCommentStyle)); Storage.SetBoolean('TruncateComments', fTruncateSourceComments); + Storage.SetBoolean('UseCommentsInUnitImpl', fCommentsInUnitImpl); Storage.SetBoolean('UseSyntaxHiliting', fSourceSyntaxHilited); Storage.Save; From 7ff032740b02126d54f9c5b78c502ba819f66a23 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 30 Apr 2025 10:00:30 +0100 Subject: [PATCH 315/330] Add option to omit comments from unit impl sections TSourceGen.UnitAsString was given an additional Boolean parameter that determines whether or not descriptive comments (that are written to the unit interface) are repeated in the implementation section. Modified TSaveUnitMgr.GenerateSource to add the required additional parameters, the value of which is obtained from preferences. --- Src/USaveUnitMgr.pas | 7 ++++++- Src/USourceGen.pas | 43 ++++++++++++++++++++++++++++++------------- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/Src/USaveUnitMgr.pas b/Src/USaveUnitMgr.pas index 1901952a4..930efb9ea 100644 --- a/Src/USaveUnitMgr.pas +++ b/Src/USaveUnitMgr.pas @@ -99,6 +99,7 @@ implementation DB.UMetaData, UAppInfo, UConsts, + UPreferences, UUrl, UUtils; @@ -215,7 +216,11 @@ function TSaveUnitMgr.GenerateSource(const CommentStyle: TCommentStyle; const TruncateComments: Boolean): string; begin Result := fSourceGen.UnitAsString( - UnitName, CommentStyle, TruncateComments, CreateHeaderComments + UnitName, + CommentStyle, + TruncateComments, + Preferences.TruncateSourceComments, + CreateHeaderComments ); end; diff --git a/Src/USourceGen.pas b/Src/USourceGen.pas index 3d9edf2a7..23093fc7a 100644 --- a/Src/USourceGen.pas +++ b/Src/USourceGen.pas @@ -198,18 +198,23 @@ TSourceGen = class(TObject) /// <summary>Generates source code of a Pascal unit containing all the /// specified snippets along with any other snippets that are required to /// compile the code.</summary> - /// <param name="UnitName">string [in] Name of unit.</param> - /// <param name="CommentStyle">TCommentStyle [in] Style of commenting used - /// in documenting snippets.</param> - /// <param name="TruncateComments">Boolean [in] Flag indicating whether or - /// not documentation comments are to be truncated at the end of the first - /// paragraph of multi-paragraph text.</param> - /// <param name="HeaderComments">IStringList [in] List of comments to be - /// included at top of unit.</param> - /// <returns>string. Unit source code.</returns> + /// <param name="UnitName"><c>string</c> [in] Name of unit.</param> + /// <param name="CommentStyle"><c>TCommentStyle</c> [in] Style of + /// commenting used in documenting snippets.</param> + /// <param name="TruncateComments"><c>Boolean</c> [in] Flag indicating + /// whether or not documentation comments are to be truncated at the end of + /// the first paragraph of multi-paragraph text.</param> + /// <param name="UseCommentsInImplmentation"><c>Boolean</c> [in] Flag + /// indicating whether or not comments are to be included in the + /// implementation section. Has no effect when <c>CommentStyle</c> = + /// <c>csNone</c>.</param> + /// <param name="HeaderComments"><c>IStringList</c> [in] List of comments + /// to be included at top of unit.</param> + /// <returns><c>string</c>. Unit source code.</returns> function UnitAsString(const UnitName: string; const CommentStyle: TCommentStyle = csNone; const TruncateComments: Boolean = False; + const UseCommentsInImplementation: Boolean = False; const HeaderComments: IStringList = nil): string; /// <summary>Generates source code of a Pascal include file containing all @@ -585,14 +590,23 @@ class function TSourceGen.IsFileNameValidUnitName(const FileName: string): function TSourceGen.UnitAsString(const UnitName: string; const CommentStyle: TCommentStyle = csNone; const TruncateComments: Boolean = False; + const UseCommentsInImplementation: Boolean = False; const HeaderComments: IStringList = nil): string; var - Writer: TStringBuilder; // used to build source code string - Snippet: TSnippet; // reference to a snippet object - Warnings: IWarnings; // object giving info about any inhibited warnings + Writer: TStringBuilder; // used to build source code string + Snippet: TSnippet; // reference to a snippet object + Warnings: IWarnings; // object giving info about any inhibited warnings + ImplCommentStyle: TCommentStyle; // style of comments in implementation begin + // Set comment style for implementation section + if UseCommentsInImplementation then + ImplCommentStyle := CommentStyle + else + ImplCommentStyle := csNone; + // Generate the unit data fSourceAnalyser.Generate; + // Create writer object onto string stream that receives output Writer := TStringBuilder.Create; try @@ -681,11 +695,14 @@ function TSourceGen.UnitAsString(const UnitName: string; for Snippet in fSourceAnalyser.AllRoutines do begin Writer.AppendLine( - TRoutineFormatter.FormatRoutine(CommentStyle, TruncateComments, Snippet) + TRoutineFormatter.FormatRoutine( + ImplCommentStyle, TruncateComments, Snippet + ) ); Writer.AppendLine; end; + // class & records-with-methods implementation source code for Snippet in fSourceAnalyser.TypesAndConsts do begin if Snippet.Kind = skClass then From c2556d91d848d9d2d9ffcee8b8edbeeec9c05832 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 30 Apr 2025 10:15:55 +0100 Subject: [PATCH 316/330] Document new UseCommentsInUnitImpl config file value This new value has been added to the [Prefs:SourceCode] section and stores the value of the new Preferences.CommentsInUnitImpl property. --- Docs/Design/FileFormats/config.html | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Docs/Design/FileFormats/config.html b/Docs/Design/FileFormats/config.html index d6a57c49a..57d636db5 100644 --- a/Docs/Design/FileFormats/config.html +++ b/Docs/Design/FileFormats/config.html @@ -1262,6 +1262,12 @@ <h4> <dd> Flag indicating whether multi-paragraph snippet descriptions are to be truncated to the first paragraph only in documentation comments. <code class="value">True</code> ⇒ truncate the description; <code class="value">False</code> ⇒ use the full description. </dd> + <dt> + <code class="key">UseCommentsInUnitImpl</code> (Boolean) + </dt> + <dd> + Flag indicating whether source code comments are repeated in a generated unit's implementation section. <code class="value">True</code> ⇒ emit comments in both the interface and implementation sections; <code class="value">False</code> ⇒ emit comments in the interface section only. + </dd> <dt> <code class="key">UseSyntaxHiliting</code> (Boolean) </dt> From b67c1fab90bc408651eca2ee7ce1eac8d93c2bee Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 30 Apr 2025 10:20:08 +0100 Subject: [PATCH 317/330] Update Code Formatting tab of Preferences dialogue Added new "Repeat comments in unit implementation section" check box to the FrSourcePrefs frame. This check box sets the value of the IPreferences.CommentsInUnitImpl property. Expanded the size of the preferences dialogue box to accommodate the increased size of the FrSourcePrefs frame. --- Src/FmPreferencesDlg.dfm | 13 ++++++------- Src/FrSourcePrefs.dfm | 18 +++++++++++++----- Src/FrSourcePrefs.pas | 12 +++++++++--- 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/Src/FmPreferencesDlg.dfm b/Src/FmPreferencesDlg.dfm index 02c3a5c19..d39f3b146 100644 --- a/Src/FmPreferencesDlg.dfm +++ b/Src/FmPreferencesDlg.dfm @@ -10,30 +10,29 @@ inherited PreferencesDlg: TPreferencesDlg TextHeight = 13 inherited pnlBody: TPanel Width = 609 - Height = 329 + Height = 353 ExplicitWidth = 609 - ExplicitHeight = 329 + ExplicitHeight = 353 object pcMain: TPageControl Left = 163 Top = 0 Width = 446 - Height = 329 + Height = 353 Align = alRight MultiLine = True TabOrder = 1 - ExplicitLeft = 159 - ExplicitHeight = 377 + ExplicitHeight = 329 end object lbPages: TListBox Left = 0 Top = 0 Width = 153 - Height = 329 + Height = 353 Align = alLeft ItemHeight = 13 TabOrder = 0 OnClick = lbPagesClick - ExplicitHeight = 377 + ExplicitHeight = 329 end end inherited btnOK: TButton 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 ab6cc70e9..c27caf5fa 100644 --- a/Src/FrSourcePrefs.pas +++ b/Src/FrSourcePrefs.pas @@ -43,6 +43,7 @@ TSourcePrefsFrame = class(TPrefsBaseFrame) lblCommentStyle: TLabel; lblSnippetFileType: TLabel; chkTruncateComments: TCheckBox; + chkUnitImplComments: TCheckBox; procedure cbCommentStyleChange(Sender: TObject); procedure cbSnippetFileTypeChange(Sender: TObject); strict private @@ -181,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; @@ -198,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; @@ -218,7 +222,7 @@ procedure TSourcePrefsFrame.ArrangeControls; TCtrlArranger.AlignLefts( [ cbCommentStyle, frmPreview, cbSnippetFileType, chkSyntaxHighlighting, - chkTruncateComments + chkTruncateComments, chkUnitImplComments ], Col2Left ); @@ -271,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; @@ -348,6 +353,7 @@ procedure TSourcePrefsFrame.UpdateControlState; chkSyntaxHighlighting.Enabled := TFileHiliter.IsHilitingSupported(GetSourceFileType); chkTruncateComments.Enabled := GetCommentStyle <> csNone; + chkUnitImplComments.Enabled := GetCommentStyle <> csNone; end; procedure TSourcePrefsFrame.UpdatePreview; From 46156b31c03aa99a7d992fc28257905fc34a2cf1 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 30 Apr 2025 10:24:18 +0100 Subject: [PATCH 318/330] Update help topic for Prefs Code Formatting tab Added info about the new "Repeat comments in unit implementation section" check box on the Code Formatting tab of the Preferences dialogue box. --- Src/Help/HTML/dlg_prefs_sourcecode.htm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Src/Help/HTML/dlg_prefs_sourcecode.htm b/Src/Help/HTML/dlg_prefs_sourcecode.htm index 199e3773c..b0ed0fef4 100644 --- a/Src/Help/HTML/dlg_prefs_sourcecode.htm +++ b/Src/Help/HTML/dlg_prefs_sourcecode.htm @@ -69,6 +69,12 @@ <h2> comment to use just the first paragraph of the snippet's description by ticking the <em>Truncate comments to one paragraph</em> check box. </p> + <p> + 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 <em>Repeat + comments in unit implementation section</em> check box. + </p> <p> <strong>Note:</strong> Descriptive comments are not applicable to <a href="snippet_freeform.htm">freeform</a> or From 220b6efc8bb870df93b2dc48f6753c47dbc74bf8 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 30 Apr 2025 10:40:06 +0100 Subject: [PATCH 319/330] Remove preferences dependency from USourceGen unit Modified TSourceGen.UnitAsString to get information aboute compiler warnings via a new parameters instead of from the Preferences object. This was the only dependency on the Preferences object in the unit. Calling code was modified to pass the required warnings to TSourceGen.UnitAsString as a parameter. This calling code now gets the value from the preferences object. Fixes #167 --- Src/USaveUnitMgr.pas | 1 + Src/USourceGen.pas | 26 ++++++++++++++++++-------- Src/UTestUnit.pas | 10 ++++++++-- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/Src/USaveUnitMgr.pas b/Src/USaveUnitMgr.pas index 930efb9ea..e94a17757 100644 --- a/Src/USaveUnitMgr.pas +++ b/Src/USaveUnitMgr.pas @@ -217,6 +217,7 @@ function TSaveUnitMgr.GenerateSource(const CommentStyle: TCommentStyle; begin Result := fSourceGen.UnitAsString( UnitName, + Preferences.Warnings, CommentStyle, TruncateComments, Preferences.TruncateSourceComments, diff --git a/Src/USourceGen.pas b/Src/USourceGen.pas index 23093fc7a..32597cf6e 100644 --- a/Src/USourceGen.pas +++ b/Src/USourceGen.pas @@ -18,9 +18,14 @@ interface uses // Delphi - Classes, Generics.Collections, + Classes, + Generics.Collections, // Project - ActiveText.UMain, DB.USnippet, UBaseObjects, UIStringList; + ActiveText.UMain, + DB.USnippet, + UBaseObjects, + UIStringList, + UWarnings; type @@ -211,7 +216,7 @@ TSourceGen = class(TObject) /// <param name="HeaderComments"><c>IStringList</c> [in] List of comments /// to be included at top of unit.</param> /// <returns><c>string</c>. Unit source code.</returns> - function UnitAsString(const UnitName: string; + function UnitAsString(const UnitName: string; const Warnings: IWarnings; const CommentStyle: TCommentStyle = csNone; const TruncateComments: Boolean = False; const UseCommentsInImplementation: Boolean = False; @@ -255,10 +260,16 @@ implementation uses // Delphi - SysUtils, Character, + SysUtils, + Character, // Project - ActiveText.UTextRenderer, DB.USnippetKind, UConsts, UExceptions, UPreferences, - USnippetValidator, UStrUtils, UWarnings, Hiliter.UPasLexer; + ActiveText.UTextRenderer, + DB.USnippetKind, + UConsts, + UExceptions, + USnippetValidator, + UStrUtils, + Hiliter.UPasLexer; const @@ -588,6 +599,7 @@ class function TSourceGen.IsFileNameValidUnitName(const FileName: string): end; function TSourceGen.UnitAsString(const UnitName: string; + const Warnings: IWarnings; const CommentStyle: TCommentStyle = csNone; const TruncateComments: Boolean = False; const UseCommentsInImplementation: Boolean = False; @@ -595,7 +607,6 @@ function TSourceGen.UnitAsString(const UnitName: string; var Writer: TStringBuilder; // used to build source code string Snippet: TSnippet; // reference to a snippet object - Warnings: IWarnings; // object giving info about any inhibited warnings ImplCommentStyle: TCommentStyle; // style of comments in implementation begin // Set comment style for implementation section @@ -620,7 +631,6 @@ function TSourceGen.UnitAsString(const UnitName: string; Writer.AppendLine; // any conditional compilation symbols - Warnings := Preferences.Warnings; if Warnings.Enabled and not Warnings.IsEmpty then begin Writer.Append(Warnings.Render); diff --git a/Src/UTestUnit.pas b/Src/UTestUnit.pas index eef7d44c5..c34262c8f 100644 --- a/Src/UTestUnit.pas +++ b/Src/UTestUnit.pas @@ -65,7 +65,13 @@ implementation // Delphi SysUtils, // Project - DB.USnippetKind, UEncodings, UIOUtils, USourceGen, USystemInfo, UUnitAnalyser, + DB.USnippetKind, + UEncodings, + UIOUtils, + UPreferences, + USourceGen, + USystemInfo, + UUnitAnalyser, UUtils; @@ -89,7 +95,7 @@ function TTestUnit.GenerateUnitSource: string; Generator.IncludeSnippet(fSnippet); // Must use Self.UnitName below for Delphis that defined TObject.UnitName // otherwise the TObject version is used. - Result := Generator.UnitAsString(Self.UnitName); + Result := Generator.UnitAsString(Self.UnitName, Preferences.Warnings); finally Generator.Free; end; From 2019c809129c01e8107a8d85173b79065124477d Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 30 Apr 2025 19:48:43 +0100 Subject: [PATCH 320/330] Bump per-user config file version to 20 Incremented version number in FirstRun.UConfigFile unit so that first run of CodeSnip v 4.26.0 will record version 20 in the config file. Also updated config file docs to refer to version 20. --- Docs/Design/FileFormats/config.html | 4 ++-- Src/FirstRun.UConfigFile.pas | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Docs/Design/FileFormats/config.html b/Docs/Design/FileFormats/config.html index 57d636db5..915d7098f 100644 --- a/Docs/Design/FileFormats/config.html +++ b/Docs/Design/FileFormats/config.html @@ -167,7 +167,7 @@ <h3> </p> <p> - There have been several versions of this file. The current one is version 19. The change to version 19 came with CodeSnip v4.21.0 and the addition of the [Compilers] section and the <code class="key">CanAutoInstall</code> key in the [Cmp:XXX] sections. + 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 <code class="key">UseCommentsInUnitImpl</code> key in the <code>[Prefs:SourceCode]</code> section. </p> <p> @@ -771,7 +771,7 @@ <h4> 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 <code class="value">1</code>. </div> <div class="half-spaced"> - The current value is <code class="value">19</code>. + The current value is <code class="value">20</code>. </div> </dd> <dt> diff --git a/Src/FirstRun.UConfigFile.pas b/Src/FirstRun.UConfigFile.pas index 50bba121b..314eaaf62 100644 --- a/Src/FirstRun.UConfigFile.pas +++ b/Src/FirstRun.UConfigFile.pas @@ -82,7 +82,7 @@ TUserConfigFileUpdater = class(TConfigFileUpdater) strict private const /// <summary>Current user config file version.</summary> - FileVersion = 19; + FileVersion = 20; strict protected /// <summary>Returns current user config file version.</summary> class function GetFileVersion: Integer; override; From fd5f8c3944a8cc89b953ad0c8c7fc82310229319 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Wed, 30 Apr 2025 20:01:18 +0100 Subject: [PATCH 321/330] Added entry to Thanks section of readme text files Added thanks to SirRufo for the fix contributed to v4.25.0 --- Docs/ReadMe-portable.txt | 3 +++ Docs/ReadMe-standard.txt | 3 +++ 2 files changed, 6 insertions(+) diff --git a/Docs/ReadMe-portable.txt b/Docs/ReadMe-portable.txt index de9019283..c22134c23 100644 --- a/Docs/ReadMe-portable.txt +++ b/Docs/ReadMe-portable.txt @@ -248,6 +248,9 @@ Thanks to: + 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 diff --git a/Docs/ReadMe-standard.txt b/Docs/ReadMe-standard.txt index f1ec09250..97ac0577b 100644 --- a/Docs/ReadMe-standard.txt +++ b/Docs/ReadMe-standard.txt @@ -293,6 +293,9 @@ Thanks to: + 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. From 4fe44cda599dfbb43146c9d814e26c37dfff4ab6 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 1 May 2025 08:33:53 +0100 Subject: [PATCH 322/330] Bump version number to v4.26.0 build 276 --- Src/VersionInfo.vi-inc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Src/VersionInfo.vi-inc b/Src/VersionInfo.vi-inc index e23088aa2..82a6dfe24 100644 --- a/Src/VersionInfo.vi-inc +++ b/Src/VersionInfo.vi-inc @@ -1,8 +1,8 @@ # CodeSnip Version Information Macros for Including in .vi files # Version & build numbers -version=4.25.0 -build=275 +version=4.26.0 +build=276 # String file information copyright=Copyright © P.D.Johnson, 2005-<YEAR>. From 9dde2c64465c68e74e088fc8348e5b939ed2d4a3 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 2 May 2025 19:49:06 +0100 Subject: [PATCH 323/330] Update change log with details of release v4.26.0 --- CHANGELOG.md | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15b4636c7..d3fbdcf23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,40 @@ 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]. @@ -15,7 +49,7 @@ Releases are listed in reverse version number order. * 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]. + * 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]. From e73b12770398c1e24530d5aae9e71d8a84bf23b9 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Thu, 11 Sep 2025 20:14:44 +0100 Subject: [PATCH 324/330] Add support for test compilation with Delphi 13 Added support for test compiling with and automatic registration of Delphi 13. Added support for saving and reading Delphi 13 test compilation results from all supported file formats. Added support for specifying Delphi 13 as the minimum compiler on the Code Generation tab of the Preference dialogue box. --- Src/Compilers.UBDS.pas | 6 ++++++ Src/Compilers.UGlobals.pas | 3 ++- Src/DBIO.UIniDataReader.pas | 2 +- Src/FrCodeGenPrefs.pas | 1 + Src/UXMLDocConsts.pas | 2 +- 5 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Src/Compilers.UBDS.pas b/Src/Compilers.UBDS.pas index 509e0f993..7c999be6a 100644 --- a/Src/Compilers.UBDS.pas +++ b/Src/Compilers.UBDS.pas @@ -154,6 +154,8 @@ function TBDSCompiler.GetIDString: string; Result := 'D11A'; ciD12A: Result := 'D12Y'; + ciD13F: + Result := 'D13F'; else raise EBug.Create(ClassName + '.GetIDString: Invalid ID'); end; @@ -179,6 +181,7 @@ function TBDSCompiler.GetName: string; sDelphi104S = 'Delphi 10.4'; // Sydney sDelphi11A = 'Delphi 11.x'; // Alexandria sDelphi12A = 'Delphi 12.x'; // Athens + sDelphi13F = 'Delphi 13.x'; // Florence begin case GetID of ciDXE: @@ -211,6 +214,8 @@ function TBDSCompiler.GetName: string; Result := sDelphi11A; ciD12A: Result := sDelphi12A; + ciD13F: + Result := sDelphi13F; else Result := Format(sCompilerName, [ProductVersion]); end; @@ -246,6 +251,7 @@ function TBDSCompiler.InstallationRegKey: string; ciD104S : Result := '\Software\Embarcadero\BDS\21.0'; ciD11A : Result := '\Software\Embarcadero\BDS\22.0'; ciD12A : Result := '\Software\Embarcadero\BDS\23.0'; + ciD13F : Result := '\Software\Embarcadero\BDS\37.0'; else raise EBug.Create(ClassName + '.InstallationRegKey: Invalid ID'); end; end; diff --git a/Src/Compilers.UGlobals.pas b/Src/Compilers.UGlobals.pas index 7e660a166..be018b805 100644 --- a/Src/Compilers.UGlobals.pas +++ b/Src/Compilers.UGlobals.pas @@ -45,6 +45,7 @@ interface ciD104S, // Delphi 10.4 Sydney, ciD11A, // Delphi 11.x Alexandria ciD12A, // Delphi 12 Athens + ciD13F, // Delphi 13 Florence ciFPC // Free Pascal ); @@ -58,7 +59,7 @@ interface cBDSCompilers = [ ciD2005w32, ciD2006w32, ciD2007, ciD2009w32, ciD2010, ciDXE, ciDXE2, ciDXE3, ciDXE4, ciDXE5, ciDXE6, ciDXE7, ciDXE8, ciD10S, ciD101B, ciD102T, - ciD103R, ciD104S, ciD11A, ciD12A + ciD103R, ciD104S, ciD11A, ciD12A, ciD13F ]; const diff --git a/Src/DBIO.UIniDataReader.pas b/Src/DBIO.UIniDataReader.pas index 90b0c9657..0e6d97f40 100644 --- a/Src/DBIO.UIniDataReader.pas +++ b/Src/DBIO.UIniDataReader.pas @@ -236,7 +236,7 @@ implementation 'Delphi2010', 'DelphiXE', 'DelphiXE2', 'DelphiXE3', 'DelphiXE4', 'DelphiXE5', 'DelphiXE6', 'DelphiXE7', 'DelphiXE8', 'Delphi10S', 'Delphi101B', 'Delphi102T', 'Delphi103R', 'Delphi104S', 'Delphi11A', - 'Delphi12A', + 'Delphi12A', 'Delphi13F', 'FPC' ); diff --git a/Src/FrCodeGenPrefs.pas b/Src/FrCodeGenPrefs.pas index cff57d325..06fbf58a9 100644 --- a/Src/FrCodeGenPrefs.pas +++ b/Src/FrCodeGenPrefs.pas @@ -682,6 +682,7 @@ procedure TCodeGenPrefsFrame.PopulatePreDefCompilerMenu; AddMenuItem('Delphi 10.4 Sydney', 34.0); AddMenuItem('Delphi 11.x Alexandria', 35.0); AddMenuItem('Delphi 12 Athens', 36.0); + AddMenuItem('Delphi 13 Florence', 37.0); end; procedure TCodeGenPrefsFrame.PreDefCompilerMenuClick(Sender: TObject); diff --git a/Src/UXMLDocConsts.pas b/Src/UXMLDocConsts.pas index 122d13322..160692895 100644 --- a/Src/UXMLDocConsts.pas +++ b/Src/UXMLDocConsts.pas @@ -68,7 +68,7 @@ interface 'd2005', 'd2006', 'd2007', 'd2009', 'd2010', 'dXE', 'dXE2', 'dXE3', 'dDX4' {error, but in use so can't fix}, 'dXE5', 'dXE6', 'dXE7', 'dXE8', - 'd10s', 'd101b', 'd102t', 'd103r', 'd104s', 'd11a', 'd12y', + 'd10s', 'd101b', 'd102t', 'd103r', 'd104s', 'd11a', 'd12y', 'd13f', 'fpc' ); From ca233f01eadab6093f1ea89ec835c2b174519615 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 12 Sep 2025 08:18:36 +0100 Subject: [PATCH 325/330] Update ReadMe text files installed with CodeSnip ReadMe-portable.txt and ReadMe-standard.txt were updated to note support for Delphi 13 for test compilation. --- Docs/ReadMe-portable.txt | 2 +- Docs/ReadMe-standard.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Docs/ReadMe-portable.txt b/Docs/ReadMe-portable.txt index c22134c23..3365cb371 100644 --- a/Docs/ReadMe-portable.txt +++ b/Docs/ReadMe-portable.txt @@ -14,7 +14,7 @@ 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 +with each installed Win32 version of Delphi from Delphi 2 to Delphi 13.x and Free Pascal. Compilable Pascal units can be created that contain selected snippets. diff --git a/Docs/ReadMe-standard.txt b/Docs/ReadMe-standard.txt index 97ac0577b..4b84aaaca 100644 --- a/Docs/ReadMe-standard.txt +++ b/Docs/ReadMe-standard.txt @@ -14,7 +14,7 @@ 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 +with each installed Win32 version of Delphi from Delphi 2 to Delphi 13.x and Free Pascal. Compilable Pascal units can be created that contain selected snippets. From 81fcbda15924314ba271264b11cf5b7499ead56e Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 12 Sep 2025 08:22:15 +0100 Subject: [PATCH 326/330] Update file format docs Updated config file, export file, main database & user database documentation re addition of a new value to represent compilation with Delphi 13. --- Docs/Design/FileFormats/config.html | 9 +++++++++ Docs/Design/FileFormats/export.html | 11 +++++++++++ Docs/Design/FileFormats/main-db.html | 3 +++ Docs/Design/FileFormats/user-db.html | 9 +++++++++ 4 files changed, 32 insertions(+) diff --git a/Docs/Design/FileFormats/config.html b/Docs/Design/FileFormats/config.html index 915d7098f..9ff1ded44 100644 --- a/Docs/Design/FileFormats/config.html +++ b/Docs/Design/FileFormats/config.html @@ -262,6 +262,9 @@ <h4> <li> <em>D12Y</em> – Delphi 12 Athens </li> + <li> + <em>D13F</em> – Delphi 13 Florence + </li> <li> <em>FPC</em> – Free Pascal </li> @@ -622,6 +625,12 @@ <h4> <dd> Indicates whether Delphi 12 Athens was included in the search. </dd> + <dt> + <code class="key">D13F</code> (Boolean) + </dt> + <dd> + Indicates whether Delphi 13 Florence was included in the search. + </dd> <dt> <code class="key">FPC</code> (Boolean) </dt> diff --git a/Docs/Design/FileFormats/export.html b/Docs/Design/FileFormats/export.html index 7f6e80653..915b8d6ca 100644 --- a/Docs/Design/FileFormats/export.html +++ b/Docs/Design/FileFormats/export.html @@ -619,6 +619,9 @@ <h2> <li> <em>d12y</em> – Delphi 12 Athens compiler <span class="highlight">(v7.4 & later)</span> </li> + <li> + <em>d13y</em> – Delphi 13 Florence compiler <span class="highlight">(v8.1 & later)</span> + </li> <li> <em>fpc</em> – Free Pascal compiler <span class="highlight">(all versions)</span> </li> @@ -1011,6 +1014,14 @@ <h2> The <em>codesnip-export/routines/routine/xref</em> and <em>codesnip-export/routines/routine/xref/pascal-name</em> tags were removed from the specification. See <a href="#erratum">Erratum</a> above for details. </p> </dd> + <dl> + <dt> + <em>Version 8.1 - 11 September 2025</em> + </dt> + <dd> + Updated with CodeSnip v4.27.0 to add support for Delphi 13 Florence. + </dd> + </dl> </dl> </section> diff --git a/Docs/Design/FileFormats/main-db.html b/Docs/Design/FileFormats/main-db.html index 1b122069c..57cb9dd7c 100644 --- a/Docs/Design/FileFormats/main-db.html +++ b/Docs/Design/FileFormats/main-db.html @@ -422,6 +422,9 @@ <h4> <li> <code class="key">Delphi12A</code> – Delphi 12 Athens compiler * </li> + <li> + <code class="key">Delphi13F</code> – Delphi 13 Florence compiler * + </li> <li> <code class="key">FPC</code> – Free Pascal compiler </li> diff --git a/Docs/Design/FileFormats/user-db.html b/Docs/Design/FileFormats/user-db.html index d8d7773f0..fbcfa7a3f 100644 --- a/Docs/Design/FileFormats/user-db.html +++ b/Docs/Design/FileFormats/user-db.html @@ -641,6 +641,9 @@ <h3 id="xml-file"> <li> <em>d12y</em> – Delphi 12 Athens compiler <span class="highlight">(v6.12 & later)</span> </li> + <li> + <em>d13f</em> – Delphi 13 Florence compiler <span class="highlight">(v6.14 & later)</span> + </li> <li> <em>fpc</em> – Free Pascal compiler <span class="highlight">(all versions)</span> </li> @@ -1042,6 +1045,12 @@ <h2> <dd> Updated with CodeSnip v4.23.0 to add support for <a href="https://htmlpreview.github.io/?https://raw.githubusercontent.com/delphidabbler/reml/main/docs/reml-v6.html">REML v6</a>, which is backwards compatible with <a href="https://htmlpreview.github.io/?https://raw.githubusercontent.com/delphidabbler/reml/main/docs/reml-v4.html">REML v4</a>. </dd> + <dt> + Version 6.14 - 12 September 2025 + </dt> + <dd> + Updated in time for CodeSnip v4.27.0 to add support for Delphi 13 Florence. + </dd> </dl> </dd> </dl> From c34ddf2d6c6984858e5535a43688d5f211f0faeb Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 12 Sep 2025 08:24:11 +0100 Subject: [PATCH 327/330] Update help topics re support for Delphi 13 The about compiler checks topic and configure compilers dialogue box help topic were updated re the extension of support for test compilation to include Delphi 13. --- Src/Help/HTML/about_compiler_checks.htm | 2 +- Src/Help/HTML/dlg_configcompilers.htm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Src/Help/HTML/about_compiler_checks.htm b/Src/Help/HTML/about_compiler_checks.htm index ab4a5784f..d5970d617 100644 --- a/Src/Help/HTML/about_compiler_checks.htm +++ b/Src/Help/HTML/about_compiler_checks.htm @@ -34,7 +34,7 @@ <h1> </p> <p> The supported compilers are the Win32 Delphi compilers from Delphi 2 to - Delphi 12 Athens and Free Pascal. + Delphi 13 Florence and Free Pascal. </p> <h2> Configuring CodeSnip diff --git a/Src/Help/HTML/dlg_configcompilers.htm b/Src/Help/HTML/dlg_configcompilers.htm index 787f3bec5..5a5256730 100644 --- a/Src/Help/HTML/dlg_configcompilers.htm +++ b/Src/Help/HTML/dlg_configcompilers.htm @@ -312,7 +312,7 @@ <h2> </h2> <p> <em>CodeSnip</em> can automatically detect the presence of Win 32 Delphi - compilers from Delphi 2 to Delphi 12 Athens. Click the <em>Detect + compilers from Delphi 2 to Delphi 13 Florence. 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. From 1f84ac90ff91820af7d63e8e101f4aac09621e8f Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 12 Sep 2025 08:44:44 +0100 Subject: [PATCH 328/330] Bump version number to v4.27.0 build 277 --- Src/VersionInfo.vi-inc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Src/VersionInfo.vi-inc b/Src/VersionInfo.vi-inc index 82a6dfe24..1b0311bde 100644 --- a/Src/VersionInfo.vi-inc +++ b/Src/VersionInfo.vi-inc @@ -1,8 +1,8 @@ # CodeSnip Version Information Macros for Including in .vi files # Version & build numbers -version=4.26.0 -build=276 +version=4.27.0 +build=277 # String file information copyright=Copyright © P.D.Johnson, 2005-<YEAR>. From b0697b9c16e83c57d01fef37c51b32901ec2ddd8 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 12 Sep 2025 08:46:46 +0100 Subject: [PATCH 329/330] Update change log with details of release v4.27.0 --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3fbdcf23..e4bc0562b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ 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.27.0 of 12 September 2025 + +* Added support for test compiling snippets with Delphi 13 Florence [issue #170]. +* Documentation changes re addition of support for Delphi 13: + * File format additions for config file, export files, user database and main database. + * `Docs/ReadMe-portable.txt` & `Docs/ReadMe-standard.txt` + * Relevant help topics. + ## Release v4.26.0 of 02 May 2025 * Updated the dialogue box displayed when saving units and annotated source code [issue #166]: From f0f0b6b42f11d435f458d2563a46e86d23166da4 Mon Sep 17 00:00:00 2001 From: delphidabbler <5164283+delphidabbler@users.noreply.github.com> Date: Fri, 12 Sep 2025 09:14:15 +0100 Subject: [PATCH 330/330] Correct error in change log for release v4.21.1 Fixes #169 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4bc0562b..716973f89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -121,7 +121,7 @@ Hotfix release. ## 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: +* 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) (omitted from v4.21.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.