Wednesday, June 24, 2020

SVG JS/ES Asteroids Video Game for Modern Chrome Browser

Browser-Based Asteroids-clone Video Game

Using 2020 JavaScript features available in Chrome browser

Earlier this year, during the beginning of the major social-distancing requirements (due to COVID), I finally decided to spend a few weeks of my free time writing a video-game clone that would make use of some of the newest features of JS / ECMAScript.   I ended up writing a clone of the famous 1979 Asteroids video game, but with all sorts of extra features and improvements (as I deem them).

JavaScript Features Used

  • requestAnimationFrame / cancelAnimationFrame  — which keeps the game frame-rate flowing nicely even as many Space Rocks and UFOs are threatening your existence.
  • Asynchronous code — a nice bit of async function and await / Promise usage!
  • JS Classes  — including plenty of encapsulation, inheritance, static variables and such
  • Game-pad support  — in addition to keyboard controls, I used the standard JS gamePad object to implement Xbox (or similar controller) support
  • Sound — without any extra files, but rather using audioCtx oscillator features

CSS / SVG Features

  • Animation — CSS animation of fills, strokes, etc.
  • SVG Symbol and Def — for maximum re-use via SVG use.
In the end, I was able to achieve a LOT of interesting visual-effects without the need to resort to a lot of custom animation code. 

UFO-Infested Space Rocks Video Game

I placed my UFO-Infested Space Rocks Video Game online at Github for anyone that wants to play with it.  The game play evolves as you go, presenting "smarter" aliens and an ever more frenetic pace.  The game works wonderfully in modern Chrome browser on the PC.  There are issues with it in Firefox.  I have not even tried to use it on a phone or whatever (no idea how keyboard controls or game controller logic would make any sense there).

Further Software and Technology Reading

Continue to read this Software Development and Technology Blog for computer programming articles (including useful free / OSS source-code and algorithms), software development insights, and technology Techniques, How-To's, Fixes, Reviews, and News — focused on Dart Language, SQL Server, Delphi, NVIDIA CUDA, VMware, Typescript, JavaScript / ECMAScript, SVG, other technology tips and how-to's, plus my varied political and economic opinions.

Friday, November 15, 2019

SunPlower Wind / Solar Energy Device HYPE (from Web Summit 2019)

SunPlower Wind / Solar Energy Device

ABUNDANT CLAIMS of Supposed Energy-Harvest Market Disruption Tech

For any of my readers that have a decent background in mathematics, engineering, physics, and a general inclination towards science and the reality of how things work, as soon as you read the claims made on the SunPlower.de website (yes, that is how they spell it; apparently trying to gain some name-recognition advantage for nearly copying the name of the large American solar company SunPower).  Just one quick look at the "SunPlower" device, which I first saw demonstrated at the Lisbon, Portugal Web Summit 2019, and you will instantly see why the company claims regarding "breakthrough" power-generation (implied efficiency) are completely absurd and utterly misleading for many reasons.

Here are some quotes from their website:
"SunPlower's (patent pending) world’s first Inclined Axis Wind Turbine (IAWT) propeller uses both lift and drag efficiently and reduces expensive & heavy masts costs. In addition to that propeller's blades are flat, which can be used for installing solar panels."
"Breakthrough in Fluid propulsion of both air and water more efficiently and effectively with SunPlower propeller."
"As the pitch of the propellers is synchronously varying while rotating, dust on blades with solar panels is self-cleaned. Its world’s first true Hybrid Solar + Wind technology, along with self-cleaning & self-cooling solar cells reducing maintenance costs."

Those are some rather wide-ranging and bold claims, implied and direct, and, given what was shown at Web Summit 2019 (a device which was a motor turning a windmill-like device whereby a set of gears simultaneously altered the blade angles), these claims are are highly unlikely, if not utterly impossible, to prove out in any controlled test environment (e.g., a wind tunnel).

Why these types of Claims are Problematic

There are many science blogs on the web that have tackled devices like this in the past and exposed obvious problems every time a new "disruptive" or "patent-pending" (not that a patent means anything with regard to efficiency, even if it were granted) or similar new wind-power technology — though, this one wants to call itself wind and solar power in one, or, as their home-page even goes further to state "Harvesting renewable energy using wind, solar hydro and tidal energy leveraging breakthrough technology", which sounds rather amazing.

Without diving too far into details, let's start by the simple fact that this and any other such device must respect:
  • Betz's law, which indicates the maximum power that can be extracted from the wind, independent of the design of a wind turbine in open flow.  Every time anyone claims they have created some "breakthrough" in a wind-power device that defies this law, by directly stating so or by implication, it is certainly an instantly dubious device! 
  • Bernoulli's principle, which, in fluid dynamics, states that an increase in the speed of a fluid occurs simultaneously with a decrease in pressure or a decrease in the fluid's potential energy — i.e., used to relate the velocity of the flow of air to the pressure difference across the turbine.
  • Mechanical efficiency, in general, especially with regards to loss throughout a mechanical device due to friction and gears and so forth.
Next, just looking at how modern wind-turbines work and some of the basic principles, you will notice quickly that the power-generation capability of this SunPlower device is certainly not going going to be in any way revolutionary or disruptive as compared to existing wind-power technology. Start by reading this Quora thread about Why 3-Blade Wind Turbines produce the best compromise between high energy yield and turbine stability and durability, and then keep in mind how this SunPlower device uses a 4-blade approach.

The device demonstrated at Lisbon Web Summit 2019 was fraught with problems and design issues that will certainly prevent it from being any "breakthrough" with regards to power-generation efficiency (whether wind and/or it's combined solar-on-blades idea, with dubious self-cleaning claims) or being produced with any sort of price/generated-watt advantage over many other existing technologies.  First of all, simply put, it is overly complex mechanically and complexity = likely for failure, just as a simple rule of mechanics. There is considerable mechanical efficiency-loss due to internal gearing that alters blade angle as the overall prop-mechanism rotates.

Keeping the Betz Limit in mind, consider the following equation that represents the theoretically available kinetic energy (in Watts) in the wind flowing through an area:
½pSv3
(where p = ~1.23kg/m3 density of air, S = swept area which is πr2 with r being the turbine blade-length in meters , v=air velocity in meters/second; also keep in mind that you cannot extract all that power or the wind would stop, and thus your wind-turbine would stop, and because of this, the maximum available power is ~59% of the number arrived at with above formula — modern turbines may reach only 35-45% in reality, and that is before loss in gears, bearings, power-transmission, etc)

Thus, the max power attainable by a turbine is known.
So, this little SunPlower device shown at the Web Summit did not even have blades of half a meter, and the swept diameter was perhaps not even half a meter.  This means very, very low power potential.  I'll keep this rough-out easy though and assume the firm may eventually build a one-meter-diameter-sweep device. So, max-theoretical-power that thing could produce at 10m/sec wind (which is a brisk and rather unlikely 22.4mph average sustained wind speed) would be:

~ .35 (efficiency extracted) x ½ x .25 (πr2, square-meters swept area) x 1.23 (air density) x 103 
... or, .35 x .125 x 1.23 x 1000 ... or, 53.81 Watts

THIS IS NOTHING!  We can get so much more out of a simple solar panel!  A SunPower brand ( NOT to be confused with this SunPlower thing) 405W solar panel outputs 200W per square meter!  And, unlike this overly-complex SunPlower device, the solar-panel is warranted for 25 years and has no moving parts; pair it with a simple Enphase IQ Microinverter, and the panel can be easily mounted near a cell-tower and you're all set.  Furthermore, the solar panel alternative is not going to be creating potential cell-tower signal interference like a spinning wind-rotor (especially if those blades were truly coated with solar-cells!).

Of course, my "red flag hype alert system" went off as soon as the developer began talking about how this device could be placed on some 4 billion (or whatever) cell-phone towers to power.  Sure. As could many existing, proven, and simpler technologies like a simple single solar-panel which is an already proven, affordable, mainstream solution. And, make no mistake: the tiny solar-panels on the rotating blades of this SunPlower (proposed) device are not going to produce any appreciable amount of electricity in the same overall footprint as a simple static solar panel, nor will it make much diff when combined with the wind-power (also, bear in mind, it's not like any solar-cells on the blades are all aiming the sun at once and capturing the power, especially given how the WIND is what will dictate which direction the supposed device is pointing).

I admire any attempt to come up with new green-tech, but please, remain aware of the limits imposed by science and the fact that hype cannot overcome such laws.  If this supposed breakthrough/disruptive-tech can prove they have overcome existing laws (like Betz), then bring it on!  Get it proven in lab conditions and you'll have achieved the formerly impossible; but, good luck at that!

Continue to read this Software Development and Technology Blog for computer programming articles (including useful free / OSS source-code and algorithms), software development insights, and technology Techniques, How-To's, Fixes, Reviews, and News — focused on Dart Language, SQL Server, Delphi, Nvidia CUDA, VMware, TypeScript, SVG, other technology tips and how-to's, plus my varied political and economic opinions.

Saturday, March 04, 2017

How to Fix: Microsoft Print to PDF (or Microsoft XPS Document Writer) Printer Not Working

How to Fix: Microsoft Print to PDF and Microsoft XPS Document Writer Printer Not Producing Output File

Symptoms: Print to PDF or Print-to-XPS Fails Silently, with no PDF File Created or no XPS File Created, in Microsoft Windows or Microsoft Office

This Microsoft Windows and/or Microsoft Office bug (or Windows "Feature") drove me crazy for a bit of time — when I wanted to print a document or spreadsheet, I would choose Print, then select either "Microsoft Print to PDF" or "Microsoft XPS Document Writer" as my target printer, proceed with printing to a file, where I would be prompted for the filename and file-location (directory), and choose to "Save" the file, and, even though all indications were that printing was functioning correctly, no XPS or PDF file was created!

Maddening!  In the end, it turns out that actually, this appears to be an issue (or problem or bug or feature) with Microsoft Windows that has to do with how the Windows Printer Driver functions (or does not function or work in certain situations), in particular with some security settings that mess it up.  I was using Microsoft Windows 10 and Microsoft Office 2013 (running in a Vmware Virtual Machine — which I mention only because this bug also prevents printing documents to PDF or XPS files via Vmware's ThinPrint driver too).

This is my list of available printers, and the two virtual printers (print to PDF or XPS file) experiencing this problem printing to a file are highlighted with a green box:
Printers: including Microsoft Print to PDF and XPS Document Writer

The problem cause, and how to fix this printing problem...

This took some time to figure out, but the root-cause of this printing problem was file-system directory security settings. It turns out that both the Microsoft Print-to-PDF and Microsoft XPS Document Writer need appropriate permissions in your temporary directory.

I tend to redefine my default Microsoft Windows temporary directory to some easier-to-quickly-locate place, like, e.g., "C:\temp" or "C:\tmp" (vs. buried in their default user subdirectories).  It turns out that creating that non-standard / non-default temp-directory is what led to my particular problem with the PDF / XPS driver not working.

In Windows 10 (or Windows 7 or whatever), your temporary directory is defined under Control Panel, System Properties, Advanced, Environment Variables, User Variables / System Variables, with the Variables of "TEMP" and/or "TMP" pointing to your temporary directory of choice, and whereby I have remapped my temp dir to be "C:\temp" or whatever.  I never even considered that changing my temp directory could break my Windows Printing (to file) features!  But, it did!

So, how to fix the fact that PDF files and XPS files were acting like they were printing, but nothing is being generated, not even in the print-queue? Easy: set the permissions on your newly chosen temporary directory location as follows:

Here is what the default permissions on my custom temp directory looked like when the PDF and XPS files refused to print:

Default custom-temporary-directory permissions

Now, this is all it took to fix the problem — one simple check-mark (see green circle), changing the permissions on the temp directory:



That did it! Enabling Authenticated Users to have Full Control of the temporary-directory fixed the Microsoft XPS Document Writer and Microsoft Print to PDF feature.

It is hard to believe that a system-level print-driver needs some special permissions on a temp directory (the same directory all sorts of program installers and software update logs go into, etc.) in order to create a PDF or XPS temp file, but that seems to be the case.  And, without that permission, you click Print and nothing happens... no file is produced... no printing errors during the PDF / XPS production process, no indication of failure... just no file produced. Period.  Ughhh.  But, now I know. I hope this saves some readers a bit of time and frustration.

This issue was as crazy as the one that forced me to write a blog about How to Fix: "Unable to find Adobe PDF resource files" - Adobe PDF Converter.joboptions - in Acrobat Pro a while back.  Equally ridiculous.

Continue to read this Software Development and Technology Blog for computer programming articles (including useful free / OSS source-code and algorithms), software development insights, and technology Techniques, How-To's, Fixes, Reviews, and News — focused on Dart Language, SQL Server, Delphi, Nvidia CUDA, VMware, TypeScript, SVG, other technology tips and how-to's, plus my varied political and economic opinions.

Tuesday, February 14, 2017

Forex : Currency Trading : Understanding Exchange-Rate Fluctuations and the Magnitude of Gain/Loss on your Positions

Currency Trading Basics : Understanding the Exposure Implications related to Exchange-Rate Fluctuations

NOTE: see below for my handy Visual Aid, the Quick-Reference Diagram: Currency-Move (Exchange-Rate Fluctuations) and the Implied Magnitude of potential Gain / Loss (impact on your portfolio) depending on Exchange-Rate Changes and your current Position Size.  Also, if using Currency-Trading for Hedging foreign-stocks, read my blog about Investing in ADR (American Depository Receipt) Stocks as a potential alternative approach.

Just type the word "Forex" into Google Search and see what comes back.  You will see countless ads for Online Forex Trading platforms, many of them advertising relatively very low initial account-opening deposit requirements (perhaps a few thousand dollars) and promising the opportunity to make huge gains and to allow you to leverage positions by a factor of up to 100:1, with relatively low "spreads" (the market difference between buy/sell prices of the currency pairs).

That all sounds wonderful! Or does it.  Let's look into this foreign exchange market, or currency market, and any thoughts of using swings in exchange-rates to make money, a bit further first.

Potential for Very Rapid and Large Gains OR Losses

You had better first understand the implications of this foreign currency exchange or Forex "trading" — I place the word trading in quotes, because, especially when you are trading leveraged positions, it is really nothing beyond betting, and betting using money you personally do not have (hence why it is called leverage).

Physical Positions (non-leveraged) in a Foreign Currency

If your particular broker / Forex-trading-platform allows, you can trade physical positions in foreign currencies (i.e., buy or sell one currency in exchange for direct holdings in another, and hold those balances in your account), but that is really what I would call "currency conversion" vs. currency trading, with the difference being that when you are holding a physical position in one or more currencies, it really isn't too much different than holding your US Dollars (USD) at a bank, more or less, because, if you wanted to transfer those physical holdings to a bank in a country that supported holding balances in that particular currency, you could do so, and you could (perhaps during a planned trip to a foreign country, or to a country where you own a vacation home, etc) withdrawal funds as you need in order to pay your expenses in the local currency.  An example might involve you maintaining an account at Deutsche Bank in the EU, in Euros, where you could transfer some EUR holdings to if you needed money (in Euros) to pay for your summer vacation home or whatever.

Your exposure to gains and losses is simply limited to the amount of each currency you hold physically (worst case), in the event you decide you need to convert any remaining physical positions back into USD or whatever.  There is no leverage, thus there is no drastic multiplier of currency-swing effects. And, presumably, you wouldn't be holding substantial positions in one or more foreign currency unless you expected to potentially need it for things like travel, real-estate, or even perhaps purchasing a business or other investment in a foreign country.

This is quite different from...

Leveraged Positions in a Foreign Currency : True Forex Trading

When you are simply looking at currency-market volatility as a vehicle for potentially producing income, the game changes considerably. I am not going to get into all the exotic trading possibilities of things like various CFD instruments (Contract for Difference) and other derivatives, but rather just focus on trading the change in relative price strengths between simple currency-pairs.  And, if you are venturing into these waters, you are most certainly doing so utilizing leveraged-positions.

When I say "currency pairs", I am referring to the quotation and pricing structure of the currencies traded on the forex market. The value of a currency is a market rate determined by its comparison to another currency. The first listed currency within a currency pair is called the base currency, and the second currency is referred to as the quote currency.  

Some common currency-pairs, which you can see on sites including Google Finance, include:

  • EUR/USD (or EUR:USD if you prefer colon form) — where 1 unit in the base currency (in this case the Euro) is worth a certain number of the quote-currency (US Dollars here); a typical value for this in early 2017 would be perhaps around the 1.05 to 1.07 range, meaning that one Euro is worth (or buys you) between 1.05 and 1.07 Dollars.  
  • USD/JPY — the US Dollar / Japonese Yen
  • GBP/USD — the British Pound Sterling (or Cable, or Pound) / US Dollar
  • USD/CAD — the US Dollar / Canadian Dollar
  • AUD/USD — the Australian Dollar vs US Dollar
Pick whichever suits your interests and where you expect you can earn money off fluctuations between the components of the pair.

So, you are ready to trade a currency-pair...

OK, time for some mathematics! You need to understand your exposure to market fluctuations in the currency-pair quotes, because markets can move fast, and when you are leveraged, the multipliers can become enormous.

I created this graphic / diagram / chart / visual-helper (below) to try to demonstrate how much a seemingly small move in an exchange-rate can impact you, in either a good way (profit) or a bad way (loss).

Quick-Reference: Currency-Price-Change-Magnitude
Quick-Reference Diagram: Currency-Move (Exchange-Rate Fluctuations) and
the Implied Magnitude of potential Gain / Loss (impact on your portfolio)
depending on the Price-Change and your current Position Size, visual aid.

I chose position-sizes that may sound large, but, considering that some Forex trading platforms allow you to leverage positions in currency-pairs up to a factor of 100:1, just think about that...  with USD $10,000, at 100-to-1 leverage, that multiplier factor means that you are betting on a position of USD $1 million!

Now, I decided to use a GBP:USD currency-pair example in my above chart.  Let's assume that at the point in time when I take a position in that pair, each British Pound cost $1.5432x (where "x" is some further decimal value if your trading platform goes down to that level of detail, which would be a fraction of a PIP).  Oh, and a PIP = "price interest point", which measures change in the exchange rate for a currency pair, which when displayed to four decimal places, one pip is equal to 0.0001, or in my GBP:USD price chart example above, the column with the value "2" in it, four positions to the right of the decimal point.

Exchange-Rate Changes and Implied Profit or Loss


Referring to my picture (diagram / visual aid) above, let's consider how changes in the currently-quoted exchange rate will affect our paper gain/loss, based on different position sizes (and, keep in mind, I am considering the value of the leveraged position, not the amount you risked in order to leverage that amount; I will use a 100:1 leverage here too, each starting at the purchase-price of the $1.5432 per GBP):

  • Scenario #1: (upper-right table in my graphic will be helpful) you buy a £100,000 position in GBP:USD, which would have cost $154,320 (plus any transaction fees), but at 100:1 leverage, the "cost" (amount put up for this bet / trade) is really just $1,543 (plus fees / commissions).

    Now, the market value of the Pound increases by 20 PIPs, to $1.5452/GBP: your leveraged position is worth $154,520, thus, you have made $200 (less fees) if you sold at this point.  Notice how my visual cheat-sheet makes this easy to quickly see and compute by looking at the column with the "3" in it (which has changed to a "5" now), you can see that each digit-change in that column implies an overall position-value change of $100. Your gain, in percentage-terms, could be very substantial: $200(less-fees)/1543 => ~13% (not counting fees).  BUT, keep in mind, you could have just LOST that amount too!
  • Scenario #2: (upper-right table in my graphic will be helpful) you buy a £1,000,000 position in GBP:USD, which would have cost $1,543,200 (plus any transaction fees), but at 100:1 leverage, the "cost" (amount put up for this bet / trade) is really "just" $15,430 (plus fees / commissions) — you now have the price of a low-end new car on the line for this substantial bet / trade position!  "

    Now, some reasonable volatility hits the market following Mark Carney opening his mouth about something regarding British interest rates or the post-Brexit economy, and the next thing you know, the market value of the Pound moves lower by a full 100 PIPs (one cent), to $1.5332/GBP: your leveraged position is now worth $1,533,200, thus, you have lost a rather whopping $10,000 (plus fees) if you sold at this point.

    Again, my visual cheat-sheet makes this easy to quickly see and compute by looking at the column with the "4" in it (which has changed to a "3" now), you can see that each digit-change in that column, at a £1,000,000 position-size implies an overall position-value change of $10,000. Your loss, in percentage-terms, is simply huge! $10,000(plus-fees)/15430 => ~65%  loss (plus fees).  Sure, you could have just gained that amount too if the rate change had gone in your favor! 

But wait, there's more!

Don't overlook the overnight-interest-charges you will be hit with on your leveraged positions! Did you think you were going to get the implied-funds for 99% of your position for free?  Think again!  These can add up. And, especially keep in mind the cost of carrying these positions over weekends / holidays when markets will be closed!  I am not going to get into the details of all that here, but perhaps I will later.  My goal is to demonstrate the sheer magnitudes of potential movements in the value of your trading positions in relation to very small changes in the underlying exchange-rates.

Potential BUST!

Next, consider that, although you can open a Forex trading account with rather low amounts, notice how, if you had opened an account with what may have seemed like a "reasonable" amount of money to you, that sudden changes and large swings in currency exchange rates (i.e., large volatility) can wipe you out in no time when the broker / trading-platform operator liquidates your account when your physical currency holdings cannot cover the current implied market-price move (i.e., implied loss on your holdings), if even for a minute,... goodbye everything.

This is all the more reason you really need to understand the implied possible changes to your position when the market swings.  Think carefully about this.  Don't take on positions where unexpected market events (e.g., an unexpected rate-rise or cut by a central bank) could move rates 2%, and thus wipe you out if you are on the wrong side of things.

Continue to read this Software Development and Technology Blog for computer programming articles (including useful free / OSS source-code and algorithms), software development insights, and technology Techniques, How-To's, Fixes, Reviews, and News — focused on Dart Language, SQL Server, Delphi, Nvidia CUDA, VMware, TypeScript, SVG, other technology tips and how-to's, plus my varied political and economic opinions.

Sunday, January 22, 2017

ES2015 (modern JavaScript) Implementation of Delphi's TList VCL Class with Multi-Field Sort Algorigthm and more

ES2015 Source Code: Class — JavaScript implementation of Delphi VCL TList Class including flexible multi-property List-Sort algorithm and more

Just for fun, I created this ECMAScript 2015 (i.e., ES2015 or modern object-oriented JavaScript) version of a Borland / Embarcadero Delphi TList VCL Class equivalent (or, at least partial equivalent) to demonstrate one approach to Object Oriented Programming (OOP) in JavaScript, and I call the class "DList". I originally implemented this years ago in straight JavaScript, and have updated it to take advantage of ES2015 syntax and encapsulation. I have implemented some of the core TList methods like Add(), Insert(), InsertRange(), Delete(), IndexOf(), Delete, and Sort() in hopes of demonstrating the power and speed of JavaScript.  This DList class wraps up the native JS array object and adds some nice extra functionality.

My DList JS/ES Class also implements my proprietary multi-column / multi-property sort algorithm that is very adaptable and flexible and should be easy to understand (see comments in source code) where I use a powers-of-two column-precedence algorithm within the sort-comparison closure callback method (in particular, see the Sorter: function(a, b) {} closure code in the source comments).

When I first wrote the original JS code, ES2015 Classes were not even an agreed standard, so I was quite excited to move on to the much simpler and much better real object oriented web-development language and framework: Google's Dart programming language. And, even though this JS/ES class makes life in JavaScript easier, simply put, Google Dart makes this type of functionality ultra-simple since their core API / framework / library includes a rich set of List / Collections classes. But, just in case you want to stick with JavaScript / ECMAScript, this should be an interesting example for you.

Live Example

In addition to the DList Object-List Class source-code, I also included the source-code I use for running various tests to confirm sorting and list-object(s) manipulation.  Just call the test routine in your HTML body tag by including: onload="DListTest();"

And, to make demonstrating this easier, I have included an embedded JSFiddle (see below my source code).  This Fiddle (link to full fiddle) ES / JavaScript TList Class (Delphi-like) Example also shows it in action. Presuming you are a developer, you will find it very easy to examine the page-source code and see the ECMAScript / JavaScript programming (.js files) used by the example and so forth.

NOTE: you definitely need a modern browser (e.g., Chrome or FireFox) with ES2015 Class support. Either one has rather fantastic built in developer tools for stepping, tracing, object-interrogation, and other features you may also want for seeing how this all works.


ECMAScript ES2015 Source Code

/********************************************************************************
This source code is Copyright (c) 2017
     Author: Mike Eberhart

I hereby release this code under the terms of the MIT License (for freeware).

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
********************************************************************************/
'use strict';

/*
███████████████████████████████████████████████████████████████████████████████████████████
DList Class

NOTE: this code uses the modern syntactic-sugar of ECMAScript 2015 CLASSES, and thus
requires a modern browser which supports their usage (e.g., Chrome, Firefox)

DESCRIPTION
This Class defines a List designed to hold objects of any type.
For proper encapsulation, the ONLY outside access to the internally maintained list is
through the Items[] array property (read only).

▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
PROPERTIES

PROPERTIES
- Items[] ............ an Array of any objects you wish to store in a List.
  Because it is an Array, all methods available to arrays can be used in
  addition to the custom DList methods defined herein; though, not all Array
  methods/properties will make sense depending on what you store in the List.
- Count .............. the number of elements in the list.
- InstanceName ....... String value assigned during constructor; nice for debugging

TODO (PERHAPS):
- EnforceUnique (true/false)... in case non-unique list requirements exist
- MaxListElements (int)........ if this becomes an issue
- Sorted (true/false).......... maintain list as sorted at all times?
  And, binary-search (IndexOf) if sorted and unique)

▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
METHODS
Add(item) ............ Add one item to list (by reference) and return index to that item
AddRange(arrayObj) ... Add list of Items at end of list
Clear() .............. Completely empty the list; i.e., Count = 0; Items=[]
Delete(index) ........ Remove item at [index] position
Equals(a,b) .......... [CLOSURE, via parm @ create] : Compare two objects
                       and return true (=) or false (!=)
Insert(index, objToInsert) ... Insert item at specified [index]
IndexOf(item) ........ Find first position of item, if not found return -1
Sort() ............... Sorts Items array based on array object's property
                       value(s) according to Sorter() method logic.
Sorter(a,b) .......... [CLOSURE, via parm  @ create] : used by Sort() method
                       to determine sort-order of Items.  See DETAILS below.
Update(index, objNew)  Update the object at specified Index with new one.

TODO (PERHAPS): Move(), Exchange(), First(), Last(), Next(), Prev()

***********************
METHOD DETAILS / NOTES:
***********************
................................................................................
Equals() method
List creator can implement, via constructor parameter, a closure for this.
See example code.
This method is used to determine whether two objects in our Items array
are to be considered as Equals for the purposes of IndexOf() operations, which
can be used for:
 1) enforcing a UNIQUE CONSTRAINT on array contents;
 2) locating an array item to Delete;
 3) locate a position in the array for an Insert.

NOTE: Equals() is NOT for testing true array-element-objects equivalence.

................................................................................
Sorter() method, used by Sort()
List creator can implement, via constructor parameter, a closure for this, which
the Sort() method will call internally.  The Sorter method must compare two
Item-array objects (let's call them "a" and "b") and return a value that
is either:
 Zero: indicating "a" and "b" are considered equal and no sorting required
       for these two objects (i.e., their order relative to each other in
       the Items array is already as we desire, since they are the "same"
       for our sorting purposes).
  <0 : less than 0, indicating that we want to Sort object "a" into a lower
       Items-array position (index) than object "b"
  >0 : greater than 0, indicating the opposite of our less-than-zero condition.

................................................................................
Insertion and Deletion methods act on an Index value that is simply the Items
array's Index position at which to perform the operation.  If you wish to
Insert or Delete at a position determined by object-property value(s) stored
within the Items array, acquire the target-index first by calling the IndexOf()
method with object-search-criteria; the result of that call will be your index
for Insert/InsertRange or Delete.
  e.g., mylist.Delete(mylist.IndexOf({id:123, name:"number123"}));
▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
EXAMPLES:

This is a how to create a new DList object and pass the Closures (functions)
for the Equals() and Sorter() methods the object should use.
--------------------------------------------------------------------------------
var mylist = new DList(
    'MyInstanceNameHere',

    //Equals() closure:
    //E.g., we may want to ensure uniqueness on the combination of two properties
    //present on each of our array Items, like id and lastname properties; if so,
    //our code is as follows (NOTE: REMEMBER CASE-SENSITIVITY ISSUES):
    function(a,b)
    {
        return (a.id == b.id && a.lastname == b.lastname);
    },

    //Sorter() closure:
    Sorter: function(a, b) {
        {
            //Example algorithm for sorting on multiple columns; adapts with ease!
            //Sort ascending by id, name
            //<variable> = <expression> ? <true clause=""> : <false clause="">
            //Use Powers-of-Two multiplier to set column-sort-order-precedence
            //IMPORTANT NOTE: HIGHER power of two implies higher precedence
            //
            //REMEMBER THIS TOO: JS array sort is CASE-SENSITIVE by default.
            //Use .toUpperCase() or such to level this, e.g. before our return-algorithm:

            return (
                    1 * ((a.id == b.id) ? 0 : (a.id < b.id) ? -1 : 1) +
                    2 * ((a.firstname == b.firstname) ? 0 : (a.firstname < b.firstname) ? -1 : 1) +
                    4 * ((a.lastname.toUpperCase() == b.lastname.toUpperCase()) ? 0
                        : (a.lastname.toUpperCase() < b.lastname.toUpperCase()) ? -1 : 1)
            )
        }
    }
);

███████████████████████████████████████████████████████████████████████████████████████████
*/
class DList {

    constructor(instancename = '[InstanceName not specified in List constructor]',
                //default closure for Sorter: default to no sort-ordering
                equals = function(a, b) {return 0},
                //default closure for Equals: default to no match
                sorter = function(a, b) {return 0}) {

        this._InstanceName = instancename;
        this.Sorter = sorter;
        this.Equals = equals;

        //create the array that will contain our list Items
        this._items = [];
    } //constructor


    //Read-only InstanceName property accessor (set during construction)
    get InstanceName() {
        return this._InstanceName;
    }

    //Provide read-only access to the internal items-array
    get Items() {
        return this._items;
    }

    //Read-only Count property
    get Count() {
        return this._items.length;
    }


    //Add single object reference to our list's Items[] array, and return its index position
    Add(objToAdd) {
        let newItemIndex = this._items.length;
        this._items[newItemIndex] = objToAdd;
        return newItemIndex;
    }

    //Use AddRange when merging an array of objects to our Items[].
    //This adds Items to the end of our list's Items[] array;
    //use Add() when adding only one item (more efficient).
    //AddRange is just an InsertRange at end-of-Items-array.
    AddRange(objArrayToAdd) {
        this.InsertRange(this._items.length, objArrayToAdd);
    }

    Clear() {
        this._items = [];
    }

    //TODO: Delete/DeleteRange with bad (i.e. -1 or non-existent) index must fail!
    //      Set to EOList?
    Delete(index) {
        this._items.splice(index, 1);
    }

    //Remove several entries at index (RemoveCount = how many to delete)
    DeleteRange(index, RemoveCount) {
        this._items.splice(index, RemoveCount);
    }

    IndexOf(obj) {
        let i = this._items.length;
        while (i--) {
            if (this.Equals(this._items[i], obj)) {
                return i;
            }
        }
        return -1;
    }

    Insert(index, objToInsert) {
        //make sure insertion-index is valid; TODO: real error check/range-warn.
        index = Math.max(0, Math.min(index, this._items.length));

        //splice obj into array at index, remove zero elements in the process.
        this._items.splice(index, 0, objToInsert);
    }

    //Insert an array of objects, at index position, into Items[]
    //Use Insert() method for single object insertion.
    InsertRange(index, objArrayToInsert) {
        //make sure insertion-index is valid; TODO: real error check/range-warn.
        index = Math.max(0, Math.min(index, this._items.length));

        let tmpArray1 = [];
        //get any portion of existing Items[] array before insertion point
        //Note: slice uses Zero-based begin/end indexes for extraction and extracts up to,
        //but NOT including end index value (i.e., stops at ending index -1).
        if (index > 0) {
            tmpArray1 = this._items.slice(0, index);
        }

        //add our new values at array insertion point
        tmpArray1 = tmpArray1.concat(objArrayToInsert);
        //use slice() to get portion after insertion point; join that with beginning + added
        this._items = tmpArray1.concat(this._items.slice(index, this._items.length));
    }

    //If sorting is desired, when creating a new() List, the creator must implement, via an
    //optional parameter, a closure which will be assigned to the method named "Sorter".
    Sort() {
        this._items.sort(this.Sorter);
    }

    //Place a new object into Items array where old object was.
    //Caller must pass valid index (TODO: test index)
    Update(index, objNew) {
        if ((this.IndexOf(objNew) == -1) || //OK if objNew's key-field-values are new (unique)
            (this.Equals(objNew, this._items[index])))   //OK to update same unique "key"
        {
            this._items[index] = objNew;
        } else {
            //TODO: REPLACE THIS with raise error if unique constraint will be broken on update...
            console.log(this.InstanceName + '.List.Update() violated unique constraint');
        }
    }

}  //DList



/*
███████████████████████████████████████████████████████████████████████████████████████████
Various test-conditions and web / browser-console logging routines...
███████████████████████████████████████████████████████████████████████████████████████████
*/
function DListTest(){

    const sSeparatorLine1 = '███████████████████████████████████████████████████████████████████████████████████████████';
    const sSeparatorLine2 = '■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■';
    const sSeparatorLine3 = '▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪';

    let traceDiv = document.getElementById('trace');

    function LogToScreen(stringToLog) {
        if (stringToLog == '<hr />') {
            console.log(sSeparatorLine1);
        } else {
            console.log(stringToLog);
        }

        if (typeof(stringToLog) == 'object') {
            traceDiv.innerHTML += '
[' + stringToLog.InstanceName + '] SEE CONSOLE FOR FULL OBJECT INSPECTION CAPABILITY; Inspect the Items (array) objects';
        } else {
            traceDiv.innerHTML += '
' + stringToLog;
        }
    }


    function LogItemsToScreen() {
        for (let i = 0; i < mylist.Count; i++) {
            LogToScreen('List Item[' + i + ']: ID=' + mylist.Items[i].id + ';    firstname=' + mylist.Items[i].firstname + ';    lastname=' + mylist.Items[i].lastname  );
        }
    }


    /*
    ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
    BEGIN: create instance of the DList Class and get these tests rolling...
    ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
    */
    let mylist = new DList(
        'mylist',

        //Equals() closure:
        // E.g., we may want to ensure uniqueness on the combination of two properties
        // present on each of our array Items, like id and lastname properties; if so,
        // our code is as follows:
        function(a, b) {
            return (a.id == b.id && a.lastname == b.lastname);
        },

        //Sorter() closure:
        function(a, b) {
            {
                //Example algorithm for sorting on multiple columns; adapts with ease!
                //Sort ascending by id, name
                //<variable> = <expression> ? <true clause=""> : <false clause="">
                //Use Powers-of-Two multiplier to set column-sort-order-precedence
                //IMPORTANT NOTE: HIGHER power of two implies higher precedence
                //REMEMBER THIS TOO: JS array sort is CASE-SENSITIVE by default.
                //  Use .toUpperCase() or such to level this, e.g. we make lastname case-insensitive:
                return (
                        1 * ((a.id == b.id) ? 0 : (a.id < b.id) ? -1 : 1) +
                        2 * ((a.firstname == b.firstname) ? 0 : (a.firstname < b.firstname) ? -1 : 1) +
                        4 * ((a.lastname.toUpperCase() == b.lastname.toUpperCase()) ? 0 : (a.lastname.toUpperCase() < b.lastname.toUpperCase()) ? -1 : 1)
                )
            }
        }
    );

//------------------------------------------------------------------------------
    mylist.Add({id:4, firstname:'first-4', lastname:'last-4'});
    mylist.Add({id:5, firstname:'first-5', lastname:'last-5'});
    mylist.Add({id:13, firstname:'first-2', lastname:'last-1'});
    mylist.Add({id:1, firstname:'first-2', lastname:'last-1'});
    mylist.Add({id:1, firstname:'first-1', lastname:'last-1'});
    mylist.Add({id:2, firstname:'FIRST-2', lastname:'last-2'});
    mylist.Add({id:3, firstname:'first-3', lastname:'last-3'});

    //List was build unsorted, now SORT it (on last, first, id) USING CLOSURE provided in constructor
    //The items will then be ordered as: id:1-5, firstname:"first-1...first-5", lastname:"last-1...last-5"
    mylist.Sort();


    //Create another list loaded from mylist (so each console.log object-ref remains valid),
    //otherwise, if we just log "mylist" over and over, in the end, all logged mylist refs will
    //point to the final state (vs. each stepped-state of these tests)
    let mylistInitialLoad = new DList('mylistInitialLoad');
    mylistInitialLoad.AddRange(mylist.Items);

    LogToScreen('Sorted (by lastname, firstname, id) initial 5-item list "mylistInitialLoad" follows:');
    LogToScreen('Count: ' + mylistInitialLoad.Count);
    LogToScreen('Items.length: ' + mylistInitialLoad.Items.length);
    LogToScreen(mylistInitialLoad);
    LogItemsToScreen();
    LogToScreen('<hr />');


//------------------------------------------------------------------------------
    mylist.Add({id:1, firstname:'dup-id-1', lastname:'dup-id-1'});
    mylist.Add({id:2, firstname:'dup-id-2', lastname:'dup-id-2'});
    mylist.Add({id:3, firstname:'dup-id-3', lastname:'dup-id-3'});

    let mylistAdds = new DList('mylistAdds2');
    mylistAdds.AddRange(mylist.Items);

    LogToScreen('Sorted List with 3 items added (duplicate id values for id 1-3) to initial sorted 5-item list;
' +
        'List not re-sorted after additions; "mylistAdds" follows:');
    LogToScreen('Count: ' + mylistAdds.Count);
    LogToScreen(mylistAdds);
    LogItemsToScreen();
    LogToScreen('<hr />');


//------------------------------------------------------------------------------
    mylist.Sort();

    let mylistSorted2 = new DList('mylistSorted2');
    mylistSorted2.AddRange(mylist.Items);


    LogToScreen('Same 8-item list, but RE-SORTED NOW (by lastname, firstname, id)... new instance dump follows;');
    LogToScreen(mylistSorted2);
    LogItemsToScreen();
    LogToScreen('<hr />');

//------------------------------------------------------------------------------
    mylist.Insert(mylist.IndexOf({id:1, firstname:'dup-id-1', lastname:'dup-id-1'}),
    {id:111, firstname:'insert-before-id-1, dup-id-1', lastname:'inserted'});

    let mylistAfterIns1 = new DList('mylistAfterIns1');
    mylistAfterIns1.AddRange(mylist.Items);

    LogToScreen('Insertion test (1 row, before dup-id-1)...;
' +
        'List not re-sorted after inserts; "mylistAfterIns1" follows:');
    LogToScreen('Count: ' + mylistAfterIns1.Count);
    LogToScreen(mylistAfterIns1);
    LogItemsToScreen();
    LogToScreen('<hr />');

//------------------------------------------------------------------------------
    mylist.Delete(mylist.IndexOf({id:4, firstname:'first-4', lastname:'last-4'}));

    let mylistAfterDel1 = new DList('mylistAfterDel1');
    mylistAfterDel1.AddRange(mylist.Items);

    LogToScreen('Deletion test (id:4 removed)...;
' +
        'List not re-sorted after delete; "mylistAfterDel1" follows:');
    LogToScreen('Count: ' + mylistAfterDel1.Count);
    LogToScreen(mylistAfterDel1);
    LogItemsToScreen();
    LogToScreen('<hr />');

//------------------------------------------------------------------------------
    //attempt to insert into invalid position
    mylist.Insert(999, {id:999, firstname:'WayOutFirst', lastname:'WayOutLast'});

    let mylistAfterBogusInsPos = new DList('mylistAfterBogusInsPos');
    mylistAfterBogusInsPos.AddRange(mylist.Items);

    LogToScreen('Attempt to INSERT OUTSIDE Items[] Bounds (id:999)... should simply place new item at end of list;
' +
        '"mylistAfterBogusInsPos" follows:');
    LogToScreen('Count: ' + mylistAfterBogusInsPos.Count);
    LogToScreen(mylistAfterBogusInsPos);
    LogItemsToScreen();
    LogToScreen('<hr />');

//------------------------------------------------------------------------------
    mylist.Sort();
    LogToScreen('RE-SORTED our list (by lastname, firstname, id)...');
    LogItemsToScreen();
    LogToScreen('<hr />');

//------------------------------------------------------------------------------
    //attempt to update a value in the Items array with new object
    mylist.Update(mylist.IndexOf({id:2, firstname:'dup-id-2', lastname:'dup-id-2'}),
    {id:222, firstname:'updated-id-2(first)', lastname:'updated-id-2(last)'});

    let mylistAfterUpdate1 = new DList('mylistAfterUpdate1');
    mylistAfterUpdate1.AddRange(mylist.Items);

    LogToScreen('List should now have dup-id-2 values changed...
' +
        '"mylistAfterUpdate1" follows:');
    LogToScreen('Count: ' + mylistAfterUpdate1.Count);
    LogToScreen(mylistAfterUpdate1);
    LogItemsToScreen();
    LogToScreen('<hr />');

//------------------------------------------------------------------------------
    //attempt update with SAME object (same, per Equals CLOSURE, which is: same id/last-name)
    mylist.Update(mylist.IndexOf({id:222, firstname:'updated-id-2(first)', lastname:'updated-id-2(last)'}),
    {id:222, firstname:'updated-id-2(first-fixed)', lastname:'updated-id-2(last)'});

    let mylistAfterUpdate2 = new DList('mylistAfterUpdate2');
    mylistAfterUpdate2.AddRange(mylist.Items);

    LogToScreen('List should now have id=222 values changed again...
' +
        '"mylistAfterUpdate2" follows:');
    LogToScreen('Count: ' + mylistAfterUpdate2.Count);
    LogToScreen(mylistAfterUpdate2);
    LogItemsToScreen();
    LogToScreen('<hr />');

//------------------------------------------------------------------------------
    LogToScreen('UNIQUE CONSTRAINT VIOLATION ERROR should follow in Console:');
    //attempt update with and violate our UNIQUE Constraint (Equals)
    mylist.Update(mylist.IndexOf({id:222, firstname:'updated-id-2(first-fixed)', lastname:'updated-id-2(last)'}),
    {id:1, firstname:'first-1', lastname:'last-1'});
    LogToScreen('<hr />');

//------------------------------------------------------------------------------
    let traceDiv2 = document.getElementById('trace2');

    let myObjList = new DList(
        'myObjectList',

        function(a, b) {
            return (a.id == b.id );
        }
    );


//------------------------------------------------------------------------------
// Test storing some references to external objects now...

    let ElementRef1 = document.getElementById('trace-mod-1');
    myObjList.Add({id:1, ElementName:'testdiv1', Element: ElementRef1, toString : function() {
        return ('toString() closure fired: ' + this.id + ', ' + this.ElementName );
    }});

    let ElementRef2 = document.getElementById('trace-mod-2');
    myObjList.Add({id:2, ElementName:'testdiv2', Element: ElementRef2});
    let ElementRef3 = document.getElementById('trace-mod-3');
    myObjList.Add({id:3, ElementName:'testdiv3', Element: ElementRef3});

    myObjList.Items[myObjList.IndexOf({id:1})].Element.innerHTML =
        'testdiv1 : REPLACEMENT via CODE; div referenced via List.Element (stored reference); stored closure results: ' +
            myObjList.Items[myObjList.IndexOf({id:1})].toString();
    myObjList.Items[myObjList.IndexOf({id:2})].Element.innerHTML = 'testdiv2 : REPLACEMENT via CODE; div referenced via List.Element (stored reference)';
    myObjList.Items[myObjList.IndexOf({id:3})].Element.innerHTML = 'testdiv3 : REPLACEMENT via CODE; div referenced via List.Element (stored reference)';

} //end function DListTest



ES2015 DList Class embedded JSFiddle...





Continue to read this Software Development and Technology Blog for computer programming, software development, and technology Techniques, How-To's, Fixes, Reviews, and News — focused on Dart Language, SQL Server, Delphi, Nvidia CUDA, VMware, TypeScript, SVG, other technology tips and how-to's, and my varied political and economic opinions.

Delphi VCL TMemo Class-Helper: Calculate SelStart when WordWrap is enabled

DELPHI Source Code — Class Enhancement: VCL TMemo Class Helper : Calculate SelStart in Memos where WordWrap is True (adjusts for "soft carriage returns")

The Delphi VCL TMemo controls are very useful for multi-line text input, as they support embedded "hard" carriage-returns / line-feeds (CR / LF), but they also support "soft" carriage returns via the WordWrap = True property value. When Word-Wrap functionality is enabled, some tasks that should be simple become rather difficult, like inserting text (a string) into the Memo control's existing text and having that Text then show immediately as "selected" / highlighted using SelText related properties of SelStart and SelLength.

This Delphi TMemo Class-Helper adds a very handy function to the standard TMemo control that will make the task of highlighting (selecting) newly-inserted text simple, regardless of whether WordWrap is True (On) or False (Off). See the inline (in source code below) comments for more details about how this method works.

This has been tested with Delphi 2006 and 2010 and should work with any version of Delphi with the standard VCL TMemo control and the class helpers language feature. In absence of classhelpers, you can certainly still use this as a standalone function in earlier versions of Delphi (pre-Delphi 2005).

You may need to adjust the Uses clause(s), but hopefully all references to the units and/or functions you will need have been included. No matter what, the core algorithm for determining the proper SelStart within the TMemo should give you what you need to work with soft line feeds / carriage returns within a TMemo just like you would as if WordWrap was not enabled.

SQL-Server User Defined Function (UDF) Source Code

//********************************************************************************
//This source code is Copyright (c) 2007-2017
//     Author: Mike Eberhart
//
//I hereby release this code under the terms of the MIT License (for freeware).
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in
//all copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//THE SOFTWARE.
//********************************************************************************
unit ClassHelpers_VCL_Example;

interface

uses
  Classes,
  StdCtrls;
  
  
type
  
  procedure LockWindowUpdateEx(Handle: HWnd; SleepTicks: LongWord; Retries: LongWord);

  {▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
  Extend the Memo controls
  {▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪}
  TMemoHelper = class helper for TMemo
  public
    procedure SelectCharsEndingAtLineCol(const Row, Col : Integer; const NumCharsToSelect: Integer);
  end;

  
implementation

uses
  SysUtils,
  StrUtils;
  

{■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
Procedure: SelectCharsEndingAtLineCol

Parms:
  Row, Col:   These are the Line, Column coordinates in the Memo where your
              Characters-to-Select END.

  NumCharsToSelect: obvious


Note:
  In a TMemo, Line and Col are 1-INDEXED variables, meaning Line = 1 when
  at top or memo, and Col = 1 when at leftmost side of memo.

  If WordWrap is True on the Memo, this procedure contains necessary algorithm
  to "detect" and adjust for any "Soft Carriage Returns" that WordWrap is
  injecting into the memo for visual display.

  If only SelStart was specified in Line, Col format like everything else in
  Memo-control coordinates, this custom-code would not be necessary.
  
Example of how this is useful:
  //Let us consider wanting to programmatically insert text into a memo control and
  //have that inserted-text instantly show as "selected" (seltext) upon its insertion.
  //We will Insert our text (at current cursor location or to replaced existing
  //selected text) via SelText; then, call this helper routine to "highlight" our
  //newly inserted text.
  //See the source-code comments for how we ultimately determine and set the new
  //values for SelStart and SelLength to accomplish our goal.
  
  SelText := OurTextToInsertIntoMemoContents;
  SelectCharsEndingAtLineCol(self.Line, self.Column, Length(OurTextToInsertIntoMemoContents));
{■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■}
procedure TMemoHelper.SelectCharsEndingAtLineCol(const Row, Col : Integer; const NumCharsToSelect: Integer);
var
  SoftCRLFsToRemove, LineToSkip, SkipChars : Integer;
  slNoWrap, slWrapped : TStringlist;
  WrappedIndex, NoWrapIndex, NoWrapLength, AccumWrapLength : Integer;
begin
  SkipChars         := 0;
  SoftCRLFsToRemove := 0;

  //No use processing unless these conditions are met...
  if (Lines.Count > 0) and
     (Row <= Lines.Count ) and
     (NumCharsToSelect > 0 ) then
  begin
    {═══════════════════════════════════════════════════════════════════════════════
    Due to the INSANITY of a Memo's "SOFT-CARRIAGE-RETURNS" (if WordWrap is True)
    it is impossible to calculate SelStart without doing some really whacky
    processing.  In particular, we create a "duplicate" of the Memo-Lines with the
    lines of text up to Cursor-Position, and then count the difference between
    the "lines" that exist pre/post WordWrap -- will need to later subtract off
    the difference in lines (times 2 - one each for CR, LF chars).
    {═══════════════════════════════════════════════════════════════════════════════}
    if (WordWrap = True) and
       (Row > 1) then        //if row to insert/select on is first row, no "adjustment" will be needed...
    begin
      slNoWrap  := TStringlist.Create;
      slWrapped := TStringlist.Create;

      try
        slWrapped.Assign(Lines);

        Lines.BeginUpdate;
        LockWindowUpdateEx(Handle, 20, 5);

        WordWrap := False;
        slNoWrap.Assign(Lines);

        //ShowMessage(IntToStr(slNoWrap.Count) + '    ' + IntToStr(slWrapped.Count));

        WrappedIndex := 0;
        for NoWrapIndex := 0 to slNoWrap.Count - 1 do
        begin
          NoWrapLength := Length(slNoWrap.Strings[NoWrapIndex]);
          if Length(slWrapped.Strings[WrappedIndex]) < NoWrapLength then
          begin
            AccumWrapLength := 0;

            While (AccumWrapLength < NoWrapLength) do
            begin
              AccumWrapLength := AccumWrapLength + Length(slWrapped.Strings[WrappedIndex]);
              Inc(WrappedIndex);

              //if we have gone as far as the Row (Line) in which the cursor was
              //positioned while wordwrap was on, then time to break out of here...
              if WrappedIndex = Row then
              begin
                AccumWrapLength := NoWrapLength + 1; //to break out of while... (and, indicate to break the "for"
                break;  //break out of While...
              end
              else
                if AccumWrapLength < NoWrapLength then
                  Inc(SoftCRLFsToRemove);

            end; //while

            if AccumWrapLength = NoWrapLength + 1 then
              break; //break FOR loop if Row target met.

          end
          else
          begin
            Inc(WrappedIndex);

            if WrappedIndex = Row then
                break;  //break out of For...
          end;

        end; //for NoWrapIndex

      finally
        slNoWrap.Free;
        slWrapped.Free;
        LockWindowUpdateEx(0, 20, 5);
        WordWrap := True;
        Lines.EndUpdate;
        Application.ProcessMessages;
      end;

    end; //if WordWrap was on and adjustment calc needed...


    //Count characters in line(s) up to, but not including the line on which
    //our selected text is on.
    for LineToSkip := 0 to Row - 2 do
      SkipChars := SkipChars + Length(Lines[LineToSkip]) + 2;   //the "+2" adjusts for CR/LF not otherwise counted

    //Now move over appropriate number of columns, less length of string we'll select
    SkipChars := SkipChars + Col - 1 - NumCharsToSelect - (SoftCRLFsToRemove * 2);

    //Now, "select" the appropriate region in memo
    SelStart  := SkipChars;
    SelLength := NumCharsToSelect;
  end; //if
end; //SelectCharsEndingAtLineCol
  
  
  
{▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪
Prevent repainting of window/control during heavy manipulation of visual items
 - especially useful with visual "lists" (e.g., memos, treeviews, shelltrees, etc).
{▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪}
procedure LockWindowUpdateEx(Handle: HWnd; SleepTicks, Retries: LongWord);
var
  CurrentRetry : LongWord;
begin
  CurrentRetry := 0;

  If Handle = 0
  then LockWindowUpdate(Handle)
  else
    While (CurrentRetry <= Retries) and not LockWindowUpdate(Handle) do
      begin
        Inc(CurrentRetry);
        Sleep(SleepTicks);
      end;

end; //LockWindowUpdateEx



end.


Continue to read this Software Development and Technology Blog for computer programming, software development, and technology Techniques, How-To's, Fixes, Reviews, and News — focused on Dart Language, SQL Server, Delphi, Nvidia CUDA, VMware, TypeScript, SVG, other technology tips and how-to's, and my varied political and economic opinions.

Saturday, January 21, 2017

Delphi Source Code to Convert between TColor and RGB Color in String Format

DELPHI Source Code — Function: Convert between TColor and RGB Color (as String representation)

Here is the code for a pair of useful Delphi functions for converting and moving color values between Delphi TColor (object / class) type and string representations of an RGB Color value (with or without leading # character).

If you use Delphi to output HTML code or CSS code that includes RGB color values, and you are using Delphi to edit colors or are otherwise manipulating colors using the Delphi TColor type, these routines should make it simple to convert between TColor and RGB-encoded strings.

The surrounding code shows the few USES units that were referenced. (and, this was tested through Delphi 2010)

Delphi Functions Source Code

--********************************************************************************
--This source code is Copyright (c) 2007-2017
--     Author: Mike Eberhart
--
--I hereby release this code under the terms of the MIT License (for freeware).
--
--Permission is hereby granted, free of charge, to any person obtaining a copy
--of this software and associated documentation files (the "Software"), to deal
--in the Software without restriction, including without limitation the rights
--to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
--copies of the Software, and to permit persons to whom the Software is
--furnished to do so, subject to the following conditions:
--
--The above copyright notice and this permission notice shall be included in
--all copies or substantial portions of the Software.
--
--THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
--IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
--FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
--AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
--LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
--OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
--THE SOFTWARE.
--********************************************************************************

unit ColorsUtilRGB;

interface

uses
  Windows,   //for RGB Macros
  Math,      //IfThen
  StrUtils,  //IfThen
  SysUtils,  //Format
  Classes,
  Graphics;  //for Shape type

type

  //Our RGB/Color Routines
  function GetColorASRGBString(
    const ColorToConvert : TColor; 
    const IncludePrefixChar : Boolean = True) : String;
    
  function GetRGBStringAsColor(const RGBString : String) : TColor;

implementation

 

{==============================================================================
GetColorASRGBString:

Convert a Tcolor Type to a String representation of the TColor's RGB equivalent
value in Hexadecimal digits.  If IncludePrefixChar (true by default), append
the "#" prefix to the string (showing Hex prefix).


Some TColor Notes:
  To assign a HEX value to a TColor type, cast as follows (example is for a 
constant declaration): clLightTan    = TColor($00CCEEEE);

  The HEX values represent RGB, but are in low-order byte to high-order byte
arrangement (i.e., it's really BRG reading left-to-right, so read right-to-left
for two-byte pairs of Red/Green/Blue)
===============================================================================}
function GetColorASRGBString(
  const ColorToConvert : TColor; 
  const IncludePrefixChar: Boolean): String;
var
  r,g,b         : Byte;
  CurrentColor  : TColor;
  HexColorWithSpaces : String;
const
  HexFormatStr  : String = '%2x';
begin
  CurrentColor  := ColorToConvert;

  CurrentColor  := ColorToRGB(CurrentColor);
  r := GetRValue(CurrentColor);
  g := GetGValue(CurrentColor);
  b := GetBValue(CurrentColor);

  HexColorWithSpaces := IfThen(IncludePrefixChar, '#','') 
    + Format(HexFormatStr, [r]) 
    + Format(HexFormatStr, [g]) 
    + Format(HexFormatStr, [b]);
  Result := AnsiReplaceStr(HexColorWithSpaces, ' ', '0');
end;


{==============================================================================
GetRGBStringAsColor:
This is the opposite of the prior function... Take a string representation of 
an RGB-encoded color and return a Delphi TColor equivalent.

ASSUMES inbound RGB String is EITHER:
  6 CHARACTERS LONG, NUMBERS/LETTERS ONLY!!
    OR
  7 CHARACTERS (WHERE FIRST CHAR IS "#" PREFIX)
===============================================================================}
function GetRGBStringAsColor(const RGBString : String) : TColor;
var
  RGBStringToConvert    : String[9];
  RBGStringChecked      : String;
begin
  if LeftStr(RGBString, 1) = '#' then
    RBGStringChecked := RightStr(RGBString, Length(RGBString) -1)
  else
    RBGStringChecked := RGBString;

  //Put in proper order for the StrToInt conversion 
  //(expects as B, G, R and NOT IN RGB order).
  RGBStringToConvert    := '$00' 
    + Copy(RBGStringChecked, 5, 2) 
    + Copy(RBGStringChecked, 3, 2) 
    + Copy(RBGStringChecked, 1, 2);

  Result := TColor(StrToInt(RGBStringToConvert));
end;



end.


Continue to read this Software Development and Technology Blog for computer programming, software development, and technology Techniques, How-To's, Fixes, Reviews, and News — focused on Dart Language, SQL Server, Delphi, Nvidia CUDA, VMware, TypeScript, SVG, other technology tips and how-to's, and my varied political and economic opinions.