In NetBeans RCP with Two TopComponent Instances, Second Tab Does Not Repaint

I’m working on a NetBeans Platform Application with jME integrated (as discussed in this thread here), and I’ve run into a problem getting my SimpleApplication to repaint properly on the second of two TopComponent instances.

So I want multiple instances of the same TopComponent, each with a separate jME3 viewer. To accomplish this, I’m using the AwtPanel and AwtPanelsContext in my SimpleApplication:

[java]import com.jme3.app.SimpleApplication;
import com.jme3.system.AppSettings;
import com.jme3.system.awt.AwtPanel;
import com.jme3.system.awt.AwtPanelsContext;
import com.jme3.system.awt.PaintMode;
import java.awt.Color;
import java.awt.Dimension;

public class TestCanvas extends SimpleApplication {

private AwtPanelsContext context;
public AwtPanel canvas1;
public AwtPanel canvas2;


@Override
public void createCanvas(){
    AppSettings settings = new AppSettings(true);
    settings.setWidth(640);
    settings.setHeight(480);
    settings.setCustomRenderer(AwtPanelsContext.class);
    settings.setFrameRate(60);

    setSettings(settings);
    setShowSettings(false);
}

@Override
public void simpleInitApp()
{
    flyCam.setEnabled(false);     
    
}

public void setupInPanel()
{
    context = (AwtPanelsContext) this.getContext();

    canvas1 = context.createPanel(PaintMode.Accelerated);
    canvas1.setPreferredSize(new Dimension(400, 300));
    canvas1.setBackground(Color.red);

    canvas2 = context.createPanel(PaintMode.Accelerated);
    canvas2.setPreferredSize(new Dimension(400, 300));
    canvas2.setBackground(Color.blue);
}

@Override
public void simpleUpdate(float tpf)
{
    
}

}[/java]

Then, in my TopComponent, I initialize TestCanvas. Each instance of this TopComponent then references the same TestCanvas instance through a static variable:

[java]import java.awt.BorderLayout;
import javax.swing.JButton;
import org.netbeans.api.settings.ConvertAsProperties;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;
import org.openide.windows.TopComponent;
import org.openide.util.NbBundle.Messages;

/**

  • Top component which displays something.
    /
    @ConvertAsProperties(
    dtd = “-//awtpanels2//AwtPanels2GUI//EN”,
    autostore = false)
    @TopComponent.Description(
    preferredID = “AwtPanels2GUITopComponent”,
    //iconBase=“SET/PATH/TO/ICON/HERE”,
    persistenceType = TopComponent.PERSISTENCE_NEVER)
    @TopComponent.Registration(mode = “editor”, openAtStartup = true)
    @ActionID(category = “Window”, id = “awtpanels2.AwtPanels2GUITopComponent”)
    @ActionReference(path = “Menu/Window” /
    , position = 333 /)
    @TopComponent.OpenActionRegistration(
    displayName = “#CTL_AwtPanels2GUIAction” /
    ,
    preferredID = “AwtPanels2GUITopComponent” */ )
    @Messages({
    “CTL_AwtPanels2GUIAction=AwtPanels2GUI”,
    “CTL_AwtPanels2GUITopComponent=AwtPanels2GUI Window”,
    “HINT_AwtPanels2GUITopComponent=This is a AwtPanels2GUI window”
    })
    public final class AwtPanels2GUITopComponent extends TopComponent {

    public static TestCanvas app;
    //public static Frame wholeFrame;

    public AwtPanels2GUITopComponent() {
    initComponents();
    setName(Bundle.CTL_AwtPanels2GUITopComponent());
    setToolTipText(Bundle.HINT_AwtPanels2GUITopComponent());

     if(app == null)
     {
         app = new TestCanvas();
         app.createCanvas();
         app.start();
         app.setupInPanel();
         app.canvas1.setBounds(0, 0, 640, 480);
         app.canvas2.setBounds(0, 0, 640, 480);
         jPanel1.add(app.canvas1, BorderLayout.CENTER);
     }
     else
     {
         jPanel1.add(app.canvas2, BorderLayout.CENTER);
         
         JButton newBut = new JButton("Garfield");
         jPanel1.add(newBut, BorderLayout.CENTER);
         newBut.setBounds(650,0,100,20);
     }
    
     jPanel1.revalidate();
     jPanel1.repaint();
     this.setVisible(true);
    

    }

    @Override
    public void componentActivated() {
    super.componentActivated();
    jPanel1.repaint();
    }

    }[/java]

At the moment, my NetBeans application opens with just one instance of TopComponent opened, so I wrote a menu Action to create a second instance:

[java]import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;
import org.openide.awt.ActionRegistration;
import org.openide.util.NbBundle.Messages;

@ActionID(
category = “Tools”,
id = “awtpanels2.NewTab”)
@ActionRegistration(
displayName = “#CTL_NewTab”)
@ActionReference(path = “Menu/Tools”, position = -100)
@Messages(“CTL_NewTab=New Tab”)
public final class NewTab implements ActionListener {

@Override
public void actionPerformed(ActionEvent e) {
    AwtPanels2GUITopComponent newtab = new AwtPanels2GUITopComponent();
    newtab.open();
}

}[/java]

So when I start my application and then create a second instance of my TopComponent, everything seems to be working fine at first. My TestCanvas has two AwtPanels (one red, one blue), and I get one on each tab, as expected:


However, if I click back to the first tab then back to the second tab, my canvas has disappeared:

This is only a problem with the second tab, never the first. Even if I switch the AwtPanels in my code so that the blue one appears on my first tab and the red on my second, whatever AwtPanel is on the second tab will disappear when I click away. The first tab continues to display normally.

Note too that only the AwtPanel disappears, not the other GUI elements on the TopComponent (such as the “Garfield” test button).

Interestingly, if I’m on the second tab with the AwtPanel missing, and I then perform some other action such as clicking in the pull-down menu, the AwtPanel suddenly returns:

I suspect that there is a problem with the way the second AwtPanel is being repainted. This is why I call repaint in componentActivated() in my TopComponent on the JPanel holding my AwtPanel, but it doesn’t seem to work.

I know my implementation is brittle, but this is just a proof-of-concept to see if I can integrate jME3 successfully into a NetBeans Platform Application. I’m completely stumped at this point and would appreciate any assistance anybody could provide.

A (somewhat) educated guess is that you are now struggling with mixing AWT components and lightweight Swing components.
http://www.oracle.com/technetwork/articles/java/mixing-components-433992.html
http://docs.oracle.com/javase/7/docs/webnotes/tsg/TSG-Desktop/html/awt.html
http://wiki.netbeans.org/DevFaqMixingLightweightHeavyweight

Hope that helps.

Thank you very much! After perusing your links for a bit, this seems to have solved the problem:

[java] @Override
public void componentActivated() {
super.componentActivated();
Window window = SwingUtilities.getWindowAncestor(jPanel1);
if (window != null) window.revalidate();
}[/java]

Here’s what I suspect was happening (and please feel free to correct me if I’m wrong):

In jME3, the AwtPanel class extends the Canvas class. Canvas (and thus AwtPanel) is a heavyweight AWT component, while the rest of the NetBeans Application GUI is made up of lightweight Swing components. This means we’re mixing heavyweight and lightweight components. Per definition for Swing components, whenever something in the GUI changes (such as switching tabs), parts of the component hierarchy become invalid, requiring a revalidation. Normally, for something like a tab switch, this revalidation occurs automatically, and we only have to call revalidate() explicitly under certain circumstances (such as adding the AwtPanel at runtime, as I do in my TopComponent).

However, thanks to Bug 6852592 in the JDK, this automatic revalidation does not work with mixed components, because the heavyweight components require more things in the hierarchy to be revalidated than what the automatic revalidation covers. Thus, to ensure everything that needs to be revalidated IS revalidated, we call the validate() or revalidate() method on the top-level component of our GUI (in this case, the NetBeans application window).

My second AwtPanel was not redisplaying because it became invalid after I switched away from it, and it was not being revalidated (or, at least, not revalidated properly). However, if I then performed some top-level GUI change (such as activating a pull-down menu or resizing the window), then the automatic revalidation process revalidated the entire hierarchy starting at the application window level, since this was the level at which the GUI change was made.

Does this sound accurate?

Also, for future reference, is there a way to monitor the components of a component hierarchy and determine their state of validity at any given point? I think a way to visualize this would be a boon to debugging efforts, since this is a good place for obscure display errors.

Point of interest: The “resolution” to Bug 6852592 appears to be revalidating at the top-level component, as just described. Even the Javadoc for java.awt.Component reinforces this. However, the bug description suggests that the revalidate() method for any Swing (aka lightweight) component should be smarter, detecting if it has a heavyweight component as a subcomponent and revalidating the entire component hierarchy if this is the case. This seems like an obvious solution, but I don’t see any indication that Oracle will implement it this way. Any thoughts as to why?

You cannot create multiple applications in one JVM with the current renderer, thats why the SDK has one main OpenGL window. You can use one application and display PreViews in the AWT panels. Look at the jme3test.awt.TestAwtPanels code, it does exactly that, using the GuiViewPort as second View.

@normen said: You cannot create multiple applications in one JVM with the current renderer, thats why the SDK has one main OpenGL window. You can use one application and display PreViews in the AWT panels. Look at the jme3test.awt.TestAwtPanels code, it does exactly that, using the GuiViewPort as second View.

I’m not looking to create multiple applications. I have one SimpleApplication with two AwtPanels, and I’m trying to put in one per TopComponent.

Having solved the original problem, I’m trying to get a different shape to show up in each AwtPanel, which is proving to be very challenging. I’ve done this before by using different viewports, but it looks like I can only get one viewport to render. Here’s my revised TestCanvas:

[java]import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.shape.Box;
import com.jme3.system.AppSettings;
import com.jme3.system.awt.AwtPanel;
import com.jme3.system.awt.AwtPanelsContext;
import com.jme3.system.awt.PaintMode;
import java.awt.Color;
import java.awt.Dimension;
import javax.swing.SwingUtilities;

public class TestCanvas extends SimpleApplication {

private AwtPanelsContext context;
public AwtPanel canvas1;
public AwtPanel canvas2;
public ViewPort view1;
public ViewPort view2;

@Override
public void createCanvas(){
    AppSettings settings = new AppSettings(true);
    settings.setWidth(640);
    settings.setHeight(480);
    settings.setCustomRenderer(AwtPanelsContext.class);
    settings.setFrameRate(60);

    setSettings(settings);
    setShowSettings(false);
}

@Override
public void simpleInitApp()
{
    flyCam.setEnabled(false);     
    
    Box b = new Box(Vector3f.ZERO, 1, 1, 1);
    Geometry geom = new Geometry("Box", b);

    Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
    mat.setColor("Color", ColorRGBA.Blue);
    geom.setMaterial(mat);

    Box box2 = new Box(1,1,1);      
    Geometry red = new Geometry("Box", box2);
    red.setLocalTranslation(new Vector3f(1,3,1));
    Material mat2 = new Material(assetManager, 
            "Common/MatDefs/Misc/Unshaded.j3md");
    mat2.setColor("Color", ColorRGBA.Red);
    red.setMaterial(mat2);
    
    Node node1 = new Node("node1");
    Node node2 = new Node("node2");
    rootNode.attachChild(node1);
    rootNode.attachChild(node2);
    
    node1.attachChild(geom);
    node2.attachChild(red);

    Camera cam1 = cam.clone();
    Camera cam2 = cam.clone();
    cam1.setViewPort( 0.0f , 1.0f   ,   0.0f , 1.0f );
    cam2.setViewPort( 0.0f , 1.0f   ,   0.0f , 1.0f );
    
    view1 = renderManager.createMainView("View of camera #1", cam1);
    view2 = renderManager.createMainView("View of Camera #2", cam2);
    
    view1.attachScene(node1);
    view1.setClearColor(true);
    view1.setBackgroundColor(ColorRGBA.Green);
    view2.attachScene(node2);
    view2.setClearColor(true);
    view2.setBackgroundColor(ColorRGBA.Yellow);
    
}

public void setupInPanel()
{
    context = (AwtPanelsContext) getContext();

    canvas1 = context.createPanel(PaintMode.Accelerated);
    canvas1.setPreferredSize(new Dimension(400, 300));
    canvas1.setBackground(Color.red);
    canvas1.attachTo(true, view1);      
    
    canvas2 = context.createPanel(PaintMode.Accelerated);
    canvas2.setPreferredSize(new Dimension(400, 300));
    canvas2.setBackground(Color.blue);
    canvas2.attachTo(true, view2);
}

@Override
public void simpleUpdate(float tpf)
{
    
}

}[/java]

The problem, of course, is that if you look at canvas1 and canvas2 in setupInPanel(), I set the first parameter to true for both in attachTo(), where I attach each viewport to its respective AwtPanel. By looking at the AwtPanel source code, it looks like this first parameter determines whether the AwtPanel should be rendered or not. It also looks like AwtPanel can only render one view, so if I set both to true, the second AwtPanel overrides the first in the rendering thread.

Is there no good workaround for this? Is it impossible to have one rendering AwtPanel per TopComponent? Please let me know.