Wednesday, August 26, 2009

Dynamically change color of Node icon

While working and extending my Palette usage I had the challenge of having 6 categories and each of those an undefined number of items. Each of the items in each category had the same icon.

Now, using 6 different 16x16 and 32x32 pixel icons I suddenly got confronted with the requirement of each of those icons should be able to represent in the standard 16 web colors.

Well, it was out of the question to add 16*(6+6) = 192 different png based icons for those different options.

Using the example of created a Palette described in the "Rich Client Programming" book I had the MyItemData object which had source code like



private Image icon16;
private Image icon32;
public static final String PROP_ID = "id";
public static final String PROP_NAME = "displayName";
public static final String PROP_COMMENT = "comment";
public static final String PROP_ICON16 = "icon16";
public static final String PROP_ICON32 = "icon32";

// ...

public Image getSmallImage() {
return icon16;
}

// ...

private void loadIcons() {
String iconId = props.getProperty(PROP_ICON16);
icon16 = ImageUtilities.loadImage(iconId);
iconId = props.getProperty(PROP_ICON32);
icon32 = ImageUtilities.loadImage(iconId);
}



The idea was now to use the BufferedImage methods getRGB and setRGB to replace the color of the initial 12 png icons (which were drawn in BLACK) dynamically with a color the user has defined as one additional property.

The code to change this depends of what initial color model used in the input PNG. In my case I created the initial icons with Gimp and it was using TYPE_4BYTE_ABGR. Have that in mind when you create the new RGB value to be set for a given pixel.

The new code looks very similar and just a few changes are needed to get the desired result


private BufferedImage icon16;
private BufferedImage icon32;
public static final String PROP_ID = "id";
public static final String PROP_NAME = "displayName";
public static final String PROP_COMMENT = "comment";
public static final String PROP_COLOR = "color";
public static final String PROP_ICON16 = "icon16";
public static final String PROP_ICON32 = "icon32";

// ...

public Image getSmallImage() {
int width = icon16.getWidth();
int height = icon16.getHeight();

for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
int rgb = icon16.getRGB(x, y);
if (icon16.getRGB(x, y) < 0) {
rgb = some_method_returning_the_RGB_for_a_selected_color;
icon16.setRGB(x, y, rgb);
}
}
}
return icon16;
}

// ...

private void loadIcons() {
String iconId = props.getProperty(PROP_ICON16);
icon16 = (BufferedImage)ImageUtilities.loadImage(iconId);
iconId = props.getProperty(PROP_ICON32);
icon32 = (BufferedImage)ImageUtilities.loadImage(iconId);
}




With respect to the method giving the new RGB value for a given pixel based on one of the Web Colors I simply get the alpha, red, green and blue components (via bit shifting, see for example here for a discussion) and then set the value for Red, Green and Blue depending on the Web color selected in the property. After that set the new RGB value equal to

rgb = (alpha << 24) | (red << 16) | (green << 8) | blue;

Now I can show the icons for the Palette Items in any color the user may select and only need to store one black version of them in the system

Friday, August 21, 2009

NB 6.5 32-bit launcher and 64-bit JVM on Windows XP

After having my RCP app running for a while on Windows XP 32 bit I had to deploy it on a 64 bit XP Windows using a 64 bit Java JVM.

Well, the Windows XP 64-bit machine had both the 32 bit and 64 bit Java 1.6.0_14 JRE installed, but just double clicking on my launcher always picked up the 32 bit JRE.

Now, adding the --jdkhome "C:\Program Files\Java\jre6" (Remember: there are two (2) dashes) to the 32 bit launcher executable, my RCP app launched happy in the 64 bit JVM. Using Help -> About gives this:



So a primitive solution is to add your own little ".bat" file where you add this option to the launcher.

I have not yet tried this on Nb 6.7 where some major rework was done on the launcher code.

Friday, August 14, 2009

Palette, ItemNode <-> DataNode

Following the (always) excellent blogs from Geertjan, I decided to leverage the Palette for some of my selections. See Palette Filter, Listen to Palette, Palette API and 6.0 Part 1, Part 2 and Part 3.

Now, I decided to have my palette instantiate its Items via a Node tree, so that those also could be represented at the same time via the Explorer Tree view. I created Nodes via the FileSystem as described in the "Rich Client Programming" book but initialize the PaletteController with the root Node instead of the root of the FileSytem representing the tree.

All happy. Now, of course I would like the user to be able to Edit the different Palette Items and followed Extend the Palette blog creating MyPalette and a CustomItermAction alla

public Action[] getCustomItemActions(Lookup lookup) {
Node itemNode = (Node)lookup.lookup( Node.class );
if( null != itemNode ) {
return new Action[] { new CustomizeItemAction( itemNode ) };
}
return null;
}

So far so good. The problem started when I tried to get hand on the underlying DataNode representing this Palette Item. In the implementation of my CustomizeItemAction I tried (naively) to cast the itemNode to a DataNode with the very unforgiving error

ClassCastException: Cannot cast org.netbeans.modules.palette.ItemNode to ... DataNode.

What the heck ? Could not even find any JavaDoc for org.netbeans.modules.palette.ItemNode. Well Google is your friend and after locating the source of ItemNode, I found that ItemNode is a FilterNode and hence of course I could not get hand on my DataNode, and the getOriginalNode() method in ItemNode was package protected - hmpf.

Solution: Lookup !

Lookup was very friendly. After adding the code

Collection c = item.lookupAll(Object.class); Iterator i = c.iterator(); while(i.hasNext()) { Object o = i.next(); System.out.println("o "+o); }

to check what is in the Lookup at the time of selection, I could see that the Lookup send to the Action did contain the instance of my DataObject represented by this Node. (Thank you !) Still, - trying to change the code from


public Action[] getCustomItemActions(Lookup lookup) {
Node itemNode = (Node)lookup.lookup( Node.class );
if( null != itemNode ) {
return new Action[] { new CustomizeItemAction( itemNode ) };
}
return null;
}


To

public Action[] getCustomItemActions(Lookup lookup) {
MyDataObject myDataObject = (MyDataObject)lookup.lookup( MyDataObject );
if( null != myDataObject ) {
return new Action[] { new CustomizeItemAction( myDataObject ) };
}
return null;
}

Did give me an annoying error in the Java Editor
inconvertable types
found: org.openide.util.Lookup.lookup
required: com.demo.MyDataObject

?!?! All looked normal - just replaced "Node" with "MyDataObject" ! After some strong coffee - the trivial solution was that my module having the action defined did not include the Datasystems API - and the reason it is triggering it is because the MyDataObject extends MultiDataObject living in the Datasystems API

So - finally, after some confusing hours I have a ItemAction which has hand on the DataObject representing the PaletteItem and can thereby get hand on the underlying FileObject and do whatever is needed.

... by the way .. it turned out to be a good idea to send down the PaletteController together with the Action to allow the code in the Action implementation to perform a refresh() on the Palette.