gt: 				builder.append (new JLabel (desc));
context: package nl.helixsoft.gui;
  
  import com.jgoodies.forms.builder.DefaultFormBuilder;
  import com.jgoodies.forms.layout.CellConstraints;
  import com.jgoodies.forms.layout.FormLayout;
  import com.jgoodies.forms.layout.RowSpec;
  
  import java.awt.BorderLayout;
  import java.awt.CardLayout;
  import java.awt.Color;
  import java.awt.Dimension;
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
  import java.io.File;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  
  import javax.swing.BorderFactory;
  import javax.swing.JButton;
  import javax.swing.JCheckBox;
  import javax.swing.JColorChooser;
  import javax.swing.JDialog;
  import javax.swing.JFileChooser;
  import javax.swing.JFrame;
  import javax.swing.JLabel;
  import javax.swing.JPanel;
  import javax.swing.JScrollPane;
  import javax.swing.JSplitPane;
  import javax.swing.JTextField;
  import javax.swing.JTree;
  import javax.swing.border.BevelBorder;
  import javax.swing.border.Border;
  import javax.swing.event.TreeSelectionEvent;
  import javax.swing.event.TreeSelectionListener;
  import javax.swing.tree.DefaultMutableTreeNode;
  import javax.swing.tree.TreeNode;
  import javax.swing.tree.TreePath;
  
  import nl.helixsoft.gui.ColorConverter;
  import nl.helixsoft.gui.preferences.Preference;
  import nl.helixsoft.gui.preferences.PreferenceManager;
  
  import org.pathvisio.desktop.PreferencePanel;
  
  /**
   * Global dialog for setting the user preferences.
   * <p>
   * Adapted from PathVisio
   */
  @SuppressWarnings("deprecation")
  public class PreferencesDlg
  {
  	public static final String UPDATE_COMMAND = "prefDlg.updated";
  	private static final String ROOT_NODE_TITLE = "Preferences";
  	private Map <String, PreferencePanel> panels = new HashMap <String, PreferencePanel>();
  	private Set<ActionListener> actionListeners = new HashSet<ActionListener>();
  
  	private DefaultMutableTreeNode createNodes()
  	{
  		DefaultMutableTreeNode top = new DefaultMutableTreeNode(ROOT_NODE_TITLE);
  
  		// sort child panels by name
  		List<String> panelTitles = new ArrayList<String>();
  		panelTitles.addAll (panels.keySet());
  		Collections.sort (panelTitles);
  		// organize child nodes
  		DefaultMutableTreeNode prevNode = null;
  		String prevTitle = null;
  		for (String title : panelTitles)
  		{
  			if (prevTitle != null && title.startsWith(prevTitle + "."))
  			{
  				prevNode.add (new DefaultMutableTreeNode (title));
  			}
  			else
  			{
  				prevNode = new DefaultMutableTreeNode (title);
  				prevTitle = title;
  				top.add (prevNode);
  			}
  		}
  
  		// add root node's preference panel last so that it's not treated as a child above
  		panels.put(ROOT_NODE_TITLE, new PreferencePanel() {
  			private JPanel panel = new JPanel();
  			public JPanel getPanel() {
  				return panel;
  			}
  			public void apply() {
  			}
  			public void reset() {
  			}
  		});
  
  		return top;
  	}
  
  	/**
  	 * @param title The title of this panel, that will be visible in the JTree on the left
  	 *  side of the dialog. You can use a dot (.) to group panels in the tree: for
  	 * 	example title "Display.Colors" will be arranged under "Display" in the tree (but "Display"
  	 *  needs to exist). Grouping goes only one level deep.
  	 * @param panel use @link{PreferencePanel.builder()} to construct an @link{PreferencePanel}.
  	 * @throws IllegalArgumentException if title has already been claimed by another PreferencePanel
  	 */
  	public void addPanel (String title, PreferencePanel panel)
  	{
  		if (panels.containsKey(title) || ROOT_NODE_TITLE.equals(title)) {
  			throw new IllegalArgumentException("Another panel has already been registered with the title '" + title + "'");
  		}
  		panels.put (title, panel);
  	}
  
  	public void removePanel(PreferencePanel panel) {
  		String key = null;
  		for (Map.Entry<String, PreferencePanel> e : panels.entrySet()) {
  			if (panel == e.getValue()) {
  				key = e.getKey();
  				break;
  			}
  		}
  		if (key != null) {
  			panels.remove(key);
  		}
  	}
  
  	public DefaultPreferencePanel.Builder builder()
  	{
  		return new DefaultPreferencePanel.Builder(prefs);
  	}
  
  	/**
  	 * PreferencePanel groups a number of preferences.
  	 * <p>
  	 * Use @link{PreferencePanel.builder()} to create one.
  	 * You can use the chained .xxxField() methods to add the preferences that you want to edit,
  	 * with a description.
  	 */
  	public static class DefaultPreferencePanel implements PreferencePanel, ActionListener
  	{
  		private JPanel panel;
  		private PreferenceManager prefs;
  		private List<FieldEditor> editors = new ArrayList<FieldEditor>();
  
  
  		public JPanel getPanel() {
  			return panel;
  		}
  
  		public void apply()
  		{
  			for (FieldEditor editor : editors)
  			{
  				editor.apply();
  			}
  		}
  
  		public void restoreDefault()
  		{
  			for (FieldEditor editor : editors)
  			{
  				editor.restoreDefault();
  			}
  		}
  
  		public void reset()
  		{
  			for (FieldEditor editor : editors)
  			{
  				editor.reset();
  			}
  		}
  
  		private abstract class FieldEditor
  		{
  			protected Preference p;
  			FieldEditor (Preference p) { this.p = p; }
  
  			/**
  			 * Should copy the value of the editing component to
  			 * the preference
  			 */
  			abstract void apply();
  
  			/**
  			 * restoreDefault should set the editing component to
  			 * the default, but not apply it to the preference yet,
  			 * so that the user still has the chance to cancel.
  			 */
  			abstract void restoreDefault();
  
  			/**
  			 * Should copy the value of the preference
  			 * to the editing component.
  			 * Is called just before PreferenceDlg is shown.
  			 */
  			abstract void reset();
  		}
  
  		private class BooleanFieldEditor extends FieldEditor
  		{
  			private JCheckBox cb;
  
  			BooleanFieldEditor(Preference p, JCheckBox cb)
  			{
  				super(p);
  				this.cb = cb;
  			}
  
  			@Override void apply()
  			{
  				prefs.setBoolean(p, cb.isSelected());
  			}
  
  			@Override void restoreDefault()
  			{
  				cb.setSelected(p.getDefault().equals ("" + true));
  			}
  
  			@Override void reset()
  			{
  				cb.setSelected (prefs.getBoolean (p));
  			}
  		}
  
  		private class ColorFieldEditor extends FieldEditor implements ActionListener
  		{
  			private JLabel colorLabel;
  
  			ColorFieldEditor (Preference p, JLabel colorLabel)
  			{
  				super(p);
  				this.colorLabel = colorLabel;
  				colorLabel.setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED));
  				colorLabel.setOpaque(true);
  			}
  
  			public void actionPerformed (ActionEvent ae)
  			{
  				Color newColor = JColorChooser.showDialog(null, "Choose a color", prefs.getColor(p));
  				if (newColor != null)
  				{
  					colorLabel.setBackground(newColor);
  				}
  			}
  
  			@Override
  			void apply() {
  				prefs.setColor(p, colorLabel.getBackground());
  			}
  
  			@Override
  			void restoreDefault()
  			{
  				Color c = ColorConverter.parseColorString(p.getDefault());
  				colorLabel.setForeground(c);
  				colorLabel.setBackground(c);
  			}
  
  			@Override void reset()
  			{
  				Color c = prefs.getColor (p);
  				colorLabel.setForeground(c);
  				colorLabel.setBackground(c);
  			}
  		}
  		private class IntegerFieldEditor extends FieldEditor
  		{
  			private JTextField txt;
  
  			IntegerFieldEditor (Preference p, JTextField txt)
  			{
  				super(p);
  				this.txt = txt;
  			}
  
  			@Override void apply()
  			{
  				try
  				{
  					prefs.set (p, "" + Integer.parseInt (txt.getText()));
  				}
  				catch (NumberFormatException e)
  				{
  					// ignore, we just leave the old value
  				}
  			}
  
  			@Override
  			void restoreDefault() {
  				txt.setText (p.getDefault());
  			}
  
  			@Override void reset()
  			{
  				txt.setText(prefs.get(p));
  			}
  		}
  		private class StringFieldEditor extends FieldEditor
  		{
  			private JTextField txt;
  
  			StringFieldEditor (Preference p, JTextField txt)
  			{
  				super(p);
  				this.txt = txt;
  			}
  
  			@Override void apply()
  			{
  				prefs.set (p, txt.getText());
  			}
  
  			@Override void restoreDefault() {
  				txt.setText (p.getDefault());
  			}
  
  			@Override void reset()
  			{
  				txt.setText (prefs.get(p));
  			}
  		}
  		private class FileFieldEditor extends FieldEditor implements ActionListener
  		{
  			private JTextField txt;
  
  			FileFieldEditor (Preference p, JTextField txt)
  			{
  				super(p);
  				this.txt = txt;
  			}
  
  			public void actionPerformed(ActionEvent ae)
  			{
  				JFileChooser jfc = new JFileChooser();
  				if (jfc.showDialog(null, "Choose") == JFileChooser.APPROVE_OPTION)
  				{
  					File result = jfc.getSelectedFile();
  					txt.setText("" + result);
  					prefs.setFile (p, result);
  				}
  			}
  
  			@Override
  			void apply() {
  				prefs.set (p, txt.getText());
  			}
  
  			@Override
  			void restoreDefault()
  			{
  				txt.setText (p.getDefault());
  			}
  
  			@Override void reset()
  			{
  				txt.setText (prefs.get(p));
  			}
  		}
  
  		public void actionPerformed(ActionEvent ae)
  		{
  			restoreDefault();
  		}
  
  		/** Helper class to build preference panel, based on a list of preferences and their types.
  		 * Builds a panel and adds a textfield for a string, checkbox for a boolean, etc.
  		 * <p>
  		 * You can't instantiate directly. Use dlg.createBuilder
  		 * */
  		public static class Builder
  		{
  			private DefaultPreferencePanel result = new DefaultPreferencePanel();
  			private DefaultFormBuilder builder;
  			private FormLayout layout;
  
  			/** Can't instantiate directly. Use dlg.createBuilder */
  			private Builder(PreferenceManager prefs)
  			{
  				layout = new FormLayout("left:pref, 6dlu, 50dlu:grow, 4dlu, default");
  				builder = new DefaultFormBuilder(layout);
  				result.prefs = prefs;
  			}
  
  			public PreferencePanel build()
  			{
  				JButton btnRestore = new JButton();
  				btnRestore.setText ("Restore Defaults");
  				btnRestore.addActionListener(result);
  
  				// add button to the bottom-right of the panel
  				CellConstraints cc = new CellConstraints();
  				builder.appendRow(RowSpec.decode("fill:pref:grow"));
  				builder.add (btnRestore,
  						cc.xyw(builder.getColumn(), builder.getRow(), 5, "right, bottom"));
  
  
  				result.panel = builder.getPanel();
  				return result;
  			}
  
  			public Builder booleanField (Preference p, String desc)
  			{
  				JCheckBox cb = new JCheckBox (desc);
  				BooleanFieldEditor editor = result.new BooleanFieldEditor (p, cb);
  				builder.append (cb, 3);
  				builder.nextLine();
  				result.editors.add(editor);
  				return this;
  			}
  
  			public Builder colorField (Preference p, String desc)
  			{
  				JButton btnColor = new JButton("Change...");
  				JLabel colorLabel = new JLabel("--");
  				ColorFieldEditor editor = result.new ColorFieldEditor (p, colorLabel);
  				btnColor.addActionListener(editor);
  				builder.append (new JLabel (desc));
  				builder.append (colorLabel);
  				builder.append (btnColor);
  				builder.nextLine();
  				result.editors.add(editor);
  				return this;
  			}
  
  
  			public Builder integerField (Preference p, String desc, int min, int max)
  			{
  				//TODO: handle min / max
  				JTextField txt = new JTextField(8);
  				IntegerFieldEditor editor = result.new IntegerFieldEditor (p, txt);
  