Skip to main content

JXTable GTKLookAndFeel boolean values renderer problem

29 replies [Last post]
gcds
Offline
Joined: 2010-03-30
Points: 0

Hello,

There seems to be a problem with the default checkbox renderer of JXTable under the GTK look and feel. When I execute the following code on Ubuntu with OpenJDK, the following Exception is thrown when I click in the second column (checkboxes) :

Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
at javax.swing.JComponent.setBorder(JComponent.java:1768)
at org.jdesktop.swingx.renderer.DefaultVisuals.configureBorder(DefaultVisuals.java:220)
at org.jdesktop.swingx.renderer.DefaultVisuals.configureVisuals(DefaultVisuals.java:109)
at org.jdesktop.swingx.renderer.ComponentProvider.configureVisuals(ComponentProvider.java:305)
at org.jdesktop.swingx.renderer.ComponentProvider.getRendererComponent(ComponentProvider.java:173)
at org.jdesktop.swingx.renderer.DefaultTableRenderer.getTableCellRendererComponent(DefaultTableRenderer.java:166)
at javax.swing.JTable.prepareRenderer(JTable.java:5676)
at org.jdesktop.swingx.JXTable.prepareRenderer(JXTable.java:3579)
at javax.swing.plaf.synth.SynthTableUI.paintCell(SynthTableUI.java:619)
at javax.swing.plaf.synth.SynthTableUI.paintCells(SynthTableUI.java:516)
at javax.swing.plaf.synth.SynthTableUI.paint(SynthTableUI.java:305)
at javax.swing.plaf.synth.SynthTableUI.update(SynthTableUI.java:234)
at javax.swing.JComponent.paintComponent(JComponent.java:765)
at javax.swing.JComponent.paint(JComponent.java:1029)
at javax.swing.JComponent.paintToOffscreen(JComponent.java:5138)
at javax.swing.BufferStrategyPaintManager.paint(BufferStrategyPaintManager.java:302)
at javax.swing.RepaintManager.paint(RepaintManager.java:1145)
at javax.swing.JComponent._paintImmediately(JComponent.java:5086)
at javax.swing.JComponent.paintImmediately(JComponent.java:4896)
at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:740)
at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:696)
at javax.swing.RepaintManager.prePaintDirtyRegions(RepaintManager.java:676)
at javax.swing.RepaintManager.access$700(RepaintManager.java:57)
at javax.swing.RepaintManager$ProcessingRunnable.run(RepaintManager.java:1550)
at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:226)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:602)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:275)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:200)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:190)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:185)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:177)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:138)

The Exception doesn't occur when I click inside the first column (String).
The Exception doesn't occur with the MetalLookAndFeel.
The Exception doesn't occur on windows with the Windows l&f.

Here is a simple runnable example, thanks a lot for your answers !!

All the best,
Guillaume.

import java.awt.Dimension;
import javax.swing.JFrame;
import javax.swing.UIManager;
import javax.swing.table.AbstractTableModel;
import org.jdesktop.swingx.JXTable;

public class test
{
public static void main(String[] args)
{
try
{
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
}
catch (Exception e){e.printStackTrace();}
Object[][] data = new Object[2][2];
data[0][0] = "a";
data[0][1] = true;
data[1][0] = "b";
data[1][1] = false;
String[] columnNames = new String[2];
columnNames[0] = "col1";
columnNames[1] = "col2";
JXTable table = new JXTable(getModel(data, columnNames));
JFrame frame = new JFrame();
frame.setContentPane(table);
frame.setPreferredSize(new Dimension(400,400));
frame.setMinimumSize(new Dimension(400,400));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}

private static AbstractTableModel getModel(final Object[][] data, final String[] columnNames)
{
return new AbstractTableModel()
{
@Override
public Class<?> getColumnClass(int columnIndex)
{
return data[0][columnIndex].getClass();
}

@Override
public String getColumnName(int column)
{
return columnNames[column];
}

public int getRowCount()
{
return data.length;
}

public int getColumnCount()
{
if (data.length > 0)
return data[0].length;
else
return 0;
}

public Object getValueAt(int rowIndex, int columnIndex)
{
return data[rowIndex][columnIndex];
}
};
}
}

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
kleopatra
Offline
Joined: 2003-06-11
Points: 0

Karl,

it's either they use the SwingX way of rendering and run into the problem (because Border is one of the properties which is always reset to its default) or not use the SwingX rendering and don't run into it (because the borders are never touched) We don't want to save the world, just protect SwingX :-)

The moment they use the SwingX rendering pipeline (which is default for JXTable and JXList), CellContext is loaded. So we should have covered all corner-cases in the SwingX uniververse.

Saw that you took over the issue - good for me, let you do the hard work and me leaning back :-)

Cheers
Jeanette

gcds
Offline
Joined: 2010-03-30
Points: 0

The problem indeed lies here (GTKPainter) :
[code]
public Insets getBorderInsets(Component c) {
return getBorderInsets(c, null);
}

public Insets getBorderInsets(Component c, Insets i) {
SynthContext context = getContext(c);

if (context != null) {
i = context.getStyle().getInsets(context, i);
}

return i;
}
[/code]

The 1-parameter method is called with the JCheckBox in parameter (your example).
The 2-parameter method is then called with null as the second parameter.
getContext(c) returns null (because c isn't a JLabel), thus the method returns null.

kleopatra
Offline
Joined: 2003-06-11
Points: 0

>
> The 1-parameter method is called with the JCheckBox
> in parameter (your example).
> The 2-parameter method is then called with null as
> the second parameter.
> getContext(c) returns null (because c isn't a
> JLabel), thus the method returns null.

ahhh ... I see - so the real crap is in the getContext():

[code]

private SynthContext getContext(Component c) {
SynthContext context = null;

ComponentUI ui = null;
///// arrrggghhhhh ....
if (c instanceof JLabel) {
ui = ((JLabel)c).getUI();
}

if (ui instanceof SynthUI) {
context = ((SynthUI)ui).getContext((JComponent)c);
}

return context;
}

[/code]

the new snippet learnt today: JComponent doesn't have a getUI() method. Which forces all ui code to type checks? Doooohhh ...

Anyway: don't see anything I could do on the SwingX level - definitely pointing fingers to GTK ;-) The underlying reason is an incorrect implementation of ListTableBorder - it must not ever return null for getBorderInsets. While that contract is undocumented (the typical left-out doc around null params/return values) in the border api, JComponent clearly expects a not-null.

Time for a bug report in core/open jdk, IMO

Good luck!
Jeanette

gcds
Offline
Joined: 2010-03-30
Points: 0

Many thanks Jeanette for your answers. I'm sorry no workaround can be found, I'll have to either use the cross-platform l&f on linux or use a standard JTable.

I'm not sure how to draw up the bug report because I don't understand well enough which class exactly is concerned by the bug (what is ListTableBorder ?). Is the code example you suggested me something we should have the right to do ?

If you can help me drawing up this bug report, I'll do it.

Thanks again.
Guillaume.

aephyr
Offline
Joined: 2009-11-20
Points: 0

As a work around until the bug is fixed, have you tried wrapping the deviant border?

[code]
class BorderWrapper extends AbstractBorder {
AbstractBorder border;

public Insets getBorderInsets(Component c, Insets i) {
i = border.getBorderInsets(c, i);
return i != null ? i : new Insets(?,?,?,?);
}

// other delegate methods...
}
[/code]

gcds
Offline
Joined: 2010-03-30
Points: 0

Thanks aephyr for your answer.

Your workaround should work but for the moment I simply add this line before the creation of the JXTable :
[code]
if (OS.isLinux())
UIManager.put("Table.focusSelectedCellHighlightBorder", BorderFactory.createEmptyBorder(0, 0, 0, 0));
[/code]

Seems like it's doing the job, and I don't see any inconsistencies with the rest of the GUI.

I keep your solution in store.

Thanks again to you and to Jeanette.

Guillaume.

kleopatra
Offline
Joined: 2003-06-11
Points: 0

> Your workaround should work but for the moment I
> simply add this line before the creation of the
> JXTable :
> [code]
> if (OS.isLinux())
>
> IManager.put("Table.focusSelectedCellHighlightBorder",
> BorderFactory.createEmptyBorder(0, 0, 0, 0));
> [/code]
>
> Seems like it's doing the job, and I don't see any
> inconsistencies with the rest of the GUI.
>

Hmm ... I would it expect it to not look the same as a the default border of a selected/focused checkbox cell in a plain core JTable. To get that, wrapping the default into a correctly behaving border is the recommended approach. We could do that on the level of an XXAddon (need to think how exactly, though). Don't know exactly how gtk/synth is supposed to work - are those values guaranteed to be loaded on receiving a propertyChanged from the UIManager? (they are not in Nimbus but that's probably a bug)

As to the core bug report, simply write a small failing unit test :-)

[code]
public BorderTest extends TestCase {

@Test
public void testBorderInsetsNotNull() {
Border border = UIManager.getBorder(".- here goes the key..");
if (border != null)
assertNotNull(border.getBorderInsets(new JCheckBox());
}
}
[/code]

Cheers
Jeanette

kschaefe
Offline
Joined: 2006-06-08
Points: 0

We can put the wrapped border into the addon, so that it works automatically for all users. It must either constantly query or perform an update on UIManager changes, since the workaround is working for them.

Karl

kleopatra
Offline
Joined: 2003-06-11
Points: 0

Karl,

yeah, that's what I had in mind :-) So will open an issue for it.

Just thinking which class should contribute the addon, need to be somewhere "single" or at the top of some hierarchy, maybe DefaultVisuals? Or CellContext? Once contributed to the LookAndFeelAddon the update should work automatically.

Plus need somebody with a linux box to run the tests - obviously not working on our win machines

CU
Jeanette

kleopatra
Offline
Joined: 2003-06-11
Points: 0

> So will open an issue for it.
>

done - https://swingx.dev.java.net/issues/show_bug.cgi?id=1297

CU
Jeanette

kschaefe
Offline
Joined: 2006-06-08
Points: 0

Perhaps in TableAddon?

Karl

kleopatra
Offline
Joined: 2003-06-11
Points: 0

> Perhaps in TableAddon?

probably - didn't try yet - not enough: from the border's name I guess it's used in both table and list. And inside swingx probably as well in wrapped tree rendering components. Suspect it will throw whenever there's a rendering component which is not a JLabel.

CU
Jeanette

kschaefe
Offline
Joined: 2006-06-08
Points: 0

Ahh, then is needs to go into LinuxLookAndFeelAddons. That's where we initialize the state for all of the addons.

Karl

kleopatra
Offline
Joined: 2003-06-11
Points: 0

hmm ... sounds a bit like a hammer ;-) I'll try a screwdriver first, like a CellContextAddon - that's where the border is accessed during visual default configuration.

Thanks - have a good day, I'm hungry now :-)
Jeanette

kschaefe
Offline
Joined: 2006-06-08
Points: 0

Maybe, it's a hammer, but I think we cannot be sure where the border will be used. If we're going to hack around a core bug, we should do so as early as possible and as broadly, to ensure the we don't leave cornercases hanging around.

Karl

kschaefe
Offline
Joined: 2006-06-08
Points: 0

More to the point: we know where we want to use it, but we don't know where the clients are using it. As many times as you tell people not to query the UIManager, they still do. We should leave the door open for people to get different results with SwingX because they have or have not loaded CellContext.

Karl

gcds
Offline
Joined: 2010-03-30
Points: 0

Hello,

Thank you Karl for committing the bug fix.
Maybe I'm missing something, but I still have the same problem.

In the debugger session, LinuxLookAndFeelAddons.initialize() is correctly executed but when I click on the checkbox, getContext(c) still returns null in this method (GTKPainter) :
[code]
public Insets getBorderInsets(Component c, Insets i) {
SynthContext context = getContext(c);

if (context != null) {
i = context.getStyle().getInsets(context, i);
}

return i;
}
[/code]

variable ui remains null in this code :
[code]
private SynthContext getContext(Component c) {
SynthContext context = null;

ComponentUI ui = null;
if (c instanceof JLabel) {
ui = ((JLabel)c).getUI();
}

if (ui instanceof SynthUI) {
context = ((SynthUI)ui).getContext((JComponent)c);
}

return context;
}
[/code]

I'm going home , I can continue debugging tomorrow if you want.

Thanks again to you Karl and Jeanette.

kleopatra
Offline
Joined: 2003-06-11
Points: 0

Guilaume,

the replacement is not yet used. And sure, debugging is greatly apreciated, both Karl and me are on windows, so not much we can do - gtk is highly OS-dependent. What I did is to grab the gtk-plaf from openjdk and commented all native methods and everything that didn't compile - so can "run", kind of - no painting done and most probably not entirely in the state it would be on a native system, just good for a crude overview.

CU
Jeanette

gcds
Offline
Joined: 2010-03-30
Points: 0

Jeanette,

I confirm that JXList uses GTKPainter$ListTableFocusBorder but JXTree doesn't (at least, the debugger session stops on my getBorderInsets breakpoint in the first case and not in sthe second).

Thanks.

Guillaume.

kschaefe
Offline
Joined: 2006-06-08
Points: 0

Guillaume,

Sorry, it has been release time at work and I've been very busy. Letting up now, so where are we on this?

Karl

kleopatra
Offline
Joined: 2003-06-11
Points: 0

Hmm ... seems to be specific to GTK which I don't have anywhere near, sorry. Could you (or somebody else using GTK) please track down what exactly is causing the NPE? From the top of the stacktrace, it's

[code]
// setBorder

if (border != oldBorder) {
if (border == null || oldBorder == null ||
--> !(border.getBorderInsets(this).equals(oldBorder.getBorderInsets(this)))) {

[/code]

which sounds like a misbehaved border (return value of getBorderInsets must be not null), hmm ...

Thanks
Jeanette

BTW, you can format code by taggin it with [ code ] .... [ /code ] - without the spaces. Please complain to the site admin that this isn't documented anywhere ;-)

kleopatra
Offline
Joined: 2003-06-11
Points: 0

for checking borders, please run the following snippet and post the output:

[code]

System.out.println("focused: " + UIManager.getBorder("Table.focusSelectedCellHighlightBorder"));
System.out.println("focused-fallback: " + UIManager.getBorder("Table.focusCellHighlightBorder"));
System.out.println("nofocus: " + UIManager.getBorder("Table.cellNoFocusBorder"));

[/code]

These are the properties which are looked up in the CellContext to decide which to use.

Thanks
Jeanette

gcds
Offline
Joined: 2010-03-30
Points: 0

Thanks a lot for your answer, Jeanette.

The output for your code is :
focused: com.sun.java.swing.plaf.gtk.GTKPainter$ListTableFocusBorder@7e78fc6
focused-fallback: com.sun.java.swing.plaf.gtk.GTKPainter$ListTableFocusBorder@73901437
nofocus: null

Seems you found the guilty property. How can I change this one ?

Thanks again.

Guillaume.

PS : Thanks for the tag, I'll remember.

gcds
Offline
Joined: 2010-03-30
Points: 0

If I run the following line before creating the JXTable, I still get the same NPE :

[code]
UIManager.put("Table.cellNoFocusBorder", BorderFactory.createEmptyBorder(0, 0, 0, 0));
System.out.println("nofocus: " + UIManager.getBorder("Table.cellNoFocusBorder"));
//nofocus: javax.swing.border.EmptyBorder@2c79a2e7
[/code]

What can I do ?

Thanks !

kleopatra
Offline
Joined: 2003-06-11
Points: 0

Guillaume,

it should not matter whether or not the nofocus is null (cellContext as well as the default renderers jump in with a default border plus null borders are valid anyway). If you want to check my initial guess - nothing more, just a blind shot - then check what the borders returned by the ui-manager report as their borderInsets, like in

[code]
Border focused = UIManager.getBorder("Table.focusSelectedCellHighlightBorder");
System.out.println("focused insets: " + focused.getBorderInsets(new JLabel()));
[/code]

if that's okay, I'm out of ideas - you'll have to go into a debugging session and find out where exactly something is null which is expected not-null, sorry

Jeanette

kleopatra
Offline
Joined: 2003-06-11
Points: 0

okay, some misbehaviour in the gtk border ListTableBorder:

[code]

public Insets getBorderInsets(Component c) {
return getBorderInsets(c, null);
}

public Insets getBorderInsets(Component c, Insets i) {
SynthContext context = getContext(c);

if (context != null) {
i = context.getStyle().getInsets(context, i);
}

return i;
}

[/code]

which looks like returning null if no context found, or maybe the style incorrectly returning a null. Could you please debug step into these to decide which of this happens? Iff the problem really is here ;-)

Thanks
Jeanette

kleopatra
Offline
Joined: 2003-06-11
Points: 0

yet another wild guess, please try this in your "real gtk" context. Mine is just a gtk skeleton laf installed, with everything trying to access native returning null - highly probable to not be stable, throwing up is expected ;-)

[code]
Border border = UIManager.getBorder("Table.focusSelectedCellHighlightBorder");
JCheckBox checkBox = new JCheckBox();
checkBox.setBorder(border);
// which throws at the same line as your stacktrace:
Exception in thread "main" java.lang.NullPointerException
at javax.swing.JComponent.setBorder(JComponent.java:1768)

[/code]

CU
Jeanette

gcds
Offline
Joined: 2010-03-30
Points: 0

Thanks Jeanette,

I get the same Exception with your code. In the debugging session, the NPE comes from this line (JComponent 1768) :

[code]
border.getBorderInsets(this).equals(oldBorder.getBorderInsets(this))
[/code]

indeed, border.getBorderInsets(this) returns null.

kleopatra
Offline
Joined: 2003-06-11
Points: 0

just committed (as of revision #3879) a tentative hack around the core swing issue

http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6739738

core probably (can't tell ... still blind on Linux) has it fixed as of 7b102. So didn't do much except replacing the gtk installed borders with a safe wrapper (basically as Karl started, just a bit different :-)

Please let me know how it goes

Thanks

Jeanette