Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Set the button "background" of a Nimbus button

I'm working on an app using the Nimbus Look and Feel. There's a table and one column contains buttons (using the Table Button Column from Rob Camick). That does work, but the result isn't what I had expected. I have tried to fix the look, but to no avail.

So the question is: how do I change the "background" (the area outside the rounded rectangle) of a Nimbus button? Preferably in a non-hacky way :-)

Using the default Table Column Button, the result looks like this:

Buttons with incorrect background

As you can see, the background (and by this I mean the area outside the button's rounded rectangle) is wrong for the odd (white) rows. The code that produces this output is:

public Component getTableCellRendererComponent(
        JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
{
    if (isSelected) {
        renderButton.setForeground(table.getSelectionForeground());
        renderButton.setBackground(table.getSelectionBackground());
    } else {
        renderButton.setForeground(table.getForeground());
        renderButton.setBackground(table.getBackground());
    }

    if (hasFocus) {
        renderButton.setBorder( focusBorder );
    } else {
        renderButton.setBorder( originalBorder );
    }

    // <snip some code>

    renderButton.setOpaque(true);

    return renderButton;
}

The renderButton is an instance of a default JButton. I've tried messing with the background color of the button, but that didn't work out like I expected at first:

        Color alternate = (Color)LookAndFeel.getDesktopPropertyValue("Table.alternateRowColor", Color.lightGray);
        Color normal = (Color)LookAndFeel.getDesktopPropertyValue("Table.background", Color.white);
        if (row % 2 == 0) {
            renderButton.setBackground(normal);
        } else {
            renderButton.setBackground(alternate);
        }

This produces:

Also incorrect button background

So this time the buttons that look alright in the first image are now bad and vice versa. The button's inner backgrounds (the areas inside the rounded rectangles) do seem to have the correct color according to the background color property (which is what's really modified with the setBackground() call). But the area outside is all wrong. Alright, let's combine the two :

        Color alternate = table.getBackground();
        Color normal = (Color)LookAndFeel.getDesktopPropertyValue("Table.background", Color.white);
        if (row % 2 == 0) {
            renderButton.setBackground(normal);
        } else {
            renderButton.setBackground(alternate);
        }

The result:

Still incorrect buttons

So now the "background" does look correct, but the buttons don't look like Nimbus buttons any more. How do I make the "background" have the correct color while still looking like Nimbus buttons?

like image 541
DarkDust Avatar asked Aug 12 '13 22:08

DarkDust


1 Answers

Below's a hacky way, following up on @Piro's suggestion: using a JPanel with the button as child component. Which in itself is a nice idea, given that we don't really want to touch the "inner" background visuals of the button.

Here the hack comes when forcing Nimbus internals to not use a JPanel's default background for filling its area but instead use the background of the given panel instance This needs relying on implementation details, particularly the lookup mechanism of a background color. That happens in SynthStyle.getColor():

// If the developer has specified a color, prefer it. Otherwise, get
// the color for the state.
Color color = null;
if (!id.isSubregion()) {
    if (type == ColorType.BACKGROUND) {
        color = c.getBackground();
    }
    ....
}

if (color == null || color instanceof UIResource) {
    // Then use what we've locally defined
    color = getColorForState(context, type);
}

Translated: it does indeed query the instance's color, but overrules it with the default if the instance color is a UIResource - which typically is the case if used as a renderer. So the trick out (tried unsuccessfully by SynthBooleanRenderer, but that's another story ;-) is to make the instance color not a UIResource. An additional quirk is that being UIResource is necessary to ensure the striping color - which is not of type UIResource, haha - be applied ... intuitive, isn't it ...

public class RendererPanel implements TableCellRenderer {

    private JComponent panel;
    private JButton button;
    public RendererPanel() {
        panel = new JPanel(new BorderLayout());
        panel.setBorder(BorderFactory.createEmptyBorder(3, 10, 2, 10));
        button = new JButton();
        panel.add(button);
    }

    @Override
    public Component getTableCellRendererComponent(JTable table,
            Object value, boolean isSelected, boolean hasFocus, int row,
            int column) {
        // suggestion by Piro - use background of default
        DefaultTableCellRenderer dt = (DefaultTableCellRenderer) table.getDefaultRenderer(Object.class);
        dt.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
        // first try: set the color as-is - doesn't work
        // panel.setBackground(dt.getBackground());
        // second try: set color as not ui-resource
        // that's working because at this point we already have the color that will be used
        // let's hinder synth background color searching to fall back to component defaults
        panel.setBackground(new Color(dt.getBackground().getRGB()));
        // hack: unwrap ui-resource as needed
        // updateBackground(isSelected ? table.getSelectionBackground() : table.getBackground(), row);
        button.setText(String.valueOf(value));
        return panel;
    }

    private void updateBackground(Color color, int row) {
        Color hack = row % 2 == 0 ? unwrap(color) : color;
        panel.setBackground(hack);
    }

    private Color unwrap(Color c) {
        if (c instanceof UIResource) {
            return new Color(c.getRGB());
        }
        return c;
    }

}

Screenshot: with unwrap hack

enter image description here

Screenshot: using default colors (from the renderer installed for Object.class)

enter image description here

The non-hacky way out might be (didn't try here, but remember having done once) to register a Region with the style, similarly to what NimbusDefaults does internally:

register(Region.PANEL, "Table:\"Table.cellRenderer\"");

Problem here being that there's no public api to do so (or could be that I simply don't know enough about Synth ;-)

like image 76
kleopatra Avatar answered Oct 12 '22 09:10

kleopatra