I‘ve not written a while about programming, let alone anything about that mailing program, RoundAbout. Today’s subject is the Windows TreeView control and its data section, the TreeNodes. As you probably know, a TreeView shows data in an hierarchical way, allowing users to ‘expand’ or ‘close’ so-called TreeNodes, so to expose or hide data they do and don’t want to see. TreeNodes in the Windows world can have user-data attached to it and often, developers will use this to store program specific information. I believe in .Net, this data propery is called ‘Tag': in the Delphi world, it was called plainly ‘Data’. So code-wise this could look like this:
var Node: TTreeNode;
/* FolderData is a pointer to a record */
Node := TreeView.Items.AddChild(Node, FolderData^.Name);
/* Attach the FolderData to the current node */
Node.Data := FolderData;
Early in the process of development, it was noticed that any font or even layout changes caused severe crashes in the TreeView: The TreeView in RoundAbout holds account and message/mail folder information via each TreeNode’s Data member. After a long investigation, I noticed that this was caused by the Delphi TreeView control itself: whenever a window anywhere was refreshed, Windows would send a message to the TreeView control to refresh and recreate itself. Let me say that again: whenever a font was changed in RoundAbout, this would trigger any TreeView to recreate itself. I think this issue was resolved in later versions of Delphi, but I decided to create my own control (PMTreeView, code here) to preserve the data with the following premises:
- Override CreateWnd and DestroyWnd
- Upon destruction of the treeview window, save the data for the nodes to a memorystream.
- Upon creation of the treeview window, (if available) load the node data from the memorystream.
- Trigger an event to notify the developer that the Data structure has changed for further activities.
As I mentioned earlier, I believe in subsequent Delphi versions, Borland fixed this in their own TreeView controls. However, for a piece of code that I debugged and coded in a night or so, my own PMTreeView control wasn’t too bad of an implementation.
One of my biggest pet peeves is ‘AutoComplete': the functionality of a text box to suggest (and complete) previously entered data. I’ve seen many bad implementations and the one fresh out of memory I can remember is Opera’s URL box, which I’ve mentioned a many times.
For RoundAbout, I implemented the AutoComplete in the ‘MailProperties’ box (see image above and video right here): The code can be found around this url, look for the method Memo3KeyUp). If I look at the code now (and to be honest, I’m hardly a Delphi programmer these days), I have no idea what I was doing: I recall that in some conditions the Autocomplete should stop doing its thing most notably when the user uses the navigational keys. Additionally, the partial matching was troublesome at one time also. What surprised me most was that everything could be implemented in one event (The KeyUP event of that particular memo box). Messy.
Eventually, I grew up and when there was a request to make an autocompleting editable combobox, I was smart enough to clean up the code and section it off in several methods (KeyUp/KeyDown and OnChange). This code end up living in its own component (PMComboBox): the code is so clean that I’m certain you should be able to take this code and rewrite it in other Windows-based programming languages.
I get cranky when I see people use regular expressions or simple substring routines when extracting strings from, for example, e-mail addresses. The first method, while powerful, is memory hungry, the second method is plain childish. You should only use substring/copy methods if you’re hundred percent certain that the data is formatted and well-formed (that is, it comes through exactly as you expect it to. In the case of e-mail addresses, this is of course, not true. After all, e-mail addresses can come in any format. The following samples are all legal: “email@example.com”, “firstname.lastname@example.org (Hey You)”, “ Hey You”, “Hey, You “. Your simple substring copy function would most likely have troubles resolving all of these e-mail variants.
During my Roundabout tenure, we ran into issues where extraction of names/e-mail from e-mail headers didn’t work out as originally planned. I was not surprised to find those evil substring routines in the code and literally rewrote that into a state machine (look for HeaderAddressToStringList). Extremely elegant and very effective.
Why use a state machine then? Because with string operations like this, looping through a string is a lot faster than trying hundreds of “if conditions” to cover all these e-mail cases. Keep in mind that simplicity is the key though: the more states you define, the complexer the code.
Staying on the programming topic: One of the best parts of Roundabout (previously) was the filtering mechanism: before mail was downloaded, the user could (if needed) invoke the Filter dialog box and mark which mails were going to be downloaded (or left/deleted on the server). This happened all in a thread, where connections to SMTP and POP servers were made and commands were issued, synchronously (“blocking”, so you will). The threading class that took care of this was (appropriately) named ‘TOnlineThread’ and resided in a file called TDOnline.pas.
Many times I’ve cursed the existing threading code: I spent plenty of hours fixing up the code, or rather making the code thread-safe, as in, wrapping code that calls the main-thread (the UI thread) in so-called ‘Synchronizers’. Looking through the current code, there were plenty of changes done to this unit (the CVS history only spans a short time and doesn’t count the changes made prior to January of 2005). Anyway, if memory serves well, there were issues during the sending of mail (which happened in that thread) and the update (count of left-over messages) in a mailbox (which resided on the main UI thread) and I ended up correcting the issue, soberly stating:
- FIX (Arthur): Threadsafe RefreshNodes in TDOnline + additional processing…
Which was followed by a more cryptic:
- FIX (Arthur): Sharpened the RefreshNodes/Tree traversal.
Before you click the ‘Continue’ link, you may want to have this link at your disposal, which may explain the whole gimmick below.
I was looking for older code through some older projects and I ended up looking at the code of RoundAbout, a Delphi project I headed with a German (Roland), an American (Marcos) and plenty of other contributors. I thought, I might just as well make this an opportunity to breathe new life into this specific category: There is some exciting and funny code in the project and I wouldn’t do the project good by not showing the effort to collectively create something good out of nothing.
But first: In the early 2000s I ended up looking for an e-mail client for Windows. For years I had been using Eudora, but, I got a bit tired of looking at the ads that Qualcomm pushed upon users. I initially looked at Phoenix Mail, an e-mailer initially programmed by Michael Haller which in turn was passed on to American Delphi developers. Via that mailing-list I discovered Roland’s excellent modernized branch and, as any good open-source branch, I ended up branching his version into RoundAbout because we couldn’t agree on many issues: I believed in going more in-depth (technically speaking) while Roland was more or less adopting a wait-and-see, conservative approach. Both approaches had their successes, and both our branches had attracted a variety of Delphi luminaries and dignitaries (I’m looking at you Duntemann), including offers of help from the original Phoenix Mail branch. In 2004, RoundAbout was left for what it was and like many open-source projects, it died a silent dead. However, without doubt, its code continues to live on in the original branch(es) or is probably floating around on the Internets. I wouldn’t recommend people to try the e-mailer nowadays: it works fine but might look odd on Windows XP and higher.
This brings me to the first piece of code I’d like to point at, which is the ability of adding or dynamically creating toolbars and toolbar buttons: It was heavily pushed by Marcos and it required so many code changes, that I ended up taking the challenge, provided that:
It is possible to create new toolbars and buttons: however, the maximum of toolbars should be 255, the maximum of total buttons (application wide) are 65,5xxx (the number that fits in a word).
I mean, 65 thousand and some buttons should be enough for everybody, right?
For the RB project someone asked me to write an autosizing and autocompleting memobox. Currently the ‘To field’ is implemented as an ‘autocompleting’ combobox: well, typically this combobox was written by myself too. When implementing that combobox I already warned people that this (Windows) control had its limitations. I think the limit is 255 characters. Maybe more, but it wouldn’t be able to show too many e-mail addresses. Hence why one would use the properties dialog.
To make a long story short, the code has been merged with the main sources and now only rests the testing.
After nearly 4 or 5 months of inactivity I’ll be breathing some life back into RB. I already had planned a couple of changes to the user interface just so I can remove the weird components and build on my own ones.
Secondly, I’ve been working on an Autocompleting autosizing memo component which apparently works perfect and (as usual) will be added to the list of homebrew functionality. I have to see if I can get myself into importing WAB books (the ones that Windows comes with): I saw all those smashing examples of calling DLLs or even using COM but it leaves me wonder if these files can’t be accessed directly.
Earlier this week someone (hi Marcos) notified me that one mailer apparantly breaks up attachments in RB. I traced the problem to the following difference:
Content-type: ….[cut] Name=RB settings mods.txt
Content-Disposition: attachment; filename=”readme.txt”
I’m totally confused. When attaching files, do I use the Content-type field to add my filename as ‘Name=xxxx’. Or do I use the Content-disposition with the ‘filename’ entity? The RFC isn’t clear. Study time.