In some cases a need my JButton to appear as if it were pressed. This depends on some boolean.
I tried to create my own ButtonModel that overrides the default isPressed() method but in that way the button appears pressed only in case the mouse pointer is on top of it (without pressing a mouse button). I need it to appear pressed also if the mouse is somewhere else.
So far I tried this:
class MyButtonModel extends DefaultButtonModel
{
private boolean appearPressed;
#Override
public boolean isPressed()
{
return super.isPressed() || appearPressed;
}
}
I cannot use a JToggleButton or something similar.
My button derives from another class that implements some additional features and derives itself from JButton.
UPDATE:
I'm running on Windows 10 and use the WindowsClassic Look&Feel.
you also need to override isArmed():
class MyButtonModel extends DefaultButtonModel
{
private boolean appearPressed = true;
#Override
public boolean isPressed()
{
return super.isPressed() || appearPressed;
}
#Override
public boolean isArmed() {
return super.isArmed() || appearPressed;
}
}
The partial reply was given by #Philipp Li and #camickr. Thank you.
My button model needs to override isArmed() in order to obtain the desired result.
However, once the button is pressed, it doesn't respond anymore.
Digging in the source of DefaultButtonModel, I found this:
public void setPressed(boolean b)
{
if ((isPressed() == b) || (!isEnabled()))
return;
...
}
This method is invoked by AbstractButton.doClick() in order to notify the various ActionListeners.
In other words, if the overridden method isPressed() returns true because I want to make the button appear pressed, a successive click on that button will be ignored.
A similar thing occurs within DefaultButtonModel.setArmed().
Therefore, I implemented my button model as follows:
class MyButtonModel extends DefaultButtonModel
{
boolean appearPressed = false;
private boolean withinSetPressedMethod = false;
private boolean withinSetArmedMethod = false;
#Override
public void setPressed(boolean pressed)
{
withinSetPressedMethod = true;
super.setPressed(pressed);
withinSetPressedMethod = false;
}
#Override
public void setArmed(boolean armed)
{
withinSetArmedMethod = true;
super.setArmed(armed);
withinSetArmedMethod = false;
}
#Override
public boolean isPressed()
{
if (withinSetPressedMethod)
return (super.isPressed());
else
return (super.isPressed() || appearPressed);
}
#Override
public boolean isArmed()
{
if (withinSetArmedMethod)
return (super.isArmed());
else
return (super.isArmed() || appearPressed);
}
} // class MyButtonModel
I'm creating a plugin for an event, this event is just: when you stop moving, you loose. I created everything, but my check to know if a player moves or not doesn't work:
I tried to make an array and when a player move event it's added, but it doesn't work,
#EventHandler
public static void OnPlayerMoov(PlayerMoveEvent e) {
if(!playerHaveMooved.contains(e.getPlayer())) {
playerHaveMooved.add(e.getPlayer());
}
}
public static boolean isMoving(Player p){
System.out.println(Respawn.playerHaveMooved.contains(p));
return Respawn.playerHaveMooved.contains(p);
}
I tried to use velocity; it doesn't work,
public static boolean isMoving(Player p){
return (p.getVelocity().length() == 0);
}
It doesn't work. It kills me when I am moving.
Do you have a solution?
The first don't work because you just check if the player already move a time, which will always be true.
The second about velocity check only if player get knockbacked for example.
First solution
Save last time of move, and check each few time if it stop move.
In this example, I will force player to move each seconds.
private final HashMap<Player, Long> timeMove = new HashMap<>(); // map for data
#Eventhandler
public void onMove(PlayerMoveEvent e) {
timeMove.put(e.getPlayer(), System.currentTimeMillis()); // update last move
}
public void startChecking(JavaPlugin pl) {
pl.getServer().getScheduler().runTaskTimer(pl, () -> {
long current = System.currentTimeMillis();
for(Player p : Bukkit.getOnlinePlayers()) {
long last = timeMove.getOrDefault(p, 0);
long diff = current - last;
if(diff > 1000) { // 1000 = second
// player don't move
}
}
}, 20, 20);
}
Second solution
Check each X time from last and current location
private final HashMap<Player, Location> lastLoc = new HashMap<>(); // map for data
public void startChecking(JavaPlugin pl) {
pl.getServer().getScheduler().runTaskTimer(pl, () -> {
for(Player p : Bukkit.getOnlinePlayers()) {
Location lastPlayerLoc = lastLoc.get(p); // get loc, or keep null if not registered
if(lastPlayerLoc != null) {
double distance = lastPlayerLoc.distance(p.getLocation());
if(distance == 0.0) {
// here the player don't move
continue; // go to next player
}
}
lastLoc.put(p, p.getLocation()); // update loc
}
}, 20, 20);
}
I have a question about using events to run loops since doing so seems to lock down the thread. For example I have an nativeMousePressed and nativeMouseReleased event and I am trying to execute some code continuously while the mouse is pressed and then stop when its released. I tried to do this by creating a static boolean variable in another manager class and then setting it to true when the mouse is being pressed and false when the mouse is released. Then I decided to make a while loop that gets called from inside that nativeMousePressed event that uses the boolean value I talked about earlier. The issue is that no events can be called while that while loop is running which means the boolean value when never become false creating an infinite loop. How can I run the while loop while keeping the events running as well?
I assume this has to do with the thread being locked down but I have not worked with stuff like this much and would like some help figuring out how to run both these things in parallel.
public class NativeMouseEvent implements NativeMouseListener {
Program program = new Program();
#Override
public void nativeMouseClicked(org.jnativehook.mouse.NativeMouseEvent e) {
}
#Override
public void nativeMousePressed(org.jnativehook.mouse.NativeMouseEvent e) {
if(e.getButton() == 1 && Controller.threeClicked) {
Controller.fixAim = true;
program.start();
}
}
#Override
public void nativeMouseReleased(org.jnativehook.mouse.NativeMouseEvent e) {
program.interrupt();
Controller.fixAim = false;
}
}
Here is what my second thread is running...
public class Program extends Thread {
public void run() {
while(Controller.fixAim) {
System.out.println("test");
}
}
Here my second attempt which also gives me an error saying that this.program is null.
public class NativeMouseEvent implements NativeMouseListener {
Program program;
#Override
public void nativeMouseClicked(org.jnativehook.mouse.NativeMouseEvent e) {
}
#Override
public void nativeMousePressed(org.jnativehook.mouse.NativeMouseEvent e) {
if(e.getButton() == 1 && Controller.threeClicked) {
Controller.fixAim = true;
if(program != null) {
program = new Program();
program.start();
}
}
}
#Override
public void nativeMouseReleased(org.jnativehook.mouse.NativeMouseEvent e) {
program.interrupt();
program = null;
Controller.fixAim = false;
}
}
Start a tread on mouse down and stop the tread on mouse up. In the thread do circle drawing.
Something like below java code. Note: it is just an example. You need to make changes to make it work in your android environment.
public class Test {
Thread drawTask;
public void mouseDown() {
drawTask = new Thread(()-> {
int i = 0;
try {
for(;;) {
System.out.print("\rDrawing circle " + i++);
Thread.sleep(500);
}
} catch(InterruptedException e) {
System.out.println("finished drawing circle.");
}
});
drawTask.start();
}
public void mouseUp() {
if(drawTask != null) {
drawTask.interrupt();
drawTask = null; //<--- make sure you do this
}
}
public static void main(String[] args) {
Test test = new Test();
Scanner in = new Scanner(System.in);
System.out.println("type anything and press Enter to simulate mouse down/up");
in.next();
test.mouseDown();
in.next();
test.mouseUp();
in.next();
in.close();
}
}
The initial situation is like this:
Now, the tree is potentially huge (I have a tree with 22 million nodes for instance). What happens here is that I use a jooq/h2 backend to store all the nodes and execute queries to find children.
Which means, in the image above, the node is marked as expandable but its children are not populated yet. It is done on demand. And after expansion I get this:
The problem I have is that of course, expansion can take time; and what I'd like to do is to add a visual clue to the graphic of the TreeItem to show that it is loading...
And I can't do it.
OK, first of all, a general view of the architecture:
it is, in short, "MVP with passive views";
what JavaFX calls a "controller", which is in my code implemented by *Display classes, are the passive views, whose only role is to capture UI events and forward them to the *Presenter; such classes are "GUI implementation specific";
the *Presenter reacts to those events by ordering the *View class to update the *Display;
when tasks require a certain amount of time to complete, too much for the GUI to remain interactive, a BackgroundTaskRunner is used by the *Presenter to:
instruct the *View to modify the UI to acknowledge the task (on the GUI thread);
perform the task (on a background thread);
instruct the *View to modify the UI when the task completes (on the GUI thread);
if the task fails, instruct the *View to modify the UI accordingly (on the GUI thread).
With JavaFX:
the UI is a *Display class; it is defined by, and loaded from, an FXML file;
the (implementation of) the *View class has visibility over all GUI elements defined in the *Display class.
The *View class is in fact an interface; this allows me to be able to make a webapp version of this program (planned).
Now, the context of this code...
The *Presenter, *View and *Display all relate to the "Parse tree" tab visible in the above images.
Given the architecture above, the problem lies with the implementation of the *View class, and with the *Display class.
The *Display class has an init() method which initializes all relevant JavaFX components, if need be. In this case, the TreeView, called parseTree, is initialized as such:
#Override
public void init()
{
parseTree.setCellFactory(param -> new ParseTreeNodeCell(this));
}
ParseTreeNodeCell is defined as such:
public final class ParseTreeNodeCell
extends TreeCell<ParseTreeNode>
{
// FAILED attempt at showing a progress indicator...
private final ProgressIndicator indicator = new ProgressIndicator();
private final Text text = new Text();
private final HBox hBox = new HBox(text, indicator);
public ParseTreeNodeCell(final TreeTabDisplay display)
{
// FIXME: not sure about the following line...
indicator.setMaxHeight(heightProperty().doubleValue());
// ... But this I want: by default, not visible
indicator.setVisible(false);
// The whole tree is readonly
setEditable(false);
// Some non relevant code snipped away
}
public void showIndicator()
{
indicator.setVisible(true);
}
public void hideIndicator()
{
indicator.setVisible(false);
}
// What to do when a TreeItem is actually attached...
#Override
protected void updateItem(final ParseTreeNode item, final boolean empty)
{
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
return;
}
final String msg = String.format("%s (%s)",
item.getRuleInfo().getName(),
item.isSuccess() ? "SUCCESS" : "FAILURE");
text.setText(msg);
setGraphic(hBox);
// HACK. PUKE. UGLY.
((ParseTreeItem) getTreeItem()).setCell(this);
}
}
ParseTreeItem is this:
public final class ParseTreeItem
extends TreeItem<ParseTreeNode>
{
private final boolean leaf;
private ParseTreeNodeCell cell;
public ParseTreeItem(final TreeTabDisplay display,
final ParseTreeNode value)
{
super(value);
leaf = !value.hasChildren();
// If the item is expanded, we load children.
// If it is collapsed, we unload them.
expandedProperty().addListener(new ChangeListener<Boolean>()
{
#Override
public void changed(
final ObservableValue<? extends Boolean> observable,
final Boolean oldValue, final Boolean newValue)
{
if (oldValue == newValue)
return;
if (!newValue) {
getChildren().clear();
return;
}
display.needChildren(ParseTreeItem.this);
}
});
}
#Override
public boolean isLeaf()
{
return leaf;
}
public void setCell(final ParseTreeNodeCell cell)
{
this.cell = cell;
}
public void showIndicator()
{
cell.showIndicator();
}
public void hideIndicator()
{
cell.hideIndicator();
}
}
Now, always in the *Display class, the needChildren() method is defined as such:
ParseTreeItem currentItem;
// ...
public void needChildren(final ParseTreeItem parseTreeItem)
{
// Keep a reference to the current item so that the *View can act on it
currentItem = parseTreeItem;
presenter.needChildren(currentItem.getValue());
}
The presenter does this:
public void needChildren(final ParseTreeNode value)
{
taskRunner.computeOrFail(
view::waitForChildren, () -> {
// FOR TESTING
TimeUnit.SECONDS.sleep(1L);
return getNodeChildren(value.getId());
},
view::setTreeChildren,
throwable -> mainView.showError("Tree expand error",
"Unable to extend parse tree node", throwable)
);
}
(see here; for the taskRunner)
The corresponding methods in the view member above (JavaFX implementation) do this:
#Override
public void waitForChildren()
{
// Supposedly shows the indicator in the TreeItemGraphic...
// Except that it does not.
display.currentItem.showIndicator();
}
#Override
public void setTreeChildren(final List<ParseTreeNode> children)
{
final List<ParseTreeItem> items = children.stream()
.map(node -> new ParseTreeItem(display, node))
.collect(Collectors.toList());
// This works fine
display.currentItem.getChildren().setAll(items);
// But this does not...
display.currentItem.hideIndicator();
}
Even though I define methods on the TreeItem to show the progress indicator, it doesn't show at all :/
In fact, my problem is twofold, and all related to ParseTreeItem:
in ParseTreeNodeCell, I need to cast to ParseTreeItem to set the cell;
even when I do this, well, it doesn't work at all, I can't see the indicator show up at all.
Not only that, but for some reason I need to check (in ParseTreeNodeCell) that I actually have a value, since otherwise I get an NPE. And I can't find a way to get the matching cell from a tree item...
So, all in all, I do many things badly and none correctly.
How do I manage to get the graphic of a TreeItem change in that situation, as long as the loading is still in progress?
EDIT
Solution found, inspited by the code written by #James_D; see my own answer for how I really did it.
First, I admit I didn't go through all your code carefully.
I think the approach to have here is to use a TreeItem subclass that exposes an observable property describing the "loaded status" of the children. Then have the tree cells observe the loaded status of the current tree item, and display a progress bar accordingly.
Here's a SSCCE:
(Updated: apparently if I only observe the treeItem and not the item, the tree fails to remove the disclosure graphics from empty cells... Fixed by using the itemProperty to manage the text.)
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class LazyTreeCellLoadingExample extends Application {
// Executor for background tasks:
private static final ExecutorService exec = Executors.newCachedThreadPool(r -> {
Thread t = new Thread(r);
t.setDaemon(true);
return t ;
});
#Override
public void start(Stage primaryStage) {
TreeView<Long> tree = new TreeView<>();
tree.setRoot(new LazyTreeItem(1L));
// cell factory that displays progress bar when item is loading children:
tree.setCellFactory(tv -> {
// the cell:
TreeCell<Long> cell = new TreeCell<>();
// progress bar to display when needed:
ProgressBar progressBar = new ProgressBar();
// listener to observe *current* tree item's child loading status:
ChangeListener<LazyTreeItem.ChildrenLoadedStatus> listener = (obs, oldStatus, newStatus) -> {
if (newStatus == LazyTreeItem.ChildrenLoadedStatus.LOADING) {
cell.setGraphic(progressBar);
} else {
cell.setGraphic(null);
}
};
// listener for tree item property
// ensures that listener above is attached to current tree item:
cell.treeItemProperty().addListener((obs, oldItem, newItem) -> {
// if we were displaying an item, (and no longer are...),
// stop observing its child loading status:
if (oldItem != null) {
((LazyTreeItem) oldItem).childrenLoadedStatusProperty().removeListener(listener);
}
// if there is a new item the cell is displaying:
if (newItem != null) {
// update graphic to display progress bar if needed:
LazyTreeItem lazyTreeItem = (LazyTreeItem) newItem;
if (lazyTreeItem.getChildrenLoadedStatus() == LazyTreeItem.ChildrenLoadedStatus.LOADING) {
cell.setGraphic(progressBar);
} else {
cell.setGraphic(null);
}
// observe loaded status of current item in case it changes
// while we are still displaying this item:
lazyTreeItem.childrenLoadedStatusProperty().addListener(listener);
}
});
// change text if item changes:
cell.itemProperty().addListener((obs, oldItem, newItem) -> {
if (newItem == null) {
cell.setText(null);
cell.setGraphic(null);
} else {
cell.setText(newItem.toString());
}
});
return cell ;
});
Button debugButton = new Button("Debug");
debugButton.setOnAction(evt -> {
dumpData(tree.getRoot(), 0);
});
primaryStage.setScene(new Scene(new BorderPane(tree, null, null, debugButton, null), 400, 250));
primaryStage.show();
}
private void dumpData(TreeItem<Long> node, int depth) {
for (int i=0; i<depth; i++) System.out.print(" ");
System.out.println(node.getValue());
for (TreeItem<Long> child : node.getChildren()) dumpData(child, depth+1);
}
// TreeItem subclass that lazily loads children in background
// Exposes an observable property specifying current load status of children
public static class LazyTreeItem extends TreeItem<Long> {
// possible load statuses:
enum ChildrenLoadedStatus { NOT_LOADED, LOADING, LOADED }
// observable property for current load status:
private final ObjectProperty<ChildrenLoadedStatus> childrenLoadedStatus = new SimpleObjectProperty<>(ChildrenLoadedStatus.NOT_LOADED);
public LazyTreeItem(Long value) {
super(value);
}
// getChildren() method loads children lazily:
#Override
public ObservableList<TreeItem<Long>> getChildren() {
if (getChildrenLoadedStatus() == ChildrenLoadedStatus.NOT_LOADED) {
lazilyLoadChildren();
}
return super.getChildren() ;
}
// load child nodes in background, updating status accordingly:
private void lazilyLoadChildren() {
// change current status to "loading":
setChildrenLoadedStatus(ChildrenLoadedStatus.LOADING);
long value = getValue();
// background task to load children:
Task<List<LazyTreeItem>> loadTask = new Task<List<LazyTreeItem>>() {
#Override
protected List<LazyTreeItem> call() throws Exception {
List<LazyTreeItem> children = new ArrayList<>();
for (int i=0; i<10; i++) {
children.add(new LazyTreeItem(10*value + i));
}
// for testing (loading is so lazy it falls asleep)
Thread.sleep(3000);
return children;
}
};
// when loading is complete:
// 1. set actual child nodes to loaded nodes
// 2. update status to "loaded"
loadTask.setOnSucceeded(event -> {
super.getChildren().setAll(loadTask.getValue());
setChildrenLoadedStatus(ChildrenLoadedStatus.LOADED);
});
// execute task in backgroun
exec.submit(loadTask);
}
// is leaf is true only if we *know* there are no children
// i.e. we've done the loading and still found nothing
#Override
public boolean isLeaf() {
return getChildrenLoadedStatus() == ChildrenLoadedStatus.LOADED && super.getChildren().size()==0 ;
}
// normal property accessor methods:
public final ObjectProperty<ChildrenLoadedStatus> childrenLoadedStatusProperty() {
return this.childrenLoadedStatus;
}
public final LazyTreeCellLoadingExample.LazyTreeItem.ChildrenLoadedStatus getChildrenLoadedStatus() {
return this.childrenLoadedStatusProperty().get();
}
public final void setChildrenLoadedStatus(
final LazyTreeCellLoadingExample.LazyTreeItem.ChildrenLoadedStatus childrenLoadedStatus) {
this.childrenLoadedStatusProperty().set(childrenLoadedStatus);
}
}
public static void main(String[] args) {
launch(args);
}
}
Update
After quite a bit of discussion, I came up with a second solution. This is basically similar to the previous solution in the way it manages the progress bar: there is a TreeItem subclass that exposes a BooleanProperty which is true if and only if the item is currently loading its children. The TreeCell observes this property on the TreeItem it is currently displaying - taking care to register a listener with the treeItemProperty so that the listener for the loadingProperty is always registered with the current item.
The difference is in the way the children are loaded, and - in the case of this solution - unloaded. In the previous solution, child nodes were loaded when first requested, and then retained. In this solution, child nodes are loaded when the node is expanded, and then removed when the node is collapsed. This is handled with a simple listener on the expandedProperty.
The first solution behaves slightly more as expected from a user perspective, in that if you collapse a node which is the head of a subtree, and then expand it again, the expanded state of the subtree is retained. In the second solution, collapsing a node has the effect of collapsing all descendent nodes (because they are actually removed).
The second solution is more robust for memory usage. This is actually unlikely to be an issue outside of some extreme use cases. TreeItem objects are purely model - i.e. they store only data, no UI. Thus they probably use no more than a few hundred bytes of memory each. In order to consume significant amounts of memory, the user would have to navigate through hundreds of thousands of nodes, which would probably take days. (That said, I'm typing this into Google Chrome, which I think I've had running for over a month with at least 8-10 hours' active use per day, so such use cases are certainly not impossible.)
Here's the second solution. One note: I don't make any effort to handle a quick expand and collapse of a node (collapsing while the data are still loading). The TreeItem subclass should really keep track of any current Task (or use a Service) and call cancel() if a task is running and the user collapses the node. I didn't want to confuse the logic more than necessary to demonstrate the basic idea.
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class LazyTreeCellLoadingExample2 extends Application {
private static final ExecutorService EXEC = Executors.newCachedThreadPool((Runnable r) -> {
Thread t = new Thread(r);
t.setDaemon(true);
return t ;
});
#Override
public void start(Stage primaryStage) {
TreeView<Integer> tree = new TreeView<>();
tree.setRoot(new LazyTreeItem(1));
tree.setCellFactory(tv -> createTreeCell()) ;
primaryStage.setScene(new Scene(new BorderPane(tree), 450, 600));
primaryStage.show();
}
private TreeCell<Integer> createTreeCell() {
ProgressBar progressBar = new ProgressBar();
TreeCell<Integer> cell = new TreeCell<>();
ChangeListener<Boolean> loadingChangeListener =
(ObservableValue<? extends Boolean> obs, Boolean wasLoading, Boolean isNowLoading) -> {
if (isNowLoading) {
cell.setGraphic(progressBar);
} else {
cell.setGraphic(null);
}
};
cell.treeItemProperty().addListener(
(ObservableValue<? extends TreeItem<Integer>> obs,
TreeItem<Integer> oldItem,
TreeItem<Integer> newItem) -> {
if (oldItem != null) {
LazyTreeItem oldLazyTreeItem = (LazyTreeItem) oldItem ;
oldLazyTreeItem.loadingProperty().removeListener(loadingChangeListener);
}
if (newItem != null) {
LazyTreeItem newLazyTreeItem = (LazyTreeItem) newItem ;
newLazyTreeItem.loadingProperty().addListener(loadingChangeListener);
if (newLazyTreeItem.isLoading()) {
cell.setGraphic(progressBar);
} else {
cell.setGraphic(null);
}
}
});
cell.itemProperty().addListener(
(ObservableValue<? extends Integer> obs, Integer oldItem, Integer newItem) -> {
if (newItem == null) {
cell.setText(null);
cell.setGraphic(null);
} else {
cell.setText(newItem.toString());
}
});
return cell ;
}
public static class LazyTreeItem extends TreeItem<Integer> {
private final BooleanProperty loading = new SimpleBooleanProperty(false);
private boolean leaf = false ;
public final BooleanProperty loadingProperty() {
return this.loading;
}
public final boolean isLoading() {
return this.loadingProperty().get();
}
public final void setLoading(final boolean loading) {
this.loadingProperty().set(loading);
}
public LazyTreeItem(Integer value) {
super(value);
expandedProperty().addListener((ObservableValue<? extends Boolean>obs, Boolean wasExpanded, Boolean isNowExpanded) -> {
if (isNowExpanded) {
loadChildrenLazily();
} else {
clearChildren();
}
});
}
#Override
public boolean isLeaf() {
return leaf ;
}
private void loadChildrenLazily() {
setLoading(true);
int value = getValue();
Task<List<TreeItem<Integer>>> loadTask = new Task<List<TreeItem<Integer>>>() {
#Override
protected List<TreeItem<Integer>> call() throws Exception {
List<TreeItem<Integer>> children = new ArrayList<>();
for (int i=0; i<10; i++) {
children.add(new LazyTreeItem(value * 10 + i));
}
Thread.sleep(3000);
return children ;
}
};
loadTask.setOnSucceeded(event -> {
List<TreeItem<Integer>> children = loadTask.getValue();
leaf = children.size() == 0 ;
getChildren().setAll(children);
setLoading(false);
});
EXEC.submit(loadTask);
}
private void clearChildren() {
getChildren().clear();
}
}
public static void main(String[] args) {
launch(args);
}
}
The "double listener" in for the cell is because we really need to observe a "property of a property". Specifically, the cell has a treeItem property, and the tree item has a loadingProperty. It's the loading property belonging to the current tree item that we're really interested in. Of course, there are two ways this can change: the tree item changes in the cell, or the loading property changes in the tree item. The EasyBind framework includes API specifically for observing "properties of properties". If you use EasyBind, you can replace the (30 or so lines of) code
ChangeListener<Boolean> loadingChangeListener = ... ;
cell.treeItemProperty().addListener(...);
with
ObservableValue<Boolean> loading = EasyBind.select(cell.treeItemProperty())
// ugly cast still here:
.selectObject(treeItem -> ((LazyTreeItem)treeItem).loadingProperty());
loading.addListener((obs, wasLoading, isNowLoading) -> {
if (isNowLoading != null && isNowLoading.booleanValue()) {
cell.setGraphic(progressBar);
} else {
cell.setGraphic(null);
}
});
Problem SOLVED!
Here is how I solved it...
The first thing that I learned with this question is that a TreeCell is in fact a "moving object": it moves from TreeItem to TreeItem; here, the TreeItem is a ParseTreeItem which has a dedicated property, loadingProperty(), and in the cell's treeItemProperty() I make use of this property to update the graphics.
As #James_D suggested in his code, I use EasyBind; the code is basically a ripoff of his, except for the fact that I don't use the cell's text but only a graphic, which is a HorizontalBox.
In addition I also have to listen to the cell's selectedProperty() since when it is selected, I need to update the info box and the text area:
public final class ParseTreeNodeCell
extends TreeCell<ParseTreeNode>
{
private final Text text = new Text();
private final ProgressBar progressBar = new ProgressBar();
private final HBox hBox = new HBox(text);
public ParseTreeNodeCell(final TreeTabDisplay display)
{
setEditable(false);
selectedProperty().addListener(new ChangeListener<Boolean>()
{
#SuppressWarnings("AutoUnboxing")
#Override
public void changed(
final ObservableValue<? extends Boolean> observable,
final Boolean oldValue, final Boolean newValue)
{
if (!newValue)
return;
final ParseTreeNode node = getItem();
if (node != null)
display.parseTreeNodeShowEvent(node);
}
});
final ObservableValue<Boolean> loading
= EasyBind.select(treeItemProperty())
.selectObject(item -> ((ParseTreeItem) item).loadingProperty());
loading.addListener(new ChangeListener<Boolean>()
{
#Override
public void changed(
final ObservableValue<? extends Boolean> observable,
final Boolean oldValue, final Boolean newValue)
{
final ObservableList<Node> children = hBox.getChildren();
if (newValue == null || !newValue.booleanValue()) {
children.remove(progressBar);
return;
}
if (!children.contains(progressBar))
children.add(progressBar);
}
});
itemProperty().addListener(new ChangeListener<ParseTreeNode>()
{
#Override
public void changed(
final ObservableValue<? extends ParseTreeNode> observable,
final ParseTreeNode oldValue, final ParseTreeNode newValue)
{
if (newValue == null) {
setGraphic(null);
return;
}
text.setText(String.format("%s (%s)",
newValue.getRuleInfo().getName(),
newValue.isSuccess() ? "SUCCESS" : "FAILURE"));
setGraphic(hBox);
}
});
}
}
Now, the code of ParseTreeItem which extends TreeItem<ParseTreeNode>, is as such:
public final class ParseTreeItem
extends TreeItem<ParseTreeNode>
{
private final BooleanProperty loadingProperty
= new SimpleBooleanProperty(false);
private final boolean leaf;
public ParseTreeItem(final TreeTabDisplay display,
final ParseTreeNode value)
{
super(value);
leaf = !value.hasChildren();
expandedProperty().addListener(new ChangeListener<Boolean>()
{
#Override
public void changed(
final ObservableValue<? extends Boolean> observable,
final Boolean oldValue, final Boolean newValue)
{
if (!newValue) {
getChildren().clear();
return;
}
display.needChildren(ParseTreeItem.this);
}
});
}
public BooleanProperty loadingProperty()
{
return loadingProperty;
}
#Override
public boolean isLeaf()
{
return leaf;
}
}
Again, this is nearly a ripoff, except for the fact that I don't want the item to hold the logic to:
fetch the children,
updating its own loading property.
The "secret" is in display.needChildren(ParseTreeItem.this); this is what it does:
public void needChildren(final ParseTreeItem parseTreeItem)
{
currentItem = parseTreeItem;
presenter.needChildren(currentItem.getValue());
}
And, in turn, the code in presenter does:
public void needChildren(final ParseTreeNode value)
{
taskRunner.computeOrFail(
view::waitForChildren,
() -> getNodeChildren(value.getId()),
view::setTreeChildren,
throwable -> mainView.showError("Tree expand error",
"Unable to extend parse tree node", throwable)
);
}
And is where the view comes in; it is the view which updates currentItem, since it has direct access to the display and its fields:
#Override
public void waitForChildren()
{
display.currentItem.loadingProperty().setValue(true);
}
#Override
public void setTreeChildren(final List<ParseTreeNode> children)
{
final List<ParseTreeItem> items = children.stream()
.map(node -> new ParseTreeItem(display, node))
.collect(Collectors.toList());
display.currentItem.getChildren().setAll(items);
display.currentItem.loadingProperty().setValue(false);
}
As you can see, when waitingForChildren() and setTreeChildren() update the loadingProperty(); and in turn the ParseTreeNodeCell will then update the graphics accordingly.
I'm trying to write a game in java3d on Linux and for that I need a proper KeyListener.
Did anyone of you know how to do it? I'm currently using following code, I found somewhere on the net. It's working pretty good, holding down just one key, but as soon, as I press more than one (like space and w) it will do unexpected things...
public class RepeatingReleasedEventsFixer implements AWTEventListener {
private final HashMap<Integer, ReleasedAction> _map = new HashMap<Integer, ReleasedAction>();
public void install() {
Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.KEY_EVENT_MASK);
}
public void remove() {
Toolkit.getDefaultToolkit().removeAWTEventListener(this);
}
#Override
public void eventDispatched(AWTEvent event) {
assert event instanceof KeyEvent : "Shall only listen to KeyEvents, so no other events shall come here";
assert assertEDT(); // REMEMBER THAT THIS IS SINGLE THREADED, so no need for synch.
// ?: Is this one of our synthetic RELEASED events?
if (event instanceof Reposted) {
// -> Yes, so we shalln't process it again.
return;
}
// ?: KEY_TYPED event? (We're only interested in KEY_PRESSED and KEY_RELEASED).
if (event.getID() == KeyEvent.KEY_TYPED) {
// -> Yes, TYPED, don't process.
return;
}
final KeyEvent keyEvent = (KeyEvent) event;
// ?: Is this already consumed?
// (Note how events are passed on to all AWTEventListeners even though a previous one consumed it)
if (keyEvent.isConsumed()) {
return;
}
// ?: Is this RELEASED? (the problem we're trying to fix!)
if (keyEvent.getID() == KeyEvent.KEY_RELEASED) {
// -> Yes, so stick in wait
/**
* Really just wait until "immediately", as the point is that the subsequent PRESSED shall already have been
* posted on the event queue, and shall thus be the direct next event no matter which events are posted
* afterwards. The code with the ReleasedAction handles if the Timer thread actually fires the action due to
* lags, by cancelling the action itself upon the PRESSED.
*/
final Timer timer = new Timer(2, null);
ReleasedAction action = new ReleasedAction(keyEvent, timer);
timer.addActionListener(action);
timer.start();
_map.put(Integer.valueOf(keyEvent.getKeyCode()), action);
// Consume the original
keyEvent.consume();
}
else if (keyEvent.getID() == KeyEvent.KEY_PRESSED) {
// Remember that this is single threaded (EDT), so we can't have races.
ReleasedAction action = _map.remove(Integer.valueOf(keyEvent.getKeyCode()));
// ?: Do we have a corresponding RELEASED waiting?
if (action != null) {
// -> Yes, so dump it
action.cancel();
}
// System.out.println("PRESSED: [" + keyEvent + "]");
}
else {
throw new AssertionError("All IDs should be covered.");
}
}
/**
* The ActionListener that posts the RELEASED {#link RepostedKeyEvent} if the {#link Timer} times out (and hence the
* repeat-action was over).
*/
private class ReleasedAction implements ActionListener {
private final KeyEvent _originalKeyEvent;
private Timer _timer;
ReleasedAction(KeyEvent originalReleased, Timer timer) {
_timer = timer;
_originalKeyEvent = originalReleased;
}
void cancel() {
assert assertEDT();
_timer.stop();
_timer = null;
_map.remove(Integer.valueOf(_originalKeyEvent.getKeyCode()));
}
#Override
public void actionPerformed(#SuppressWarnings ("unused") ActionEvent e) {
assert assertEDT();
// ?: Are we already cancelled?
// (Judging by Timer and TimerQueue code, we can theoretically be raced to be posted onto EDT by TimerQueue,
// due to some lag, unfair scheduling)
if (_timer == null) {
// -> Yes, so don't post the new RELEASED event.
return;
}
// Stop Timer and clean.
cancel();
// Creating new KeyEvent (we've consumed the original).
KeyEvent newEvent = new RepostedKeyEvent((Component) _originalKeyEvent.getSource(),
_originalKeyEvent.getID(), _originalKeyEvent.getWhen(), _originalKeyEvent.getModifiers(),
_originalKeyEvent.getKeyCode(), _originalKeyEvent.getKeyChar(), _originalKeyEvent.getKeyLocation());
// Posting to EventQueue.
Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(newEvent);
// System.out.println("Posted synthetic RELEASED [" + newEvent + "].");
}
}
/**
* Marker interface that denotes that the {#link KeyEvent} in question is reposted from some
* {#link AWTEventListener}, including this. It denotes that the event shall not be "hack processed" by this class
* again. (The problem is that it is not possible to state "inject this event from this point in the pipeline" - one
* have to inject it to the event queue directly, thus it will come through this {#link AWTEventListener} too.
*/
public interface Reposted {
// marker
}
/**
* Dead simple extension of {#link KeyEvent} that implements {#link Reposted}.
*/
public static class RepostedKeyEvent extends KeyEvent implements Reposted {
public RepostedKeyEvent(#SuppressWarnings ("hiding") Component source, #SuppressWarnings ("hiding") int id,
long when, int modifiers, int keyCode, char keyChar, int keyLocation) {
super(source, id, when, modifiers, keyCode, keyChar, keyLocation);
}
}
private static boolean assertEDT() {
if (!EventQueue.isDispatchThread()) {
throw new AssertionError("Not EDT, but [" + Thread.currentThread() + "].");
}
return true;
}
}
I can't be the only one who still runs into this - meanwhile 15 y.o. - problem and don't want to use timers...
EDIT: What this code is doing is fix the known problem on any Linux distri, where you add a simple KeyListener, which handles keyDowns, but invokes keyReleased Event repeatedly. To clearify my problem here a simple example
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
public class Test5 extends JFrame{
public Test5() {
addKeyListener(new KeyListener() {
boolean keydown = false;
#Override
public void keyTyped(KeyEvent arg0) {
// TODO Auto-generated method stub
}
#Override
public void keyReleased(KeyEvent arg0) {
keydown = false;
System.out.println("keyup");
}
#Override
public void keyPressed(KeyEvent arg0) {
if (keydown){
System.out.println("key is down");
} else {
System.out.println("key not down");
}
keydown = true;
}
});
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(400, 400);
setVisible(true);
//new RepeatingReleasedEventsFixer().install(); // This line will fix it for one key pressed
}
public static void main(String[] args) {
new Test5();
}
}
The output without the line being commented out:
key not down
keyup
key not down
keyup
key not down
keyup
key not down
keyup
key not down
keyup
otherwise:
key not down
key is down
key is down
key is down
key is down
key is down
key is down
key is down
key is down
key is down
keyup
Btw. How come, that it's not beeing fixed by now?
EDIT:
I tried the KeyBindings, as suggested, where it comes to these problems:
public class Test5 extends JFrame{
long timestamp = 0;
public Test5() {
((JComponent)getComponent(0)).getInputMap().put(KeyStroke.getKeyStroke('a'), "a");
((JComponent)getComponent(0)).getActionMap().put("a", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("time: "+(System.currentTimeMillis()-timestamp));
timestamp = System.currentTimeMillis();
}
});
((JComponent)getComponent(0)).getInputMap().put(KeyStroke.getKeyStroke('s'), "s");
((JComponent)getComponent(0)).getActionMap().put("s", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent arg0) {
System.out.println("s");
}
});
((JComponent)getComponent(0)).getInputMap().put(KeyStroke.getKeyStroke('d'), "d");
((JComponent)getComponent(0)).getActionMap().put("d", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent arg0) {
System.out.println("d");
}
});
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(400, 400);
setVisible(true);
new RepeatingReleasedEventsFixer().install(); // This line will fix it for one key pressed
}
/**
* #param args
*/
public static void main(String[] args) {
new Test5();
}
Holding down "a" will give me following output:
time: 4171
time: 501
time: 30
time: 30
time: 30
Where the second time is the actual problem. It takes about 470ms too long.
Holding down "s" and then somewhne pressing "d" will give me that output:
s
s
s
s
d
d
d
d
d
So I can't process two actions as the same time, so I can't use KeyBindings
This is not an answer, it is a long comment with a picture and some explanations.
I used your Test5 (without RepeatingReleasedEventsFixer) to hold down a and measure the time responses. The output is of the form
time: t1
time: t2
time: t3
time: t3
time: t3
...
t1 is meaningless since it depends on the current time and has nothing to do with response time (you also seem to ignore it).
t2 is the time it takes for the OS to realize that you're holding the key for repeated input.
t3 is the "sample time" of the held key, or a discretization of the input.
I'm using Windows where I have the following control panel options:
Repeat delay allows me to set t2 between ~257 (short) and ~1050 (long).
Repeat rate allows me to set t3 between ~407 (slow) and ~37 (fast).
For Linux, you'll have to consult someone / somewhere on how to change these values if you don't already know how to.
As for using multiple keys, see this question and answer and the excellent link within (especially the "Motion With Multiple Keys Pressed" section). It's a short tutorial and analysis of key bindings and key listeners, similar to the one I sent you to on this site.
Key bindings will always be preferred over key listeners unless maybe there is some very low level thing you want to do.
After days of researching and putting stuff together, I ended up writing my own Listener combined with a KeyEventDispatcher, here is the code for someone running into the same problem. It can and should be optimized, but is working for now:
Klass to test if a specific key is pressed:
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.event.KeyEvent;
import java.util.HashMap;
public class IsKeyPressed {
private static boolean wPressed = false;
private HashMap<Integer, Boolean> keys = new HashMap<Integer, Boolean>();
public IsKeyPressed() {
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(new KeyEventDispatcher() {
#Override
public boolean dispatchKeyEvent(KeyEvent ke) {
synchronized (IsKeyPressed.class) {
switch (ke.getID()) {
case KeyEvent.KEY_PRESSED:
keys.put(ke.getKeyCode(), true);
break;
case KeyEvent.KEY_RELEASED:
keys.put(ke.getKeyCode(), false);
break;
}
return false;
}
}
});
}
public static boolean isWPressed() {
synchronized (IsKeyPressed.class) {
return wPressed;
}
}
public boolean isPressed(int keyCode){
synchronized (IsKeyPressed.class) {
if (keys == null)
return false;
if (keys.get(keyCode) == null)
return false;
return keys.get(keyCode);
}
}
}
Abstract class, thats beeing used for the actions.
public abstract class KeyActionListener {
protected int keyCode;
public KeyActionListener(int keyCode) {
this.keyCode = keyCode;
}
public void setKeyCode(int keyCode){
this.keyCode = keyCode;
}
public int getKeyCode(){
return this.keyCode;
}
public abstract void onKeyDown();
public abstract void onKeyUp();
public abstract void onKeyHolding();
}
Start listening to the keys and run the actions.
import java.util.ArrayList;
import java.util.HashMap;
public class KeyThread extends Thread{
private int sleep = 3;
ArrayList<KeyActionListener> listener = new ArrayList<KeyActionListener>();
IsKeyPressed isPressed = new IsKeyPressed();
HashMap<KeyActionListener, Boolean> pressed = new HashMap<KeyActionListener, Boolean>();
public KeyThread() {
this.start();
}
public void run() {
while (true){
for (int i = 0; i < listener.size(); i++) {
KeyActionListener curListener = listener.get(i);
if (isPressed.isPressed(curListener.getKeyCode()) && !pressed.get(curListener)){
curListener.onKeyDown();
pressed.put(curListener, true);
} else if(!isPressed.isPressed(curListener.getKeyCode()) && pressed.get(curListener)) {
curListener.onKeyUp();
pressed.put(curListener, false);
}
if(isPressed.isPressed(curListener.getKeyCode())){
curListener.onKeyHolding();
}
try{
Thread.sleep(sleep);
} catch(InterruptedException e){
}
}
}
}
public void addKeyActionListener(KeyActionListener l){
listener.add(l);
pressed.put(l, false);
}
}