mutterings of a cynic

Wednesday, June 27, 2007

too long

Sometimes I do, say or think something that makes me realise I've been in Switzerland too long. Today I went one step further and actually wrote this sentence in an email.


Since it's an interface I'm not technically overriding anything, so the @Override annotation wouldn't if I added it compile.


Thankfully I caught it before sending the email out, but this really does go to show that perhaps I really have been here too long.


Tuesday, June 26, 2007

gratitude

Receiving praise is a very important aspect of social interactions. At work in particular, praise (when warranted) is extremely important. When you write some code that for one reason or another is impressive, it feels good when someone says "ooh, that's quite nice that is". That praise alone can be enough to sustain a person even if other aspects of their environment are less appealing for whatever reason.

A while ago I wrote a threading api which is quite large and (if I say so myself) quite useful. A few people have said nice things about it and that made me feel good. Today however I got a rather unusual, but rather fulfilling comment about it. I was actually outright thanked for having written it. That felt great.


Sunday, June 24, 2007

rounding errors in swing

I quite enjoyed blogging about layout managers the other day so I thought I'd follow up with another easy mistake to make in swing - positioning your components incorrectly due to rounding errors.

I recently wanted the equivalent of a GridLayout, but I wanted to be able to set the ratios of the grid sizes myself rather than have them all equal.

The required result is pictured below using the LayoutManager configured to lay the components out vertically using the ratios specified in their names.



As you can see here, the buttons are laid out such that all the available space (within the red border) is completely used up.

Naively, you would think that the code to produce that result looks like this (stripped down for easier reading):


float[] ratios = ...;
Component[] cc = parent.getComponents();

int x = insets.left;
int y = insets.top;
int tw = [avail width minus insets]
int th = [avail height minus insets]

for (int i = 0; i < cc.length; i++) {
  cc[i].setLocation(x, y);
  int h = (int) (th * ratios[i]);
  cc[i].setSize(tw, h);
  y += h;
}


However if you do that, the result looks like this:



Note that the bottom button is a good 3 pixels away from the border. Problem here is that we have a cumulative rounding error. Don't think that the solution is to round the calculation of each unit height either, if you do that, you'll end up using more vertical space than available.



So the solution is to not store the individual ratios at all, but rather store the cumulative ratios in an array that is 1 longer than the component array. For instance, rather than have an array like this [0.25, 0.5, 0.25] to lay out 3 components, use an array like this [0, 0.25, 0.75, 1] (make sure you manually clamp the last value to 1 to avoid another possible rounding error).

Here's some code:


float[] cumulativeRatios = ...;
int[] ys = new int[cumulativeRatios.length];

int tw = [avail width minus insets]
int th = [avail height minus insets]

for (int i = 0; i < cumulativeRatios.length; i++) {
  ys[i] = Math.round(th * cumulativeRatios[i]);
}

Component[] cc = parent.getComponents();
for (int i = 0; i < cc.length; i++) {
  cc[i].setLocation(insets.left, insets.top + ys[i]);
  cc[i].setSize(tw, ys[i + 1] - ys[i]);
}


Now you've spread the rounding errors over the available space in the parent and your components are laid out edge to edge - much nicer!

Incidentally, GridLayout doesn't use this mechanism of avoiding rounding errors. Here's a picture of the same test code using GridLayout with 5 rows and 1 column. This isn't necessarily wrong, it just might not be ideal in all circumstances.



I won't post the full code to the layout manager (unless someone asks for it), but rather leave it as an exercise for the reader.

As before, questions, comments and critique welcome.

Labels: , ,


Saturday, June 23, 2007

something new

I love learning new things. Recently I updated firefox on my various machines and it comes with a built in spell checker which I actually find very useful (since my speling is terrible). So, today I wrote an email and used the word schlep and it didn't give it a squiggly underline that would indicate a misspelt word. I found that amusing because my instant reaction was that they don't use a real dictionary, but rather a combination of real words and commonly used slang. I was however a little curious so I checked on dictionary.com and to my surprise, the word actually exists.


schlep /ʃlɛp/ Pronunciation Key - Show Spelled Pronunciation[shlep] Pronunciation Key - Show IPA Pronunciation verb, schlepped, schlep·ping, noun Slang.
–verb (used with object)
1. to carry; lug: to schlep an umbrella on a sunny day.
–verb (used without object)
2. to move slowly, awkwardly, or tediously: We schlepped from store to store all day.
–noun
3. Also, schlepper. someone or something that is tedious, slow, or awkward; drag.
Also, schlepp, shlep, shlepp.


Friday, June 22, 2007

know your layout manager

I often notice that people get really confused with the way java lays out components. Experience also shows that people struggle with what the sizing methods on JComponents really do. More than once I've seen code that does this kind of thing:

 JComponent c = ...;
 Dimension d = new Dimension(...);
 c.setMinimumSize(d);
 c.setMaximumSize(d);
 c.setPreferredSize(d);
 c.setSize(d);

Clearly that's insane, so I thought I might try to make people aware of a few little tricks to help size your components perfectly.

Here's a couple of points to remember:

  • The preferred size of a JComponent always tells you how big it wants to be. Mostly that size is sensible and it should be used.

  • If you set a preferred size of a component it is permanently altered. Stop and think before you do this. If you alter the preferred size of a combo box that has an item "Cat" (because you think it was too tall) like this:


     Dimension d = combo.getPreferredSize();
     d.height = 6;
     combo.setPreferredSize(d);


    Then when someone adds an element "Catamaran" you'll find your preferred width is now too small.

  • Know your layout manager! A LayoutManager will size a component in whichever way it feels is correct. If a LayoutManager ignores minimum size then don't get angry with your custom component because you set a minimum size and it's being ignored.

  • Don't be afraid to nest panels. Using layout gaps, borders and glue can get you quite far but for even the most basic setup of components nesting panels with individual layouts will take you to the finish line.


Here's a simple example illustrating a couple of those points.




 JPanel p = new JPanel(new BorderLayout());
 JScrollPane scroller = new JScrollPane(VERTICAL_SCROLLBAR_NEVER,  HORIZONTAL_SCROLLBAR_ALWAYS);
 scroller.setBorder(null);
 scroller.setViewportView(new JTextArea("My little scrolling thing "));
 p.add(scroller);


This is a panel containing a scrollpane (without its border cause it mucks up my example later on). If I want to add a widget of some sort next to the scrollbar then it'll adversely effect my size.



This was done by moving the scroll bar from out of its JScrollPane and into a custom panel next to another component.


 JPanel scrollBarPanel = new JPanel(new BorderLayout());
 JComboBox combo = new JComboBox(new String[] { "Some options" });
 scrollBarPanel.add(combo, BorderLayout.WEST);
 scrollBarPanel.add(scroller.getHorizontalScrollBar(), BorderLayout.CENTER);
 p.add(scrollBarPanel, BorderLayout.SOUTH);


Notice in the code that I'm adding the combo to the west side of a border layout. This means that I know that the combo will always get its preferred size and therefore look correct. The scroll bar on the other hand ends up in the center of the BorderLayout which means it gets all of the rest of the space.

Problem here is that the combo's preferred size is taller than the scroll bars and that makes the scroll bar look ugly!



Since I want to retain the correct width, my solution is mostly correct, but I just want to adjust the height of the combo, but retain its relative calculation of its preferred size (which means I don't want to just set its preferred size).

The solution I'm getting at here is that you should know your layout manager and not bother tweaking your components. The BorderLayout will have its minimum height set to the largest preferred height. That means that all we need to do it ensure that the largest preferred height in the panel is the scroll bar and not the combo.



The result, pictured above, was produced by embedding the combo into a panel with its preferred size calculation always clamping the height to 0. By that I mean literally creating a class called ZeroHeightPanel that extends JPanel and override a single method as follows:


@Override
public Dimension getPreferredSize() {
 final Dimension d = super.getPreferredSize();
 d.height = 0;
 return d;
}

This will now let your layout manager get preferred sizes of all the components it's laying out without having had to alter any settings on any of the components and without having to override any methods on the components. (Obviously the behaviour of the panel is altered, but my point is that if you get your JComponent from elsewhere in the API you won't be able to override it, but rather only embed it into something that you have control over.) Note that we are now laying out components following the composition over inheritance described in Item 14 of Josh Bloch's Effective Java. I believe that the rule can easily be applied to how UIs are laid out since we can easily compose a panel with altered sizing behaviour.

I'm not saying that this is how you should handle all of your sizing issues, but I just want to point out that there are other solutions that aren't necessarily obvious. If you know your layout managers and know what rules they apply to laying components out then you should be able to produce your desired behaviour without giving yourself a headache.

As a last point, if a layout manager doesn't do what you want it to do, don't be afraid of writing one. They are easy to write, but make sure that you document what rules it's using to size its component(s).

Questions and critique are welcome.

Labels: , , ,


Thursday, June 14, 2007

interface on the fly

On rahel's suggestion I thought I should post something really useful that you can do with generics. In java you can map most things by just adding another interface but sometimes you really don't want to define a crappy interface like:


interface DisposableComponent {
  void dispose();
  Component getComponent();
}


So here's something that not everyone knows about generics:


interface Flappable {
  void flap();
}

interface Flippable {
  void flip();
}

class FlipFlapper {
  <T extends Flappable & Flippable> void flipAndFlap(T ff) {
    ff.flip();
    ff.flap();
  }
}

Labels: ,