I have been working on PGnJ 0.8 since the day I released 0.7. With this release I really wanted to concentrate on further refining the application rather than adding new SQL related features.
First, I added an about box and a preference system.
After that, I decided to tackle automatic software updates. Unfortunately, since PGnJ is written in Java, I didn't get to use that slick Sparkle Framework that all of the Cocoa developers use. I had to do my own thing.
So, being that I try and make PGnJ look and feel like a real OS X application, I decided to model my software update system after the Sparkle Framework. In addition to just looking good and working well, I figured that people are used to the software update process that Sparkle provides so I went ahead and developed a pretty neat software update framework for PGnJ. Take a look at the third thumbnail to see it in action.
Another thing I thought I could do to really spruce things up was something that I hope most people don't even notice. I really wanted to add some row selection intelligence to the database object tree on the left side of the application.
Essentially, the problem is that in a JTree, the actual jtree cell renderer contains only the icon and the text, not the entire row. This means that when you want to impose some right click logic on the tree that you also have to manually tell it what row to highlight on that right click. If the third row is currently highlighted and the user right clicks onto the fifth row, you must first select the fifth row (highlight it) and then fire the right click menu. If you didn't do this, the right click menu would be associated with the previously selected row, the third row.
JTree provides two methods to find the row to select based on the MouseEvent's x and y coordinates:
getRowForLocation(int x, int y)getClosestRowForLocation(int x, int y)Each method has pros and cons. getRowForLocation will only return the row if the actual JTreeCellRenderer was clicked on. This means that if you were to click to the right of the words on the row, or the left of the icon on the row, getRowForLocation would return no row. That's no good.
getClosestRowForLocation, on the other hand, will return the proper row if you click too far right or too far left. Unfortunately, if you select below all of the rows, it will return the last row. That may not be a huge deal, but I find that kind of annoying. I want to be able to de-select by clicking nothing in the tree.
So I decided to make my own rules. First, I getClosestRowForLocation based on the MouseEvent's x and y coordinates. Then, getRowBounds for the row that was returned with getClosestRowForLocation. The bounds that are actually returned are the bounds of the JTreeCellRenderer, which starts at the icon and goes right until the end of the text. I then check to see if the mouse click was vertically inside the closestRow by comparing the mouse click's y coordinate with the closestRow's y (top) and y + height (bottom). If the mouse click's y coordinate does not fall between those two values, I know that the person clicked too far south of a row and I simply setSelectionRow(-1) to de-select. If the mouse click falls within the height bounds of a row, I then look to see if the node in the tree in that row is at depth 0 or not. If a node is at depth 0, that means it is a leaf node (it can not be expanded). If the node is indeed a leaf node, I select the closestRow. If the node at that row is not a leaf node I then ensure that the mouse click was somewhere to the right of the leftmost pixel of the icon in the JTreeCellRenderer. I do this because if the user actually clicks the expand/collapse arrow, I don't want to select that row, I only want to select a non-leaf row if they click somewhere to the right of the expand/collapse arrow.
public void mousePressed(MouseEvent e) {
int closestRow = databaseTree.getClosestRowForLocation(e.getX(), e.getY());
Rectangle closestRowBounds = databaseTree.getRowBounds(closestRow);
if(e.getY() > closestRowBounds.getY() && e.getY() < closestRowBounds.getY() + closestRowBounds.getHeight()) {
if(((DefaultMutableTreeNode) databaseTree.getClosestPathForLocation(e.getX(), e.getY()).getLastPathComponent()).getDepth() > 0 && e.getX() > closestRowBounds.getX())
databaseTree.setSelectionRow(closestRow);
} else
databaseTree.setSelectionRow(-1);
}
This little bit of logic really goes a long way in making the JTree stay out of the way while using the application.
I hope to roll out PGnJ 0.8 in less than a week.