How do I notify a TreeModel that its underlying model has changed? - java

I am developing a simulation of the world, where the World is represented by:
public class World {
Turtle turtle;
// .. basic constructors, getters, setters
}
and a Turtle is represented by:
public class Turtle {
List<Turtle> turtles;
// .. basic constructors, getters, setters
}
where a Turtle stands on the backs of its turtles and the World rests on one, main Turtle (who may have turtles underneath it). I want to represent this, in Swing, as a JTree.
So I code up a JTree and a TreeModel:
JTree tree = new JTree(new WorldModel(world.getTurtle());
public class WorldModel implements TreeModel {
private Turtle = null;
public WorldModel(Turtle turtle) {
this.turtle = turtle;
}
#Override
int getChildCount(Object object) {
return ((Turtle) object).getNumTurtles();
}
#Override
Object getChild(Object parent, int index) {
return ((Turtle) object).getTurtle(index);
}
// etc., you get the overbearing point
}
This works splendidly up until the point at which I change the Turtle I initially passed in to the TreeModel's constructor:
world.getTurtle().removeAllTurtleChildren();
How do I update my JList to reflect this change? Do I have to create a new TreeModel and set it again?
(I would like to stay away from DefaultTreeModel, if possible.)

add api that supports modifying the structure to the model:
public class WorldModel implements TreeModel {
...
public void removeAllChildren(Turtle parent) {
if (parent.getChildCount() == 0) return;
Turtle[] children = parent.getChildren();
int[] locations = new int[children.length());
for(int loc = 0; loc < locations.length; loc++) {
locations[i] = i;
}
parent.removeAllChildren();
List<Turtle> path = new ArrayList<>();
while (parent != null) {
path.add(0, parent);
parent = parent.getParent();
}
TreeModelEvent event = new TreeModelEvent(this, path, locations, children);
// for each listener
listener.treeNodesRemoved(event);
}
}
As you see, it's quite some work - you might reconsider not using DefaultTreeModel :-)

Related

TheBennyBox Engine - Performance Friendly Instanced Static Meshes Like In UE4

I'm using the java game engine made by thebennybox to create my own engine. Previously I worked with UE4 where you could have Static Mesh Actors, in his engine this would probably be a GameObject with a MeshRenderer component attached to it:
GameObject staticMeshActor_Grass = new GameObject();
AddObject(staticMeshActor_Grass.addComponent(new MeshRenderer(
new Mesh("plane.obj"), new Material(
new Texture("T_Grass_BC.png"), 1, 8,
new Texture("T_Grass_N.png")))));
Now I'm trying to create a large field of these grass tiles but since these are all their individual GameObjects (or Actors in UE4 terms) after 16x16 tiles it starts to cripple the frame rate!
I believe I can solve this problem if I make instances of multiple static meshes inside one GameObject in order to make it performance friendly. In UE4 this feature is called 'Instanced Static Mesh Component' which allows for that and I would like to have something similar.
Here's the GameObject class made by thebennybox:
public class GameObject
{
private ArrayList<GameObject> m_children;
private ArrayList<GameComponent> m_components;
private Transform m_transform;
private CoreEngine m_engine;
public GameObject()
{
m_children = new ArrayList<GameObject>();
m_components = new ArrayList<GameComponent>();
m_transform = new Transform();
m_engine = null;
}
public GameObject AddChild(GameObject child)
{
m_children.add(child);
child.SetEngine(m_engine);
child.GetTransform().SetParent(m_transform);
return this;
}
public GameObject AddComponent(GameComponent component)
{
m_components.add(component);
component.SetParent(this);
return this;
}
public void InputAll(float delta)
{
Input(delta);
for(GameObject child : m_children)
child.InputAll(delta);
}
public void UpdateAll(float delta)
{
Update(delta);
for(GameObject child : m_children)
child.UpdateAll(delta);
}
public void RenderAll(Shader shader, RenderingEngine renderingEngine)
{
Render(shader, renderingEngine);
for(GameObject child : m_children)
child.RenderAll(shader, renderingEngine);
}
public void Input(float delta)
{
m_transform.Update();
for(GameComponent component : m_components)
component.Input(delta);
}
public void Update(float delta)
{
for(GameComponent component : m_components)
component.Update(delta);
}
public void Render(Shader shader, RenderingEngine renderingEngine)
{
for(GameComponent component : m_components)
component.Render(shader, renderingEngine);
}
public ArrayList<GameObject> GetAllAttached()
{
ArrayList<GameObject> result = new ArrayList<GameObject>();
for(GameObject child : m_children)
result.addAll(child.GetAllAttached());
result.add(this);
return result;
}
public Transform GetTransform()
{
return m_transform;
}
public void SetEngine(CoreEngine engine)
{
if(this.m_engine != engine)
{
this.m_engine = engine;
for(GameComponent component : m_components)
component.AddToEngine(engine);
for(GameObject child : m_children)
child.SetEngine(engine);
}
}
}
The AddObject() method shown in the first part comes from an abstract class and eventually calls the method AddChild(GameObject child) inside the GameObject class. As you can see we have an ArrayList of GameObjects and GameComponents. Now I would have to implement some method in order to support the creation of multiple mesh instances inside one GameObject.
I've already tried to add multiple MeshRenderer components to one GameObject but this didn't work out. Maybe someone has a suggestion how I might go about this?
if you're really interested and want to help me you can download the engine here: https://github.com/BennyQBD/3DGameEngine
Just use 'Open Projects From File System..' in eclipse, choose the directory of this folder and once you've done that you should be able to run the program.
Thanks a lot for your help!
this might be what you are looking for:
http://www.java-gaming.org/topics/issues-with-batching-meshes/37809/view.html
They mention that ThinMatrix has created a tutorial about instancing meshes, hope this helps you!

Hierarchy of Transformations, recursive function troubles

I began implementing a hierarchical scene graph in my Java/OpenGL project with basic entities/objects that have a position component, and that know both their parent entity and their child entity(s).
public class Entity
{
private Entity parent;
private ArrayList<Entity> children = new ArrayList<Entity>();
private Vector3f pos = new Vector3f(0,0,0); //simplified transformation
addChild(Entity child){...}
setParent(Entity parent){...}
public Vector3f getPos(){ return pos; }
public void setPos(Vector3f pos){this.pos = pos}
//this is my non-functional attempt at creating hierarchical movement
public void setRelativePos(Vector3f pos)
{
this.setPos(parent.getPos().add(pos)); //position relative to parent
for(Entity child : children)
{
//how the child relatives to the newly moved parent
vec3 relativePos = child.getPos().sub(getPos());
child.setRelativePos(relativePos);
}
}
}
What I had in mind was that children will move relative to their parent when the parent's position is changed/set.
From what you are describing, I think that when you move an Entity, you want to move all its children.
Your recursion has no stopping condition, and probably causes a stack overflow.
It also has a confusing name, setPos, which implies a simple setter, but that's not what it does. You should have different methods for each type of movevent. Ex : translate, rotate, etc...
You should do something like this :
// simple setter
public void setPos(Vector3f pos)
{
this.pos = pos;
}
// translation movement
public void translate(Vector3f delta)
{
// translate the current Entity
setPos (getPos().add(delta));
// translate the children
for (Entity child : children)
child.translate (delta);
}

How to disable expand sign in Swing JTree?

I'm working in Swing and I would like to disable the expand (plus [+]) sign on a certain type of nodes.
Not sure how to do it because my nodes aren't leaves and I also cannot use setShowsRootHandles (which is only for the root).
I'm referring to to JTree: suppose i got this structure:
Root
--[+] node1
--[+] node2
when I load this structure i would like not to see the [+] sign on node2 (because it a special type node). But I also would like to expand it by using a special command.
I've overridden isLeaf() (method from DefaultMutableTreeNode) so it would set to to TRUE when i'm in the special type node, but then when I'm trying to expand it, it wouldn't expand because isLeaf() == TRUE...
Hope this will make things more clear.
While it is not possible to remove the handles, it is possible to restrict the expansion of nodes. The way to go is a TreeWillExpandListener combined with a custom treeNode that has state to restrict expansion:
the custom node below has an expandable property that's false by default
when detecting custom nodes, the listener allows/vetoes expansion based on that expandable property
for programmatic expansion, the expandable property is set to true temporarily to pass the listener
Example code:
// mixed tree of normal/restricted noded
DefaultMutableTreeNode root = new DefaultMutableTreeNode("root");
DefaultMutableTreeNode normalSubTree = new DefaultMutableTreeNode("normal");
normalSubTree.add(new DefaultMutableTreeNode("normalChild"));
MyNode restrictedSubTree = new MyNode("restrictedSubtree");
restrictedSubTree.add(new DefaultMutableTreeNode("restrictedChild"));
root.add(normalSubTree);
root.add(restrictedSubTree);
final JTree tree = new JTree(root);
// the listener which vetos expansion of MyNodes that are not expandable
TreeWillExpandListener l = new TreeWillExpandListener() {
#Override
public void treeWillExpand(TreeExpansionEvent event)
throws ExpandVetoException {
TreePath path = event.getPath();
if (path.getLastPathComponent() instanceof MyNode) {
if (!((MyNode) path.getLastPathComponent()).isExpandable()) {
throw new ExpandVetoException(event, "node not expandable");
}
}
}
#Override
public void treeWillCollapse(TreeExpansionEvent event)
throws ExpandVetoException {
}
};
tree.addTreeWillExpandListener(l);
Action expand = new AbstractAction("Expand") {
#Override
public void actionPerformed(ActionEvent e) {
TreePath selected = tree.getSelectionPath();
if (selected == null) return;
if (selected.getLastPathComponent() instanceof MyNode) {
MyNode last = (MyNode) selected.getLastPathComponent();
boolean old = last.isExpandable();
last.setExpandable(true);
tree.expandPath(selected);
last.setExpandable(old);
}
}
};
JXFrame frame = wrapWithScrollingInFrame(tree, "veto expand");
addAction(frame, expand);
show(frame);
}
// custom node which has an expandable property
public static class MyNode extends DefaultMutableTreeNode {
private boolean expandable;
public MyNode() {
this(null);
}
public MyNode(Object userObject) {
super(userObject);
}
public void setExpandable(boolean expandable) {
this.expandable = expandable;
}
public boolean isExpandable() {
return expandable;
}
}
It's possible to remove the handles - despite what others have mentioned.
I've attached a snippet on how to do this below. The key thing is to override shouldPaintExpandControl in BasicTreeUI.
jtree.setUI(new BasicTreeUI() {
#Override
protected boolean shouldPaintExpandControl(final TreePath path, final int row
, final boolean isExpanded, final boolean hasBeenExpanded, final boolean isLeaf)
{
boolean shouldDisplayExpandControl = false;
return shouldDisplayExpandControl;
}
This should really be documented in the JTree API but that's another issue.
Another approach to consider:
If you call DefaultTreeModel(TreeNode root, boolean asksAllowsChildren) the model will "ask" the nodes you insert if they are allowed to have children. If they cannot, it should not display the expand icon.
Be sure to override javax.swing.tree.TreeNode.getAllowsChildren() in your class.

GWT Editors - how to add N sub-editors of the same type based on a Collection

I have an object, Supply, that can either be an ElecSupply or GasSupply (see related question).
Regardless of which subclass is being edited, they all have a list of BillingPeriods.
I now need to instantiate N number of BillingPeriodEditors based on the contents of that list, and am pretty baffled as to how I should do it.
I am using GWTP. Here is the code of the SupplyEditor I have just got working:
public class SupplyEditor extends Composite implements ValueAwareEditor<Supply>
{
private static SupplyEditorUiBinder uiBinder = GWT.create(SupplyEditorUiBinder.class);
interface SupplyEditorUiBinder extends UiBinder<Widget, SupplyEditor>
{
}
#Ignore
final ElecSupplyEditor elecSupplyEditor = new ElecSupplyEditor();
#Path("")
final AbstractSubTypeEditor<Supply, ElecSupply, ElecSupplyEditor> elecSupplyEditorWrapper = new AbstractSubTypeEditor<Supply, ElecSupply, ElecSupplyEditor>(
elecSupplyEditor)
{
#Override
public void setValue(final Supply value)
{
setValue(value, value instanceof ElecSupply);
if(!(value instanceof ElecSupply))
{
showGasFields();
}
else
{
showElecFields();
}
}
};
#Ignore
final GasSupplyEditor gasSupplyEditor = new GasSupplyEditor();
#Path("")
final AbstractSubTypeEditor<Supply, GasSupply, GasSupplyEditor> gasSupplyEditorWrapper = new AbstractSubTypeEditor<Supply, GasSupply, GasSupplyEditor>(
gasSupplyEditor)
{
#Override
public void setValue(final Supply value)
{
setValue(value, value instanceof GasSupply);
if(!(value instanceof GasSupply))
{
showElecFields();
}
else
{
showGasFields();
}
}
};
#UiField
Panel elecPanel, gasPanel, unitSection;
public SupplyEditor()
{
initWidget(uiBinder.createAndBindUi(this));
gasPanel.add(gasSupplyEditor);
elecPanel.add(elecSupplyEditor);
}
// functions to show and hide depending on which type...
#Override
public void setValue(Supply value)
{
if(value instanceof ElecSupply)
{
showElecFields();
}
else if(value instanceof GasSupply)
{
showGasFields();
}
else
{
showNeither();
}
}
}
Now, as the list of BillingPeriods is a part of any Supply, I presume the logic for this should be in the SupplyEditor.
I got some really good help on the thread How to access PresenterWidget fields when added dynamically, but that was before I had implemented the Editor Framework at all, so I think the logic is in the wrong places.
Any help greatly appreciated. I can post more code (Presenter and View) but I didn't want to make it too hard to read and all they do is get the Supply from the datastore and call edit() on the View.
I have had a look at some examples of ListEditor but I don't really get it!
You need a ListEditor
It depends of how you want to present them in your actual view, but the same idea apply:
public class BillingPeriodListEditor implements isEditor<ListEditor<BillingPeriod,BillingPeriodEditor>>, HasRequestContext{
private class BillingPeriodEditorSource extends EditorSource<BillingPeriodEditor>{
#Override
public EmailsItemEditor create(final int index) {
// called each time u add or retrive new object on the list
// of the #ManyToOne or #ManyToMany
}
#Override
public void dispose(EmailsItemEditor subEditor) {
// called each time you remove the object from the list
}
#Override
public void setIndex(EmailsItemEditor editor, int index) {
// i would suggest track the index of the subeditor.
}
}
private ListEditor<BillingPeriod, BillingPeriodEditor> listEditor = ListEditor.of(new BillingPeriodEditorSource ());
// on add new one ...
// apply or request factory
// you must implement the HasRequestContext to
// call the create.(Proxy.class)
public void createNewBillingPeriod(){
// create a new one then add to the list
listEditor.getList().add(...)
}
}
public class BillingPeriodEditor implements Editor<BillingPeriod>{
// edit you BillingPeriod object
}
Then in you actual editor edit as is in the path Example getBillingPeriods();
BillingPeriodListEditor billingPeriods = new BillingPeriodListEditor ();
// latter on the clickhandler
billingPeriods.createNewBillingPeriod()
You are done now.

JTree update nodes without collapsing

I have a Java SE 7 application that needs to have the JTree nodes updated. From the tutorial given by Oracle using this thread, there's no given hint on how I could update the label (displayed text of the node on the Tree) on code. Currently I am using DefaultTreeModel as the model of my JTree and DefaultMutableTreeNode as the nodes of the said Tree.
To further detail about the application I am working on, I am developing a chat facility having the contact(s) displayed with their availability status (whether Online, Offline, etc.) per account.
The question is, how can I update the displayed text of a particular node without (at most) removing it from it's parent and adding it on it's designated index. Like a DefaultMutableTreeNode.setText("<new label>")?
UPDATE : January 20, 2013
Redefined the question for clarifications.
Perhaps if you use 'nodeChanged()' instead of 'reload()' you will get the effect you desire.
There are a bunch of methods on the DefaultTreeModel class that cause various parts of the tree to be changed and redrawn. There are also other methods on DefaultTreeModel that only cause redrawing to take place.
You mentioned 'reload(node)' and commented that it causes the tree to collapse when you call it. 'reload' causes the entire sub-tree to be completely redrawn starting at that node. (But if that node isn't visible, it changes nothing.) That is called a 'structure change'.
'insertNodeInto()' and 'removeNodeFromParent()' modify the tree structure by adding or removing the node and then redrawing.
I think 'nodeChanged()' is the one you need since it just notifies the model that something changed in the node that will cause it to display differently. Perhaps the displayable text is now different than it was. Perhaps you changed the user object in the node. That's when you call 'nodeChanged()' on a node.
You should try 'nodeChanged()' in place of the 'reload()' call in your own code that was collapsing and in the example program vels4j provided. This might take care of the problem.
Note that there are also two other families of methods on the DefaultTreeModel that are used in other cases:
These methods work with the tree nodes and use the tree path to define where the change took place. They do not change the data structures underlying the tree but notify the model that something changed so it can notify the listeners that actually redraw things or otherwise respond to changes.
nodesWereInserted()
nodesWereRemovde()
nodesChanged()
nodeStructureChanged()
There are also a set of fire...() methods that are used internally to the DefaultTreeModel and any sub-classes you may create. They merely notify any listeners that something changed. Notice that they are protected.
May this simple and executable program help you to resolve your issue.
public class JTreeDemo extends JPanel
implements Runnable {
private JTree tree;
private DefaultTreeModel treeModel ;
private Random rnd = new Random();
private List<User> userList;
public JTreeDemo() {
super( );
//Create the nodes.
DefaultMutableTreeNode top =
new DefaultMutableTreeNode("Users");
treeModel = new DefaultTreeModel(top);
createNodes(top);
//Create a tree that allows one selection at a time.
tree = new JTree(treeModel);
tree.getSelectionModel().setSelectionMode
(TreeSelectionModel.SINGLE_TREE_SELECTION);
//Create the scroll pane and add the tree to it.
JScrollPane treeView = new JScrollPane(tree);
//Add the split pane to this panel.
add(treeView);
}
public String getRandomStatus() {
int nextInt = rnd.nextInt(100);
if( nextInt%2==0) {
return "Online";
} else {
return "Offline";
}
}
#Override
public void run() {
while(true) {
try {
Thread.sleep(1000);
int nextInt = rnd.nextInt(10);
User user = userList.get(nextInt);
user.setStatus(getRandomStatus());
treeModel.nodeChanged(user);
} catch (InterruptedException ex) {
// handle it if necessary
}
}
}
private class User extends DefaultMutableTreeNode {
public String userName;
public String status;
public User(String name) {
userName = name;
}
public void setStatus(String status) {
this.status = status;
}
public String getStatus() {
return status;
}
#Override
public String toString() {
String color = status.equals("Online") ? "Green" : "Red";
return "<html><b color='"+color+"'>"+
userName +"-"+status +"</b></html>";
}
}
private void createNodes(DefaultMutableTreeNode top) {
userList = new ArrayList() ;
for(int i=0;i<10;i++) {
User u1 = new User("User " + (i+1));
u1.setStatus("Online");
top.add(u1);
userList.add(u1);
}
}
private static void createAndShowGUI() {
JFrame frame = new JFrame("TreeDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Add content to the window.
JTreeDemo jTreeDemo = new JTreeDemo();
frame.add(jTreeDemo);
frame.pack();
frame.setVisible(true);
// update status randomly
Thread thread = new Thread(jTreeDemo);
thread.start();
}
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
I've added a Thread to update Status randomly, hope you can modify base on your need.
Output :
Edit:
1. Based on suggestion I've removed reload(node) and added tree model reload.
It's easy if nodes contains objects which are unique in the tree and have implemented method equals and hashCode (for example you show strings or object with unique ID from database). First of all you iterate over all expanded nodes and save objects from the nodes in a set. Then you perform update of the model. After update you iterate over all nodes and if they are in the set you expand the node in the tree.
If nodes are not unique - you need to save in the set the complete tree path (for example as list) and check it after update to expand the nodes.
If objects has neither equals nor hashCode (both these methods must be implemented) - this variant cannot be used.
Just for the record (I voted for Lee Meador), DefaultTreeModel#nodeChanged(javax.swing.tree.TreeNode) is the way to go:
public class TestFrame extends JFrame {
public TestFrame() {
//create gui with simple jtree (and DefaultTreeModel)
JButton changeBtn = new JButton();
final JTree jTree = new JTree();
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
changeBtn.setText("update selected node");
getContentPane().add(changeBtn, java.awt.BorderLayout.PAGE_END);
DefaultMutableTreeNode treeNode1 = new DefaultMutableTreeNode("root");
DefaultMutableTreeNode treeNode2 = new DefaultMutableTreeNode("blue");
treeNode1.add(treeNode2);
treeNode2 = new DefaultMutableTreeNode("violet");
DefaultMutableTreeNode treeNode3 = new DefaultMutableTreeNode("red");
treeNode2.add(treeNode3);
treeNode3 = new DefaultMutableTreeNode("yellow");
treeNode2.add(treeNode3);
treeNode1.add(treeNode2);
jTree.setModel(new DefaultTreeModel(treeNode1));
getContentPane().add(jTree, BorderLayout.CENTER);
pack();
//add listener to button, to change selected node on button click
changeBtn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
DefaultMutableTreeNode dmt = (DefaultMutableTreeNode)jTree.getSelectionPath().getLastPathComponent();
//update content/representation of selected node
dmt.setUserObject("My update: " + new Date());
//nodeChanged
((DefaultTreeModel) jTree.getModel()).nodeChanged(dmt);
}
});
}
public static void main(String args[]) {
EventQueue.invokeLater(new Runnable() {
public void run() {
new TestFrame().setVisible(true);
}
});
}
}

Categories