Cant clone using lemur Container

I was playing around with lemur and i got really confuse when i couldnt clone a Container. This is the code im using:

    Container window = new Container("BottomVF");
    window.setLocalTranslation(0,0,0);
    Spatial newwindow = window.clone();
    this.guiNode.attachChild(newwindow);

If i attach the first window, it appears normaly, but when i clone it, the newwindow dont appear.

Probably because some other “I’ve already been rendered so I won’t recalculate this” state was cloned, too.

Lemur GUI elements have not been designed with cloning in mind. They were written before I rewrote JME’s cloning system… so it was pretty impossible before. At this point I just haven’t gotten back to it.

It also rarely comes up, I guess.

Thanks for the response, for now im just gonna override the clonning methods in the Container class but i cant clone the controls, but its ok, i can add them manualy.
this is the code im using:

@Override
public Container clone() {
    return clone(true);
}
@Override
public Container clone(boolean cloneMaterial) {
    ElementId element = this.getElementId();
    String style = this.getStyle();
    Container clone = new Container(null, true, element, style);
    if (worldBound != null) {
        clone.worldBound = worldBound.clone();
    }
    clone.worldLights = this.worldLights.clone();
    clone.localLights = this.localLights.clone();
    clone.localLights.setOwner(clone);
    clone.worldLights.setOwner(clone);
    clone.worldOverrides = new SafeArrayList<>(MatParamOverride.class);
    clone.localOverrides = new SafeArrayList<>(MatParamOverride.class);
    for (MatParamOverride override : this.localOverrides) {
        clone.localOverrides.add((MatParamOverride) override.clone());
    }
    clone.worldTransform = this.worldTransform.clone();
    clone.localTransform = this.localTransform.clone();
    
    clone.parent = null;
    clone.setBoundRefresh();
    clone.setTransformRefresh();
    clone.setLightListRefresh();
    clone.setMatParamOverrideRefresh();
    
    if (this.userData != null) {
        clone.userData = (HashMap<String, Savable>) this.userData.clone();
    }
    return clone;
}

Yikes! The way you’ve done that will completely override the new JmeCloner stuff… so you will probably break all kinds of things.

It’s probably best to figure out why cloning doesn’t work in Lemur and just fix that rather than “replace everything with something more broken”. For example, where did all of the children go?

I’ve been looking into the cloner class to figure out what changes do i have to make in lemur.
I came into the conclution that i have to start implementing Cloneable or JmeCloneable into the clases that needs to be clone, and update all the clonefields to add this classes
I started with the Panel class and made a change in the cloneFields method like this

@Override
public void cloneFields( Cloner cloner, Object original ) {
    cloner.setCloneFunction(java.util.Map.class, new MapCloneFunction());
    super.cloneFields(cloner, original);
    this.elementId = cloner.clone(elementId);
    this.name = this.name+" clone";
}

I made this class to handle map cloning because theres a map that needs to be cloned

public class MapCloneFunction<T extends Map> implements CloneFunction<T> {
public T cloneObject( Cloner cloner, T object ) {         
    try {
        T clone = cloner.javaClone(object);         
        return clone;
    } catch( CloneNotSupportedException e ) {
        throw new IllegalArgumentException("Clone not supported for type:" + object.getClass(), e);
    }
}
 
/**
 *  Clones the elements of the map.
 */    
@SuppressWarnings("unchecked")
public void cloneFields( Cloner cloner, T clone, T object ) {
    for(Object mapp : object.entrySet()){
        Map.Entry entry = (Map.Entry) mapp;
        if(entry.getKey().equals(entry.getValue().getClass()) ||
           entry.getKey() instanceof String ||
           entry.getKey() instanceof Integer ){
            ValueClone( cloner,  clone,  object ,entry);
        }else{
            KeyValueClone( cloner,  clone,  object ,entry);
        }
    }
  
}
    
/**
 *  It only clones the value of the map if it's part of the jme-clonning system 
 */  
public void ValueClone(Cloner cloner, T clone, T object ,Map.Entry entry){
    CloneFunction f = cloner.getCloneFunction(entry.getValue().getClass());
    if(f != null ||
       entry.getValue() instanceof Cloneable || 
       entry.getValue() instanceof JmeCloneable ||
       entry.getValue().getClass().isArray() ){
        clone.put(entry.getKey(), cloner.clone(entry.getValue()));
    }
}
    
/**
 *  It clones the key and value of the map if they are part of the jme-clonning system 
 */  
public void KeyValueClone(Cloner cloner, T clone, T object ,Map.Entry entry){
    CloneFunction f = cloner.getCloneFunction(entry.getKey().getClass());
    CloneFunction f2 = cloner.getCloneFunction(entry.getValue().getClass());
    
    if(f != null ||
       entry.getKey() instanceof Cloneable || 
       entry.getKey() instanceof JmeCloneable ||
       entry.getKey().getClass().isArray() 
       &&
       f2 != null ||
       entry.getValue() instanceof Cloneable || 
       entry.getValue() instanceof JmeCloneable ||
       entry.getValue().getClass().isArray()){
        
        clone.put(cloner.clone(entry.getKey()), cloner.clone(entry.getValue()));
        
    }else{
    if(f != null ||
       entry.getKey() instanceof Cloneable || 
       entry.getKey() instanceof JmeCloneable ||
       entry.getKey().getClass().isArray() ){
        
        clone.put(cloner.clone(entry.getKey()), entry.getValue());
        
    }else{
    if(f2 != null ||
       entry.getValue() instanceof Cloneable || 
       entry.getValue() instanceof JmeCloneable ||
       entry.getValue().getClass().isArray()){
        
        clone.put(entry.getKey(), cloner.clone(entry.getValue()));
        
    }else{
        clone.put(entry.getKey(), entry.getValue());
    }
    }
    }
}

}

On the ElementId class i implemented Cloneable and ad this method

@Override
public ElementId clone() {
    return new ElementId(this.getId());
}

On the GuiControl class i override the cloneFields method like this

@Override
public void cloneFields( Cloner cloner, Object original ) { 
    super.cloneFields(cloner, original);
    this.listeners = cloner.clone(listeners);
    this.focusListeners = cloner.clone(focusListeners);
    this.updateListeners = cloner.clone(updateListeners);
    this.preferredSizeOverride = cloner.clone(preferredSizeOverride);
    this.lastSize = cloner.clone(lastSize);
    this.componentStack = cloner.clone(componentStack);
    this.componentStack.attach(this);
    this.layout = cloner.clone(layout);
    this.setLayout(this.layout);
}

Seens im cloning the ComponentStack class i implemented JmeCloneable and add this methods

@Override
public Object jmeClone() {
    try {
        return super.clone();
    } catch( CloneNotSupportedException e ) {
        throw new RuntimeException( "Can't clone ComponentStack ", e );
    }
}     

@Override
public void cloneFields( Cloner cloner, Object original ) { 
    this.components = cloner.clone(components);
    this.topList = cloner.clone(topList);
    this.index = cloner.clone(index);
}

And on the GuiComponent interface i extended Cloneable

With this changes i can clone a Container object without the childrens, i hope if i keep doing this type of changes to all the GuiElements i could clone everything, but before i continue i whant to know if im doing it right or is there another way

I think that’s the right approach.

However, things like ElementId are immutable and don’t need to be cloned.

Also, if you implement a clone() method then it should return the type itself instead of just Object. It’s just nicer.

Where things get weird is the cloning of listeners. In general, listeners should not be cloned and there would need to be some way to have the clone reinitialized for its own internal listeners. There are aspects of this that aren’t straight forward with respect to listeners. Sometimes the caller will want them cloned… but quite often, I suspect not.

Since I don’t even understand the use-case for cloning a GUI element, I can’t really comment. I have never even close to needed such a thing and it feels like maybe what you are really trying to do has a better way that’s down a completely different path.

The thing is I whant to reuse some classes that I made but in those cases I use a simple Geometry, now I whant to change everything with
lemur Containers.
Look at this simple code that i would like to reuse without doing to much changes

		PhotoCollector foto = new PhotoCollector();
        foto.SetMatrixSize(3,4);
        foto.SetFile(File);
        foto.SetOriginalSpatial(Geometry);
		Node viewer = new Node();
		viewer.addControl(foto);
		this.guiNode.attachChild(viewer);

This PhotoCollector class diplays all the photos in a file, it uses a geometry as base of what im going to use to diplay the photos,
then i clone that geometry for all the photos. it has a matrix size, so any photo out of scope gets null so y dont have any memory issue .
This class is a combination of a AbstractControl and a listener module, when i add this control to a node it attachs all the geometrys to it and also to the
listener.
I know i have to change stuff to make it work with lemur, even stop using my listener implentation and use lemurs way.
Im thinking if i use a Container with a Label, i could make it work that the Container diplays the photo and the label the name of the foto.

Like in a list or in a grid?

Maybe have a look at ListBox of GridPanel with an appropriate cell adapter. Then you just give it an appropriate list of photos in the file (or a model that wraps your file… model in the model-view-controller sense, not the ‘mesh’ sense.)

My concern is with the problem of having to many photos, I use this on android and even reducing the size of the photos, theres always a limit to the fotos i can have, so i null everything out of scope. Going to loot at the ListBox and see if i can do this to

ListBox only creates labels for what is visible and then reuses them… it’s based on GridPanel which basically does the same thing.

So if you have 100 photos but only 5 are visible then there are only five photos, basically.