How to know input event handler owner in Piccolo2D? - java

I see no getSource() method in PInputEvent class.
I want to get reference to an object, which handler was added to.
UPDATE
In the following example, three concentric circles created. Each bigger circle owns smaller one. Mouse click handler is attached to circle2. Since this circle owns circle3, handler triggers when clicked both circle2 and circle3, but not circle1.
How to obtain reference to circle2 from within handler if click was made to circle3?
Neither of tested methods help.
package test.piccolo;
import edu.umd.cs.piccolo.event.PBasicInputEventHandler;
import edu.umd.cs.piccolo.event.PInputEvent;
import edu.umd.cs.piccolo.nodes.PPath;
import edu.umd.cs.piccolox.PFrame;
public class App {
#SuppressWarnings("serial")
public static void main(String[] args) {
new PFrame() {
public void initialize() {
PPath circle1 = PPath.createEllipse(-50, -50, 100, 100);
circle1.setPaint(null);
System.out.println("circle1 = " + circle1.toString());
getCanvas().getLayer().addChild(circle1);
PPath circle2 = PPath.createEllipse(-40, -40, 80, 80);
circle2.setPaint(null);
System.out.println("circle2 = " + circle2.toString());
circle1.addChild(circle2);
PPath circle3 = PPath.createEllipse(-30, -30, 60, 60);
circle3.setPaint(null);
System.out.println("circle3 = " + circle3.toString());
circle2.addChild(circle3);
circle2.addInputEventListener(new PBasicInputEventHandler() {
#Override
public void mouseClicked(PInputEvent event) {
Object o;
o = event.getSourceSwingEvent().getSource().toString();
System.out.println("event.getSourceSwingEvent().getSource() = " + o);
o = event.getComponent().toString();
System.out.println("event.getComponent() = " + o);
o = event.getPickedNode().toString();
System.out.println("event.getPickedNode() = " + o);
}
});
};
};
}
}
UPDATE 2
My requirement is to treat some topmost dummy node as wrapper for it's children. I want to attach handler to a parent and track all events underneath. At the same moment, parent can be a child of some more complex scene as a whole. So, I need to catch event at some intermediate level.

PInputEvent.getSourceSwingEvent() returns the underlying swing event - InputEvent, from that getSource() should give you the source, ie:
event.getSourceSwingEvent().getSource()
EDIT:
It appears that event.getSourceSwingEvent().getSource() is always a canvas. It makes sense as the event actually originated on canvas.
event.getPickedNode() is the actual picked node. It may be a child of a node that have a registered listener, such as circle3 in the posted sample code.
I am not sure what is the scenario and the reason for finding a node that registered a listener. In most cases, the desired output is the picked node. To find what you're looking for you may have a custom designated extension of PInputEventListener that can hold a reference to a node that registers it. It may be an overkill, depending on a situation. For example:
public static class MyEventHandler extends PBasicInputEventHandler {
private PNode node;
public MyEventHandler(PNode node) {
this.node = node;
}
public PNode getNode() {
return this.node;
}
}
Another hacky way that comes to mind is to traverse either a stack of objects in a pick path or an hierarchy of nodes to find a node that has this listener. For example to traverse the hierarchy of nodes (similarly it is also possible to enumerate nodes in event.getPath().getNodeStackReference()) :
public static PNode getRealEventSource(PInputEvent event, PInputEventListener listener) {
PNode node = event.getPickedNode();
while(node != null){
EventListenerList listeners = node.getListenerList();
if (listeners != null) {
if (Arrays.asList(listeners.getListenerList()).contains(listener)) {
return node;
}
}
node = node.getParent();
}
return null;
}
And then, invoke it like this from within the mouseClicked() method:
System.out.println("The real source:" + getRealEventSource(event, this));

Related

Select Node in TreeViewer

The lack of documentation for this really simple feature is disturbing. I have a TreeViewer and want to select a node. And the only way this makes sense is if the tree expands all elements up to the selection, else the user can't see it.
public class TreeWindow extends ApplicationWindow {
public static void main(String[] args) {
new TreeWindow().open();
}
public TreeWindow() {
super(null);
setBlockOnOpen(true);
}
#Override
protected Control createContents(Composite parent) {
final TreeViewer treeViewer = new TreeViewer(parent);
treeViewer.setContentProvider(new FileTreeContentProvider());
treeViewer.setLabelProvider(new LabelProvider() {
#Override
public String getText(Object element) {
String result = ((File) element).getName();
if (result.isEmpty()) {
result = ((File) element).getPath(); // root like C:\
}
return result;
}
});
treeViewer.setInput(File.listRoots());
// expand
final File fileToExpand = new File("src");
System.out.println("Expand to file: " + fileToExpand.getAbsolutePath());
return treeViewer.getControl();
}
static class FileTreeContentProvider extends ArrayContentProvider implements ITreeContentProvider {
#Override
public Object[] getChildren(Object parentElement) {
return ((File) parentElement).listFiles();
}
#Override
public Object getParent(Object element) {
return ((File) element).getParentFile();
}
#Override
public boolean hasChildren(Object element) {
return ((File) element).isDirectory();
}
}
}
What I tried:
treeViewer.setSelection(new StructuredSelection(fileToExpand));
System.out.println("Selection: " + treeViewer.getSelection());
The selection doesn't get set. (I saw multiple times that TreeViewer#setSelection(ISelection, boolean) was used, but the JavaDoc states "Currently the reveal parameter is not honored because Tree does not provide an API to only select an item without scrolling it into view").
treeViewer.expandToLevel(fileToExpand, AbstractTreeViewer.ALL_LEVELS);
This method... does nothing?
final Tree tree = treeViewer.getTree();
final TreeItem[] items = tree.getSelection();
for (int i = 0; i < items.length; ++i) {
final TreeItem item = items[i];
TreeItem treeParent = item.getParentItem();
while (treeParent != null) {
treeParent.setExpanded(true);
treeParent = treeParent.getParentItem();
}
}
Might work maybe? But the selection does not get set, so...
treeViewer.expandAll();
This method normally works, but I don't think it's a good idea to try it in the above example. It does not do what I want, so it's a moot point anyways.
The problem seems to be that the TreeItem is created lazily. To check that you can try this:
for (final TreeItem item : this.treeViewer.getTree().getItems()) {
System.out.println(item.getData() + " " + item.getItemCount());
}
This method outputs either 1 if the item has children or 0 if not, but not the actual item count. Also, if you try to get the children's data, it's null.
How do I select a node that is not expanded? How do I expand the tree to the selection / a specified node?
Possible duplicate:
How to expand a specific node in TreeViewer(org.eclipse.jface) (I'm not sure if this is the same problem, but there is no solution either way)
I was able to get this to work on JFace 3.13.2:
// element is any object of your tree content provider data model,
// in your case a File.
treeViewer.expandToLevel(element, 0);
treeViewer.setSelection(new StructuredSelection(element));
The above code will make all the nodes down to the level of the selected element expanded, scroll the relevant part of the tree into view and mark the element as selected.
In my case the tree was four levels deep and the selected element was a leaf node. If you wanted to expand levels below the selected node, you can provide a level higher than 0 as second parameter to expandToLevel, or TreeViewer.ALL_LEVELS to expand all levels in the subtree.

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.

Instantiating a sub-class of an abstract class

I'll get straight into it. Below is a code snippet from my GeneralSearch class I am implementing in an AI application for solving a word puzzle. I am wondering how to instantiate the subclass within the abstract class. I feel I have implemented all relevant abstract methods in my BreadthFirstSearch class as well as its constructor calling its super-constructor. (Nested subclass)
I understand you cant instantiate an abstract class but from previous threads and information you can instantiate a subclass with an abstract super-class.
I try the following calls to no avail:
GeneralSearch bfs = new BreadthFirstSearch(now.getState(), info); //Correct parameters
OR.
GeneralSearch bfs = GeneralSearch.BreadthFirstSearch(now.getState(), info);
Errors received:
1)BreadthFirstSearch cannot be resolved to a type
2)The method BreadthFirstSearch(WordState, WordNodeInfo) is undefined for the type GeneralSearch
I cant seem to get a working instantiation for the subclass so I can perform such searches. If anyone could shed some light on my confusion and understanding that would be great. (Code below)
public abstract class GeneralSearch {
NodeInfo nodeInfo;
ArrayList unvisited, visited;
public GeneralSearch (State startState, NodeInfo nodeInfo) {
this.nodeInfo = nodeInfo;
unvisited = new ArrayList();
unvisited.add(new Node(startState, new Actions()));
visited = new ArrayList();
}
public Node search() {
Actions moves;
Action move;
//Iterating through arrayList for unvisited and possible arcs
Node visit, successor;
if(unvisited.isEmpty()) return null;
while( !unvisited.isEmpty() ) {
visit = select();
if(nodeInfo.isGoal(visit)) return visit;
moves = visit.getState().getActions();
Iterator<Action> it = moves.iterator();
while(it.hasNext()) {
successor = (Node) visit.clone();
move = it.next();
successor.getState().update(move);
insert(successor);
}
visited.add(visit);
}
return null;
}
public abstract Node select ();
public abstract void insert (Node node);
public class BreadthFirstSearch extends GeneralSearch {
public BreadthFirstSearch(State startState, NodeInfo nodeInfo) {
super(startState, nodeInfo);
}
public Node select() {
return (Node) visited.get(0);
}
public void insert(Node node) {
unvisited.add(node);
}
}
}
Put the BreadthFirstSearch class in a separate file, outside of the GeneralSearch class.
If you want to keep it there, try instantiating GeneralSearch.BreadthFirstSearch instead of just BreadthFirstSearch, but I'm not even sure it's possible to have a subclass within it's own parent.
In your second try you're missing a new statement:
GeneralSearch bfs = new GeneralSearch.BreadthFirstSearch(now.getState(), info);

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);
}
});
}
}

Checking if JTree node exist using path

I have a classic JTree populated with some nods. Lets assume tree looks like this:
Root
|-Fruit
|--Apple
|--Orange
|-Objects
|--Table
|--Car
Is there any way in java to check if node exists using assumed path like this:
TreeNode found = model.getNodeOrNull("\\Fruit\\Apple")
so if node in given location exists it's returned, if not null is returned? Is there any such mechanism in Java?
You might experiment with something along these lines.
Example Output
food:pizza found true
od:pizza found false
sports:hockey found true
sports:hockey2 found false
TreeNodeLocation.java
import java.awt.BorderLayout;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.text.Position;
import javax.swing.tree.TreePath;
public class TreeNodeLocation {
private JTree tree = new JTree();
TreeNodeLocation() {
JPanel p = new JPanel(new BorderLayout(2,2));
final JTextField find = new JTextField("food:pizza");
find.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
boolean found = findText(find.getText());
System.out.println(find.getText() + " found " + found);
}
});
p.add(find, BorderLayout.PAGE_START);
tree.setVisibleRowCount(8);
for (int row=tree.getRowCount(); row>=0; row--) {
tree.expandRow(row);
}
p.add(new JScrollPane(tree),BorderLayout.CENTER);
JOptionPane.showMessageDialog(null, p);
}
public boolean findText(String nodes) {
String[] parts = nodes.split(":");
TreePath path = null;
for (String part : parts) {
int row = (path==null ? 0 : tree.getRowForPath(path));
path = tree.getNextMatch(part, row, Position.Bias.Forward);
if (path==null) {
return false;
}
}
tree.scrollPathToVisible(path);
tree.setSelectionPath(path);
return path!=null;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TreeNodeLocation();
}
});
}
}
You can search using one of model's Enumeration instances, as shown here.
Unfortunately, no there is nothing out of the box that does that. That makes some assumptions about nodes that Swing didn't want to impose of their design because it would constrain what it means to be a node in a tree (namely all nodes have some sort of string that uniquely identifies them). That doesn't mean you can't easily implement it yourself. You maybe aware TreePath is a common idea in JTree so here is a simple method that returns a TreePath to the node given a path:
http://www.exampledepot.8waytrips.com/egs/javax.swing.tree/FindNode.html
You could use that as a basis to implement what you are after.

Categories