I know Piccolo2d is an old project, but I have a couple of questions.
1) Is it possible to disable dragging a selected object? For this particular use case, I only want to select an object, not move or delete it. I know I can disable deletion using:
this.selector.setDeleteKeyActive(false);
But I don't see an option to disable dragging. Is the only option to override the drag functionality in the event handler?
2) Is there no way to have the selection handler active at the same time as the pan/zoom handlers? It seems a bit archaic to disable pan/zoom when you want to support object picking. Or do I have to create my own handlers?
My current code is:
...
this.pluginContext.getCanvas().setPanEventHandler(null);
this.selector = new PSelectionEventHandler(this.mapLayer.getNode(), this.mapLayer.getNode()) {
};
this.selector.setDeleteKeyActive(false);
this.pluginContext.getCanvas().addInputEventListener(this.selector);
PNotificationCenter.defaultCenter().addListener(this, "nodeSelected",
PSelectionEventHandler.SELECTION_CHANGED_NOTIFICATION, this.selector);
...
public void nodeSelected(final PNotification notification) {
logger.debug("Selection - " + this.selector.getSelection().toString());
}
In true developer tradition, I found an answer.
My pickable nodes are all grouped under individual grouping nodes. This lets me add an event listener on the grouping node, like this:
groupNode.addInputEventListener(new PBasicInputEventHandler() {
public void mouseReleased(final PInputEvent event) {
PNode node = event.getPickedNode();
if (node != null) {
onNodeSelected(node); // your logic here
}
}
});
The beauty of this is that the event contains the picked node that is grouped under that group node. Perfect! Also, you don't have to disable the pan/zoom handlers.
The only downside is that this doesn't give you the selection decorator around the node. Can't win 'em all!
Related
I'd like to know how can I determine if the KeyCombination (constructed via Mnemonic) for a control is triggered. Or for simplicity's sake, a handler method for Mnemonics?
Basically, I'm working with a custom control which extends from Labeled and from its Behavior class, I want to perform some extra actions when the assigned Mnemonic is triggered.
EDIT
So after a little bit digging, I came up with an idea to simply listen to a KeyEvent from the Scene (but take note that this is JUST AN IDEA. Either way, I'll figure out the rest later on). Here's how:
public CustomLabeledBehavior(CustomLabeled control) {
super(control, ...);
/* IMPORTANT PART */
// Assume that mnemonicParsing is set to true
TextBinding tb = new TextBinding(control.getText());
KeyCombination kc = tb.getMnemonicKeyCombination();
control.getScene().addEventFilter(KeyEvent.KEY_PRESSED, e -> {
if (kc.match(e)) {
System.out.println("MATCHED!");
}
});
}
UPDATE
To make this answer more useful than it used to be, I'll show you the usage of the method I'm currently using to determine if the KeyCombination is triggered. So I'll try to explain every single detail if I can.
We are only focusing on adding a Mnemonic, using the MnemonicParsing property of a custom control. And we will call this custom control as CustomLabeled since based on the question, it extends from the Labeled class, hence the name is based to it. Then we will work things inside its Skin class called, CustomLabeledSkin.
#1 Initializing the control:
<!-- Assuming that this FXML is already set and is attached to a Scene -->
<CustomLabeled text="_Hello World!" mnemonicParsing="true"/>
#2 Setting up our MnemonicHandler:
/* These two variables are initialized from the Constructor. */
private KeyCombination kb; // The combination of keys basically, ALT + Shortcut key
private String shortcut; // The shortcut key itself basically, a Letter key
private boolean altDown = false; // Indicator if the user press ALT key
/**
* This handler is a convenience variable to easily attach
* and detach to the Scene's event handlers. This is the
* whole idea to determine whether the KeyCombination is
* triggered.
*/
private EventHandler<KeyEvent> mnemonicHandler = new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
// The KeyCombination, basically, the ALT + Shortcut key.
if (kb.match(event) {
// TODO: Execute command here.
event.consume(); // Prevent from further propagation.
return; // Make sure that the rest won't be executed.
}
// While these two functions are for separate event.
// Example, pressing ALT first before the Shortcut key.
if (event.isAltDown()) altDown = !altDown;
if (altDown && event.getCode() == KeyCode.getKeyCode(shortcut)) {
// TODO: Execute command here.
event.consume();
}
}
}
#3 Initialize our Skin class:
/**
* Constructor
*/
public CustomLabeledSkin(CustomLabeled control) {
// Since this is just an example/testing, we will only assume
// that MnemonicParsing is enabled and a valid Mnemonic is
// registered. So if you want to validate a mnemonic, you
// might want to do it here.
TextBinding tb = new TextBinding(control.getText());
kc = tb.getMnemonicKeyCombination();
shortcut = tb.getMnemonic();
// Then we can just filter for a KEY_PRESS from the Scene, then
// everything else will be handled by our MnemonicHandler.
control.getScene().addEventFilter(KeyEvent.KEY_PRESSED, mnemonicHandler);
}
NOTE: The TextBinding class is not part of the public API, it is used by the Labeled node to handle Mnemonics.
Further more, you can create a method to detach the MnemonicHandler if there's no currently Mnemonics assigned (E.g previously there's a mnemonic, then code was changed...).
I have a project with a TreeView and I want that if I select a TreeItem and that I type DELETE the file in relation with it is automatically delete on the disk. But it doesn't work and I don't find my answer just by searching on Internet. This is my function (listeArbres is a TreeView) :
private void recupereFichierProjet(File repertoire, FileFilter filtre) {
File[] fichiers = repertoire.listFiles(filtre);
TreeItem<String> rootItem = new TreeItem<String>("Workspace");
rootItem.setExpanded(true);
for (File fichier : fichiers) {
Projet projet = Projet.charge(fichier);
TreeItem<String> item = new TreeItem<String>(fichier.getName());
item.addEventHandler(KeyEvent.KEY_TYPED, event -> {
if (event.getCode() == KeyCode.DELETE) {
System.out.println("la");
Projet.supprime(
new File("./workspace/" + listeArbres.getSelectionModel().getSelectedItem().getValue()));
initialiseTreeView();
}
});
rootItem.getChildren().add(item);
for (Arbre arbre : projet.getArbreDuProjet()) {
TreeItem<String> itemBis = new TreeItem<String>(arbre.getEntete().getNomFonction());
item.getChildren().add(itemBis);
}
}
listeArbres.setRoot(rootItem);
listeArbres.setVisible(true);
}
I think I understand that addEventHandler is for distinct Event so I don't understand how to use a KeyListener on the object 'Item'.
The static methode 'supprime' on 'Projet' is use to deleted my file.
Thank you beforehand.(And sorry for my bad english).
As stated in the TreeItem documentation (under "TreeItem Events"):
It is important to note however that a TreeItem is not a Node, which
means that only the event types defined in TreeItem will be delivered.
To listen to general events (for example mouse interactions), it is
necessary to add the necessary listeners to the cells contained within
the TreeView (by providing a cell factory).
For key presses, however, the actual cells do not get keyboard focus, and so they do not receive key events. So what you really want here is that when the TreeView has focus and the delete key is pressed, then delete the selected item in the tree. So you need
TreeView<String> tree = ... ;
tree.setOnKeyPressed(e -> {
TreeItem<String> selected = tree.getSelectionModel().getSelectedItem();
if (selected != null && e.getCode() == KeyCode.DELETE) {
System.out.println("Delete pressed on "+selected.getValue());
// delete file associated with selected.getValue()...
}
});
A couple of other notes:
Key typed events do not have a code associated with them (see docs). You need a key pressed event here, not a key typed event.
Since your tree view seems to be displaying files, it might make (a lot more) sense to have a TreeView<File> and to modify the updateItem method in the cell implementation to display the name of the file. Then you can get the file directly with getItem() in the listener, and the code to delete it will be much easier.
I have a TreePanel which shows different kind of objects hierarchically. Region, City, Location...
I want to be able to show different context menu items in different levels. For example: miR for Region, miC for City, miL for Location...
I used this snipped to achieve that dynamic structure:
contextMenu.addListener(Events.BeforeShow, new Listener<MenuEvent>() {
#Override
public void handleEvent(MenuEvent be) {
//First make all menu items invisible
List<Component> menuItems = contextMenu.getItems();
for (Component c : menuItems) {
c.setVisible(false);
}
//And make apprepriate menu items visible
TopologyTreeElement s = tree.getSelectionModel().getSelectedItem();
if (s instanceof TopologyTreeElement.Region) {
miR.setVisible(true);
}
if (s instanceof TopologyTreeElement.City) {
miC.setVisible(true);
}
}
});
But, in any level if all of the items are invisible, it shows an empty box. I want it not to show the menu totally. I tried adding this code snippet to the method, but it gave no help.
//Do not show menu if no menu item is invisible
boolean isMenuShouldBeVisible = miC.isVisible() || miR.isVisible();
if (!isMenuShouldBeVisible) {
be.preventDefault();
be.stopEvent();
}
Anyone can suggest a different approach?
Since you are listening to the BeforeShow event, you are allowed to cancel the event and stop the actual Show event from happening. Check to see if all items are invisible, and if so, call be.setCancelled(true).
Any event that starts in Before can be used to cancel the later event - this is why these before- events exist at all.
I am working on a project using Synth for the UI and want to implement some custom buttons. The buttons need to make use of style settings from a synth XML settings file - e.g. font colors which are different for different states (MOUSE_OVER, PRESSED, etc).
The problem I'm stuck on is that some of the buttons need to have extra sub-components - e.g. some need more than one label. I want the sub-components to pick up the same style settings as the standard button sub-components.
I feel like I ought to be able to just extend JButton and override/extend paintComponent to call the draw methods of some child components. I'm a bit unsure about a few aspects of that approach though: e.g. what parameters to pass to paintComponent; and how to ensure the sub-components get the correct Synth style settings (particularly wrt. the states).
An aside: I have tried extending JPanel but have run into some difficulties with that approach (see here: JPanel states for Synth).
EDIT: So, I've discovered that it is possible to add sub-components to buttons and have them render correctly. It seems that even though JButton.getLayout() returns null, the button will use an OverlayLayout unless you call JButton.setLayout(). Calling JButton.setLayout(null) does prevent the OverlayLayout being used, so that's how I'm handling the layout.
I'm looking into a couple of different approaches to updating the styles for the child controls, will report back on those later.
So, in case it's of use to anyone else here's the approach I took in the end:
class CustomButton extends JButton {
CustomButton() {
// ... normal button init
// Enable absolute positioning of sub-components.
setLayout(null);
updateStyles();
getModel().addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
updateStyles();
}
});
}
private void updateStyles() {
// See below for implementation.
}
private int getSynthComponentState() {
// This is basically a copy of SynthButtonUI.getComponentState(JComponent)
int state = SynthConstants.ENABLED;
if (!isEnabled()) {
state = SynthConstants.DISABLED;
}
if (model.isPressed()) {
if (model.isArmed()) {
state = SynthConstants.PRESSED;
} else {
state = SynthConstants.MOUSE_OVER;
}
}
if (model.isRollover()) {
state |= SynthConstants.MOUSE_OVER;
}
if (model.isSelected()) {
state |= SynthConstants.SELECTED;
}
if (isFocusOwner() && isFocusPainted()) {
state |= SynthConstants.FOCUSED;
}
if (isDefaultButton()) {
state |= SynthConstants.DEFAULT;
}
return state;
}
}
I found 2 approaches to how to implement the updateStyles() method: (A) change the name of the component to use a different named style, or (B) copy the style settings from the button to the sub-components. Approach (A) is pretty straightforward, approach (B) works as follows:
private void updateStyles() {
SynthStyle ss = SynthLookAndFeel.getStyle(this, Region.BUTTON);
SynthContext sc = new SynthContext(this, Region.BUTTON, ss, getSynthComponentState());
for (Component c : getComponents()) {
c.setFont(ss.getFont(sc));
c.setBackground(ss.getColor(sc, ColorType.BACKGROUND));
c.setForeground(ss.getColor(sc, ColorType.FOREGROUND));
// ... and so on if you have other style elements to be changed.
}
}
Approach (A) is probably better if you're changing more than a couple of style settings with each different state, although it could get unwieldy if you have different styles for a lot of different states. If you're only changing a couple of style settings (e.g. in my case I only care about colours, at least for now) then approach (B) seems best.
There's also the approach suggested by trashgod of implementing a custom UI delegate (extending BasicButtonUI) but if you take that route I think you'll have to re-implement much of SynthButtonUI.
I have a JTree where users can drop elements from other components. When the users hovers over nodes in the tree (during "drop mode") the most near lying node is highlighted. This is achieved in the implementation of TransferHandler.
#Override
public boolean canImport(TransferSupport support) {
//Highlight the most near lying node in the tree as the user drags the
//mouse over nodes in the tree.
support.setShowDropLocation(true);
Each time a new node is selected (also during "drop mode"), this will kick of a TreeSelectionEvent. This in turn will invoke a listener i have created which will query a database for details related to that node.
Now, I am looking for a way to somehow filter out events that is generated from node selections during "drop mode". It is an attempt to limit database calls. Does anyone have any ideas about how I can achieve this?
All input will be highly appreciated!
There is a very indirect method to detect this case. You may register a PropertyChangeListener on the property "dropLocation" with the tree component. This will be called whenever the drop location changes and thus you can set a field dropOn there which you then can read in the TreeSelectionListener.
tree.addPropertyChangeListener("dropLocation", new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent pce) {
dropOn = pce.getNewValue() != null;
}
});
tree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
public void valueChanged(TreeSelectionEvent tse) {
System.out.println(tse + " dropOn=" + dropOn);
}
});
Note that this does fire a wrong false value for the first time it enters the tree, but all subsequent events then show dropOn = true.