forked from phoedos/pmd
Treat node selection as events
This commit is contained in:
parent
613eb9601c
commit
c11d3abe80
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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 {
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user