diff --git a/pmd-dist/src/main/scripts/run.sh b/pmd-dist/src/main/scripts/run.sh index b02f115c51..5e38865f32 100755 --- a/pmd-dist/src/main/scripts/run.sh +++ b/pmd-dist/src/main/scripts/run.sh @@ -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}" "$@" + diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/SourceEditorController.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/SourceEditorController.java index 9e4731318c..8b52712f80 100644 --- a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/SourceEditorController.java +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/SourceEditorController.java @@ -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 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> selectionModel = astTreeView.getSelectionModel(); selectionModel.select(found); astTreeView.getFocusModel().focus(selectionModel.getSelectedIndex()); - // astTreeView.scrollTo(selectionModel.getSelectedIndex()); + if (!treeViewWrapper.isIndexVisible(selectionModel.getSelectedIndex())) { + astTreeView.scrollTo(selectionModel.getSelectedIndex()); + } } } diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/controls/TreeViewWrapper.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/controls/TreeViewWrapper.java new file mode 100644 index 0000000000..0e15e62aae --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/controls/TreeViewWrapper.java @@ -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 Element type of the treeview + * @author Clément Fournier + * @since 6.4.0 + */ +public class TreeViewWrapper { + + + private final TreeView 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 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() { + @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> first = getFirstVisibleCell(); + Optional> last = getLastVisibleCell(); + + return first.isPresent() + && last.isPresent() + && first.get().getIndex() <= index + && last.get().getIndex() >= index; + } + + + private Optional> getCellFromAccessor(Method accessor) { + return Optional.ofNullable(accessor).map(m -> { + try { + @SuppressWarnings("unchecked") + TreeCell cell = (TreeCell) m.invoke(virtualFlow); + return cell; + } catch (IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); + } + return null; + }); + } + + + private Optional> getFirstVisibleCell() { + return getCellFromAccessor(treeViewFirstVisibleMethod); + } + + + private Optional> 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; + } +}