Background
Being new to the SWT I wanted to learn a little about the API by making a simple change to the Commerce Manager UI. I put this post together with the hope that it might have some useful tidbits for other SWT newbies out there.
Goal
The CM client displays a product icon in the catalog's Product Listing view. The icon may vary depending on whether the product is a bundle or has multiple SKUs, but otherwise it is the same icon for every product. To make things more interesting I decided to customise the view to generate an icon specific to each individual product. In the real world, the image location would probably come from a product attribute. To keep things simple however we will simply generate a small square image with a random background colour.
Solution
To achieve our goal we will create a new class ProductIconImageRegistry. This class will be responsible for creating SWT product image icons that can be obtained via the following get(Product) method.
/**
* Returns an image for the given product.
*/
public Image get(final Product product) {
final Long key = product.getUidPk();
Image image = imageCache.get(key);
if (image == null) {
if (isCacheFull()) {
freeCache();
}
image = getProductIcon(product);
addToCache(key, image);
}
return image;
}
Because every new SWT image instance requires an allocation of OS resources, we will cache the icons in a hash map. To keep the cache from growing indefinitely we will define an upper limit and free up some cache when it reaches the maximum size.
Two important rules to bear in mind (see References) when working with SWT components are:
"If you created it, you dispose it."
"Disposing the parent disposes the children"
The rules apply to a number of SWT classes including Image, Color, Font, Widget, GC, and so forth. If you invoke a constructor to instantiate one of these classes, you must free them using the dispose() method.
Color color = new Color(...); // allocates platform resources
color.dispose();
However if you acquire an instance without calling the constructor there is no need to dispose().
Color color = display.getSystemColor(SWT.COLOR_BLUE);
In the case of the ProductIconImageRegistry class, we are creating a new icon image as follows:
/**
* Returns an icon for the given product.
*/
private Image getProductIcon(final Product product) {
final Display display = Display.getCurrent();
final Color color = createRandomColor(display);
final Image iconImage = createIconImage(display, color);
return iconImage;
}
/**
* Creates a random Color.
*/
private Color createRandomColor(final Display display) {
final int red = random.nextInt(256);
final int green = random.nextInt(256);
final int blue = random.nextInt(256);
return new Color(display, red, green, blue);
}
/**
* Creates an Image with the specified background Color.
*/
private Image createIconImage(final Display display, final Color color) {
final Image image = new Image(display, ICON_LENGTH, ICON_LENGTH);
final GC gc = new GC(image);
gc.setBackground(color);
gc.fillRectangle(0, 0, ICON_LENGTH, ICON_LENGTH);
drawIconBorder(gc, display.getSystemColor(SWT.COLOR_GRAY));
gc.dispose();
return image;
}
/**
* Draws a border around the icon.
*/
private void drawIconBorder(final GC gc, final Color borderColor) {
final int border = ICON_LENGTH - 1;
gc.setForeground(borderColor);
gc.drawLine(0, 0, 0, border);
gc.drawLine(0, 0, border, 0);
gc.drawLine(0, border, border, border);
gc.drawLine(border, 0, border, border);
}
Our freeCache() method will simply purge a tenth of its contents and call dispose() on every removed image.
private void freeCache() {
int removeQty = CACHE_SIZE / 10;
for (int i = 0; i < removeQty; i++) {
final Long removedKey = cacheKeys.removeFirst();
final Image removedImage = imageCache.remove(removedKey);
removedImage.dispose();
}
}
We will also provide a disposeAllImages() method on the ProductIconImageRegistry class to allow the client code purge everything on shutdown.
public void disposeAllImages() {
for (Long key : imageCache.keySet()) {
final Image image = imageCache.get(key);
image.dispose();
}
imageCache.clear();
cacheKeys.clear();
}
Finally, we need to plug in our new class into the existing CatalogImageRegistry and CoreImageRegistry classes. This can be done by updating the getImageForProduct(Product) method:
public static Image getImageForProduct(final Product product) {
if (product instanceof ProductBundle) {
return getImage(PRODUCT_BUNDLE);
}
if (product.hasMultipleSkus()) {
return getImage(PRODUCT_MULTI_SKU);
}
return getImage(PRODUCT);
}
and replacing the last line with
return productIconImageRegistry.get(product);
We also need to hook in the disposeAllImages() method:
static void disposeAllImages() {
for (final ImageDescriptor desc : IMAGES_MAP.keySet()) {
final Image image = IMAGES_MAP.get(desc);
if (!image.isDisposed()) {
image.dispose();
}
}
productIconImageRegistry.disposeAllImages();
}
With the all pieces together, the final result looks as shown in the screen shot. The icons in the Product Listing view are provided by the CatalogImageRegistry class.
The CoreImageRegistry class provides icons for the Select a Product dialog, which looks as follows with the changes in place:
References
http://www.eclipse.org/articles/Article-SWT-images/graphics-resources.html
http://www.eclipse.org/articles/swt-design-2/swt-design-2.html
http://eclipse.org/articles/Article-SWT-graphics/SWT_graphics.html










