From f7980cfbef58da74bf58878b7b14aba36be1de3a Mon Sep 17 00:00:00 2001 From: raghucssit Date: Tue, 22 Aug 2023 10:44:07 +0200 Subject: [PATCH] Expand an expandable node once it is visible. It will calculate the location of expandable node, once it enters visible area of Viewer expand it. Install resize listener for control when limit is set and remove limit is reset.Do the same thing for Vertical Scroll bar control. Honor the entire drag event as one selection at the end of the drag. see https://github.com/eclipse-platform/eclipse.platform.ui/issues/818 --- .../jface/viewers/AbstractTreeViewer.java | 2 +- .../eclipse/jface/viewers/ColumnViewer.java | 12 +- .../eclipse/jface/viewers/TableViewer.java | 188 ++++++++++++---- .../org/eclipse/jface/viewers/TreeViewer.java | 205 ++++++++++++++---- 4 files changed, 321 insertions(+), 86 deletions(-) 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 0d1e89e7b2e..88e05a29dfb 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 @@ -1541,7 +1541,7 @@ protected List getSelectionFromWidget() { protected void handleDoubleSelect(SelectionEvent event) { // expand ExpandableNode for default selection. if (event.item != null && event.item.getData() instanceof ExpandableNode node) { - handleExpandableNodeClicked(event.item); + handleExpandableNodeClicked(event.item, true); // do not notify client listeners for this item. return; } 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 01e8a995551..a6be714ba6f 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 @@ -668,7 +668,7 @@ private void handleMouseDown(MouseEvent e) { if (cell != null) { if (cell.getElement() instanceof ExpandableNode) { - handleExpandableNodeClicked(cell.getItem()); + handleExpandableNodeClicked(cell.getItem(), true); } else { triggerEditorActivationEvent(new ColumnViewerEditorActivationEvent(cell, e)); } @@ -903,9 +903,10 @@ final Object[] applyItemsLimit(Object parent, Object[] sorted) { *

* Default implementation does nothing. * - * @param cell selected on click + * @param cell selected on click + * @param setSelection */ - void handleExpandableNodeClicked(Widget cell) { + void handleExpandableNodeClicked(Widget cell, boolean setSelection) { // default implementation does nothing. Actual viewers can decide how to // populate remaining elements. } @@ -935,12 +936,15 @@ Object[] getChildrenWithLimitApplied(final Object parent, Item[] visibleChildren } // fetch entire sorted children we need them in any of next cases. + boolean oldBusy = isBusy(); + setBusy(true); setDisplayIncrementally(0); Object[] sortedAll; try { sortedAll = getSortedChildren(parent); } finally { setDisplayIncrementally(limit); + setBusy(oldBusy); } // model has lost some elements and length is less then visible items. @@ -1049,7 +1053,7 @@ Set getExpandableNodes() { @Override protected void handleDoubleSelect(SelectionEvent event) { if (event.item != null && event.item.getData() instanceof ExpandableNode) { - handleExpandableNodeClicked(event.item); + handleExpandableNodeClicked(event.item, true); // we do not want client listeners to be notified for this item. return; } 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 a117d7c4897..3a31ce59819 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 @@ -22,10 +22,15 @@ import org.eclipse.core.runtime.Assert; import org.eclipse.jface.viewers.internal.ExpandableNode; import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.BusyIndicator; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.ControlListener; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Item; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.ScrollBar; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableItem; import org.eclipse.swt.widgets.Widget; @@ -78,6 +83,16 @@ public class TableViewer extends AbstractTableViewer { */ private TableViewerRow cachedRow; + /** + * listen to tree resize and expand expandable node once it is visible. + */ + private ControlListener controlListener = null; + + /** + * listen to scroll bar selection and expand expandable node once it is visible. + */ + private Listener vScrollListener = null; + /** * Creates a table viewer on a newly-created table control under the given * parent. The table control is created using the SWT style bits @@ -458,51 +473,150 @@ protected Widget doFindItem(Object element) { } @Override - void handleExpandableNodeClicked(Widget w) { - if (!(w instanceof Item item)) { - return; - } + void handleExpandableNodeClicked(Widget w, boolean setSelection) { + BusyIndicator.showWhile(table.getDisplay(), () -> { + + if (!(w instanceof Item item)) { + return; + } + + Object data = item.getData(); + + if (!(data instanceof ExpandableNode expNode)) { + return; + } + boolean oldBusy = isBusy(); + Table table = getTable(); + try { + setBusy(true); + table.setRedraw(false); + + Object[] sortedChildren = expNode.getRemainingElements(); + Object[] nextChildren = applyItemsLimit(data, sortedChildren); + + if (nextChildren.length > 0) { + disassociate(item); + int index = doIndexOf(item); + // will also call item.dispose() + doRemove(new int[] { index }); + + for (int i = 0; i < nextChildren.length; i++) { + createItem(nextChildren[i], index++); + } + + if (!setSelection) { + return; + } + + // 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 { + // reset the selection. client's selection listener should not be triggered. + // there was only one selection on Expandable Node. + int[] curSel = table.getSelectionIndices(); + if (curSel.length == 1) { + table.deselect(curSel[0]); + } + } + } + } finally { + setBusy(oldBusy); + table.setRedraw(true); + } - Object data = item.getData(); + }); + } + + @Override + public void setDisplayIncrementally(int incrementSize) { + super.setDisplayIncrementally(incrementSize); - if (!(data instanceof ExpandableNode expNode)) { + if (isBusy()) { return; } - boolean oldBusy = isBusy(); - Table table = getTable(); - try { - setBusy(true); - table.setRedraw(false); - - Object[] sortedChildren = expNode.getRemainingElements(); - Object[] nextChildren = applyItemsLimit(data, sortedChildren); - - if (nextChildren.length > 0) { - disassociate(item); - int index = doIndexOf(item); - // will also call item.dispose() - doRemove(new int[] { index }); - - for (int i = 0; i < nextChildren.length; i++) { - createItem(nextChildren[i], index++); + + // trying to reset the viewer limit. + if (incrementSize <= 0) { + if (controlListener != null) { + table.removeControlListener(controlListener); + controlListener = null; + } + if (vScrollListener != null) { + ScrollBar vScrol = table.getVerticalBar(); + if (vScrol != null) { + vScrol.removeListener(SWT.Selection, vScrollListener); + vScrollListener = null; } - // 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 { - // reset the selection. client's selection listener should not be triggered. - // there was only one selection on Expandable Node. - int[] curSel = table.getSelectionIndices(); - if (curSel.length == 1) { - table.deselect(curSel[0]); + } + return; + } + + // trying to set/change viewer limit. + if (controlListener == null) { + controlListener = new ControlListener() { + @Override + public void controlResized(ControlEvent e) { + if (table.getItemCount() > 0) { + TableItem lastItem = table.getItems()[table.getItemCount() - 1]; + if (isExpandableNode(lastItem.getData())) { + int treeHeight = table.getBounds().height; + int itemsHeight = lastItem.getBounds().height * table.getItemCount(); + int origHeight = itemsHeight; + // there are few items shown than tree height. + while (itemsHeight < treeHeight) { + // expand last node. + handleExpandableNodeClicked(lastItem, false); + // check if if have no more expandable node. + lastItem = table.getItems()[table.getItemCount() - 1]; + if (!isExpandableNode(lastItem.getData())) { + break; + } + // each expansion should increase the height by original height. + itemsHeight = itemsHeight + origHeight; + } + } } } + + @Override + public void controlMoved(ControlEvent e) { + } + }; + // listen to tree resizes. + table.addControlListener(controlListener); + } + + if (vScrollListener == null) { + // listen to vertical scroll bar selection + ScrollBar vScrol = table.getVerticalBar(); + if (vScrol != null) { + vScrollListener = event -> { + // A series events from drag triggers multiple expansions. This results in + // behavior of Virtual viewer. + // Entire drag ends with single selection and if the Expandable node is visible + // we can expand. + if (event.detail == 1) { + return; + } + if (table.getItemCount() > 0) { + TableItem lastItem = table.getItems()[table.getItemCount() - 1]; + if (isExpandableNode(lastItem.getData())) { + int treeHeight = table.getBounds().height; + int startOfLast = lastItem.getBounds().y; + // last element became visible. + if (startOfLast < treeHeight) { + handleExpandableNodeClicked(lastItem, false); + } + } + } + + }; + vScrol.addListener(SWT.Selection, vScrollListener); } - } finally { - setBusy(oldBusy); - table.setRedraw(true); + } } 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 acb669e7c13..8d847c4c2ac 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 @@ -27,12 +27,17 @@ import org.eclipse.jface.util.Policy; import org.eclipse.jface.viewers.internal.ExpandableNode; import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.BusyIndicator; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.ControlListener; import org.eclipse.swt.events.TreeEvent; import org.eclipse.swt.events.TreeListener; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Item; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.ScrollBar; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeItem; import org.eclipse.swt.widgets.Widget; @@ -96,6 +101,16 @@ public class TreeViewer extends AbstractTreeViewer { */ private boolean insidePreservingSelection; + /** + * listen to tree resize and expand expandable node once it is visible. + */ + private ControlListener controlListener = null; + + /** + * listen to scroll bar selection and expand expandable node once it is visible. + */ + private Listener vScrollListener = null; + /** * Creates a tree viewer on a newly-created tree control under the given * parent. The tree control is created using the SWT style bits @@ -1133,59 +1148,70 @@ public void editElement(Object element, int column) { } @Override - void handleExpandableNodeClicked(Widget w) { - if (!(w instanceof Item item)) { - return; - } + void handleExpandableNodeClicked(Widget w, boolean setSelection) { + BusyIndicator.showWhile(getTree().getDisplay(), new Runnable() { + @Override + public void run() { - Object data = item.getData(); - if (data == null) { - return; - } + if (!(w instanceof Item item)) { + return; + } - Object[] children = getSortedChildren(data); - if (children.length == 0) { - return; - } + Object data = item.getData(); + if (data == null) { + return; + } - boolean oldBusy = isBusy(); - Tree tree = getTree(); - try { - setBusy(true); - tree.setRedraw(false); + Object[] children = getSortedChildren(data); + if (children.length == 0) { + return; + } - Widget parent = getParentItem(item); - if (parent == null) { - parent = getControl(); - } + boolean oldBusy = isBusy(); + Tree tree = getTree(); + try { + setBusy(true); + tree.setRedraw(false); - // destroy widget - disassociate(item); - item.dispose(); + Widget parent = getParentItem(item); + if (parent == null) { + parent = getControl(); + } - // create children on parent - for (Object element : children) { - createTreeItem(parent, element, -1); - } + // destroy widget + disassociate(item); + item.dispose(); - // 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 { - // reset the selection. client's selection listener should not be triggered. - // there was only one selection on Expandable Node. - Item[] curSel = tree.getSelection(); - if (curSel.length == 1) { - tree.deselect((TreeItem) curSel[0]); + // create children on parent + for (Object element : children) { + createTreeItem(parent, element, -1); + } + + if (!setSelection) { + return; + } + + // 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 { + // reset the selection. client's selection listener should not be triggered. + // there was only one selection on Expandable Node. + Item[] curSel = tree.getSelection(); + if (curSel.length == 1) { + tree.deselect((TreeItem) curSel[0]); + } + } + } finally { + tree.setRedraw(true); + setBusy(oldBusy); } + } - } finally { - tree.setRedraw(true); - setBusy(oldBusy); - } + }); } /** @@ -1203,4 +1229,95 @@ Object getLastElement(Widget parent) { } return items[length - 1].getData(); } + + @Override + public void setDisplayIncrementally(int incrementSize) { + super.setDisplayIncrementally(incrementSize); + + if (isBusy()) { + return; + } + + // trying to reset the viewer limit. + if (incrementSize <= 0) { + if (controlListener != null) { + tree.removeControlListener(controlListener); + controlListener = null; + } + if (vScrollListener != null) { + ScrollBar vScrol = tree.getVerticalBar(); + if (vScrol != null) { + vScrol.removeListener(SWT.Selection, vScrollListener); + vScrollListener = null; + } + } + return; + } + + // trying to set/change viewer limit. + if (controlListener == null) { + controlListener = new ControlListener() { + @Override + public void controlResized(ControlEvent e) { + if (tree.getItemCount() > 0) { + TreeItem lastItem = tree.getItems()[tree.getItemCount() - 1]; + if (isExpandableNode(lastItem.getData())) { + int treeHeight = tree.getBounds().height; + int itemsHeight = lastItem.getBounds().height * tree.getItemCount(); + int origHeight = itemsHeight; + // there are few items shown than tree height. + while (itemsHeight < treeHeight) { + // expand last node. + handleExpandableNodeClicked(lastItem, false); + // check if if have no more expandable node. + lastItem = tree.getItems()[tree.getItemCount() - 1]; + if (!isExpandableNode(lastItem.getData())) { + break; + } + // each expansion should increase the height by original height. + itemsHeight = itemsHeight + origHeight; + } + } + } + } + + @Override + public void controlMoved(ControlEvent e) { + } + }; + // listen to tree resizes. + tree.addControlListener(controlListener); + } + + if (vScrollListener == null) { + // listen to vertical scroll bar selection + ScrollBar vScrol = tree.getVerticalBar(); + if (vScrol != null) { + vScrollListener = event -> { + // A series events from drag triggers multiple expansions. This results in + // behavior of Virtual viewer. + // Entire drag ends with single selection and if the Expandable node is visible + // we can expand. + if (event.detail == 1) { + return; + } + if (tree.getItemCount() > 0) { + TreeItem lastItem = tree.getItems()[tree.getItemCount() - 1]; + if (isExpandableNode(lastItem.getData())) { + int treeHeight = tree.getBounds().height; + int startOfLast = lastItem.getBounds().y; + // last element became visible. + if (startOfLast < treeHeight) { + handleExpandableNodeClicked(lastItem, false); + } + } + } + + }; + + vScrol.addListener(SWT.Selection, vScrollListener); + } + + } + } }