Treat node selection as events

This commit is contained in:
Clément Fournier 2019-01-28 17:06:33 +01:00
parent 613eb9601c
commit c11d3abe80
9 changed files with 288 additions and 95 deletions

View File

@ -17,7 +17,9 @@ import java.util.Stack;
import java.util.stream.Collectors;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.mutable.MutableInt;
import org.reactfx.value.Val;
import org.reactfx.value.Var;
import net.sourceforge.pmd.lang.LanguageVersion;
import net.sourceforge.pmd.lang.ast.Node;
@ -25,14 +27,17 @@ import net.sourceforge.pmd.lang.symboltable.NameOccurrence;
import net.sourceforge.pmd.util.fxdesigner.model.XPathEvaluationException;
import net.sourceforge.pmd.util.fxdesigner.popups.EventLogController;
import net.sourceforge.pmd.util.fxdesigner.util.AbstractController;
import net.sourceforge.pmd.util.fxdesigner.util.CompositeSelectionSource;
import net.sourceforge.pmd.util.fxdesigner.util.DesignerUtil;
import net.sourceforge.pmd.util.fxdesigner.util.LimitedSizeStack;
import net.sourceforge.pmd.util.fxdesigner.util.NodeSelectionSource;
import net.sourceforge.pmd.util.fxdesigner.util.SoftReferenceCache;
import net.sourceforge.pmd.util.fxdesigner.util.TextAwareNodeWrapper;
import net.sourceforge.pmd.util.fxdesigner.util.beans.SettingsPersistenceUtil;
import net.sourceforge.pmd.util.fxdesigner.util.beans.SettingsPersistenceUtil.PersistentProperty;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableSet;
import javafx.fxml.FXML;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
@ -62,7 +67,7 @@ import javafx.stage.FileChooser;
* @since 6.0.0
*/
@SuppressWarnings("PMD.UnusedPrivateField")
public class MainDesignerController extends AbstractController {
public class MainDesignerController extends AbstractController implements CompositeSelectionSource {
/**
* Callback to the owner.
@ -145,8 +150,28 @@ public class MainDesignerController extends AbstractController {
updateRecentFilesMenu();
refreshAST(); // initial refreshing
sourceEditorController.moveCaret(0, 0);
MutableInt mutableInt = new MutableInt();
Var<Boolean> canProcessEvent = Var.newSimpleVar(true);
// .conditionOn(canProcessEvent)
// .distinct()
getSelectionEvents().hook(n -> System.out.println(mutableInt.incrementAndGet() + ": " + n))
.subscribe(n -> {
canProcessEvent.setValue(false);
CompositeSelectionSource.super.select(n);
// onNodeItemSelected(n.getSelection());
canProcessEvent.setValue(true);
});
}
@Override
public ObservableSet<? extends NodeSelectionSource> getComponents() {
return FXCollections.observableSet(nodeInfoPanelController, sourceEditorController, xpathPanelController);
}
public void shutdown() {
try {
SettingsPersistenceUtil.persistProperties(this, DesignerUtil.getSettingsFile());
@ -193,19 +218,8 @@ public class MainDesignerController extends AbstractController {
* Executed when the user selects a node in a treeView or listView.
*/
public void onNodeItemSelected(Node selectedValue) {
onNodeItemSelected(selectedValue, false);
}
/**
* Executed when the user selects a node in a treeView or listView.
*
* @param isFromNameDecl Whether the node was selected in the scope hierarchy treeview
*/
public void onNodeItemSelected(Node selectedValue, boolean isFromNameDecl) {
// doing that in parallel speeds it up
Platform.runLater(() -> nodeInfoPanelController.setFocusNode(selectedValue, isFromNameDecl));
Platform.runLater(() -> sourceEditorController.setFocusNode(selectedValue));
nodeInfoPanelController.setFocusNode(selectedValue);
sourceEditorController.setFocusNode(selectedValue);
}

View File

@ -17,6 +17,7 @@ import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.reactfx.EventStream;
import org.reactfx.EventStreams;
import org.reactfx.value.Var;
@ -30,6 +31,7 @@ import net.sourceforge.pmd.lang.symboltable.Scope;
import net.sourceforge.pmd.lang.symboltable.ScopedNode;
import net.sourceforge.pmd.util.fxdesigner.model.MetricResult;
import net.sourceforge.pmd.util.fxdesigner.util.AbstractController;
import net.sourceforge.pmd.util.fxdesigner.util.NodeSelectionSource;
import net.sourceforge.pmd.util.fxdesigner.util.beans.SettingsPersistenceUtil.PersistentProperty;
import net.sourceforge.pmd.util.fxdesigner.util.controls.ScopeHierarchyTreeCell;
import net.sourceforge.pmd.util.fxdesigner.util.controls.ScopeHierarchyTreeItem;
@ -55,7 +57,7 @@ import javafx.scene.control.TreeView;
* @since 6.0.0
*/
@SuppressWarnings("PMD.UnusedPrivateField")
public class NodeInfoPanelController extends AbstractController {
public class NodeInfoPanelController extends AbstractController implements NodeSelectionSource {
private final MainDesignerController parent;
@ -91,13 +93,6 @@ public class NodeInfoPanelController extends AbstractController {
xpathAttributesListView.setPlaceholder(new Label("No available attributes"));
EventStreams.valuesOf(scopeHierarchyTreeView.getSelectionModel().selectedItemProperty())
.filter(Objects::nonNull)
.map(TreeItem::getValue)
.filterMap(o -> o instanceof NameDeclaration, o -> (NameDeclaration) o)
.filter(nd -> !Objects.equals(nd.getNode(), selectedNode))
.subscribe(declaration -> Platform.runLater(() -> parent.onNodeItemSelected(declaration.getNode(), true)));
scopeHierarchyTreeView.setCellFactory(view -> new ScopeHierarchyTreeCell());
hideCommonAttributesProperty()
@ -108,20 +103,25 @@ public class NodeInfoPanelController extends AbstractController {
}
/** <pre>{@linkplain #setFocusNode(Node, boolean) setFocusNode}(node, false)</pre> */
public void setFocusNode(Node node) {
setFocusNode(node, false);
@Override
public EventStream<NodeSelectionEvent> getSelectionEvents() {
return EventStreams.valuesOf(scopeHierarchyTreeView.getSelectionModel().selectedItemProperty())
.filter(Objects::nonNull)
.map(TreeItem::getValue)
.filterMap(o -> o instanceof NameDeclaration, o -> (NameDeclaration) o)
.map(NameDeclaration::getNode)
.map(n -> new NodeSelectionEvent(n, this));
}
/**
* Displays info about a node. If null, the panels are reset.
*
* @param node Node to inspect
* @param isFromNameDecl Whether the node was selected in the scope hierarchy treeview.
* If so we'll attempt to preserve that selection.
* @param node Node to inspect
*/
public void setFocusNode(Node node, boolean isFromNameDecl) {
@Override
public void setFocusNode(Node node) {
if (node == null) {
invalidateInfo();
return;
@ -132,15 +132,14 @@ public class NodeInfoPanelController extends AbstractController {
}
selectedNode = node;
displayAttributes(node);
displayMetrics(node);
displayScopes(node, isFromNameDecl);
Platform.runLater(() -> displayAttributes(node));
Platform.runLater(() -> displayMetrics(node));
displayScopes(node);
if (node instanceof ScopedNode) {
// not null as well
highlightNameOccurences((ScopedNode) node);
}
}
@ -198,7 +197,7 @@ public class NodeInfoPanelController extends AbstractController {
}
private void displayScopes(Node node, boolean focusScopeView) {
private void displayScopes(Node node) {
// current selection
TreeItem<Object> previousSelection = scopeHierarchyTreeView.getSelectionModel().getSelectedItem();
@ -206,7 +205,7 @@ public class NodeInfoPanelController extends AbstractController {
ScopeHierarchyTreeItem rootScope = ScopeHierarchyTreeItem.buildAscendantHierarchy(node);
scopeHierarchyTreeView.setRoot(rootScope);
if (focusScopeView && previousSelection != null) {
if (previousSelection != null) {
// Try to find the node that was previously selected and focus it in the new ascendant hierarchy.
// Otherwise, when you select a node in the scope tree, since focus of the app is shifted to that
// node, the scope hierarchy is reset and you lose the selection - even though obviously the node

View File

@ -33,7 +33,9 @@ import net.sourceforge.pmd.util.fxdesigner.model.ASTManager;
import net.sourceforge.pmd.util.fxdesigner.model.ParseAbortedException;
import net.sourceforge.pmd.util.fxdesigner.popups.AuxclasspathSetupController;
import net.sourceforge.pmd.util.fxdesigner.util.AbstractController;
import net.sourceforge.pmd.util.fxdesigner.util.CompositeSelectionSource;
import net.sourceforge.pmd.util.fxdesigner.util.DesignerUtil;
import net.sourceforge.pmd.util.fxdesigner.util.NodeSelectionSource;
import net.sourceforge.pmd.util.fxdesigner.util.TextAwareNodeWrapper;
import net.sourceforge.pmd.util.fxdesigner.util.beans.SettingsPersistenceUtil.PersistentProperty;
import net.sourceforge.pmd.util.fxdesigner.util.codearea.AvailableSyntaxHighlighters;
@ -45,6 +47,8 @@ import net.sourceforge.pmd.util.fxdesigner.util.controls.NodeParentageCrumbBar;
import net.sourceforge.pmd.util.fxdesigner.util.controls.ToolbarTitledPane;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableSet;
import javafx.css.PseudoClass;
import javafx.fxml.FXML;
import javafx.scene.control.MenuButton;
@ -58,7 +62,7 @@ import javafx.scene.control.ToggleGroup;
* @author Clément Fournier
* @since 6.0.0
*/
public class SourceEditorController extends AbstractController {
public class SourceEditorController extends AbstractController implements CompositeSelectionSource {
private static final Duration AST_REFRESH_DELAY = Duration.ofMillis(100);
@ -132,21 +136,12 @@ public class SourceEditorController extends AbstractController {
codeEditorArea.setParagraphGraphicFactory(lineNumberFactory());
astTreeView.onNodeClickedHandlerProperty().setValue(parent::onNodeItemSelected);
}
@Override
protected void afterParentInit() {
DesignerUtil.rewire(astManager.languageVersionProperty(), languageVersionUIProperty);
// Focus the crumb
focusNodeParentageCrumbBar.setOnRegularCrumbAction(treeitem -> {
if (treeitem != null && treeitem.getValue() != null) {
astTreeView.focusNode(treeitem.getValue());
}
});
}
@ -196,6 +191,12 @@ public class SourceEditorController extends AbstractController {
}
@Override
public ObservableSet<? extends NodeSelectionSource> getComponents() {
return FXCollections.observableSet(astTreeView, focusNodeParentageCrumbBar);
}
/**
* Refreshes the AST and returns the new compilation unit if the parse didn't fail.
*/
@ -263,20 +264,21 @@ public class SourceEditorController extends AbstractController {
* Highlights the given node (or nothing if null).
* Removes highlighting on the previously highlighted node.
*/
@Override
public void setFocusNode(Node node) {
if (Objects.equals(node, currentFocusNode.getValue())) {
return;
}
codeEditorArea.styleNodes(node == null ? emptyList() : singleton(node), StyleLayerIds.FOCUS, true);
// editor is always scrolled when re-selecting a node
if (node != null) {
Platform.runLater(() -> scrollEditorToNode(node));
}
if (Objects.equals(node, currentFocusNode.getValue())) {
return;
}
currentFocusNode.setValue(node);
Platform.runLater(() -> astTreeView.focusNode(node));
focusNodeParentageCrumbBar.setFocusNode(node);
// editor is only restyled if the selection has changed
Platform.runLater(() -> codeEditorArea.styleNodes(node == null ? emptyList() : singleton(node), StyleLayerIds.FOCUS, true));
}

View File

@ -17,6 +17,7 @@ import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.controlsfx.validation.ValidationSupport;
import org.controlsfx.validation.Validator;
import org.reactfx.EventStream;
import org.reactfx.EventStreams;
import org.reactfx.collection.LiveArrayList;
import org.reactfx.value.Val;
@ -34,6 +35,7 @@ import net.sourceforge.pmd.util.fxdesigner.model.XPathEvaluator;
import net.sourceforge.pmd.util.fxdesigner.popups.ExportXPathWizardController;
import net.sourceforge.pmd.util.fxdesigner.util.AbstractController;
import net.sourceforge.pmd.util.fxdesigner.util.DesignerUtil;
import net.sourceforge.pmd.util.fxdesigner.util.NodeSelectionSource;
import net.sourceforge.pmd.util.fxdesigner.util.TextAwareNodeWrapper;
import net.sourceforge.pmd.util.fxdesigner.util.autocomplete.CompletionResultSource;
import net.sourceforge.pmd.util.fxdesigner.util.autocomplete.XPathAutocompleteProvider;
@ -74,7 +76,7 @@ import javafx.stage.StageStyle;
* @see ExportXPathWizardController
* @since 6.0.0
*/
public class XPathPanelController extends AbstractController {
public class XPathPanelController extends AbstractController implements NodeSelectionSource {
private static final Duration XPATH_REFRESH_DELAY = Duration.ofMillis(100);
private final DesignerRoot designerRoot;
@ -123,12 +125,6 @@ public class XPathPanelController extends AbstractController {
exportXpathToRuleButton.setOnAction(e -> showExportXPathToRuleWizard());
EventStreams.valuesOf(xpathResultListView.getSelectionModel().selectedItemProperty())
.conditionOn(xpathResultListView.focusedProperty())
.filter(Objects::nonNull)
.map(TextAwareNodeWrapper::getNode)
.subscribe(parent::onNodeItemSelected);
xpathExpressionArea.richChanges()
.filter(t -> !t.isIdentity())
.successionEnds(XPATH_REFRESH_DELAY)
@ -226,6 +222,23 @@ public class XPathPanelController extends AbstractController {
}
@Override
public EventStream<NodeSelectionEvent> getSelectionEvents() {
return EventStreams.valuesOf(xpathResultListView.getSelectionModel().selectedItemProperty())
.conditionOn(xpathResultListView.focusedProperty())
.filter(Objects::nonNull)
.map(TextAwareNodeWrapper::getNode)
.map(n -> new NodeSelectionEvent(n, this));
}
@Override
public void setFocusNode(Node node) {
xpathResultListView.getItems().stream()
.filter(wrapper -> wrapper.getNode().equals(node))
.findFirst()
.ifPresent(xpathResultListView.getSelectionModel()::select);
}
/**
@ -256,10 +269,10 @@ public class XPathPanelController extends AbstractController {
parent.highlightXPathResults(results);
violationsTitledPane.setTitle("Matched nodes (" + results.size() + ")");
// Notify that everything went OK so we can avoid logging very recent exceptions
designerRoot.getLogger().logEvent(new LogEntry(null, Category.XPATH_OK));
designerRoot.getLogger().logEvent(LogEntry.createExceptionEntry(null, Category.XPATH_OK));
} catch (XPathEvaluationException e) {
invalidateResults(true);
designerRoot.getLogger().logEvent(new LogEntry(e, Category.XPATH_EVALUATION_EXCEPTION));
designerRoot.getLogger().logEvent(LogEntry.createExceptionEntry(e, Category.XPATH_EVALUATION_EXCEPTION));
}
}
@ -341,4 +354,6 @@ public class XPathPanelController extends AbstractController {
public List<SettingsOwner> getChildrenSettingsNodes() {
return Collections.singletonList(getRuleBuilder());
}
}

View File

@ -0,0 +1,11 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.util.fxdesigner.util;
/**
* @author Clément Fournier
*/
public class AbstractNodeSelectingController {
}

View File

@ -0,0 +1,50 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.util.fxdesigner.util;
import org.reactfx.EventStream;
import org.reactfx.EventStreams;
import net.sourceforge.pmd.lang.ast.Node;
import javafx.collections.ObservableSet;
/**
* @author Clément Fournier
*/
public interface CompositeSelectionSource extends NodeSelectionSource {
ObservableSet<? extends NodeSelectionSource> getComponents();
@Override
default EventStream<NodeSelectionEvent> getSelectionEvents() {
return EventStreams.merge(getComponents(), NodeSelectionSource::getSelectionEvents);
}
@Override
default void setFocusNode(Node node) {
// by default do nothing,
// maybe it should only be handled by the components
}
@Override
default void select(NodeSelectionEvent selectionEvent) {
System.out.println("\t" + this.getClass().getSimpleName() + " handling " + selectionEvent);
for (NodeSelectionSource source : getComponents()) {
if (!selectionEvent.getOrigin().equals(source)) {
source.select(selectionEvent);
}
}
if (!this.equals(selectionEvent.getOrigin())) {
setFocusNode(selectionEvent.getSelection());
}
}
}

View File

@ -0,0 +1,96 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.util.fxdesigner.util;
import java.util.Objects;
import org.reactfx.EventStream;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.util.fxdesigner.MainDesignerController;
import net.sourceforge.pmd.util.fxdesigner.util.controls.AstTreeView;
import net.sourceforge.pmd.util.fxdesigner.util.controls.NodeParentageCrumbBar;
/**
* A control or controller that has the ability to push node selection events.
* When a node is selected in the control (e.g. {@link AstTreeView}, {@link NodeParentageCrumbBar}, etc),
* the whole UI is synchronized to the node. Selection events are merged iteratively into
* a global stream for the whole app. Events from that stream are handled by {@link MainDesignerController}.
*
* @author Clément Fournier
*/
public interface NodeSelectionSource {
/**
* Returns a stream of nodes that pushes an event every time
* this control records a *user* change in selection.
*/
EventStream<NodeSelectionEvent> getSelectionEvents();
default void select(NodeSelectionEvent selectionEvent) {
System.out.println("\t\t" + this.getClass().getSimpleName() + " handling " + selectionEvent);
if (selectionEvent.getOrigin() != this) {
setFocusNode(selectionEvent.getSelection());
} else {
System.out.println("\tUnhandled");
}
}
void setFocusNode(Node node);
final class NodeSelectionEvent {
private final Node selection;
private final NodeSelectionSource origin;
public NodeSelectionEvent(Node selection, NodeSelectionSource origin) {
this.selection = selection;
this.origin = origin;
}
public Node getSelection() {
return selection;
}
public NodeSelectionSource getOrigin() {
return origin;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
NodeSelectionEvent that = (NodeSelectionEvent) o;
return Objects.equals(selection, that.selection) &&
Objects.equals(origin, that.origin);
}
@Override
public int hashCode() {
return Objects.hash(selection, origin);
}
@Override
public String toString() {
return getSelection().getXPathNodeName() + "(" + hashCode() + ")\t\tfrom " + getOrigin().getClass().getSimpleName();
}
}
}

View File

@ -8,15 +8,16 @@ import static net.sourceforge.pmd.internal.util.IteratorUtil.toIterable;
import static net.sourceforge.pmd.util.fxdesigner.util.DesignerIteratorUtil.parentIterator;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.reactfx.EventSource;
import org.reactfx.EventStream;
import org.reactfx.EventStreams;
import org.reactfx.Subscription;
import org.reactfx.value.Var;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.util.fxdesigner.util.NodeSelectionSource;
import javafx.scene.control.SelectionModel;
import javafx.scene.control.TreeItem;
@ -27,38 +28,43 @@ import javafx.scene.control.TreeView;
* @author Clément Fournier
* @since 7.0.0
*/
public class AstTreeView extends TreeView<Node> {
public class AstTreeView extends TreeView<Node> implements NodeSelectionSource {
private final Var<Consumer<Node>> onNodeClickedHandler = Var.newSimpleVar(n -> {});
private final TreeViewWrapper<Node> myWrapper = new TreeViewWrapper<>(this);
private Subscription myNodeSelectedSub;
private ASTTreeItem selectedTreeItem;
private final EventSource<Node> selectionEvents = new EventSource<>();
public AstTreeView() {
// push a node selection event whenever...
// * The selection changes
EventStreams.valuesOf(getSelectionModel().selectedItemProperty())
.filterMap(Objects::nonNull, TreeItem::getValue)
.subscribe(selectionEvents::push);
// * a cell is explicitly clicked. This catches the case where the cell was already selected
setCellFactory(tv -> new ASTTreeCell(n -> {
// only push an event if the node was already selected
if (selectedTreeItem != null && selectedTreeItem.getValue() != null && selectedTreeItem.getValue().equals(n)) {
selectionEvents.push(n);
}
}));
onNodeClickedHandler.values()
.subscribe(handler -> {
setCellFactory(tv -> new ASTTreeCell(handler));
Optional.ofNullable(myNodeSelectedSub).ifPresent(Subscription::unsubscribe);
myNodeSelectedSub = EventStreams.valuesOf(getSelectionModel().selectedItemProperty())
.filterMap(Objects::nonNull, TreeItem::getValue)
.subscribe(handler);
});
}
@Override
public EventStream<NodeSelectionEvent> getSelectionEvents() {
return selectionEvents.map(n -> new NodeSelectionEvent(n, this));
}
/**
* Focus the given node, handling scrolling if needed.
*/
public void focusNode(Node node) {
@Override
public void setFocusNode(Node node) {
SelectionModel<TreeItem<Node>> selectionModel = getSelectionModel();
if (selectedTreeItem == null && node != null

View File

@ -9,13 +9,15 @@ import static net.sourceforge.pmd.internal.util.IteratorUtil.asReversed;
import static net.sourceforge.pmd.internal.util.IteratorUtil.count;
import static net.sourceforge.pmd.util.fxdesigner.util.DesignerIteratorUtil.parentIterator;
import java.util.function.Consumer;
import java.util.function.Function;
import org.controlsfx.control.BreadCrumbBar;
import org.reactfx.EventSource;
import org.reactfx.EventStream;
import org.reactfx.value.Val;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.util.fxdesigner.util.NodeSelectionSource;
import javafx.application.Platform;
import javafx.css.PseudoClass;
@ -35,7 +37,7 @@ import javafx.util.Callback;
* @author Clément Fournier
* @since 7.0.0
*/
public class NodeParentageCrumbBar extends BreadCrumbBar<Node> {
public class NodeParentageCrumbBar extends BreadCrumbBar<Node> implements NodeSelectionSource {
private static final int DEFAULT_PX_BY_CHAR = 5;
private static final int DEFAULT_CONSTANT_PADDING = 19;
@ -44,7 +46,7 @@ public class NodeParentageCrumbBar extends BreadCrumbBar<Node> {
private final TreeItem<Node> ellipsisCrumb = new TreeItem<>(null);
/** number of nodes currently behind the ellipsis */
private int numElidedNodes = 0;
private final EventSource<Node> selectionEvents = new EventSource<>();
public NodeParentageCrumbBar() {
// This allows to click on a parent crumb and keep the children crumb
@ -53,6 +55,12 @@ public class NodeParentageCrumbBar extends BreadCrumbBar<Node> {
// captured in the closure
final Callback<TreeItem<Node>, Button> originalCrumbFactory = getCrumbFactory();
setOnCrumbAction(ev -> {
if (ev.getSelectedCrumb() != ellipsisCrumb) {
selectionEvents.push(ev.getSelectedCrumb().getValue());
}
});
setCrumbFactory(item -> {
Button button = originalCrumbFactory.call(item);
if (item == ellipsisCrumb) {
@ -74,18 +82,9 @@ public class NodeParentageCrumbBar extends BreadCrumbBar<Node> {
}
/**
* Set a handler that executes when the user selects a crumb other than the ellipsis.
* This shouldn't be calling {@link #setFocusNode(Node)} on the same node otherwise
* the crumb bar will set the deepest node to the node and the children won't be
* available.
*/
public void setOnRegularCrumbAction(Consumer<TreeItem<Node>> handler) {
setOnCrumbAction(e -> {
if (e.getSelectedCrumb() != ellipsisCrumb) {
handler.accept(e.getSelectedCrumb());
}
});
@Override
public EventStream<NodeSelectionEvent> getSelectionEvents() {
return selectionEvents.map(n -> new NodeSelectionEvent(n, this));
}
// getSelectedCrumb gets the deepest displayed node
@ -96,6 +95,7 @@ public class NodeParentageCrumbBar extends BreadCrumbBar<Node> {
* sets the focus on it. Otherwise, sets the node to be
* the deepest one of the crumb bar. Noop if node is null.
*/
@Override
public void setFocusNode(Node node) {
if (node == null) {
return;