Merge commit '33513f49d' into designer-breadcrumbbar

This commit is contained in:
Clément Fournier 2019-01-24 00:34:26 +01:00
commit 3de2d87abf
5 changed files with 160 additions and 108 deletions

View File

@ -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. */

View File

@ -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();
}
});

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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>