Make the ast treeview autoscroll when selecting a node that's not

visible
This commit is contained in:
Clément Fournier
2018-03-30 01:34:44 +02:00
parent 855d528563
commit 5e819c5826
3 changed files with 160 additions and 2 deletions

View File

@ -74,6 +74,19 @@ check_lib_dir() {
fi
}
jre_specific_vm_options() {
# java_ver is eg "18" for java 1.8, "90" for java 9.0
java_ver=$(java -version 2>&1 | sed -n ';s/.* version "\(.*\)\.\(.*\)\..*"/\1\2/p;')
if [ $java_ver -ge 90 ]
then
# Opens internal module of javafx to reflection
options="--add-opens javafx.controls/javafx.scene.control.skin=ALL-UNNAMED"
echo $options
fi
}
readonly APPNAME="${1}"
if [ -z "${APPNAME}" ]; then
usage
@ -128,4 +141,5 @@ cygwin_paths
java_heapsize_settings
java ${HEAPSIZE} -cp "${classpath}" "${CLASSNAME}" "$@"
java ${HEAPSIZE} $(jre_specific_vm_options) -cp "${classpath}" "${CLASSNAME}" "$@"

View File

@ -29,6 +29,7 @@ import net.sourceforge.pmd.util.fxdesigner.util.codearea.AvailableSyntaxHighligh
import net.sourceforge.pmd.util.fxdesigner.util.codearea.CustomCodeArea;
import net.sourceforge.pmd.util.fxdesigner.util.codearea.SyntaxHighlighter;
import net.sourceforge.pmd.util.fxdesigner.util.controls.ASTTreeItem;
import net.sourceforge.pmd.util.fxdesigner.util.controls.TreeViewWrapper;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
@ -55,16 +56,22 @@ public class SourceEditorController implements Initializable, SettingsOwner {
private CustomCodeArea codeEditorArea;
private ASTManager astManager;
private TreeViewWrapper<Node> treeViewWrapper;
public SourceEditorController(DesignerRoot owner, MainDesignerController mainController) {
parent = mainController;
astManager = new ASTManager(owner);
}
@Override
public void initialize(URL location, ResourceBundle resources) {
treeViewWrapper = new TreeViewWrapper<>(astTreeView);
languageVersionProperty().values()
.filterMap(Objects::nonNull, LanguageVersion::getLanguage)
.distinct()
@ -162,7 +169,9 @@ public class SourceEditorController implements Initializable, SettingsOwner {
SelectionModel<TreeItem<Node>> selectionModel = astTreeView.getSelectionModel();
selectionModel.select(found);
astTreeView.getFocusModel().focus(selectionModel.getSelectedIndex());
// astTreeView.scrollTo(selectionModel.getSelectedIndex());
if (!treeViewWrapper.isIndexVisible(selectionModel.getSelectedIndex())) {
astTreeView.scrollTo(selectionModel.getSelectedIndex());
}
}
}

View File

@ -0,0 +1,135 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.util.fxdesigner.util.controls;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.Optional;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.commons.lang3.reflect.MethodUtils;
import javafx.scene.control.Skin;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeView;
/**
* Reflective solution to know if a cell in a TreeView is
* visible or not, to prevent confusing scrolling. Works
* under Java 8, 9, 10. Under Java 9+, requires the
* "--add-opens javafx.controls/javafx.scene.control.skin=ALL-UNNAMED"
* VM option.
*
* @param <T> Element type of the treeview
* @author Clément Fournier
* @since 6.4.0
*/
public class TreeViewWrapper<T> {
private final TreeView<T> wrapped;
private Method treeViewFirstVisibleMethod;
private Method treeViewLastVisibleMethod;
// We can't use strong typing
// because the class has moved packages over different java versions
private Object virtualFlow = null;
public TreeViewWrapper(TreeView<T> wrapped) {
Objects.requireNonNull(wrapped);
this.wrapped = wrapped;
initialiseTreeViewReflection();
}
private void initialiseTreeViewReflection() {
// we can't use wrapped.getSkin() because it may be null.
// we don't care about the specific instance, we just want the class
Skin<?> dftSkin = new TreeView<Object>() {
@Override
protected Skin<?> createDefaultSkin() {
return super.createDefaultSkin();
}
}.createDefaultSkin();
Object flow = getVirtualFlow(dftSkin);
if (flow == null) {
return;
}
treeViewFirstVisibleMethod = MethodUtils.getMatchingMethod(flow.getClass(), "getFirstVisibleCell");
treeViewLastVisibleMethod = MethodUtils.getMatchingMethod(flow.getClass(), "getLastVisibleCell");
}
/**
* Returns true if the item at the given index
* is visible in the TreeView.
*/
public boolean isIndexVisible(int index) {
if (virtualFlow == null && wrapped.getSkin() == null) {
return false;
} else if (virtualFlow == null && wrapped.getSkin() != null) {
// the flow is cached, so the skin must not be changed
virtualFlow = getVirtualFlow(wrapped.getSkin());
}
if (virtualFlow == null) {
return false;
}
Optional<TreeCell<T>> first = getFirstVisibleCell();
Optional<TreeCell<T>> last = getLastVisibleCell();
return first.isPresent()
&& last.isPresent()
&& first.get().getIndex() <= index
&& last.get().getIndex() >= index;
}
private Optional<TreeCell<T>> getCellFromAccessor(Method accessor) {
return Optional.ofNullable(accessor).map(m -> {
try {
@SuppressWarnings("unchecked")
TreeCell<T> cell = (TreeCell<T>) m.invoke(virtualFlow);
return cell;
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
return null;
});
}
private Optional<TreeCell<T>> getFirstVisibleCell() {
return getCellFromAccessor(treeViewFirstVisibleMethod);
}
private Optional<TreeCell<T>> getLastVisibleCell() {
return getCellFromAccessor(treeViewLastVisibleMethod);
}
private static Object getVirtualFlow(Skin<?> skin) {
try {
// On JRE 9 and 10, the field is declared in TreeViewSkin
// http://hg.openjdk.java.net/openjfx/9/rt/file/c734b008e3e8/modules/javafx.controls/src/main/java/javafx/scene/control/skin/TreeViewSkin.java#l85
// http://hg.openjdk.java.net/openjfx/10/rt/file/d14b61c6be12/modules/javafx.controls/src/main/java/javafx/scene/control/skin/TreeViewSkin.java#l85
// On JRE 8, the field is declared in the VirtualContainerBase superclass
// http://hg.openjdk.java.net/openjfx/8/master/rt/file/f89b7dc932af/modules/controls/src/main/java/com/sun/javafx/scene/control/skin/VirtualContainerBase.java#l68
return FieldUtils.readField(skin, "flow", true);
} catch (IllegalAccessException ignored) {
}
return null;
}
}