From 4986ed6ff72ee1debaa010bb9fd1848df2c66fa7 Mon Sep 17 00:00:00 2001 From: raghucssit Date: Fri, 9 Jun 2023 10:19:53 +0200 Subject: [PATCH] Limit based table and tree viewer implementation (#818) 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 https://github.com/eclipse-platform/eclipse.platform.ui/issues/818 --- .../org.eclipse.jface/META-INF/MANIFEST.MF | 2 +- .../jface/viewers/AbstractTableViewer.java | 90 +++++- .../jface/viewers/AbstractTreeViewer.java | 129 +++++++- .../eclipse/jface/viewers/ColumnViewer.java | 209 +++++++++++- .../eclipse/jface/viewers/ExpandableNode.java | 62 ++++ .../jface/viewers/IExpandableNode.java | 69 ++++ .../eclipse/jface/viewers/TableViewer.java | 38 +++ .../org/eclipse/jface/viewers/TreeViewer.java | 80 +++++ .../eclipse/jface/viewers/ViewerColumn.java | 11 + .../org.eclipse.search/META-INF/MANIFEST.MF | 2 +- .../ui/text/AbstractTextSearchViewPage.java | 3 + .../views/markers/ExtendedMarkersView.java | 12 +- .../MarkerProblemSeverityAndMessageField.java | 5 +- .../markers/MarkerViewerContentProvider.java | 5 +- .../views/markers/MarkersTreeViewer.java | 7 +- .../eclipse/ui/views/markers/MarkerField.java | 6 +- .../META-INF/MANIFEST.MF | 2 +- .../navigator/resources/ProjectExplorer.java | 2 + .../org.eclipse.ui.views/META-INF/MANIFEST.MF | 2 +- .../contentoutline/ContentOutlinePage.java | 2 + .../ui/IWorkbenchPreferenceConstants.java | 13 + .../ui/internal/WorkbenchMessages.java | 2 + .../WorkbenchPreferenceInitializer.java | 3 + .../dialogs/WorkbenchPreferencePage.java | 41 +++ .../eclipse/ui/internal/messages.properties | 3 + .../ui/views/WorkbenchViewerSetup.java | 103 ++++++ .../META-INF/MANIFEST.MF | 4 +- .../Snippet068TableViewerWithLimit.java | 165 ++++++++++ .../Snippet069TreeViewerWithLimit.java | 298 +++++++++++++++++ .../META-INF/MANIFEST.MF | 4 +- .../jface/tests/viewers/AllViewersTests.java | 4 +- .../viewers/BaseLimitBasedViewerTest.java | 115 +++++++ ...TableViewerWithLimitCompatibilityTest.java | 35 ++ .../viewers/TableViewerWithLimitTest.java | 263 +++++++++++++++ .../TreeViewerWithLimitCompatibilityTest.java | 38 +++ .../viewers/TreeViewerWithLimitTest.java | 299 ++++++++++++++++++ 36 files changed, 2096 insertions(+), 32 deletions(-) create mode 100644 bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/ExpandableNode.java create mode 100644 bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/IExpandableNode.java create mode 100644 bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/views/WorkbenchViewerSetup.java create mode 100644 examples/org.eclipse.jface.snippets/Eclipse JFace Snippets/org/eclipse/jface/snippets/viewers/Snippet068TableViewerWithLimit.java create mode 100644 examples/org.eclipse.jface.snippets/Eclipse JFace Snippets/org/eclipse/jface/snippets/viewers/Snippet069TreeViewerWithLimit.java create mode 100644 tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/viewers/BaseLimitBasedViewerTest.java create mode 100644 tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/viewers/TableViewerWithLimitCompatibilityTest.java create mode 100644 tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/viewers/TableViewerWithLimitTest.java create mode 100644 tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/viewers/TreeViewerWithLimitCompatibilityTest.java create mode 100644 tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/viewers/TreeViewerWithLimitTest.java diff --git a/bundles/org.eclipse.jface/META-INF/MANIFEST.MF b/bundles/org.eclipse.jface/META-INF/MANIFEST.MF index 0d14a219357..6f2992e2208 100644 --- a/bundles/org.eclipse.jface/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.jface/META-INF/MANIFEST.MF @@ -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, diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/AbstractTableViewer.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/AbstractTableViewer.java index f8854200090..bfe9519b6e1 100644 --- a/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/AbstractTableViewer.java +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/AbstractTableViewer.java @@ -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); @@ -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); @@ -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); @@ -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(); } @@ -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) { @@ -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]; @@ -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. @@ -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); } @@ -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); } } diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/AbstractTreeViewer.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/AbstractTreeViewer.java index 416ea870b8b..04248f80dec 100644 --- a/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/AbstractTreeViewer.java +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/AbstractTreeViewer.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.LinkedList; import java.util.List; @@ -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; /** @@ -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); @@ -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 @@ -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; } @@ -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); @@ -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) { @@ -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) { @@ -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) { @@ -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 @@ -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 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 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, @@ -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. * @@ -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; + } + } diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/ColumnViewer.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/ColumnViewer.java index baea113292f..8f2c86c9fe5 100644 --- a/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/ColumnViewer.java +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/ColumnViewer.java @@ -17,11 +17,18 @@ package org.eclipse.jface.viewers; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jface.internal.InternalPolicy; import org.eclipse.jface.util.Policy; +import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; @@ -45,6 +52,13 @@ * */ public abstract class ColumnViewer extends StructuredViewer { + + /** + * Number of items to be shown before an {@link IExpandableNode} is displayed, + * default is zero (no limits). + */ + private int itemsLimit; + private CellEditor[] cellEditors; private ICellModifier cellModifier; @@ -63,6 +77,8 @@ public abstract class ColumnViewer extends StructuredViewer { private MouseListener mouseListener; + private Set expandableNodes; + // after logging for the first // time @@ -70,7 +86,7 @@ public abstract class ColumnViewer extends StructuredViewer { * Create a new instance of the receiver. */ public ColumnViewer() { - + expandableNodes = new HashSet<>(); } @Override @@ -652,8 +668,11 @@ private void handleMouseDown(MouseEvent e) { ViewerCell cell = getCell(new Point(e.x, e.y)); if (cell != null) { - triggerEditorActivationEvent(new ColumnViewerEditorActivationEvent( - cell, e)); + if (cell.getElement() instanceof IExpandableNode) { + handleExpandableNodeClicked(cell); + } else { + triggerEditorActivationEvent(new ColumnViewerEditorActivationEvent(cell, e)); + } } } @@ -701,6 +720,9 @@ protected Object[] getRawChildren(Object parent) { boolean oldBusy = isBusy(); setBusy(true); try { + if (parent instanceof IExpandableNode expNode) { + return expNode.getRemainingElements(); + } return super.getRawChildren(parent); } finally { setBusy(oldBusy); @@ -817,4 +839,185 @@ protected void setBusy(boolean busy) { public boolean isBusy() { return busy; } + + @Override + protected Object[] getSortedChildren(Object parent) { + Object[] sorted = super.getSortedChildren(parent); + return applyItemsLimit(parent, sorted); + } + + /** + * {@link StructuredViewer#getSortedChildren(Object)} always returns the + * complete list of elements to be populated. Now we have to apply viewer limit + * for the items to be created on the viewer. Note that in case of parent is + * {@link IExpandableNode} we will return next block of limited elements to be + * created. + * + * @param parent + * @param sorted + * @return returns only limited items. + * @since 3.31 + */ + protected Object[] applyItemsLimit(Object parent, Object[] sorted) { + // limit the number of items to be created. sorted always gets the remaining + // elements to be created. + int itemsLimit = getItemsLimit(); + if (itemsLimit <= 0) { + return sorted; + } + + if (sorted.length <= itemsLimit) { + return sorted; + } + + int offSet = itemsLimit; + int srcPos = 0; + + if (parent instanceof IExpandableNode expNode) { + srcPos = expNode.getOffSet(); + offSet = srcPos + itemsLimit; + // pass on original parent and children + sorted = expNode.getAllElements(); + parent = expNode.getParent(); + } + + Object[] partialChildren = new Object[itemsLimit + 1]; + + // Extract a subset of children + System.arraycopy(sorted, srcPos, partialChildren, 0, itemsLimit); + + // Add an expandable node + partialChildren[itemsLimit] = createExpandableNode(parent, sorted, offSet); + + return partialChildren; + } + + /** + * Viewer must dispose this cell and populate the remaining elements returned by + * this cell. + * + * @param cell + * @since 3.31 + */ + protected void handleExpandableNodeClicked(ViewerCell cell) { + // default implementation does nothing. Actual viewers can decide how to + // populate remaining elements. + } + + /** + * We should not dispose already visible items and we should display limited + * elements along with already visible items. + * + * @param parent model item to be refreshed. + * @param visibleChildren currently visible children. This includes any elements + * already expanded by user. + * @return list of children to be displayed/refreshed. + * @since 3.31 + */ + protected Object[] getVisibleLimitBasedChildren(Object parent, Item[] visibleChildren) { + Object[] updatedChildren = null; + final int limit = getItemsLimit(); + // at least one iteration of expandable items are populated. + if (visibleChildren.length > limit) { + List listOfEle = new ArrayList<>(Arrays.asList(getSortedChildren(parent))); + // case 1 . if returned are less then limit just stop here and add list of ele + // to updatedChildren + if (listOfEle.size() < limit) { + updatedChildren = new Object[listOfEle.size()]; + listOfEle.toArray(updatedChildren); + } else { + // numberOfTimesRead is equal to number of times it was expanded by user. + int numberOfTimesRead = visibleChildren.length / limit; + + if (visibleChildren.length % limit > 0 + && !(visibleChildren[visibleChildren.length - 1].getData() instanceof IExpandableNode)) { + numberOfTimesRead++; + } + List newList = new ArrayList<>(); + Object ele1 = parent; + + while (numberOfTimesRead > 0) { + List subList = new ArrayList<>(Arrays.asList(getSortedChildren(ele1))); + // no more to read. case 2. possibility of less items than visibleChildren but + // more than basic limit. + if (!(subList.get(subList.size() - 1) instanceof IExpandableNode)) { + newList.addAll(subList); + break; + } + numberOfTimesRead--; + // decide to remove last element or not + if (numberOfTimesRead > 0) { + ele1 = subList.remove(subList.size() - 1); + } + newList.addAll(subList); + } + updatedChildren = new Object[newList.size()]; + newList.toArray(updatedChildren); + + } + } + return updatedChildren; + } + + /** + * Returns the current viewer limit. + * + * @return limit + * @since 3.31 + */ + public int getItemsLimit() { + return itemsLimit; + } + + /** + * Sets the viewers items limit. This must be called before + * {@link Viewer#setInput(Object)}. A parameter less than or equal to 0 OR + * greater than length of input has no effect on the viewer. + *

+ * This will limit the number of items to be rendered on any level. It creates + * an {@link IExpandableNode} at the end of limited items which will then + * creates next block of items on receiving {@link SWT#MouseDown} event. + *

+ *

+ * Client can adapt {@link ILabelProvider} to provide desired label and image + * for {@link IExpandableNode} otherwise default label and no image will be + * used. + *

+ * + * @param newLimit A non-negative integer greater than 0. + * @since 3.31 + */ + public void setItemsLimit(int newLimit) { + itemsLimit = newLimit; + } + + /** + * @param parent + * @param result + * @param startOffSet + * @return new node + * @since 3.31 + */ + protected IExpandableNode createExpandableNode(Object parent, Object[] result, int startOffSet) { + ExpandableNode expandableNode = new ExpandableNode(parent, result, startOffSet); + expandableNodes.add(expandableNode); + return expandableNode; + } + + @Override + protected void disassociate(Item item) { + Object element = item.getData(); + if (element instanceof IExpandableNode expNode) { + expandableNodes.remove(expNode); + } + super.disassociate(item); + } + + /** + * @return Returns the expandableNodes. + * @since 3.31 + */ + protected Set getExpandableNodes() { + return expandableNodes; + } } diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/ExpandableNode.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/ExpandableNode.java new file mode 100644 index 00000000000..6879e142325 --- /dev/null +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/ExpandableNode.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Raghunandana Murthappa + *******************************************************************************/ +package org.eclipse.jface.viewers; + +/** + * @since 3.31 + * + */ +public class ExpandableNode implements IExpandableNode { + + private Object[] orginalArray; + private Object parent; + private int startOffSet; + + /** + * @param parent + * @param children + * @param startOffSet + */ + public ExpandableNode(Object parent, Object[] children, int startOffSet) { + this.parent = parent; + this.orginalArray = children; + this.startOffSet = startOffSet; + } + + @Override + public Object getParent() { + return parent; + } + + @Override + public int getOffSet() { + return startOffSet; + } + + @Override + public Object[] getRemainingElements() { + Object[] children = new Object[orginalArray.length - startOffSet]; + System.arraycopy(orginalArray, startOffSet, children, 0, children.length); + return children; + } + + @Override + public Object[] getAllElements() { + return orginalArray; + } + + @Override + public String getLabel() { + return "Expand [" + (startOffSet + 1) + "..." + orginalArray.length + "]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + +} diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/IExpandableNode.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/IExpandableNode.java new file mode 100644 index 00000000000..8f1a1d0a6c2 --- /dev/null +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/IExpandableNode.java @@ -0,0 +1,69 @@ +/******************************************************************************* + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Raghunandana Murthappa + *******************************************************************************/ +package org.eclipse.jface.viewers; + +import org.eclipse.swt.widgets.Item; + +/** + * This is the model element to be used for viewer item which represents an + * expansion node. + * + * @since 3.31 + * + */ +public interface IExpandableNode { + + /** + * NOTE: This should return all the remaining elements not the + * next block of elements. i.e. all the original elements after offset. + * + * @return return the remaining elements under the parent. + * + */ + Object[] getRemainingElements(); + + /** + * parent of this node. It must always contain the original parent of all + * elements in this list. + * + * @return returns the parent of this node. + */ + Object getParent(); + + /** + * Every expansion node must contain the reference to original children should + * be created under a parent. Offset must be moved according to created items. + * + * @return original list of elements of the parent. + */ + Object[] getAllElements(); + + /** + * Client's label provider must be adapted to provide proper label and image for + * this element. In case if client has not returned any label for this item, we + * will use this label to {@link Item#setText(String)} + *

+ * NOTE: {@link Object#toString()} is always overridden by this + * label. + *

+ * + * @return returns the label to be used on this element. + */ + String getLabel(); + + /** + * Current pointer in the original list of elements. + * + * @return current offset in the original list + */ + int getOffSet(); +} diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/TableViewer.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/TableViewer.java index 23dfa2b1c3d..9546f95a445 100644 --- a/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/TableViewer.java +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/TableViewer.java @@ -456,4 +456,42 @@ protected Widget doFindItem(Object element) { return super.doFindItem(element); } + /** + * @since 3.30 + */ + @Override + protected void handleExpandableNodeClicked(ViewerCell cell) { + Widget item = cell.getItem(); + + if (!(item instanceof Item)) { + return; + } + + Object data = item.getData(); + + if (data == null) { + return; + } + + Object[] sortedChildren = getSortedChildren(cell.getElement()); + disassociate((Item) item); + int index = doIndexOf((Item) item); + doRemove(new int[] { index }); + // create remaining elements + for (int i = 0; i < sortedChildren.length; ++i) { + createItem(sortedChildren[i], -1); + } + if (sortedChildren.length > 0) { + // If we've expanded but still have not reached the limit + // select new expandable node, so user can click through + // to the end + if (getLastElement() instanceof ExpandableNode node) { + setSelection(new StructuredSelection(node), true); + } else { + // set selection to first element of newly created items. + setSelection(new StructuredSelection(sortedChildren[0])); + } + } + } + } diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/TreeViewer.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/TreeViewer.java index 83934b974fa..2c9884a6cf8 100644 --- a/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/TreeViewer.java +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/TreeViewer.java @@ -550,6 +550,9 @@ public boolean isExpandable(Object element) { virtualMaterializeItem(treeItem); return treeItem.getItemCount() > 0; } + if (element instanceof IExpandableNode) { + return false; + } return super.isExpandable(element); } @@ -820,6 +823,12 @@ public void remove(final Object parentOrTreePath, final int index) { if (internalIsInputOrEmptyPath(parentOrTreePath)) { Tree tree = (Tree) getControl(); if (index < tree.getItemCount()) { + + if (getItemsLimit() > 0 && hasLimitedChildrenItems(tree)) { + internalRefreshStruct(tree, getInput(), false); + return; + } + TreeItem item1 = tree.getItem(index); if (item1.getData() != null) { removedPath = getTreePathFromItem(item1); @@ -833,6 +842,12 @@ public void remove(final Object parentOrTreePath, final int index) { TreeItem parentItem = (TreeItem) parentWidget; if (parentItem.isDisposed()) continue; + + if (getItemsLimit() > 0 && hasLimitedChildrenItems(parentWidget)) { + internalRefreshStruct(parentWidget, parentWidget.getData(), false); + continue; + } + if (index < parentItem.getItemCount()) { TreeItem item2 = parentItem.getItem(index); @@ -1116,4 +1131,69 @@ public void editElement(Object element, int column) { } } + /** + * @since 3.30 + */ + @Override + protected void handleExpandableNodeClicked(ViewerCell cell) { + Widget item = cell.getItem(); + + if (item == null) { + return; + } + + Object data = item.getData(); + + if (data == null) { + return; + } + + Object[] children = getSortedChildren(data); + + if (children.length == 0) { + return; + } + + Widget parent = getParentItem((Item) item); + if (parent == null) { + parent = getControl(); + } + // destroy widget + disassociate((Item) item); + item.dispose(); + + // create children on parent + for (Object element : children) { + createTreeItem(parent, element, -1); + } + + if (children.length > 0) { + // If we've expanded but still have not reached the limit + // select new expandable node, so user can click through + // to the end + Object lastElement = getLastElement(parent); + if (lastElement instanceof ExpandableNode node) { + setSelection(new StructuredSelection(node), true); + } else { + // set selection to first element of newly created items. + setSelection(new StructuredSelection(children[0]), true); + } + } + } + + /** + * Returns the data of the last item on the viewer. + * + * @param parent + * + * @return may return null + */ + Object getLastElement(Widget parent) { + Item[] items = getChildren(parent); + int length = items.length; + if (length == 0) { + return null; + } + return items[length - 1].getData(); + } } diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/ViewerColumn.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/ViewerColumn.java index 4e063c83b88..e397208250d 100644 --- a/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/ViewerColumn.java +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/viewers/ViewerColumn.java @@ -19,6 +19,7 @@ package org.eclipse.jface.viewers; import org.eclipse.core.runtime.Assert; +import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.util.Policy; import org.eclipse.swt.widgets.Widget; @@ -142,6 +143,16 @@ public void setEditingSupport(EditingSupport editingSupport) { " has no label provider."); //$NON-NLS-1$ } labelProvider.update(cell); + + // check if client has updated the label for this element. Otherwise use default + // label provided + if (cell.getElement() instanceof IExpandableNode expNode) { + cell.setFont(JFaceResources.getFontRegistry().getItalic(JFaceResources.DEFAULT_FONT)); + String text = cell.getText(); + if (text.isEmpty() || text.equals(expNode.toString())) { + cell.setText(expNode.getLabel()); + } + } } /** diff --git a/bundles/org.eclipse.search/META-INF/MANIFEST.MF b/bundles/org.eclipse.search/META-INF/MANIFEST.MF index f943706c2cd..1714742892a 100644 --- a/bundles/org.eclipse.search/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.search/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.search; singleton:=true -Bundle-Version: 3.15.100.qualifier +Bundle-Version: 3.15.200.qualifier Bundle-Activator: org.eclipse.search.internal.ui.SearchPlugin Bundle-ActivationPolicy: lazy Bundle-Vendor: %providerName diff --git a/bundles/org.eclipse.search/new search/org/eclipse/search/ui/text/AbstractTextSearchViewPage.java b/bundles/org.eclipse.search/new search/org/eclipse/search/ui/text/AbstractTextSearchViewPage.java index aba0d224119..1d583510194 100644 --- a/bundles/org.eclipse.search/new search/org/eclipse/search/ui/text/AbstractTextSearchViewPage.java +++ b/bundles/org.eclipse.search/new search/org/eclipse/search/ui/text/AbstractTextSearchViewPage.java @@ -84,6 +84,7 @@ import org.eclipse.ui.part.Page; import org.eclipse.ui.part.PageBook; import org.eclipse.ui.progress.UIJob; +import org.eclipse.ui.views.WorkbenchViewerSetup; import org.eclipse.ui.texteditor.IUpdate; @@ -723,10 +724,12 @@ private void createViewer(Composite parent, int layout) { if ((layout & FLAG_LAYOUT_FLAT) != 0) { TableViewer viewer = createTableViewer(parent); fViewer = viewer; + WorkbenchViewerSetup.setupViewer(viewer); configureTableViewer(viewer); } else if ((layout & FLAG_LAYOUT_TREE) != 0) { TreeViewer viewer = createTreeViewer(parent); fViewer = viewer; + WorkbenchViewerSetup.setupViewer(viewer); configureTreeViewer(viewer); fCollapseAllAction.setViewer(viewer); fExpandAllAction.setViewer(viewer); diff --git a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/views/markers/ExtendedMarkersView.java b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/views/markers/ExtendedMarkersView.java index cc1ebd94311..1f8e18fa928 100644 --- a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/views/markers/ExtendedMarkersView.java +++ b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/views/markers/ExtendedMarkersView.java @@ -110,6 +110,7 @@ import org.eclipse.ui.part.ViewPart; import org.eclipse.ui.progress.IWorkbenchSiteProgressService; import org.eclipse.ui.statushandlers.StatusManager; +import org.eclipse.ui.views.WorkbenchViewerSetup; import org.eclipse.ui.views.markers.MarkerField; import org.eclipse.ui.views.markers.MarkerItem; import org.eclipse.ui.views.markers.internal.ContentGeneratorDescriptor; @@ -256,6 +257,7 @@ private void createViewer(Composite parent) { viewer = new MarkersTreeViewer(new Tree(parent, SWT.H_SCROLL /*| SWT.VIRTUAL */| SWT.V_SCROLL | SWT.MULTI | SWT.FULL_SELECTION)); + WorkbenchViewerSetup.setupViewer(viewer); viewer.getTree().setLinesVisible(true); viewer.setUseHashlookup(true); createColumns(new TreeColumn[0], new int[0]); @@ -872,7 +874,8 @@ public IMarker[] getSelectedMarkers() { final List result = new ArrayList<>(structured.size()); MarkerCategory lastCategory = null; for (Iterator i = structured.iterator(); i.hasNext();) { - final MarkerSupportItem next = (MarkerSupportItem) i.next(); + Object item = i.next(); + if (item instanceof MarkerSupportItem next) { if (next.isConcrete()) { if (lastCategory != null && lastCategory == next.getParent()) { continue; @@ -887,6 +890,7 @@ public IMarker[] getSelectedMarkers() { } } } + } return result.toArray(new IMarker[result.size()]); } @@ -1377,13 +1381,15 @@ void updateDirectionIndicator(TreeColumn column, MarkerField field) { * @param newSelection */ void updateStatusLine(IStructuredSelection newSelection) { - String message; + String message = null; if (newSelection == null || newSelection.isEmpty()) { message = MarkerSupportInternalUtilities.EMPTY_STRING; } else if (newSelection.size() == 1) { // Use the Message attribute of the marker - message = ((MarkerSupportItem) newSelection.getFirstElement()).getDescription(); + if (newSelection.getFirstElement() instanceof MarkerSupportItem element) { + message = element.getDescription(); + } } else { Iterator elements = newSelection.iterator(); diff --git a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/views/markers/MarkerProblemSeverityAndMessageField.java b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/views/markers/MarkerProblemSeverityAndMessageField.java index 42bc04ccd0b..a4b2d3be545 100644 --- a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/views/markers/MarkerProblemSeverityAndMessageField.java +++ b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/views/markers/MarkerProblemSeverityAndMessageField.java @@ -76,7 +76,8 @@ private Image getImage(MarkerItem item) { public void update(ViewerCell cell) { super.update(cell); - MarkerItem item = (MarkerItem) cell.getElement(); - cell.setImage(annotateImage(item, getImage(item))); + if (cell.getElement() instanceof MarkerItem item) { + cell.setImage(annotateImage(item, getImage(item))); + } } } diff --git a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/views/markers/MarkerViewerContentProvider.java b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/views/markers/MarkerViewerContentProvider.java index 57761f5f861..28c3330951c 100644 --- a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/views/markers/MarkerViewerContentProvider.java +++ b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/views/markers/MarkerViewerContentProvider.java @@ -82,7 +82,10 @@ private Object[] getLimitedChildren(Object[] children) { @Override public Object getParent(Object element) { - Object parent = ((MarkerSupportItem) element).getParent(); + Object parent = null; + if (element instanceof MarkerSupportItem) { + parent = ((MarkerSupportItem) element).getParent(); + } if (parent == null) return input; return parent; diff --git a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/views/markers/MarkersTreeViewer.java b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/views/markers/MarkersTreeViewer.java index 66d4a6f7b85..c87a2e3bbee 100644 --- a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/views/markers/MarkersTreeViewer.java +++ b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/views/markers/MarkersTreeViewer.java @@ -71,9 +71,10 @@ protected void doUpdateItem(Item item, Object element) { /* * For performance reasons clear cache of the item used in updating UI. */ - MarkerSupportItem cellItem = (MarkerSupportItem) element; - if (cellItem.isConcrete()) - cellItem.clearCache(); + if (element instanceof MarkerSupportItem cellItem) { + if (cellItem.isConcrete()) + cellItem.clearCache(); + } } } diff --git a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/views/markers/MarkerField.java b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/views/markers/MarkerField.java index b5993672440..3c71d2ff62b 100644 --- a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/views/markers/MarkerField.java +++ b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/views/markers/MarkerField.java @@ -252,8 +252,10 @@ public final void setImageManager(ResourceManager manager) { * @param cell cell to update; not null */ public void update(ViewerCell cell) { - cell.setText(getValue((MarkerItem) cell.getElement())); - cell.setImage(null); + if (cell.getElement() instanceof MarkerItem element) { + cell.setText(getValue(element)); + cell.setImage(null); + } } @Override diff --git a/bundles/org.eclipse.ui.navigator.resources/META-INF/MANIFEST.MF b/bundles/org.eclipse.ui.navigator.resources/META-INF/MANIFEST.MF index 14c20c4a5c9..6392614d07e 100644 --- a/bundles/org.eclipse.ui.navigator.resources/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.ui.navigator.resources/META-INF/MANIFEST.MF @@ -15,7 +15,7 @@ Export-Package: org.eclipse.ui.internal.navigator.resources;x-internal:=true, org.eclipse.ui.navigator.resources Require-Bundle: org.eclipse.ui.ide;bundle-version="[3.15.0,4.0.0)", org.eclipse.core.resources;bundle-version="[3.6.0,4.0.0)", - org.eclipse.jface;bundle-version="[3.18.0,4.0.0)", + org.eclipse.jface;bundle-version="[3.31.0,4.0.0)", org.eclipse.ui;bundle-version="[3.6.0,4.0.0)", org.eclipse.ui.navigator;bundle-version="[3.9.0,4.0.0)", org.eclipse.core.runtime;bundle-version="[3.27.0,4.0.0)", diff --git a/bundles/org.eclipse.ui.navigator.resources/src/org/eclipse/ui/navigator/resources/ProjectExplorer.java b/bundles/org.eclipse.ui.navigator.resources/src/org/eclipse/ui/navigator/resources/ProjectExplorer.java index cb3f5b2f7c4..d98031c675b 100644 --- a/bundles/org.eclipse.ui.navigator.resources/src/org/eclipse/ui/navigator/resources/ProjectExplorer.java +++ b/bundles/org.eclipse.ui.navigator.resources/src/org/eclipse/ui/navigator/resources/ProjectExplorer.java @@ -73,6 +73,7 @@ import org.eclipse.ui.navigator.CommonNavigator; import org.eclipse.ui.navigator.CommonViewer; import org.eclipse.ui.navigator.INavigatorContentService; +import org.eclipse.ui.views.WorkbenchViewerSetup; /** * @@ -335,6 +336,7 @@ protected void handleDoubleClick(DoubleClickEvent anEvent) { protected CommonViewer createCommonViewer(Composite aParent) { CommonViewer viewer = super.createCommonViewer(aParent); emptyWorkspaceHelper.setNonEmptyControl(viewer.getControl()); + WorkbenchViewerSetup.setupViewer(viewer); return viewer; } diff --git a/bundles/org.eclipse.ui.views/META-INF/MANIFEST.MF b/bundles/org.eclipse.ui.views/META-INF/MANIFEST.MF index ca0dcd96a4d..0f3cc66a91d 100644 --- a/bundles/org.eclipse.ui.views/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.ui.views/META-INF/MANIFEST.MF @@ -12,6 +12,6 @@ Export-Package: org.eclipse.ui.internal.views.contentoutline;x-internal:=true, Require-Bundle: org.eclipse.core.runtime;bundle-version="[3.28.0,4.0.0)", org.eclipse.help;bundle-version="[3.2.0,4.0.0)", org.eclipse.ui;bundle-version="[3.5.0,4.0.0)", - org.eclipse.jface;bundle-version="[3.18.0,4.0.0)" + org.eclipse.jface;bundle-version="[3.31.0,4.0.0)" Bundle-RequiredExecutionEnvironment: JavaSE-17 Automatic-Module-Name: org.eclipse.ui.views diff --git a/bundles/org.eclipse.ui.views/src/org/eclipse/ui/views/contentoutline/ContentOutlinePage.java b/bundles/org.eclipse.ui.views/src/org/eclipse/ui/views/contentoutline/ContentOutlinePage.java index 7440918738f..22534e2f418 100644 --- a/bundles/org.eclipse.ui.views/src/org/eclipse/ui/views/contentoutline/ContentOutlinePage.java +++ b/bundles/org.eclipse.ui.views/src/org/eclipse/ui/views/contentoutline/ContentOutlinePage.java @@ -26,6 +26,7 @@ import org.eclipse.swt.widgets.Control; import org.eclipse.ui.part.IPageSite; import org.eclipse.ui.part.Page; +import org.eclipse.ui.views.WorkbenchViewerSetup; /** * An abstract base class for content outline pages. @@ -80,6 +81,7 @@ public void addSelectionChangedListener(ISelectionChangedListener listener) { @Override public void createControl(Composite parent) { treeViewer = new TreeViewer(parent, getTreeStyle()); + WorkbenchViewerSetup.setupViewer(treeViewer); treeViewer.addSelectionChangedListener(this); } diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/IWorkbenchPreferenceConstants.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/IWorkbenchPreferenceConstants.java index 9e94e253104..c24fe38e4ff 100644 --- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/IWorkbenchPreferenceConstants.java +++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/IWorkbenchPreferenceConstants.java @@ -685,4 +685,17 @@ public interface IWorkbenchPreferenceConstants { * @since 3.129 */ String DISPOSE_CLOSED_BROWSER_HOVER_TIMEOUT = "disposeClosedBrowserHoverTimeout"; //$NON-NLS-1$ + + /** + * This preference specifies the limit for number of children that can be shown + * per parent element without expanding. + *

+ * This preference is an int value that defines how many child + * elements a parent viewer element can show before workbench will create + * "expand" item that hides all remaining child elements. + *

+ * + * @since 3.130 + */ + String LARGE_VIEW_LIMIT = "largeViewLimit"; //$NON-NLS-1$ } diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/WorkbenchMessages.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/WorkbenchMessages.java index 9c80e488012..35e14e90a58 100644 --- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/WorkbenchMessages.java +++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/WorkbenchMessages.java @@ -730,6 +730,8 @@ public class WorkbenchMessages extends NLS { public static String WorkbenchPreference_recentFilesError; public static String WorkbenchPreference_workbenchSaveInterval; public static String WorkbenchPreference_workbenchSaveIntervalError; + public static String WorkbenchPreference_largeViewLimit; + public static String WorkbenchPreference_largeViewLimitError; public static String WorkbenchEditorsAction_label; public static String WorkbookEditorsAction_label; diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/WorkbenchPreferenceInitializer.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/WorkbenchPreferenceInitializer.java index 259d218b47c..8a512db9333 100644 --- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/WorkbenchPreferenceInitializer.java +++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/WorkbenchPreferenceInitializer.java @@ -126,6 +126,9 @@ public void initializeDefaultPreferences() { node.putInt(IWorkbenchPreferenceConstants.DISPOSE_CLOSED_BROWSER_HOVER_TIMEOUT, -1); + // Don't show more than 10.000 child elements per parent by default + node.putInt(IWorkbenchPreferenceConstants.LARGE_VIEW_LIMIT, 10000); + node.put(IWorkbenchPreferenceConstants.RESOURCE_RENAME_MODE, IWorkbenchPreferenceConstants.RESOURCE_RENAME_MODE_INLINE); diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/dialogs/WorkbenchPreferencePage.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/dialogs/WorkbenchPreferencePage.java index 0a53697123e..27937bee24a 100644 --- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/dialogs/WorkbenchPreferencePage.java +++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/dialogs/WorkbenchPreferencePage.java @@ -67,6 +67,8 @@ public class WorkbenchPreferencePage extends PreferencePage implements IWorkbenc private IntegerFieldEditor saveInterval; + private IntegerFieldEditor largeViewLimit; + private boolean openOnSingleClick; private boolean selectOnHover; @@ -78,6 +80,7 @@ public class WorkbenchPreferencePage extends PreferencePage implements IWorkbenc private Button showInlineRenameButton; protected static int MAX_SAVE_INTERVAL = 9999; + protected static int MAX_VIEW_LIMIT = 1_000_000; private boolean renameModeInline; @@ -109,6 +112,7 @@ protected void createSettings(Composite composite) { createShowUserDialogPref(composite); createStickyCyclePref(composite); createHeapStatusPref(composite); + createLargeViewLimitPref(composite); } /** @@ -138,6 +142,41 @@ protected void createHeapStatusPref(Composite composite) { PrefUtil.getAPIPreferenceStore().getBoolean(IWorkbenchPreferenceConstants.SHOW_MEMORY_MONITOR)); } + /** + * Create the widget for the max number of elements in the view + * + * @param composite + */ + protected void createLargeViewLimitPref(Composite composite) { + Composite groupComposite = new Composite(composite, SWT.LEFT); + GridLayout layout = new GridLayout(); + layout.numColumns = 2; + groupComposite.setLayout(layout); + GridData gd = new GridData(); + gd.horizontalAlignment = GridData.FILL; + gd.grabExcessHorizontalSpace = true; + groupComposite.setLayoutData(gd); + + largeViewLimit = new IntegerFieldEditor(IWorkbenchPreferenceConstants.LARGE_VIEW_LIMIT, + WorkbenchMessages.WorkbenchPreference_largeViewLimit, groupComposite); + + largeViewLimit.setPreferenceStore(getPreferenceStore()); + largeViewLimit.setPage(this); + largeViewLimit.setTextLimit(7); + largeViewLimit.setErrorMessage( + NLS.bind(WorkbenchMessages.WorkbenchPreference_largeViewLimitError, Integer.valueOf(MAX_VIEW_LIMIT))); + largeViewLimit.setValidateStrategy(StringFieldEditor.VALIDATE_ON_KEY_STROKE); + largeViewLimit.setValidRange(0, MAX_VIEW_LIMIT); + + largeViewLimit.load(); + + largeViewLimit.setPropertyChangeListener(event -> { + if (event.getProperty().equals(FieldEditor.IS_VALID)) { + setValid(largeViewLimit.isValid()); + } + }); + } + /** * Creates the composite which will contain all the preference controls for this * page. @@ -359,6 +398,7 @@ public void init(IWorkbench aWorkbench) { protected void performDefaults() { IPreferenceStore store = getPreferenceStore(); saveInterval.loadDefault(); + largeViewLimit.loadDefault(); stickyCycleButton.setSelection(store.getBoolean(IPreferenceConstants.STICKY_CYCLE)); openOnSingleClick = store.getDefaultBoolean(IPreferenceConstants.OPEN_ON_SINGLE_CLICK); selectOnHover = store.getDefaultBoolean(IPreferenceConstants.SELECT_ON_HOVER); @@ -395,6 +435,7 @@ public boolean performOk() { store.setValue(IPreferenceConstants.OPEN_AFTER_DELAY, openAfterDelay); store.setValue(IPreferenceConstants.RUN_IN_BACKGROUND, showUserDialogButton.getSelection()); store.setValue(IPreferenceConstants.WORKBENCH_SAVE_INTERVAL, saveInterval.getIntValue()); + store.setValue(IWorkbenchPreferenceConstants.LARGE_VIEW_LIMIT, largeViewLimit.getIntValue()); String renameModeValue = IWorkbenchPreferenceConstants.RESOURCE_RENAME_MODE_INLINE; if (!renameModeInline) { diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/messages.properties b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/messages.properties index 86b224ef186..dffa5dbd881 100644 --- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/messages.properties +++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/messages.properties @@ -711,6 +711,9 @@ WorkbenchPreference_workbenchSaveIntervalError=The workbench save interval shoul WorkbenchEditorsAction_label=S&witch to Editor... WorkbookEditorsAction_label=&Quick Switch Editor +WorkbenchPreference_largeViewLimit=Show not more than given number of children per parent in views: +WorkbenchPreference_largeViewLimitError=The limit should be an integer between 0 and {0}. + WorkbenchEditorsDialog_title=Switch to Editor WorkbenchEditorsDialog_label=Select an &editor to switch to: WorkbenchEditorsDialog_closeSelected=&Close Selected Editors diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/views/WorkbenchViewerSetup.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/views/WorkbenchViewerSetup.java new file mode 100644 index 00000000000..4d00481b9c5 --- /dev/null +++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/views/WorkbenchViewerSetup.java @@ -0,0 +1,103 @@ +/******************************************************************************* + * Copyright (c) 2023 Andrey Loskutov (loskutov@gmx.de) and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Andrey Loskutov (loskutov@gmx.de) - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.views; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.jface.viewers.ColumnViewer; +import org.eclipse.jface.viewers.IExpandableNode; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.widgets.Control; +import org.eclipse.ui.IWorkbenchPreferenceConstants; +import org.eclipse.ui.internal.WorkbenchPlugin; + +/** + * @since 3.130 + */ +public class WorkbenchViewerSetup { + + static Map registeredViewers = new ConcurrentHashMap<>(); + + static final IPropertyChangeListener propertyChangeListener = new IPropertyChangeListener() { + + @Override + public void propertyChange(PropertyChangeEvent event) { + if (!IWorkbenchPreferenceConstants.LARGE_VIEW_LIMIT.equals(event.getProperty())) { + return; + } + int itemsLimit = getItemsLimit(); + registeredViewers.values().forEach(v -> { + v.setItemsLimit(itemsLimit); + v.refresh(); + }); + } + }; + + static { + getPreferenceStore().addPropertyChangeListener(propertyChangeListener); + } + + /** + * Returns the current viewer limit set in the {@code General} preference page. + * Default value is set to {@code 10000} + * + * @return {@link IWorkbenchPreferenceConstants#LARGE_VIEW_LIMIT} + */ + public static int getItemsLimit() { + return getPreferenceStore().getInt(IWorkbenchPreferenceConstants.LARGE_VIEW_LIMIT); + } + + private static IPreferenceStore getPreferenceStore() { + return WorkbenchPlugin.getDefault().getPreferenceStore(); + } + + /** + * Configure a {@link ColumnViewer} to show limited items per parent before + * showing an {@link IExpandableNode}. Limit used is read from preference + * {@link IWorkbenchPreferenceConstants#LARGE_VIEW_LIMIT}. Client must call this + * before {@link Viewer#setInput(Object)} + *

+ * User can change the viewer limit on preference page any time in the lifetime + * of the viewer. This setup takes care of refreshing the viewer with the new + * limit set. + *

+ * + * @param viewer {@link ColumnViewer} which has to configured for showing + * limited items. + */ + public static void setupViewer(ColumnViewer viewer) { + viewer.setItemsLimit(getItemsLimit()); + Control control = viewer.getControl(); + if (control != null) { + control.addDisposeListener(new DisposeListener(viewer)); + } + } + + private static class DisposeListener implements org.eclipse.swt.events.DisposeListener { + + public DisposeListener(ColumnViewer viewer) { + registeredViewers.put(this, viewer); + } + + @Override + public void widgetDisposed(DisposeEvent e) { + registeredViewers.remove(this); + } + + } +} diff --git a/bundles/org.eclipse.ui.workbench/META-INF/MANIFEST.MF b/bundles/org.eclipse.ui.workbench/META-INF/MANIFEST.MF index 087a1913342..adcf8dde3eb 100644 --- a/bundles/org.eclipse.ui.workbench/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.ui.workbench/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.ui.workbench; singleton:=true -Bundle-Version: 3.129.100.qualifier +Bundle-Version: 3.130.0.qualifier Bundle-Activator: org.eclipse.ui.internal.WorkbenchPlugin Bundle-ActivationPolicy: lazy Bundle-Vendor: %providerName @@ -95,7 +95,7 @@ Export-Package: org.eclipse.e4.ui.workbench.addons.perspectiveswitcher;x-interna org.eclipse.ui.wizards Require-Bundle: org.eclipse.core.runtime;bundle-version="[3.28.0,4.0.0)", org.eclipse.help;bundle-version="[3.2.0,4.0.0)", - org.eclipse.jface;bundle-version="[3.18.0,4.0.0)", + org.eclipse.jface;bundle-version="[3.31.0,4.0.0)", org.eclipse.jface.databinding;bundle-version="[1.3.0,2.0.0)", org.eclipse.core.databinding.property;bundle-version="[1.2.0,2.0.0)", org.eclipse.core.databinding.observable;bundle-version="[1.2.0,2.0.0)", diff --git a/examples/org.eclipse.jface.snippets/Eclipse JFace Snippets/org/eclipse/jface/snippets/viewers/Snippet068TableViewerWithLimit.java b/examples/org.eclipse.jface.snippets/Eclipse JFace Snippets/org/eclipse/jface/snippets/viewers/Snippet068TableViewerWithLimit.java new file mode 100644 index 00000000000..fe07a102b78 --- /dev/null +++ b/examples/org.eclipse.jface.snippets/Eclipse JFace Snippets/org/eclipse/jface/snippets/viewers/Snippet068TableViewerWithLimit.java @@ -0,0 +1,165 @@ +/******************************************************************************* + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Raghunandana Murthappa + *******************************************************************************/ + +package org.eclipse.jface.snippets.viewers; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; + +/** + * Table viewer with limits + */ +public class Snippet068TableViewerWithLimit { + List model; + final TableViewer viewer; + + private SelectionListener listener = new SelectionListener() { + + @Override + public void widgetSelected(SelectionEvent e) { + String data = (String) e.widget.getData(); + MyModel element = null; + switch (data) { + case "refresh": + viewer.refresh(); + break; + case "add": + element = new MyModel(); + model.add(element); + viewer.add(element); + break; + case "remove": + viewer.remove(model.remove(model.size() - 1)); + break; + case "setSelection": + viewer.setSelection(new StructuredSelection(model.get(model.size() - 1))); + default: + break; + } + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + + } + }; + + private static class MyContentProvider implements IStructuredContentProvider { + + @Override + public Object[] getElements(Object inputElement) { + return ((ArrayList) inputElement).toArray(); + } + + } + + public static class MyModel { + static int counter; + int id; + + public MyModel() { + this.id = counter++; + } + + @Override + public String toString() { + return "Item " + id; + } + } + + public Snippet068TableViewerWithLimit(Shell shell) { + Composite tableComp = new Composite(shell, SWT.BORDER); + tableComp.setLayout(new FillLayout(SWT.VERTICAL)); + tableComp.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + viewer = new TableViewer(tableComp, SWT.MULTI); + viewer.setLabelProvider(new LabelProvider()); + viewer.setItemsLimit(3); + viewer.setContentProvider(new MyContentProvider()); + createColumn(viewer.getTable(), "column1"); + model = createModel(); + viewer.setInput(model); + viewer.getTable().setLinesVisible(true); + + Composite buttons = new Composite(shell, SWT.BORDER); + buttons.setLayout(new FillLayout(SWT.VERTICAL)); + buttons.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, true)); + + Button refresh = new Button(buttons, SWT.PUSH); + refresh.setData("refresh"); + refresh.setText("refresh"); + refresh.addSelectionListener(listener); + + Button add = new Button(buttons, SWT.PUSH); + add.setText("add"); + add.setData("add"); + add.addSelectionListener(listener); + + Button remove = new Button(buttons, SWT.PUSH); + remove.setText("remove"); + remove.setData("remove"); + remove.addSelectionListener(listener); + + Button setSel = new Button(buttons, SWT.PUSH); + setSel.setText("setSelection"); + setSel.setData("setSelection"); + setSel.addSelectionListener(listener); + } + + public void createColumn(Table tb, String text) { + TableColumn column = new TableColumn(tb, SWT.NONE); + column.setWidth(100); + column.setText(text); + tb.setHeaderVisible(true); + } + + private List createModel() { + List model = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + model.add(new MyModel()); + } + return model; + } + + public static void main(String[] args) { + Display display = new Display(); + Shell shell = new Shell(display); + shell.setLayout(new GridLayout(2, false)); + new Snippet068TableViewerWithLimit(shell); + shell.open(); + + while (!shell.isDisposed()) { + if (!display.readAndDispatch()) + display.sleep(); + } + + display.dispose(); + + } + +} diff --git a/examples/org.eclipse.jface.snippets/Eclipse JFace Snippets/org/eclipse/jface/snippets/viewers/Snippet069TreeViewerWithLimit.java b/examples/org.eclipse.jface.snippets/Eclipse JFace Snippets/org/eclipse/jface/snippets/viewers/Snippet069TreeViewerWithLimit.java new file mode 100644 index 00000000000..8df60abd5cc --- /dev/null +++ b/examples/org.eclipse.jface.snippets/Eclipse JFace Snippets/org/eclipse/jface/snippets/viewers/Snippet069TreeViewerWithLimit.java @@ -0,0 +1,298 @@ +/******************************************************************************* + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Raghunandana Murthappa + *******************************************************************************/ + +package org.eclipse.jface.snippets.viewers; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.TreeSelection; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.swt.widgets.TreeColumn; + +/** + * A simple TreeViewer to demonstrate the usage of limits + */ + +public class Snippet069TreeViewerWithLimit { + + final TreeViewer viewer; + MyModel root; + private Object curSel; + + private SelectionListener listener = new SelectionListener() { + + @Override + public void widgetSelected(SelectionEvent e) { + String data = (String) e.widget.getData(); + switch (data) { + case "refresh": + if (curSel instanceof MyModel current) { + viewer.refresh(current, false); + } else { + viewer.refresh(); + } + break; + case "add": + if (curSel instanceof MyModel) { + MyModel current = (MyModel) curSel; + MyModel eleToAdd = new MyModel(current, 100); + current.addChild(eleToAdd); + viewer.add(curSel, eleToAdd); + } + break; + case "remove": + if (curSel instanceof MyModel current) { + current.getParent().getChildren().remove(current); + viewer.remove(current); + } + break; + case "setSelection": + IStructuredSelection sel = new StructuredSelection( + root.getChildren().get(2).getChildren().get(7).getChildren().get(7)); + viewer.setSelection(sel, true); + break; + case "expandAll": + viewer.expandAll(); + break; + case "expandToLevel": + if (curSel instanceof MyModel) { + viewer.expandToLevel(curSel, 2, false); + } + break; + case "collapseAll": + if (curSel instanceof MyModel) { + viewer.collapseAll(); + } + break; + case "collapseToLevel": + if (curSel instanceof MyModel) { + viewer.collapseToLevel(curSel, 3); + } + break; + default: + break; + } + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + + } + }; + + private static class MyContentProvider implements ITreeContentProvider { + + @Override + public Object[] getElements(Object inputElement) { + return ((MyModel) inputElement).children.toArray(); + } + + @Override + public Object[] getChildren(Object parentElement) { + if (!(parentElement instanceof MyModel)) { + return null; + } + return getElements(parentElement); + } + + @Override + public Object getParent(Object element) { + if (!(element instanceof MyModel)) { + return null; + } + + return ((MyModel) element).parent; + } + + @Override + public boolean hasChildren(Object element) { + if (!(element instanceof MyModel)) { + return false; + } + MyModel myModel = (MyModel) element; + return myModel.children.size() > 0; + } + + } + + public static class MyModel { + private MyModel parent; + + public MyModel getParent() { + return parent; + } + + private List children = new ArrayList<>(); + + public List getChildren() { + return children; + } + + static int counter; + int id; + + public MyModel(MyModel parent, int id) { + this.parent = parent; + this.id = id; + } + + @Override + public String toString() { + String rv = "Item "; + if (parent != null) { + rv = parent + "."; + } + + rv += id; + + return rv; + } + + public boolean removeChild(MyModel child) { + return children.remove(child); + } + + public boolean addChild(MyModel child) { + return children.add(child); + } + } + + public Snippet069TreeViewerWithLimit(Shell shell) { + Composite tableComp = new Composite(shell, SWT.BORDER); + tableComp.setLayout(new FillLayout(SWT.VERTICAL)); + tableComp.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + viewer = new TreeViewer(tableComp, SWT.MULTI); + viewer.setLabelProvider(new LabelProvider()); + viewer.setItemsLimit(3); + viewer.setContentProvider(new MyContentProvider()); + createColumn(viewer.getTree(), "Column1"); + createColumn(viewer.getTree(), "Column2"); + root = createModel(); + viewer.setInput(root); + + viewer.addSelectionChangedListener(new ISelectionChangedListener() { + + @Override + public void selectionChanged(SelectionChangedEvent event) { + if (event.getSelection() instanceof TreeSelection) { + curSel = ((TreeSelection) event.getSelection()).getFirstElement(); + } + } + }); + + Composite buttons = new Composite(shell, SWT.BORDER); + buttons.setLayout(new FillLayout(SWT.VERTICAL)); + buttons.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, true)); + + Button refresh = new Button(buttons, SWT.PUSH); + refresh.setData("refresh"); + refresh.setText("refresh"); + refresh.addSelectionListener(listener); + + Button add = new Button(buttons, SWT.PUSH); + add.setText("add"); + add.setData("add"); + add.addSelectionListener(listener); + + Button remove = new Button(buttons, SWT.PUSH); + remove.setText("remove"); + remove.setData("remove"); + remove.addSelectionListener(listener); + + Button setSel = new Button(buttons, SWT.PUSH); + setSel.setText("setSelection"); + setSel.setData("setSelection"); + setSel.addSelectionListener(listener); + + Button expAll = new Button(buttons, SWT.PUSH); + expAll.setText("expandAll"); + expAll.setData("expandAll"); + expAll.addSelectionListener(listener); + + Button expandToLevel = new Button(buttons, SWT.PUSH); + expandToLevel.setText("expandToLevel"); + expandToLevel.setData("expandToLevel"); + expandToLevel.addSelectionListener(listener); + + Button collapseAll = new Button(buttons, SWT.PUSH); + collapseAll.setText("collapseAll"); + collapseAll.setData("collapseAll"); + collapseAll.addSelectionListener(listener); + + Button collapseToLevel = new Button(buttons, SWT.PUSH); + collapseToLevel.setText("collapseToLevel"); + collapseToLevel.setData("collapseToLevel"); + collapseToLevel.addSelectionListener(listener); + + } + + public void createColumn(Tree tr, String text) { + TreeColumn column = new TreeColumn(tr, SWT.NONE); + column.setWidth(200); + column.setText(text); + tr.setHeaderVisible(true); + } + + private MyModel createModel() { + + MyModel root = new MyModel(null, 0); + + for (int i = 0; i < 15; i++) { + MyModel l1 = new MyModel(root, i); + root.addChild(l1); + for (int j = 0; j < 10; j++) { + MyModel l2 = new MyModel(l1, j); + l1.addChild(l2); + for (int j2 = 0; j2 < 10; j2++) { + MyModel l3 = new MyModel(l2, j2); + l2.addChild(l3); + + } + } + } + + return root; + } + + public static void main(String[] args) { + Display display = new Display(); + Shell shell = new Shell(display); + shell.setLayout(new GridLayout(2, false)); + new Snippet069TreeViewerWithLimit(shell); + shell.open(); + + while (!shell.isDisposed()) { + if (!display.readAndDispatch()) + display.sleep(); + } + + display.dispose(); + } +} diff --git a/examples/org.eclipse.jface.snippets/META-INF/MANIFEST.MF b/examples/org.eclipse.jface.snippets/META-INF/MANIFEST.MF index e1f124ac6df..5d7848897ab 100644 --- a/examples/org.eclipse.jface.snippets/META-INF/MANIFEST.MF +++ b/examples/org.eclipse.jface.snippets/META-INF/MANIFEST.MF @@ -2,9 +2,9 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: JFace Snippets Plug-in Bundle-SymbolicName: org.eclipse.jface.snippets -Bundle-Version: 3.6.0.qualifier +Bundle-Version: 3.6.100.qualifier Bundle-Vendor: Eclipse.org -Require-Bundle: org.eclipse.jface;bundle-version="[3.18.0,4.0.0)", +Require-Bundle: org.eclipse.jface;bundle-version="[3.31.0,4.0.0)", org.eclipse.jface.databinding, org.eclipse.core.runtime, org.eclipse.core.databinding, diff --git a/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/viewers/AllViewersTests.java b/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/viewers/AllViewersTests.java index 4ebae292ece..a4db27bf1a2 100644 --- a/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/viewers/AllViewersTests.java +++ b/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/viewers/AllViewersTests.java @@ -30,7 +30,9 @@ ListViewerRefreshTest.class, Bug200558Test.class, Bug201002TableViewerTest.class, Bug201002TreeViewerTest.class, Bug200337TableViewerTest.class, Bug203657TreeViewerTest.class, Bug203657TableViewerTest.class, Bug205700TreeViewerTest.class, Bug180504TableViewerTest.class, Bug180504TreeViewerTest.class, - Bug256889TableViewerTest.class, Bug287765Test.class, Bug242231Test.class, StyledStringBuilderTest.class }) + Bug256889TableViewerTest.class, Bug287765Test.class, Bug242231Test.class, StyledStringBuilderTest.class, + TreeViewerWithLimitTest.class, TreeViewerWithLimitCompatibilityTest.class, TableViewerWithLimitTest.class, + TableViewerWithLimitCompatibilityTest.class }) public class AllViewersTests { public static void main(String[] args) { diff --git a/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/viewers/BaseLimitBasedViewerTest.java b/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/viewers/BaseLimitBasedViewerTest.java new file mode 100644 index 00000000000..6a169244219 --- /dev/null +++ b/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/viewers/BaseLimitBasedViewerTest.java @@ -0,0 +1,115 @@ +/******************************************************************************* + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Raghunandana Murthappa + *******************************************************************************/ + +package org.eclipse.jface.tests.viewers; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jface.viewers.IExpandableNode; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.StructuredViewer; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerComparator; +import org.eclipse.jface.viewers.ViewerFilter; +import org.eclipse.swt.widgets.Composite; + +public class BaseLimitBasedViewerTest extends ViewerTestCase { + + List rootModel; + protected static final int VIEWER_LIMIT = 4; + + public BaseLimitBasedViewerTest(String name) { + super(name); + } + + @Override + protected StructuredViewer createViewer(Composite parent) { + return null; + } + + protected static List createModel() { + List rootModel = new ArrayList<>(); + for (int i = 0; i < 40; i++) { + if (i % 2 == 0) { + DataModel rootLevel = new DataModel(Integer.valueOf(i)); + for (int j = 0; j < 40; j++) { + if (j % 2 == 0) { + DataModel level1 = new DataModel(Integer.valueOf(j)); + level1.parent = rootLevel; + for (int k = 0; k < 40; k++) { + if (k % 2 == 0) { + DataModel level2 = new DataModel(Integer.valueOf(k)); + level2.parent = level1; + level1.addChild(level2); + } + + } + rootLevel.addChild(level1); + } + + } + rootModel.add(rootLevel); + } + } + return rootModel; + } + + protected static class DataModel { + public Integer id; + public List children; + public DataModel parent; + + public DataModel(Integer id) { + this.id = id; + children = new ArrayList<>(); + } + + public void addChild(DataModel child) { + children.add(child); + } + + @Override + public String toString() { + return "Item " + id; + } + } + + protected static class TestLabelProvider extends LabelProvider { + public static String expNodeLabel = "Load more.."; + + @Override + public String getText(Object element) { + if (element instanceof IExpandableNode) { + return expNodeLabel; + } + return super.getText(element); + } + } + + protected static class TestComparator extends ViewerComparator { + @Override + public int compare(Viewer viewer, Object e1, Object e2) { + DataModel dm1 = (DataModel) e1; + DataModel dm2 = (DataModel) e2; + return dm1.id.compareTo(dm2.id); + } + } + + public static class TestViewerFilter extends ViewerFilter { + @Override + public boolean select(Viewer viewer, Object parent, Object element) { + return ((DataModel) element).id.intValue() > 10; + } + } + +} diff --git a/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/viewers/TableViewerWithLimitCompatibilityTest.java b/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/viewers/TableViewerWithLimitCompatibilityTest.java new file mode 100644 index 00000000000..bdf2d59cbc5 --- /dev/null +++ b/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/viewers/TableViewerWithLimitCompatibilityTest.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Raghunandana Murthappa + *******************************************************************************/ + +package org.eclipse.jface.tests.viewers; + +import org.eclipse.jface.viewers.ColumnViewer; +import org.eclipse.jface.viewers.StructuredViewer; +import org.eclipse.swt.widgets.Composite; + +public class TableViewerWithLimitCompatibilityTest extends TableViewerTest { + + public TableViewerWithLimitCompatibilityTest(String name) { + super(name); + } + + @Override + protected StructuredViewer createViewer(Composite parent) { + ColumnViewer viewer = (ColumnViewer) super.createViewer(parent); + viewer.setItemsLimit(10); + return viewer; + } + + public static void main(String args[]) { + junit.textui.TestRunner.run(TableViewerWithLimitCompatibilityTest.class); + } +} diff --git a/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/viewers/TableViewerWithLimitTest.java b/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/viewers/TableViewerWithLimitTest.java new file mode 100644 index 00000000000..e206b68f45b --- /dev/null +++ b/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/viewers/TableViewerWithLimitTest.java @@ -0,0 +1,263 @@ +/******************************************************************************* + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Raghunandana Murthappa + *******************************************************************************/ + +package org.eclipse.jface.tests.viewers; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jface.viewers.IExpandableNode; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.StructuredViewer; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Item; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableItem; + +public class TableViewerWithLimitTest extends BaseLimitBasedViewerTest { + + public TableViewerWithLimitTest(String name) { + super(name); + } + + TableViewer tableViewer; + + public void testLimitedItemsCreatedWithExpansionNode() { + Table table = tableViewer.getTable(); + TableItem[] items = table.getItems(); + assertLimitedItems(items); + } + + public void testAddElement() { + Table table = tableViewer.getTable(); + assertLimitedItems(table.getItems()); + DataModel data = (DataModel) table.getItems()[2].getData(); + assertEquals("wrong item is found at given location", Integer.valueOf(4), data.id); + + // this element must be visible + DataModel newEle = new DataModel(Integer.valueOf(3)); + rootModel.add(newEle); + tableViewer.add(newEle); + // check items and label after addition. + assertLimitedItems(table.getItems()); + data = (DataModel) table.getItems()[2].getData(); + assertEquals("wrong item is found at given location", Integer.valueOf(3), data.id); + + // this element must not be visible only expandable node label must be updated. + DataModel newEle1 = new DataModel(Integer.valueOf(9)); + rootModel.add(newEle1); + tableViewer.add(newEle1); + // check items and label after addition. + assertLimitedItems(table.getItems()); + data = (DataModel) table.getItems()[2].getData(); + assertEquals("wrong item is found at given location", Integer.valueOf(3), data.id); + } + + public void testRemoveElement() { + Table table = tableViewer.getTable(); + assertLimitedItems(table.getItems()); + DataModel data = (DataModel) table.getItems()[2].getData(); + assertEquals("wrong item is found at given location", Integer.valueOf(4), data.id); + + // this element must be visible + DataModel removed = rootModel.remove(2); + tableViewer.remove(removed); + // check items and label after addition. + assertLimitedItems(table.getItems()); + data = (DataModel) table.getItems()[2].getData(); + assertEquals("wrong item is found at given location", Integer.valueOf(6), data.id); + + // this element must not be visible only expandable node label must be updated. + DataModel removed1 = rootModel.remove(7); + tableViewer.remove(removed1); + // check items and label after addition. + assertLimitedItems(table.getItems()); + data = (DataModel) table.getItems()[2].getData(); + assertEquals("wrong item is found at given location", Integer.valueOf(6), data.id); + } + + public void testClickExpandableNode() { + Table table = tableViewer.getTable(); + assertLimitedItems(table.getItems()); + TableItem lastItem = table.getItems()[table.getItems().length - 1]; + clickTableItem(table, lastItem); + Item[] itemsBefore = table.getItems(); + assertEquals("There are more/less items rendered than viewer limit", VIEWER_LIMIT * 2 + 1, itemsBefore.length); + assertTrue("Last node must be an Expandable Node", + itemsBefore[itemsBefore.length - 1].getData() instanceof IExpandableNode); + assertTrue("Expandable node has an incorrect text", itemsBefore[itemsBefore.length - 1].getText() + .equals("Expand [" + (VIEWER_LIMIT * 2 + 1) + "..." + rootModel.size() + "]")); + + // click until all expandable nodes are expanded. + clickUntilAllExpandableNodes(table); + + // all the elements of the model should be visible + Item[] itemsAfterExp = table.getItems(); + assertEquals("There are more/less items rendered than viewer limit", rootModel.size(), itemsAfterExp.length); + assertTrue("Last node must be an DataModel after all the elements expanded.", + itemsAfterExp[itemsAfterExp.length - 1].getData() instanceof DataModel); + } + + public void testApplyFilter() { + Table table = tableViewer.getTable(); + clickUntilAllExpandableNodes(table); + + // all the elements of the model should be visible + assertEquals("There are more/less items rendered than viewer limit", rootModel.size(), table.getItems().length); + DataModel data = (DataModel) table.getItems()[2].getData(); + assertEquals("wrong item is found at given location", Integer.valueOf(4), data.id); + + tableViewer.setFilters(new TestViewerFilter()); + + clickUntilAllExpandableNodes(table); + + // only filtered items are visible + assertEquals("There are more/less items rendered than viewer limit", 14, table.getItems().length); + data = (DataModel) table.getItems()[2].getData(); + assertEquals("wrong item is found at given location", Integer.valueOf(16), data.id); + } + + private void clickUntilAllExpandableNodes(Table table) { + TableItem lastItem = table.getItems()[table.getItems().length - 1]; + processEvents(); + while (lastItem.getData() instanceof IExpandableNode) { + clickTableItem(table, lastItem); + processEvents(); + lastItem = table.getItems()[table.getItems().length - 1]; + } + } + + public void testLabelProviderChanged() { + tableViewer.setLabelProvider(new TestLabelProvider()); + TableItem[] items = tableViewer.getTable().getItems(); + assertTrue("new label for expandable node is wrong", + items[items.length - 1].getText().equals(TestLabelProvider.expNodeLabel)); + } + + public void testResetComparator() { + Table table = tableViewer.getTable(); + // add an element at the end of the model. comparator will add it to right + // location + DataModel newEle1 = new DataModel(Integer.valueOf(3)); + rootModel.add(newEle1); + tableViewer.add(newEle1); + DataModel data = (DataModel) table.getItems()[2].getData(); + assertEquals("wrong item is found at given location", Integer.valueOf(3), data.id); + // reset comparator + tableViewer.setComparator(null); + data = (DataModel) table.getItems()[2].getData(); + assertEquals("wrong item is found at given location", Integer.valueOf(4), data.id); + } + + public void testSelection() { + // select an element which is not visible + DataModel toSelect = rootModel.get(rootModel.size() - VIEWER_LIMIT); + tableViewer.setSelection(new StructuredSelection(toSelect)); + ISelection selection = tableViewer.getSelection(); + assertTrue("Selection must not be empty", selection instanceof IStructuredSelection); + Object selEle = ((IStructuredSelection) selection).getFirstElement(); + assertTrue("Selection must be ExpandableNode", selEle instanceof IExpandableNode); + + // select an element which is visible + toSelect = rootModel.get(VIEWER_LIMIT / 2); + tableViewer.setSelection(new StructuredSelection(toSelect)); + selection = tableViewer.getSelection(); + assertTrue("Selection must not be empty", selection instanceof IStructuredSelection); + selEle = ((IStructuredSelection) selection).getFirstElement(); + assertEquals("selection must be desired element which is visible", toSelect, selEle); + + // select something not present in model. + tableViewer.setSelection(new StructuredSelection("dummy")); + selection = tableViewer.getSelection(); + assertTrue("Selection must not be empty", selection.isEmpty()); + } + + public void testRefresh() { + Table table = tableViewer.getTable(); + assertLimitedItems(table.getItems()); + assertEquals("third element must be third element of the input", rootModel.get(2), table.getItem(2).getData()); + DataModel ele1 = new DataModel(Integer.valueOf(100)); + rootModel.add(ele1); + tableViewer.add(ele1); + assertLimitedItems(table.getItems()); + assertEquals("third element must be third element of the input", rootModel.get(2), table.getItem(2).getData()); + DataModel newEle = new DataModel(Integer.valueOf(3)); + rootModel.add(newEle); + tableViewer.add(newEle); + assertLimitedItems(table.getItems()); + assertEquals("third element must be newly added element", newEle, table.getItem(2).getData()); + } + + private void assertLimitedItems(TableItem[] itemsBefore) { + assertEquals("There are more/less items rendered than viewer limit", VIEWER_LIMIT + 1, itemsBefore.length); + assertTrue("Last node must be an Expandable Node", + itemsBefore[itemsBefore.length - 1].getData() instanceof IExpandableNode); + assertTrue("Expandable node has an incorrect text", itemsBefore[itemsBefore.length - 1].getText() + .equals("Expand [" + (VIEWER_LIMIT + 1) + "..." + rootModel.size() + "]")); + } + + public void testSetInput() { + List rootModel = new ArrayList<>(); + DataModel rootLevel = new DataModel(Integer.valueOf(100)); + rootModel.add(rootLevel); + tableViewer.setInput(rootModel); + assertEquals("there must be only one item", 1, tableViewer.getTable().getItems().length); + tableViewer.setInput(createModel()); + assertLimitedItems(tableViewer.getTable().getItems()); + } + + private static class TestContentProvider implements IStructuredContentProvider { + @Override + public Object[] getElements(Object inputElement) { + return ((ArrayList) inputElement).toArray(); + } + + } + + @Override + protected StructuredViewer createViewer(Composite parent) { + tableViewer = new TableViewer(parent); + tableViewer.setItemsLimit(VIEWER_LIMIT); + tableViewer.setLabelProvider(new LabelProvider()); + tableViewer.setContentProvider(new TestContentProvider()); + tableViewer.setComparator(new TestComparator()); + return tableViewer; + } + + @Override + protected void setInput() { + rootModel = createModel(); + fViewer.setInput(rootModel); + } + + public static void main(String args[]) { + junit.textui.TestRunner.run(TableViewerWithLimitTest.class); + } + + private void clickTableItem(Control viewerControl, TableItem item) { + Rectangle bounds = item.getBounds(); + Event event = new Event(); + event.x = bounds.x + 5; + event.y = bounds.y + 5; + viewerControl.notifyListeners(SWT.MouseDown, event); + } + +} diff --git a/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/viewers/TreeViewerWithLimitCompatibilityTest.java b/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/viewers/TreeViewerWithLimitCompatibilityTest.java new file mode 100644 index 00000000000..74183b74ba7 --- /dev/null +++ b/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/viewers/TreeViewerWithLimitCompatibilityTest.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Raghunandana Murthappa + *******************************************************************************/ + +package org.eclipse.jface.tests.viewers; + +import org.eclipse.jface.viewers.ColumnViewer; +import org.eclipse.jface.viewers.StructuredViewer; +import org.eclipse.swt.widgets.Composite; + +public class TreeViewerWithLimitCompatibilityTest extends TreeViewerTest { + + public TreeViewerWithLimitCompatibilityTest(String name) { + super(name); + } + + @Override + protected StructuredViewer createViewer(Composite parent) { + ColumnViewer viewer = (ColumnViewer) super.createViewer(parent); + // set higher limit than number of items in the input. + // this should not break any existing tests. + viewer.setItemsLimit(20); + return viewer; + } + + public static void main(String args[]) { + junit.textui.TestRunner.run(TreeViewerWithLimitCompatibilityTest.class); + } + +} diff --git a/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/viewers/TreeViewerWithLimitTest.java b/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/viewers/TreeViewerWithLimitTest.java new file mode 100644 index 00000000000..60ee412a687 --- /dev/null +++ b/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/viewers/TreeViewerWithLimitTest.java @@ -0,0 +1,299 @@ +/******************************************************************************* + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Raghunandana Murthappa + *******************************************************************************/ + +package org.eclipse.jface.tests.viewers; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jface.viewers.IExpandableNode; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.StructuredViewer; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.TreeItem; + +public class TreeViewerWithLimitTest extends BaseLimitBasedViewerTest { + + private TreeViewer treeViewer; + + public TreeViewerWithLimitTest(String name) { + super(name); + } + + public void testSetSelection() { + DataModel firstEle = rootModel.get(0); + assertSetSelection(firstEle); + + DataModel invisibel = rootModel.get(rootModel.size() - VIEWER_LIMIT); + assertSetSelectionExpNode(invisibel); + } + + private void assertSetSelectionExpNode(DataModel invisibel) { + treeViewer.setSelection(new StructuredSelection(invisibel), true); + ISelection selection = treeViewer.getSelection(); + assertTrue("Selection must not be empty", selection instanceof IStructuredSelection); + assertTrue("Selection must be expandable node", + ((IStructuredSelection) selection).getFirstElement() instanceof IExpandableNode); + } + + private void assertSetSelection(DataModel firstEle) { + treeViewer.setSelection(new StructuredSelection(firstEle)); + ISelection selection = treeViewer.getSelection(); + assertTrue("Selection must not be empty", selection instanceof IStructuredSelection); + assertEquals("incorrect element is selected", firstEle, ((IStructuredSelection) selection).getFirstElement()); + } + + public void testReveal() { + DataModel firstEle = rootModel.get(0); + DataModel toReveal = firstEle.children.get(2).children.get(2); + treeViewer.reveal(toReveal); + // selection works if the element was revealed. selection don't try to create + // item but reveal does. + assertSetSelection(toReveal); + + // now try to reveal non expanded item. it should not reveal anything because in + // case of limit based tree we don't create an item if it hidden. + DataModel inVisible = firstEle.children.get(2).children.get(VIEWER_LIMIT + 2); + assertSetSelectionExpNode(inVisible); + Object selEle = ((IStructuredSelection) treeViewer.getSelection()).getFirstElement(); + Object[] remEle = ((IExpandableNode) selEle).getRemainingElements(); + boolean found = false; + for (Object object : remEle) { + if (object == inVisible) { + found = true; + break; + } + + } + assertTrue("item to select must be inside expandable node", found); + } + + public void testCollapseAll() { + treeViewer.expandAll(); + DataModel l2ThirdEle = rootModel.get(0).children.get(2).children.get(2); + assertSetSelection(l2ThirdEle); + + treeViewer.collapseAll(); + + TreeItem[] items = assertLimitedItems(); + for (int i = 0; i < VIEWER_LIMIT; i++) { + TreeItem treeItem = items[i]; + assertFalse("expansion must be false", treeItem.getExpanded()); + + } + } + + public void testCollapseToLevel() { + } + + public void testExpandAll() { + treeViewer.expandAll(); + TreeItem[] rootLevelItems = assertLimitedItems(); + for (int i = 0; i < VIEWER_LIMIT; i++) { + TreeItem[] l1Items = assertLimitedItems(rootLevelItems[i]); + for (int j = 0; j < VIEWER_LIMIT; j++) { + TreeItem treeItem2 = l1Items[j]; + assertLimitedItems(treeItem2); + } + } + } + + private static TreeItem[] assertLimitedItems(TreeItem treeItem) { + TreeItem[] l1Items = treeItem.getItems(); + assertEquals("There should be only limited items", VIEWER_LIMIT + 1, l1Items.length); + assertTrue("last item must be expandable node", l1Items[VIEWER_LIMIT].getData() instanceof IExpandableNode); + return l1Items; + } + + private TreeItem[] assertLimitedItems() { + TreeItem[] rootLevelItems = treeViewer.getTree().getItems(); + assertEquals("There should be only limited items", VIEWER_LIMIT + 1, rootLevelItems.length); + assertTrue("last item must be expandable node", + rootLevelItems[VIEWER_LIMIT].getData() instanceof IExpandableNode); + return rootLevelItems; + } + + public void testExpandToLevelInt() { + treeViewer.expandToLevel(2, true); + TreeItem[] rootLevelItems = assertLimitedItems(); + for (int i = 0; i < VIEWER_LIMIT; i++) { + TreeItem[] l1Items = assertLimitedItems(rootLevelItems[i]); + for (int j = 0; j < VIEWER_LIMIT; j++) { + TreeItem treeItem2 = l1Items[j]; + assertDummyItem(treeItem2); + } + } + } + + private static void assertDummyItem(TreeItem treeItem2) { + assertTrue("Level3 must not be expanded", treeItem2.getItems().length == 1); + assertNull("Dummy tree item data must be null", treeItem2.getItems()[0].getData()); + } + + public void testExpandToLevelObjectInt() { + DataModel firstEle = rootModel.get(0); + treeViewer.expandToLevel(firstEle, 3, true); + TreeItem[] topLevelItems = assertLimitedItems(); + TreeItem forstItem = topLevelItems[0]; + TreeItem[] L1Children = assertLimitedItems(forstItem); + for (int i = 1; i < VIEWER_LIMIT; i++) { + TreeItem treeItem2 = L1Children[i]; + assertLimitedItems(treeItem2); + } + // no other items are expanded + for (int i = 1; i < VIEWER_LIMIT; i++) { + TreeItem treeItem2 = topLevelItems[i]; + assertDummyItem(treeItem2); + } + } + + public void testRemoveItemsAtParent() { + treeViewer.expandAll(); + DataModel firstEle = rootModel.get(0); + DataModel thirdOfFirst = firstEle.children.get(2).children.remove(2); + TreeItem visItem = treeViewer.getTree().getItem(0).getItem(2).getItem(2); + assertEquals("element contains unexpected data", thirdOfFirst, visItem.getData()); + treeViewer.remove(firstEle, new Object[] { thirdOfFirst }); + thirdOfFirst = firstEle.children.get(2).children.get(2); + visItem = treeViewer.getTree().getItem(0).getItem(2).getItem(2); + assertEquals("element contains unexpected data", thirdOfFirst, visItem.getData()); + + } + + public void testRemoveItem() { + DataModel firstEle = rootModel.remove(0); + TreeItem firstItem = treeViewer.getTree().getItem(0); + assertEquals("element contains unexpected data", firstEle, firstItem.getData()); + treeViewer.remove(firstEle); + firstEle = rootModel.get(0); + firstItem = treeViewer.getTree().getItem(0); + assertEquals("element contains unexpected data", firstEle, firstItem.getData()); + } + + public void testSetAutoExpandLevel() { + treeViewer.setInput(new DataModel(Integer.valueOf(100))); + treeViewer.setAutoExpandLevel(2); + treeViewer.setInput(rootModel); + TreeItem[] rootLevelItems = assertLimitedItems(); + for (int i = 0; i < VIEWER_LIMIT; i++) { + TreeItem[] l1Items = assertLimitedItems(rootLevelItems[i]); + for (int j = 0; j < VIEWER_LIMIT; j++) { + TreeItem treeItem2 = l1Items[j]; + assertDummyItem(treeItem2); + } + } + } + + public void testInsert() { + TreeItem thirdItem = treeViewer.getTree().getItem(2); + assertEquals("unexpected element found at position 2", rootModel.get(2), thirdItem.getData()); + DataModel newElement = new DataModel(Integer.valueOf(3)); + rootModel.add(newElement); + treeViewer.insert(rootModel, newElement, 2); + thirdItem = treeViewer.getTree().getItem(2); + assertEquals("unexpected element found at position 2", newElement, thirdItem.getData()); + } + + public void testRefresh() { + DataModel firstEle = rootModel.remove(0); + TreeItem firstItem = treeViewer.getTree().getItem(0); + assertEquals("element contains unexpected data", firstEle, firstItem.getData()); + treeViewer.refresh(); + firstEle = rootModel.get(0); + firstItem = treeViewer.getTree().getItem(0); + assertEquals("element contains unexpected data", firstEle, firstItem.getData()); + } + + public void testSetFilters() { + DataModel firstEle = rootModel.get(0); + TreeItem firstItem = treeViewer.getTree().getItem(0); + assertEquals("element contains unexpected data", firstEle, firstItem.getData()); + treeViewer.setFilters(new TestViewerFilter()); + firstEle = rootModel.get(6); + firstItem = treeViewer.getTree().getItem(0); + assertEquals("element contains unexpected data", firstEle, firstItem.getData()); + } + + public void testSetInput() { + List rootModel = new ArrayList<>(); + DataModel rootLevel = new DataModel(Integer.valueOf(100)); + rootModel.add(rootLevel); + treeViewer.setInput(rootModel); + assertEquals("there must be only one item", 1, treeViewer.getTree().getItems().length); + treeViewer.setInput(createModel()); + assertLimitedItems(); + } + + @Override + protected StructuredViewer createViewer(Composite parent) { + treeViewer = new TreeViewer(parent); + treeViewer.setItemsLimit(VIEWER_LIMIT); + treeViewer.setContentProvider(new TestTreeContentProvider()); + treeViewer.setLabelProvider(new LabelProvider()); + treeViewer.setComparator(new TestComparator()); + return treeViewer; + } + + @Override + protected void setInput() { + rootModel = createModel(); + treeViewer.setInput(rootModel); + } + + public static void main(String args[]) { + junit.textui.TestRunner.run(TreeViewerWithLimitTest.class); + } + + private static class TestTreeContentProvider implements ITreeContentProvider { + + @Override + public Object[] getElements(Object inputElement) { + if (inputElement instanceof ArrayList) { + return ((ArrayList) inputElement).toArray(); + } + return ((DataModel) inputElement).children.toArray(); + } + + @Override + public Object[] getChildren(Object parentElement) { + if (!(parentElement instanceof DataModel)) { + return null; + } + return getElements(parentElement); + } + + @Override + public Object getParent(Object element) { + if (!(element instanceof DataModel)) { + return null; + } + + return ((DataModel) element).parent; + } + + @Override + public boolean hasChildren(Object element) { + if (element instanceof ArrayList) { + return !((ArrayList) element).isEmpty(); + } + DataModel myModel = (DataModel) element; + return myModel.children.size() > 0; + } + + } + +}