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
A new API has been introduced ColumnViewer#setItemsLimit(int
newLimit). This will limit the number of items rendered by the viewer
per parent with a node called IExpandableNode which will then render
next block of limited items on selection.

This API is introduced to solve UI freeze caused by Viewer when it tries
to render large number of items at a given level. This is backward
compatible provided clients LabelProvider and ContentProvider safe for
receiving IExpandableNode.

A new preference is introduced to configure viewer limit on any clients
viewer provided they will configure their viewer using
WorkbenchViewerSetupsetupViewer(ColumnViewer viewer). Currently
ProjectExplorer, ContentOutline, MarkersView and SearchResultsView has
been configured to show limited items. Current default for limited items
is 10000

Fixes eclipse-platform#818
  • Loading branch information
raghucssit committed Jul 17, 2023
1 parent 7ca06fd commit 878ae64
Show file tree
Hide file tree
Showing 36 changed files with 2,090 additions and 33 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 @@ -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 @@ -281,9 +288,9 @@ public void add(Object[] elements) {
* @param element
* @param index
*
* @since 3.1
* @since 3.31
*/
private void createItem(Object element, int index) {
protected 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
*/
private 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,17 @@ public void setExpandPreCheckFilters(boolean checkFilters) {
}
}

/**
* @param widget
* @return if the given widget's children has an expandable node at the end.
* @since 3.31
*/
protected 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 878ae64

Please sign in to comment.