tag:blogger.com,1999:blog-33862480749802332622024-03-18T04:03:08.489+01:00Fixed by CodeSolutions, Code Memory and minor "Rants"Steffen Nyelandhttp://www.blogger.com/profile/08158042558199515350noreply@blogger.comBlogger54125tag:blogger.com,1999:blog-3386248074980233262.post-53231837740459047392024-01-13T18:16:00.000+01:002024-01-13T18:16:09.544+01:00Splitters Helpers<h3 style="text-align: left;"> - or how to find ways to add own helpers and keeping existing helpers.</h3><div class="separator" style="clear: both; text-align: center;"><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgI5AZKnxF4a8GyOeTPStmwjOMUHtFqf4q0FeDF01D3sSgK_RGs1TpBTqauaQOGv6Oowc5CrLqwus86X_ZCmyMMceRo3iSar2ZBQJHnFzFqMUrX1ogt967lSDYgXhjX680aaAA3aoHvm6NMf2Sn1nHl5GY1DIT_AQmPZ2nD2lPtLf-Rf2BJF71BGfsE475o/s587/TomQuote.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="300" data-original-width="587" height="205" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgI5AZKnxF4a8GyOeTPStmwjOMUHtFqf4q0FeDF01D3sSgK_RGs1TpBTqauaQOGv6Oowc5CrLqwus86X_ZCmyMMceRo3iSar2ZBQJHnFzFqMUrX1ogt967lSDYgXhjX680aaAA3aoHvm6NMf2Sn1nHl5GY1DIT_AQmPZ2nD2lPtLf-Rf2BJF71BGfsE475o/w400-h205/TomQuote.png" width="400" /></a></div><div class="separator" style="clear: both; text-align: center;"><br /></div><div>Wanted to do a pun on Finders Keepers, but failed - so this post is a sample on how to add your own record helper - in this case for the string type.</div><p>Delphi/Object Pascal does not allow for multiple helpers for the same type to be available at the same time.</p><p>So in my little example I wanted a string function that would split a string by a char, but up till a given max length.</p><p>A scenario for that use could be if you need to feed a system that has limited fixed size fields, spanning over more fields - CompanyName1, CampanyName2 only being 30 chars each. And for readability and UI, you also need to consider not to split mid-word.</p><p>To overcome the issue with the one helper active per type, defined your own matching type:</p><div style="text-align: left;"><span style="font-family: courier; font-size: x-small;">MyString = type string;</span></div><p>Define the new record helper for that type with its function:</p><div style="text-align: left;"><span style="font-family: courier; font-size: x-small;">MyStringHelper = record helper for MyString<br /> function SplitMaxProper(const ch: char; const len: Integer): TArray<string>;<br />end;</span></div><p>Since we also want to use the normal string helpers within the helper function, we need to do some casting when referring to the helpers type itself:</p><div style="text-align: left;"><span style="font-family: courier; font-size: x-small;">function MyStringHelper.SplitMaxProper(const ch: char; const len: Integer): TArray<string>;<br />var<br /> sl: TStringList;<br />begin<br /> var sidx := 0;<br /> var done := False;<br /> sl := TStringList.Create;<br /> try<br /> while (not done) do<br /> begin<br /> var delta := <b>string(</b>Self<b>)</b>.LastIndexOf(ch, sidx+len, len);<br /> if (delta = -1) or (<b>string(</b>Self<b>)</b>.Length-sidx <= len) then<br /> begin<br /> sl.Add(Trim(<b>string(</b>Self<b>)</b>.Substring(sidx)));<br /> done := True;<br /> end<br /> else<br /> sl.Add(Trim(<b>string(</b>Self<b>)</b>.Substring(sidx, delta-sidx)));<br /> sidx := delta;<br /> end;<br /> Result := sl.ToStringArray;<br /> finally<br /> sl.Free;<br /> end;<br />end;</span></div><p>And when using the new string helper we do need to cast to get to our new function:</p><div style="text-align: left;"><span style="font-family: courier; font-size: x-small;">procedure TForm6.btnSplitClick(Sender: TObject);<br />begin<br /> meSplitText.Clear;<br /> var len := StrToInt(edSplitLength.Text);<br /> var str := <b>MyString(</b>edTextToSplit.Text<b>)</b>;<br /> meSplitText.Lines.AddStrings(str.SplitMaxProper(#32, len));<br />end;</span></div><p>Disclaimer: The code is done so that it does fix better in the narrow width of my blog layout, and the function might also need some optimization :)</p><p>A note on the LastIndexOf string helper functions, the current official documentation seems to me a bit unclear:</p><p><i><code>StartIndex</code> specifies the offset in this 0-based string where the <a class="mw-selflink selflink">LastIndexOf</a> method begins the search, and <code>Count</code> specifies the end offset where the search ends.</i></p><p>Do remember that the search in this case, of cause goes from right to left - so StartIndex would normally be the length of the string.</p><p>I started with a Stephen King book title pun, so I should also comment on the quote used in the image shown - a quote from the brilliant author Tom Holt and the book The Portable Door - which is highly recommended, even if you have watched the movie adaptation, which is also good - but different - since adapting Tom Holt's books is no trivial task.</p><p>/Enjoy</p><p><br /></p>Steffen Nyelandhttp://www.blogger.com/profile/14576369608629841090noreply@blogger.com0tag:blogger.com,1999:blog-3386248074980233262.post-33739718272939654522024-01-02T16:26:00.006+01:002024-01-06T19:38:27.729+01:00Just Ping someone!<h3 style="text-align: left;">- or a component for Ping Identity's authentication within your Delphi application.</h3><div class="separator" style="clear: both; text-align: center;"><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhY0O14u1rPFO9cbVMTEJ23IWo-wVwdSQ7M3ksWS48qduhrycI31Ra_SyOCRU43yKIxQQ59ecDXZKad-nLogHhLLIGjdXo_ev3W8pPTJmzwT_LrjfiegbZhkAfdf8tCFwPLNunzIQ3oagb9LgZUUgZvJJw7N0Kl67JyOy0rDKq2pN6HkercBuAfa8aj95dT/s805/AuthTest1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="463" data-original-width="805" height="230" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhY0O14u1rPFO9cbVMTEJ23IWo-wVwdSQ7M3ksWS48qduhrycI31Ra_SyOCRU43yKIxQQ59ecDXZKad-nLogHhLLIGjdXo_ev3W8pPTJmzwT_LrjfiegbZhkAfdf8tCFwPLNunzIQ3oagb9LgZUUgZvJJw7N0Kl67JyOy0rDKq2pN6HkercBuAfa8aj95dT/w400-h230/AuthTest1.png" width="400" /></a></div><p>It has often been the rule that native applications either authenticate the user by current OS user or by an application-centric user and role model - but that might for multiple reasons not be good enough anymore.</p><p>The old AD or LDAP lookups are being replaced by cloud IAM platforms, to control and secure the authentication of the identity of the user.</p><p>After the user is authenticated (and is authorised "access" your application), the application-centric roles flow can continue as-is.</p><p>One of these IAM cloud provides is PingIdentity, and they have a fairly extensive Postman collection for their PingOne Platform API - found <a href="https://apidocs.ping dentity.com/pingone/main/v1/api/src/getting-started/download-the-pingone-postman-collections/" target="_blank"><b><span style="color: #2b00fe;">here</span></b></a>. Their developer documentation is also very useful - found <a href="https://apidocs.pingidentity.com/pingone/main/v1/api/#getting-started-with-the-pingone-apis" target="_blank"><b><span style="color: #2b00fe;">here</span></b></a>.</p><p>I have previously used various ways against various providers, but it seemed that when testing against PingOne with MFA enabled (Multi-Factor Authentication - which might include the annoying phone thing - that everyone uses) - I was hit by either a CORS issue or something else.</p><p>When using an OAuth2/OpenID Connect authentication, it does require that you setup and use a redirect uri, to tell the provider who is locally listening/waiting for the "response".</p><p>The idea with this type of authentication, is that the flow is handled securely within a web browser session, and the client does at no point in time know the login credentials - only when the user is authenticated do we need an "id token". So no "local" storage/handling of "passwords".</p><p>The listener part and possible timeouts has always annoyed me, so it seemed based on the flow, that I could handle it differently by just intercepting the local redirect call with the auth code - when the user is authenticated on the PingOne side - to get the id token needed.</p><h4 style="text-align: left;">Implementation</h4><p>The TPingOneAuth component is a descendant of standard TWebBrowser overriding the IDocHostUIHandler interface, and adding a some properties.</p><p>The GetHostInfo override is to ensure that all redirects in the browser component is triggering the OnBeforeNavigate2 event - which then on the redirect auth code call, will get the wanted id token.</p><div style="text-align: left;"><span style="font-family: courier; font-size: x-small;">function TPingOneAuth.GetHostInfo(var pInfo: TDocHostUIInfo): HRESULT;<br />begin<br /> pInfo.cbSize := SizeOf(pInfo);<br /> pInfo.dwFlags := 0;<br /> pInfo.dwFlags := pInfo.dwFlags or DOCHOSTUIFLAG_NO3DBORDER;<br /> pInfo.dwFlags := pInfo.dwFlags or DOCHOSTUIFLAG_THEME;<br /> pInfo.dwFlags := pInfo.dwFlags or DOCHOSTUIFLAG_ENABLE_REDIRECT_NOTIFICATION;<br /> Result := S_OK;<br />end;</span></div><p>The id token is then parsed using <a href="https://github.com/paolo-rossi/delphi-jose-jwt" target="_blank"><b><span style="color: #2b00fe;">Pablo Rossi's brilliant JOSE and JWT library</span></b></a>, that might as well have been done using a call to one of the PingOne's Token Introspection endpoints.</p><p>Since we do need an user id - the OpenID Connect scope must include <span style="font-family: courier;">profile</span> also.</p><p>To parse the custom claim a custom TJWTClaims class is added, containing the profile claims we want to read.</p><div style="text-align: left;"><span style="font-family: courier; font-size: x-small;">TPingOneClaims = class(TJWTClaims)<br /></span><span style="font-family: courier; font-size: x-small;">// Adding some given by the OpenID Connect scope: profile<br /></span><span style="font-family: courier; font-size: x-small;">private<br /></span><span style="font-family: courier; font-size: x-small;"> function GetPreferredUsername: string;<br /></span><span style="font-family: courier; font-size: x-small;"> procedure SetPreferredUsername(const Value: string);<br /></span><span style="font-family: courier; font-size: x-small;"> function GetGivenName: string;<br /></span><span style="font-family: courier; font-size: x-small;"> procedure SetGivenName(const Value: string);<br /></span><span style="font-family: courier; font-size: x-small;"> function GetFamilyName: string;<br /></span><span style="font-family: courier; font-size: x-small;"> procedure SetFamilyName(const Value: string);<br /></span><span style="font-family: courier; font-size: x-small;">public<br /></span><span style="font-family: courier; font-size: x-small;"> property PreferredUsername: string read GetPreferredUsername write SetPreferredUsername;<br /></span><span style="font-family: courier; font-size: x-small;"> property GivenName: string read GetGivenName write SetGivenName;<br /></span><span style="font-family: courier; font-size: x-small;"> property FamilyName: string read GetFamilyName write SetFamilyName;<br /></span><span style="font-family: courier; font-size: x-small;">end;</span></div><p style="text-align: left;"><br /></p><h4 style="text-align: left;">Usage</h4><p>Install the TPingOneAuth component in the Delphi IDE, and set the library path - business as usual.</p><p>There is a small sample application in the GitHub repo, but steps are at follows:</p><p>Drop or Create the TPingOneAuth on a form, setting following properties:</p><p></p><ul style="text-align: left;"><li>AuthEndpoint: /as/authorize</li><li>AuthPath: https://auth.pingone.eu/</li><li>ClientId:</li><li>ClientSecret:</li><li>EnvironmentId:</li><li>RedirectUri:</li><li>ResponseType: code</li><li>Scope: openid profile</li><li>TokenEndpoint: /as/token</li></ul><div>All these values are found in the PingOne console, under the application you setup as a "authorization" reference to your native Delphi application.</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxGzPNAfBgpMhIYc2Yq5JOSrmTQMDSHe70YmSXz447oqRbY8nNac0xBLRLJ4JwUj7PvWKYhW5DLmBzlBBfKnxFix1QN1frwaO0Rl4y03NqdPql5HwFbYynDR4TJFZNFSTppDXu3Q2zdP_CzMNnogz4cmfGDE5znVc1L8OQoUfyJezmCt4yQo6KauRry9gE/s1362/PingOne.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="844" data-original-width="1362" height="248" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxGzPNAfBgpMhIYc2Yq5JOSrmTQMDSHe70YmSXz447oqRbY8nNac0xBLRLJ4JwUj7PvWKYhW5DLmBzlBBfKnxFix1QN1frwaO0Rl4y03NqdPql5HwFbYynDR4TJFZNFSTppDXu3Q2zdP_CzMNnogz4cmfGDE5znVc1L8OQoUfyJezmCt4yQo6KauRry9gE/w400-h248/PingOne.png" width="400" /></a></div><br /><div>Add code to the OnAuthenticated (and OnDenied) event(s) - were on a successful authentication the public properties UserId and GreetName can be useful.</div><div><br /></div><div>To start the authentication just call the Authorize method - and with MFA enabled, you will see the prompt bellow, once you initially have given your credentials.</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2XHXal3aLU4eOkQTJ6Aud0KR4R9fmPfvkzVWi1vNdvJldAed9XU_X0ivqWAumFJX19y9MZP04FdPR3Lm4HrVgh4Xy9E7Et4lr1-j2vf8izX-NYv450_oDZxwSjqsZFUIaT-O2Vn_2Tcfi3DLi5Lmq2QSwKeetPJGivq2KMwID1EluB0-K3k9-YVtmfgIY/s805/AuthTest2.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="463" data-original-width="805" height="230" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2XHXal3aLU4eOkQTJ6Aud0KR4R9fmPfvkzVWi1vNdvJldAed9XU_X0ivqWAumFJX19y9MZP04FdPR3Lm4HrVgh4Xy9E7Et4lr1-j2vf8izX-NYv450_oDZxwSjqsZFUIaT-O2Vn_2Tcfi3DLi5Lmq2QSwKeetPJGivq2KMwID1EluB0-K3k9-YVtmfgIY/w400-h230/AuthTest2.png" width="400" /></a></div><div><br /></div>After finding your phone, and having swiped to authenticate in the PingOne mobile companion app, the OnAuthenticated event is called.<div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhNDyIWeXB7P54jh9uni6M208CQ-QfxwPMAi2IoQpLc5qAnzdXwGh2LcQiAowlgN0r5AqSKRRLVJxlSMfmQ0vijKjxRoMSaNtiWzaSQq-44Gk50IS21LWAOHA-XwW3VQFIE-HTkP8C4s6GthjNalizO0dEpsqpbmRlobIKd2V67d4y0MLx_VezCIbD1Nrn-/s804/AuthTest0.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="462" data-original-width="804" height="230" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhNDyIWeXB7P54jh9uni6M208CQ-QfxwPMAi2IoQpLc5qAnzdXwGh2LcQiAowlgN0r5AqSKRRLVJxlSMfmQ0vijKjxRoMSaNtiWzaSQq-44Gk50IS21LWAOHA-XwW3VQFIE-HTkP8C4s6GthjNalizO0dEpsqpbmRlobIKd2V67d4y0MLx_VezCIbD1Nrn-/w400-h230/AuthTest0.png" width="400" /></a></div><br /><div>The code for the component and the sample is found <a href="https://github.com/SteveNew/PingOneAuth" target="_blank"><b><span style="color: #2b00fe;">here</span></b></a>.</div><div><br /></div><div>A thing to note:</div><div><br /></div><div>When having MFA enabled - and you need to pair a device the first time around - the url Ping provides does contain some "strict mode" Javascript that IE will prompt you about twice, the solution to this is to set the SelectedEngine property to EdgeIfAvailable or EdgeOnly and deploy the EdgeView2 Runtime (WebView2Loader.dll) which can be found in GetIt as the EdgeView2 SDK.</div><div><br /></div><div>/Enjoy<br /><div><br /></div><div><br /></div><p></p><div><br /></div></div>Steffen Nyelandhttp://www.blogger.com/profile/14576369608629841090noreply@blogger.com0tag:blogger.com,1999:blog-3386248074980233262.post-80172831563880333602023-10-22T18:55:00.001+02:002023-10-22T18:56:08.649+02:00Use(s) less<p>- how to visualize your uses - for un-needed and wrong scoped units.</p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEioTha4G4tlpV5cwhvwl0N-lnepN95pvvVL9vlP_IeziT60uRewT5aaO1jxhoLmKppFLSRrk8_g9S1Wx2iXZDnS7m6UuHMuBhZSrmRPqQpwr7YTLyr9S6xc8KjvibbGW9zixZ27N7YoPzvgEgW4c8RPMIz5_pGTm_WRrwfpf-FyG2BA-cN4DvAHl3dGYuhq/s853/uses.png" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="227" data-original-width="853" height="170" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEioTha4G4tlpV5cwhvwl0N-lnepN95pvvVL9vlP_IeziT60uRewT5aaO1jxhoLmKppFLSRrk8_g9S1Wx2iXZDnS7m6UuHMuBhZSrmRPqQpwr7YTLyr9S6xc8KjvibbGW9zixZ27N7YoPzvgEgW4c8RPMIz5_pGTm_WRrwfpf-FyG2BA-cN4DvAHl3dGYuhq/w640-h170/uses.png" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">The "Uses" plugin installed and feed with a PAL report.</td></tr></tbody></table><div><b><br /></b></div><div><b>Disclaimer:</b> Even if the above picture shows my usage of the code in the current state - the code/plugin is by no means done and complete - but I put this out for others to tinker with and benefit from.</div><p>One of the things that has been on my list of wish's for the Delphi IDE since forever, has been the ability to identify which units in your uses clause is either not needed, or should/could be moved from the interface section to the implementation section.</p><p>There seems to be good reasons for that feature not to be a 100% correct in it assumption in what is needed and what could be moved, and that might also be the reason Embarcadero has not included that feature yet, but..</p><p>I only need something that is good enough, and that can help me make the decision on what to clean up.</p><p>There are some third-party tools that attempt to do that, one being Peganza's Pascal Analyzer - and the free <b><span style="color: #2b00fe;"><a href="https://www.peganza.com/download.html" target="_blank">Lite</a></span></b> version is sufficient to what I attempted to try here.</p><p>With the new CodeEditorEvents added to the OTAPI, it is now a lot easier to change the rendering of the code editor window in a safer way.</p><p>So this is what you need to do to get to a similar result - you need to download and install Pascal Analyzer (Lite).</p><p>Setup a project to handle your Delphi project, and you can optimizer the output from the analysis to only output the Uses.txt with only the Usage (USES1) - since that is the only needed.</p><p>I should mention that people more clever than me - could probably utilize the LSP or DelphiAST (or whatnot) - to make a more elegant solution - and they should :D.</p><p>So the prerequisite for the plugin is in place - a Uses.txt to parse. And to "refresh" the Uses.txt - you would have to run <span style="font-family: courier; font-size: x-small;">C:\Program Files\Peganza\Pascal Analyzer Lite\palcmd.exe <MyDocuments>\Pascal Analyzer Lite\Projects\<Myproject>.pap</span> again.</p><p>The plugin contains code to parse Uses.txt and created a dictionary where each unit/module has a list of flagged units.</p><p>That dictionary is used to get the uses statements for the current unit, so that the renderer can either gray out or strikeout the unit name in the Uses clauses'.</p><p>As mentioned, the state of the code is more a prof of concept and does include code that is more a comment and an intent to what I had planned at that point of time - but it has now been laying around since summer, and I will not get this perfected in foreseeable future so it will have a better life set free.</p><p>The code can be found here: <b><span style="color: #2b00fe;"><a href="https://github.com/SteveNew/CodeEditorPaintTextPAL">https://github.com/SteveNew/CodeEditorPaintTextPAL</a></span></b></p><p><br /></p>Steffen Nyelandhttp://www.blogger.com/profile/14576369608629841090noreply@blogger.com0tag:blogger.com,1999:blog-3386248074980233262.post-37787984039059923522023-02-18T14:58:00.003+01:002023-02-20T23:37:20.314+01:00IAP Client, therefore IAM<h3 style="text-align: left;">- or creating a simple Google IAP client using JOSE and a service account key file.</h3><p><br /></p><p>Google offers a massive amount of services and APIs to these, and I doubt anyone has the full overview unless they are in that domain naming space 24/7, and the same would be true for Amazon, Microsoft and other offerings.</p><p>In this example, I am using Googles IAP (Identity-Aware Proxy) to get an OIDC token that can be used to authorise the requests sent by a service account.</p><p>And a disclaimer: This is a conceptual example, for more secure and correct use one should not store any form of keys - so for that look into something like Workforce identity federation - which gives the same short-lived OIDC tokens, with the help of an identity provider. But for the purpose of the example - less is more.</p><p>First you would need to create or get the service account key file by using the Google Cloud console to create a new key for the service account, and download and store that in a safe place. The Grijjy guys did a similar example years back, where they used a PEM key based from the P12 file.</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEh9Fl47roUSxeT5mEbsajAkRLW2Gry5GaSICUIbf9W0hj6UU76bX3gQxhbIKlO4K1o9f3RKPhf8CucplNGgRCsfnpdNldmIHBKonEL_W6OUAnNdw0A7ALbQiGqZHPhGwL00LF7YvcPVNcLiEtA1OOzhkuRc42Zx6-HOp4gDdlVfcO6UPGCJJUVbc3yxYQ" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="500" data-original-width="868" height="184" src="https://blogger.googleusercontent.com/img/a/AVvXsEh9Fl47roUSxeT5mEbsajAkRLW2Gry5GaSICUIbf9W0hj6UU76bX3gQxhbIKlO4K1o9f3RKPhf8CucplNGgRCsfnpdNldmIHBKonEL_W6OUAnNdw0A7ALbQiGqZHPhGwL00LF7YvcPVNcLiEtA1OOzhkuRc42Zx6-HOp4gDdlVfcO6UPGCJJUVbc3yxYQ" width="320" /></a></div><p>I just created a class to load our json key into to extract the values needed, and by using the JOSE library, I did a customer header and claim/payload - which was signed as the example given by the bash shell script in Googles documentation here: <a href="https://cloud.google.com/iap/docs/authentication-howto#obtaining_an_oidc_token_from_a_local_service_account_key_file"><b>Get OpenID Connect Id token from key file</b></a>.<br /></p><p>Since the signing requires RS256, it does disqualify some of the other libraries, but JOSE does support that.</p><p></p><p>The Google API Client libraries and Cloud Client Libraries tend to use what they call the Application Default Credentials (ADC) strategy, and to mimic that, I added a bit of code that should cover the 3 desktop OSs.</p><p></p><p>You can read more about ADC here: <b><a href="https://cloud.google.com/docs/authentication/application-default-credentials">Google Application Default Credentials</a></b></p><p>One requirement of the signing is the OpenSSL libraries - JOSE does use these. You could of course also "just" use the EVP_DigestSignXXX functions from the SSLEAY library, and wrap what you needed, but JOSE does such a nice job of that, so why bother.</p><p>Adding a scope will give an access_token, whereas without it you will only get the id_token which is the OIDC token.</p><p>I did add an expiration check on the OIDC token, and it does seem to do the job of not having to request the token more than needed - but it might not be the perfect way.</p><p>The OIDC token is used as a bearer token for the actual requests to the service the proxy works for, so I added some HTTP methods as sample - and they just return an IHTTPResponse interface.</p><p>So an example of use would be something like:</p><div style="text-align: left;"><span style="font-family: courier;">uses<br /></span><span style="font-family: courier;"> System.Net.HttpClient,<br /></span><span style="font-family: courier;"> FbC.IAPClient;</span></div><div style="text-align: left;"><span style="font-family: courier;"><br /></span><span style="font-family: courier;">procedure TForm1.Button1Click(Sender: TObject);<br /></span><span style="font-family: courier;">const<br /></span><span style="font-family: courier;"> cSERVICE_ACCOUNT_KEY='lustrous-stack-342709-93cfcb2a8000.json';<br /></span><span style="font-family: courier;"> cIAP_CLIENT_ID='108584532133305403517';<br /></span><span style="font-family: courier;"> cURL='https://mytest.com';<br /></span><span style="font-family: courier;">var<br /></span><span style="font-family: courier;"> IAPClient: TIAPClient;<br /></span><span style="font-family: courier;">begin<br /></span><span style="font-family: courier;"> IAPClient := TIAPClient.Create(cSERVICE_ACCOUNT_KEY, cIAP_CLIENT_ID, cURL);<br /></span><span style="font-family: courier;"> try<br /></span><span style="font-family: courier;"> Memo1.Text := IAPClient.IdToken;<br /></span><span style="font-family: courier;">// Memo1.Text := IAPClient.Get('/companies').ContentAsString();<br /></span><span style="font-family: courier;"> finally<br /></span><span style="font-family: courier;"> IAPClient.Free;<br /></span><span style="font-family: courier;"> end;<br /></span><span style="font-family: courier;">end;</span></div><p style="text-align: left;">The IdToken property should not be public, and the client code is just meant as a starting point - since it does solve the painful bit - signing and authentication.</p><p>The code for the conceptual IAPClient can be found as a gist here: <a href="https://gist.github.com/SteveNew/ebcaf649a283afd65ef896c3fa898f7d"><b>Delphi Google IAPClient</b></a></p><p></p><p>Requirements are also:</p><p>JOSE library: <a href="https://github.com/paolo-rossi/delphi-jose-jwt"><b>https://github.com/paolo-rossi/delphi-jose-jwt</b></a></p><p>The appropriate OpenSSL dlls: <a href="https://github.com/IndySockets/OpenSSL-Binaries"><b>https://github.com/IndySockets/OpenSSL-Binaries</b></a></p><p>An implementation of TOAuth2Authenticator with this might have been a good idea, but ...</p><p>Well I hope it will at least get you on the right track, if you need to go through an IAP. There is no check for an unsuccessful attempt to get the OIDC token - it will just return an empty string - so that is meant as homework.</p><p>/Enjoy</p><p></p>Steffen Nyelandhttp://www.blogger.com/profile/14576369608629841090noreply@blogger.com0tag:blogger.com,1999:blog-3386248074980233262.post-67101669314665227492022-06-22T20:24:00.000+02:002022-06-22T20:24:53.282+02:00A SET up<h3 style="text-align: left;">- or how the for-in statement changed recently.</h3><div><br /></div><div>Before Delphi 10.4, the statement:</div><div><br /></div><div><span style="font-size: medium;"><span style="font-family: courier;">for integer in [] do</span> </span></div><div><br /></div><div>would operate on a set, but since then it now operates on an array. Which then also means a bit of different behaviour.</div><div><br /></div><div>I had missed this change completely, and only recently gotten aware of it since we had a build issue - mixing source from different IDE versions. (BTW: Do not do that).</div><div><br /></div><div>Since it previously worked on a set - the "list" when traversed would be an ordered list of values with an ordinal value no bigger than 255, and no more than 256 elements, that is now changed. I will list a few examples - illustrating the pre-10.4 behaviour compared to 10.4 and later.</div><div><br /></div><h3 style="text-align: left;">Duplicates and ordering</h3><div><br /></div><div><div><span style="font-family: courier; font-size: medium;">var</span></div><div><span style="font-family: courier; font-size: medium;"> i: integer;</span></div><div><span style="font-family: courier; font-size: medium;">begin</span></div><div><span style="font-family: courier; font-size: medium;"> for i in [42, 10, 42, 3, 42, 7] do</span></div><div><span style="font-family: courier; font-size: medium;"> WriteLn(i.ToString);</span></div><div><span style="font-family: courier; font-size: medium;">end.</span></div></div><div><span style="font-family: courier;"><br /></span></div><div>returns:</div><div><br /></div><div>10.4+: <span style="font-family: courier; font-size: medium;">42, 10, 42, 3, 42, 7</span></div><div>pre10.4: <span style="font-family: courier; font-size: medium;">3,7,10,42</span></div><div><br /></div><h3 style="text-align: left;">Range</h3><div><br /></div><div><div><span style="font-family: courier; font-size: medium;">var</span></div><div><span style="font-family: courier; font-size: medium;"> i: integer;</span></div><div><span style="font-family: courier; font-size: medium;">begin</span></div><div><span style="font-family: courier; font-size: medium;"> for i in [42, 365, MaxInt] do</span></div><div><span style="font-family: courier; font-size: medium;"> WriteLn(i.ToString);</span></div><div><span style="font-family: courier; font-size: medium;">end.</span></div></div><div><span style="font-family: courier;"><br /></span></div><div>10.4+: <span style="font-family: courier; font-size: medium;">42, 365, MaxInt</span></div><div>pre10.4: Does not compile - In 10.3 you get an E1012 Constant expression violates subrange bounds (earlier you might also get an E2024)</div><div><br /></div><div>To solve that you could replace it with something like:</div><div><br /></div><div><div><span style="font-family: courier; font-size: medium;">uses</span></div><div><span style="font-family: courier; font-size: medium;"> System.Types,</span></div><div><span style="font-family: courier; font-size: medium;"> System.SysUtils;</span></div><div><span style="font-family: courier; font-size: medium;"><br /></span></div><div><span style="font-family: courier; font-size: medium;">var</span></div><div><span style="font-family: courier; font-size: medium;"> i: integer;</span></div><div><span style="font-family: courier; font-size: medium;">begin</span></div><div><span style="font-family: courier; font-size: medium;"> for i in TIntegerDynArray.Create(42, 365, MaxInt) do</span></div><div><span style="font-family: courier; font-size: medium;"> WriteLn(i.ToString);</span></div><div><span style="font-family: courier; font-size: medium;">end.</span></div></div><div><br /></div><div>I do think the change is a step up, and does syntax-wise work more like one would expect, so a change for the greater good - but do be aware of the change of behaviour. Another benefit of a newer version is the usage of inline variable declaration and type inference, like in this case:</div><div><br /></div><div><div><span style="font-family: courier; font-size: medium;">for var i in [42, 10, 42, 3, 42, 7] do</span></div><div><span style="font-family: courier; font-size: medium;"> WriteLn(i.ToString);</span></div></div><div><br /></div><div>/Enjoy</div>Steffen Nyelandhttp://www.blogger.com/profile/14576369608629841090noreply@blogger.com0tag:blogger.com,1999:blog-3386248074980233262.post-48992521136870232322021-10-14T23:16:00.000+02:002021-10-14T23:16:01.040+02:00A Taste of WINE<h3 style="text-align: left;">- or how to detect if your Windows application is run under a flavor of WINE</h3><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiBDOoGWxJtkFBLtyVpG3mnXVsbqwTpPkNPpZbnw4O8BtyDxaqi-TEn9VX8sFwgfg4mXYPpnnmdxWtOUiwA-pEakdEYxlr1WJH8anxODdt4BHxTzdz8eAOr8j7Musz6O456dNVDSc7w_U9S/s750/Screenshot+from+2021-10-14+22-29-43.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="496" data-original-width="750" height="212" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiBDOoGWxJtkFBLtyVpG3mnXVsbqwTpPkNPpZbnw4O8BtyDxaqi-TEn9VX8sFwgfg4mXYPpnnmdxWtOUiwA-pEakdEYxlr1WJH8anxODdt4BHxTzdz8eAOr8j7Musz6O456dNVDSc7w_U9S/s320/Screenshot+from+2021-10-14+22-29-43.png" width="320" /></a></div><p></p><p>It has been a while since I mentioned what has been going on with Siege Of Avalon - and the short story is that I have been involved in getting the game re-released on GOG and Steam - the long story deserves a longer post another time.</p><p>We currently have a bigger update/patch planned - that would be the 4th - since its re-release in April 2021 - but it is not finalized what goes in and what not.</p><p>But one of the minor things, is trying to make the game more Steam Play friendly - so that basically just allowing custom ddraw.dll and disabling a few things when run under Wine/Proton - so that the game is playable without the need to jump through hoops like winetricks and protontricks.</p><p>Since these compatibility layers are meant to be transparent to the windows application, one would need to actually in some detect if running under Wine, luckily there is an extra bit in the NTDll.dll - the wine_get_version function.</p><p>So dynamically loading of the Dll, and try to get the address pointer of the function will reveal if your plain Delphi VCL Windows is run under Wine on Linux (or other Wine supported platforms).</p><p>The code for the simple test pictured:</p><div style="text-align: left;"><span style="font-family: courier;">procedure TForm10.Button1Click(Sender: TObject);<br /></span><span style="font-family: courier;">var<br /></span><span style="font-family: courier;"> dll: HMODULE;<br /></span><span style="font-family: courier;"> get_WINE_version: function: PAnsiChar;<br /></span><span style="font-family: courier;">begin<br /></span><span style="font-family: courier;"> dll := LoadLibrary('ntdll.dll');<br /></span><span style="font-family: courier;"> if dll<>0 then<br /></span><span style="font-family: courier;"> begin<br /></span><span style="font-family: courier;"> @get_WINE_version := GetProcAddress(dll, 'wine_get_version');<br /></span><span style="font-family: courier;"> if Assigned(@get_WINE_version) then<br /></span><span style="font-family: courier;"> Label1.Caption := 'Running under ' + get_WINE_version()<br /></span><span style="font-family: courier;"> else<br /></span><span style="font-family: courier;"> Label1.Caption := 'Pure ol'' Windows';<br /></span><span style="font-family: courier;"> FreeLibrary(dll);<br /></span><span style="font-family: courier;"> end;<br /></span><span style="font-family: courier;">end;</span></div><div><br /></div><p>This is by no means the real deal, but if it helps bringing an existing application run on a different platform and making it aware of that - then it might be helpful.</p><p>A more "correct" way to migrate existing VCL applications could be <a href="https://www.crossvcl.com/"><b>https://www.crossvcl.com</b></a>, but in this case that is not doable - since not a standard Delphi VCL application.</p><p>But as you can see below this detection short-cut can help create an experimental build of Siege Of Avalon that is running on Steam Play out of the box. AS-IS.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhK4kppsa3QV1QtL6UU8-BB_mPzrhREnNTU05xDRt31hgjIaVTD5Bv6ZDWSIRoucaHM5jNGyFsbjQX1s6R95iPRn3lkyLdj17fN7x-Eqc_JpaEa7cYBOxktiXrLJvk64frriJfZooCuDwO3/s1282/Screenshot+from+2021-10-14+23-07-56.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="759" data-original-width="1282" height="236" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhK4kppsa3QV1QtL6UU8-BB_mPzrhREnNTU05xDRt31hgjIaVTD5Bv6ZDWSIRoucaHM5jNGyFsbjQX1s6R95iPRn3lkyLdj17fN7x-Eqc_JpaEa7cYBOxktiXrLJvk64frriJfZooCuDwO3/w400-h236/Screenshot+from+2021-10-14+23-07-56.png" width="400" /></a></div><div style="text-align: center;">Siege of Avalon launched under Steam Play on Linux - also showing the new live-mapping.</div><p>Please support the nice publisher of this re-released classic old game, and their willingness to keep releasing the Delphi code of the GOG/Steam releases.</p><p>/Enjoy</p>Steffen Nyelandhttp://www.blogger.com/profile/14576369608629841090noreply@blogger.com0tag:blogger.com,1999:blog-3386248074980233262.post-89383647533520981722021-09-09T17:05:00.075+02:002021-09-09T21:17:44.657+02:00My favorite %11 new things in Delphi 11 Alexandria<h3 style="text-align: left;">- or all things "eleven", watch out for dialects.</h3><p>With the public release of Delphi 11 (and RADStudio and C++Builder) today, I would like to share my %11 initial favorites in the new release.</p><h4 style="text-align: left;">%01. Styled Form designer</h4><p>Together with the IDE HighDPI support, it is great that the gap between the running UI and the UI while designing has been reduced, and keeps in my opinion Delphi IDEs the best to design visual applications - whether it is on desktop OSs or mobile OSs.</p><h4 style="text-align: left;">%10. HTTP/2 support</h4><p>I am looking forward to start experimenting with this, and see how much benefit and performance it will give when taking advantage of fully.</p><h4 style="text-align: left;">%11. Remote Desktop Support improvement</h4><p>In this world where more people are coming to their senses, and wish to work remotely, focus on improving the IDE experience with remote connection is nice. And the things done can also benefit the applications build.</p><p><br /></p><p>But there are many other things that not even a list of $11 things would cover it, and I am really looking forward to also experience to the various optimizations and running the IDE in HighDPI on my old Surface Pro.</p><p>Go EMBT :)</p><p>There is one slightly annoying thing that might split the Delphi community - adding to the list of:</p><p>- Is Delphi pronounced /del·fai/ or /del·fee/?</p><p>- Does "then begin" go on together on the same line?</p><p>- FreeAndNil?</p><p>and now this to split the community further and create fractions:</p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="266" src="https://www.youtube.com/embed/BOUTfUmI8vs" width="320" youtube-src-id="BOUTfUmI8vs"></iframe></div><div class="separator" style="clear: both; text-align: center;"><br /></div><div class="separator" style="clear: both; text-align: center;">https://youtu.be/BOUTfUmI8vs</div><p>Well the above was just an excuse for me to include that funny Scottish sketch - no harm done I hope.</p><p>BTW: If you wonder about the % prefix - the it is the prefix for binary literals adding in Delphi 11.</p><p>/Enjoy</p><p><br /></p>Steffen Nyelandhttp://www.blogger.com/profile/14576369608629841090noreply@blogger.com0tag:blogger.com,1999:blog-3386248074980233262.post-71730421519158638122021-05-22T21:43:00.000+02:002021-05-22T21:43:11.753+02:00VAT's in it for me? - or I call my layer.<h3 style="text-align: left;"> - or a shout-out to the various services of apilayer.com</h3><div class="separator" style="clear: both; text-align: center;"><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1N5llfS5Hx2h2FlxxnWACG-X9KutjNXVChwwbEOYQRI7lSbleXJhZAau1hCIZQgjcirNmxWd-avFZrQKTy_bLNElyc5ZLjoz8dKeOVlV7V9qMnilXSR5Iq3-egJojfQTtTZQQUVQkWNdP/s854/CaptureVAT.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="822" data-original-width="854" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1N5llfS5Hx2h2FlxxnWACG-X9KutjNXVChwwbEOYQRI7lSbleXJhZAau1hCIZQgjcirNmxWd-avFZrQKTy_bLNElyc5ZLjoz8dKeOVlV7V9qMnilXSR5Iq3-egJojfQTtTZQQUVQkWNdP/s320/CaptureVAT.PNG" width="320" /></a></div><br /><div>Sorry about the intended pun in the title - but it will all make sense.</div><p>Have you ever wanted to know the various VAT rates in the EU - no? - well neither have I, because it might put me in a bad mood, but it fits the intended pun for the post, and I also wanted to show how it is done from from Delphi.</p><p>The mother company of Embarcadero, Idera acquired another company early this year - this time apilayer.com, which provides numerous "simple" cloud-based API services - among these are:</p><p></p><ul style="text-align: left;"><li>IP geolocation and reverse lookup</li><li>Language detection</li><li>Mail address validation</li><li>Phone number validation</li><li>Flight tracking</li><li>Currency conversion and rates (including crypto currency)</li><li>Weather data and forecast</li><li>News, Headline and Stock apis</li><li>Conversion PDF and scraping</li></ul><div>But go to <a href="https://apilayer.com/"><b><span style="color: #2b00fe;">https://apilayer.com/</span></b></a>, and read more about their various layers and tiers for these.</div><div><br /></div><div>We will in this short demo look at the vatlayer - which does EU VAT stuff.</div><div><br /></div><div>So I started by signing up for the free tier on <a href="https://vatlayer.com/"><b><span style="color: #2b00fe;">https://vatlayer.com/</span></b></a> - which gives you an API access key and access to a dashboard. As always do not share the access key - just saying.</div><div><br /></div><div>Now you can fire up Delphi, create a new application and either use the REST Debugger or just manually throw in the REST component or create runtime - what you prefer.</div><div><br /></div><div>I just threw in a edit control and a couple of memos and buttons, as seem above. And then I added an parameter on the RESTClient with the access_key - since I wanted to clear the request parameters on the RESTRequest, but keep the access_key. And set the BaseURL property to http://apilayer.net/api.</div><div><br /></div><div>I just took two of the methods/resources from <a href="https://vatlayer.com/documentation"><b><span style="color: #2b00fe;">https://vatlayer.com/documentation</span></b></a> - so the VAT lookup button looks like this:</div><p></p><div><span style="font-family: courier;">var</span></div><div><div><span style="font-family: courier;"> json: TJSONValue;</span></div><div><span style="font-family: courier;">begin</span></div><div><span style="font-family: courier;"> Memo1.Clear;</span></div><div><span style="font-family: courier;"> RESTRequest1.Method := rmGET;</span></div><div><span style="font-family: courier;"> RESTRequest1.Resource := 'validate';</span></div><div><span style="font-family: courier;"> RESTRequest1.Params.Clear;</span></div><div><span style="font-family: courier;"> RESTRequest1.AddParameter('vat_number', LabeledEdit2.Text, pkGETorPOST);</span></div><div><span style="font-family: courier;"> RESTRequest1.Execute;</span></div><div><span style="font-family: courier;"> if RESTResponse1.StatusCode=200 then</span></div><div><span style="font-family: courier;"> begin</span></div><div><span style="font-family: courier;"> json := RESTResponse1.JSONValue;</span></div><div><span style="font-family: courier;"> Memo1.Lines.Add(json.GetValue<string>('company_name'));</span></div><div><span style="font-family: courier;"> Memo1.Lines.Add(json.GetValue<string>('company_address'));</span></div><div><span style="font-family: courier;"> end;</span></div><div><span style="font-family: courier;">end;</span></div></div><p></p><div>..and the VAT rates for HU (since they have a higher VAT rate than DK :D) button looks like this:</div><p></p><div><span style="font-family: courier;">var</span></div><div><div><span style="font-family: courier;"> json: TJSONValue;</span></div><div><span style="font-family: courier;">begin</span></div><div><span style="font-family: courier;"> Memo2.Clear;</span></div><div><span style="font-family: courier;"> RESTRequest1.Method := rmGET;</span></div><div><span style="font-family: courier;"> RESTRequest1.Resource := 'rate';</span></div><div><span style="font-family: courier;"> RESTRequest1.Params.Clear;</span></div><div><span style="font-family: courier;"> RESTRequest1.AddParameter('country_code', 'HU', pkGETorPOST);</span></div><div><span style="font-family: courier;"> RESTRequest1.Execute;</span></div><div><span style="font-family: courier;"> if RESTResponse1.StatusCode=200 then</span></div><div><span style="font-family: courier;"> begin</span></div><div><span style="font-family: courier;"> json := RESTResponse1.JSONValue;</span></div><div><span style="font-family: courier;"> Memo2.Lines.Add(json.Format());</span></div><div><span style="font-family: courier;"> end;</span></div><div><span style="font-family: courier;">end;</span></div></div><p></p><div>There nice twists to these - by IP address or get list of rate types - and if combining these services by apilayer.com - I could make a speeding white-van "whistle-blower" mobile app - since all company owned registered vehicles in DK - need to have their VAT numbers on their fleet.</div><div><br /></div><div>So I could lookup the company and their address, get their phone number by a different api layer - and then call and tell on them - MUHAAHAAAHAA (evil laughter)</div><div><br /></div><div>..or maybe I should just create something useful and fun.</div><div><br /></div><div>And the best part about writing this post, I now learned that Denmark has only the second highest VAT rate in EU :D</div><div><br /></div><div>/Enjoy</div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><p></p>Steffen Nyelandhttp://www.blogger.com/profile/14576369608629841090noreply@blogger.com0tag:blogger.com,1999:blog-3386248074980233262.post-83904806268204556562021-02-15T19:00:00.000+01:002021-02-15T19:00:08.811+01:00Closing in on Modal Jump Lists<h3 style="text-align: left;">- or getting a WM_SYSCOMMAND to a modal dialog.</h3><p>An idea that initially seemed clever, has been haunting me a bit lately - I did put a modal dialog into a project before the main Application block (Initialize .. Run). Shame on me.</p><p>So the code looks similar to like this:</p><div style="text-align: left;"><span style="font-family: courier;">if (TfrmLaunchSetting.Execute = mrOK) then<br /></span><span style="font-family: courier;">begin<br /></span><span style="font-family: courier;"> Application.Initialize;<br /></span><span style="font-family: courier;"> Application.MainFormOnTaskbar := True;<br /></span><span style="font-family: courier;"> Application.Title := 'MyApplication';<br /></span><span style="font-family: courier;"> Application.CreateForm(TfrmMain, frmMain);<br /></span><span style="font-family: courier;"> Application.Run;<br /></span><span style="font-family: courier;">end;</span></div><p>and the TFrmLaunchSetting.Execute is just a class function returning the ModalResult, from its ShowModal.</p><p>But here comes the issue at hand - the Close Window in the task bars Jump List sends a WM_SYSCOMMAND that goes to the system menu in the main window - but if a modal form is shown the main window is disabled - and in this case I do not even have a main form yet.</p><p>So TApplication.HookMainWindow to the rescue - that would enable me to intercept messages sent to the main form - and act upon these.</p><p>So in the FormCreate of my modal dialog I hook the my hook function up like this:</p><div style="text-align: left;"><span style="font-family: courier;">Application.HookMainWindow(AppHookFunc);</span></div><p>And on FormDestroy I do the following:</p><div style="text-align: left;"><span style="font-family: courier;">Application.UnHookMainWindow(AppHookFunc);</span></div><p>The AppHookFunc goes as follows:</p><div style="text-align: left;"><span style="font-family: courier;">function TfrmLaunchSetting.AppHookFunc(var Message: TMessage): Boolean;<br />begin<br /> Result := False;<br /> if Message.Msg = WM_SYSCOMMAND then<br /> begin<br /> PostMessage(Handle, WM_CLOSE, 0, 0);<br /> Result := True;<br /> end;<br />end;</span></div><p>This way modal dialogs can react to Windows messages that normally be intended for the main form.</p><p>There are probably side-effects - but the issue at hand is solved - for now?</p><p>BTW: Yesterday was Delphi 26th birthday - I did not forget - but I am still wrapping up the present. Stay tuned.</p><p>/Enjoy</p>Steffen Nyelandhttp://www.blogger.com/profile/14576369608629841090noreply@blogger.com0tag:blogger.com,1999:blog-3386248074980233262.post-43873590268634666222020-12-30T19:14:00.003+01:002021-01-04T22:02:31.358+01:00It deploys and runs - what a Big Sur prize<h3 style="text-align: left;">- or the initial steps to get a Windows IDE to run cross-platform using CrossVCL.</h3><div class="separator" style="clear: both; text-align: center;"><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjJjlNrHggodH-xJpTiHxicxPy-IXT6AtsUQGoA8f3mmV_M16CyJNBBis8VcN6ac0WKGSZ7t6RG-DreSHaFLKoQUlVnWOoNpozOZ9d3vvk99xeXX7nhP4Bw16TmQUocgO0ZTH6jDXVP626_/s816/Sk%25C3%25A6rmbillede+2021-01-04+kl.+21.07.07.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="535" data-original-width="816" height="420" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjJjlNrHggodH-xJpTiHxicxPy-IXT6AtsUQGoA8f3mmV_M16CyJNBBis8VcN6ac0WKGSZ7t6RG-DreSHaFLKoQUlVnWOoNpozOZ9d3vvk99xeXX7nhP4Bw16TmQUocgO0ZTH6jDXVP626_/w640-h420/Sk%25C3%25A6rmbillede+2021-01-04+kl.+21.07.07.png" title="Dev-C++ starting on macOS 11.1 - Big Sur using CrossVCL" width="640" /></a></div><div><br /></div><p>Sorry about the pun, and I should also mention that this project is WIP, so no workable release/code yet.</p><p><b>Update:</b> In the original image above the language selection was broken/disabled. Read the update in the end of the post on the changes I decided to do to makes this work better overall.</p><p>A few days back was a new update of <a href="https://www.crossvcl.com/index.html" target="_blank"><b><span style="color: #2b00fe;">CrossVCL</span></b></a> announced in my email - for the lifetime license I had bought years earlier - which triggered an idea.</p><span><a name='more'></a></span><p>I hadn't really gotten around to use the product, but it could be nice to take it for a spin on some VCL project that would be great also to have running on Linux and macOS - since I do once again spend more time on a Linux desktop.</p><p>I do unfortunately not have the source code for my favorite IDE, and that would also be more than a one man job, and it would also result in a very amputated and lighter version - which would probably be great in many cases anyway.</p><p>But there are other open-sourced IDEs out there - PyScripter and Dev C++ could be great candidates for some CrossVCL adjustments.</p><p>CrossVCL is not an emulation, but a WinAPI implementation that is intended to cover the VCL/UI part, for the rest of the code the old Windows specific function should be replaced by their newer platform-agnostic variants. Like <span style="font-family: courier;">WinAPI.Windows.DeleteFile</span> should now be <span style="font-family: courier;">System.SysUtils.DeleteFile</span>.</p><div>I ended up forking Embarcadero Dev-C++ (<a href="https://github.com/Embarcadero/Dev-Cpp"><span style="color: #2b00fe;"><b>https://github.com/Embarcadero/Dev-Cpp</b></span></a>) - the nicely update version of the BloodShed Dev-C++ IDE.</div><p>My fork is still very much work in progress - and the Dev C++ source has some old code and some very Windows specific code, like the TClientSocket (unused) and TDDEServerConv - so these things needs to go - or be re-implemented in a different way.</p><p>I also temporarily removed some of the nicer new addition Embarcadero had made - like the extended styling and the use of SVG Icons - just to concentrate on the getting the basics up running in some form.</p><p>There are a few things to be aware of when updating Delphi to use CrossVCL - one is to remember to manually add the OpenGL framework on macOS - but read both the <b><a href="https://www.crossvcl.com/faq.html" target="_blank"><span style="color: #2b00fe;">FAQ</span></a></b>, <a href="https://www.crossvcl.com/guide.html" target="_blank"><b><span style="color: #2b00fe;">Getting Started</span></b></a> and the <a href="https://www.crossvcl.com/devguide.html" target="_blank"><b><span style="color: #2b00fe;">Dev Guide</span></b></a> first.</p><p>Start with deploying and debugging a simple "Hello World" example, via the normal PAServer setup given by Embarcadero - to ensure everything is working, also be aware that when switching platforms - some value might have been added to your version resources like the CFBundle entries, but I guess that is nothing CrossVCL specific.</p><p>So in regards to the Dev-C++ code there are some parts that will need a dual-implementation like:</p><p></p><ul style="text-align: left;"><li>Pipes</li><li>Console window - PowerShell would be able work cross-platform probably - CMD and bash not.</li><li>Threading/Processes</li><li>Interaction with debugger.</li></ul><p></p><p>So there will be sections with code like:</p><p><span style="font-family: courier;">{$IFDEF MSWINDOWS}</span></p><p><span style="font-family: courier;">// Windows-only code</span></p><p><span style="font-family: courier;">{$ELSE}</span></p><p><span style="font-family: courier;">// CrossVcl code</span></p><p><span style="font-family: courier;">{$ENDIF}</span></p><p>Current status is that the code is linking and deploying on both macOS 11.1 (Big Sur) and Ubuntu 20.04, but since my wife's mini Mac is painfully slow - I will concentrate on debugging and getting a working build up running on Ubuntu, and then most issues are probably fixed for macOS.</p><p>CrossVCL seems to me like a nice and a bit overlooked product - that for most Delphi VCL-based projects would be a fairly easy way to target two extra desktop platforms. Dev-C++ was not the easiest project to start with due to the legacy of some of the code, and all the integrations.</p><p>But let us see how far it brings me/us. I will write a follow-up post when my repo is pushed with a mostly working macOS and Ubuntu build.</p><p>Happy New Year - and stay healthy and save on the fireworks (think about all our fellow animals).</p><h4 style="text-align: left;">Update 4.january 2021</h4><p>I have now spend a bit more time (too much time on a slow mac) on the Dev-C++ code, and there are some parts off the code I would characterise a defects and bit rot - remember most of this code is +10 year. </p><p>Among these are:</p><p>Non UTF-8 language and tip files: Extra code to handle that has been removed and the files have been converted - which also means that the language picker is shown correct - see top image in post.</p><p>Saving and loading of configuration files could seem to be implemented in a clever way, but in my opinion it ended up over-engineered. I have currently only patched that in an ugly way.</p><p>The components like the classbrowser, did have old Kylix ready code - that needed to go away - since its $LINUX directives would be picked up by CrossVCL, and they do point to the old Kylix Qt libraries.</p><p>The SVG images and extended styling makes less sense on macOS (and Linux) _ and since the used SVG components does currently use and windows xml.dll - some extra work would need to be put into making that feature cross-platform - which in my opinion is very low priority.</p><p>Even if I did include the icns and it starts up nicely and loads into the main form - there are still some windowism that stops me from compiling a console Hello World application - which would be my end goal - and when other more skilled people should take over :)</p><p>The next thing I want to look into is the editor - there is a SynEdit CrossVCL adjusted fork already - so I might look at these adjustments.</p><p>I should also say that I get a bit distracted by some of the bugs in the old original code - and fix these mostly UI glitches while "conversion".</p><p>I am working in a separate branch, but I do not think a merge will make any sense before the SVG/styling code is split - so that a Windows build in this branch will not be felt as a step back from the nice enhancement sponsored by EMBT.</p><p>But I am also sure that things like the language fix and the less use of WinAPI is beneficial in the main branch.</p><p>/Enjoy.</p><p><br /></p><p><br /></p><p><br /></p>Steffen Nyelandhttp://www.blogger.com/profile/14576369608629841090noreply@blogger.com0tag:blogger.com,1999:blog-3386248074980233262.post-57418956807290218072020-11-28T21:28:00.001+01:002020-11-28T21:29:28.481+01:00A Set back?<h3 style="text-align: left;">- or how could I forget some set operators.</h3><div><br /></div><div>Recently a colleague of mine asked me to review some old code, and a line similar to the one below pop into my view as strange:</div><div><br /></div><div><span style="font-family: courier;">if (doorStates * [dsClosed, dsUnlocked] <> []) then</span></div><div><br /></div><div>It had been so many years since I had used that specific syntax, that I completely forgot about some of the set operators in Pascal and Delphi - so I thought I would write up a small post on Sets in Pascal/Delphi.</div><div><br /></div><div><div>In regards to the silly doorStates example above - here is a little riddle: <b>When is a door not a door?</b></div><span><a name='more'></a></span><div><br /></div><div>The theory on the subject is just basic algebra and useful in relational algebra also - so help me Codd.</div><div><br /></div><div>A set variable in Pascal can hold up till 256 elements, which ordinal values can be 0..255. The type of the elements must all be the same, and be a scalar type - but not floating point numbers.</div><div><br /></div><div>And compared to other variables can a set have none, 1 or multiple values, and values in a set is unique - in an array you can have duplicates.</div><div><br /></div><div>Set types are defined with the SET OF keywords - like:</div><div><br /></div><div><span style="font-family: courier;">Type</span></div><div><span style="font-family: courier;"> TDigits = set of '0'..'9';</span></div><div><span style="font-family: courier;"> TGarmentColors = set of (Yellow, Green, Blue, Red, Black);</span></div><div><span style="font-family: courier;"> TDoorStates = set of (dsOpen, dsClosed, dsLock, dsUnLock, dsAjar);</span></div><div><span style="font-family: courier;"> TBelow50 = set of 0..49;</span></div><div><span style="font-family: courier;"> TLetters = set of 'A'-'Z';</span></div><div><br /></div><div>So let us go through the various set operators one by one with some examples, and also add a couple that are not directly represented by a single operator.</div></div><div><br /></div><div>Example set variables defined are - using just sets of integers to keep it simple:</div><div><br /></div><div><span style="font-family: courier;">A := [5, 3, 7, 9, 16];</span></div><div><span style="font-family: courier;">B := [2, 6, 9, 1, 4];</span></div><div><span style="font-family: courier;">C := [1..7, 9, 16];</span></div><div><span style="font-family: courier;"><br /></span></div><h4 style="text-align: left;">Union</h4><div>A ∪ B is in Pascal with as A + B so:</div><div><span style="font-family: courier;">A + B = [1, 2, 3, 4, 5, 6, 7, 9, 16]</span></div><div><span style="font-family: courier;"><br /></span></div><h4 style="text-align: left;">Difference</h4><div>A ∖ B or A - B is in Pascal written as A - B so:</div><div><span style="font-family: courier;">A - B = [3, 5, 7, 16]</span></div><div><span style="font-family: courier;"><br /></span></div><h4 style="text-align: left;">Intersection</h4><div>A ∩ B is in Pascal written as A * B, so:</div><div><span style="font-family: courier;">A * B = [9]</span></div><div><span style="font-family: courier;"><br /></span></div><h4 style="text-align: left;">Subset</h4><div>A ⊂ C is in Pascal written as A <= C, so:</div><div><span style="font-family: courier;">A <= C = True;</span></div><div><span style="font-family: courier;"><br /></span></div><h4 style="text-align: left;">Superset</h4><div>C ⊃ A is in Pascal written as C >= A, so:</div><div><span style="font-family: courier;">C >= A = True;</span></div><div><span style="font-family: courier;"><br /></span></div><h4 style="text-align: left;">Equality</h4><div>C = A ∪ B is in Pascal written as C = A + B, so:</div><div><span style="font-family: courier;">(C = A + B) = True;</span></div><div><span style="font-family: courier;"><br /></span></div><div><br /></div><h4 style="text-align: left;">Inequality (disjoint)</h4><div>A ≠ B is in Pascal written as A <> B, so:</div><div><span style="font-family: courier;">(A <> B) = True;</span></div><div><span style="font-family: courier;"><br /></span></div><h4 style="text-align: left;">Membership</h4><div>{3} ⊂ A could in Pascal be written as 3 in A, so:</div><div><span style="font-family: courier;">3 in A = True;</span></div><div><span style="font-family: courier;"><br /></span></div><h4 style="text-align: left;">Symmetric difference</h4><div>A Δ B or A ⊕ B which is the same as (A ∪ B) - (A ∩ B), can in Pascal be written as (A + B) - (A * B), so:</div><div><span style="font-family: courier;">(A + B) - (A * B) = [1, 2, 3, 4, 5, 6, 7, 16]</span></div><div>or</div><div><span style="font-family: courier;">(A - B) + (B - A) = [1, 2, 3, 4, 5, 6, 7, 16]</span></div><div><span style="font-family: courier;"><br /></span></div><h4 style="text-align: left;">Include</h4><div>B ∪ {3} could in Pascal be written as Include(B, 3), so:</div><div><span style="font-family: courier;">Include(B, 3);</span></div><div><span style="font-family: courier;">B = [1, 2, 3, 4, 6, 9]</span></div><div><span style="font-family: courier;"><br /></span></div><h4 style="text-align: left;">Exclude</h4><div>C ∖ {5} or C - {5} could in Pascal be written as Exclude(C, 5), so:</div><div><div><span style="font-family: courier;">Exclude(C, 5);</span></div><div><span style="font-family: courier;">C = [1,2,3,4,6,7,9,16]</span></div></div><div><span style="font-family: courier;"><br /></span></div><div>I did a small application to illustrate the operators, and it can be used as evaluator - on a 0..255 integer set. I will put it on GitHub, if it has any interest.</div><div><br /></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjELtDtLPMs1QExBS1v5BA1my8Q-M0hsJirnmc9i5Up7oxyZ3cv2UxM0ViNj2ux7PGR3DwJHRw_CDWgJo3bsnjKKfhEGexiTThByCRwd2Wwe470C3vHQ7QZmed8vm5nM12_Ow3GG9m-S9UN/" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="602" data-original-width="355" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjELtDtLPMs1QExBS1v5BA1my8Q-M0hsJirnmc9i5Up7oxyZ3cv2UxM0ViNj2ux7PGR3DwJHRw_CDWgJo3bsnjKKfhEGexiTThByCRwd2Wwe470C3vHQ7QZmed8vm5nM12_Ow3GG9m-S9UN/s16000/image.png" /></a></div><br />So I do like Pascal sets, I think the only drawback is that a set is restricted by 256 element and the ordinal value must also be within 0-255.</div><div><br /></div><div>Coming back to the riddle: <b>When It's A Jar.</b></div><div><br /></div><div>Which is also the title of one of Tom Holts many funny books - which I highly recommend. <span style="color: #2b00fe;"><b><a href="https://www.amazon.com/When-Its-Jar-Tom-Holt/dp/0316226122" target="_blank">Amazon link.</a></b></span></div><div><br /></div><div>There are also many great Delphi books being published recently - which will fit great as a Christmas gift for your employees, if you are an employer or team lead.</div><div><br /></div><div>/Enjoy</div>Steffen Nyelandhttp://www.blogger.com/profile/14576369608629841090noreply@blogger.com2tag:blogger.com,1999:blog-3386248074980233262.post-12335930346724904372020-10-31T20:48:00.001+01:002020-11-10T19:40:37.007+01:00Not exactly Kierkegaard - pun intended<h3 style="text-align: left;">- or the books on Delphi keep coming.</h3><p>Well the pun in the title would probably need a bit of explaining, so the reference is to Søren Kierkegaard (1813-1855) - the Danish philosopher, theologian, poet, social critic and religious author who is widely considered to be the first existentialist philosopher - but his surname is also pronounced as the Danish word for graveyard or churchyard.</p><p>I sometimes still hear people say, that nothing is happening in relation to Delphi in general or more specific in the Delphi book area - but that is not true.</p><p>It might have been many years since a Danish book on Delphi was published - but there where many in the early days - I had only 2 of the 15-16 published, all the rest I had were in English. So the scene for Danish Delphi books does feel like a "kirkegård".</p><p>And it is all my fault - I do prefer my technical books (and my software) in English, and I am sure most Danish developers feel the same.</p><p>But when you look at the number of English books that has been published the last couple of years - the technical quality, the passion, level of detail and diversity of topics matches greatly what also has been going on in the rest of the Delphi community - if you haven't noticed you might be looking in the wrong places.</p><p>Fellow Embarcadero MVP Patrick Prémartin has started to setup a nice site called <a href="https://delphi-books.com/en/"><span style="color: #2b00fe;"><b>delphi-books.com</b></span></a> - some books still need to be added at the time of writing this. <strike>- no books in the Danish section yet - but covers are scanned</strike>. <b>Update:</b> Patrick has uploaded the info and covers I provided him with - and also published an extensive open-data API for the site.</p><div>I know Patrick has grouping by "year published" <strike>on his to-do list</strike>, that will help indicate how much great content has been published in traditional book form the latest years - so a big thanks to all the authors and publishers - for keep doing this non-trivial task of writing books.</div><p>It is almost 30 years since I co-authored two books on Paradox DOS - in Danish and just prior to Paradox Windows was launched - they were not bestsellers btw 😁. But I still remember the tolls it took on everyone, and still surprised that my then to-be-my wife, stayed.</p><p>/Enjoy some of these great books.</p>Steffen Nyelandhttp://www.blogger.com/profile/14576369608629841090noreply@blogger.com0tag:blogger.com,1999:blog-3386248074980233262.post-64249574902186445422020-09-30T19:26:00.002+02:002020-09-30T19:41:12.282+02:00Good on ya, mate!<h3 style="text-align: left;">- or a shout-out for Alister Christie's new book: Code Faster in Delphi.</h3><div><div>I just got a chance to review the first of a series of new books about Delphi from Alister Christie, and even if it is a short book - 160 pages - it is perfect and on the spot - looking forward to the next in the series.</div></div><div><br /></div><div>You can get the book here: <span style="color: #2b00fe;"><a href="https://leanpub.com/codefasterindelphi"><b><span style="color: #2b00fe;">https://leanpub.com/codefasterindelphi</span></b></a><b> </b></span>- just take a look at the Table Of Content - a lot of great stuff.</div><div><br /></div><div>Alister Christie has been doing his <a href="https://learndelphi.tv/"><span style="color: #2b00fe;"><b>LearnDelphi.tv</b></span></a> for many years - and his 150+ short videos are always to the point - on topic and precise - at least those I have seen :)</div><div><br /></div><div>And speaking of video - watch his <a href="https://www.youtube.com/watch?v=pnTcwlJug3E&feature=youtu.be" target="_blank"><b><span style="color: #2b00fe;">promotional video</span></b></a> for the book - always fun and this one has WordStar bits :)</div><div><br /></div><div>I will get my dead tree version as well - even if shipment cost probably will exceed the price of the book - but I want this nice books content and cover on my bookshelf.</div><div><br /></div><div>Speaking of cover - as a little bonus I found this on 99designs: <a href="https://99designs.dk/book-cover-design/contests/code-faster-delphi-book-1018068"><span style="color: #2b00fe;"><b>https://99designs.dk/book-cover-design/contests/code-faster-delphi-book-1018068</b></span></a> - so if anyone has a potential writer in them, this shows one process of getting a nice cover selected.</div><div><br /></div><div>It has been more than 25 years since I last wrote a book - even if self publishing and cover contents are now a thing - it is not a trivial task - as I remember it, so well done - and thanks.</div><div><br /></div><div>/Enjoy Alister's new book.</div>Steffen Nyelandhttp://www.blogger.com/profile/14576369608629841090noreply@blogger.com2tag:blogger.com,1999:blog-3386248074980233262.post-72352336789027986222020-08-31T22:12:00.000+02:002020-08-31T22:12:01.603+02:00Hygge by the fireside<h3>
- or a fireside chat with Jim McKeeth</h3>
<br />
End April in the evening Jim and I had a fireside chat, which should had been in front of our masonry oven - but I ended up having it from my home office.<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe width="320" height="266" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/6Hm1gDvaXUE/0.jpg" src="https://www.youtube.com/embed/6Hm1gDvaXUE?feature=player_embedded" frameborder="0" allowfullscreen></iframe></div>
<br />
<br />
You can see the chat here - it is a bit long but I think we had fun, and to help you through it, I have done this post as a commentary track, and put a bit more details in various places.<br />
<br />
And the word hygge now seems to have gotten into the Oxford English Dictionary - but still pronounced wrong :)<br />
<br />
<a name='more'></a><h3>
[0:20] Wasabi - encounter with a Delphi beta</h3>
<br />
June 1994. I was PM for Database (and Paradox Engine) products in Borland Scandinavia at that time, when the PM for Languages handed me 8 3.5" disks with "Wasabi" written on them - and told me that this was going to be the new big thing.<br />
<br />
I was sure that it was "just" the next version of Turbo Pascal for Windows, and it installed as AppBuilder. But it was all the things from my Paradox for Windows and SQLLinks compiling into exe files.<br />
<br />
The language of Paradox for Windows - ObjectPAL - had, as I remember it - an event model where external events first hit the form, then the UIObject (button) and then bubbled up to the form again - and objects prefixed with an # where ignored. I think the team lead (architect?) on that language was Jesper Shultz - another danish employee at Borland at that time. And the language was very "begin-end" style - and combined the the database access included in Delphi, explains why many Paradox developers transformed into Delphi developers.<br />
<br />
So in a way Delphi caused the end of my carrier at Borland, but opened up a new, doing Delphi training, consultancy and development for a living.<br />
<br />
<h3>
[2:56] Delphi, my longest lasting love - correction</h3>
<br />
My wife has been looking oddly at me after this part in the video - so just to be clear - the comment on my longest lasting love was in the context of computer languages - in real life that goes to my wife, which I have known longer than Delphi, which should prove my point.<br />
<br />
<h3>
[10:12] OpenCV and comics</h3>
<br />
This was just the reference to this old blog post: <b><span style="color: blue;"><a href="https://fixedbycode.blogspot.com/2017/09/match-of-day.html">https://fixedbycode.blogspot.com/2017/09/match-of-day.html</a></span></b><br />
<b><br /></b>
<h3>
[22:38] gitMonocle - simple git plugin</h3>
<br />
I do not use the IDEs VersionInsight, since it is modelled too generic on SVN use, and git and Hg are just pressed into the same paradigm - instead of designed on usage scenarios. The two things I missed the most are an indication of what my current branch is, and the blame for the current line(s).<br />
<br />
So in 10.2 I did a small plugin - I called GitMonocle - that did these 2 things - that then broke in the versions afterwards - but I hope I will get around to fix and expand it at some point. Or others will.<br />
In addition I did a dock-able bash console window - but more on that another time.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiUzHuA7yphgpGsNtp-vaMHQZYzswL54miv8npOoDnMwp42I4PY4IHGLhAQX-hmhFj076NXkuE1xo8JlutF4gJigM26qSE5Wcl9JUEjbtEFixk_cPQ40hUYvz7YLyPAUpGzbVVlDXDZpI7K/s1600/gitMonocle.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="581" data-original-width="1557" height="236" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiUzHuA7yphgpGsNtp-vaMHQZYzswL54miv8npOoDnMwp42I4PY4IHGLhAQX-hmhFj076NXkuE1xo8JlutF4gJigM26qSE5Wcl9JUEjbtEFixk_cPQ40hUYvz7YLyPAUpGzbVVlDXDZpI7K/s640/gitMonocle.PNG" width="640" /></a></div>
<br />
<br />
<h3>
[24:06] Pipe output from external processes</h3>
<br />
This was just the reference to this old blog post: <span style="color: blue;"><b><a href="https://fixedbycode.blogspot.com/2014/07/redirecting-or-capturing-output-from.html">https://fixedbycode.blogspot.com/2014/07/redirecting-or-capturing-output-from.html</a></b></span><br />
<span style="color: blue;"><br /></span>
<h3>
[28:37] Prince of Persia</h3>
<br />
The creator of Prince of Persia - Jordan Mechner did a diary on the development of the game, that I can recommend - I did never complete the game, only the book :) : <span style="color: blue;"><b><a href="https://jordanmechner.com/store/the-making-of-prince-of-persia/">https://jordanmechner.com/store/the-making-of-prince-of-persia/</a></b></span><br />
<span style="color: blue;"><br /></span>
<div>
<h3>
[29:50] Games and timing - HeroX</h3>
<br />
Jim and I talked about the timing of game releases and shifting trends - and this does apply to many things in life. But I would like to mention the game <a href="https://www.mobygames.com/game/windows/hero-x"><b><span style="color: blue;">HeroX</span></b></a> - which was done by the same people who did Siege of Avalon. The same people and a variation of the same resource file format, indicates that that engine was just an evolved Siege engine also done in Delphi.<br />
<br />
The game is a Superhero action RPG, that possibly could have had more success if it hadn't been for a game like <a href="https://www.mobygames.com/game/freedom-force"><b><span style="color: blue;">Freedom Force</span></b></a> which launched at the same time, executing better on almost all parameters - probably also on a much higher budget.<br />
<br />
I am not sure on the morale on this - should they have shipped earlier - then the market for Superhero ARPG might not have been there.<br />
<br />
It was a nice idea, but what put me off was the style of the drawn comic cut-scenes, they should have used a known superhero comic artist (preferable a Marvel artist). Revolution Software did good in Beneath a Steel Sky, to spend time and money on <a href="https://www.youtube.com/watch?v=5kjLDp8Eu5o"><b><span style="color: blue;">Dave Gibbons</span></b></a>.<br />
<br />
I still hope that the HeroX engine source will surface - preferable in a better shape than Siege of Avalon was.<br />
<br />
<h3>
The End</h3>
<br />
There might be a second part - since we did talk for a long time covering some ground - and I skipped things.<br />
<br />
/Enjoy</div>
Steffen Nyelandhttp://www.blogger.com/profile/14576369608629841090noreply@blogger.com0tag:blogger.com,1999:blog-3386248074980233262.post-55766235865129134622020-07-31T22:23:00.002+02:002020-07-31T22:23:45.344+02:00I am no Charles TDickensionary<> <h3>
- or a few TDictionary<> tricks.</h3>
<br />
One of my favourite classes in Delphi is TDictionary<> - might be because it resembles a database table which can have composite keys, and I have always been a ClientDataset/VirtualTable/FDMemTable fan-boy :)<br />
<br />
I do not get to use the memTables that much i recent years, and the RTL generic collections also got better - while not on par with third party collections like <a href="https://bitbucket.org/sglienke/spring4d/wiki/Home"><span style="color: blue;"><b>Spring4D</b></span></a> - which I also not get to use much.<br />
<br />
I recently had a task where I had to transform some code that was collecting data from an external source, that provided data in a way that multiple iterations had to be done, before each row/item was complete - since data was coming in subsets per field/property. TDictionary<> to the rescue.<br />
<br />
<br />
<a name='more'></a>There is probably better ways of doing this - but this was the first thing that came to my mind, and it worked out well on the fairly complex set of data and the constraints in the form they came.<br />
<br />
I have come up with a unrelated but similar very small scenario - where the key to the data is Date, CharityId and values are Count, EmpId and Sum.<br />
<br />
That should illustrate a case where Charities have collectors by their employee id, that daily collect money. So the id of the collector for a given Date+Charity is separated from the donations by a "dimension" value.<br />
<br />
Stupid sample scenario, but that is what fast ideas give you - when the real life is too complex as examples :D - here the input data to transform:<br />
<br />
<b>IntDate, CharityId, dimension, value</b><br />
0, 0, 0, 1<br />
1, 0, 0, 2<br />
0, 1, 0, 3<br />
0, 0, 1, 23<br />
1, 0, 1, 1.99<br />
0, 0, 1, 12<br />
0, 0, 1, 5<br />
1, 0, 1, 10.59<br />
0, 1, 1, 20.59<br />
<br />
..and output we want as below, and sorted date and charity:<br />
<br />
<b>IntDate, CharityId, Count, EmpId, Sum</b><br />
0, 0, 3, 1, 55<br />
0, 1, 1, 3, 20.59<br />
1, 0, 2, 2, 12.58<br />
<br />
So types are defined as:<br />
<br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">TKeyRecord = record</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> IntDate: Integer;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> CharityId: Integer;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> class function New(intDate, charityId: Integer): TKeyRecord; static;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">end;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">TValueRecord = record</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> Count: Integer;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> EmpId: Integer;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> Sum: Double;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">end;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">TDictionaryData = class(TDictionary<TKeyRecord,TValueRecord>)</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> procedure AddData(const intDate, charityId, dim: Integer; const value: Double);</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">end;</span><br />
<br />
<h3>
Create and populate</h3>
<br />
The AddData procedure does try and find the value record by its key record and if that fails it adds a new value for the new key, or updates the fields in the value record given by the dimension in that iteration - the thing I worked on in real life had +20 dimensions and some were nested - terrible structure :)<br />
<br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">procedure TDictionaryData.AddData(const intDate, charityId, dim: Integer;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> const value: Double);</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">var</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> KRec: TKeyRecord;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> VRec: TValueRecord;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">begin</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> KRec := TKeyRecord.New(intDate, charityId);</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> Self.TryGetValue(KRec, VRec);</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> if dim=0 then VRec.EmpId := floor(value);</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> if dim=1 then</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> begin</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> VRec.Count := VRec.Count+1;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> VRec.Sum := VRec.Sum + value;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> end;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> Self.AddOrSetValue(KRec, VRec);</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">end;</span><br />
<br />
Normally one would check on the boolean result of TryGetValue - but here we want to do something regardless what it is. So in this sample input data is then added as:<br />
<br />
<span style="font-family: "Courier New", Courier, monospace; font-size: x-small;">Data := TDictionaryData.Create();</span><br />
<span style="font-family: "Courier New", Courier, monospace; font-size: x-small;">...</span><br />
<span style="font-family: "Courier New", Courier, monospace; font-size: x-small;">Data.AddData(0, 0, 0, 1);</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">Data.AddData(1, 0, 0, 2);</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">Data.AddData(0, 1, 0, 3);</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">Data.AddData(0, 0, 1, 23);</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">Data.AddData(1, 0, 1, 1.99);</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">Data.AddData(0, 0, 1, 12);</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">Data.AddData(0, 0, 1, 5);</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">Data.AddData(1, 0, 1, 10.59);</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">Data.AddData(0, 1, 1, 20.59);</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span>
<h3>
Add or accumulate</h3>
<br />
TDictionary<> does have TryGetValue and AddOrSetValue as shown above, but I often miss an AddOrAccumulate function, that either adds new data or accumulates some of the values there - and since our "Value" is a record and not a simple type, we would also have to write some code anyway.<br />
<br />
For this example I did just create the AddData method on the class, but otherwise could a class helper like the one shown here be helpful for simple values:<br />
<br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">TDictionaryHelper = class helper for TDictionary<string, Double></span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> procedure AddOrAccumulate(const Key: string; const Value: Double);</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">end;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">...</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">procedure TDictionaryHelper.AddOrAccumulate(const Key: string; const Value: Double);</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">var</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> oldValue: Double;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">begin</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> if Self.TryGetValue(Key, oldValue) then</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> Self.AddOrSetValue(Key, oldValue + Value)</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> else</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> Self.AddOrSetValue(Key, Value);</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">end;</span><br />
<br />
<h3>
Sorting the data</h3>
<br />
A TDictionary<> can't directly be sorted - so one way of doing it is by putting it into an TArray<>, defined as:<br />
<br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">DataArray: TArray<TPair<TKeyRecord,TValueRecord>>;</span><br />
<br />
and then sort that using a TComparer with an anonymous method:<br />
<br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">DataArray := Data.ToArray;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span>
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">TArray.Sort<TPair<TKeyRecord,TValueRecord>>(DArray, TComparer<TPair<TKeyRecord,TValueRecord>>.Construct(</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> function (const L, R: TPair<TKeyRecord,TValueRecord>): Integer</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> begin</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> Result := CompareValue(L.Key.IntDate, R.Key.IntDate);</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> if Result = 0 then</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> Result := CompareValue(L.Key.CharityId, R.Key.CharityId);</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> end)</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> );</span><br />
<br />
<h3>
Accessing the full data</h3>
<br />
The split of the data in a key record and a value record might seem a bit messy, but an easy way to traverse through the data, whether it is in the TDictionary<> or the TArray<> - were we sorted the data - is just to loop through the TPair<TKeyRecord,TValueRecord> record - like this to fill our memo:<br />
<br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">pair: TPair<TKeyRecord,TValueRecord>;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">...</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">Memo1.Lines.BeginUpdate;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">for pair in DataArray do</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">begin</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> Memo1.Lines.Add(pair.Key.IntDate.ToString+', '+pair.Key.CharityId.ToString+', '+</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;"> pair.Value.Count.ToString+', '+pair.Value.EmpId.ToString+', '+pair.Value.Sum.ToString);</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">end;</span><br />
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">Memo1.Lines.EndUpdate;</span><br />
<br />
So when having the pair, both parts can be accessed by either Key or Value.<br />
<br />
<h3>
All done and good</h3>
<br />
I will put the full source code of the sample on my GitHub - but I might add some extra features and another post before that.<br />
<br />
I hope that there might have been something useful in here, and not too many errors and babbling, since I did put myself on a stopwatch - and the sample did change a bit along the way :D<br />
<br />
I did use 10.4 doing this post - and I should say that the new LSP-based code-insight is very enjoyable compared to what we have had for many years - just that you can just type a word and all results where the word is contain - not only starting with - is very helpful. Sorry went off-topic. Good night.<br />
<br />
<span style="background-color: white; color: #181818; font-family: Merriweather, Georgia, serif; font-size: 14px;"><i>“It was the best of times, it was the worst of times.”</i></span><br />
<span style="background-color: white; color: #181818; font-family: Merriweather, Georgia, serif; font-size: 14px;">― </span><span class="authorOrTitle" style="background-color: white; color: #333333; font-family: Lato, "Helvetica Neue", Helvetica, sans-serif; font-size: 14px;">Charles Dickens, </span><span style="background-color: white; color: #181818; font-family: Merriweather, Georgia, serif; font-size: 14px;"></span><span id="quote_book_link_1953" style="background-color: white; color: #181818; font-family: Merriweather, Georgia, serif; font-size: 14px;">A Tale of Two Cities</span><br />
<br />
/Enjoy - and stay safe.<br />
<br />Steffen Nyelandhttp://www.blogger.com/profile/14576369608629841090noreply@blogger.com0tag:blogger.com,1999:blog-3386248074980233262.post-91372235275939341772020-07-27T22:40:00.000+02:002020-07-27T22:40:59.849+02:00TTreeview your TOutline<h3 style="text-align: left;">- or migrating a TOutline to a TTreeview many years too late.</h3><div><br /></div><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ96w0V-SB1n1pjz7GTqtA2wWdnlNV-hyJe3ewzP4NsflqCIS4LvPf3AvFYarLznToSeIrsUFySxHj6hpyfuvR4uWHsQ_anDUily1b7ZGoP9r60g5ccyeUyI6JBQwFa_9xm-w2DntV5m_5/s819/Capture.PNG" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="440" data-original-width="819" height="215" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ96w0V-SB1n1pjz7GTqtA2wWdnlNV-hyJe3ewzP4NsflqCIS4LvPf3AvFYarLznToSeIrsUFySxHj6hpyfuvR4uWHsQ_anDUily1b7ZGoP9r60g5ccyeUyI6JBQwFa_9xm-w2DntV5m_5/w400-h215/Capture.PNG" width="400" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">HighDPI themed Delphi 10.4 TOutline and TTreeview<br /></td></tr></tbody></table><div><br /></div><div>The task to migrate from a working old TOutline to a TTreeview was long overdue in a small program I almost use daily - and its code had not really been touched since it was created.</div><div><br /></div><div>TOutline was included in Delphi 1 back in the Win 3.1 days, and since Windows did not have any Treeview/Outline in its common controls it was purely VCL, not a wrapper.</div><div><br /></div><div>It has for many years been advised to not use TOutline for any new projects, but instead use TTreeview or similar - but once in a while I do bump into this component - and this time I thought I would write a small and incomplete migration guide.</div><span><a name='more'></a></span><div><br /></div><div>I will just list the TOutline related code, and the TTreeview euiqvalent - with reference to the sample application shown in the image at the top.</div><div><br /></div><div><div><font face="courier" size="2">// TOutline code</font></div><div><font face="courier" size="2">Outline1.Clear;</font></div><div><font face="courier" size="2">var ParentIdx: Integer := Outline1.AddChild(0, 'Root node'); // Returns absolute index of node</font></div><div><font face="courier" size="2"><br /></font></div><div><font face="courier" size="2">for var I: integer := 1 to 5 do</font></div><div><font face="courier" size="2"> Outline1.AddChild(ParentIdx, 'ChildNode '+I.ToString);</font></div><div><font face="courier" size="2"><br /></font></div><div><font face="courier" size="2">Outline1.Items[1].Expand;</font></div><div><font face="courier" size="2"><br /></font></div><div><font face="courier" size="2">Outline1.SelectedItem := 4;</font></div><div><font face="courier" size="2"><br /></font></div><div><font face="courier" size="2">// TTreeview code</font></div><div><font face="courier" size="2">TreeView1.HideSelection := False; // Show selection even if focus is lost.</font></div><div><font face="courier" size="2">Treeview1.Items.Clear;</font></div><div><font face="courier" size="2">var ParentNode: TTreeNode := TreeView1.Items.AddChild(nil, 'Root node');</font></div><div><font face="courier" size="2"><br /></font></div><div><font face="courier" size="2">for var I: integer := 1 to 5 do</font></div><div><font face="courier" size="2"> TreeView1.Items.AddChild(ParentNode, 'ChildNode '+I.ToString);</font></div><div><font face="courier" size="2"><br /></font></div><div><font face="courier" size="2">TreeView1.Items[0].Expand(True);</font></div><div><font face="courier" size="2"><br /></font></div><div><font face="courier" size="2">TreeView1.Selected := TreeView1.Items[3];</font></div><div><font face="courier" size="2">TreeView1.OnClick(nil);</font></div></div><div><br /></div><div>So as can be seen is TOutline more integer index based, compared to TTreeview more relying on its TTreeNodes - which recently had it's Item property set to default, so no need to write Treeview1.Items.Item[3] anymore - Treeview1.Items[3] will do.</div><div><br /></div><div>Another thing to be aware of is also that TOutline indexes are 1-based, whereas TTreeview is 0-based, and a node's index on a TTreeNode is related to its Parent, if you want the comparable to the TOutline items index, and the absolute index for the full TTreeview you can use TTreeNode.AbsoluteIndex.</div><div><br /></div><div>Setting TOutline.SelectedItem does fire an OnClick event, so on the TTreeview it is done by hand.</div><div><br /></div><div>Here are the onClick for the two components shown:</div><div><br /></div><div><div><font face="courier" size="2">Label1.Caption := 'SelectedItem: '+Outline1.SelectedItem.ToString + sLineBreak + sLineBreak +</font></div><div><font face="courier" size="2"> 'FullPath: '+Outline1.Items[Outline1.SelectedItem].FullPath;</font></div><div><br /></div><div><font face="courier" size="2">Label2.Caption := 'Selected AbsoluteIndex: '+ TreeView1.Selected.AbsoluteIndex.ToString +</font></div><div><font face="courier" size="2"> ', Index: '+ TreeView1.Selected.Index.ToString + sLineBreak + sLineBreak +</font></div><div><font face="courier" size="2"> 'FullPath by Class helper: '+TreeView1.Selected.FullPath;</font></div></div><div><br /></div><div>TTreeNode does not have a FullPath property as TOutlineNode does, so I added a small class helper:</div><div><br /></div><div><div><font face="courier" size="2">TTreeNodeHelper = class helper for TTreeNode</font></div><div><font face="courier" size="2"> function FullPath: string;</font></div><div><font face="courier" size="2"> end;</font></div></div><div><font face="courier" size="2">....</font></div><div><div><font face="courier" size="2">function TTreeNodeHelper.FullPath: string;</font></div><div><font face="courier" size="2">var</font></div><div><font face="courier" size="2"> node: TTreeNode;</font></div><div><font face="courier" size="2">begin</font></div><div><font face="courier" size="2"> node := Self;</font></div><div><font face="courier" size="2"> Result := node.Text;</font></div><div><font face="courier" size="2"> while node.Parent<>nil do</font></div><div><font face="courier" size="2"> begin</font></div><div><font face="courier" size="2"> Result := node.Parent.Text + '\' + Result;</font></div><div><font face="courier" size="2"> node := node.Parent;</font></div><div><font face="courier" size="2"> end;</font></div><div><font face="courier" size="2">end;</font></div></div><div><br /></div><div>To get the selected Node:</div><div><div><font face="courier" size="2">Node := Outline1.Items[Outline1.SelectedItem];</font></div><div>compared to:</div><div><font face="courier" size="2">Node := TreeView1.Selected;</font></div></div><div><br /></div><div>Check if anything is selected:</div><div><div><font face="courier" size="2">if Outline1.SelectedItem <= 0 then</font></div><div>compared to:</div><div><font face="courier" size="2">if TreeView1.Selected=nil then</font></div></div><div>or</div><div><font face="courier" size="2">if TreeView1.SelectionCount > 0 then</font></div><div><br /></div><div>Well this was just to get you going, but use the relevant Embarcadero DocWiki pages to find out what the various properties and methods do, and go from there.</div><div><br /></div><div>As also seem in the image, does TOutline work perfectly well and have been maintained to support styles and whatnot, so the urgency of updating and maintaining you code - is not forced on you as with other technologies and frameworks - but it never hurts to keep your code fresh and current - and also throw out all the unused bits :)</div><div><br /></div><div>/Enjoy</div>Steffen Nyelandhttp://www.blogger.com/profile/14576369608629841090noreply@blogger.com0tag:blogger.com,1999:blog-3386248074980233262.post-58706924371398704982020-06-18T22:40:00.002+02:002024-01-08T21:27:38.892+01:00D&D in Delphi source code?<h3>
- or does your device support Phone, Tablet or Elf?!</h3>
<div>
<br /></div>
<img alt="" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAjQAAAA/CAYAAAD301LpAAAgAElEQVR4Ae1di3XrOg5MXS7I9bgaN5NivGcGGACkKMcfJS+5i7vnrWVZIsHBbwhSyset/zUCjUAj0Ag0Ao1AI/DHEfj44/K3+I1AI9AINAKNQCPQCNya0LQRNAKNQCPQCDQCjcCfR6AJzZ9XYQ+gEWgEGoFGoBFoBJrQtA00Ao1AI9AINAKNwJ9HoAnNn1dhD6ARaAQagUagEWgEmtC0DTQCjUAj0Ag0Ao3An0egCc2fV2EPoBFoBBqBRqARaASa0LQNNAKNQCPQCDQCjcCfR6AJzZ9XYQ+gEWgEGoFGoBFoBJrQtA00Ao1AI9AINAKNwJ9HoAnNn1dhD6ARaAQagUagEWgEmtC0DTQCjUAj0Ag0Ao3An0egCc2fV2EPoBFoBBqBRqARaAS+ndB8fn7e9F/D/dcQ+Lxdr9fb9fp5+5TooU+d6M9XEfgMUF9t4YH7PqVD6PF6u/5Enw+I9cwlih/47H+NQCPQCOwh8DKh+bxebufTx+3jw/47nc4Mlnb+FOf1e36ebqfz5U8G1j0Q/8XznxfX4el0OxU9px4/bh+nSxKdfxGEp8cE8u7E4XK+nS8XJ4RXJ/XX2/VyTjzP16d7ePiGz9E/R72Zrz7c1jdd2DHkm4D9g82StHLyJOINXyoD+bzezoxFp9v5cu24U6Dpw0TgNUJzPd8+Pk63c5m5f17PtxPJjQhLzurDMDFbrAHdSVCK80ePvGqR0ltVKr//saPPy+30cbpdPm83JJ0L9MyZ/uft03UuwvOdOTlRS1vKc/eOzM5+JvC5TcP2T6fbGWS9BmYcXy638xnE8Hy7KBgD4+8ihNRfTjYGMuMTkP+cjHYMGQ34X4sh4+iW32ZCC/850YdA+nMixeNzkpjr2WLTstE++X+NwAuE5vN2OX3cmMg4C/RAHbXsibQogCrge0CvBAjEaPnv83q7nM+WJGKpw5JrLUPXYxAmzIzB5pGQ89/nDY5gjpKkK8+dh+s/g3jZ+K6sSPn94VyGBcjduooxy5DS7B6VcY7XuPx71a0DsSJZwRiDpFqyNsKKRHm+XW/X2xk6FaNh/8LXEreRV8Nv0EXYjenhAp0xgJ1Jnuq4oZ+PE2zA2jnTHvD9/n80qStsYTCC2rQfO/nxYBr2AfJRCPvixtvN8Tm5PaA6A3udbQFtiuhgrKhQfn4boXG9VL/7+DAydYV8WT2V6pZj+9aTHUNkM7fbvxlD7plPjStWrTcfjepmnRAwHt4ws7pdEPtucLv9uKoVAiNBiCtlBeGeUE//lku5mG5xTDsx7PGmldumO1gIQE5D3H3k35PtHJg77kv3pFz3G1v++gKhQcCEQVmCHWZ/UKgCKasvGkCZxfJ3q+7cykxyG1xRYlRpHAaDWe79JMakAblQEkIyC+0raHh7dBJLPrjWyp1ZlbiekcBxrf1GknQu3/E7khiWZZj4IRuW0SCnlUxh5CB+kfCX8OPkRAA3iTVJgvG+6+2cA/NWj8QK0HkQuHzaGKVT/2RlzpekKAr16BW7wFYE5HK7EE8tT1nCZRtanhFmKCt/OFnGyDCLtw5Y9ajE9f4xMDLScI/QGGlNuUFaL1wmSr0jMC45kVcY+Nu95Z0JO/oLKjPfRWgo11idOU0DkH6/ts1do33zh44h1ME/HEPWBqI4/BHbDjZVmpW/xDlL6GtCg7YxQdKSr0+kWF1GfDxuiTwImcdqy4HoOyczs88t8SBRUTUKk56S32oeOKNKfuX4Jle2Zt9q59jcMYzzLbmGlh7+8iKh+biFwhYBlDN4rnlmYDUmbglLJIhtBKkZEwfIAn7X0obKkTmDVqKfPmH8SIIDoVGiBAmTTFiLzX0G6otGyQpEIWxcKvAlNRxzbFZVQGImcQmn8/bhWEj6u8sKSfJs/5GIle2zMOPW8gV+c8NDZWQiNMdiZbMhVl9IXEedYTwcl3AmcQMpuN1IEHAeFQkPJCAexEc4wF6cLI64Gdm0ioyRHwWuCCAzxne/oz0nNgt3IGZIKJCvVC1km1pSxcwQ1aMRctmTk66NHBbcWHXa/JZkOHxoId9zp3JJLu077XwOgulTIpnP9fb+1YZfjL9jyL8XQzZG4qSCExauZUf1pPoc8gSS97Bsq/188CUGmpqErSPFQPkr20E1lP53ul2uOWHdiPbMCc9XjG+cAPn+OE64bVJrPjjms7kL+qCqwJ74rUrt+ezsy9P8zZb9FQ9rW++2I9zYDrCqROresXQx5NmU7F25sqXnjl4gNOMM/qalBxpOJvMAiOVuVW7ON8yCo4rjxh3XKul5dcNm15WEePtIqrPR8/vFlkG8ggTGbsG8BtBFe8pWEVh135aswJCtTAzDgzxIdouk721F0B70IueGo3n1yUuj6dxKSPXTl3oCJzRqMh6HlQvqVReSt6rbIDSJERxNJfQxoeIax0YYe8nYSG/iS5wC/7KkpbHGb45HaW+AFogoADL4XSjbcA2Winh/6k3yE9Fio5SLWJRyL2ShXHl/6I0k6XrjMhqqPazajTaPoAySZm0Mkr38BVVEK8dXe8Gx9JRNh46Ebf70Y0eSwfxjB0fpsWPIQi9/IIaE1JI1tyrUHBCTB1yPBI5YXjfVk/9g32ax58l/lOyXkx9OoMzG7oSNkPbeAWOL/LfGxTj2iRS+73RG21ecWE6mZh+2AoLGKPneb+d7csf7cmmEz3++RGiURIe9FKFQM1oFrFCqKjFg4FElgeKKAZTlBiWlMdG5omlQqyWosu8DwRp9ToknE1QaDe1O8sU4rMpQl8VsLJ6E1e4JxoZyYLb3gTHi+9KgPXhTvlIFYr9ODGIXtRSaiZ9txrjs92OxUp+QsxAS4bIhNHa9jHgkuNCtJXNLXGrbSDHOmezoJ8cY10onwHFFaOZzjqHNxqAPS+YIBKkKze6yv9Qryr6mg9S7taFAhhFgrJRx0/+Il4I2rg1/kIw+3pQrsXnpiFhh309WHUWyzD5FyAp5OKzzVyQW/oW8ysY8DgRmkjPsoWMIK6i/PobALqTnHTLDOMpZyKJio5iacYQ2TXuwdhUrzFbk77rPPjEJlS3JlF6xWN7DfXPoJ8clP8Mn5IlKNSv9U0+wYeqt+KHbvVWnYNuqLOU4bNIsHza8jmjn8Nxx1Pgm2B79+iKhQfNQ6BjApVgYjQxIBkeBmABOnL3q2jQCPSacZXCybSh/ThwgNJy91eUmWyO9xrVp3Gb/miVvZR4SDjagBjlBRUkzAzikHMPaJgpcGnuU0GimAgedHMIdW5vaKj62RCMHsL4x/ortoViZsp4iNGkPpULiDk5bUOBi2x7AVoRmcR2rKUUPxMaMzDcMa+0ZlcBRZ8QI93okE25yZCPlJrNfEpsOZcNWSQH+WQXBtdmGAk8GXmvLdLyp4BVcqg4FzWufsid/rBX7k0qFw8aga0ze4/p+TeK0Gdm2cBwnRYOctIOOIXOFkz7x62JIWZKlQ4z2J9/LyQomE2XJSQ9mbCoi5of0vxizkwAs9Q8TZovbw7L3q+bK+zAGxOB5LLDd+XzmCXUJmQHFSkZWurnMYw8k1GvgA/hucSXjVb0GNmA5Snnx63Yg15G546jxCa9nP98gNFAKFPZEMHK2fsLelTIbY7DV7GsqkeOR1z1CMwQ63D8ZM5RPo5cVeFKUcQRpKATJlqicuMR5GauNVc3RGJ4gNGZ8ZuSUSxjQKYuD+KOLrBbwmkyUkJnjJl6FsSN2HIkVlxIX+t2p0AALOdwwtlLlmfWFZTK7Fv3Y+IdraM2+Z2iH0IzXgzCWPT7AzozAiE6Q8MTa7k+yYl06AZpsUuPT5zhO2YgHK+pU9pL9VZtDO6P87P3F/5v6AA71KbWJ6EUyebG3o27rGGIx6tEqL2xGlcfB/n5tDEl5k7jgnCdn+ZgTk9mu5CM2brtP47cqqsVTu89ixXitCM0YK+d+Hv4OeSGr5FYM908QiqjWx0Mtah2/ZayLWBC+adsXuIIx5TLmyJiYHdWO5Doqd2Bl4ojxpVzPHh1AaIoCQ6nJQOeATWM7zSVjgJDEqN7zTpJmO2EEgMb7GIhKqexUp0IyuNSxaeY/OgaCykPByB2AMg3OYA5pTmjHgxLdgdaz7fH647Ea9UIHDMIw9k10fQYxBFrYBHAlISn34MmgbyI0AwGuhMaxrMGI5HSwES/nshoz2aRfZzrHwv5l2A8mfOIJN1X7ZnvzmRQCX7X1Qe9Pfhkxt8A4TBpIhDF7tUpoJeVPdnXo5UZoqp9lslNimjHqGDLbnvnVb4shYZM0tlnHFkdN5jGmVgPD79C/XSdCU8lQiSn+wMT2WvS930ft76tj+X6MDRWVQmogq/1W5VKrkAPnV1gg1tg91sdU9cdvF+27O6odyfUeoaF/MjbeITQxQd4h8MP4Uq5nj36c0BhLxw7ufB+G2HoYYom2DydpAEZG7HsI+DSSE6tozw0pEqwcRBWQNPrNzFFJqZIe5r7HCI2NzQw2xglHgGxMjCsH8OSqvuU4lMHGEkN7pkLzEFZysEzqSthGrrbyYlw2nFG3uC+CkvD7FkIDfUMuW8e2R+nNgYJUDVhmUKVThvdo7EnMQ0+ynaID4iLd4BOkjzknccDMTe+p4Qv/ZoIXfb9+MNpVnTSkHFy+hCyvd3PondJXzGodR9iRxjPqhqVA7r3rGOLx61fGECVtjxMz+Wfgstiy0W+xMJIDM4by9nlvc+ND312h2RKJzXJykZVDLGPBoWKkbDtiB+ISJtEgPE5chkmsqjje6FHtSLyj8uzRckm+Rz//I0LzxRo4Z8c2hMeBzsQV74/hbHRMvGZIIjAiNEjEmcRsbV8VGbsmEvJkpUs2PW8KljPz3pEg4BSdVol+ozlbRqlVBdsPYu1UcY7FSniM8jJZ+/6l+XFgGTPHUxM8j6EHa4sB7JsIjcgh1uL1MkQEDeGc+6C8chTLoDXFJ1mMsVA/wiR/n8voEaB8tjVWcbBH4Ml3VWzsYf9EyCpSBT25XTFYwi75KGv1CXtdwH6r3/vL64SmY4hsTbYtXW819h/EEMU82d9iAitfnePIID/bsYmH3kWGJ0MtRiM25ySUFfhhwmKx25aB6nVDD49/gSwYj8bmfVtOUS7J3LIiavBRnicZ0z32ySctESN9Dycr2P7+NXsPjVemYiJt71uTHejzmXY0+KNyx1Hjk1zPfv48oXFjOIOF1qQHrwwlZ8B9DGh7pwsMHQEy20XpvSYqze7QvmYQbkz+pJIRBAsAlSGLidvvCbMpcCoPToRGiYb3xhjR716FINvHERzGnAPJB2dUgh2d9FislLyPIjROIKh/ey/EoUtODATYDAciCv2NG+OEowU3DyRBUjJQGPJmG8BcujNdCRPZUeovbc7bdtum3iJI2YZdyDdZpXX71v9nIKQswMEJTT5VaJsuZ5eIILTpX/a2+eGwEy8Rmo4hJcalDVqMWKvmp2OI/EZxa6zAWXy3a8YYtpKehKG8s6W+jG8cs1Vo1Ld88ihCIz+J9hHQ+Z6YMedEbpmTBQeHGIIxj/lHsuodXZnHLI7hdz4cEQAd1Y41eFzuOFauGO6DB/8hobkMa4+cXQTzzQRzD2goHy+gG/4w5tKIChreBxxhYNa+CYsO4vtn8nex7iRaatGM/D6hsXaqE9eEWmb7anTzqWSVxk0Dn6Y2x2Kl5I3PkqQf3UOz2SdlbQBfBYQ8ToJJ/Ifxf7EpWMQFbwbmfcBzFSRxHv2U8TAgmR3OZgOdyRYs2KSMupZ6JSnaCU4iNZg1BoE2OxiGeMgXe5/RuJ5ve2bwAsl6HpsqB6JfS/f6ExaDzsc/C3KIuN7Ie4RmGtcwe+4YMurpZ2OIYqf5SvE52lWSMMb9UdDFt3mi6rFEjhh3fCehUfyQrysejJV8xWUb/yoOlYp8jUXV31jdVZw0X11NgBhHafMzvorXJqPF2/12AN+RueNIuUK1Dx78UUKj5KVRokLjf+cDT/ro9PLTlT+XJj1RK8mqIsNkBqNh0N8aKJR3f1OwOwATX+4LiHbLZqmluHGy/PkH/D2sxSDXRjnj8ShWwAkOMTnLE4QmNscunFUbYuVsmtU8TGi4X8qWS/RnEK54uyb3rqwSsI+nvDQv9sVoeSiwdj0hYEZFTQEsE2WUvanbddA1MmR7pb6P1LiN6ckIr1LxZXscLwi5/ixHfU8NxoRBu46nDY6YMFh5vwBz8OF/R2he9YuCV8cQS4Qbm6hJ3/cB1hhQY+GGlGwaKyfchlfBj1d9I6HBRBhya9Idx3oLsUiE74usk4QyAh2S8HCiM5HywMkJyM7y/uHtLJ+Qfd1Hjhqfxvno5+8iNAqsejR5jzmG0osRqYz3gIMQbBhkJCu040ybSUEVGW9fiWLR9teERskCxiFHz3YtmSixPKq29XVLQvMyVk4A3iI0i2AGeTwYvLXkdHdcsyMCL4xnq9e9dXw55IrQjKRLhBVt+zsdlrJ5oAsb35LjtVYfOBt2bH+Woy6VGtm2P/Nh71TyfvGemkgMbqNFbq7DP9D1u5ccTmgCXy3T7sw+y1iDdHYMWc/Un8ZK9uRxLezT4x78Hx6JR9AXMfV1m/o+QmNxXlWT8pCDPz04LKkF2amTn3lUihsgSVNeKHijkpqTvrkNfD+mnWNzx3FyrUZ879zPExoPOPY3k9zAoUAauRzhgWCkpQZ+oh2fQcfSw/6wzUCQgLI/3o/ZLJexxneZaN/FmMis/T9BaF7CCtgA04qR6ckS5paEIUAhPhFfd2r+aYdpQyASCCth5DtYf3bdLR9jXgcp3AMbqn/QTX9IEv1Pq3HfSGhkB3ixly/lzJiVAJVVIdn8vp0+9ksNhuOSZOCKDdK+GdGepPCZJpfg0Muo45WdPybL81e9RGg6huQeGhIC+erz+M937Ca2p2KI7MnJ858nNPCxjFHmVzZGVvI14Q0/x7WOwReEjfnDq83whSTXyo3WL8kfCfesMfv+bjvH6H0r27tybVu8f+bnCY32sMyvd75DaOqMkwpn1qx/wAxTY1uDsQB5f9CadYuoyIi0zISAbgYkoiTj3bZLhd390wdu2BxfTT5KaPttb3u7fwZGeRxWkKs4ppz1mSUn6roGAjmpcHXy8wqhMeZUCI09Kg2ETCfzmhzG8x0VmqIT2TbZ1KRr4VeeuBKxKy08fzgni9LPMPulbJZg7IknvUl4Kpl/EYCfF/D+HS8RGuHcMWSYCB6humNiiMc8+jVLMVOiNjtkjGVcvG8jj/+6nvxYnHdy9XhjeSXszSdo3IuGpSJ/aSWepuTrIaZJG3Tx8PjYvi8xLf25TL7vKfmNdo7Re0I2HL0h19DOA1/+M0Kz2ahYDWZaclolaSSt2BSMII5ES1aMBPrFyAGw95HEBVUD29w7EB3IRSNbO8TDhMbZde3PnpyxxPforBjvMuFTMosx7hnla1gdRWhG4ibyyM+BHO3hsA5STNYIKnw8W5uykZwtmG7xXIwHwYG2sLUZ6smikgfjJGbWtu2Zyc22qBhhb5PLgmOVgyvJ4DFsSYRnbVdfWHD8PNpTJYxzudtwYUXLy9iw99G3tjhYR9fbBTgvbC4EefHgHULTMQT6Tlva2vxaKd8fQ8zWrJKxWnY2O2NculN1WEt/7+w6VrxLaCzGa+ln8is85XTGdgXLKYpv0MXe+OpTWpzUsELjT2bivhWpcRIVmALW6e+22cMxz7cDRI/MHUfKdU/bq99+D6GJ5GJBWc55D2hdE+uITmwYd7EJcjVinvNkAiOpzJqlw+2MldfszCTM2JVQPaEMj20rcVUnzsRjwz59vZbM9xH4n6r3v+CMmUEd47FYLQiAiCMx2yY/JNcYjwgqn3baf/eCBQC09SKhKew1dLnUlbWvKpwI1acvX0Du/FdkieCCxOGkgI+Fi0S7nYnIlGoTSNceqaHtettpxynBY0eyrbQnBVRLdLUVXCucTR+y3Sx175MrEaej99YcSmg6hlSFb49/LIakXZpti+CkndLfaP/bOLIVHGfs1RxYViYpxxLYhmQ/S2jsAQm2t/OgheUW+U3Kn37mfzttWrblFgr37yG28BwqLv5XxRnL8QoT9eG+6ffWfhQjNZmjj7/bjoN9WO44anxrI/jy7M8TGil5fpQUWi9KlBE8BrQbPN7vQafFS9VQybC/XFpyXgDCAK2kGzNoPNVh5UQZEhyS10qgaMEOlBSGTWEDofHSo6pOXh1S+4NxTm3nV3NUObXegIvqwrnIdSxWxxEakgbfr6HEGOMn9nLm3Ds1jz3IinTl9jKTAT7xlDcPR+h7qL5xdiiSUi9NQpP95nIVgirJStUl30ztgZyEyo+pn21AN8Lh55cErMqzd7xq14Pu3TYhmz2dB0KBNxdbpWY/uRjx8OU8zCj3RHry/EuEpmPI+Bi++8KWxFZl/GwMCT+n/bsvyHfxWfyCh1XUzbHZ6yaO8xUD1RafIDTyXX/1h73MbmH/uG6TJzCZ0XuuViQH53IClDFK4xj9Fr8zzkyYBIaOW1yH/YOMp++2k9gdkzuOGl/KtTGFL078R4Tmq7d8pmE9DrSVNi3BG5vnhlSU9lESnICwRJVGZwnW+k1DwnczmjTKsSG0o6WqSNIToQmixiQzGqEZvjn8rmNfz1n5iKAA2XBfju1YrCBnjj/GNiwTjVgYYfB9MQoCSqx8rTcqSvP44fway/uEZpRo/KagYbpXv0Y4R/2ajNBHXItxBOFOeQMX6CWuwe/oO9sJGwj9WTXLbE3Xj/J+/W2FpQdY4b7biAUfLjuds2JTS9p5q9sZEoieuviy/bz73tHrhKZjSNoeYsDviiH5ZyksPoUfhf3bedr/buBzy/H3grFaoo2zinsgHHH/44QG/ZIgcFJoy6krWSB3EomsyuKVCPZAgk+eJVeMr8SIKh+ITsSR4qsiThgyj4HP5N8RX3DfMe1ItGNyx/Fy3Ysdq99+nNCYYc9/y6konwZhxg6BnwKaid8cW87OROWEYAAARlP+SKCu38zged1+wjGDv7fkxFH4y+msndm56TA08hz3IOtiXJAXxqiZ8/FYwZkg79ap9mbzCAiQieOD81Un5d5ACw62b8WdmfpO/VNf4+BZhZoxU2Vre/1w8/gFGA9BwYMaltDk2biDOocu0paoo1ieXAQbjINJXrh516HXbEu2Vtus3Y9C3/u2bVNt35+tq027H8twfC8Q1+ntPTW2H8je8wN9Q1YG/GITr8msvu3zFUJjttAxJHXtdhy2NmLMbz8dQ0oFnnbi8XaW2Yj+fnyF7IpxmxhAf4MNK24+SmjkoyW2LeLVdrmpbM7lO5psP+MFE4LNk06mE/OZIt8Uf4iHzhWHUiy1+xUrs0o8x7FX21GXR+TZiPu0Q8lcJnrqjDpV/irXgaS5nZRLF8a8f+qHCY0H4I3ycyChGJf5eaCLkSrJBMOvQNh1GagN2NPlkm/GlaGxfFjvzWMo8csKjSuQY4OmNs5tCZ3GC+esG2PQFa7fYCYSkMHgWKwWTu94vkpoWLHwpacxOGksFgQSXRytg9RLhIa4Z19paxirgo4TMnqUbKncw+obvuu34pCh22wrg2IuPbJfPbruJOlVBx4DXpGFxGNEcvMt7DA3U8er1/E3n7ysDpJvOpePGLmB77z773lC0zGEth+6k87NRn9PDCmTPBq3640TGMkMP0l97tnSno1kdRtjx93rWLHdFOyxrW7kle8i5ksQYIzvwjpicPVvuxi4n2t7GCfaZHKXfBgrjm0yrThA//LJoLrGmO12YVWIATE8ph2qBsjtPCFr+S0Q0WD9bx+uZTtifJJLeDz6+bOExhU5bMycAjuUXAPlfaDLJllsruIfPCyO5G3B2GqbBk5xpJlRutPhnmCdO4ha22OfH1pymp3D24WyxoQOw4CB2g53e3JGa7T25+lZVq2OEBua07mOxcqdfk7cry45Cb+dpad03hyP3bIOUgoWW72qo9Vn6nxcPjJ9WFXCnhJAgAwdlaCG/ix4qWKRTq3ANMqkIPY9hCZkdNtSkJz9aIlGVJwQLOdJhUrqeL9OjpHtu+2N41z18PW5vWSV9jD5bscQPnX3F2JI2qYn9EWcpQ0prnET/dZm0I78rtr3piKK9iMups0aoRnfW3U6gYDkpIRy+P2SAP1WnEMGnJz+mYxTHigVB8YWzk1tAowYwrGwumQxh3vZsPeTk4nF8nwZGycb2Pv2ZjsaylG5w+LOcXJNMH/59QcJjSeTSIgyOATSNCyt4SPhwOhgtHV2qIB688eXbfNveScNyGQ4jq0z4oVn42RSychIRBjFHLinRDajiWAsBY6bgrMKQ0fxG0cHhwzbFynlzn1/rX90qscAPfkAH8gLI/8WrL4iNFYyDPHKGj7xFHaV1PnFGHcEB2HO69wO/J5P/mVoH2NNvj5u6blinPLsHNE2Jr3X/vAUBeKVgqwTTbMRJ1toA/eEnfnsCXbMlwMm0eZY2WC1cbN9nJbtKbDsSL1/OuSUP9XPmRyWZup9siEReOlk51O6e1nmIgbwiac3Sn8Vm9Rvx5C/F0PcHuljrr+iZ4ufnD34X5m2p5fwcEY+2OGb8Ku/DRNhxXkjv4lR+tlntKd28aZs+aTlB9gi7M4eO/anl4Y+c98KJ5huxxan0IbaSx+E7TI+c8zKdaUiivOcTCDeu2x4p1o8NZltGTHDd/X1fjv4I9HH5llg8L5cY74uAeOLwzcIjWaeWyOlEpWAoKx4Xt6O64wPShoM0KMkAj2WcoyfpFJJaPTo3vSmWNukpRkl1tgz6Vcc1B8D5WCwUz8wNg/2NhuorZjxUv5NGxakrZ+aVCpW9ZpFv3QAVGm2v8X7NzwRH4+VJeZ0nCID+5Tj+tg8kdMxS9k1HBCktFYtQcBiT0a2TbtRovUq174NYAYjPCvGVUerY5cd46ivHGdQseszADlpc/3SXniJ9Wsbaov8sBX8j0/aocKBcWPgItB5rZXLhSNsYSXrY+dEiugbtJvSD2Wa2hkCr9gX0nYAAAg4SURBVPqGzn1WiKWmuZ34jush9zOYT/3HV+BofjBMCIZkJQKvv0HVMSSxMuwUz5b65xMxxR5cj98fQ0zJVTbzH9n8KFNUMPzJJcVy+lD8dfpyL/33YnaKJ5UwkSi5Y8SiVLvj6aRi44XQ1438VfZoT9cCR/qW+0KJe3GtfAbXMR+6ryGexPLVhAPlG8+xPYxXeYZ+6hP3N9u5fEeePWp8ESceP3iB0HgS8U1R2BC1USBLbXi6KH+zF6CV/SkKWlISle8Kp64smVSj0pr+l59syzcNSvkVk0iamEm7U8j4pk8mWRijO0s2YziwbOmkA2O0x//MuWiAm/6LU+JV+Xz/AV5cllht8URQt/cvWFUGOH4XVq7fpX7gaMUpgRVwAUGB026wNKbOAFzwg05BdviCwCAlatv7d0fFbdUGBmxwDXSzwTi1tDySzfnMiCXeSe8gZ1x20rVzAudYMb6t/fDPZ6AcrPLx3LawDdJf1u2XAn91strUIhjCzkT+8ch5kSdJmgiNvXR7q0trd98fvpKx/t4xxND4V2PIQtduc7S3TZxIm0UM5aszuIXAN6W7Hym2wt/zRXYWj0Dqx1ixQ2IqoWHcBjG3ZW2T2n0JMaXIyZzDMaA/+RvulQ4z3+kFd9wszBhTr/d4tkNEqm/q2GKr2sjKOOPim+1scfuIvXOv5tkj5KoW9MzxC4TGyuR1/VHAj5/+4iOW+vA+mBpILaHVmTDuzeDq7NMTFWbS+LcsGy5LifbCJBr9Mtl5QFWf8XIihw6zhIv9pWHMtrdLVnYdFafqA2cW/k6PshdmGJM3j3JiOKc7iRGb4sA+Lr5XBU+h8DFBm/GjNMkZiab1SLoHYsWKT52JlARoOs4gUgkNKxEXBCRUKSyhsz7ha+AaPku7eCszbQKkwJfX+NZNsxPynzoutudlWX9kMkrGhSypj68+kyTZEwrEmXbgskRVUURravEOoRn9IIO1zs/k7wXxJ2HqEtm2P/W7+awdA2t8l21x9mmvP0BgU7UJupOtbYV4/IwF0gzSG9ncL2wiBFvqGDKg+8tjyCgrYmjapZGaeb9J/r62hfI7SI/vHcHEiP88VvAr469PKFb5Qef8Or5KInwhcwPsnH+lHnEBm2ZJshC3XBa/x2KJ5zReaysD9rfmxkrQICvi+hDX8F6x3LsmYlcnXEM+0ZjfaafG2KPy7BFyDQb0+JeXCE0wMM0wYaya0W4crRgi/6Cg7S9Y7ouZ5EbQQ0UASl4tvdwz/PzjlzulcYBenazOXGNJy0lYGPskYGHwta083umbzXiyKDLkfWDJFTfD94y9J/X6IteRWImoGXHCTAdrv1galKNi9mLJyESwY1YmJCMwDNKSswoM3WSdxlfHFSRUwcUrDMJE1Sr/XmCYFHT/60yoK/5xXJaihtZEaBwHs9NJP3VMOIbciLjVbmKsQ+svfslZYsg/y+Dfh8AY77uAvTspl67jxWP52xurYzGujiEORbWFpa7+ZgwJRetgHqdPBG2/yp1YIHvlioAq4GdunB2JtWJFtvVVhQE+worQXIGfcsPal7xSzfFt+97eU69XvPT8Ir1H3jFSM+YAjx3Ck5/HtHNk7jDxjpFrGOqDX14iNNqMacEZM1qWT/guCxIbzbjFhPFJdrtO4pyxLgWery+l88JkK6utx0z+SEjLtpFXtN/GnYBJWE5j5/Zl80a1OZdkDRuY1WY14B0B/DScGi//mx0QYyFLH27XJrPZwA/EyoOPVRJ8TwjWqH2ZcdgTJdkCh2l5zIPGmARNVs5OKinWEpza5Kdtnk4iN+0repsQOG6V0FKXVqEbRBm+WBADMbAq2uWGfQk2YfRKkuwfto97vYoXwe5t2QeBypfVmFwvq1e8azMi/La0QpHdb33yNv36xldPGh1DrBpt9u1V7X8hhqxMYyY1iA2MKWZ18KPhIY+wvblCbTEwlr+jL68MP2ys/mCGT8ajGboq8oCWrUQwREDm2Is7zed4D+O24iZ0asvXtX0u0yveeQWIE9bo0/pWZXS4t35h3NXWBciAKo/kzs/77Xi8ELHyIkLNpfeOl3n2ELnqQB87fo3QxIwOSR+gJSuOYE1wEtBMSPXalWE8JvihVz3sAIf2+qsb+7KKAv2+WhpZjPxpFYgkLNr6qVNWaq72XI6HwFTOu19wifGnBP2V/dgszuJFx5BfqaJvEepeFbHkC/hPJFj4j/2Wn+ZTB4agbxltN/qzCLxIaEBIx3VREZlc6pmDeH6vrPxnh9u9PY7AGHios5g5Yrb/7kbWxyX5vVeq1FxnSJjNlcCsoIwArZnb7x3Qz0rWMeRn8f5NvZW9Jsod20+rWGGPXfybqpyxhyYu6IP/ZwReJzSOmjZ/Xjfl6qnszmWo/2eo/52xP11N+XeG3iP5BgQ6hnwDqH+sSW3K1+fX4ttS79fX9RX/Twi8TWj+n8DqsTYCjUAj0Ag0Ao3A70SgCc3v1EtL1Qg0Ao1AI9AINAJPINCE5gmw+tJGoBFoBBqBRqAR+J0INKH5nXppqRqBRqARaAQagUbgCQSa0DwBVl/aCDQCjUAj0Ag0Ar8TgSY0v1MvLVUj0Ag0Ao1AI9AIPIFAE5onwOpLG4FGoBFoBBqBRuB3ItCE5nfqpaVqBBqBRqARaAQagScQaELzBFh9aSPQCDQCjUAj0Aj8TgSa0PxOvbRUjUAj0Ag0Ao1AI/AEAk1ongCrL20EGoFGoBFoBBqB34lAE5rfqZeWqhFoBBqBRqARaASeQKAJzRNg9aWNQCPQCDQCjUAj8DsRaELzO/XSUjUCjUAj0Ag0Ao3AEwj8D/flwdTOGft5AAAAAElFTkSuQmCC" /><br />
<br />
While looking through some of the RTL source code, in the quest for the "Tablet" mode (see my previous post from yesterday) - I stumbled upon the TDeviceInfo.TDeviceClass enum.<br />
<br />
<a name='more'></a><br />
So my small application has just listed the ID and the name of the TDeviceClass enum, for my old Surface Pro - together with one I added for fun. The one I added has a strange TDeviceClass value.<br />
<br />
Following that is the full list of named enum values of TDeviceInfo.TDeviceClass - notice the last 3.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYWw7YILbOUjvazFkCNON0c8M3VwyptedgNbO6pg1tGTsnz9fOoO7_kBdhbrRiLqUk1X94YqjI87aLBfPZiU6GUMlKeVc5Cio__TilUD1ruJWKPd14YsTzAmylvIqdYmMsGvY88Bbzd3TE/s1600/dandD.JPG" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="548" data-original-width="417" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYWw7YILbOUjvazFkCNON0c8M3VwyptedgNbO6pg1tGTsnz9fOoO7_kBdhbrRiLqUk1X94YqjI87aLBfPZiU6GUMlKeVc5Cio__TilUD1ruJWKPd14YsTzAmylvIqdYmMsGvY88Bbzd3TE/s320/dandD.JPG" width="243" /></a></div>
<br />
Maybe D&D does not refer to Dungeons & Dragons, but DeviceInfos & DeviceClasses.... well someone had fun at some point of time sneaking them in, and I hope that Embarcadero does not plan to remove those extra "classes" - since the might break backwards compatibility - with my newly added Rincewind deviceInfo :D<br />
<br />
Just a small silly finding I wanted share - nothing useful - but no post without code :)<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">uses</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> System.Devices,</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> System.TypInfo;</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">procedure TForm1.Button1Click(Sender: TObject);</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">var</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> s: TSize;</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">begin</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> // Let us add a silly device</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> s.cx := 100;</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> s.cy := 100;</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> TDeviceInfo.AddDevice(TDeviceInfo.TDeviceClass.Wizard, 'Rincewind', s, s, pfLinux, 96);</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> // Let us see what are known</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> Memo1.Lines.Add('-- Known devices: Name and Class');</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> for var d: Integer := 0 to TDeviceInfo.DeviceCount-1 do</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> Memo1.Lines.Add(TDeviceInfo.Devices[d].ID+' - '+</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> GetEnumName(TypeInfo(TDeviceInfo.TDeviceClass), ord(TDeviceInfo.Devices[d].DeviceClass)));</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> // Let us see possible value of a TDeviceClass</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> Memo1.Lines.Add('');</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> Memo1.Lines.Add('-- TDeviceClass enums:');</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> for var i: TDeviceInfo.TDeviceClass :=</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> Low(TDeviceInfo.TDeviceClass) to High(TDeviceInfo.TDeviceClass) do</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"> Memo1.Lines.Add(GetEnumName(TypeInfo(TDeviceInfo.TDeviceClass), ord(i)));</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">end;</span><br />
<br />
Enjoy<br />
<br />
<b>Update:</b> Brian Long has already mentioned this in #24 of his beatiful blong #Delphi25 post <span style="color: blue;"><a href="http://blog.blong.com/2020/02/25-delphi-tips-for-25-years-of-delphi.html" style="color: blue; font-weight: bold;">here</a><b style="color: blue;"> - </b>which I originally missed.</span><br />
<br /><b>Another update:</b> Jim McKeeth informed me that the official DocWiki now does included the "Fantasy devices": <a data-saferedirecturl="https://www.google.com/url?q=https://docwiki.embarcadero.com/Libraries/en/System.Devices.TDeviceInfo.TDeviceClass&source=gmail&ust=1704830206589000&usg=AOvVaw2P-chk_lpgpZpT3c38Ai0N" href="https://docwiki.embarcadero.com/Libraries/en/System.Devices.TDeviceInfo.TDeviceClass" style="background-color: white; font-family: Arial, Helvetica, sans-serif; font-size: small;" target="_blank"><b><span style="color: #2b00fe;">https://docwiki.embarcadero.<wbr></wbr>com/Libraries/en/System.<wbr></wbr>Devices.TDeviceInfo.<wbr></wbr>TDeviceClass</span></b></a> with proper description :) Thanks Jim.<div><br /></div><div><table style="background-color: #f8f9fa; border-collapse: collapse; border: 1px solid rgb(162, 169, 177); color: #222222; font-family: "Open Sans", sans-serif; font-size: 14px; margin: 1em 0px;"><tbody><tr valign="top"><td style="background-color: whitesmoke; border: 1px solid rgb(212, 212, 212); color: black; margin: 0px; padding: 0.2em 0.4em;"><p style="line-height: inherit; margin: 0.5em 0px;"><code style="background-color: transparent; border-radius: 2px; border: none; font-family: monospace, monospace; font-size: 1.15em; padding: 1px 4px;">Elf</code></p></td><td style="background-color: whitesmoke; border: 1px solid rgb(212, 212, 212); color: black; margin: 0px; padding: 0.2em 0.4em;"><p style="line-height: inherit; margin: 0.5em 0px;">Tiny being with pointed ears.</p></td></tr><tr valign="top"><td style="background: rgb(255, 255, 255); border: 1px solid rgb(212, 212, 212); color: black; margin: 0px; padding: 0.2em 0.4em;"><p style="line-height: inherit; margin: 0.5em 0px;"><code style="background-color: transparent; border-radius: 2px; border: none; font-family: monospace, monospace; font-size: 1.15em; padding: 1px 4px;">Dwarf</code></p></td><td style="background: rgb(255, 255, 255); border: 1px solid rgb(212, 212, 212); color: black; margin: 0px; padding: 0.2em 0.4em;"><p style="line-height: inherit; margin: 0.5em 0px;">Short being that dwells in mountains and is associated with smithing, mining and crafting.</p></td></tr><tr valign="top"><td style="background-color: whitesmoke; border: 1px solid rgb(212, 212, 212); color: black; margin: 0px; padding: 0.2em 0.4em;"><p style="line-height: inherit; margin: 0.5em 0px;"><code style="background-color: transparent; border-radius: 2px; border: none; font-family: monospace, monospace; font-size: 1.15em; padding: 1px 4px;">Wizard</code></p></td><td style="background-color: whitesmoke; border: 1px solid rgb(212, 212, 212); color: black; margin: 0px; padding: 0.2em 0.4em;"><p style="line-height: inherit; margin: 0.5em 0px;">Being who practices magic, also known as magician or mage.</p></td></tr></tbody></table><br />
BTW: I used the DNDC font from <a href="https://famfonts.com/dungeons-and-dragons/"><span style="color: blue;"><b>famfonts.com</b></span></a><br />
<br /></div>Steffen Nyelandhttp://www.blogger.com/profile/14576369608629841090noreply@blogger.com0tag:blogger.com,1999:blog-3386248074980233262.post-86061926734152112132020-06-17T23:12:00.000+02:002020-06-17T23:13:06.779+02:00A cure for Win10 in Tablet mode<h3>
- or how to detect tablet mode in Windows 10 from Delphi.</h3>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHkS5xF5c9u0gj4ovVeZGTrNE7N224PzhTgSQ_vTnufDA3JiHi04eOcUds1JD3oS8SSCkIJ8nxZPYnI9hmhVRHBI3NIOa0y77Slq24MOa5ICwfcQlxjH6F5rdndP51WccTxBUGP8SyPVoX/s1600/UserInteractionmode.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="310" data-original-width="460" height="215" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHkS5xF5c9u0gj4ovVeZGTrNE7N224PzhTgSQ_vTnufDA3JiHi04eOcUds1JD3oS8SSCkIJ8nxZPYnI9hmhVRHBI3NIOa0y77Slq24MOa5ICwfcQlxjH6F5rdndP51WccTxBUGP8SyPVoX/s320/UserInteractionmode.JPG" width="320" /></a></div>
<br />
Sorry about the cure-tablet pun, but Windows 10 is not able yet to enter Pill mode.<br />
<br />
This small post is actually also about wrapping Win10 SDK apis, that might not have been fully implemented as Delphi classes/interfaces or are missing because of their little use - which incidentally is that ones I often miss.<br />
<br />
I was getting a bit frustrated refactoring some old code, and thought that it would be fun to add a Windows 10 feature to the project instead - and since I was sitting on an old Surface - I could switch that into "Tablet" mode.<br />
<br />
<a name='more'></a><br />
But my internet search was a bit disappointing - one SO entry that suggested to use an external DLL - and then the reference guide from <a href="https://devblogs.microsoft.com/oldnewthing/?p=93815"><b><span style="color: blue;">Raymond Chen</span></b></a>, which is always great, but his examples are still not in Delphi or ObjectPascal.<br />
<br />
I was pretty sure the unit Winapi.UI.ViewManagement had everything I needed, so in my head the test code would look something like this - which was a lot nicer than the SO and the C++ samples:<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">uses</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> Winapi.UI.ViewManagement;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span>
<span style="font-family: "courier new" , "courier" , monospace;">procedure TForm1.Button1Click(Sender: TObject);</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">var</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> UI: TUIViewSettings;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> UIView: IUIViewSettings;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">begin</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> UI := TUIViewSettings.Create;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> try</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> UIView := UI.GetForWindow(Self.Handle);</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> case UIView.UserInteractionMode of</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> 0: Memo1.Lines.Add('Normal mouse mode.');</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> 1: Memo1.Lines.Add('Tablet mode.');</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> end;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> finally</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> UI.Free;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> end;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> end;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">end;</span><br />
<br />
..but no GetForWindow was implemented, so no interoperability between the UWP/WinRT view and my window handle. The implementation of IUIViewSettingsInterop was missing. The cause is probably that the interop header is located somewhere completely different compared to some of the other interop headers - and just looking at Raymonds post with reference to WRL, which I guess you now should move to WinRT and then to ??<br />
<br />
Keeping track of the ever changing landscape of Windows SDKs and UI frameworks is not what I spend most of my time on - but in these days one might hope for a "reunion" - we just had a 100th year anniversary on one, from the part in Denmark where I was born. See <a href="https://genforeningen2020.dk/english/"><span style="color: blue;"><b>Genforeningen2020</b></span></a><br />
<br />
It had been years since I had converted .h headers or created .tlb from .idl files, which was the route I was sure I had to ascend down.<br />
<br />
After not having much success, I swallowed my pride, and asked some grown-ups for help - but it seemed that no-one had had the stupid idea before me.<br />
<br />
But that actually made me step back - and think the answer is in the source - Embarcadero had implemented other WinRT interfaces - just not the one I needed, so given the "Windows 10 SharingContract" sample I found an example on how the WinAPI.ApplicationModel.DataTransferInterop interface was implemented, using the TWinRTImportHelper class and others. So the unit System.Win.WinRT is you little helper.<br />
<br />
I did, for now, end up messing up a local copy of Winapi.UI.ViewManagement.pas - so these are the additions - that I will put in a separate unit - until Embarcadero hopefully gets them included - vote for <a href="https://quality.embarcadero.com/browse/RSP-29682"><b><span style="color: blue;">RSP-29682</span></b></a> :)<br />
<br />
I added these lines:<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">// Interop Intf: "IUIViewSettingsInterop"</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> IUIViewSettingsInterop = interface(IInspectable)</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> ['{3694DBF9-8F68-44BE-8FF5-195C98EDE8A6}']</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> function GetForWindow(appWindow: THandle; const riid: TGUID): IUIViewSettings; safecall;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> end;</span><br />
<br />
and changed this:<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">TUIViewSettings = class(TWinRTGenericImportS<IUIViewSettingsStatics>)</span><br />
to<br />
<span style="font-family: "courier new" , "courier" , monospace;">TUIViewSettings = class(TWinRTGenericImportSO<IUIViewSettingsStatics, IUIViewSettingsInterop>)</span><br />
<br />
All done - and now the actual working test code example looks like this:<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">uses</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> Winapi.CommonTypes,</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> System.Win.WinRT,</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> Winapi.UI.ViewManagement;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">...</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">procedure TForm1.Button1Click(Sender: TObject);</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">var</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> UI: TUIViewSettings;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> UIView: Winapi.UI.ViewManagement.IUIViewSettings;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">begin</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> if TOSVersion.Check(10) then</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> begin</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> UI := TUIViewSettings.Create;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> try</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> UIView := UI.Interop.GetForWindow(Self.Handle, TWinRTImportHelper.GetUIID<IUIViewSettings>);</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> case UIView.UserInteractionMode of</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> UserInteractionMode.Mouse: Memo1.Lines.Add('Normal mouse mode.');</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> UserInteractionMode.Touch: Memo1.Lines.Add('Tablet mode.');</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> end;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> finally</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> UI.Free;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> end;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> end;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">end;</span><br />
<div>
<br /></div>
Thanks to Embarcadero for bringing me out of my comfort zone - looking at .h and .idl files again, just to let me discover the WinRT helpers they DID provide - the answers are in the source.<br />
<br />
Enjoy.Steffen Nyelandhttp://www.blogger.com/profile/14576369608629841090noreply@blogger.com5tag:blogger.com,1999:blog-3386248074980233262.post-71263482666746947172020-05-24T16:22:00.000+02:002020-05-24T17:16:08.427+02:00May the "Default" be with you<h3>
- or bit of housekeeping in some very old code.</h3>
Since we are still in the month of May - I though I could get away with that title - but this small post is not about calendar months or Star Wars.<br />
<br />
I have not been very active here for a while - but I wanted to write a bit on the lack of use of <span style="font-family: "courier new" , "courier" , monospace;">StrToXDef</span>, <span style="font-family: "courier new" , "courier" , monospace;">TryStrToX</span> and overloaded constructors that now have been in the RTL for many years.<br />
<br />
Just preparing an old Delphi project for the upcoming 10.4 release (real soon now) - and I stumbled upon some horrid old code like this:<br />
<br />
<br />
<a name='more'></a><br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">S := Character.Properties[ 'Distance' ];</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">try</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> if S = '' then</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> iDistance := 175</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> else</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> iDistance := StrToInt( S );</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">except</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> iDistance := 175;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">end;</span><br />
<br />
and this:<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">S := LowerCase( Character.Properties[ 'PlaySFXOther' ] );</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">try</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> if S = '' then</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> bPlaySFXOther := false</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> else if S = 'true' then</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> bPlaySFXOther := true</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> else</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> bPlaySFXOther := False;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">except</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> bPlaySFXOther := false;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">end;</span><br />
<br />
Which can in later versions be written like:<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">iDistance := StrToIntDef( Character.Properties[ 'Distance' ], 175 );</span><br />
<br />
and this:<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">bPlaySFXOther := StrToBoolDef( Character.Properties[ 'PlaySFXOther' ], False );</span><br />
<br />
The above code is from the Siege Of Avalon game - originally written in Delphi 4, and it illustrates that non-maintained code rots over time, and that one should also utilize "helper" functions that improves readability, or even better keep up with the language and RTL changes introduced in newer versions.<br />
<br />
Another example is the TStringList constructor - originally you needed to do this for a sorted case-insensitive list that ignores duplicates:<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">var</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> strListOld :TStringList;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">begin</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> strListOld := TStringList.Create;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> strListOld.Duplicates := dupIgnore;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> strListOld.Sorted := True;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> strListOld.CaseSensitive := False;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> //...</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> strListOld.Free;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">end;</span><br />
<br />
compared to one of the overloaded constructors, and inline variables from 10.3 onward:<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">var strListNew: TStringList := TStringList.Create(dupIgnore, True, False);</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">//...</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">strListNew.Free;</span><br />
<br />
Faster to write and a lot more readable. There are many example like this where either overloaded or class helpers makes code more writable and readable.<br />
<br />
I have been given permission to talk about 10.4 - and having now worked a bit with it - it is great to be able to use code- and error insight together with the new language features from the last version without getting squiggly red lines - all now thanks to the async Delphi LSP process. The "insights" now see what the compiler sees.<br />
<br />
The speed improvements in the IDE and generated code is also noticeable, and I look forward to refactor a lot more old code - so that it actually is a joy to work with. I am very pleased about where this is going.<br />
<br />
Which finally brings me to the point of this post: Keep you code fresh and updated! This goes for all languages and applications.<br />
<br />
I would rather work with "old" maintained and tested code - than getting frustrated with new and untested code.<br />
<br />
The worst yet is unmaintained old code, which brings bad reputation to both the application and the language.<br />
<br />
My fork of the Siege Of Avalon game engine can be found on <a href="https://github.com/SteveNew/Siege-of-Avalon-Open-Source">https://github.com/SteveNew/Siege-of-Avalon-Open-Source</a>.<br />
<br />Steffen Nyelandhttp://www.blogger.com/profile/14576369608629841090noreply@blogger.com0tag:blogger.com,1999:blog-3386248074980233262.post-28523651994065841292019-06-12T22:39:00.000+02:002019-06-12T22:39:54.046+02:00Bringing the POX cross-platform<h3>
- or a few things on the extended RTTI and new inline variables presented in 10.3.</h3>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-SYYiSmHDk7hVgGpJmgsCXTqAGBe7nXMM0VhZFi6eEbYRbqzey89_kjXEGeVX3xfmXLDNAsA3y_10AOYEsPp-BhrECD1GlfpXnv8NsAPzbTc96SQbaaafCzCj25EtbTFU_wK3KRhYSfks/s1600/ultimatePOXViewer.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="571" data-original-width="928" height="196" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-SYYiSmHDk7hVgGpJmgsCXTqAGBe7nXMM0VhZFi6eEbYRbqzey89_kjXEGeVX3xfmXLDNAsA3y_10AOYEsPp-BhrECD1GlfpXnv8NsAPzbTc96SQbaaafCzCj25EtbTFU_wK3KRhYSfks/s320/ultimatePOXViewer.png" width="320" /></a></div>
<br />
Having figured out the internals of the <a href="https://fixedbycode.blogspot.com/2019/06/cure-for-pox-found-in-avalon.html" target="_blank"><b><span style="color: blue;">POX file format</span></b></a> used in the game Siege of Avalon, and it's sibling from the modding community: Ashes of Avalon, Pillars of Avalon and Days of Ahoul to name the larger ones - enables to revise the tooling used for the engine - most of it closed source or requiring jumping through hoops while juggling.<br />
<br />
So the first thing I brewed together is a POX file viewer/player with an export function. A preliminary test was done in the previous post, but I redid it using the FMX multi-device UI framework that comes with RAD Studio/Delphi/C++Builder - so that modders using for instance macOS, do not need to run in a virtual OS - and it all comes for "free" when using FMX.<br />
<br />
<a name='more'></a><br />
Since it is WIP, I only want to go through a few parts of the code - but the full source can be found in the Src/New Tools folder in my fork of the Siege of Avalon GitHub repo <a href="https://github.com/SteveNew/Siege-of-Avalon-Open-Source/tree/master/src/NewTools/POXFile" target="_blank"><b><span style="color: blue;">here</span></b></a>.<br />
<br />
Also feel free to point at any errors or improvements that could be made - it only makes the code better and all of us smarter - and I am sure it can be done better :)<br />
<br />
The POX files contains data for most assets the game engine uses - they typically have a "setting/property" part, in the form of a .ini file structure, and a run length encoded (RLE) part containing the pixel/bitmap data.<br />
<br />
<h4>
Ini file data structure from string</h4>
Since the ini file data is embedded in the POX file, we end up with a list of strings containing the info we want to parse, as if it was an .ini file. Since the constructor of TMemIniFile does require a filename, it is not given that we instead can populate its data, calling SetStrings, after having created it with an empty filename - like below:<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">ini := TMemIniFile.Create('');</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">try</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> ini.SetStrings(iniData);</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> var actionStr := ini.ReadString('HEADER', 'Actions', '');</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">finally</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> ini.Free;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">end;</span><br />
<br />
There is a GetStrings as well, if you need to create data structured as .ini data, and just wants to end up with a list of strings;<br />
<br />
<h4>
Accessing memory using TMemoryStream</h4>
A similar issue, do we have with the raw RLE data already read from the POX file, since the header for every frame has a pointer to the RLE data that needs decoded, and just using a TMemoryStream seemed more readable that pointer arithmetic :). But what we need is protected, so the usual hack applies, by class inheritance to get access to call SetPointer:<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">TROMemoryStream = class(TMemoryStream)</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">private</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> OrigPtr : Pointer;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> OrigSize : LongInt;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">public</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> procedure SetMemPointer(Ptr: Pointer; newSize: Longint);</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> destructor Destroy; override;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">end;</span><br />
<br />
As in most things in life, you should put things back to the state you found them (or a better state), so we store the original properties as they were created, before we point the memory stream to the new memory location.<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">destructor TROMemoryStream.Destroy;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">begin</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> SetPointer ( OrigPtr, OrigSize );</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> inherited;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">end;</span><br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">procedure TROMemoryStream.SetMemPointer(ptr: Pointer; newSize: Longint);</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">begin</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> OrigPtr := Memory;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> OrigSize := Self.Size;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> SetPointer ( ptr, newSize );</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">end;</span><br />
<br />
And use it like this:<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">rleData := TROMemoryStream.Create;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">rleData.SetMemPointer(rle.DataPtr, rleSize);</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">rleData.Position := 0;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">rleData.Read(&c, 1);</span><br />
...<br />
<br />
<h4>
Inline variables and RTTI on enumerations</h4>
Since I do want the ability to "play" the frames ordered by the type of action they represent, and the direction they are heading - instead of just playing all frames in stored order - some frames are used more than once, and in different order. The ini data has this information - both which action that are available for the "asset" stored in the POX file, and what directions - of possible 9 it "moves" in.<br />
<br />
So two enumerations is defined, that actually matches the stored properties/values in the ini data.<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">TActionEnum = ( Stand, Attack1, BowAttack, Cast, Pain, Death, Walk, Run, Default, Explode, Sit, Reveal, Hide );</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">TDirectionEnum = ( NWFrames, NNFrames, NEFrames, EEFrames, SEFrames, SSFrames, SWFrames, WWFrames );</span><br />
<br />
and a list type for the frames, that gets added to a dictionary with a key expressing action and direction.<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">TFrameList = class(TList<Integer>);</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">movements: TObjectDictionary<Byte, TFrameList>;</span><br />
<br />
An example of the ini data, shows that each action has 8 directions stored as a list of frames, and a "behavior" tag - which we want to ignore for now.<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">[Action Pain]</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">NWFrames=97,98,99,99,98,97,END</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">NNFrames=105,106,107,107,106,105,END</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">NEFrames=113,114,115,115,114,113,END</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">EEFrames=121,122,123,123,122,121,END</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">SEFrames=129,130,131,131,130,129,END</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">SSFrames=137,138,139,139,138,137,END</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">SWFrames=145,146,147,147,146,145,END</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">WWFrames=153,154,155,155,154,153,END</span><br />
<br />
By going through the TActionEnum....<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">for var action := Low(TActionEnum) to High(TActionEnum) do</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> addFrames(action);</span><br />
<br />
...for each "Action", the TDirectionEnum directions "framelist" are read...<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">procedure addFrames(action: TActionEnum);</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">begin</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> for var direction := Low(TDirectionEnum) to High(TDirectionEnum) do</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> begin</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> var Frames := ini.ReadString('Action '+TRttiEnumerationType.GetName(action), TRttiEnumerationType.GetName(direction), '');</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> var i: integer;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> if Frames<>'' then</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> begin</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> var frameList: TFrameList := TFrameList.Create;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> for var str: string in Frames.Split([',']) do</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> begin</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> if TryStrToInt(str, i) then</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> frameList.Add(i);</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> end;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> movements.Add(ord(action)*10+ord(direction), frameList);</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> end;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> end;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">end;</span><br />
<br />
..and converted into a list of integers, and added to the movements dictionary. Notice the use of TRttiEnumerationType from the RTTI unit - since we have the info anyway - let us use it.<br />
<br />
So now we can "play" a specific list of frames by looking up by action+direction.<br />
<br />
<h4>
RLE decoding, colour convertion and pixels </h4>
We need to do 3 things here, decode the run length encode data, convert the RGB565 pixelformat and then set the pixels colour in the bitmap.<br />
<br />
To access the bitmap we need to map into a TBitmapData structure, set the pixels and unmap the data back - we add the bitmap to a list of bitmaps afterwards - and to show the bitmap we can just assign the bitmap to the images' bitmap property - so no need for draw or copy functions.<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">var</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> i : integer;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> c : byte;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> colour: word;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> pxCol: TAlphaColorRec;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> rleData: TROMemoryStream;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> bmpData: TBitmapData;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">begin</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> pxCol.A := $FF;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> if bitmap.Map(TMapAccess.Write, bmpData) then</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> begin</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> var x: Integer := 0;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> var y: Integer := 0;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> rleData := TROMemoryStream.Create;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> rleData.SetMemPointer(rle.DataPtr, rleSize);</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> rleData.Position := 0;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> rleData.Read(&c, 1);</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> while (c > 0) and (c < 4) do</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> begin</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> case c of</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> 1 : begin // colour/pixel data</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> rleData.Read(&i, 4);</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> while i > 0 do</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> begin</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> rleData.Read(&colour, 2);</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> pxCol.B := (Colour and $1F) shl 3;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> pxCol.G := ((Colour and $7E0) shr 5) shl 2;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> pxCol.R := ((Colour and $F800) shr 11) shl 3;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> bmpData.SetPixel(X+rle.AdjX, Y+rle.AdjY, pxCol.Color);</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> inc(x);</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> dec(i);</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> end;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> end;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> 2 : begin // add x offset</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> rleData.Read(&i, 4);</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> i := i div 2;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> inc(x, i);</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> end;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> 3 : inc(y); // new line</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> end;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> rleData.Read(&c, 1);</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> end;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> FreeAndNil(rleData);</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> bitmap.Unmap(bmpData);</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> end;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">end;</span><br />
<br />
The pixelformat convertion seems correct, but I do have a "alternative" at hand - where values were found by brute force:<br />
<br />
R := (r * 527 + 23 ) shr 6;<br />
G := (g * 259 + 33 ) shr 6;<br />
B := (b * 527 + 23 ) shr 6;<br />
<br />
And hopefully this is fast enough and correct - since going from $FFFF number of colors to $FFFFFF does give a bit of room for the originators of the POX algorithm to play some tricks on us.<br />
<br />
The FMX TAlphaColorRec structure is actually quite nice and readable - not having to convert back and forth.<br />
<br />
I do like to use inc(x, y) - instead of x := x + y - and sprinkled with the new inline variables can make things more compact and still very readable.<br />
<br />
<h4>
Two sinful things to minimize code - tags and string enumeration</h4>
In the initial code I put into the repo in a sec, both some features are missing and it does not handle some specific scenarios like some sprite object items - try SmallSack.pox - since it does not have any directions - just one image - but everything is on the TODO :)<br />
<br />
It does also contain examples of code that I normally consider bad practices. Since this is a small simple tool to handle a simple task - this is a bit quick and dirty.<br />
<br />
I mention them here so that you do not included this type of code into big life-preserving software :)<br />
<br />
To get the ordinal value of the current selected action type I do like this:<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">currentAction := ord( TRttiEnumerationType.GetValue<TActionEnum>(TRadioButton(Sender).Text) );</span><br />
<br />
Let us hope that I never decide to translate the UI - that will break the code. But I only need one line of code to handle all the 13 action type radiobuttons. What I might do is to make the list dynamic - driven by the enum.<br />
<br />
This was also an example of using the RTTI the other way round - getting the enum value by name.<br />
<br />
The second example is "tag programming" - I have the 8 (9) directional buttons, where I on the onClick set the chosen direction:<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">currentDirection := TSpeedButton(Sender).Tag;</span><br />
<br />
So every button component has it's tag property set to the LSD (least significant digit - I think I just made that TLA up :D), of the key value needed to lookup the movements dictionary.<br />
<br />
I would have taken a picture of a macOS build, but my wifes miniMac is not update-able after El Capitan.... So Apple is tightening the ropes - if I need to build for macOS/iOS in the near future - lets see if she wishes for a newer miniMac ;D<br />
<br />
Enjoy.Steffen Nyelandhttp://www.blogger.com/profile/14576369608629841090noreply@blogger.com4tag:blogger.com,1999:blog-3386248074980233262.post-79207537624227766632019-06-08T11:44:00.002+02:002019-06-08T11:44:32.424+02:00Cure for POX found in Avalon<h3>
- or how reverse engineering makes a game engine portable.</h3>
<div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEii801wsD8ECYeJtHHLkE2C28cevIdJ5WbYPAZOfBxUaTFFluYwo3yjKTwEMQ_bESrZQxvoQ3VW53i9TMEmQV4lRL51h4GyEk3MpyJP4BkY43-PQ76kk1zF5Cd1aM9MeNyMfUsNFuAGCmc_/s1600/POXView.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="610" data-original-width="627" height="311" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEii801wsD8ECYeJtHHLkE2C28cevIdJ5WbYPAZOfBxUaTFFluYwo3yjKTwEMQ_bESrZQxvoQ3VW53i9TMEmQV4lRL51h4GyEk3MpyJP4BkY43-PQ76kk1zF5Cd1aM9MeNyMfUsNFuAGCmc_/s320/POXView.png" width="320" /></a></div>
<br /></div>
<div>
One thing that can stop software from being portable and surviving the decay of time, is when closed and proprietary file formats block any chance of progress - the game <b>Siege of Avalon</b> has the POX.</div>
<div>
<br />
<a name='more'></a></div>
<div>
I did pick up the ball earlier this year to move the <a href="https://github.com/SteveNew/Siege-of-Avalon-Open-Source" target="_blank"><b><span style="color: blue;">Siege of Avalon OS game "engine"</span></b></a>, that was open sourced back in mid-2003, onto newer version of the compiler, and eventually to other platforms - a thing that had been the original plan for opening the source. Read about it <a href="https://fixedbycode.blogspot.com/2019/03/siege-of-avalon-lifted.html" target="_blank"><b><span style="color: blue;">here</span></b></a>.</div>
<div>
<br /></div>
<div>
But after having succeeded with some of the initial issues - I hit the roadblock that might have been the cause that this game hasn't been "ported" yet - the POX file format.</div>
<div>
<br /></div>
<div>
The POX files contains data for most assets the game engine uses - they typically have a "setting/property" part, in the form of a .ini file structure, and a run length encoded (RLE) part containing the pixel/bitmap data.</div>
<div>
<br /></div>
<div>
That would be all fine and good, if it hadn't been for the usage of special "drivers" in the form of close-sourced DLLs - provided by a deceased third-party - and not part of the open sourced code. The main job of these are to decode the RLE image data and "draw" this to a bitplane.</div>
<div>
<br /></div>
<div>
So it is not that RLE is a way to encrypt anything, but just to optimize the storage and possibly performance - which made very much sense back when the game was done - and still does.</div>
<div>
<br /></div>
<div>
Basically does RLE mean that you "compress" repetitive data - by just storing the length of "what". But the way it is done, varies with what data you store - so no encoding is the same.</div>
<div>
<br /></div>
<div>
I had been looking at what I refer to as the DigiFX (that use the DLLs) pox in the code, and hoped that it would just go away, but no..</div>
<div>
<br /></div>
<div>
So having a few days off - I actually found my old IDA Pro Free - had actually motivated myself into how great it would be to get my lack of x86 assembly skills challenged. But I did a last search on the SoA forums - and tada! Someone had back in mid 2011, actually done what I planned to do, figuring out how the POX RLE data was decoded - and even left some C pseudo code:</div>
<div>
<br /></div>
<div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">void UnpackRLE(istream& fin, Bitmap* pixelmap, RLEHDR* rlehdr)</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">{</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">int x=0, y=0;</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">char c;</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">short colour;</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">int i;</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">while(1) {</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">c = fin.get();</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">switch(c) {</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">case 1: // colour/pixel data</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">for( fin.read(&i,4); i > 0; i-- ) {</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">fin.read(&colour, 2);</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">pixelmap->setPixel(x + rlehdr->AdjX , y + rlehdr->AdjY, colour);</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">x++;</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">break;</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">case 2: // add x offset</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">fin.read(&i, 4);</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">i >> 1;</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">x += i;</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">break;</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">case 3: // new line, carriage return (y++, x=0)</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">y++;</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">break;</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">case 0: // end of rle stream</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">return;</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">default:</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">throw;</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">}</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">}</span></div>
</div>
<div>
<br /></div>
<div>
That enabled me to finish my document on the "POX (Proprietary Object eXtension) file format" including some Pascal pseudo code and put that onto my SoAOS GitHub repo <a href="https://github.com/SteveNew/Siege-of-Avalon-Open-Source/blob/master/POXFileFormat.md" target="_blank"><b><span style="color: blue;">here</span></b></a>.</div>
<div>
<br /></div>
<div>
There is still much left to do - but the pox of the POX is cured. I did a small PoC application as shown in the start - and I will just move that to FMX, and fix the colour/pixelformat issue - and test that it can handle all the various resource types.</div>
<div>
<br /></div>
<div>
I know that as an alternative it had been suggested/tried to just convert the POX files into a different "better" format - utilizing PNG files - that would also be a better format for SDL2 - using the alpha channel.</div>
<div>
<br /></div>
<div>
But the user would still have to run the conversion on Windows due to the DLLs, and since assets (apart from the first chapters) are not free (and there is also all the mods) - these could not be provided "converted".</div>
<div>
<br /></div>
<div>
My take on the work of the engine is also to keep it compatible with the original assets - so that you can copy the asset files to a new platform together with a fresh native "engine" :)</div>
<div>
<br /></div>
<div>
And do remember that reverse engineering, can be a good thing under fair use - and here we have a "clean room" example - someone provided me with the spec - the pseudo C code - and from there I could implement code, that could decode the data.</div>
<div>
<br /></div>
<div>
But always thread carefully - there could have been a EULA or TOC that would have prohibited you from solving the wish of interoperability :)</div>
<div>
<br /></div>
<div>
Read EFF.orgs "Coders’ Rights Project Reverse Engineering FAQ", <a href="https://www.eff.org/issues/coders/reverse-engineering-faq" target="_blank"><b><span style="color: blue;">here</span></b></a>. </div>
<div>
<br /></div>
<div>
/enjoy</div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
Steffen Nyelandhttp://www.blogger.com/profile/14576369608629841090noreply@blogger.com0tag:blogger.com,1999:blog-3386248074980233262.post-12883373449499651332019-06-05T15:28:00.000+02:002019-06-05T15:28:34.482+02:00Take a "leak"<h3>
- or introducing Deleaker - now available for Delphi/C++Builder/RAD Studio</h3>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwW70bYcnaF2eBwt2cKXgmfElS5zDEmhM57Fq3hAISp19YzCl482M8KpX6OALA0eKiSxBwnuQlwWlCYVLu0JDUR11ygRlYpBQCHFYQN7ucZdvDWOpIZ9BjtyqEisu3z7mhxPqCF-DSOLNw/s1600/Options.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="609" data-original-width="845" height="230" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwW70bYcnaF2eBwt2cKXgmfElS5zDEmhM57Fq3hAISp19YzCl482M8KpX6OALA0eKiSxBwnuQlwWlCYVLu0JDUR11ygRlYpBQCHFYQN7ucZdvDWOpIZ9BjtyqEisu3z7mhxPqCF-DSOLNw/s320/Options.jpg" width="320" /></a></div>
<br />
Since we in RAD Studio need to be able to master the fine art of manual memory management - things might leak if you do not pay attention and follow the rules. The most basic rule being: <i>What you create - you must "destroy"</i> (read free).<br />
<br />
<a name='more'></a><br />
The adventures into "Automatic Reference Counting" has been announced to be reverted, and all supported platforms will eventually be back to the manual model - being apparently more efficient and what most of us are used to, that gives us the feeling of control we like :)<br />
<br />
But even if ARC eventually goes away - I would more than ever recommend Dalija Prasnikar's <a href="https://dalija.prasnikar.info/delphimm/" target="_blank"><b><span style="color: blue;">"Delphi Memory Management for Classic and ARC Compilers"</span></b></a> - a must read by someone who knows what she talks about.<br />
<br />
That means it is also interesting when a "new" player enters the market for detecting memory leaks, since even if you think your code is perfect - you might be depending on frameworks or components that combined with the way you use them - could leak.<br />
<br />
<a href="https://www.deleaker.com/"><span style="color: blue;"><b>Deleaker</b></span></a> which has been available as a plugin for Visual Studio for many years - and gotten its fame there - is now also available as an add-on for RAD Studio. And when doing a test run, I was very pleased with the simplicity - I did not have to spend time tweaking various setting or modify my code to enable any leak detection.<br />
<br />
Take a look at the official blog post <a href="https://www.deleaker.com/blog/2019/03/11/integration-with-rad-studio/" target="_blank"><b><span style="color: blue;">here</span></b></a> - introducing RADStudio support - which also includes a quick nice video :).<br />
<br />
What even more surprised me was the list of known leaks - that by default is ignored coming from various internal parts of Windows - no wonder you need to restart your Windows once in a while - try to disable this option - very educating :)<br />
<br />
The tool either works within the IDE, enabling you to jump to the line in the source code that causes the issues found, or by running as a standalone application and just pointing to your executable, comparing the before and after allocation "leftovers" I guess - by hooking into resource allocations and deallocations.<br />
<br />
The last option might be helpful if you are embedding something like EurekaLog into your program, and it tries to do its thing while Deleaker also tries to help out - just stepping on each others toes. I am sure that the people behind Deleaker are eager to improve the tool, and also circumvent this issue if possible.<br />
<br />
I do also use things like EurekaLog - to gather exception logs and FastMMs <span style="font-family: Courier New, Courier, monospace;">ReportMemoryLeaksOnShutdown := True</span> - and sometimes madExcept and AQTime - but do give Deleaker's 14 days trial version a spin - I am sure that Artem will be happy to help if any issues arises to make the product even better for RADStudio.<br />
<br />
It should also be noted that updates are very frequent and support seems very responsive - and if you are also using other IDE's it is nice that the tool does support these directly.<br />
<br />
Now I just need to see it also available inside GetIt :D<br />
<br />
/EnjoySteffen Nyelandhttp://www.blogger.com/profile/14576369608629841090noreply@blogger.com0tag:blogger.com,1999:blog-3386248074980233262.post-42051473435657428392019-03-23T16:24:00.001+01:002019-03-23T20:55:04.704+01:00Siege of Avalon lifted<h3>
- or an example of migrating old source code to newer grounds.</h3>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgFsKWdjjL70fGymhOOIVXfJJa61e5XbZtgclDGbtaen34YhjSG3VazH_zNlR0vBh28AJbk3p99a9m1Bp-214aHcrzxGj95XNlL0fgKWv71y8Rv6DVIX5hKgs_r4Mr4gHIEpFONcYIQCcqR/s1600/SS0AA1B002.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="600" data-original-width="800" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgFsKWdjjL70fGymhOOIVXfJJa61e5XbZtgclDGbtaen34YhjSG3VazH_zNlR0vBh28AJbk3p99a9m1Bp-214aHcrzxGj95XNlL0fgKWv71y8Rv6DVIX5hKgs_r4Mr4gHIEpFONcYIQCcqR/s320/SS0AA1B002.jpg" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Compiled with latest Delphi (10.3.1 Rio) - Nice inventory graphics.</td></tr>
</tbody></table>
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
This post is just about the few steps I did on migrating the Delphi 4 source of Siege of Avalon to Delphi 10.3.1, and the intentions and road-map I have proposed to the players and modders of this nice game.<br />
<br />
<a name='more'></a><br />
<b>Intentions and road-map for my fork of the GitHub repository - as proposed on a German forum</b><br />
<ol>
<li>Ensure that everyone can compile the original source with the newest free Delphi CE - which actually make it bearable to work with the source. <b>DONE</b></li>
<li>Clean up the source - remove rot. And different separation, to help move code to cross-platform. <b>WIP</b></li>
<li>Add the fixes, enhancements done by the great people who have gritted their teeth on the code previously.</li>
<li>Add documentation on the assets file-formats used.</li>
<li>Evaluate the tools - maybe redo/merge them - and include them in the repository.</li>
<li>Replacing DirectX - with what makes better sense (SDL2 would be easiest) - might include the DDraw fixes in code as first step (getting rid of DDraw.dll).</li>
<li>Cross-platform - that could be Windows, macOS, (Linux) and tablets (Android/iOS) - would need control overlays. Lowest priority since - clean code makes the rest easier.</li>
</ol>
<div>
Under the enhancements I refer to:</div>
<br />
<ul>
<li>HD/FullHD support - but I think that should be optional (also keeping the original 800x600) - so either add a "launcher" or re-organizing the "Options"</li>
<li>Add a character/sex selector - again probably re-organizing the "Character Creator" dialog - Character in the center - male/female choices.</li>
<li>Mod selection - more DLC style - extendable instead of destructive copy/paste exercises.</li>
<li>Better multi-language support in the released source - right now it is crippled, like other parts of the UI code.</li>
</ul>
<br />
<b>Backstory</b><br />
<b><br /></b>
Siege was open-sourced back in mid-2003, and at that time I grabbed the source with the intention to look into it some day.<br />
<br />
Fast forward almost 16 years - and when Diablo was released on GOG.com (in corporation with Blizzard) two weeks ago - I remembered that I had the source laying about somewhere, found it and threw it on my private git server - where it should have been.<br />
<br />
I then started to google about what had happened in the years gone by, finding that the proponent of the open-sourcing had put some of the original source, including an unfinished port to SDL and FPC on GitHub.<br />
<br />
That had very recently been forked and picked up - compiled and working in Delphi 4 - with some else then adding support for higher resolutions screens, all based on the non-SDL code.<br />
<br />
<b>Process</b><br />
<br />
So I thought that I could contribute to this project, I the way I would have liked it done - with focus on the code - so I did a fork of the Delphi 4 working repo - always building on the great shoulders of the people that came prior to us :)<br />
<br />
Found my old CD of my Delphi 4 Enterprise - which in my opinion was the worst version of Delphi ever.<br />
<br />
Ensured I could compile and play the game from that source - and the started to move the code to Delphi 7 and again Delphi 2007 until at last Delphi 10.3.1 Rio.<br />
<br />
Apart from a bit of pointer fun and code clean-ups - it was fairly painless - the biggest pain was the changes that had happened in the Windows OS and DirectX area, breaking things and adding glitches - 2 days lost on that account :(.<br />
<br />
So first goal achieved, the code is now able to compile in a free Delphi 10.3.1 Community Edition, to produce a fully working executable. Link to source code<span style="color: blue;"> <a href="https://github.com/SteveNew/Siege-of-Avalon-Open-Source"><b><span style="color: blue;">here</span></b></a></span>.<br />
<br />
There is still a lot of code cleanup to be done. The project is not trivial, and might take a bit of time - but it is just like eating an elephant - bit by bit - pun intended.<br />
<br />
<b>Why I did not try to fix the non-working FPC/SDL code</b><br />
<br />
Order matters - both in the sense of keeping things tidy, but also in what order things are done to make it easier<br />
<ul>
<li>Code that compiles and works is funnier to work with.</li>
<li>Based on working code you can refactor - otherwise it is bug fixing.</li>
<li>Based on working code you can enhance and add features - otherwise it is useless.</li>
<li>Getting clean working and accessible code invites others to contribute.</li>
<li>By having more to contribute everyone wins - that is what it is about.</li>
<li>Adding more platforms at this stages just adds more complexity - and more platforms to test and debug on.</li>
<li>The working code was originally written in Delphi 4, so moving it to the latest "open/free" Community Edition is to me the natural choice - that is what the Community Edition is supposed to be used for.</li>
<li>To me it is a very big difference to work in a Delphi 4/7/2007-like environment, and the latest modern version of Delphi.</li>
<li>Spending my time effectively.</li>
</ul>
<b><br /></b>
<b>And why not just port the code to C#/Go/Java/Python/Unity?</b><br />
<b><br /></b>
<br />
<ul>
<li>Well feel free, but to what purpose? Using the same assets and story, does not make it a better game - it could make it worse if the execution of the port is badly done. This game has sold worldwide - the original source is where to start.</li>
<li>The game does not benefit from it - unless you rewrite the engine, modernize the assets, but then you end up with a different game.</li>
<li>The engine is open-sourced, not the assets - so you can add new assets.</li>
</ul>
<div>
And should you not be aware; the Community Edition of Delphi - is free and feature-wise identical to the Professional edition which includes support for Windows, macOS, iOS and Android - you just need to keep your earnings with it below $5000 USD :) Get it <b><span style="color: blue;"><a href="https://www.embarcadero.com/products/delphi/starter"><span style="color: blue;">here</span></a>.</span></b></div>
<br />
/enjoySteffen Nyelandhttp://www.blogger.com/profile/14576369608629841090noreply@blogger.com11tag:blogger.com,1999:blog-3386248074980233262.post-11249609715626855442018-07-18T17:58:00.000+02:002019-06-08T09:31:52.569+02:00Delphi and C++Builder Coming Everywhere<b>- or it finally happened - a Delphi and C++Builder Community Edition!</b><br />
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwwl8z-elzDvbVZuviNmomR54NN30J15vri2fftw7t4R558W8g8z9IcJYAxM80N7Ta7_wG-bpXxBVNanxA4zzHdmSU8DLJ15Nmplj3pIDvk37BRSQnltFyKXY70yX6dqU00Im55Cz94-tP/s1600/CE.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="358" data-original-width="564" height="203" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwwl8z-elzDvbVZuviNmomR54NN30J15vri2fftw7t4R558W8g8z9IcJYAxM80N7Ta7_wG-bpXxBVNanxA4zzHdmSU8DLJ15Nmplj3pIDvk37BRSQnltFyKXY70yX6dqU00Im55Cz94-tP/s320/CE.jpg" width="320" /></a></div>
<br />
The great thing about these editions is that they are free to use without being amputated like the Starter Edition was, feature-wise they are identical to the Professional Edition - with mobile now included.<br />
<br />
I have personally been looking forward to this move, and that should help a lot of hobbyists get out of their Delphi 7 stasis and discover how much Delphi has evolved over the years.<br />
<br />
<div>
</div>
The restrictions that has been applied has a lot to do with common sense - if you earn money and profit from the use of the CE license, you can afford and must convert to a paid license that includes some extra license benefits.<br />
<br />
<a name='more'></a><br />
<br />
<br />
This should also phase-out discussions where people get mixed up in the product features and what they like about the product, against the upgrade license cost. Some who fondly remembed the enjoyment they had using the product, but ended being frustrated about the product due to the subscription they wouldn't or couldn't pay.<br />
<br />
Just because I can't afford a Tesla X or BMW i8, does not mean they are bad products - one could sometime experience that just because Delphi has a price-tag, it got a bashing and free alternatives where thought better.<br />
<br />
By offering the Professional features in the Community Edition, it also enables some ex-users to re-discover the productivity of Delphi, compared to the free alternatives.<br />
<div>
<br /></div>
I do spend time on free frameworks and library - and sometimes that can be very non-productive time.<br />
<br />
A product with a version available to a large community and an enterprise paid version - can let the developers innovate while paid, and share their creation with a larger public - that can be very motivating.<br />
<br />
A hope for the future, is that this move allows EMBT to break a bit of the backward-compatible mantra - that is a great thing in Delphi - but which has also stopped great feature requests from being added - since they could break the compilation of the FishTank demo from Delphi 1, without changing a comma in the old code :D.<br />
<br />
Backward compatibility is one of the greatest strengths and a thing I am thankful for, but it should not be a showstopper, for adding new things that improves readability and productivity.<br />
<br />
A software developer ignorant company like Apple, has not even heard about the term backward-compatible - they move hardware - where backward compatible is considered bad for sales.<br />
<br />
Thanks Embarcadero for making this great and sensible move - the whole world is not that crazy after all and this is not fake news ;)<br />
<br />
Check the EULA, request a serial, download and start dusting off your old but well running hobby projects - and enjoy the newer features and contribute to the spreading of more happy Object Pascal code into the world.<br />
<br />
BTW: Passion and hobbies are two great needed motivators - so my intention was not to talk down to people doing "hobby" project in old Delphi versions - great code is done in spare time - fixing an itch :DSteffen Nyelandhttp://www.blogger.com/profile/14576369608629841090noreply@blogger.com1tag:blogger.com,1999:blog-3386248074980233262.post-21723956432265404902018-02-14T22:24:00.001+01:002018-03-05T20:44:00.495+01:00JSON Find / Rewind<h3>
- or using the TJSONIterator without wearing a Cardigan.</h3>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi70qMUd4dZ-9DgX_kAVDnJGw_PjmHPmTFiFiKFRnLJq65w7Biqtx-UysVqEVn46UqC9bxYK_AUrlfIp1Keg89EMcmSmsdlgECnXTe8xW-h3JJRrpRLYGcLg3Ja_3ET70rFmzE5qHEiPA29/s1600/json_find.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="864" data-original-width="1296" height="213" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi70qMUd4dZ-9DgX_kAVDnJGw_PjmHPmTFiFiKFRnLJq65w7Biqtx-UysVqEVn46UqC9bxYK_AUrlfIp1Keg89EMcmSmsdlgECnXTe8xW-h3JJRrpRLYGcLg3Ja_3ET70rFmzE5qHEiPA29/s320/json_find.png" width="320" /></a></div>
<br />
Working with JSON files has been made easier over the years with Delphi - which btw turns 23 today - by either third-party libraries or especially within the RTL.<br />
<br />
The System.JSON and the REST.JSON units have added the one-lines ObjectToJSON and JSONToObject, which can add a tiny bit of fat to the JSON generated to enable to get it back into your TObjectLists - but makes your code cleaner to read.<br />
<br />
Another way is the adding of the TJSONWriter and TJSONReader which by using the TTextWriter and TTextReader mimics the .Net equivalents.<br />
<br />
The TJSONWriter includes easy formatting of the output and in the System.JSON.Builders unit there are a couple of extra goodies, two of these being the TJSONObjectBuilder and the TJSONIterator.<br />
<br />
I will dig a bit into TJSONIterator, since only its Recurse, Next and Return methods is mentioned on its DocWiki page <a href="http://docwiki.embarcadero.com/CodeExamples/Tokyo/en/RTL.JSONIterator"><b><span style="color: blue;">here</span></b></a>.<br />
<br />
<a name='more'></a><br />
<br />
So if we basically take the code sample from the wiki page - but we only want to get the 2nd elements lastName and price values - iterating through the JSON using Recurse and Next might be a bit hard to follow. Se the wiki page as an example of their use.<br />
<br />
We need: <span style="font-family: "courier new" , "courier" , monospace; font-size: xx-small;">System.JSON.Builders, System.JSON.Readers</span><br />
<br />
I create the TStringReader and the TJSONTextReader:<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: xx-small;">LStringReader := TStringReader.Create('{"Transaction":[' + sLineBreak</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: xx-small;"> + '{"id":662713, "firstName":"John", "lastName":"Doe", "price": 2.1},' + sLineBreak</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: xx-small;"> + '{"id":662714, "firstName":"Anna", "lastName":"Smith", "price": 4.5},' + sLineBreak</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: xx-small;"> + '{"id":662715, "firstName":"Peter", "lastName":"Jones", "price": 3.6} ' + sLineBreak</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: xx-small;"> + ']}');</span><br />
<br />
So since I want to be able to use the Find method, the TJSONIterator needs to be create with a reference to a "Rewind" procedure.<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: xx-small;">LJsonTextReader := TJsonTextReader.Create(LStringReader);</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: xx-small;"> LIterator := TJSONIterator.Create(LJsonTextReader,</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: xx-small;"> procedure (AReader: TJsonReader) begin end);</span><br />
<br />
Note that I left the anonymous procedure empty since Rewind already calls rewind on its internal TJSONReader, but I could have added extra functionality. The iterator iterates through the JSON one-way, so its Find (and Rewind) needs to be able to restart from the beginning.<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: xx-small;">// Check if there is even the expected 3 elements in the array of interest</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: xx-small;"> if LIterator.Find('Transaction[2]') then</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: xx-small;"> begin</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: xx-small;"> Memo1.Lines.Add(LIterator.Path);</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: xx-small;"> if LIterator.Find('Transaction[1].lastName') then</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: xx-small;"> begin</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: xx-small;"> Memo1.Lines.Add(LIterator.AsString); // Smith</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: xx-small;"> LIterator.Next('price');</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: xx-small;"> Memo1.Lines.Add(LIterator.AsDouble.ToString); // 4.5</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: xx-small;"> end;</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: xx-small;"> end;</span><br />
<br />
Just to illustrate the usefulness of Find (and Rewind), someone decided that in the JSON supplied, price was more important, so the order of the pairs in the array elements was changed to:<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: xx-small;">LStringReader := TStringReader.Create('{"Transaction":[' + sLineBreak</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: xx-small;"> + '{"id":662713, "price": 2.1, "firstName":"John", "lastName":"Doe"},' + sLineBreak</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: xx-small;"> + '{"id":662714, "price": 4.5, "firstName":"Anna", "lastName":"Smith"},' + sLineBreak</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: xx-small;"> + '{"id":662715, "price": 3.6, "firstName":"Peter", "lastName":"Jones"} ' + sLineBreak</span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: xx-small;"> + ']}');</span><br />
<br />
Now we would still get "Smith", but our price would return "0" - since I just used iterated with the Next('price') within the element and lastName now came after.<br />
<br />
So if you need to parse the full JSON - Recurse, Next and Return it is, but if you need to pick out specific pairs, elements or just what to figure out if they exists in the provided JSON, TJSONIterator.Find can be helpful and keep your code more readable.<br />
<br />
Did anyone get the Cardigan reference?<br />
<br />
Enjoy, happy Valentine and Happy Birthday Delphi. :)Steffen Nyelandhttp://www.blogger.com/profile/14576369608629841090noreply@blogger.com0