It seems like there's no shortage of unusual behaviours to be found in SWT. The most recent one that I've come across is the behaviour associated with the SWT.FILL style. Consider the layout found in the GlazedLists demo. Laying out the interface in a single GridLayout puts two Tables above one another. Those Tables should both be styled so that they grab excess vertical space and have the SWT.FILL style applied. This is a requirement so that they are rendered correctly when the window is resized.
When the demo is initially created, it renders correctly. The tables are sized fairly evenly. However, as soon as the window is resized, the lower Table implodes never to return. It's easy enough to expect this behaviour. If two or more widgets are contending for free space in the layout, the only two outcomes are either the first one wins and takes all or they should all share space evenly. The real problem is not how the widgets contend for space. The real problem is that that contention is different on initialization than it is on future resizings.
Just another bullet in the list of things that make SWT trying to use.
My latest code depends on ServiceMBeanSupport.class from JBoss. The problem is that JBoss is composed of dozens of .jar files and I'm not sure which one contains the file I'm interested in.
Tools
find this command will list all the files that meet the specified metadata. It can also execute a command for each file found, using that filename as a parameter. For example, to remove all the CVS folders in a directory tree, type find . -name CVS -exec rm -rf {} \; . Find will substitute {} for the file that's found and it needs \; to designate the end of the command.
grep give input text, grep prints only the lines that match my regular expression.
bash -c executes the bash shell. This can be used with find -exec to run multiple commands in sequence
jar tvf print all the files in a .jar file
Find my file in a jar
jar tvf jarfile | grep ServiceMBeanSupport.class
This will tell me if the file is in the jar file or not. Its a start.
Find my file in all the .jar files in the current directory or deeper
This will tell me if the file is in any of the jarfiles. Unfortunately it doesn't tell me which jarfile it is in, since it just prints the contents of the jarfiles and not their names.
PLAF Detection Workaround for Ocean Metal and Windows XP
Currently, Swing has a couple of bugs which hinder PLAF detection on the client. In particular, Windows and Metal suffer from ambiguity in their UIManagers. In GlazedLists it's important that we inspect the PLAF precisely so that the pixel-perfect sort arrow icons that Jesse made are chosen correctly. Jesse wrote a couple of methods that workaround these bugs a while ago. In some recent refactorings, I extracted these into a better home and had a chance to look them over. I think them worthy of mention to help others who may have related problems.
Metal
There are now two types of Metal, Ocean and Steel. Unfortunately, the result of calling UIManager.getLookAndFeel().getName() is "Metal" for both. However, Ocean only exists on Java 5 so here's the workaround exploiting the new getCurrentTheme() method added in Java 5:
public static String getMetalTheme() {
try {
MetalLookAndFeel metalLNF = (MetalLookAndFeel)UIManager.getLookAndFeel();
Method getCurrentTheme = metalLNF.getClass().getMethod("getCurrentTheme", new Class[0]);
MetalTheme currentTheme = (MetalTheme)getCurrentTheme.invoke(metalLNF, new Object[0]);
return "Metal/" + currentTheme.getName();
} catch(NoSuchMethodException e) {
// must be Java 1.4 because getCurrentTheme() method does not exist
// therefore the theme of interest is "Steel"
return "Metal/Steel";
} catch(Exception e) {
e.printStackTrace();
return "Metal/Steel";
}
}
Windows
There are two types of Windows themes as well called XP and Classic. Just as with Metal, the result of calling UIManager.getLookAndFeel().getName() is "Windows" for both. Here's a workaround derived from code in the XPStyle class:
public static String getWindowsTheme() {
String classic = "Classic Windows";
String xp = "Windows XP";
// theme active property must be "Boolean.TRUE";
String themeActiveKey = "win.xpstyle.themeActive";
Boolean themeActive = (Boolean)java.awt.Toolkit.getDefaultToolkit().getDesktopProperty(themeActiveKey);
if(themeActive == null) return classic;
if(!themeActive.booleanValue()) return classic;
// no "swing.noxp" system property
String noXPProperty = "swing.noxp";
if(System.getProperty(noXPProperty) != null) return classic;
// l&f class must not be "WindowsClassicLookAndFeel"
String classicLnF = "com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel";
if(UIManager.getLookAndFeel().getClass().getName().equals(classicLnF)) return "Classic Windows";
I'll try to keep my rants on SWT to a minimum, but I feel the need to provide information to others who run head first into the brick wall that is SWT. I remember reading a comparison of Swing and SWT some time ago and it clicked into my mind today. Despite Google's best efforts, I couldn't find the article but I'll give you the conclusion:
SWT is great and fast as long as you want to do small, simple things, one time.
Bearing that in mind, parts of the framework make a little more sense. But then again, lots of things make sense when you don't plan to ever make anything complex, scalable or maintainable. Large projects need Swing, or at least, they need something that feels Object-Oriented; Something that's built with best-practices and patterns like MVC in mind.
Today's SWT Strangeness: A Table widget only fires events to a registered MouseListener iff the MouseEvent occurs inside the table.
Doesn't seem strange at first save for the following problem: The table headers are not inside the table.
In order to gain information on table headers being clicked you need to use a SelectionListener(groan). SelectionEvents are only fired by mouse clicking with the left mouse button. But can't you add a MouseListener to the TableColumn and access the header? No, you can't. The only Table-related widget that supports MouseListeners is Table. I even tried using the base Listener (which you can add to anything), and seeing if perhaps handleEvent() might shed some light on things. Wrapping the event in a MouseEvent I dumped the details of the MouseEvent to my console. At first it was exciting. I had beaten SWT. But no, just like that SelectionEvent, those events only show up with you left-click. In the event that you wanted to provide a complex interface that behaves different when you right click or ctrl-click on a table header, come running back to Swing before your boss has his heart set on SWT.
So I found myself writing some code that seemed like a GoF design pattern. I'm not so sure that it is, but if it were, here's its criteria...
Name: Private Interfaces
Intent: Implement methods in a class without exposing them in the public API.
Motivation: Hiding methods prevents them from being called by the API's user. Method hiding also streamlines the API of a class. For example, a class may need to receive mouse events without exposing methods like mouseClicked() in its external API.
Applicability: When the class must implement an API without exposing it.
Consequences: Essentially, Interface methods become 'private'. This prevents extending classes from overriding them and users from calling them. The interface may still be exposed where it is needed.
Implementation: Add an inner class that implements all the private interfaces. Create a private member of this inner class, and use pass as the parameter wherever the private interfaces are needed.
Sample Code:
/** Before. This class exposes 6 methods */
public class ClickCounter implements MouseListener {
private int clicks = 0;
private int presses = 0;
private int releases = 0;
public MyClass(Component c) {
c.addMouseListener(this);
}
public void mouseClicked(MouseEvent e) { clicks++; }
public void mousePressed(MouseEvent e) { presses++; }
public void mouseReleased(MouseEvent e) { released++; }
public int getClickCount() { return clicks; }
public int getPressCount() { return presses; }
public int getReleaseCount() { return releases; }
}
/** After. This class exposes only 3 methods */
public class ClickCounter {
private int clicks = 0;
private int presses = 0;
private int releases = 0;
private PrivateInterfaces privateInterfaces = new PrivateInterfaces();
public MyClass(Component c) {
c.addMouseListener(privateInterfaces);
}
private class PrivateInterfaces implements MouseListener {
public void mouseClicked(MouseEvent e) { clicks++; }
public void mousePressed(MouseEvent e) { presses++; }
public void mouseReleased(MouseEvent e) { released++; }
}
public int getClickCount() { return clicks; }
public int getPressCount() { return presses; }
public int getReleaseCount() { return releases; }
}
It wouldn't be too big a deal but I want my application to fail if it can't bind! One horrible problem that has come up in my tests is this:
1. Bind to port X as server.
2. Connect to self on port X as client.
Now the client has fully connected but server has not received anything!
Yuck. And Google doesn't tell me anything. Yuck yuck.
Some reasons why SWT frustrates me in no particular order:
A column title in a table header has the same justification as the contents of the column.
Common style constants have common (and therefore ridiculously vague) descriptions in the documentation. Next to no effort was put in to provide more detailed descriptions in classes which will commonly use those styles. In fact, most if not all of the widgets even specify a list of best fit styles for that widget but don't elaborate on what, if anything, something like SHADOW_IN might do when applied.
Common style constants period. If I enjoyed writing painful to maintain code, my language of choice would not be Java. It just makes so much more sense to have a Style object that you can set properties on. Would that really be any different that the GridData class? Internally, it could pretend to be all 1337 and do everything with bitmasks like it was written in C, but I shouldn't have to see fifty million constants. It's no wonder Sun refuses to touch SWT with a ten foot pole. But I suppose I shouldn't complain, they could replace GridData with 25 more common style constants.
Virtually every class has the following warning (or a slightly different one with the same meaning): 'IMPORTANT: This class is not intended to be subclassed'. If that's the case, Java has a keyword for it. It's called final.
Out of the box, my apps don't look right on Windows. Seriously...I have to manually set the margins on the Layout for it to look native. Why isn't native the default? The default width on a Shell with a GridLayout is at least twice the size of the native window border. Funny though, the window border with the defaults looks identical to that of a Swing window. And here I thought SWT was supposed to be 'better'.
EVERYTHING is a SelectionEvent. Clicking on a Button, okay. I can shift my mindset to see how that is a selection. You are 'selecting' that Button when you click it. But will someone please tell me, honestly, if I press ENTER in a Text field, how is it that I just defaultSelected it? In fact, I would have already had to select it a while a go to type ENTER into it in the first place. And defaultSelected? To me that sounds like a widget has default focus, not the user did something that can't possibly be considered default by any definition of the word. I'm very curious why SWT felt ActionEvents were the devil, as they make so much more sense.
The GridData class has no methods, just public fields and constructors. And yet they aren't supposed to be reused? Weird that their siblings RowData and FormData don't have this problem. Oh, and as Jesse mentioned, they also have some convenience style constants that don't work in obvious cases.
Sorry, I just felt the need to rant. It seems that working with SWT has that affect on people.
Today I ran into an unexpected problem while dealing with InputStreams and OutputStreams...
Pop-Quiz How many times is "World O Hell" printed when the following code is executed?
for(byte i = -128; i <= 127; i++) {
System.out.println("World O Hell");
}
< 256 times
255 times
256 times
> 256 times
Pop-Answer If you answered > 256 times you're correct. This is because in byte math, 128 + 1 = -127. It reminds me of the good old days of modular arithmetic in Math 137.
Sometimes I write broken code. Sometimes my test cases never complete. For this, there is a simple solution. Type CTRL+BACKSLASH into the Java process. The result is a full stack trace of all my JVM's threads.
My current project at work uses JavaServer Faces extensively. Although the current implementation feels like a version 1.0, it's quie a kick-ass framework.
Today I found out that a small component called "NewspaperTable" that I have built has made its way into the MyFaces open source JSF implementation. I even get my name in the credits!
I think that Glazed Lists has to be open source for the project to be successful. Currently the project progresses because of the charity of myself, Kevin Maltby, James Lemieux, and O'Dell Engineering Ltd. Although these contributions are voluntary, I think that we need a profit strategy for the project. The project doesn't need to make anybody rich but it should pay for itself.
Meanwhile, I'm planning on revising the project tutorial for the upcoming 1.0 release. I've decided to switch to Docbook, which is the ideal hacker's publishing tool. Docbook exports 'real documents' like paper and PDF and people have been known to pay for these things.
Should I write a Glazed Lists book and sell it, then use that money to further finance Glazed Lists?
I think the book is a very clean profit strategy:
Everyone can convince their boss to drop $40 on a book
Still open source in its purest form
Encourages further documentation on the project
But it has its downsides:
I'd have to write a book
Would enough people buy it to justify its expense (in terms of my time)
I think I'll wait for the big 1 dot owe, and then give this some further thought.