Beauty with less Beast

We've always prided ourselves that Dolphin is one of the "better looking" Smalltalks out there. Indeed, when we first started, I think Dolphin was the first Smalltalk IDE to use icons to represent classes in the various browsers. As a result, it's been pretty easy to use icons to represent objects and toolbar commands, both as part of the IDE and in end-user applications. But, and here's the crunch, it has always been necessary to load these icons from external files.

This can be a little inconvenient at times:

  • External files are just more things that have to be distributed and installed with your application. In fact, since virtually every application will want to use at least one icon - to represent the shell window when minimized - it hasn't really been feasible to deploy true one-click executables before. Instead, we find that most Dolphin apps require the use of some form of installer program.
  • External files can't currently be placing into the built-in Smalltalk Tracking System (STS) version control. This means that bitmap and icon resources must be stored in a separate repository. Whilst not a complete solution for all cases where external files are needed, it would be nice if such image resources could actually be held as Smalltalk methods so they could be successfully versioned.
  • Icons, with their multi-resolution formats can be tricky to create. At the very least you'll need another application - an icon editor - to manipulate them. Surely, it would be better if we could make use of an alpha-transparency capable image format, such as PNG, for both bitmaps and icons and then we'd only need a single image editing application for both?

To get around these issues, I've put together a goodies package called "Internal Bitmaps and Icons". You can download this here. What this package does is to introduce two new classes, InternalIcon and InternalBitmap, which can be used to hold the pixels of a PNG image internally within the Dolphin object memory.

These classes make use of GDI+ to load in external picture files and hold them as GdiplusBitmap byte array initializers within the image. Hence the appropriate bitmap can be created at anytime without further reference to the external file. Typically, the external file will be a PNG, which can contain transparency information and is therefore capable of replacing most of the functionality of standard Windows ICO files. Other files, such as BMP and JPG, may also be used if alpha transparency is not required.

Let's see how this might work in practice. There are two things we often want to use icons for: the first is to represent classes or objects pictorially in an application; the second is to represent commands as button images in the application's toolbar.

Using InternalIcons in class #icon methods.

Looking around the standard Dolphin system I can see a number of areas where the included icons are looking a bit tired. So, as an example of how to use InternalIcon, lets try and replace the "Registry Editor" sample application icon with something a bit more swish.

The current icon is actually referenced directly out of the standard Windows RegEdit.exe executable (as are some of the other icons that the sample makes use of). Take a look at RegEdit class>>icon. We are going to modify this method so that all the pixels of our new icon are held within it.

The first thing we need is a suitable icon image. Since the original looks a bit "Rubik's Cubey" I used Google Images to hunt down a suitable PNG file to replace it. Obviously, if this was to be a commercial solution, one would be more wary of copyright, however, for the purpose of this example I took the (rather large) PNG of a Rubik's Cube from this Wikipedia page:

The image you choose can be any size but, to be used as an icon, it should be square. InternalIcon will, by default, resize the picture to 48x48 pixels so an oblong image will become distorted if you decide to use one. The package introduces some helper methods to ClassDescription, so open a workspace and evaluate:

RegEdit createIconMethod.

This will prompt you for the location of your PNG file and will then load it into an InternalIcon instance and from this it will create a RegEdit class>>icon method. If you now cause the System Folder to refresh (by changing folder and going back again) you'll see the new icon has taken the place of the old one:

Taking a look at the RegEdit class>>icon method we see that it has been compiled to answer an instance of InternalIcon built from a literal byte array:

That literal array contains all of the pixel information for the reduced size image in PNG format. If you ever update the external PNG file in future, you can bring it into your image again and rebuild #icon method by just evaluating the Smalltalk code in the method comment. Easy-peasey.

Now, whilst the above method source might appear quite long, remember this is not, itself, kept within the image but in the changes (CHG) or source (SML) files. What will be kept in the image is the compiled version of that ByteArray, which will be held in the literal frame of the #icon method. In this case the byte array is 4379 bytes which would compare favourably with the size of a 48x48 PNG image file. Typically, though, this will be a lot smaller than the equivalent Windows ICO file which might have several different resolution renditions of the same uncompressed bitmaps within it. Newer Vista-style icons which hold PNG images anyway are more efficient and might come in at about the same size as our byte array.

So we have added an additional 4379 bytes to our application's runtime footprint but we have got rid of an external file reference, allowed the icon to be versioned with STS and reduced its general download size by being able to omit any external icon files. This seems to me like a reasonable compromise and an overall Good Thing. You would, I suspect, not want to use this technique to load a large number of huge bitmaps into your Dolphin image, however.

Note: If you open the RegEdit application you will notice that the new icon does not show up correctly as the icon for the shell window. This is because the shell view's icon has been explicitly set to be something else in the View Composer. To fix this, edit the RegEdit view, and set the #largeIcon and #smallIcon aspects to nil. Now the shell icon will be taken from that of the RegEdit class.

Using InternalIcons in toolbar buttons

Another great use for InternalIcons is in toolbar buttons. Basically, the method of assigning an icon to a button stays the same but, rather than instantiating a plain old Icon instance, we create an InternalIcon instead. Here are the steps:

  • In the View Composer select the toolbar's #items aspect, click on the New Item button and choose ToolbarIconButton new as normal.
  • Drill down into the #commandDescription of this button and find the #image aspect. You will see that this is predefined to hold an instance of Icon using the standard Object.ico resource identifier.
  • Normally, to choose an external icon file you can double-click the #image aspect and you will be prompted for an ICO filename. However, until the "Internal Bitmaps and Icons" package is integrated into the base Dolphin system this won't work for creating an InternalIcon. Instead you need to go to the Smalltalk workspace pane and type: InternalIcon chooseFile. Accept this (with Ctrl+S) and you will be prompted for the name of your PNG file to use.

After you've done this, here's how the new InternalIcon will appear in the View Composer:

One disadvantage of using this approach (InternalIcon chooseFile) is that a new instance of the InternalIcon will be created each time. Hence, if you do this in several places you will have duplicate copies of the icon and all its pixel bytes being held in each instance, which is rather wasteful of runtime memory. In such cases a better approach might be to create a class side method (Myclass class>>#myIcon, say) on one of your application classes that answers this InternalIcon in just the same way that a standard #icon method might. Then you can just evaluate, MyClass myIcon each time you want to have shared access to the icon data.

I hope this is clear. If you're in any doubt that such an approach really does share the image bytes for each invocation of the method then try the following in a workspace (after having replaced RegEdit class>>icon as explained in the first part of this article).

icon1 := RegEdit icon.icon2 := RegEdit icon.icon1 bitmap initializer bytes==icon2 bitmap initializer bytes. "Display It - should get true"

I hope this has been an interesting diversion for at least a few Dolphin developers. Let me know via the newsgroup if you have any problems with these internal icons and bitmaps. If not, then I hope to roll this goodie into the base Dolphin image at some point in the near future.

Oh, and here's the link to the package file again: Internal Bitmaps and Icons.pac

blog comments powered by Disqus