[tonegodgui] bugs & fixes with Table

There is somes bug in the “Table” class.

The removeRow method is currently like this

[java]
public void removeRow(int index, boolean pack) {
selectedCells.remove(index);
selectedRows.remove(index); //<- the bug is here
this.getVScrollBar().hide();
if (!rows.isEmpty()) {
if (index >= 0 && index < rows.size()) {
rows.remove(index);
pack(); //bug also here ?
}
}
}
[/java]

The first is a real bug. When you do “selectedCells.remove(index);” it’s perfectly fine as selectedCells is a Map and a map has only a method to remove a key. So the method with the Object parameter is selected.
However, when you do the same call with selectedRows, you do it on a List. A List has 2 “remove” methods:
remove(int index)
remove(Object o)

so, when you do “selectedRows.remove(index);” you DON’T remove the integer “index” from the list, you try to remove the nth (with n = index) element of the list, and you get an ArrayIndexOutOfBoundsException.
The fix is:
[java]
public void removeRow(int index, boolean pack) {
selectedCells.remove(index);
selectedRows.remove(Integer.valueOf(index)); // transform the int into Integer, so the method with an Object as parameter is used.
this.getVScrollBar().hide();
if (!rows.isEmpty()) {
if (index >= 0 && index < rows.size()) {
rows.remove(index);
pack();
}
}
}
[/java]
I also think that the pack method should be enclosed in an “if (pack) { }” (you have it when you add a row).

For the second thing, it’s not really a bug but it’s something a bit confusing: the insertrow method is currently like this:

[java]
public void insertRow(int index, TableRow row, boolean pack) {
if (!rows.isEmpty()) {
if (index >= 0 && index < rows.size()) {
this.getVScrollBar().hide();
this.rows.add(index, row);
if (pack) {
pack();
}
}
}
}
[/java]
So, you can’t insert a row in an empty table or at the end of the list. Maybe that it’s a design choice, but it is just unintuitive, especially when the List (the standard collection) chose the opposite.

Maybe that something like this would be better:
[java]
@Override
public void insertRow(int index, TableRow row, boolean pack)
{
int s = rows.size();
if (index >= 0 && index < s)
{
this.getVScrollBar().hide();
this.rows.add(index, row);
if (pack)
{
pack();
}
}
else if (index == s)
{
addRow(row, pack);
}
else
{
//warn, maybe with a logger
}
}
[/java]

Also, for people that still has a “buggy” version and want a temporary fix, i wrote this class that extends Table and changes methods as described above (it doesn’t contain the “if” thing ,as i don’t know if it has been omitted on purpose):

[java]
/**

  • Temporary fix for the Table class
  • @author Bubuche
    */
    public abstract class ETable extends Table
    {
    private static final Logger LOG = Logger.getLogger(ETable.class.getName());

protected List<TableRow> rows = new ArrayList();
protected List<Integer> selectedRows = new ArrayList();
protected Map<Integer, List<Integer>> selectedCells;

public ETable(ElementManager screen, Vector2f position)
{
super(screen, position);
init();
}

public ETable(ElementManager screen, Vector2f position, Vector2f dimensions)
{
super(screen, position, dimensions);
init();
}

public ETable(ElementManager screen, Vector2f position, Vector2f dimensions, Vector4f resizeBorders, String defaultImg)
{
super(screen, position, dimensions, resizeBorders, defaultImg);
init();
}

public ETable(ElementManager screen, String UID, Vector2f position)
{
super(screen, UID, position);
init();
}

public ETable(ElementManager screen, String UID, Vector2f position, Vector2f dimensions)
{
super(screen, UID, position, dimensions);
init();
}

public ETable(ElementManager screen, String UID, Vector2f position, Vector2f dimensions, Vector4f resizeBorders, String defaultImg)
{
super(screen, UID, position, dimensions, resizeBorders, defaultImg);
init();
}

private void init()
{
try
{
Field field;

  field = Table.class.getDeclaredField("selectedRows");
  field.setAccessible(true);
  this.selectedRows = (List&lt;Integer&gt;) field.get(this);

  field = Table.class.getDeclaredField("selectedCells");
  field.setAccessible(true);
  this.selectedCells = (Map&lt;Integer, List&lt;Integer&gt;&gt;) field.get(this);

  field = Table.class.getDeclaredField("rows");
  field.setAccessible(true);
  this.rows = (List&lt;TableRow&gt;) field.get(this);
}
catch (NoSuchFieldException | SecurityException | IllegalArgumentException |
        IllegalAccessException ex)
{
  Logger.getLogger(ETable.class.getName()).log(Level.SEVERE, null, ex);
}

}

@Override
public void removeRow(int index, boolean pack)
{
Integer index_as_object = Integer.valueOf(index);

selectedCells.remove(index);
selectedRows.remove(index_as_object);

this.getVScrollBar().hide();
if (!rows.isEmpty())
{
  if (index &gt;= 0 &amp;&amp; index &lt; rows.size())
  {
    rows.remove(index);
    pack(); //should have "if ( pack )", shouldn't it ?
  }
}

}

@Override
public void insertRow(int index, TableRow row, boolean pack)
{
int s = rows.size();
if (index >= 0 && index < s)
{
this.getVScrollBar().hide();
this.rows.add(index, row);
if (pack)
{
pack();
}
}
else if (index == s)
{
addRow(row, pack);
}
else
{
LOG.log(Level.WARNING, “Invalid index: {0} (max index allowed: {1}”, new Object[]{index, s});
}
}
}
[/java]

And … " @t0neg0d see this message" (like a divin invocation).

FYI: http://hub.jmonkeyengine.org/forum/board/projects/tonegodgui/
…for next time.

Haven’t seen her around for a while. Maybe you should fork tgg, that might spark her interest =D

@normen said: Maybe you should fork tgg, that might spark her interest =D

Possibly on github! I also am using t0neg0d GUI and have some bug to fix. 4 eyeballs are better than 2 :wink:

actually i also found bugs in it. For exemple with the Spinner, you have 2 major problems :
1/ the spinner IS a textfield, and HAS two buttons. So, when you define the size of the spinner, it resizes itself to let some place to the button. And, if you resize it later (via setdimensions) it doesn’t overload this method and the button doens’t move (and you have a graphic bug).
2/ when you define the min/max range the spinner pre-calculate all the string it can show. So, if you define a range from Integer.MIN_VALUE to Integer.MAX_VALUE you create about 4 billion strings. And it can even be worse, as you can define min and max ranges that are not integers AND a very small step, resulting in a very huge number of unnecessary strings.
3?/ this is not realyl a bug but … the value is never stored, you only have a string (so you have its visual representation and have to deduce its real value from here.

I already create a code that fix all of this (basically i use a double to store the min, the max, the step and the current value. So yeah it’s 4 double. The spinner is an Element now, and HAS a textfield and two buttons). However the code is not complete, i only made the “horizontal” type (one button on the left, one button on the right). I think i can improve it and when i’ll have do that i’ll post the code.

The code of the tabcontrol is also unnecessarily complex and you can’t remove tabs. I made my own tabber from scratch, but i am not proud enough of it to show it.

But i don’t have time myself to fork and maintain the forked tonegod gui.

New bug and new fix.

Bug : when you use a table in a subscreen you get a null pointer exception. After a lot of search, i found that the guilty line is in an unavoidable constructor. The line is:
[java]
clipLayer.setAsContainerOnly();
[/java]
Where clipLayer is an element created and added in the constructor (the one with all parameters, but all constructor for Table use this(blabla), so … )

The null pointer exception occurs in the render pass, cause the renderer is trying to determine if it has to cull the object or not.
As i am not very comfortable to explain this in english, here is the stack trace:
[java]
java.lang.NullPointerException
at com.jme3.bounding.BoundingBox.intersects(BoundingBox.java:587)
at com.jme3.renderer.Camera.containsGui(Camera.java:1062)
at com.jme3.scene.Spatial.checkCulling(Spatial.java:282)
at com.jme3.renderer.RenderManager.renderSubScene(RenderManager.java:647)
at com.jme3.renderer.RenderManager.renderSubScene(RenderManager.java:665)
at com.jme3.renderer.RenderManager.renderSubScene(RenderManager.java:665)
at com.jme3.renderer.RenderManager.renderSubScene(RenderManager.java:665)
at com.jme3.renderer.RenderManager.renderSubScene(RenderManager.java:665)
at com.jme3.renderer.RenderManager.renderScene(RenderManager.java:640)
at com.jme3.renderer.RenderManager.renderViewPort(RenderManager.java:974)
at com.jme3.renderer.RenderManager.render(RenderManager.java:1023)
at com.jme3.app.SimpleApplication.update(SimpleApplication.java:252)
at com.jme3.system.lwjgl.LwjglAbstractDisplay.runLoop(LwjglAbstractDisplay.java:151)
at com.jme3.system.lwjgl.LwjglDisplay.runLoop(LwjglDisplay.java:185)
at com.jme3.system.lwjgl.LwjglAbstractDisplay.run(LwjglAbstractDisplay.java:228)
at java.lang.Thread.run(Thread.java:722)

[/java]
As you can see, none of my classes are involved into this. I managed to solve the bug, but i would appreciate a
“Warning, this spatial [name of the spatial, whatever] doesn’t have a bounding box” as it would make it easier to track bugs like this.

Anyway.

The only thing that the method “setAsContainerOnly” does is:
[java]
detachChildAt(0);
[/java]

so, if you don’t want to copy/past everything from Table.java and comment the line, you can do this:

[java]
Field field = Table.class.getDeclaredField(“clipLayer”);
field.setAccessible(true);
Element clipLayer = (Element) field.get(this);

clipLayer.attachChild(new Element(
screen,
getUID()+":Savior Sibling",
Vector2f.ZERO,
Vector2f.UNIT_XY,
Vector4f.ZERO,
null).detachChildAt(0));
[/java]

But i think that the root of the problem is still here, as any element that calls “setAsContainerOnly” will likely cause the same bug. I’ll try to find a better fix, but you still have this one.

@bubuche said: actually i also found bugs in it. For exemple with the Spinner, you have 2 major problems : 1/ the spinner IS a textfield, and HAS two buttons. So, when you define the size of the spinner, it resizes itself to let some place to the button. And, if you resize it later (via setdimensions) it doesn't overload this method and the button doens't move (and you have a graphic bug). 2/ when you define the min/max range the spinner pre-calculate all the string it can show. So, if you define a range from Integer.MIN_VALUE to Integer.MAX_VALUE you create about 4 billion strings. And it can even be worse, as you can define min and max ranges that are not integers AND a very small step, resulting in a very huge number of unnecessary strings. 3?/ this is not realyl a bug but ... the value is never stored, you only have a string (so you have its visual representation and have to deduce its real value from here.

I already create a code that fix all of this (basically i use a double to store the min, the max, the step and the current value. So yeah it’s 4 double. The spinner is an Element now, and HAS a textfield and two buttons). However the code is not complete, i only made the “horizontal” type (one button on the left, one button on the right). I think i can improve it and when i’ll have do that i’ll post the code.

The code of the tabcontrol is also unnecessarily complex and you can’t remove tabs. I made my own tabber from scratch, but i am not proud enough of it to show it.

But i don’t have time myself to fork and maintain the forked tonegod gui.

I posted some code in this forum somewhere for ‘ModelledSpinner’ that overcomes most of those problems. I found even doubles to not be good enough under some circumstances, the only way that REALLY works is using BigDecimal.

However… it seems to have got lost with recent site troubles. It should be here - http://hub.jmonkeyengine.org/forum/topic/suggestion-for-spinner-control/

The thing with how spinner is constructed, that is not the only element that works that way. CheckBox / RadioButton is also the same and can be frustrating.

Well, here : https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:contributions:tonegodgui:donts
you can read: “there is NO need to create windows, panels, etc as anything but screen level components”
she calls that “unnecessary nesting”. So i think that it was done on purpose. Also, remember that when she made these components there was nothing like layout in the library, so elements wasn’t designed to handle resizing.
This doesn’t excuse the “pre-create all the possible values” though. I think i can adapt my code to make it uses “BigDecimal” as backend, but i am not sure about that (it’s maybe an overkill. doubles are very precise, and you can go to very high int values with them).

Anyway, i recently tried to used the “SubScreen” thing (having a screen on an object in-game) and there is some bugs here also apparently (my mouse clicks where eaten although i wasn’t looking toward the ingame screen). But i managed to have a pretty nice interaction with a code from pspeed i grabed on this forum (something to get the x/y coordinates of an item’s texture). I also think that it will work just fine with a non-plan ingame screen (a ball, a curved wall etc.) even if i didn’t test it yet.

But right now (flash news) i am working on a little lib in C cause i found a way to have a graphics window and no memory leaks at all (even with the simplest sdl/qt/glut program (= init then close) you have memory leak.
It seems that it’s not the case with XLib (as long as you don’t call functions like XLookupString) so i’ll create a small lib around that, inspired from jmonkeyengine (appstates, assetsmanager, controls, scene graph … and a gui lib). Everything in C, with the wonderfull program valgrind to help me :slight_smile:

After that, if i am proud enough of my gui lib, i’ll port it to java… or i’ll try to make a way to use a jmonkey’s texture as a canvas for x11.

@bubuche said: Well, here : https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:contributions:tonegodgui:donts you can read: "there is NO need to create windows, panels, etc as anything but screen level components" she calls that "unnecessary nesting". So i think that it was done on purpose. Also, remember that when she made these components there was nothing like layout in the library, so elements wasn't designed to handle resizing. This doesn't excuse the "pre-create all the possible values" though. I think i can adapt my code to make it uses "BigDecimal" as backend, but i am not sure about that (it's maybe an overkill. doubles are very precise, and you can go to very high int values with them).

Anyway, i recently tried to used the “SubScreen” thing (having a screen on an object in-game) and there is some bugs here also apparently (my mouse clicks where eaten although i wasn’t looking toward the ingame screen). But i managed to have a pretty nice interaction with a code from pspeed i grabed on this forum (something to get the x/y coordinates of an item’s texture). I also think that it will work just fine with a non-plan ingame screen (a ball, a curved wall etc.) even if i didn’t test it yet.

But right now (flash news) i am working on a little lib in C cause i found a way to have a graphics window and no memory leaks at all (even with the simplest sdl/qt/glut program (= init then close) you have memory leak.
It seems that it’s not the case with XLib (as long as you don’t call functions like XLookupString) so i’ll create a small lib around that, inspired from jmonkeyengine (appstates, assetsmanager, controls, scene graph … and a gui lib). Everything in C, with the wonderfull program valgrind to help me :slight_smile:

After that, if i am proud enough of my gui lib, i’ll port it to java… or i’ll try to make a way to use a jmonkey’s texture as a canvas for x11.

Yeah, when I started using TonegodGUI that was very much the case. But, I am more used to Swing, and over time built up quite a library of reusable components and my own layout system. This was partly because I started concentrating on building a number of separate design tools that share lots of components and have some fairly complex layouts. The game itself ‘screen level components’ would probably suffice (although in practice I use layout and nested components a lot there too).

Weirdly that link to my ModelledSpinner started working, so you can see the explanation of where float/decimal fails. It’s actually a Java bug (https://www.securecoding.cert.org/confluence/display/java/NUM09-J.+Do+not+use+floating-point+variables+as+loop+counters). I also did the same with Slider which suffers from exactly the same problem. Tonegod was going to add this an extra into the library but sadly it never happened.

Your GUI lib sounds interesting, good luck with it! :slight_smile:

@rockfire said: It's actually a Java bug (https://www.securecoding.cert.org/confluence/display/java/NUM09-J.+Do+not+use+floating-point+variables+as+loop+counters).

It’s not a Java bug. It’s just a fact of life of floating point. Who in their right mind would use floating point as loop counters anyway? I thought that was just a well understood no-no but that was true even back when using C… so maybe it’s just like breathing now.

…and certainly you should always be suspicious of code that uses someFloatingPointValue == someFloatConstant.

Old thread, I am sure… I didn’t write the table class… it’s cool… don’t get me wrong, but I didn’t write this.

EDIT: As mentioned in this thread… the source is available… if you want to grab it and modify. No clue when life is gonna allow me to get back to this, so… may be the best option. Or doing what others are doing… writing your own UI library.