Skip to content

Commit

Permalink
TreeViewer: AutoExpandTrivialPaths
Browse files Browse the repository at this point in the history
This commit addresses the need to automatically expand paths which
consist of chained singular nodes, eg:

com.wittmaxi.plugin
 └─src
    └─org.foo.com
       └─Bar.java

It introduces two new methods to the API of TreeViewer

-setAutoExpandOnSingleChildLevel
-getAutoExpandOnSingleChildLevel
  • Loading branch information
Maximilian Wittmer authored and Maximilian Wittmer committed Oct 12, 2023
1 parent 16cb21c commit 393e4ce
Show file tree
Hide file tree
Showing 3 changed files with 208 additions and 22 deletions.
8 changes: 8 additions & 0 deletions bundles/org.eclipse.jface/.settings/.api_filters
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,12 @@
</message_arguments>
</filter>
</resource>
<resource path="src/org/eclipse/jface/viewers/AbstractTreeViewer.java" type="org.eclipse.jface.viewers.AbstractTreeViewer">
<filter id="336658481">
<message_arguments>
<message_argument value="org.eclipse.jface.viewers.AbstractTreeViewer"/>
<message_argument value="NO_EXPAND"/>
</message_arguments>
</filter>
</resource>
</component>
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Function;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IStatus;
Expand Down Expand Up @@ -83,9 +84,18 @@ public abstract class AbstractTreeViewer extends ColumnViewer {
*
* @see #expandToLevel(int)
* @see #collapseToLevel(Object, int)
* @see #setAutoExpandOnSingleChildLevel(int)
*/
public static final int ALL_LEVELS = -1;

/**
* Constant indicating that no level of the tree should be expanded or collapsed
*
* @see #setAutoExpandOnSingleChildLevel(int)
* @since 3.32
*/
public static final int NO_EXPAND = 0;

/**
* List of registered tree listeners (element type:
* <code>TreeListener</code>).
Expand All @@ -102,8 +112,24 @@ public abstract class AbstractTreeViewer extends ColumnViewer {
private int expandToLevel = 0;

/**
* Indicates if filters should be checked to determine expandability of
* a tree node.
* How many levels to autoexpand in a path of consecutive single children. 0
* will disable the feature, while a negative value (<code>ALL_LEVELS</code>)
* will expand to infinity
*/
private int autoExpandOnSingleChildLevel = 0;

/**
* Listens to expansion events and recursively expands all children which
* themselves only contain one single child.
*
* This listener is added and removed as needed by
* {@link #setAutoExpandOnSingleChildLevel(int)}
*/
private ITreeViewerListener autoExpandOnSingleChildListener;

/**
* Indicates if filters should be checked to determine expandability of a tree
* node.
*/
private boolean isExpandableCheckFilters = false;

Expand Down Expand Up @@ -1221,6 +1247,17 @@ public int getAutoExpandLevel() {
return expandToLevel;
}

/**
* @return <code> NO_EXPAND </code> for disabled, <code> ALL_LEVELS </code> for
* infinite expansion or any integer value for the currently set level
* of expansion
* @since 3.32
*/
public int getAutoExpandOnSingleChildLevel() {
return autoExpandOnSingleChildLevel;
}


/**
* Returns the SWT child items for the given SWT widget.
*
Expand Down Expand Up @@ -1817,22 +1854,31 @@ protected Widget internalGetWidgetToSelect(Object elementOrTreePath) {
}

/**
* Recursively expands the subtree rooted at the given widget to the given
* level.
* Recursively, conditionally expands the subtree rooted at the given widget to
* the given level. Takes a predicate as input which takes in a Widget and
* returns a boolean value.
* <p>
* Note that the default implementation of this method does not call
* <code>setRedraw</code>.
* </p>
*
* @param widget the widget
* @param level non-negative level, or <code>ALL_LEVELS</code> to collapse all
* levels of the tree
*/
protected void internalExpandToLevel(Widget widget, int level) {
* @param widget the widget
* @param level non-negative level, or <code>ALL_LEVELS</code> to
* collapse all levels of the tree
* @param shouldChildrenExpand the predicate run on every iteration to tell
* whether the current node's children should be
* expanded further. The predicate-function takes in
* a Widget-type and returns a Boolean value, true
* indicating that the Widget should be expanded
* further
* @since 3.32
*/
protected void internalConditionalExpandToLevel(Widget widget, int level,
Function<Widget, Boolean> shouldChildrenExpand) {
if (level == ALL_LEVELS || level > 0) {
Object data = widget.getData();
if (widget instanceof Item && data != null
&& !isExpandable((Item) widget, null, data)) {
&& !isExpandable((Item) widget, null, data)) {
return;
}
createChildren(widget, false);
Expand All @@ -1842,16 +1888,33 @@ protected void internalExpandToLevel(Widget widget, int level) {
if (level == ALL_LEVELS || level > 1) {
Item[] children = getChildren(widget);
if (children != null) {
int newLevel = (level == ALL_LEVELS ? ALL_LEVELS
: level - 1);
int newLevel = (level == ALL_LEVELS ? ALL_LEVELS : level - 1);
for (Item element : children) {
internalExpandToLevel(element, newLevel);
if (shouldChildrenExpand.apply(widget).booleanValue()) {
internalConditionalExpandToLevel(element, newLevel, shouldChildrenExpand);
}
}
}
}
}
}

/**
* Recursively expands the subtree rooted at the given widget to the given
* level.
* <p>
* Note that the default implementation of this method does not call
* <code>setRedraw</code>.
* </p>
*
* @param widget the widget
* @param level non-negative level, or <code>ALL_LEVELS</code> to collapse all
* levels of the tree
*/
protected void internalExpandToLevel(Widget widget, int level) {
internalConditionalExpandToLevel(widget, level, w -> Boolean.TRUE);
}

/**
* Non-recursively tries to find the given element as a child of the given
* parent (item or tree).
Expand Down Expand Up @@ -2455,6 +2518,59 @@ public void setAutoExpandLevel(int level) {
expandToLevel = level;
}

/**
* Sets the level of auto expand on paths of consecutive single children.
* <code> NO_EXPAND </code> means that such paths are not automatically
* expanded.
* <p>
* Auto Expansion of paths of consecutive single children is off by default.
* Turning this behaviour off should be done cautiously on trees with
* lazy-loaded child-nodes.
* <p>
* <p>
* Using <code> ALL_LEVELS </code> as arguments will set to infinite
* auto-expansion of paths of consecutive single children.
* </p>
*
* @param level <code> NO_EXTEND </code> for disabled, <code> ALL_LEVELS </code>
* for infinite expansion or any integer value to set a level to
* which paths of consecutive single children are automatically
* expanded to
* @since 3.32
*/
public void setAutoExpandOnSingleChildLevel(int level) {
autoExpandOnSingleChildLevel = level;
if (level == NO_EXPAND) {
removeAutoExpandOnSingleChildListener();
} else {
registerAutoExpandOnSingleChildListener();
}
}

private void registerAutoExpandOnSingleChildListener() {
autoExpandOnSingleChildListener = new ITreeViewerListener() {
@Override
public void treeCollapsed(TreeExpansionEvent event) {
// Do nothing
}

@Override
public void treeExpanded(TreeExpansionEvent e) {
Widget item = doFindItem(e.getElement());

internalConditionalExpandToLevel(item, autoExpandOnSingleChildLevel,
w -> Boolean.valueOf(doesWidgetHaveExactlyOneChild(w)));
}
};
addTreeListener(autoExpandOnSingleChildListener);
}

private void removeAutoExpandOnSingleChildListener() {
if (autoExpandOnSingleChildListener != null) {
removeTreeListener(autoExpandOnSingleChildListener);
}
}

/**
* Sets the content provider used by this <code>AbstractTreeViewer</code>.
* <p>
Expand Down Expand Up @@ -2573,24 +2689,25 @@ public int hashCode(Object element) {

/**
* Sets whether the node corresponding to the given element or tree path is
* expanded or collapsed.
* expanded or collapsed. Will auto expand paths of single-children if setup by
* {@link #setAutoExpandOnSingleChildLevel(int) }
*
* @param elementOrTreePath
* the element
* @param expanded
* <code>true</code> if the node is expanded, and
* <code>false</code> if collapsed
* @param elementOrTreePath the element
* @param expanded <code>true</code> if the node is expanded, and
* <code>false</code> if collapsed
*/
public void setExpandedState(Object elementOrTreePath, boolean expanded) {
Assert.isNotNull(elementOrTreePath);
if (checkBusy())
return;
Widget item = internalExpand(elementOrTreePath, false);

if (item instanceof Item) {
if (expanded) {
createChildren(item);
}
setExpanded((Item) item, expanded);
if (autoExpandOnSingleChildLevel != NO_EXPAND && expanded) {
internalConditionalExpandToLevel(item, autoExpandOnSingleChildLevel,
w -> Boolean.valueOf(doesWidgetHaveExactlyOneChild(w)));
}
}
}

Expand Down Expand Up @@ -3426,4 +3543,18 @@ ISelection getUpdatedSelection(ISelection selection) {
return selection;
}

/**
* Returns whether the given Widget is a single child. Used as functional
* Parameter to {@link #internalConditionalExpandToLevel(Widget, int, Function)}
* while expanding paths of single children
* {@link #setAutoExpandOnSingleChildLevel(int)}
*
* @param Widget
* @return boolean value stating wether the given Widget has only one single
* child
*/
private boolean doesWidgetHaveExactlyOneChild(Widget w) {
return getChildren(w).length == 1;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,53 @@ public void testExpandToLevel() {
assertNotNull("first3 is visible", fViewer.testFindItem(first3));
}

public void testAutoExpandTrivialPath() {
TestElement modelRoot = TestElement.createModel(5, 1);
TestElement trivialPathRoot = modelRoot.getFirstChild();
fViewer.setInput(modelRoot);

fTreeViewer.setAutoExpandOnSingleChildLevel(2);
fTreeViewer.setExpandedState(trivialPathRoot, true);

assertTrue("The expanded widget child is not expanded", fTreeViewer.getExpandedState(trivialPathRoot));
assertTrue("The first child of the trivial path was not auto-expanded",
fTreeViewer.getExpandedState(trivialPathRoot.getFirstChild()));
assertTrue("Trivial path is expanded further than specified depth ",
fTreeViewer.getExpandedState(trivialPathRoot.getFirstChild().getFirstChild()) == false);
}

public void testAutoExpandTrivialPathManualDisable() {
// We need our own model since some default models do not generate trivial
// paths
TestElement modelRoot = TestElement.createModel(5, 1);
TestElement trivialPathRoot = modelRoot.getFirstChild();
fViewer.setInput(modelRoot);

fTreeViewer.setAutoExpandOnSingleChildLevel(AbstractTreeViewer.NO_EXPAND);
fTreeViewer.setExpandedState(trivialPathRoot, true);

assertTrue("The expanded widget child is not expanded", fTreeViewer.getExpandedState(trivialPathRoot));
assertTrue("The first child of the trivial path was auto-expanded",
fTreeViewer.getExpandedState(trivialPathRoot.getFirstChild()) == false);
}

public void testAutoExpandTrivialPathInfiniteExpand() {
TestElement modelRoot = TestElement.createModel(5, 1);
TestElement trivialPathRoot = modelRoot.getFirstChild();
fViewer.setInput(modelRoot);

fTreeViewer.setAutoExpandOnSingleChildLevel(AbstractTreeViewer.ALL_LEVELS);
fTreeViewer.setExpandedState(trivialPathRoot, true);

assertTrue("The expanded widget child is not expanded", fTreeViewer.getExpandedState(trivialPathRoot));
assertTrue("The first child of the trivial path was not auto-expanded",
fTreeViewer.getExpandedState(trivialPathRoot.getFirstChild()));
assertTrue("The second child of the trivial path was not auto-expanded",
fTreeViewer.getExpandedState(trivialPathRoot.getFirstChild().getFirstChild()));
assertTrue("The third child of the trivial path was not auto-expanded",
fTreeViewer.getExpandedState(trivialPathRoot.getFirstChild().getFirstChild().getFirstChild()));
}

public void testFilterExpanded() {
TestElement first = fRootElement.getFirstChild();
TestElement first2 = first.getFirstChild();
Expand Down

0 comments on commit 393e4ce

Please sign in to comment.