Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian W. Damus2019-05-15 15:59:52 +0000
committerChristian Damus2019-05-16 16:57:55 +0000
commitec01e683ad249b8e6aeab6e29f86a420a3fab547 (patch)
treef0ebaab72cdc1b9cdd6ea110b0cdaca122a74c48
parentdf8d884e8ee5485690d049f6a0a3d6d8e8ce024b (diff)
downloadorg.eclipse.emf.ecp.core-ec01e683ad249b8e6aeab6e29f86a420a3fab547.tar.gz
org.eclipse.emf.ecp.core-ec01e683ad249b8e6aeab6e29f86a420a3fab547.tar.xz
org.eclipse.emf.ecp.core-ec01e683ad249b8e6aeab6e29f86a420a3fab547.zip
Bug 547271 - [Performance] Filtering and adding objects to large tables is slow
Provide a more efficient filtering mechanism and avoid redundant viewer refreshes. Cache a generated view model for each EClass that needs it and clone in the usual way (plus tweaks for read-only controls) for each object of that class. Don’t set properties of cells that don’t need updating, because it is expensive to do so (especially fg/bg colours and text). Change-Id: I47a3fd894e3e692252fc4448806dc827ad2ab671 Signed-off-by: Christian W. Damus <give.a.damus@gmail.com>
-rw-r--r--bundles/org.eclipse.emf.ecp.edit.swt/src/org/eclipse/emf/ecp/edit/spi/swt/table/ECPFilterableCell.java38
-rw-r--r--bundles/org.eclipse.emf.ecp.view.model.generator/src/org/eclipse/emf/ecp/view/model/generator/ViewCache.java325
-rw-r--r--bundles/org.eclipse.emf.ecp.view.model.generator/src/org/eclipse/emf/ecp/view/model/generator/ViewProvider.java144
-rw-r--r--bundles/org.eclipse.emf.ecp.view.table.celleditor.rcp/src/org/eclipse/emf/ecp/view/internal/table/celleditor/rcp/BooleanCellEditor.java10
-rw-r--r--bundles/org.eclipse.emf.ecp.view.table.ui.nebula.grid/META-INF/MANIFEST.MF1
-rw-r--r--bundles/org.eclipse.emf.ecp.view.table.ui.nebula.grid/src/org/eclipse/emf/ecp/view/spi/table/nebula/grid/GridTableViewerComposite.java54
-rw-r--r--bundles/org.eclipse.emf.ecp.view.table.ui.nebula.grid/src/org/eclipse/emf/ecp/view/spi/table/nebula/grid/GridViewerColumnBuilder.java14
-rw-r--r--bundles/org.eclipse.emf.ecp.view.table.ui.swt/src/org/eclipse/emf/ecp/view/spi/table/swt/TableControlSWTRenderer.java106
-rw-r--r--bundles/org.eclipse.emfforms.swt.table/src/org/eclipse/emfforms/spi/swt/table/AbstractTableViewerComposite.java9
-rw-r--r--bundles/org.eclipse.emfforms.swt.table/src/org/eclipse/emfforms/spi/swt/table/ViewerRefreshManager.java72
-rw-r--r--tests/org.eclipse.emf.ecp.view.model.provider.generator.test/src/org/eclipse/emf/ecp/view/model/generator/ViewProvider_Test.java118
-rw-r--r--tests/org.eclipse.emf.ecp.view.table.ui.nebula.grid.test/src/org/eclipse/emf/ecp/view/internal/table/nebula/grid/GridControlRenderer_PTest.java87
12 files changed, 770 insertions, 208 deletions
diff --git a/bundles/org.eclipse.emf.ecp.edit.swt/src/org/eclipse/emf/ecp/edit/spi/swt/table/ECPFilterableCell.java b/bundles/org.eclipse.emf.ecp.edit.swt/src/org/eclipse/emf/ecp/edit/spi/swt/table/ECPFilterableCell.java
new file mode 100644
index 0000000000..947985ac76
--- /dev/null
+++ b/bundles/org.eclipse.emf.ecp.edit.swt/src/org/eclipse/emf/ecp/edit/spi/swt/table/ECPFilterableCell.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * Copyright (c) 2019 Christian W. Damus and others.
+ *
+ * All rights reserved. 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:
+ * Christian W. Damus - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.emf.ecp.edit.spi.swt.table;
+
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.jface.viewers.CellLabelProvider;
+
+/**
+ * Optional interface (either implemented or provided as an {@linkplain IAdaptable adapter})
+ * for a cell that supports filtering by its text content. This allows for complex cell
+ * renderers, for example, to avoid costly updates via the
+ * {@link CellLabelProvider#update(org.eclipse.jface.viewers.ViewerCell)} API that do more
+ * than just render text.
+ *
+ * @since 1.21
+ */
+public interface ECPFilterableCell {
+
+ /**
+ * Query the text to filter on.
+ *
+ * @param object the object to be filtered
+ * @return the text to filter on, or an empty string if none
+ */
+ String getFilterableText(Object object);
+
+}
diff --git a/bundles/org.eclipse.emf.ecp.view.model.generator/src/org/eclipse/emf/ecp/view/model/generator/ViewCache.java b/bundles/org.eclipse.emf.ecp.view.model.generator/src/org/eclipse/emf/ecp/view/model/generator/ViewCache.java
new file mode 100644
index 0000000000..0ee09cc318
--- /dev/null
+++ b/bundles/org.eclipse.emf.ecp.view.model.generator/src/org/eclipse/emf/ecp/view/model/generator/ViewCache.java
@@ -0,0 +1,325 @@
+/*******************************************************************************
+ * Copyright (c) 2011-2019 EclipseSource Muenchen GmbH and others.
+ *
+ * All rights reserved. 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:
+ * Edgar - initial API and implementation
+ * Christian W. Damus - bug 547271
+ ******************************************************************************/
+package org.eclipse.emf.ecp.view.model.generator;
+
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Random;
+import java.util.function.Predicate;
+
+import org.eclipse.emf.ecore.EAttribute;
+import org.eclipse.emf.ecore.EClass;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EPackage;
+import org.eclipse.emf.ecore.EReference;
+import org.eclipse.emf.ecore.EStructuralFeature;
+import org.eclipse.emf.ecore.util.EcoreUtil;
+import org.eclipse.emf.ecp.view.model.common.edit.provider.CustomReflectiveItemProviderAdapterFactory;
+import org.eclipse.emf.ecp.view.spi.model.VControl;
+import org.eclipse.emf.ecp.view.spi.model.VDomainModelReference;
+import org.eclipse.emf.ecp.view.spi.model.VFeaturePathDomainModelReference;
+import org.eclipse.emf.ecp.view.spi.model.VView;
+import org.eclipse.emf.ecp.view.spi.model.VViewFactory;
+import org.eclipse.emf.ecp.view.spi.model.VViewPackage;
+import org.eclipse.emf.ecp.view.spi.table.model.VTableDomainModelReference;
+import org.eclipse.emf.ecp.view.spi.table.model.VTableFactory;
+import org.eclipse.emf.edit.provider.AdapterFactoryItemDelegator;
+import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
+import org.eclipse.emf.edit.provider.IItemPropertyDescriptor;
+
+/**
+ * A cache of the generated {@link VView} for an {@code EObject}.
+ * It is assumed that the valid features of an {@link EClass} are the same for all instances
+ * of that class and will not change during its lifetime, in keeping with standard EMF
+ * assumptions and the actual behaviour of generated models.
+ */
+final class ViewCache {
+
+ private static final int CACHE_SIZE = 100;
+ private static final Random RANDOMIZER = new Random();
+
+ @SuppressWarnings("serial")
+ private final Map<EClass, ViewRecord> views = new LinkedHashMap<EClass, ViewRecord>() {
+ @Override
+ protected boolean removeEldestEntry(java.util.Map.Entry<EClass, ViewRecord> eldest) {
+ return size() > CACHE_SIZE;
+ }
+ };
+ private final AdapterFactoryItemDelegator delegator;
+
+ /**
+ * Initializes me.
+ */
+ ViewCache() {
+ super();
+
+ final ComposedAdapterFactory composedAdapterFactory = new ComposedAdapterFactory(
+ ComposedAdapterFactory.Descriptor.Registry.INSTANCE);
+ composedAdapterFactory.insertAdapterFactory(new CustomReflectiveItemProviderAdapterFactory());
+
+ delegator = new AdapterFactoryItemDelegator(composedAdapterFactory);
+ }
+
+ /**
+ * Get the generated view for an {@code object}.
+ *
+ * @param object an object for which to get the generated mutable view model
+ * @return the generated view model for the {@code object}
+ */
+ VView getView(EObject object) {
+ final EClass eClass = object.eClass();
+ ViewRecord prototype = views.get(eClass);
+ if (prototype == null) {
+ prototype = generatePrototype(eClass);
+ views.put(eClass, prototype);
+ }
+
+ final VView result = prototype.instantiate(object);
+ return result;
+ }
+
+ private ViewRecord generatePrototype(EClass eClass) {
+ final VView view = VViewFactory.eINSTANCE.createView();
+ view.setUuid(generateId(eClass, null));
+ final ViewRecord result = new ViewRecord(view);
+
+ final EObject example = EcoreUtil.create(eClass);
+ final Predicate<EStructuralFeature> isValidFeature = feature -> isValidFeature(feature, example);
+ eClass.getEAllStructuralFeatures().stream().filter(isValidFeature)
+ .forEach(feature -> {
+ final VControl control;
+ if (isTableFeature(feature)) {
+ control = VTableFactory.eINSTANCE.createTableControl();
+ final VTableDomainModelReference tableDmr = VTableFactory.eINSTANCE
+ .createTableDomainModelReference();
+ tableDmr.setDomainModelReference(createModelReference(feature));
+ control.setDomainModelReference(tableDmr);
+ } else {
+ control = VViewFactory.eINSTANCE.createControl();
+ control.setDomainModelReference(createModelReference(feature));
+ }
+ control.setUuid(result.generateID(eClass, feature));
+ view.getChildren().add(control);
+
+ // If it was valid, then it had a property descriptor
+ final IItemPropertyDescriptor propertyDescriptor = delegator.getPropertyDescriptor(example, feature);
+ result.add(control, feature, propertyDescriptor);
+ });
+
+ // Let the adapter factory not leak the example instance
+ example.eAdapters().clear();
+
+ view.setRootEClass(eClass);
+
+ return result;
+ }
+
+ private VDomainModelReference createModelReference(final EStructuralFeature feature) {
+ final VFeaturePathDomainModelReference modelReference = VViewFactory.eINSTANCE
+ .createFeaturePathDomainModelReference();
+ modelReference.setDomainModelEFeature(feature);
+ return modelReference;
+ }
+
+ private boolean isTableFeature(EStructuralFeature feature) {
+ if (feature instanceof EReference) {
+ final EReference ref = (EReference) feature;
+ return ref.isMany() && ref.isContainment();
+ }
+ return false;
+ }
+
+ private boolean isValidFeature(EStructuralFeature feature, EObject owner) {
+ boolean result = !isInvalidFeature(feature);
+
+ if (result) {
+ // Further, check that there's a property descriptor
+ result = delegator.getPropertyDescriptor(owner, feature) != null;
+ }
+
+ return result;
+ }
+
+ private boolean isInvalidFeature(EStructuralFeature feature) {
+ return isContainerReference(feature) || isTransient(feature) || isVolatile(feature);
+ }
+
+ private boolean isContainerReference(EStructuralFeature feature) {
+ if (feature instanceof EReference) {
+ final EReference reference = (EReference) feature;
+ if (reference.isContainer()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private boolean isTransient(EStructuralFeature feature) {
+ return feature.isTransient();
+ }
+
+ private boolean isVolatile(EStructuralFeature feature) {
+ return feature.isVolatile();
+ }
+
+ // this is not unique, because of the use of hashCode, so it needs to be post-processed
+ private static String generateId(EClass eClass, EStructuralFeature feature) {
+ final StringBuilder stringBuilder = new StringBuilder();
+ final EPackage ePackage = eClass.getEPackage();
+ if (ePackage != null) {
+ /* might be null with dynamic emf */
+ stringBuilder.append(ePackage.getNsURI());
+ }
+ stringBuilder.append("#"); //$NON-NLS-1$
+ stringBuilder.append(eClass.getName());
+ if (feature != null) {
+ stringBuilder.append("#"); //$NON-NLS-1$
+ stringBuilder.append(feature.getName());
+ }
+ return Integer.toHexString(stringBuilder.toString().hashCode());
+ }
+
+ //
+ // Nested types
+ //
+
+ /**
+ * Internal tracking of a generated view model with metadata for calculation
+ * of enablement of individual controls according to the EMF.Edit property
+ * descriptor for each control as driven by a particular object in the editor.
+ */
+ private static final class ViewRecord {
+ private final Map<String, ControlRecord> controls = new HashMap<>();
+ private final VView view;
+
+ private final ViewCopier copier = new ViewCopier();
+
+ ViewRecord(VView view) {
+ super();
+
+ this.view = view;
+ }
+
+ /**
+ * Generate an ID that is guaranteed to be unique within my view.
+ *
+ * @param eClass the owner class for which to generate the ID
+ * @param feature the feature for which to generate the ID
+ *
+ * @return the unique generated ID
+ */
+ String generateID(EClass eClass, EStructuralFeature feature) {
+ String result = ViewCache.generateId(eClass, feature);
+
+ while (controls.containsKey(result)) {
+ // mangle it
+ int value = Integer.parseInt(result, 16);
+ value = value ^ RANDOMIZER.nextInt();
+ result = Integer.toHexString(value);
+ }
+
+ return result;
+ }
+
+ void add(VControl control, EStructuralFeature feature, IItemPropertyDescriptor propertyDescriptor) {
+ controls.put(control.getUuid(), new ControlRecord(feature, propertyDescriptor));
+ }
+
+ VView instantiate(EObject object) {
+ copier.setOwner(object);
+ final VView result = (VView) copier.copy(view);
+ copier.copyReferences();
+ copier.clear();
+ return result;
+ }
+
+ //
+ // Nested types
+ //
+
+ /**
+ * A specialized copier that sets control enablement computed from
+ * the EMF.Edit property source for the control.
+ */
+ @SuppressWarnings("serial")
+ private final class ViewCopier extends EcoreUtil.Copier {
+
+ private EObject owner;
+
+ ViewCopier() {
+ super();
+ }
+
+ @Override
+ public void clear() {
+ owner = null;
+ super.clear();
+ }
+
+ @Override
+ protected void copyAttribute(EAttribute eAttribute, EObject eObject, EObject copyEObject) {
+ // Don't copy the read-only attribute; we calculate it
+ if (eAttribute != VViewPackage.Literals.ELEMENT__READONLY) {
+ super.copyAttribute(eAttribute, eObject, copyEObject);
+ }
+
+ if (eAttribute == VViewPackage.Literals.ELEMENT__UUID) {
+ // We now have the UUID, so can compute enablement override
+ if (copyEObject instanceof VControl) {
+ final VControl control = (VControl) copyEObject;
+ final ControlRecord record = controls.get(control.getUuid());
+ if (record != null) {
+ control.setReadonly(!record.isEditable(owner));
+ }
+ }
+ }
+ }
+
+ /**
+ * Set the object for which we are copying the view model, to edit it.
+ *
+ * @param owner the owner object of the features to be edited
+ */
+ void setOwner(EObject owner) {
+ this.owner = owner;
+ }
+ }
+
+ }
+
+ /**
+ * A record tracking metadata about the structural feature edited by a control,
+ * in particular for determination of enablement according to its EMF.Edit item
+ * provider.
+ */
+ private static final class ControlRecord {
+ private final IItemPropertyDescriptor propertyDescriptor;
+ private final boolean changeable;
+
+ ControlRecord(EStructuralFeature feature, IItemPropertyDescriptor propertyDescriptor) {
+ super();
+
+ this.propertyDescriptor = propertyDescriptor;
+ changeable = feature.isChangeable();
+ }
+
+ boolean isEditable(EObject owner) {
+ return changeable && propertyDescriptor.canSetProperty(owner);
+ }
+ }
+
+}
diff --git a/bundles/org.eclipse.emf.ecp.view.model.generator/src/org/eclipse/emf/ecp/view/model/generator/ViewProvider.java b/bundles/org.eclipse.emf.ecp.view.model.generator/src/org/eclipse/emf/ecp/view/model/generator/ViewProvider.java
index cc019c3795..ad654543ac 100644
--- a/bundles/org.eclipse.emf.ecp.view.model.generator/src/org/eclipse/emf/ecp/view/model/generator/ViewProvider.java
+++ b/bundles/org.eclipse.emf.ecp.view.model.generator/src/org/eclipse/emf/ecp/view/model/generator/ViewProvider.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2011-2018 EclipseSource Muenchen GmbH and others.
+ * Copyright (c) 2011-2019 EclipseSource Muenchen GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
@@ -10,153 +10,35 @@
*
* Contributors:
* Edgar - initial API and implementation
+ * Christian W. Damus - bug 547271
******************************************************************************/
package org.eclipse.emf.ecp.view.model.generator;
-import java.util.Collection;
-import java.util.LinkedHashSet;
-import java.util.Set;
-
-import org.eclipse.emf.common.notify.AdapterFactory;
-import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
-import org.eclipse.emf.ecore.EPackage;
-import org.eclipse.emf.ecore.EReference;
-import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.util.EcoreUtil;
-import org.eclipse.emf.ecp.view.model.common.edit.provider.CustomReflectiveItemProviderAdapterFactory;
-import org.eclipse.emf.ecp.view.spi.model.VControl;
-import org.eclipse.emf.ecp.view.spi.model.VDomainModelReference;
-import org.eclipse.emf.ecp.view.spi.model.VFeaturePathDomainModelReference;
import org.eclipse.emf.ecp.view.spi.model.VView;
-import org.eclipse.emf.ecp.view.spi.model.VViewFactory;
import org.eclipse.emf.ecp.view.spi.model.VViewModelProperties;
import org.eclipse.emf.ecp.view.spi.provider.IViewProvider;
-import org.eclipse.emf.ecp.view.spi.table.model.VTableDomainModelReference;
-import org.eclipse.emf.ecp.view.spi.table.model.VTableFactory;
-import org.eclipse.emf.edit.provider.AdapterFactoryItemDelegator;
-import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
-import org.eclipse.emf.edit.provider.IItemPropertyDescriptor;
/**
* View Provider.
*/
public class ViewProvider implements IViewProvider {
- @Override
- public VView provideViewModel(EObject eObject, VViewModelProperties properties) {
- final VView view = VViewFactory.eINSTANCE.createView();
- view.setUuid(generateId(eObject.eClass(), null));
- final ComposedAdapterFactory composedAdapterFactory = new ComposedAdapterFactory(
- new AdapterFactory[] {
- new CustomReflectiveItemProviderAdapterFactory(),
- new ComposedAdapterFactory(
- ComposedAdapterFactory.Descriptor.Registry.INSTANCE) });
- final AdapterFactoryItemDelegator delegator = new AdapterFactoryItemDelegator(
- composedAdapterFactory);
- for (final EStructuralFeature feature : getValidFeatures(delegator, eObject)) {
- final VControl control;
- if (isTableFeature(feature)) {
- control = VTableFactory.eINSTANCE.createTableControl();
- final VTableDomainModelReference tableDmr = VTableFactory.eINSTANCE.createTableDomainModelReference();
- tableDmr.setDomainModelReference(createModelReference(feature));
- control.setDomainModelReference(tableDmr);
- } else {
- control = VViewFactory.eINSTANCE.createControl();
- control.setDomainModelReference(createModelReference(feature));
- }
- control.setReadonly(isReadOnly(delegator, eObject, feature));
- control.setUuid(generateId(eObject.eClass(), feature));
- view.getChildren().add(control);
- }
- composedAdapterFactory.dispose();
- view.setRootEClass(eObject.eClass());
- view.setLoadingProperties(EcoreUtil.copy(properties));
- return view;
- }
-
- private VDomainModelReference createModelReference(final EStructuralFeature feature) {
- final VFeaturePathDomainModelReference modelReference = VViewFactory.eINSTANCE
- .createFeaturePathDomainModelReference();
- modelReference.setDomainModelEFeature(feature);
- return modelReference;
- }
-
- private boolean isTableFeature(EStructuralFeature feature) {
- if (feature instanceof EReference) {
- final EReference ref = (EReference) feature;
- return ref.isMany() && ref.isContainment();
- }
- return false;
- }
-
- private boolean isReadOnly(AdapterFactoryItemDelegator delegator,
- EObject owner, EStructuralFeature feature) {
- if (!feature.isChangeable()) {
- return true;
- }
- final IItemPropertyDescriptor descriptor = delegator.getPropertyDescriptor(owner,
- feature);
- return !descriptor.canSetProperty(feature);
- }
+ private final ViewCache cache = new ViewCache();
- private boolean isInvalidFeature(EStructuralFeature feature) {
- return isContainerReference(feature) || isTransient(feature) || isVolatile(feature);
+ /**
+ * Initializes me.
+ */
+ public ViewProvider() {
+ super();
}
- private boolean isContainerReference(EStructuralFeature feature) {
- if (feature instanceof EReference) {
- final EReference reference = (EReference) feature;
- if (reference.isContainer()) {
- return true;
- }
- }
-
- return false;
- }
-
- private boolean isTransient(EStructuralFeature feature) {
- return feature.isTransient();
- }
-
- private boolean isVolatile(EStructuralFeature feature) {
- return feature.isVolatile();
- }
-
- private Set<EStructuralFeature> getValidFeatures(
- AdapterFactoryItemDelegator itemDelegator, EObject eObject) {
- final Collection<EStructuralFeature> features = eObject.eClass()
- .getEAllStructuralFeatures();
- final Set<EStructuralFeature> featuresToAdd = new LinkedHashSet<EStructuralFeature>();
- IItemPropertyDescriptor propertyDescriptor = null;
- for (final EStructuralFeature feature : features) {
- propertyDescriptor = itemDelegator
- .getPropertyDescriptor(eObject, feature);
- if (propertyDescriptor == null || isInvalidFeature(feature)) {
- continue;
- }
-
- featuresToAdd.add(feature);
-
- }
- return featuresToAdd;
- }
-
- // TODO this is not unique, because of the use of hashCode, but maybe good enough?
- private static String generateId(EClass eClass, EStructuralFeature feature) {
- final StringBuilder stringBuilder = new StringBuilder();
- final EPackage ePackage = eClass.getEPackage();
- if (ePackage != null) {
- /* might be null with dynamic emf */
- stringBuilder.append(ePackage.getNsURI());
- }
- stringBuilder.append("#"); //$NON-NLS-1$
- stringBuilder.append(eClass.getName());
- if (feature != null) {
- stringBuilder.append("#"); //$NON-NLS-1$
- stringBuilder.append(feature.getName());
- }
- return String.valueOf(stringBuilder.toString().hashCode());
+ @Override
+ public VView provideViewModel(EObject eObject, VViewModelProperties properties) {
+ final VView result = cache.getView(eObject);
+ result.setLoadingProperties(EcoreUtil.copy(properties));
+ return result;
}
@Override
diff --git a/bundles/org.eclipse.emf.ecp.view.table.celleditor.rcp/src/org/eclipse/emf/ecp/view/internal/table/celleditor/rcp/BooleanCellEditor.java b/bundles/org.eclipse.emf.ecp.view.table.celleditor.rcp/src/org/eclipse/emf/ecp/view/internal/table/celleditor/rcp/BooleanCellEditor.java
index 4d2670c9c9..58c979eb29 100644
--- a/bundles/org.eclipse.emf.ecp.view.table.celleditor.rcp/src/org/eclipse/emf/ecp/view/internal/table/celleditor/rcp/BooleanCellEditor.java
+++ b/bundles/org.eclipse.emf.ecp.view.table.celleditor.rcp/src/org/eclipse/emf/ecp/view/internal/table/celleditor/rcp/BooleanCellEditor.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2011-2015 EclipseSource Muenchen GmbH and others.
+ * Copyright (c) 2011-2019 EclipseSource Muenchen GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
@@ -10,6 +10,7 @@
*
* Contributors:
* jfaltermeier - initial API and implementation
+ * Christian W. Damus - bug 547271
******************************************************************************/
package org.eclipse.emf.ecp.view.internal.table.celleditor.rcp;
@@ -235,8 +236,13 @@ public class BooleanCellEditor extends CellEditor implements ECPCellEditor, ECPC
*/
@Override
public void updateCell(ViewerCell cell, Object value) {
+ if (!"".equals(cell.getText())) { //$NON-NLS-1$
cell.setText(""); //$NON-NLS-1$
- cell.setImage(getImage(value));
+ }
+ final Image image = getImage(value);
+ if (cell.getImage() != image) {
+ cell.setImage(image);
+ }
setCopyTextMarker(cell, value);
}
diff --git a/bundles/org.eclipse.emf.ecp.view.table.ui.nebula.grid/META-INF/MANIFEST.MF b/bundles/org.eclipse.emf.ecp.view.table.ui.nebula.grid/META-INF/MANIFEST.MF
index ac17abcf55..9590679ebb 100644
--- a/bundles/org.eclipse.emf.ecp.view.table.ui.nebula.grid/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.emf.ecp.view.table.ui.nebula.grid/META-INF/MANIFEST.MF
@@ -25,6 +25,7 @@ Require-Bundle: org.eclipse.emf.ecp.edit.swt;bundle-version="[1.21.0,1.22.0)",
org.eclipse.emf.ecp.view.core.swt;bundle-version="[1.21.0,1.22.0)";visibility:=reexport,
org.eclipse.emf.ecp.ui.view.swt;bundle-version="[1.21.0,1.22.0)"
Import-Package: javax.inject;version="1.0.0",
+ org.eclipse.core.runtime;version="[3.5.0,4.0.0)",
org.eclipse.emf.ecp.view.spi.table.model;version="[1.21.0,1.22.0)",
org.eclipse.emf.ecp.view.spi.table.swt;version="[1.21.0,1.22.0)",
org.eclipse.emfforms.spi.common.report;version="[1.21.0,1.22.0)",
diff --git a/bundles/org.eclipse.emf.ecp.view.table.ui.nebula.grid/src/org/eclipse/emf/ecp/view/spi/table/nebula/grid/GridTableViewerComposite.java b/bundles/org.eclipse.emf.ecp.view.table.ui.nebula.grid/src/org/eclipse/emf/ecp/view/spi/table/nebula/grid/GridTableViewerComposite.java
index 1f53d734ea..12a9242993 100644
--- a/bundles/org.eclipse.emf.ecp.view.table.ui.nebula.grid/src/org/eclipse/emf/ecp/view/spi/table/nebula/grid/GridTableViewerComposite.java
+++ b/bundles/org.eclipse.emf.ecp.view.table.ui.nebula.grid/src/org/eclipse/emf/ecp/view/spi/table/nebula/grid/GridTableViewerComposite.java
@@ -11,7 +11,7 @@
* Contributors:
* Alexandra Buzila - initial API and implementation
* Johannes Faltermeier - initial API and implementation
- * Christian W. Damus - bugs 534829, 530314
+ * Christian W. Damus - bugs 534829, 530314, 547271
******************************************************************************/
package org.eclipse.emf.ecp.view.spi.table.nebula.grid;
@@ -28,7 +28,9 @@ import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.observable.value.IValueChangeListener;
import org.eclipse.core.databinding.observable.value.ValueChangeEvent;
import org.eclipse.core.databinding.observable.value.WritableValue;
+import org.eclipse.core.runtime.Adapters;
import org.eclipse.emf.databinding.EMFDataBindingContext;
+import org.eclipse.emf.ecp.edit.spi.swt.table.ECPFilterableCell;
import org.eclipse.emf.ecp.view.spi.table.nebula.grid.GridControlSWTRenderer.CustomGridTableViewer;
import org.eclipse.emf.ecp.view.spi.table.nebula.grid.menu.GridColumnAction;
import org.eclipse.emf.ecp.view.spi.table.nebula.grid.messages.Messages;
@@ -535,19 +537,14 @@ public class GridTableViewerComposite extends AbstractTableViewerComposite<GridT
return true;
}
- grid.setRedraw(false);
- final GridItem dummyItem = new GridItem(grid, SWT.NONE);
+ GridItem dummyItem = null;
+ GridViewerRow viewerRow = null;
try {
-
- dummyItem.setData(element);
- final GridViewerRow viewerRow = (GridViewerRow) ((CustomGridTableViewer) tableViewer)
- .getViewerRowFromItem(dummyItem);
-
for (final Widget widget : getColumns()) {
-
final ColumnConfiguration config = getColumnConfiguration(widget);
+ // Do we even have a filter to apply?
final Object filter = config.matchFilter().getValue();
if (filter == null || String.valueOf(filter).isEmpty()) {
continue;
@@ -555,20 +552,41 @@ public class GridTableViewerComposite extends AbstractTableViewerComposite<GridT
final GridColumn column = (GridColumn) widget;
final int columnIndex = tableViewer.getGrid().indexOf(column);
-
- final ViewerCell cell = viewerRow.getCell(columnIndex);
final CellLabelProvider labelProvider = tableViewer.getLabelProvider(columnIndex);
- labelProvider.update(cell);
- if (!matchesColumnFilter(cell.getText(), filter)) {
- return false;
- }
+ // Optimize for the standard case
+ final ECPFilterableCell filterable = Adapters.adapt(labelProvider, ECPFilterableCell.class);
+ if (filterable != null) {
+ // Just get the text and filter it
+ final String text = filterable.getFilterableText(element);
+ if (!matchesColumnFilter(text, filter)) {
+ return false;
+ }
+ } else {
+ // We have a filter, but we need something to filter on
+ if (dummyItem == null) {
+ grid.setRedraw(false);
+ dummyItem = new GridItem(grid, SWT.NONE);
+
+ dummyItem.setData(element);
+ viewerRow = (GridViewerRow) ((CustomGridTableViewer) tableViewer)
+ .getViewerRowFromItem(dummyItem);
+ }
- }
+ // Update the cell, the slow way
+ final ViewerCell cell = viewerRow.getCell(columnIndex);
+ labelProvider.update(cell);
+ if (!matchesColumnFilter(cell.getText(), filter)) {
+ return false;
+ }
+ }
+ }
} finally {
- dummyItem.dispose();
- grid.setRedraw(true);
+ if (dummyItem != null) {
+ dummyItem.dispose();
+ grid.setRedraw(true);
+ }
}
return true;
diff --git a/bundles/org.eclipse.emf.ecp.view.table.ui.nebula.grid/src/org/eclipse/emf/ecp/view/spi/table/nebula/grid/GridViewerColumnBuilder.java b/bundles/org.eclipse.emf.ecp.view.table.ui.nebula.grid/src/org/eclipse/emf/ecp/view/spi/table/nebula/grid/GridViewerColumnBuilder.java
index 508091351a..3ec2d945f9 100644
--- a/bundles/org.eclipse.emf.ecp.view.table.ui.nebula.grid/src/org/eclipse/emf/ecp/view/spi/table/nebula/grid/GridViewerColumnBuilder.java
+++ b/bundles/org.eclipse.emf.ecp.view.table.ui.nebula.grid/src/org/eclipse/emf/ecp/view/spi/table/nebula/grid/GridViewerColumnBuilder.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2011-2016 EclipseSource Muenchen GmbH and others.
+ * Copyright (c) 2011-2019 EclipseSource Muenchen GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
@@ -10,6 +10,7 @@
*
* Contributors:
* jonas - initial API and implementation
+ * Christian W. Damus - bug 547271
******************************************************************************/
package org.eclipse.emf.ecp.view.spi.table.nebula.grid;
@@ -18,6 +19,7 @@ import org.eclipse.emfforms.common.Property;
import org.eclipse.emfforms.common.Property.ChangeListener;
import org.eclipse.emfforms.spi.swt.table.AbstractTableViewerColumnBuilder;
import org.eclipse.emfforms.spi.swt.table.ColumnConfiguration;
+import org.eclipse.emfforms.spi.swt.table.ViewerRefreshManager;
import org.eclipse.jface.databinding.swt.WidgetValueProperty;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
@@ -50,6 +52,12 @@ import org.eclipse.swt.widgets.Widget;
public class GridViewerColumnBuilder extends AbstractTableViewerColumnBuilder<GridTableViewer, GridViewerColumn> {
/**
+ * To avoid redundant refreshes from the filtering control listener of every column
+ * in the grid, refresh requests are posted on this async runnable manager.
+ */
+ private ViewerRefreshManager refreshManager;
+
+ /**
* The constructor.
*
* @param config the {@link ColumnConfiguration}
@@ -60,6 +68,8 @@ public class GridViewerColumnBuilder extends AbstractTableViewerColumnBuilder<Gr
@Override
public GridViewerColumn createViewerColumn(GridTableViewer tableViewer) {
+ refreshManager = ViewerRefreshManager.getInstance(tableViewer);
+
return new GridViewerColumn(tableViewer, getConfig().getStyleBits());
}
@@ -178,7 +188,7 @@ public class GridViewerColumnBuilder extends AbstractTableViewerColumnBuilder<Gr
getConfig().matchFilter().addChangeListener(new ChangeListener<Object>() {
@Override
public void valueChanged(Property<Object> property, Object oldValue, Object newValue) {
- tableViewer.refresh();
+ refreshManager.postRefresh();
}
});
diff --git a/bundles/org.eclipse.emf.ecp.view.table.ui.swt/src/org/eclipse/emf/ecp/view/spi/table/swt/TableControlSWTRenderer.java b/bundles/org.eclipse.emf.ecp.view.table.ui.swt/src/org/eclipse/emf/ecp/view/spi/table/swt/TableControlSWTRenderer.java
index 6a1f27ec81..754b75733b 100644
--- a/bundles/org.eclipse.emf.ecp.view.table.ui.swt/src/org/eclipse/emf/ecp/view/spi/table/swt/TableControlSWTRenderer.java
+++ b/bundles/org.eclipse.emf.ecp.view.table.ui.swt/src/org/eclipse/emf/ecp/view/spi/table/swt/TableControlSWTRenderer.java
@@ -11,10 +11,12 @@
* Contributors:
* Eugen Neufeld - initial API and implementation
* Johannes Faltermeier - refactorings
- * Christian W. Damus - bugs 544116, 544537, 545686, 530314
+ * Christian W. Damus - bugs 544116, 544537, 545686, 530314, 547271
******************************************************************************/
package org.eclipse.emf.ecp.view.spi.table.swt;
+import static org.eclipse.emfforms.spi.swt.table.ViewerRefreshManager.getRefreshRunnable;
+
import java.net.MalformedURLException;
import java.net.URL;
import java.text.MessageFormat;
@@ -43,7 +45,9 @@ import org.eclipse.core.databinding.observable.map.IObservableMap;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.property.value.IValueProperty;
import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Platform;
import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.command.CompoundCommand;
import org.eclipse.emf.common.notify.Notification;
@@ -63,6 +67,7 @@ import org.eclipse.emf.ecp.edit.spi.swt.table.ECPCellEditor;
import org.eclipse.emf.ecp.edit.spi.swt.table.ECPCellEditorComparator;
import org.eclipse.emf.ecp.edit.spi.swt.table.ECPCustomUpdateCellEditor;
import org.eclipse.emf.ecp.edit.spi.swt.table.ECPElementAwareCellEditor;
+import org.eclipse.emf.ecp.edit.spi.swt.table.ECPFilterableCell;
import org.eclipse.emf.ecp.edit.spi.swt.table.ECPViewerAwareCellEditor;
import org.eclipse.emf.ecp.edit.spi.swt.util.ECPDialogExecutor;
import org.eclipse.emf.ecp.view.internal.table.swt.Activator;
@@ -149,6 +154,7 @@ import org.eclipse.emfforms.spi.swt.table.TableViewerCreator;
import org.eclipse.emfforms.spi.swt.table.TableViewerFactory;
import org.eclipse.emfforms.spi.swt.table.TableViewerSWTBuilder;
import org.eclipse.emfforms.spi.swt.table.TableViewerSWTCustomization;
+import org.eclipse.emfforms.spi.swt.table.ViewerRefreshManager;
import org.eclipse.emfforms.spi.swt.table.action.ActionBar;
import org.eclipse.emfforms.spi.swt.table.action.ActionConfiguration;
import org.eclipse.emfforms.spi.swt.table.action.ActionConfigurationBuilder;
@@ -416,6 +422,8 @@ public class TableControlSWTRenderer extends AbstractControlSWTRenderer<VTableCo
tableViewerSWTBuilder
.configureTable(TableConfigurationBuilder.from(tableViewerSWTBuilder)
.dataMapEntry(TableConfiguration.DMR, dmrToCheck)
+ .dataMapEntry(ViewerRefreshManager.REFRESH_MANAGER,
+ (ViewerRefreshManager) this::postRefresh)
.build());
regularColumnsStartIndex = 0;
@@ -1511,6 +1519,18 @@ public class TableControlSWTRenderer extends AbstractControlSWTRenderer<VTableCo
}
/**
+ * Post a refresh request on the asynchronous refresh manager.
+ *
+ * @since 1.21
+ */
+ protected void postRefresh() {
+ final Viewer viewer = getTableViewer();
+ if (viewer != null && !viewer.getControl().isDisposed()) {
+ getRunnableManager().executeAsync(getRefreshRunnable(viewer));
+ }
+ }
+
+ /**
* Returns the add button created by the framework.
*
* @deprecated use {@link #getControlForAction(String)} instead
@@ -1822,7 +1842,6 @@ public class TableControlSWTRenderer extends AbstractControlSWTRenderer<VTableCo
*/
private final class TableControlDropAdapter extends EditingDomainViewerDropAdapter {
- private final AbstractTableViewer tableViewer;
private EObject eObject;
private EStructuralFeature eStructuralFeature;
private List<Object> list;
@@ -1830,7 +1849,6 @@ public class TableControlSWTRenderer extends AbstractControlSWTRenderer<VTableCo
@SuppressWarnings("unchecked")
TableControlDropAdapter(EditingDomain domain, Viewer viewer, AbstractTableViewer tableViewer) {
super(domain, viewer);
- this.tableViewer = tableViewer;
try {
final Setting setting = getEMFFormsDatabinding().getSetting(getDMRToMultiReference(),
getViewModelContext().getDomainModel());
@@ -1907,7 +1925,7 @@ public class TableControlSWTRenderer extends AbstractControlSWTRenderer<VTableCo
}
domain.getCommandStack().execute(command);
- tableViewer.refresh();
+ postRefresh();
}
}
@@ -2178,7 +2196,8 @@ public class TableControlSWTRenderer extends AbstractControlSWTRenderer<VTableCo
// Update these specific objects
getTableViewer().update(updates.toArray(), null);
} else {
- // Just refresh everything
+ // Just refresh everything. We are already in the RunnableManager
+ // context, so don't post but do it directly
getTableViewer().refresh();
}
}
@@ -2477,7 +2496,7 @@ public class TableControlSWTRenderer extends AbstractControlSWTRenderer<VTableCo
private void sortAndReveal(Object toReveal) {
Display.getDefault().asyncExec(() -> {
- getTableViewer().refresh();
+ postRefresh();
getTableViewer().reveal(toReveal);
});
}
@@ -2490,7 +2509,7 @@ public class TableControlSWTRenderer extends AbstractControlSWTRenderer<VTableCo
* @author emueller
*
*/
- public class ECPCellLabelProvider extends ObservableMapCellLabelProvider implements IColorProvider {
+ public class ECPCellLabelProvider extends ObservableMapCellLabelProvider implements IColorProvider, IAdaptable {
private final EStructuralFeature feature;
private final CellEditor cellEditor;
@@ -2555,21 +2574,50 @@ public class TableControlSWTRenderer extends AbstractControlSWTRenderer<VTableCo
@Override
public void update(ViewerCell cell) {
final EObject element = (EObject) cell.getElement();
- final Object value = attributeMaps[0].get(element);
+ final Object value = getValue(element);
if (ECPCustomUpdateCellEditor.class.isInstance(cellEditor)) {
((ECPCustomUpdateCellEditor) cellEditor).updateCell(cell, value);
- } else if (ECPCellEditor.class.isInstance(cellEditor)) {
- final ECPCellEditor ecpCellEditor = (ECPCellEditor) cellEditor;
- final String text = ecpCellEditor.getFormatedString(value);
- cell.setText(text == null ? "" : text); //$NON-NLS-1$
- cell.setImage(ecpCellEditor.getImage(value));
} else {
- cell.setText(value == null ? "" : value.toString()); //$NON-NLS-1$
- cell.getControl().setData(CUSTOM_VARIANT, "org_eclipse_emf_ecp_edit_cellEditor_string"); //$NON-NLS-1$
+ String text;
+ Image image = null;
+
+ if (ECPCellEditor.class.isInstance(cellEditor)) {
+ final ECPCellEditor ecpCellEditor = (ECPCellEditor) cellEditor;
+ text = Objects.toString(ecpCellEditor.getFormatedString(value), ""); //$NON-NLS-1$
+ image = ecpCellEditor.getImage(value);
+ } else {
+ text = Objects.toString(value, ""); //$NON-NLS-1$
+ cell.getControl().setData(CUSTOM_VARIANT, "org_eclipse_emf_ecp_edit_cellEditor_string"); //$NON-NLS-1$
+ }
+
+ if (!Objects.equals(text, cell.getText())) {
+ cell.setText(text);
+ }
+
+ // Don't try to compare images
+ if (image != null || cell.getImage() != null) {
+ cell.setImage(image);
+ }
}
- cell.setForeground(getForeground(element));
- cell.setBackground(getBackground(element));
+ final Color foreground = getForeground(element);
+ if (!Objects.equals(cell.getForeground(), foreground)) {
+ cell.setForeground(foreground);
+ }
+ final Color background = getBackground(element);
+ if (!Objects.equals(cell.getBackground(), background)) {
+ cell.setBackground(background);
+ }
+ }
+
+ /**
+ * Get the value for an {@code object} from my observable map.
+ *
+ * @param object an object to look up the value for
+ * @return its value
+ */
+ Object getValue(Object object) {
+ return attributeMaps[0].get(object);
}
/**
@@ -2619,6 +2667,30 @@ public class TableControlSWTRenderer extends AbstractControlSWTRenderer<VTableCo
protected VDomainModelReference getDmr() {
return dmr;
}
+
+ @Override
+ public <T> T getAdapter(Class<T> adapter) {
+ T result = null;
+
+ // For custom cell update, we must ask the cell editor to render a string for filtering
+ if (adapter == ECPFilterableCell.class && !(cellEditor instanceof ECPCustomUpdateCellEditor)) {
+ ECPFilterableCell filterable = null;
+
+ if (cellEditor instanceof ECPCellEditor) {
+ final ECPCellEditor ecpCellEditor = (ECPCellEditor) cellEditor;
+ filterable = object -> ecpCellEditor.getFormatedString(getValue(object));
+ } else {
+ filterable = object -> Objects.toString(object, ""); //$NON-NLS-1$
+ }
+
+ result = adapter.cast(filterable);
+ } else {
+ result = Platform.getAdapterManager().getAdapter(this, adapter);
+ }
+
+ return result;
+ }
+
}
/**
diff --git a/bundles/org.eclipse.emfforms.swt.table/src/org/eclipse/emfforms/spi/swt/table/AbstractTableViewerComposite.java b/bundles/org.eclipse.emfforms.swt.table/src/org/eclipse/emfforms/spi/swt/table/AbstractTableViewerComposite.java
index bfc69730cc..9cfef4836c 100644
--- a/bundles/org.eclipse.emfforms.swt.table/src/org/eclipse/emfforms/spi/swt/table/AbstractTableViewerComposite.java
+++ b/bundles/org.eclipse.emfforms.swt.table/src/org/eclipse/emfforms/spi/swt/table/AbstractTableViewerComposite.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2011-2016 EclipseSource Muenchen GmbH and others.
+ * Copyright (c) 2011-2019 EclipseSource Muenchen GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
@@ -10,6 +10,7 @@
*
* Contributors:
* jonas - initial API and implementation
+ * Christian W. Damus - bug 547271
******************************************************************************/
package org.eclipse.emfforms.spi.swt.table;
@@ -165,6 +166,12 @@ public abstract class AbstractTableViewerComposite<V extends AbstractTableViewer
final V tableViewer = createTableViewer(customization, viewerComposite);
+ final TableConfiguration configuration = customization.getTableConfiguration();
+ if (configuration != null) {
+ // Pump in the configuration data
+ configuration.getData().forEach(tableViewer::setData);
+ }
+
// If an action configuration was configured, bind key bindings to the viewer
final Optional<ActionConfiguration> actionConfiguration = customization.getActionConfiguration();
if (actionConfiguration.isPresent()) {
diff --git a/bundles/org.eclipse.emfforms.swt.table/src/org/eclipse/emfforms/spi/swt/table/ViewerRefreshManager.java b/bundles/org.eclipse.emfforms.swt.table/src/org/eclipse/emfforms/spi/swt/table/ViewerRefreshManager.java
new file mode 100644
index 0000000000..5dd4d19d7b
--- /dev/null
+++ b/bundles/org.eclipse.emfforms.swt.table/src/org/eclipse/emfforms/spi/swt/table/ViewerRefreshManager.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright (c) 2019 Christian W. Damus and others.
+ *
+ * All rights reserved. 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:
+ * Christian W. Damus - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.emfforms.spi.swt.table;
+
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.widgets.Control;
+
+/**
+ * Protocol for asynchronous non-redundant refresh of a viewer.
+ * This is {@linkplain Viewer#setData(String, Object) associated with the table viewer}
+ * under the {@link #REFRESH_MANAGER} key.
+ *
+ * @since 1.21
+ */
+@FunctionalInterface
+public interface ViewerRefreshManager {
+
+ /** Viewer data key for the refresh manager. */
+ String REFRESH_MANAGER = "refreshManager"; //$NON-NLS-1$
+
+ /**
+ * Post an asynchronous request to refresh the table viewer.
+ */
+ void postRefresh();
+
+ /**
+ * Obtain the refresh manager instance for the given {@code viewer}.
+ *
+ * @param viewer a viewer
+ * @return its refresh manager or a simple default implementaiton; never {@code null}
+ */
+ static ViewerRefreshManager getInstance(Viewer viewer) {
+ final Object result = viewer.getData(REFRESH_MANAGER);
+
+ if (result instanceof ViewerRefreshManager) {
+ return (ViewerRefreshManager) result;
+ }
+
+ final Runnable refresher = getRefreshRunnable(viewer);
+ return () -> viewer.getControl().getDisplay().asyncExec(refresher);
+ }
+
+ /**
+ * Obtain a runnable that {@linkplain Viewer#refresh() refreshes} a {@code viewer}.
+ *
+ * @param viewer a viewer to refresh
+ * @return the refresh manager
+ *
+ * @see Viewer#refresh()
+ */
+ static Runnable getRefreshRunnable(Viewer viewer) {
+ final Control control = viewer.getControl();
+
+ return () -> {
+ if (!control.isDisposed()) {
+ viewer.refresh();
+ }
+ };
+ }
+
+}
diff --git a/tests/org.eclipse.emf.ecp.view.model.provider.generator.test/src/org/eclipse/emf/ecp/view/model/generator/ViewProvider_Test.java b/tests/org.eclipse.emf.ecp.view.model.provider.generator.test/src/org/eclipse/emf/ecp/view/model/generator/ViewProvider_Test.java
index ab7c031aca..8fb9f2de3c 100644
--- a/tests/org.eclipse.emf.ecp.view.model.provider.generator.test/src/org/eclipse/emf/ecp/view/model/generator/ViewProvider_Test.java
+++ b/tests/org.eclipse.emf.ecp.view.model.provider.generator.test/src/org/eclipse/emf/ecp/view/model/generator/ViewProvider_Test.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2011-2018 EclipseSource Muenchen GmbH and others.
+ * Copyright (c) 2011-2019 EclipseSource Muenchen GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
@@ -10,32 +10,66 @@
*
* Contributors:
* Lucas Koehler - initial API and implementation
+ * Christian W. Damus - bug 547271
******************************************************************************/
package org.eclipse.emf.ecp.view.model.generator;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.withSettings;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.Spliterator;
+import java.util.Spliterators;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+import org.eclipse.emf.common.command.BasicCommandStack;
+import org.eclipse.emf.common.notify.AdapterFactory;
+import org.eclipse.emf.common.util.ECollections;
+import org.eclipse.emf.common.util.EList;
+import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
+import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EcoreFactory;
import org.eclipse.emf.ecore.EcorePackage;
+import org.eclipse.emf.ecore.InternalEObject;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.emf.ecore.resource.ResourceSet;
+import org.eclipse.emf.ecore.resource.impl.ResourceImpl;
+import org.eclipse.emf.ecore.util.BasicExtendedMetaData.EClassifierExtendedMetaData;
+import org.eclipse.emf.ecore.util.EObjectContainmentWithInverseEList;
+import org.eclipse.emf.ecore.util.EObjectEList;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecp.view.spi.model.VControl;
import org.eclipse.emf.ecp.view.spi.model.VDomainModelReference;
+import org.eclipse.emf.ecp.view.spi.model.VElement;
import org.eclipse.emf.ecp.view.spi.model.VFeaturePathDomainModelReference;
import org.eclipse.emf.ecp.view.spi.model.VView;
import org.eclipse.emf.ecp.view.spi.model.VViewFactory;
import org.eclipse.emf.ecp.view.spi.model.VViewModelLoadingProperties;
import org.eclipse.emf.ecp.view.spi.table.model.VTableControl;
import org.eclipse.emf.ecp.view.spi.table.model.VTableDomainModelReference;
+import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
+import org.eclipse.emf.edit.domain.EditingDomain;
import org.junit.Before;
import org.junit.Test;
@@ -168,10 +202,92 @@ public class ViewProvider_Test {
assertSame(multiContainmentRef, featureDmr.getDomainModelEFeature());
}
+ /**
+ * Verify that controls' generated UUIDs actually are unique within the view (there is no
+ * guarantee that they are unique globally, but the {@link ViewProvider} does not need that).
+ */
+ @Test
+ public void controlsHaveUniqueUUIDs() {
+ final EClass eClass = mock(EClass.class,
+ withSettings().extraInterfaces(InternalEObject.class, EClassifierExtendedMetaData.Holder.class));
+ when(eClass.eContainer()).thenReturn(this.eClass.eContainer());
+ when(eClass.getEPackage()).thenReturn(this.eClass.getEPackage());
+ when(eClass.getName()).thenReturn("Mock");
+ when(eClass.getESuperTypes()).thenReturn(ECollections.emptyEList());
+ when(((EClassifierExtendedMetaData.Holder) eClass).getExtendedMetaData())
+ .thenReturn(mock(EClassifierExtendedMetaData.class));
+
+ // Repeat the exact same feature multiple times to create controls
+ final EList<EStructuralFeature> features = new EObjectContainmentWithInverseEList<>(EStructuralFeature.class,
+ (InternalEObject) eClass, EcorePackage.ECLASS__ESTRUCTURAL_FEATURES,
+ EcorePackage.ESTRUCTURAL_FEATURE__ECONTAINING_CLASS);
+ final EReference ref = EcoreFactory.eINSTANCE.createEReference();
+ ref.setEType(refType);
+ ref.setName("ref");
+ final EList<EReference> references = new EObjectEList<>(EReference.class, (InternalEObject) eClass,
+ EcorePackage.ECLASS__EREFERENCES);
+ references.addAll(Collections.nCopies(500, ref));
+ features.addAll(references);
+ when(eClass.getEStructuralFeatures()).thenReturn(features);
+ when(eClass.getEAllStructuralFeatures()).thenReturn(ECollections.unmodifiableEList(features));
+ when(eClass.getEReferences()).thenReturn(references);
+ when(eClass.getEAllReferences()).thenReturn(references);
+ when(eClass.getEAttributes()).thenReturn(ECollections.emptyEList());
+ when(eClass.getEAllAttributes()).thenReturn(ECollections.emptyEList());
+ final EObject object = EcoreUtil.create(eClass);
+
+ final VView view = viewProvider.provideViewModel(object, viewProperties);
+
+ final Set<String> uuids = new HashSet<>();
+ controls(view).forEach(control -> {
+ final String uuid = control.getUuid();
+ assertThat("No UUID generated", uuid, notNullValue());
+ assertThat("Non-unique UUID: " + uuid, uuids.add(uuid));
+ });
+
+ assertThat("Didn't get a control for each occurrence of the feature", uuids.size(),
+ is(eClass.getEAllStructuralFeatures().size()));
+ }
+
+ /**
+ * Verify that the item provider is used to determine read-only state of controls.
+ */
+ @Test
+ public void readOnlyControls() {
+ final EReference ref = EcoreFactory.eINSTANCE.createEReference();
+ ref.setEType(refType);
+ ref.setName("ref");
+ eClass.getEStructuralFeatures().add(ref);
+
+ final Map<Resource, Boolean> readOnly = new HashMap<>();
+ final EditingDomain domain = new AdapterFactoryEditingDomain(mock(AdapterFactory.class),
+ new BasicCommandStack(), readOnly);
+ final ResourceSet rset = domain.getResourceSet();
+ final Resource resource = new ResourceImpl(URI.createURI("test:/resource"));
+ final EObject object = EcoreUtil.create(eClass);
+ resource.getContents().add(object);
+ rset.getResources().add(resource);
+ readOnly.put(resource, true);
+
+ final VView view = viewProvider.provideViewModel(object, viewProperties);
+ final int[] count = { 0 };
+ controls(view)
+ .peek(__ -> count[0]++)
+ .forEach(control -> assertThat("Control not read-only", control.isEffectivelyReadonly(), is(true)));
+ assertThat("No controls found", count[0], not(0));
+ }
+
private void assertView(final VView view) {
assertNotNull(view);
assertNotNull(view.getUuid());
assertFalse(view.getUuid().isEmpty());
assertEquals(1, view.getChildren().size());
}
+
+ private Stream<VControl> controls(VElement viewModel) {
+ return StreamSupport
+ .stream(Spliterators.spliteratorUnknownSize(EcoreUtil.getAllContents(Collections.singleton(viewModel)),
+ Spliterator.DISTINCT | Spliterator.NONNULL | Spliterator.ORDERED), false)
+ .filter(VControl.class::isInstance).map(VControl.class::cast);
+ }
}
diff --git a/tests/org.eclipse.emf.ecp.view.table.ui.nebula.grid.test/src/org/eclipse/emf/ecp/view/internal/table/nebula/grid/GridControlRenderer_PTest.java b/tests/org.eclipse.emf.ecp.view.table.ui.nebula.grid.test/src/org/eclipse/emf/ecp/view/internal/table/nebula/grid/GridControlRenderer_PTest.java
index d65effda01..0f8ee38a4a 100644
--- a/tests/org.eclipse.emf.ecp.view.table.ui.nebula.grid.test/src/org/eclipse/emf/ecp/view/internal/table/nebula/grid/GridControlRenderer_PTest.java
+++ b/tests/org.eclipse.emf.ecp.view.table.ui.nebula.grid.test/src/org/eclipse/emf/ecp/view/internal/table/nebula/grid/GridControlRenderer_PTest.java
@@ -74,6 +74,7 @@ import org.eclipse.emf.ecp.view.template.model.VTViewTemplateProvider;
import org.eclipse.emf.ecp.view.template.style.keybinding.model.VTKeyBinding;
import org.eclipse.emf.ecp.view.template.style.keybinding.model.VTKeyBindings;
import org.eclipse.emf.ecp.view.template.style.keybinding.model.VTKeybindingFactory;
+import org.eclipse.emf.ecp.view.test.common.swt.spi.SWTTestUtil;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.emfforms.common.Optional;
import org.eclipse.emfforms.spi.common.converter.EStructuralFeatureValueConverterService;
@@ -522,7 +523,7 @@ public class GridControlRenderer_PTest extends AbstractControl_PTest<VTableContr
columnConfiguration.getEnabledFeatures()
.contains(ColumnConfiguration.FEATURE_COLUMN_FILTER));
- assertEquals(expectedRows, grid.getItems().length); // 3 players/rows defined in mockSampleDataSet()
+ assertEquals(expectedRows, countItems(grid)); // 3 players/rows defined in mockSampleDataSet()
/*
* test filtering
@@ -537,34 +538,34 @@ public class GridControlRenderer_PTest extends AbstractControl_PTest<VTableContr
columnConfiguration.matchFilter().setValue("er");
switch (dataset) {
case Simple:
- assertEquals(3, filterVisible(grid.getItems()).length);
+ assertEquals(3, countVisible(grid));
break;
default: // complex case, has only two logins (a bot doesn't have one)
- assertEquals(2, filterVisible(grid.getItems()).length);
+ assertEquals(2, countVisible(grid));
}
columnConfiguration.matchFilter().setValue("foo");
- assertEquals(0, filterVisible(grid.getItems()).length);
+ assertEquals(0, countVisible(grid));
// double check, that extending a filter which lead to an empty result still created an empty result
columnConfiguration.matchFilter().setValue("foo2");
- assertEquals(0, filterVisible(grid.getItems()).length);
+ assertEquals(0, countVisible(grid));
columnConfiguration.matchFilter().resetToDefault();
- assertEquals(expectedRows, filterVisible(grid.getItems()).length);
+ assertEquals(expectedRows, countVisible(grid));
columnConfiguration.matchFilter().setValue("Kliver");
- assertEquals(1, filterVisible(grid.getItems()).length);
+ assertEquals(1, countVisible(grid));
columnConfiguration.matchFilter().resetToDefault();
columnConfiguration.matchFilter().setValue("branzfeckenbauer");
switch (dataset) {
case Simple:
- assertEquals(0, filterVisible(grid.getItems()).length);
+ assertEquals(0, countVisible(grid));
break;
default: // complex case, there is exactly one matching login
- assertEquals(1, filterVisible(grid.getItems()).length);
+ assertEquals(1, countVisible(grid));
}
/*
@@ -576,8 +577,8 @@ public class GridControlRenderer_PTest extends AbstractControl_PTest<VTableContr
is(false));
assertFilterMenuChecked(tableViewerComposite, null);
- assertEquals(3, grid.getItems().length);
- assertEquals(3, filterVisible(grid.getItems()).length);
+ assertEquals(3, countItems(grid));
+ assertEquals(3, countVisible(grid));
}
/**
@@ -593,7 +594,7 @@ public class GridControlRenderer_PTest extends AbstractControl_PTest<VTableContr
final Event event = new Event();
// Some menu actions are not defined if the mouse is not over a grid cell
- final Rectangle cell = composite.getTableViewer().getGrid().getItem(0).getBounds(1);
+ final Rectangle cell = allItems(composite.getTableViewer().getGrid())[0].getBounds(1);
event.type = SWT.MouseMove;
event.x = cell.x + cell.width / 2;
event.y = cell.y + cell.height / 2;
@@ -651,12 +652,12 @@ public class GridControlRenderer_PTest extends AbstractControl_PTest<VTableContr
is(true));
columnConfiguration.matchFilter().setValue("foo");
- assertEquals(0, filterVisible(grid.getItems()).length);
+ assertEquals(0, countVisible(grid));
tableViewerComposite.setFilteringMode(null);
columnConfiguration.visible().setValue(Boolean.FALSE);
- assertEquals(expectedRows, filterVisible(grid.getItems()).length);
+ assertEquals(expectedRows, countVisible(grid));
/*
* test for Gerrit #110529 (filter again after filters have been hidden)
@@ -668,7 +669,7 @@ public class GridControlRenderer_PTest extends AbstractControl_PTest<VTableContr
is(true));
columnConfiguration.matchFilter().setValue("bar");
- assertEquals(0, filterVisible(grid.getItems()).length);
+ assertEquals(0, countVisible(grid));
columnConfiguration.matchFilter().resetToDefault();
tableViewerComposite.setFilteringMode(null);
@@ -677,7 +678,7 @@ public class GridControlRenderer_PTest extends AbstractControl_PTest<VTableContr
// will result in a NPE without Gerrit #110529
columnConfiguration.visible().setValue(Boolean.FALSE);
- assertEquals(expectedRows, filterVisible(grid.getItems()).length);
+ assertEquals(expectedRows, countVisible(grid));
}
@@ -701,8 +702,8 @@ public class GridControlRenderer_PTest extends AbstractControl_PTest<VTableContr
assertThat("Feature not enabled/supported. Check ColumnConfiguration.FEATURES?",
columnConfiguration.getEnabledFeatures(), hasItem(ColumnConfiguration.FEATURE_COLUMN_REGEX_FILTER));
- assertThat("Wrong number of rrows filtered",
- grid.getItems().length, is(expectedRows)); // 3 players/rows defined in mockSampleDataSet()
+ assertThat("Wrong number of rows filtered",
+ countItems(grid), is(expectedRows)); // 3 players/rows defined in mockSampleDataSet()
/*
* test filtering
@@ -714,63 +715,63 @@ public class GridControlRenderer_PTest extends AbstractControl_PTest<VTableContr
columnConfiguration.matchFilter().setValue("er");
switch (dataset) {
case Simple:
- assertThat("No rows should be filtered", filterVisible(grid.getItems()).length, is(3));
+ assertThat("No rows should be filtered", countVisible(grid), is(3));
break;
default: // complex case, has only two logins (a bot doesn't have one)
- assertThat("One row should be filtered", filterVisible(grid.getItems()).length, is(2));
+ assertThat("One row should be filtered", countVisible(grid), is(2));
}
// Match only 'er' at the end of the string
columnConfiguration.matchFilter().setValue("er$");
switch (dataset) {
case Simple:
- assertThat("One row should be filtered", filterVisible(grid.getItems()).length, is(2));
+ assertThat("One row should be filtered", countVisible(grid), is(2));
break;
default: // complex case, has only two logins (a bot doesn't have one)
- assertThat("Two rows should be filtered", filterVisible(grid.getItems()).length, is(1));
+ assertThat("Two rows should be filtered", countVisible(grid), is(1));
}
columnConfiguration.matchFilter().setValue("foo");
- assertThat("All rows should be filtered", filterVisible(grid.getItems()).length, is(0));
+ assertThat("All rows should be filtered", countVisible(grid), is(0));
// double check, that extending a filter which lead to an empty result still created an empty result
columnConfiguration.matchFilter().setValue("foo2");
- assertThat("All rows should be filtered", filterVisible(grid.getItems()).length, is(0));
+ assertThat("All rows should be filtered", countVisible(grid), is(0));
// but an invalid regex doesn't filter anything (user should see examples in the grid to
// be guides in formulating the regex)
columnConfiguration.matchFilter().setValue("([a].*\\1");
- assertThat("No rows should be filtered", filterVisible(grid.getItems()).length, is(expectedRows));
+ assertThat("No rows should be filtered", countVisible(grid), is(expectedRows));
columnConfiguration.matchFilter().setValue("([a]).*\\1");
switch (dataset) {
case Simple:
- assertThat("Two rows should be filtered", filterVisible(grid.getItems()).length, is(1));
+ assertThat("Two rows should be filtered", countVisible(grid), is(1));
break;
default: // complex case, where the bot hasn't a login
- assertThat("Two rows should be filtered", filterVisible(grid.getItems()).length, is(1));
+ assertThat("Two rows should be filtered", countVisible(grid), is(1));
}
columnConfiguration.matchFilter().setValue("([ae]).*\\1");
switch (dataset) {
case Simple:
- assertThat("One row should be filtered", filterVisible(grid.getItems()).length, is(2));
+ assertThat("One row should be filtered", countVisible(grid), is(2));
break;
default: // complex case, where the bot hasn't a login
- assertThat("Two rows should be filtered", filterVisible(grid.getItems()).length, is(1));
+ assertThat("Two rows should be filtered", countVisible(grid), is(1));
}
columnConfiguration.matchFilter().resetToDefault();
- assertThat("No rows should be filtered", filterVisible(grid.getItems()).length, is(expectedRows));
+ assertThat("No rows should be filtered", countVisible(grid), is(expectedRows));
columnConfiguration.matchFilter().setValue("branzfeckenbauer");
switch (dataset) {
case Simple:
- assertThat("All rows should be filtered", filterVisible(grid.getItems()).length, is(0));
+ assertThat("All rows should be filtered", countVisible(grid), is(0));
break;
default: // complex case, there is exactly one matching login
- assertThat("Two rows should be filtered", filterVisible(grid.getItems()).length, is(1));
+ assertThat("Two rows should be filtered", countVisible(grid), is(1));
}
/*
@@ -782,13 +783,13 @@ public class GridControlRenderer_PTest extends AbstractControl_PTest<VTableContr
assertThat("Filter control still showing", columnConfiguration.showFilterControl().getValue(),
is(false));
- assertThat("Wrong number of items in the grid", grid.getItems().length, is(3));
- assertThat("No rows should be filtered", filterVisible(grid.getItems()).length, is(3));
+ assertThat("Wrong number of items in the grid", countItems(grid), is(3));
+ assertThat("No rows should be filtered", countVisible(grid), is(3));
}
- private GridItem[] filterVisible(GridItem[] items) {
+ private GridItem[] filterVisible(Grid grid) {
final List<GridItem> visibleItems = new ArrayList<GridItem>();
- for (final GridItem item : items) {
+ for (final GridItem item : allItems(grid)) {
if (!item.isVisible()) {
continue;
}
@@ -797,6 +798,19 @@ public class GridControlRenderer_PTest extends AbstractControl_PTest<VTableContr
return visibleItems.toArray(new GridItem[] {});
}
+ private GridItem[] allItems(Grid grid) {
+ SWTTestUtil.waitForUIThread(); // The table refresh is asynchronous
+ return grid.getItems();
+ }
+
+ private int countItems(Grid grid) {
+ return allItems(grid).length;
+ }
+
+ private int countVisible(Grid grid) {
+ return filterVisible(grid).length;
+ }
+
@Override
public void renderValidationIconLabelAlignmentLeft()
throws NoRendererFoundException, NoPropertyDescriptorFoundExeption {
@@ -849,6 +863,7 @@ public class GridControlRenderer_PTest extends AbstractControl_PTest<VTableContr
assertFalse(duplicateRowButton.isPresent());
}
+ @Test
public void testActionKeyBindings()
throws DatabindingFailedException, NoRendererFoundException, NoPropertyDescriptorFoundExeption {

Back to the top