2007-12-10

Even slimmer Delphi for Win32 2007

Slim Following on previous posts, I decided to remove the Windows SDK documentation from my Delphi 2007 installation. The main drive was for the added space: with those 250 MB less I would be able to fit the whole C: into a compressed ghost image file with less than 2 GB and that's with Windows XP SP2 + Office 2003 + Delphi 2007 + quite a few more programs and updates.

Why is it so important to be < 2GB? Because I can easily upload that single file to any FTP server without worrying about the server or client being unable to FTP files bigger than 2GB; because I can copy that to any disk partition/external drive without worrying about the 2GB FAT 32 limit; because it's a single file to keep track of, without risking to have no backup at all just because somehow one of the multi-part files didn't get copied for whatever reason and finally, because I can now fit the file into a 2 GB usb flash drive.

So, rather than just delete the files, I decided to check out the Help registration files and modify them as well. I needed to modify 3 files: h2reg.ini, Master.HxT and RADStudioFilter.xml (get all 3 from here).

What did I gain?

  1. 250 MB less of disk space;
  2. Going from 14 to 4 seconds on the first help call of each session;
  3. Getting less extraneous results;
  4. Being able to now fit my whole C: drive onto a single < 2GB file.

What did I loose?

  1. Basically, the Dinkumware, C++, Delphi.NET and Windows Platform SDK help.

Of these, the most "troublesome" will be the PSDK, but that can be easily accessed online directly from MSDN Library, and has the advantage of being more up to date...

How did I do it:

  1. Go into your "%ProgramFiles%\CodeGear\RAD Studio\5.0\Help\Doc" folder;
  2. h2reg -u
  3. copy the supplied files over the ones with the same name there;
  4. h2reg -r (or run the install_and_view.cmd file that is already there)

Now you can delete the PSDK and Dinkumware folders.

If you want to "play safe", backup the whole DOC folder to a CD or external drive, should you want to go back. To undo, h2reg -u, replace the folder, run the install_and_view.cmd.

BTW: disregarding the .NET pre-reqs, Delphi 2007 for Win32 uses around 400 MB of my disk space now, which is just about right for me!

Get rid of another 343 MB...

If you installed the Help Update, be aware that you now have an extra cached (yes, duplicate!) 343 MB worth of data in C:\Documents and Settings\All Users\Application Data\{15EDF4CD-698A-4E52-8278-2E25143AD95B} (change to wherever you have your user profiles and I don't know where that is in Vista, but if you scan your HDD for the folder named {15EDF4CD-698A-4E52-8278-2E25143AD95B}, you should find out easy!

Yep, that's CG again thinking that they ought to know better than us, developers, and treating us like "dumb users" and caching stuff without even asking for our consent...

343MB

For those thinking "when will this guy stop complaining about this?": you know the answer! When CG stops DOING it! :) I can understand some lack of choices for "normal" software, where the users may need to be protected of themselves, but as a developer, I like to be in control of my machine, rather than have it control me, or in this case, rather than have software waste disk space for no good reason other than because someone was lazy when creating the installer...

Sure, disk space is cheap, but what happens when you try to BACKUP that "cheap" disk space? You're left with no other choice than backing up to ANOTHER disk which has huge drawbacks such as now allowing you to (easily) keep a backup outside your installation so you can quickly recover from a building fire or something... And most 1-man shops just can't afford a fire-proof vault that can keep data backups safe because those things are just too expensive, not to mention bulky and heavy...

EDIT (for those that don't read the comments!): Chris Pattinson, from CodeGear, warns about not being able to run future help patches and needing a full re-install should you delete this folder, so, you can do two things if you still need the space:

1) Back up the folder to a CD/other disk prior to removing it;

2) If you have already deleted the folder, simply install on a Virtual Machine and copy the folder from there. Or, should you have multiple machines with Delphi 2007, just copy the folder from another one. As long as it's added to the same place, you should be fine.

2007-11-29

Delphi 2007 SP3 - Some quirks

On my Help Tests, I ran around some minor quirks and I decided to blog about them as well! (Whoa: 3 whole posts in one day? Don't worry, that's probably 3 more months without posting so you'll have enough time to recover!)

Don't get me wrong, these are minor quirks, but it's also the 4th release of Delphi 2007 for Win32 (Release, SP1, SP2, SP3), so these should have been caught and dealt with by now, unless the quality control processes are seriously flawed... And before someone else goes "Oh, but the product is VERY good if this is all you can say about it", let me just point out that, no, this is not all (I've placed over 40 QC reports back when I did care about doing it), these are just those that immediately jump on you seconds after you start using the product, which doesn't give a good idea about the overall product quality... First impressions usually take longer to disappear...

1) Even selecting "Just me", the shortcut for the RAD Studio Documentation is installed for All Users.

In all honesty, I can't say whether this is a bug from the default SP3 install or caused by the Help Update. In either case, it should have been child's play to both detect and fix this, so there's no real excuse for letting something as simple as this slip through...

2) Minor toolbar sizing errors:
Toolbars

3) Personality Icon not showing (it appears to show only when a project is loaded, which is a bit odd for a single personality product)
Personality

4) Help Improve Visual Studio. WHY? If you buy a Volvo, will you get a form to fill out and return to FORD about how pleased you are with their engine? It doesn't make sense and for a team of developers, it shouldn't be hard to determine what registry key is needed to stop that from showing. Creates a wrong impression, when one does NOT buy Visual Studio and instead opts for buying a CodeGear product and then sees that "Help Improve Visual Studio"!

5) What's with this dull launch screen? So much space and the only thing that changes is a couple lines at the bottom? I used to like the previous launch screen better. Maybe it gets "filled" when you have some optional dotNET "modules", but as it is, it's plain dull...

Dull

Like I said above, all minor quirks, but also all first wrong impressions with a product...

Delphi 2007 Improved Help (?)

If you recall from previous posts (yes, I know it has been a while, but I've been busy!), I was frankly disappointed at the so-called "Improved Help".

The major flaws

Back on release of Delphi 2007 for Win32, the major flaws I found were the following (listed in no particular order, but numbered for easier reference):

  1. Crashing the IDE when requesting help on a menu;
  2. Failing to retrieve help for common Pascal/Delphi keywords;
  3. Giving "precedence" in searches to results pertaining to VB, VC++, Anything else under the moon and, in the last spot, Delphi;
  4. Taking up a huge amount of space with non Delphi for Win32 help contents;
  5. Using an Help engine that would consistently remain in memory leaking resources;
  6. Failure to retrieve help for components in forms or giving too many pointless options to choose from;

What has changed on SP3

#1 was solved with SP1, IIRC;

Help on End#2 Simple test program loaded from the demo ones. Pressed help on unit, interface, uses. All of these would pop a few options to choose from, but among those was the "correct" one. Then press F1 over type: only two options given and neither very useful... Class, procedure: again a few choices; private: a few choices too and among them the "good" one, but the one that I clicked first because it looked as the most promising, took me instead to the C++ reference section. No, that's not C++.net, but rather RAD Studio C++ reference. Odd for a "Delphi for Win32" product. Yes, I know that they have common bases for all their products, but that's THEIR choice and US, END-USERS, should NOT be bothered with that. If I'm programming in Delphi (Pascal), I should NOT need C++ help on my system and certainly not being offered that help when I press F1 on a Delphi keyword... The image on the right shows a list of the choices given when pressing F1 on end (but a similar one shows up for begin as well)...

#3 From the tests above, it appears that this has been nicely worked out: many VC++, VB or J# entries still show up, but usually towards the END of the list, rather than being at the top of it.

#4 remains the same and I don't expect it to change seeing that it's a common base for their Win32+dotNET products;

#5 I couldn't get a single DEXPLORER instance to stay in memory. Maybe the conditions that cause that are rather peculiar, but in all the open/closes I did, not once did I see it left or even another instance being loaded while another one was already in memory.

#6 I had to try REALLY hard to find a component that would not get me to that component's reference help. Almost every single component, from those in a form, to even those in the component selector, got me to the proper page after pressing F1 with no further questions asked.

Conclusions

1) Definitely a much welcome improvement, but still needs a lot of work. It's sad that 9 months after the initial release that touted "Improved Help" as one of the key factors for purchasing the new version, it still fails to live to that promise. Maybe by the time they get to Delphi 2008 the help is then at the standards they said it was back on release of Delphi 2007. Let's just hope they keep improving it, because it certainly looks as they still have a lot of work to do, but as long as they keep doing it, maybe in the future we will get a decent help!

2) There are still quite a few "place-holders" like the one in this example:
Place Holder

3) I was, towards the end of my tests, prompted with this:
Local Help

I opted to use Local Help as the primary source and haven't tested with the default option given as shown above. Seemed to work pretty much the same but only did a few more tests.

Delphi 2007 for Win32 SP3

This week I've been a bit sick. Nothing serious, just a nasty cold with the nasty side effect of going through paper tissues like there was no tomorrow.

So, rather than trying really hard to stay focused on debugging tasks at hand when I had to interrupt every 30 seconds to wipe my nose, I decided it was about time to do something I've been wanting to do for a long time and just couldn't afford the time to do it: upgrade my Delphi 2007 SP1 with both the SP3 and the improved help files that were released after that. That's something that doesn't require much concentration and is "compatible" with using paper tissues every 30 seconds...

So, I start by going to the Delphi registered user's downloads and download two files:

Armed with those files, I reset my computer to a ghost file with nothing but Windows XP SP2 + patches + drivers. After a few hours of installing more patches and all the software I use *except* Delphi (a process that is now much faster as I keep many utilities pre-installed on my D:\Utils folder), it was time to tackle the main procedure: Install Delphi 2007 SP3.

So, I start by creating a new ghost image to have a more recent "fall-back" should something fail, and I mount the ISO on a Virtual CloneDrive.

My first impression is not good when I double-click the drive:

Error01 Error02 Error03

I then proceed to open the drive instead and manually click the install file which brings me to this:

InstallLauncher

Pre-Reqs

Pre-Reqs Disk Space From this point on, everything runs much smoother: I proceed to install the pre-reqs, which I intentionally had not installed before, and then a reboot is in order.

The pre-reqs took an astonishing 2 GB of my C: drive as you can see from the image on the left. After closer scrutiny, you can easily see that it still suffers from the same "bugs" as the original install, namely, caching the same install files in several different places. That's 1.1 GB worth of .NET SDK install files in those two folders marked in the image.

This whole process did complete without any problems and within approximately 10 minutes, after which I had to reboot to proceed with the install.

 

 

Install

Delphi Disk Space Then, on to installing Delphi 2007 for Win32 SP3. After inputting my registration data, I selected to install everything available to my PRO SKU, including Rave Reports. The whole process took around 15 minutes and again proceeded with no problems.

As you can see, Delphi itself, discounting the pre-reqs, requires around 1.5 GB in the Professional Win32 SKU. That's roughly 450 MB for cached install files, 340 MB for help files and 690 MB for CodeGear files in either the program folder or common files.

I could have ran the IDE, but instead I opted for installing the Help Update. The one minor quirk about it, is that you need to go to a command line and type HelpSetup /upgrade. But the upgrade ran smoothly and this time I didn't time it, but it was around 10 or 15 minutes. Not worth another space screenshot as it only differed in a couple MB.

 

Full space report

Full  Disk Space To the left you can find a FULL disk space report before installing even the pre-reqs and after installing Delphi and the help update. As you can see, that's 3.7 GB worth of stuff, from pre-reqs to cached files to more cached files to the proper program files. Not really an improvement over the original setup, IIRC... However, since I believed that at least 1.1 GB of this can be safely shaved, that's what I'll did next. So, I removed the first of the two folders marked in the Pre-Reqs image above and was surprised to find out that the 2nd one was no longer there. However, the total space used was not consistent with that 2nd folder being removed, so after a quick search, I found out that it had been instead moved into my own Local Settings rather than the All Users' one. That's consistent with the choice I was given of installing for myself only (which I chose), or installing for all users. So, I deleted that 2nd folder and then ran Windows Update to download the latest .NET 2 security updates. As expected, it worked flawlessly, and I don't expect to be running into problems by not having those files around.  Of course, Windows Update doesn't update the SDK itself, so this was not a big test. But if you want to play safe, just burn those two folders to a DVD should they be required at some upgrade point in the future...

So, the (current) final space used is down to 2.5 GB, but that's without having yet followed my own guide on how to trim some more MBs out of it. That's intentional, as I want to test the help file and see if it has been REALLY improved without using my own tweaks. That will be the subject of my next post which should be posted either later today or tomorrow, if all goes as planned.

Conclusion

The whole process ran very smoothly, even if CodeGear still thinks that, just because disk space is cheap, they can waste as much as they want. Sure, disk space IS cheap, but if you want to create a ghost image of your main working drive and burn that to a DVD, having 2 GB worth of data or having 2 GB worth of data + 2 GB of worthless cached files DOES make a big difference in making it all fit onto a DVD or not. The way I do it, if anything other than an hardware failure goes very wrong, I can be up and running with a working configuration by popping a DVD into my drive and restoring my system to a full working condition in under 20 minutes, instead of re-installing everything for the better part of 1 or 2 days. The same problem applies to creating and backing up a Virtual Machine with a full working install, so, please CodeGear, stop wasting one's disk space just because it's "apparently" cheap!

2007-09-24

Windows Live Writer and Pascal Code Formatting (updated)

Since Steve Dunn has updated his WLW add-in, I checked it and found out that it still doesn't use the fixed pascal definition file that I sent Actipro some months ago.

So, if you want proper pascal syntax highlighting, download his control, follow his instructions and afterwards copy this file over the same named one in the languages folder.

Among other fixes that I recall are some missing keywords (unit for instance) and support for // comments and proper nested comment / string support.

2007-07-27

The best 330 MB of my C: drive...

Well, I hate to be negative about stuff, I really do, but I just can't help it. Having fought so many times with a lacking help system, I finally decided it was time to uninstall it. It's pretty useless to me as if I want MS docs I'll search for them on the net and they will be more up to date. So, I finally decided to go to Add-Remove Programs and Change the Rad Studio installation to remove those help files.

In doing so, I found out a few more things:

  1. Install-aware sucks even more than I thought! What used to be a few seconds on a decent installer takes over 6 minutes to do in this Install-Aware setup!
  2. They use a lot of progress bars but really show no progress: a blinking light would show the same information... At one point 2 were present at once and really I could be no closer or farther away from knowing how much more time I'd have to wait as they were file-based progress bars and not *process* based ones...
  3. What the heck is a "Cach"? As in "Removing Cach folder xxx"
  4. Shouldn't I get a more graceful message when pressing F1 on RAD Studio if I chose to remove the help files? (in a legitimate way and not hacking the files!)
    And the message even shows in Portuguese even though my Windows and RAD Studio are both English...
    X
  5. Finally, if I chose to remove the cached install files, what are 100 MB left there for?

This is all a nasty combo of a lousy installer with probably some lousy install scripts...

Q: Would I uninstall Delphi 2007?

A: Well, I've got used to it and it works well overall so most likely no, I'll keep using it. I just got fed up with the so labeled "Improved Help"... Pitty that MSFT set the road that others followed by releasing unfinished software...

For those who may not like me "bitching" about this: would you like to buy a car that "sort of works"? And that has to go through a few Service Packs before you really got what you're promised it would be before purchasing? I'm just applying the same principle to Software...

[EDIT: I've just "fixed" that error message: removed the htmlhelp2100.bpl (or something like that) from the known IDE packages so now pressing F1 silently fails. I'll probably try and find some replacement help files/reader as I vaguely remember something about using previous versions help files. Then I'll try downloading those help files from the customer area if it works! :)]

2007-07-16

No comments...

Well, I lied! I have one comment: I did press the feedback link, even though the question looks almost a joke! :)

Joke

BTW: The Void stamp and yellow highlighting is just me playing around with SnagIt! :)

EDIT: It appears it gets even better:

Bug2

2007-07-14

Delphi "killed" by VB6? WTF?

Well, I read in TWM's blog that Delphi is listed in the 12 languages that never took off.

Since I can't comment on either page, I decided to comment in my blog...

I can only say one thing: that article reminds one of the languages mentioned in it (the one that starts with brain...); that article is surely someone's brain fart!

Saying that VB6 killed Delphi and showing a stupid piece of partial code as if it's a "hello world" program???

And for those that would accuse me of attacking that article because I'm a Delphi fan-boy: just read my blogs here and you'll know that I'm everything but a fan-boy!

But I also don't like to see some moron (sorry, but can't help to use that word), say such things about Delphi.

Is that guy getting paid from MSFT or something? Or is he just a dumb arse?

[Edit:]

I've since gone to digg.com and checked out who blogged about that digg among other things: it turns out that my impression that digg was just full of meaningless trash is correct! Of those that "blogged" about that digg, nearly all are non-related commercial sites of some sort that are probably just using some robot-like technology to crank up their site in the listings!

Now, I already thought that digg was a waste of time but now I know for sure that it is really a waste of time! At least that dumb article had some usefulness! :)

2007-07-13

Command line: do you still use it?

Well, I do and for quite some years I've been using JPSoft's 4NT. I have some nice batch files, custom prompts, custom functions and it has a whole lot of built-in commands, variables and functions as well.

But, not only do I find myself doing less actual command line stuff, I sometimes get a bit mad at this product, like yesterday when I was attempting to use drive aliases inside a function and later found out that it doesn't work there, only in commands... Kind of the legacy from a program that, the author has admitted, has a ton of different parsers for a ton of different functions/commands. It's also partly due to the fact of maintaining compatibility with command.com and cmd.exe in their many versions and changes so far, but sometimes I think that it would be better to use something else for my command line scripting, perhaps something that doesn't carry this legacy compat baggage and thus can have a more uniform and predictable syntax.

Which brings me to the question: do you still do comand line? What tools/programs do you use for it? Or do you rely on good old fashioned cmd.exe for your command line work?

I've once tried to use PowerShell from MSFT, but, not only did it still have a lot of rough edges (like, for instance, not being able to easily work on files with some valid dos file name characters!), and, even though it is a very considerable change from cmd.exe, and sure looks powerful, it wasn't that much better than cmd.exe and even a bit lacking in many basic functions, at least a couple years ago when I tested it!

So, if you have a nice experience with something other than 4NT/CMD/COMMAND, let me know what you use and how happy you're about it and I may even go and try that for a change!

2007-07-10

Quick tip for compiling/testing DLLs

Now that Delphi 2007 is using MSBuild, you can take advantage of the Pre-Build events to automate a common headache: killing that task that is using your DLL before you build it!

I'm currently developing an Outlook add-in so I found myself repeatedly quitting Outlook or getting an error because Delphi can't generate the DLL if I don't close Outlook, so, to solve that, and since, for some weird reason, I got used to always build instead of compiling, I just added the following command to this project's Pre-Build Events:

taskkill /F /IM outlook.exe

DIR >NUL

And that's all there is to it. You can also launch outlook after the build so as to immediately test your changes by adding the proper command to the Post-Build events, but I didn't add that as sometimes I do a few builds just to check syntax/compiler errors while I'm still working on something and I don't want to have outlook popup everytime...

Also, you can try without /F (force) and if your outlook or dll host can terminate gracefully in a speedy way you won't need to force termination, but in my PC and with Outlook it doesn't quit that fast, so I need the /F...

EDIT: The DIR >NUL command is just so that, should you not currently have outlook running, the error from taskkill won't stop your build process! :)

BTW: you can achieve something similar in BDS 2006 (and maybe 2005: haven't tested on this one), if you read my blog post about adding pre/post build events to BDS 2006...

2007-07-06

Code formatting and readability

Well, each developer tends to have his own set of rules be that for code formatting, identifier naming or whatever. And each developer, when looking at someone else's code tends to go like: "wow: what was this guy thinking?"...

Here's a piece of auto-generated code from Add-In Express:

procedure ActivateEvent(Sender: TObject);
procedure ClickEvent(Sender: TObject);
procedure CreateEvent(Sender: TObject);
procedure DblClickEvent(Sender: TObject);
procedure DeactivateEvent(Sender: TObject);
procedure DestroyEvent(Sender: TObject);
procedure KeyPressEvent(Sender: TObject; var Key: Char);
procedure PaintEvent(Sender: TObject);


I find this very annoying and hard to read/browse! So, even though I know it serves no useful purpose other than please my eyesight, I turn that into something like this:



procedure ActivateEvent ( Sender: TObject );
procedure ClickEvent ( Sender: TObject );
procedure CreateEvent ( Sender: TObject );
procedure DblClickEvent ( Sender: TObject );
procedure DeactivateEvent( Sender: TObject );
procedure DestroyEvent ( Sender: TObject );
procedure KeyPressEvent ( Sender: TObject; var Key: Char);
procedure PaintEvent ( Sender: TObject );


Now, isn't this 2nd version much easier on the eyes? Surely it compiles the same as the 1st one no faster nor slower no more and no less bytes used, but for us, humans, it's way easier to look at!


Heck, I even do this on forms after they are stable enough and I know I won't be adding too much stuff/events to it! :) And if I do add, I'll re-sort and re-format those lines! :)


There are a ton more of things I change for cosmetic reasons, such as placing begin/end on pretty much every single if, even if it really wasn't needed (there are exceptions mainly when the one-liner if's are used as a sort of case construct where keeping them one-lined does help readability).


Again, a small sample:



if FPropertyPageSite = nil then
if Assigned(ActiveFormControl) then
if Assigned(ActiveFormControl.ClientSite) then
ActiveFormControl.ClientSite.QueryInterface( IID_PropertyPageSite, FPropertyPageSite );


And, after reformatting:



if FPropertyPageSite = nil then begin
if Assigned(ActiveFormControl) then begin
if Assigned(ActiveFormControl.ClientSite) then begin
ActiveFormControl.ClientSite.QueryInterface( IID_PropertyPageSite, FPropertyPageSite );
end;
end;
end;


Now, you may think that there is not much difference in here, but there is! Especially if some have begin/end and others don't and depending on what code follows. Plus, always using begin/end means you don't need to look too hard on the code as to where to place the begin/end pair when instead of a single line inside a multiple if you need to have two lines of code: all the code layout is perfectly done and you just add that extra line where you want it without much trouble...


There are many more things that I change, even in code that is not my own as long as I'm the only one using it and as long as I need to change/edit/maintain it, but I thought of these as I was making some changes to this code and figured I'd take a few mins break and blog about it! :)


Also, you can take advantage of Delphi's built-in begin/end helper: just type begin at the end of a line, place your cursor to the end of the last line of the code block to be enclosed and press enter: et voilá, instant end properly lined up, courtesy of Dephi's IDE... :)


[EDIT: I knew I was being lazy with the begin/end/if example so here's a better one...]



// Messy version
begin
Result :
= (AControl.Handle = Focused);
if not Result then
for i := 0 to AControl.ControlCount - 1 do
if AControl.Controls[i] is TWinControl then
if TWinControl(AControl.Controls[i]).Handle = Focused then
Result :
= True;
else
if TWinControl(AControl.Controls[i]).ControlCount > 0 then
Result :
= SearchForHWND(TWinControl(AControl.Controls[i]), Focused);
end;

// Cleaned up version
begin
Result :
= (AControl.Handle = Focused);
if not Result then begin
for i := 0 to AControl.ControlCount - 1 do begin
if AControl.Controls[i] is TWinControl then begin
if TWinControl(AControl.Controls[i]).Handle = Focused then begin
Result :
= True;
end else begin
if TWinControl(AControl.Controls[i]).ControlCount > 0 then begin
Result :
= SearchForHWND(TWinControl(AControl.Controls[i]), Focused);
end;
end;
end;
end;
end;
end;


Of the two pieces of identical code, which one do you think is easier to add something to? Say an else to one of the if's or some other instruction in any of the "branches"... And note that, the indentation on the messy one is not that messy and it helps a bit, but you sure can follow the cleaned up version better when trying to see all possible outcomes from that function, no? It certainly works much better for me at least! :)

Bugsie - Part 2

In my previous post, I mentioned a minor layout bug that I watched during the CodeRage Encore presentations. I can now consistently replicate it but other people have mixed success depending on what system they use. Here are the necessary steps to reproduce that show that it is in fact a question of the IDE not redrawing the gray/black corner handles.

  1. File -> New Form
  2. Add 2 buttons to the form such that they're not aligned
  3. Drag a selection rectangle and note which button has darker corner handles
  4. Right-Click the other button, that is, the one with the lighter corners
  5. Select Position -> Align -> Cancel
  6. At this point, if the bug happens on your system, the corner grabbing rectangles should not have changed colors, i.e., the button with the darker corners is still the same
  7. If step 6 happens on your system, then click the Align Tops on the Align toolbar and see that the anchor button was the wrong button!

Now, I said that you could see that it was a display issue. Here's how to do that:

  1. Repeat steps 1-5 on the above list
  2. If, after step 5, you can confirm that the button corner handles haven't changed color, i.e., bug is happening, then simply open up something that will cover the form so as to force Delphi to later redraw it (Internet Explorer, FireFox, Explorer, Outlook, etc)
  3. When you move that window away (or switch back to Delphi), you will see that the darker corners are now properly displayed.

Here's the QC report created on this one:

Report No: 48631          Status: Reported
Form designer may fail to correctly update changed anchors
http://qc.codegear.com/wc/qcmain.aspx?d=48631

BTW, if you need more information on the black/gray corner handles, check this post from Steve Trefethen...

2007-07-05

Can you confirm this bug?

Well, 3 months ago, when watching one of the CodeRage Encore presentations, I witnessed a bug in action (more on that sooner!); even though I tried (as you can see in the replay), I couldn't get the presenter to replicate the bug. Now that the replays are finally available and now that I know how to skip so as to quickly find out which presentation and at what point it was (3 months make you forget a lot of stuff, especially when you were thinking the replays would be on after a week or so!), I've finally confirmed that I did indeed saw a bug.

Can you please go here, wait for the video to fully stream (progress bar fully light gray) and then position a few seconds before 06:38 where the bug happens and confirm that it is indeed a bug? I'll report it in QC as soon as I get some 3rd party confirmation on that.

Here's the bug: if you notice in that presentation, there are 3 buttons selected; the middle button has darker corners meaning that it's the button that is used to "control" the action, that is, whatever you choose it relates to that button. Notice that, when selecting the "Align top" the 3 buttons are (incorrectly!) aligned to the first button's top and not to the one with the darker corners. (Watch the video from the start for the explanation on the dark corners). [Edit: the dark corners are either explained elsewhere or later in the video, not before!]

If you can confirm what I saw, then please leave a comment here.

(CodeGear should probably hire me for finding bugs for them, but since I do it for free anyway, I don't think they want to! :))

[EDIT:]

Actually, at first, after making this blog post, I thought it could be some Camtasia bug as I could not reproduce it (as I right-clicked the top-left button it was selected); I have however been able to replicate it afterwards!

Here are the full steps:

1) File->New Form
2) Double click 3 times on TButton
3) Grab button 3 to top left
4) Grab button 2 to mid right
5) Drag a selection box from upper-left to lower-right

At this point, Button 1 is selected;

6) Right-click Button 3
7) Choose Position->Align from the menu and Cancel

At this point, Button 1 still appears to be selected.

8) Click on Align Tops in the Align toolbar and get the wrong behavior doing the align with Button 3 instead.

It appears that Button 3 was in fact selected by right-clicking and that the designer didn't redraw to show that.

BTW: Using Delphi 2007 SP1 on Windows XP with theming active.

CodeGear Replays: not entirely their fault... :)

Well, after my previous post, and after some comment left in by a reader, I decided to "bug" Anders (not knowing he's on vacation!), and it appears that the problem with the video not skipping to where you try to skip is Camtasia's fault... Having tried Camtasia in the past, and seeing how many "helper" files it generates and all of them including the video duration, I would expect it to be able to do something as simple as positioning the video cursor somewhere where the video has already streamed (you can tell by the bar color, gradually filling with a lighter gray where it has already streamed).

It appears that instead, you have to wait until the whole video has finished streaming before you can reliably position the video playback cursor somewhere! I just browsed some of their own videos and noticed that they too suffer from the same problem so it does not look like it's CodeGear to blame on this one! :)

On the plus side, Anders also said he might be looking at making the download links available after returning from his vacation next week...

For now, you can start the playback of any video, pause it (if you don't want to be listening to it while it streams), and when the progress bar has completely filled light gray, you can then position the playback cursor wherever you want and use that as a quick way to skip past content that you want to, well, skip! :)

2007-06-28

CodeGear Encore - The (non) Replays

As you may have noticed, CodeGear finally placed the replays online. Well, I'm not even going to mention that the initial plan was to have those available only to registered Delphi Customers as it was in those terms that the event took place... (Well, it seems I did mention it, but I have no problem that it's available for everyone!)

What I'm complaining here is about the lousy job done that took them 3 whole months to do!

Sure, I know that CodeGear has been busy but did they really need 3 whole months between the CodeRage Encore event and this (let's be nice) publishing of the sessions?

I mean: where are the download links? Only choice is to view online. But much worst than that, try and position the video replay cursor somewhere in the timeline: it will never be where you put it, thus completely preventing you from skimming those contents...

Three months ago, when the event took place, I saw what looked like a bug; I tried to get the presenter to repeat the steps but that didn't seem to reproduce the bug; since we had already been promised the replays before the event started, I didn't worry too much. I though: I'll wait for the replays and try and see if I did really see a bug in action and if true, I'll report it, if not, no further action will be needed.

Well, never in a million lifetimes would I think that publishing those replays would take 3 whole months and, worst, that after those 90 days, the job would still be done in a very amateurishly way...

CodeGear, this is not the way to tell us customers that one of your goals is quality: doing a bad service and taking so long to do it so bad is not what I or many others would call quality...

BTW: Should the responsible at CG want to at least fix the skimming bit, it has to do with the many files that Camtasia uses and the different ways of indicating the video size: you can't re-use them for other presentations unless they have the exact same duration, otherwise you'll end up in the mess you have managed to do...

2007-06-20

Windows Live Writer, Spell Checker, Pascal Code Formatting

September 2007 edit: Steve Dunn has updated his control, and so this no longer applies. Check my other post about using his newer version and "fixing" the used Pascal definition file.

After reading this post from Primoz Gabrijelcic, which took me to this other post from Jimmy Bergmark, my Windows Live Writer has once again the Spell check option.

In the 2nd post mentioned above, I saw a reference to another plug-in, a code-formatting plug-in, something that I wanted to add to WLW too, so I decided to check it out. I downloaded it (from here), and was a bit surprised about the lack of Pascal as a choice for language. A quick check later and I saw the different languages each had an XML file, so I thought for myself: "It can't be that hard to grab one and modify it for Pascal". And so, I started the conversion process. Unfortunately, I had placed too much faith on the recently fixed "Improved Help" of Delphi 2007: needless to say that, after a few minutes of fighting with the help, which, although improved since Delphi 2007's release as at least now it no longer renders the IDE unusable if you press F1 on a menu, I had nevertheless to give up. Finding basic things in that help is a pain in the ***, and that's putting it nicely! Be that using filters or no filters, language filters or no language filters, finding Pascal help for a list of basic types has proven to be a much greater challenge than it should have been.

So, attempt number two: Google to the rescue! I googled a bit and within seconds had found this other page, from where I could download the controls that were used by this code formatting plug-in, so, after typing a fake name and e-mail (I hate these "we want your name before you can download" type of sites!), I downloaded the free controls. Extracted the ActiproSoftware.Pascal.xml file and placed it in the code formatting plug-in Languages folder and fired up WLW.

Things didn't quite work out: no Pascal available in the language choices. A little bit more digging, and I found that the DunnHQ.Wlw.CodeFormatter.dll.config file is in fact an XML file with a list of all available languages and definitions, so, after adding the following line:

<language key="Pascal" definitionPath="./plugins/Languages/ActiproSoftware.Pascal.xml" />

and restarting WLW, I "Pasted Clipboard as Code", selected Pascal from the list of languages and got this as result:


Bugger


Humm... Something is amiss surely...


After another quick check, I found the source: the language definition XML files used in the plug-in have LanguageDefinitionVersion="3.0", while this one downloaded from ActivePro has LanguageDefinitionVersion="4.0", so, time to go check if I can find one of those for version 3...


(At this point, I can not even save a draft of this post as I get this when trying to do it:)


Another bugger


(D:\XDesk is my Desktop folder: somehow it's trying to load the language definition for the small line of XML above from the Desktop instead of from the program's start location..)


So, after a quick copy/paste of that plugins folder to my desktop, I saved, quit WLW and resumed Googling...


It turns out that the format has been significantly changed between versions 3 and 4 and that the Pascal definition was added only in version 4. Luckily, at this point I also found another code-formatting plug-in that seems to be based around v 4 as it lists Pascal in the available languages! So, after downloading and trying SyntaxColor4Writer, I was again in a dead end: it's for Beta 1 and would not work on Beta 2!


Well, I ended up clicking the "Add a Plugin..." button in WLW and downloading Code Snippet plugin for Windows Live Writer so that's what I'll try next. If it doesn't work ok, I'll update this blog post asap. Another dead end... List of supported languages:


image


Uninstall this one, try the "Add a Plugin..." button again... Another quick look finds another one that is probably based around the same source as the list of languages is identical, so, back to Google again... (I really should be working instead!)


Back to the starting idea and let's see how hard it is to retrofit v 4 Pascal definition XML into v3 controls. It turns out that it's VERY easy! All you need to do for it to start working is change both 4s in the first line of the Pascal.xml file into 3s. Like this:



<SyntaxLanguage Key="Pascal" LanguageDefinitionVersion="3.0" Secure="True" xmlns="http://ActiproSoftware/SyntaxEditor/3.0/LanguageDefinition">


So, after that it's now working with Pascal code. There are a couple things that don't look that good, namely the wrong color of keywords such as "unit", "uses" and others, but it should be easy now to fix the Pascal.xml file and simply add those keywords. So, with a little help from Delphi Basics to get what the built-in help in Delphi 2007 won't give me (a list of reserved keywords), here's how some sample Pascal code looks like:


unit uHeader;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
Outlook2000, adxolFormsManager, StdCtrls;

type
TfrmHeader
= class(TadxOlForm)
lbxHeader: TListBox;
procedure adxOlFormADXSelectionChange(Sender: TObject);
private
{ Private declarations }
protected
{ Protected declarations }
public
{ Public declarations }
published
{ Published declarations }
end;

{NOTE: The adxOlForm1 variable is intended for the exclusive use
by the TadxOlFormsCollectionItem Designer.
NEVER use this variable for other purposes.
}
var
frmHeader : TfrmHeader;

implementation

{$R *.DFM}

uses MapiDefs, MapiTags, MAPIUtil;

procedure StringToList( Str: string; lstResult: TStrings );
// Convert a header string containing CR/LFs and TABs into
// a stringlist where each new property starts on a new line
var
iPos : integer;
const
CRLF
= #13#10;
begin
lstResult.Clear;
iPos :
= Pos( CRLF, Str );
while (iPos > 0) and (iPos < (Length( Str )-1) ) do begin
// If the new line starts with a Space or Tab, it's a continuation line
if (Str[iPos+2] = #9) or (Str[iPos+2] = #32) then begin
Delete( Str, iPos,
2 );
end else begin
lstResult.Add( Copy( Str,
1, Pred(iPos) ) );
Delete( Str,
1, Succ( iPos ) );
end;
iPos :
= Pos( CRLF, Str );
end;
end;

procedure TfrmHeader.adxOlFormADXSelectionChange( Sender: TObject );
var
Selection: Outlook2000.Selection;
IMail : _MailItem;
IMessage : MAPIDefs.IMessage;
PropValue: PSPropValue;
begin
lbxHeader.Items.Clear;
Selection :
= Self.ExplorerObj.Selection;
if Selection.Count > 0 then begin
try
// Try for a "normal" mail item
Selection.Item(1).QueryInterface( IID__MailItem, IMail );
if not Assigned( IMail ) then begin
// If it failed, try for a Read/Delivery Receipt item
Selection.Item(1).QueryInterface( IID__ReportItem, IMail );
end;
if not Assigned( IMail ) then begin
// I don't use Remote Mail, but for completeness this should be tested also...
Selection.Item(1).QueryInterface( IID__RemoteItem, IMail );
end;
except // Ignore exceptions
end;
if Assigned(IMail) then begin
try
if IMail.Sent then begin
IMail.MAPIOBJECT.QueryInterface(MapiDefs.IMessage, IMessage);
if Assigned(IMessage) then begin
try
PropValue :
= nil;
if HrGetOneProp(IMessage, PR_TRANSPORT_MESSAGE_HEADERS, PropValue) = S_OK then begin
StringToList( PropValue^.Value.lpszA, lbxHeader.Items );
end;
finally
MAPIFreeBuffer(PropValue);
IMessage :
= nil;
end;
end;
end;
finally
IMail :
= nil;
end;
end else begin
// If no headers found...
lbxHeader.Items.Add( 'This type of message does' );
lbxHeader.Items.Add(
'not have mail headers' );
end;
end;
end;

(* Test Comment *)
initialization
(* Another test comment
spanning 2 lines
*)
RegisterClass(TPersistentClass(TfrmHeader));
(* Another comment w/ a
{ Nested comment }
*)
finalization
{ Another test of nested comments
(* This is
another one *)
}
end.


Wrap up


Edit: Check this post for the updated files.



So, if you got kind of lost, go here to download the WLW code formatting plug-in then download this file which is my updated ActiproSoftware.Pascal.xml file and place it in the plugins\languages folder. Download this file and place it in the plugins folder overwriting the existing one (updated languages definition file for the plugin). And you're good to go!


From now on you'll be able to easily insert Pascal formatted code onto your WLW post!


If you find some syntax formatting error, let me know: I'll fix whatever needs fixing and, in a couple days, I'll send this updated file to the plugin's author so he can update his plug-in and also to the component's vendor since they're providing this for free so they too can update their component.


Have fun blogging about code... :)


Note: Somehow, either this plug-in's fault or WLW beta 2 fault, extra empty lines were added to my post, so don't be surprised if you need to delete some extra lines after publishing... :)

Also, for no good reason, even though it was displaying properly in WLW, some spaces were removed from the code, either from publishing or from my editing of this post afterwards. Just added those extra spaces and will see if they stick after re-publishing...
Update: apparently did not work! :( Still, not a big issue so I won't be spending time on it for now...


Yet another update: I'm now re-editing the post from within WLW: first time I try this!! It appears to work just fine, so I'll try to re-publish it from WLW and see if the missing spaces in the code somehow re-appear as they show nicely in WLW...


Well, it appears to be the plug-in's fault for adding the extra space: probably some style change that was not undone. As for the spaces, as long as I don't edit the post in the browser they display correctly. I'll have to get back to work, so maybe later I'll check the generated html from the code plug-in and try to find the bug. Until then, just ignore the extra spacing that makes this post look a bit ugly... :)

2007-06-15

Developing Office Add-ins the Easy Way - Part 3 of 2 (AKA: Yes, I do know how to count!)

Well, even though it's probably not the best use for my time judging from the overwhelming feedback I got on the previous 2 parts, it also doesn't take me that long to write this 3rd part of 2 so I'll do it anyway! :)

I've fixed most (all?) of the blogger screw-ups with the formatting of my post and some incorrect color coding from using Steve Trefethen's Syntax Highliting page, and now I decided to add a couple files for easier download. Here you can find the zip file with nothing but the source code and the Extended Mapi units, and here you can find a nice setup made with InnoSetup in a few minutes that will install the compiled add-in, register it, create a source subfolder with the source code in it and allow you to uninstall all this at any time should you want to. Just remember that you need to restart Outlook to have it acknowledge the newly installed add-in.

Because I know that Blogger will again mess up my formating, I'm instead placing here a prt-scr of the InnoSetup script used for creating the installer: most of it was done through the wizard...

Inno

On a different note, Part 3 of Delphi in a Radical Build will be postponed for a while: my available time has dropped considerably and whenever I pick up on that 3rd part I'll have to redo the 1st two parts as I've recently had some data loss while trying to repartition my external HDD using a re-partitioning utility that had not let me down before... So, the amount of work involved in creating that part 3 of 3 would largely surpass my free time for a lot longer. Not all is lost however as at least I can read my own articles and recreate it: that's one nice side-effect of blogging about something! :)

On another different note, is it my impression or the Windows Live Writer beta 2 (which I installed after finishing the previous 2 posts), lost the spell checker?? I could swear there was an option to spell check in the previous beta!

2007-06-14

Developing Office Add-ins the Easy Way - Part 2 of 2 (AKA: The tutorial stuff...)

If you haven't read Part 1, I'd advise you to do so. In it you'll find some more information on these components and, more importantly, your 20% discount coupon redeemable until the end of June 2007...

Step 1: Running the new Add-In Wizard

  1. Fire up Delphi and choose File->New->Other, Delphi Projects->Add-In Express VCL->ADX Outlook Add-in.
  2. Click Next and, for the project name, I'm using Insider and coInsider for the coClass Name. The first is used, guess what?, for the Project's name and the 2nd is used in Outlook's COM Addins dialog (Tools->Options->Other->Advanced Options)
  3. Clicking next will show you the "Option Pages" step which is optional.
    • For this addin we'll just hit next again. For a more complex add-in, this would be where you'd create your own option pages. You can have option pages displayed as another tab on the Options dialog or by right-clicking all or some folders in outlook, say one for Inbox, other for Outbox: they are different types of dialogs serving different purposes and you can add as many as you like.
  4. Hit next again and we'll be in the "Outlook 2007 Task Panes"; again, not of interest to us here so move along.
  5. Click Next, Finish and we're done here.

Step 1.5: More blah, blah, blah...

We now have a basic project that we'll add some meat to in order for it to do what we set out to. We could easily add an explorer or inspector command bar, but we don't need them for this project. An explorer command bar is one that is displayed on Outlook; an inspector command bar is displayed when you double-click some outlook item and "inspect" it in it's own window. Say, when you double click an e-mail message and another window pops up. You can have the inspector command bars only display for certain types of folders by simply setting a property, say, create an e-mail inspector bar that is displayed only when inspecting a mail message but not when viewing an appointment. And, of course, you can add as many as you want and add buttons, menus and other controls to them at will.

Among other cool objects you can use (not needed for this add-in), are the TADX<AppNameHere>AppEvents, such as TADXOutlookAppEvents from where you can add your own handlers. Here's the list of OutlookAppEvents (these are global events: other components have their own events as well, but these are events that don't relate to a particular button being pressed or hotkey being used):

  • OnItemSend
  • OnNewMail
  • OnNewMailEx
  • OnQuit
  • OnReminderFire
  • OnStartup

BTW: Thanks to blogger messing up my formatting big-time, I had to shorten the list to just a few sample items so as not to have a whole lot of space wasted. Maybe I'll take a prt-scr later and update this...

Step 2: Adding some components and code

  1. What we are now going to add is an "ADX Outlook Form" which is a small pane where we will be displaying the headers of the selected message, so go to File->New->Other, Delphi Projects->Add-In Express VCL and double click "ADX Outlook Form".
  2. Save the form (I used uHeader for the unit name).
  3. Add a TListBox, call it lbxHeader and set it to fill the form.
  4. We need another component to control creation of these panes. Go on to the main project form (should be empty) and add a "TadxOlFormsManager".
  5. In this component's events, add the following code to the "OnADXBeforeFormInstanceCreateEx" event:
    1 if args.FolderObj.EntryID = OutboxEntryID then begin
    2 args.Cancel := true;
    3 end;
  6. Let's add that OutboxEntryID as a protected property to the form (TAddInModule) of type string:
    1 property OutboxEntryID: string read FOutboxEntryID;
  7. And the corresponding internal field to the private section of TAddInModule:
    1 FOutboxEntryID: string;
  8. And a method to set the value in the protected section (not a setter method though!):
       1 procedure SetOutboxEntryID;
      2
      3 procedure TAddInModule.SetOutboxEntryID;
      4 var
      5 nameSpace : Outlook2000.NameSpace;
      6 outboxFolder: Outlook2000.MAPIFolder;
      7 begin
      8 try
      9 nameSpace := OutlookApp .GetNamespace ( 'MAPI' );
      10 outboxFolder := nameSpace .GetDefaultFolder( olFolderOutbox );
      11 FOutboxEntryID := outboxFolder.EntryID;
      12 finally
      13 nameSpace := nil;
      14 outboxFolder := nil;
      15 end;
      16 end;
    • Now click the form and enter the following code to it's "OnAddInInitialize" event:
      1 SetOutboxEntryID();
    • Finally, let's give the TadxOlFormsManager something to manage: double click it and add a new item. Set the following properties for that item:
      1. ExplorerItemTypes->expMailItem, set to True as we want this form displayed on mail folders only; leave all others to false.
      2. ExplorerLayout set to elTopSubpane; (you can play around with it later if you want!)
      3. And finally, let's link it to the form we created above: set FormClassName to our form created above. It will be available on the drop down as soon as you go into File->Use Unit and select our uHeader unit.

    If you build the project, register it and launch outlook, you'll see a blank new pane displayed: Sorry for the large, dimmed and blurred image: it's from my main outlook... :)

    Also, if you notice, so far most of the code was related to adding an exception, that is, we wanted our form to display on mail folders, unless that folder is Outbox.

    Now it's time to add our real meat into this.

    Step 3: Adding our application logic

    1. Lets go back to our uHeader unit and add the following code to the onADXSelectionChange event:
       1 procedure TfrmHeader.adxOlFormADXSelectionChange( Sender: TObject );
      2 var
      3 Selection: Outlook2000.Selection;
      4 IMail : _MailItem;
      5 IMessage : MAPIDefs.IMessage;
      6 PropValue: PSPropValue;
      7 begin
      8 lbxHeader.Items.Clear;
      9 Selection := Self.ExplorerObj.Selection;
      10 IMail := nil;
      11 if Selection.Count > 0 then begin
      12 try // Try for a "normal" mail item
      13 Selection.Item(1).QueryInterface( IID__MailItem, IMail );
      14 if not Assigned( IMail ) then begin
      15 // If it failed, try for a Read/Delivery Receipt item
      16 Selection.Item(1).QueryInterface( IID__ReportItem, IMail );
      17 end;
      18 if not Assigned( IMail ) then begin
      19 // I don't use Remote Mail, but for completeness this should be tested also...
      20 Selection.Item(1).QueryInterface( IID__RemoteItem, IMail );
      21 end;
      22 except// Ignore exceptions
      23 // QueryInterface may throw up an exception, but I don't recall under what condition it was
      24 // It was for some non->relevant item, i.e., one that we're not processing here
      25 // That's the bad thing of writing something and not documenting it properly when it happens!
      26 end;
      27 if Assigned(IMail) then begin
      28 try
      29 if IMail.Sent then begin
      30 IMail.MAPIOBJECT.QueryInterface(MapiDefs.IMessage, IMessage);
      31 if Assigned(IMessage) then begin
      32 try
      33 PropValue := nil;
      34 if HrGetOneProp(IMessage, PR_TRANSPORT_MESSAGE_HEADERS, PropValue) = S_OK then begin
      35 StringToList( PropValue^.Value.lpszA, lbxHeader.Items );
      36 end;
      37 finally
      38 MAPIFreeBuffer(PropValue);
      39 IMessage := nil;
      40 end;
      41 end;
      42 end;
      43 finally
      44 IMail := nil;
      45 end;
      46 end else begin // If no headers found...
      47 lbxHeader.Items.Add( 'This type of message does' );
      48 lbxHeader.Items.Add( 'not have mail headers' );
      49 end;
      50 end;
      51 end;
    2. Now let's add the MAPI units to our project and to the uses clause in this form:
      1 uses MapiDefs, MapiTags, MAPIUtil;
    3. Now all that is left is to add that StringToList function I used to process the semi-colon formatted mail headers into a more readable list:
       1 procedure StringToList( Str: string; lstResult: TStrings );
      2 // Convert a header string containing CR/LFs and TABs into
      3 // a stringlist where each new property starts on a new line
      4 var
      5 iPos : integer;
      6 const
      7 CRLF = #13#10;
      8 begin
      9 lstResult.Clear;
      10 iPos := Pos( CRLF, Str );
      11 while (iPos > 0) and (iPos < (Length( Str )-1) ) do begin
      12 // If the new line starts with a Space or Tab, it's a continuation line
      13 if (Str[iPos+2] = #9) or (Str[iPos+2] = #32) then begin
      14 Delete( Str, iPos, 2 );
      15 end else begin
      16 lstResult.Add( Copy( Str, 1, Pred(iPos) ) );
      17 Delete( Str, 1, Succ( iPos ) );
      18 end;
      19 iPos := Pos( CRLF, Str );
      20 end;
      21 end;

    And we're done!

    Just build again, and re-launch outlook and you'll have something like this:

    Final thoughts and notes:

    1. Don't forget that you can regsvr32 dlllname.dll or regsvr32 -u dllname.dll and any installer will do that for you if you mark the dll as self-registering.
    2. Don't forget that you'll need to close the application you're testing/compiling your add-in for, before you can compile it as the dll will be in use! :)
    3. If you look back, you'll see that you pratically never knew you were handling COM objects and that most of the code was for the application logic with part of the complexity in that arising from using Extended Mapi because Outlook's object model is a bit limited in some places... Creating Excel add-ins is even easier as you don't have to deal with Extended Mapi! ;) You will require some knowledge of the application's Object Model, but that comes out of the box in the form of some VBA help files that you can install with your Office.
    4. I didn't show in this simple demo two other easy to implement features, mostly out of lazyness: adding toolbars/buttons/menus and option pages. Trust me (or don't trust and instead try for yourself!): those are very easy to implement!
    5. Don't forget to take advantage of the 20% discount coupon (see Part 1), and have fun Developing Office Add-ins the Easy Way!
    6. Syntax highlighting done using Steve Trefethen's Syntax Highlighter and then manually tweaked the generated HTML to fix some coloring bugs.