Merge commit '33513f49d' into designer-breadcrumbbar
This commit is contained in:
commit
3de2d87abf
@ -6,8 +6,6 @@ package net.sourceforge.pmd.util.fxdesigner;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.singleton;
|
||||
import static net.sourceforge.pmd.util.fxdesigner.util.IteratorUtil.parentIterator;
|
||||
import static net.sourceforge.pmd.util.fxdesigner.util.IteratorUtil.toIterable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@ -18,13 +16,11 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.IntFunction;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.fxmisc.richtext.LineNumberFactory;
|
||||
import org.reactfx.EventStreams;
|
||||
import org.reactfx.value.Val;
|
||||
import org.reactfx.value.Var;
|
||||
|
||||
@ -43,22 +39,17 @@ import net.sourceforge.pmd.util.fxdesigner.util.beans.SettingsPersistenceUtil.Pe
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.codearea.AvailableSyntaxHighlighters;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.codearea.HighlightLayerCodeArea;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.codearea.HighlightLayerCodeArea.LayerId;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.controls.ASTTreeCell;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.controls.ASTTreeItem;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.controls.NodeParentageCrumbBar;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.controls.ASTTreeView;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.controls.ToolbarTitledPane;
|
||||
import net.sourceforge.pmd.util.fxdesigner.util.controls.TreeViewWrapper;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.MenuButton;
|
||||
import javafx.scene.control.RadioMenuItem;
|
||||
import javafx.scene.control.SelectionModel;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.control.TreeItem;
|
||||
import javafx.scene.control.TreeView;
|
||||
|
||||
|
||||
/**
|
||||
@ -71,28 +62,24 @@ public class SourceEditorController extends AbstractController {
|
||||
|
||||
private static final Duration AST_REFRESH_DELAY = Duration.ofMillis(100);
|
||||
|
||||
@FXML
|
||||
private ToolbarTitledPane astTitledPane;
|
||||
@FXML
|
||||
private ToolbarTitledPane editorTitledPane;
|
||||
@FXML
|
||||
private MenuButton languageSelectionMenuButton;
|
||||
@FXML
|
||||
private Label sourceCodeTitleLabel;
|
||||
@FXML
|
||||
private Label astTitleLabel;
|
||||
@FXML
|
||||
private TreeView<Node> astTreeView;
|
||||
private ASTTreeView astTreeView;
|
||||
@FXML
|
||||
private HighlightLayerCodeArea<StyleLayerIds> codeEditorArea;
|
||||
@FXML
|
||||
private NodeParentageCrumbBar focusNodeParentageBreadCrumbBar;
|
||||
|
||||
private final ASTManager astManager;
|
||||
private TreeViewWrapper<Node> treeViewWrapper;
|
||||
|
||||
private final MainDesignerController parent;
|
||||
|
||||
private final Var<Node> currentFocusNode = Var.newSimpleVar(null);
|
||||
private ASTTreeItem selectedTreeItem;
|
||||
|
||||
private final Var<List<File>> auxclasspathFiles = Var.newSimpleVar(emptyList());
|
||||
private final Val<ClassLoader> auxclasspathClassLoader = auxclasspathFiles.map(fileList -> {
|
||||
@ -116,8 +103,6 @@ public class SourceEditorController extends AbstractController {
|
||||
|
||||
@Override
|
||||
protected void beforeParentInit() {
|
||||
treeViewWrapper = new TreeViewWrapper<>(astTreeView);
|
||||
astTreeView.setCellFactory(treeView -> new ASTTreeCell(parent));
|
||||
|
||||
initializeLanguageSelector(); // languageVersionProperty() must be initialized
|
||||
|
||||
@ -132,9 +117,6 @@ public class SourceEditorController extends AbstractController {
|
||||
.map(lang -> "Source Code (" + lang + ")")
|
||||
.subscribe(editorTitledPane::setTitle);
|
||||
|
||||
EventStreams.valuesOf(astTreeView.getSelectionModel().selectedItemProperty())
|
||||
.filterMap(Objects::nonNull, TreeItem::getValue)
|
||||
.subscribe(parent::onNodeItemSelected);
|
||||
|
||||
codeEditorArea.plainTextChanges()
|
||||
.filter(t -> !t.isIdentity())
|
||||
@ -150,6 +132,8 @@ public class SourceEditorController extends AbstractController {
|
||||
|
||||
codeEditorArea.setParagraphGraphicFactory(lineNumberFactory());
|
||||
|
||||
astTreeView.onNodeClickedHandlerProperty().setValue(parent::onNodeItemSelected);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -160,8 +144,7 @@ public class SourceEditorController extends AbstractController {
|
||||
// Focus the crumb
|
||||
focusNodeParentageBreadCrumbBar.setOnRegularCrumbAction(treeitem -> {
|
||||
if (treeitem != null && treeitem.getValue() != null) {
|
||||
|
||||
focusNodeInTreeView(treeitem.getValue());
|
||||
astTreeView.focusNode(treeitem.getValue());
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -229,7 +212,7 @@ public class SourceEditorController extends AbstractController {
|
||||
try {
|
||||
current = astManager.updateIfChanged(source, auxclasspathClassLoader.getValue());
|
||||
} catch (ParseAbortedException e) {
|
||||
astTitleLabel.setText("Abstract syntax tree (error)");
|
||||
astTitledPane.setTitle("Abstract syntax tree (error)");
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@ -246,7 +229,7 @@ public class SourceEditorController extends AbstractController {
|
||||
|
||||
private void setUpToDateCompilationUnit(Node node) {
|
||||
parent.invalidateAst();
|
||||
astTitleLabel.setText("Abstract syntax tree");
|
||||
astTitledPane.setTitle("Abstract syntax tree");
|
||||
ASTTreeItem root = ASTTreeItem.getRoot(node);
|
||||
astTreeView.setRoot(root);
|
||||
}
|
||||
@ -258,15 +241,15 @@ public class SourceEditorController extends AbstractController {
|
||||
}
|
||||
|
||||
|
||||
/** Clears the name occurences. */
|
||||
/** Clears the error nodes. */
|
||||
public void clearErrorNodes() {
|
||||
codeEditorArea.clearStyleLayer(StyleLayerIds.ERROR);
|
||||
}
|
||||
|
||||
|
||||
/** Clears the name occurences. */
|
||||
/** Clears the name occurrences. */
|
||||
public void clearNameOccurences() {
|
||||
codeEditorArea.clearStyleLayer(StyleLayerIds.ERROR);
|
||||
codeEditorArea.clearStyleLayer(StyleLayerIds.NAME_OCCURENCE);
|
||||
}
|
||||
|
||||
|
||||
@ -292,7 +275,8 @@ public class SourceEditorController extends AbstractController {
|
||||
}
|
||||
|
||||
currentFocusNode.setValue(node);
|
||||
Platform.runLater(() -> focusNodeInTreeView(node));
|
||||
Platform.runLater(() -> astTreeView.focusNode(node));
|
||||
focusNodeParentageCrumbBar.setFocusNode(node);
|
||||
}
|
||||
|
||||
|
||||
@ -342,55 +326,7 @@ public class SourceEditorController extends AbstractController {
|
||||
}
|
||||
|
||||
|
||||
private void focusNodeInTreeView(Node node) {
|
||||
SelectionModel<TreeItem<Node>> selectionModel = astTreeView.getSelectionModel();
|
||||
|
||||
if (node != null
|
||||
&& (selectedTreeItem == null || !Objects.equals(node, selectedTreeItem.getValue()))) {
|
||||
// node is different from the old one
|
||||
// && node is not null
|
||||
|
||||
ASTTreeItem found = ((ASTTreeItem) astTreeView.getRoot()).findItem(node);
|
||||
if (found != null) {
|
||||
selectionModel.select(found);
|
||||
}
|
||||
|
||||
highlightFocusNodeParents(selectedTreeItem, found);
|
||||
|
||||
selectedTreeItem = found;
|
||||
|
||||
astTreeView.getFocusModel().focus(selectionModel.getSelectedIndex());
|
||||
if (!treeViewWrapper.isIndexVisible(selectionModel.getSelectedIndex())) {
|
||||
astTreeView.scrollTo(selectionModel.getSelectedIndex());
|
||||
}
|
||||
|
||||
focusNodeParentageBreadCrumbBar.setFocusNode(node);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void sideEffectParents(ASTTreeItem deepest, BiConsumer<ASTTreeItem, Integer> itemAndDepthConsumer) {
|
||||
|
||||
int depth = 0;
|
||||
for (TreeItem<Node> item : toIterable(parentIterator(deepest, true))) {
|
||||
// the depth is "reversed" here, i.e. the deepest node has depth 0
|
||||
itemAndDepthConsumer.accept((ASTTreeItem) item, depth++);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void highlightFocusNodeParents(ASTTreeItem oldSelection, ASTTreeItem newSelection) {
|
||||
if (oldSelection != null) {
|
||||
// remove highlighting on the cells of the item
|
||||
sideEffectParents(oldSelection, (item, depth) -> item.setStyleClasses());
|
||||
}
|
||||
|
||||
if (newSelection != null) {
|
||||
// 0 is the deepest node, "depth" goes up as we get up the parents
|
||||
sideEffectParents(newSelection, (item, depth) -> item.setStyleClasses("ast-parent", "depth-" + depth));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Moves the caret to a position and makes the view follow it. */
|
||||
|
@ -7,13 +7,13 @@ package net.sourceforge.pmd.util.fxdesigner.util.controls;
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.apache.commons.beanutils.PropertyUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.reactfx.value.Val;
|
||||
|
||||
import net.sourceforge.pmd.lang.ast.Node;
|
||||
import net.sourceforge.pmd.util.fxdesigner.MainDesignerController;
|
||||
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.CustomMenuItem;
|
||||
@ -34,11 +34,11 @@ import javafx.scene.input.MouseEvent;
|
||||
*/
|
||||
public class ASTTreeCell extends TreeCell<Node> {
|
||||
|
||||
private final MainDesignerController controller;
|
||||
private final Consumer<Node> onNodeItemSelected;
|
||||
|
||||
|
||||
public ASTTreeCell(MainDesignerController controller) {
|
||||
this.controller = controller;
|
||||
public ASTTreeCell(Consumer<Node> clickHandler) {
|
||||
this.onNodeItemSelected = clickHandler;
|
||||
|
||||
// Binds the cell to its treeItem
|
||||
Val.wrap(treeItemProperty())
|
||||
@ -209,7 +209,7 @@ public class ASTTreeCell extends TreeCell<Node> {
|
||||
this.addEventHandler(MouseEvent.MOUSE_CLICKED, t -> {
|
||||
if (t.getButton() == MouseButton.PRIMARY
|
||||
&& getTreeView().getSelectionModel().getSelectedItem().getValue() == item) {
|
||||
controller.onNodeItemSelected(item);
|
||||
onNodeItemSelected.accept(item);
|
||||
t.consume();
|
||||
}
|
||||
});
|
||||
|
@ -0,0 +1,122 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.util.fxdesigner.util.controls;
|
||||
|
||||
import static net.sourceforge.pmd.util.fxdesigner.util.IteratorUtil.parentIterator;
|
||||
import static net.sourceforge.pmd.util.fxdesigner.util.IteratorUtil.toIterable;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.reactfx.EventStreams;
|
||||
import org.reactfx.Subscription;
|
||||
import org.reactfx.value.Var;
|
||||
|
||||
import net.sourceforge.pmd.lang.ast.Node;
|
||||
|
||||
import javafx.scene.control.SelectionModel;
|
||||
import javafx.scene.control.TreeItem;
|
||||
import javafx.scene.control.TreeView;
|
||||
|
||||
|
||||
/**
|
||||
* @author Clément Fournier
|
||||
* @since 7.0.0
|
||||
*/
|
||||
public class ASTTreeView extends TreeView<Node> {
|
||||
|
||||
|
||||
private final Var<Consumer<Node>> onNodeClickedHandler = Var.newSimpleVar(n -> {});
|
||||
private final TreeViewWrapper<Node> myWrapper = new TreeViewWrapper<>(this);
|
||||
|
||||
private Subscription myNodeSelectedSub;
|
||||
private ASTTreeItem selectedTreeItem;
|
||||
|
||||
|
||||
public ASTTreeView() {
|
||||
|
||||
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);
|
||||
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Focus the given node, handling scrolling if needed.
|
||||
*/
|
||||
public void focusNode(Node node) {
|
||||
SelectionModel<TreeItem<Node>> selectionModel = getSelectionModel();
|
||||
|
||||
if (selectedTreeItem == null && node != null
|
||||
|| selectedTreeItem != null && !Objects.equals(node, selectedTreeItem.getValue())) {
|
||||
// node is different from the old one
|
||||
// && node is not null
|
||||
|
||||
ASTTreeItem found = ((ASTTreeItem) getRoot()).findItem(node);
|
||||
if (found != null) {
|
||||
selectionModel.select(found);
|
||||
}
|
||||
|
||||
highlightFocusNodeParents(selectedTreeItem, found);
|
||||
|
||||
selectedTreeItem = found;
|
||||
|
||||
getFocusModel().focus(selectionModel.getSelectedIndex());
|
||||
if (!isIndexVisible(selectionModel.getSelectedIndex())) {
|
||||
scrollTo(selectionModel.getSelectedIndex());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void highlightFocusNodeParents(ASTTreeItem oldSelection, ASTTreeItem newSelection) {
|
||||
if (oldSelection != null) {
|
||||
// remove highlighting on the cells of the item
|
||||
sideEffectParents(oldSelection, (item, depth) -> item.setStyleClasses());
|
||||
}
|
||||
|
||||
if (newSelection != null) {
|
||||
// 0 is the deepest node, "depth" goes up as we get up the parents
|
||||
sideEffectParents(newSelection, (item, depth) -> item.setStyleClasses("ast-parent", "depth-" + depth));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void sideEffectParents(ASTTreeItem deepest, BiConsumer<ASTTreeItem, Integer> itemAndDepthConsumer) {
|
||||
|
||||
int depth = 0;
|
||||
for (TreeItem<Node> item : toIterable(parentIterator(deepest, true))) {
|
||||
// the depth is "reversed" here, i.e. the deepest node has depth 0
|
||||
itemAndDepthConsumer.accept((ASTTreeItem) item, depth++);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if the item at the given index
|
||||
* is visible in the TreeView.
|
||||
*/
|
||||
public boolean isIndexVisible(int index) {
|
||||
return myWrapper.isIndexVisible(index);
|
||||
}
|
||||
|
||||
|
||||
public Var<Consumer<Node>> onNodeClickedHandlerProperty() {
|
||||
return onNodeClickedHandler;
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@ import java.util.Optional;
|
||||
import org.apache.commons.lang3.reflect.FieldUtils;
|
||||
import org.apache.commons.lang3.reflect.MethodUtils;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.TreeCell;
|
||||
import javafx.scene.control.TreeView;
|
||||
@ -28,7 +29,7 @@ import javafx.scene.control.TreeView;
|
||||
* @author Clément Fournier
|
||||
* @since 6.4.0
|
||||
*/
|
||||
public class TreeViewWrapper<T> {
|
||||
class TreeViewWrapper<T> {
|
||||
|
||||
|
||||
private final TreeView<T> wrapped;
|
||||
@ -39,10 +40,10 @@ public class TreeViewWrapper<T> {
|
||||
private Object virtualFlow = null;
|
||||
|
||||
|
||||
public TreeViewWrapper(TreeView<T> wrapped) {
|
||||
TreeViewWrapper(TreeView<T> wrapped) {
|
||||
Objects.requireNonNull(wrapped);
|
||||
this.wrapped = wrapped;
|
||||
initialiseTreeViewReflection();
|
||||
Platform.runLater(this::initialiseTreeViewReflection);
|
||||
}
|
||||
|
||||
|
||||
@ -73,7 +74,7 @@ public class TreeViewWrapper<T> {
|
||||
* Returns true if the item at the given index
|
||||
* is visible in the TreeView.
|
||||
*/
|
||||
public boolean isIndexVisible(int index) {
|
||||
boolean isIndexVisible(int index) {
|
||||
if (virtualFlow == null && wrapped.getSkin() == null) {
|
||||
return false;
|
||||
} else if (virtualFlow == null && wrapped.getSkin() != null) {
|
||||
|
@ -5,15 +5,12 @@
|
||||
|
||||
<?import org.kordamp.ikonli.javafx.FontIcon?>
|
||||
<?import net.sourceforge.pmd.util.fxdesigner.util.codearea.HighlightLayerCodeArea?>
|
||||
<?import net.sourceforge.pmd.util.fxdesigner.util.controls.ASTTreeView?>
|
||||
<?import net.sourceforge.pmd.util.fxdesigner.util.controls.NodeParentageCrumbBar?>
|
||||
<?import net.sourceforge.pmd.util.fxdesigner.util.controls.ToolbarTitledPane?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.MenuButton?>
|
||||
<?import javafx.scene.control.SplitPane?>
|
||||
<?import javafx.scene.control.TextArea?>
|
||||
<?import javafx.scene.control.ToolBar?>
|
||||
<?import javafx.scene.control.TreeView?>
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
<?import javafx.scene.layout.BorderPane?>
|
||||
<BorderPane xmlns="http://javafx.com/javafx/8.0.172-ea"
|
||||
@ -64,23 +61,19 @@
|
||||
</AnchorPane>
|
||||
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0">
|
||||
<children>
|
||||
<BorderPane prefHeight="200.0"
|
||||
prefWidth="200.0"
|
||||
AnchorPane.bottomAnchor="0.0"
|
||||
AnchorPane.leftAnchor="0.0"
|
||||
AnchorPane.rightAnchor="0.0"
|
||||
AnchorPane.topAnchor="0.0">
|
||||
<center>
|
||||
<TreeView fx:id="astTreeView" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER" />
|
||||
</center>
|
||||
<top>
|
||||
<ToolBar prefHeight="40.0" prefWidth="200.0" styleClass="accent-header" BorderPane.alignment="CENTER">
|
||||
<items>
|
||||
<Label fx:id="astTitleLabel" text="Abstract Syntax Tree" />
|
||||
</items>
|
||||
</ToolBar>
|
||||
</top>
|
||||
</BorderPane>
|
||||
<ToolbarTitledPane
|
||||
collapsible="false"
|
||||
title="Abstract Syntax Tree"
|
||||
fx:id="astTitledPane"
|
||||
styleClass="full-size-title"
|
||||
AnchorPane.bottomAnchor="0.0"
|
||||
AnchorPane.leftAnchor="0.0"
|
||||
AnchorPane.rightAnchor="0.0"
|
||||
AnchorPane.topAnchor="0.0">
|
||||
|
||||
<ASTTreeView fx:id="astTreeView" />
|
||||
|
||||
</ToolbarTitledPane>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
</items>
|
||||
|
Loading…
x
Reference in New Issue
Block a user