Skip to content

Commit

Permalink
Limit based table and tree viewer implementation (eclipse-platform#818)
Browse files Browse the repository at this point in the history
New org.eclipse.jface.viewers.ColumnViewer#setItemsLimit(int) API has
been introduced. This API will limit the number of direct children
rendered per parent by the viewers by introducing an intermediate
IExpandableNode element which will then render a subset of original
children and show more elements on demand (user click).

This API is introduced to solve UI freezes caused by viewers when they
try to render large number of items at a given level. The changes made
for the new API should not affect any existing clients, because new
behavior has to be explicitly enabled by using new
ColumnViewer.setItemsLimit(int) API.

For the clients using this new API the change should be mostly backwards
compatible. Clients might need to adopt label provider and viewer code
to make them safe for receiving IExpandableNode from viewer.

Note: clients that extended (not allowed to be extended) TreeViewer
should make sure that isExpandable() returns "false" for
IExpandableNode.

A new workbench preference is introduced to provide a default viewer
limit suitable for most of the clients. Current default value for this
limit is 10000. Clients that adopt new API can use this preference to
configure their viewer by using new
WorkbenchViewerSetup.setupViewer(ColumnViewer) API.

Project Explorer, Outline, Markers, Problems and Search views has
been adopted to the new API and use the new preference to show limited
number of items. Package Explorer adoption for JDT follows in a
separated commit.

Fixes eclipse-platform#818
  • Loading branch information
raghucssit committed Jul 20, 2023
1 parent e869f85 commit 0ec3d94
Show file tree
Hide file tree
Showing 37 changed files with 2,100 additions and 32 deletions.
2 changes: 1 addition & 1 deletion bundles/org.eclipse.jface/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %pluginName
Bundle-SymbolicName: org.eclipse.jface;singleton:=true
Bundle-Version: 3.30.100.qualifier
Bundle-Version: 3.31.0.qualifier
Bundle-Vendor: %providerName
Bundle-Localization: plugin
Export-Package: org.eclipse.jface,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,6 @@ ConfigureColumnsDialog_Title=Configure Columns
ConfigureColumnsDialog_WidthOfSelectedColumn=&Width of selected column:
ConfigureColumnsDialog_up = &Up
ConfigureColumnsDialog_down = Dow&n

# org.eclipse.jface.viewers.internal.ExpandableNode
ExpandableNode.defaultLabel = Expand [{0}]...[{1}]
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,13 @@ public void add(Object[] elements) {
return;
Object[] filtered = filter(elements);

// if limit is set and still some elements are not populated. Assumption that
// user has already updated the model and needs addition of an item.
if (getItemsLimit() > 0 && filtered.length > 0 && getLastElement() instanceof IExpandableNode) {
internalRefreshAll(false);
return;
}

for (Object element : filtered) {
int index = indexForElement(element);
createItem(element, index);
Expand All @@ -283,7 +290,7 @@ public void add(Object[] elements) {
*
* @since 3.1
*/
private void createItem(Object element, int index) {
void createItem(Object element, int index) {
if (virtualManager == null) {
updateItem(internalCreateNewRowPart(SWT.NONE, index).getItem(),
element);
Expand Down Expand Up @@ -384,6 +391,12 @@ protected void doUpdateItem(Widget widget, Object element, boolean fullMap) {
// Also enter loop if no columns added. See 1G9WWGZ: JFUIF:WINNT -
// TableViewer with 0 columns does not work
for (int column = 0; column < columnCount || column == 0; column++) {

// ExpandableNode is shown in first column only.
if (element instanceof IExpandableNode && column != 0) {
continue;
}

ViewerColumn columnViewer = getViewerColumn(column);
ViewerCell cellToUpdate = updateCell(viewerRowFromItem,
column, element);
Expand Down Expand Up @@ -597,6 +610,14 @@ public void insert(Object element, int position) {
add(element);
return;
}

// if limit is set and still some elements are not populated.Assumption that
// user has already updated the model and needs addition of item.
if (getItemsLimit() > 0 && getLastElement() instanceof IExpandableNode) {
internalRefreshAll(false);
return;
}

if (position == -1) {
position = doGetItemCount();
}
Expand Down Expand Up @@ -666,9 +687,13 @@ private void internalRefreshAll(boolean updateLabels) {
// e.g. if (a, b) is replaced by (b, a), the disassociate of b to
// item 1 could undo
// the associate of b to item 0.

Object[] children = getSortedChildren(getRoot());
Object[] children = null;
Item[] items = doGetItems();
if (getItemsLimit() > 0 && items.length > getItemsLimit()) {
children = getVisibleLimitBasedChildren(getRoot(), items);
} else {
children = getSortedChildren(getRoot());
}
int min = Math.min(children.length, items.length);
for (int i = 0; i < min; ++i) {

Expand Down Expand Up @@ -747,6 +772,14 @@ private void internalRemove(final Object[] elements) {
return;
}
}

// if limit is set and still some elements are not populated.Assumption that
// user has already updated the model and needs removal of an item.
if (getItemsLimit() > 0 && getLastElement() instanceof IExpandableNode) {
internalRefreshAll(false);
return;
}

// use remove(int[]) rather than repeated TableItem.dispose() calls
// to allow SWT to optimize multiple removals
int[] indices = new int[elements.length];
Expand Down Expand Up @@ -779,6 +812,20 @@ private void internalRemove(final Object[] elements) {
}
}

/**
* Returns the data of the last item on the viewer.
*
* @return may return null
*/
Object getLastElement() {
Item[] items = doGetItems();
int length = items.length;
if (length == 0) {
return null;
}
return items[length - 1].getData();
}

/**
* Removes the given elements from this table viewer. The selection is
* updated if required.
Expand Down Expand Up @@ -825,6 +872,22 @@ public void remove(Object element) {
public void reveal(Object element) {
Assert.isNotNull(element);
Widget w = findItem(element);

if (w == null) {
// if limited elements are populated, element to reveal may be inside
// ExpandableNode.
if (getLastElement() instanceof IExpandableNode node) {
Item[] items = doGetItems();
Object[] remEles = node.getRemainingElements();
for (Object object : remEles) {
if (equals(object, element)) {
w = items[items.length - 1];
break;
}
}
}
}

if (w instanceof Item) {
doShowItem((Item) w);
}
Expand Down Expand Up @@ -885,6 +948,27 @@ protected void setSelectionToWidget(@SuppressWarnings("rawtypes") List list, boo
System.arraycopy(indices, 0, indices = new int[count], 0, count);
}

// item to select may be hidden inside expandable node.
if (indices.length < list.size() && getLastElement() instanceof IExpandableNode expNode) {
Object[] remEles = expNode.getRemainingElements();
boolean breakOuter = false;
for (Object searchItem : list) {
for (Object remEle : remEles) {
if (equals(remEle, searchItem)) {
int[] placeHolder = new int[indices.length + 1];
System.arraycopy(indices, 0, placeHolder, 0, indices.length);
placeHolder[placeHolder.length - 1] = items.length - 1;
indices = placeHolder;
breakOuter = true;
break;
}
}
if (breakOuter) {
break;
}
}
}

doSelect(indices);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

Expand All @@ -43,6 +44,7 @@
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Item;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.swt.widgets.Widget;

/**
Expand Down Expand Up @@ -286,6 +288,15 @@ protected void internalAdd(Widget widget, Object parentElementOrTreePath,
comparator.sort(this, filtered);
}
}
// there are elements to be shown and viewer is showing limited items.
// newly added element can be inside expandable node or can be visible item.
// Assumption that user has already updated the model and needs addition of
// item.
if (getItemsLimit() > 0 && hasLimitedChildrenItems(widget)) {
internalRefreshStruct(widget, parent, false);
return;
}

createAddedElements(widget, filtered);
if (InternalPolicy.DEBUG_LOG_EQUAL_VIEWER_ELEMENTS) {
Item[] children = getChildren(widget);
Expand Down Expand Up @@ -635,10 +646,11 @@ private int internalCompare(ViewerComparator comparator,

@Override
protected Object[] getSortedChildren(Object parentElementOrTreePath) {
Object[] result = getFilteredChildren(parentElementOrTreePath);
Object[] result = null;
ViewerComparator comparator = getComparator();
if (parentElementOrTreePath != null
&& comparator instanceof TreePathViewerSorter) {
result = getFilteredChildren(parentElementOrTreePath);
TreePathViewerSorter tpvs = (TreePathViewerSorter) comparator;

// be sure we're not modifying the original array from the model
Expand All @@ -655,10 +667,9 @@ protected Object[] getSortedChildren(Object parentElementOrTreePath) {
}
}
tpvs.sort(this, path, result);
} else if (comparator != null) {
// be sure we're not modifying the original array from the model
result = result.clone();
comparator.sort(this, result);
result = applyItemsLimit(parentElementOrTreePath, result);
} else {
return super.getSortedChildren(parentElementOrTreePath);
}
return result;
}
Expand Down Expand Up @@ -958,6 +969,12 @@ protected void doUpdateItem(final Item item, Object element) {
}

for (int column = 0; column < columnCount; column++) {

// ExpandableNode is shown in first column only.
if (element instanceof IExpandableNode && column != 0) {
continue;
}

ViewerColumn columnViewer = getViewerColumn(column);
ViewerCell cellToUpdate = updateCell(viewerRowFromItem, column,
element);
Expand Down Expand Up @@ -1422,6 +1439,9 @@ protected Object[] getRawChildren(Object parentElementOrTreePath) {
return super.getRawChildren(parent);
}
IContentProvider cp = getContentProvider();
if (getItemsLimit() > 0 && parent instanceof IExpandableNode expNode) {
return expNode.getRemainingElements();
}
if (cp instanceof ITreePathContentProvider) {
ITreePathContentProvider tpcp = (ITreePathContentProvider) cp;
if (path == null) {
Expand Down Expand Up @@ -1968,7 +1988,17 @@ protected void internalRefresh(Widget widget, Object element,
*/
/* package */void internalRefreshStruct(Widget widget, Object element,
boolean updateLabels) {
updateChildren(widget, element, null, updateLabels);
Object[] updatedChildren = null;

// updateChildren will ask getSortedChildren for items to be populated.
// getSortedChildren always returns the limited items doesn't matter if there
// were any items were expanded. We need to fetch exactly same number of
// elements which were shown in the viewer.
if (getItemsLimit() > 0) {
updatedChildren = getVisibleLimitBasedChildren(element, getChildren(widget));
}

updateChildren(widget, element, updatedChildren, updateLabels);
Item[] children = getChildren(widget);
if (children != null) {
for (Item item : children) {
Expand Down Expand Up @@ -2000,6 +2030,27 @@ protected void internalRemove(Object[] elementsOrPaths) {
setInput(null);
return;
}

boolean continueOuter = false;
if (getItemsLimit() > 0) {
Widget[] itemsOfElement = internalFindItems(element);
for (Widget item : itemsOfElement) {
if (item instanceof TreeItem) {
TreeItem parentItem = ((TreeItem) item).getParentItem();
if (parentItem == null) {
internalRefreshStruct(((TreeItem) item).getParent(), getInput(), false);
continueOuter = true;
break;
}
// refresh parent item with the latest model.
internalRefreshStruct(parentItem, parentItem.getData(), false);
continueOuter = true;
}
}
}
if (continueOuter) {
continue;
}
Widget[] childItems = internalFindItems(element);
if (childItems.length > 0) {
for (Widget childItem : childItems) {
Expand Down Expand Up @@ -2053,6 +2104,15 @@ protected void internalRemove(Object parent, Object[] elements) {
// Iterate over the child items and remove each one
Item[] children = getChildren(parentItem);

// there are elements to be shown and viewer is showing limited items.
// newly added element can be inside expandable node or can be visible item.
// Assumption that user has already updated the model and needs removal of
// an item.
if (getItemsLimit() > 0 && hasLimitedChildrenItems(parentItem)) {
internalRefreshStruct(parentItem, parentItem.getData(), false);
continue;
}

if (children.length == 1 && children[0].getData() == null &&
parentItem instanceof Item) { // dummy node
// Remove plus if parent element has no children
Expand Down Expand Up @@ -2579,6 +2639,41 @@ protected void setSelectionToWidget(@SuppressWarnings("rawtypes") List v, boolea
}
}
}

// there can be some items inside expandable node and not populated yet. In this
// case try to find the item to select inside all the visible expandable nodes.
if (newSelection.size() < v.size() && getItemsLimit() > 0) {
// make out still not found items
List<Object> notFound = new ArrayList<>();
for (Object toSelect : v) {
boolean bFound = false;
for (Item found : newSelection) {
if (equals(toSelect, found.getData())) {
bFound = true;
break;
}
}
if (!bFound) {
notFound.add(toSelect);
}
}

// find out all visible expandable nodes
Collection<IExpandableNode> expandItems = getExpandableNodes();

// search for still missing items inside expandable nodes
for (Object nFound : notFound) {
for (IExpandableNode expNode : expandItems) {
if (findElementInExpandableNode(expNode, nFound)) {
Widget w = findItem(expNode);
if (w instanceof Item item) {
newSelection.add(item);
}
}
}
}
}

setSelection(newSelection);

// Although setting the selection in the control should reveal it,
Expand All @@ -2594,6 +2689,16 @@ protected void setSelectionToWidget(@SuppressWarnings("rawtypes") List v, boolea
}
}

private boolean findElementInExpandableNode(IExpandableNode expNode, Object toFind) {
Object[] remEles = getFilteredChildren(expNode);
for (Object element : remEles) {
if (equals(element, toFind)) {
return true;
}
}
return false;
}

/**
* Shows the given item.
*
Expand Down Expand Up @@ -3253,4 +3358,16 @@ public void setExpandPreCheckFilters(boolean checkFilters) {
}
}

/**
* @param widget
* @return if the given widget's children has an expandable node at the end.
*/
boolean hasLimitedChildrenItems(Widget widget) {
Item[] items = getChildren(widget);
if (items.length == 0) {
return false;
}
return items[items.length - 1].getData() instanceof IExpandableNode;
}

}
Loading

0 comments on commit 0ec3d94

Please sign in to comment.