Skip to content

Package: ItemProviderEnumCellEditor$1

ItemProviderEnumCellEditor$1

nameinstructionbranchcomplexitylinemethod
convert(Object)
M: 0 C: 15
100%
M: 1 C: 1
50%
M: 1 C: 1
50%
M: 0 C: 3
100%
M: 0 C: 1
100%
{...}
M: 0 C: 9
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%

Coverage

1: /*******************************************************************************
2: * Copyright (c) 2017-2019 EclipseSource Muenchen GmbH and others.
3: *
4: * All rights reserved. This program and the accompanying materials
5: * are made available under the terms of the Eclipse Public License 2.0
6: * which accompanies this distribution, and is available at
7: * https://www.eclipse.org/legal/epl-2.0/
8: *
9: * SPDX-License-Identifier: EPL-2.0
10: *
11: * Contributors:
12: * Edgar Mueller - initial API and implementation
13: * Christian W. Damus - rework for i18n support and code cleanup
14: * Lucas Koehler - rework integration
15: ******************************************************************************/
16: package org.eclipse.emf.ecp.view.spi.table.swt;
17:
18: import static java.lang.Math.min;
19: import static org.eclipse.emf.ecp.view.internal.table.swt.FigureUtilities.getTextWidth;
20:
21: import java.text.MessageFormat;
22: import java.util.Collection;
23: import java.util.Collections;
24: import java.util.List;
25: import java.util.Optional;
26: import java.util.stream.Collectors;
27:
28: import org.eclipse.core.databinding.Binding;
29: import org.eclipse.core.databinding.DataBindingContext;
30: import org.eclipse.core.databinding.UpdateValueStrategy;
31: import org.eclipse.core.databinding.observable.Observables;
32: import org.eclipse.core.databinding.observable.value.IObservableValue;
33: import org.eclipse.core.databinding.observable.value.ValueDiff;
34: import org.eclipse.core.databinding.property.INativePropertyListener;
35: import org.eclipse.core.databinding.property.ISimplePropertyListener;
36: import org.eclipse.core.databinding.property.value.IValueProperty;
37: import org.eclipse.core.databinding.property.value.SimpleValueProperty;
38: import org.eclipse.core.runtime.IStatus;
39: import org.eclipse.emf.common.util.Enumerator;
40: import org.eclipse.emf.databinding.IEMFObservable;
41: import org.eclipse.emf.ecore.EAttribute;
42: import org.eclipse.emf.ecore.EEnum;
43: import org.eclipse.emf.ecore.EEnumLiteral;
44: import org.eclipse.emf.ecore.EObject;
45: import org.eclipse.emf.ecore.EStructuralFeature;
46: import org.eclipse.emf.ecp.common.spi.EMFUtils;
47: import org.eclipse.emf.ecp.edit.spi.swt.table.ECPEnumCellEditor;
48: import org.eclipse.emf.ecp.view.internal.core.swt.MatchItemComboViewer;
49: import org.eclipse.emf.ecp.view.spi.context.ViewModelContext;
50: import org.eclipse.emf.edit.provider.IItemPropertyDescriptor;
51: import org.eclipse.emf.edit.provider.IItemPropertySource;
52: import org.eclipse.emfforms.spi.common.BundleResolver;
53: import org.eclipse.emfforms.spi.common.BundleResolver.NoBundleFoundException;
54: import org.eclipse.emfforms.spi.common.BundleResolverFactory;
55: import org.eclipse.emfforms.spi.common.report.AbstractReport;
56: import org.eclipse.emfforms.spi.common.report.ReportService;
57: import org.eclipse.emfforms.spi.localization.EMFFormsLocalizationService;
58: import org.eclipse.jface.databinding.viewers.typed.ViewerProperties;
59: import org.eclipse.jface.layout.GridDataFactory;
60: import org.eclipse.jface.viewers.ArrayContentProvider;
61: import org.eclipse.jface.viewers.ColumnViewerEditorActivationEvent;
62: import org.eclipse.jface.viewers.LabelProvider;
63: import org.eclipse.jface.viewers.StructuredSelection;
64: import org.eclipse.swt.SWT;
65: import org.eclipse.swt.custom.CCombo;
66: import org.eclipse.swt.events.FocusEvent;
67: import org.eclipse.swt.events.FocusListener;
68: import org.eclipse.swt.graphics.Image;
69: import org.eclipse.swt.graphics.Point;
70: import org.eclipse.swt.widgets.Composite;
71: import org.eclipse.swt.widgets.Control;
72: import org.osgi.framework.Bundle;
73:
74: /**
75: * Generic {@link org.eclipse.emf.ecp.edit.spi.swt.table.ECPCellEditor ECPCellEditor} which is
76: * applicable for all {@link EAttribute EAttributes} with a Single {@link EEnum} data type.
77: * This cell editor uses the EMF.Edit item provider to determine
78: * the model's proper choice of values for an {@link EEnum} attribute. Additionally, it filters out enum literals with
79: * are marked as <code>isInputtable=false</code> with a custom annotation.
80: *
81: * @author Christian W. Damus
82: * @author Lucas Koehler
83: * @since 1.22
84: */
85: public class ItemProviderEnumCellEditor extends ECPEnumCellEditor {
86:
87:         /**
88:          * Template to generate the localization key for an enum value. First parameter is the EEnum's type name, second
89:          * parameter is the value's name.
90:          */
91:         private static final String LOCALIZATION_KEY_TEMPLATE = "_UI_%s_%s_literal"; //$NON-NLS-1$
92:
93:         private EMFFormsLocalizationService l10n;
94:         private MatchItemComboViewer viewer;
95:         private int minWidth;
96:
97:         private EAttribute attribute;
98:         private BundleResolver bundleResolver = BundleResolverFactory.createBundleResolver();
99:         /** The edit bundle for the EEnum renderered by this cell editor. */
100:         private Optional<Bundle> editBundle;
101:         private Optional<EObject> source = Optional.empty();
102:
103:         /**
104:          * Initializes me with my parent.
105:          *
106:          * @param parent my parent composite
107:          */
108:         public ItemProviderEnumCellEditor(Composite parent) {
109:                 super(parent);
110:         }
111:
112:         /**
113:          * Initializes me with my parent and style.
114:          *
115:          * @param parent my parent composite
116:          * @param style my style bits
117:          */
118:         public ItemProviderEnumCellEditor(Composite parent, int style) {
119:                 super(parent, style);
120:         }
121:
122:         /**
123:          * Initializes me with my parent, style, and custom {@link BundleResolver} and {@link EMFFormsLocalizationService}.
124:          *
125:          * @param parent my parent composite
126:          * @param style my style bits
127:          * @param bundleResolver custom {@link BundleResolver}
128:          * @param l10n custom {@link EMFFormsLocalizationService}
129:          */
130:         public ItemProviderEnumCellEditor(Composite parent, int style, BundleResolver bundleResolver,
131:                 EMFFormsLocalizationService l10n) {
132:                 this(parent, style);
133:                 this.bundleResolver = bundleResolver;
134:                 this.l10n = l10n;
135:         }
136:
137:         @Override
138:         @SuppressWarnings("rawtypes")
139:         public UpdateValueStrategy getModelToTargetStrategy(DataBindingContext databindingContext) {
140:                 return new UpdateValueStrategy() {
141:                         @Override
142:                         public Object convert(Object value) {
143:•                                if (!source.isPresent()) {
144:                                         // Extract the source model element from the data binding
145:                                         source = inferSource(databindingContext);
146:                                 }
147:
148:                                 return value;
149:                         }
150:                 };
151:         }
152:
153:         @Override
154:         @SuppressWarnings("rawtypes")
155:         public UpdateValueStrategy getTargetToModelStrategy(DataBindingContext databindingContext) {
156:                 return new UpdateValueStrategy();
157:         }
158:
159:         @Override
160:         protected Control createControl(Composite parent) {
161:                 viewer = new MatchItemComboViewer(new CCombo(parent, SWT.NONE)) {
162:                         @Override
163:                         public void onEnter() {
164:                                 super.onEnter();
165:                                 applySelection();
166:                                 focusLost();
167:                         }
168:
169:                         @Override
170:                         protected void onEscape() {
171:                                 fireCancelEditor();
172:                         }
173:                 };
174:
175:                 final CCombo combo = viewer.getCCombo();
176:                 GridDataFactory.fillDefaults().grab(true, false).applyTo(combo);
177:                 viewer.setContentProvider(ArrayContentProvider.getInstance());
178:                 viewer.setLabelProvider(new EnumLabelProvider());
179:                 combo.addFocusListener(new FocusListener() {
180:
181:                         @Override
182:                         public void focusLost(FocusEvent e) {
183:                                 applySelection();
184:                         }
185:
186:                         @Override
187:                         public void focusGained(FocusEvent e) {
188:                                 // nothing to do here
189:                         }
190:                 });
191:                 return combo;
192:         }
193:
194:         private void applySelection() {
195:                 final CCombo combo = viewer.getCCombo();
196:                 final int selection = combo.getSelectionIndex();
197:                 if (selection >= 0) {
198:                         final List<?> input = (List<?>) viewer.getInput();
199:                         viewer.setSelection(new StructuredSelection(input.get(selection)));
200:                 }
201:         }
202:
203:         /**
204:          * Gets the proper choice of values provided by the item-provider
205:          * of the source object of our data binding for the attribute that
206:          * is bound. In addition, we remove all choices annotated by our custom annotation.
207:          * <br/>
208:          * If the property descriptor is not available or does not return any choice, the enum's literals minus the
209:          * annotated ones are returned.
210:          *
211:          * @return The available enum values, might be empty but never <code>null</code>
212:          */
213:         protected List<?> getChoiceOfValues() {
214:                 final Collection<?> providerChoices = getPropertyDescriptor()
215:                         // if the propertyDescriptor is present, we have a source
216:                         .map(descriptor -> descriptor.getChoiceOfValues(getSource().get()))
217:                         .orElse(Collections.emptySet());
218:
219:                 final List<Enumerator> result = getELiterals().stream().map(EEnumLiteral::getInstance)
220:                         .collect(Collectors.toList());
221:                 if (!providerChoices.isEmpty()) {
222:                         result.retainAll(providerChoices);
223:                 }
224:
225:                 return result;
226:         }
227:
228:         @Override
229:         public String getFormatedString(Object value) {
230:                 // If the propertyDescriptor is present, then we have a source
231:                 return getPropertyDescriptor().map(desc -> desc.getLabelProvider(getSource().get()))
232:                         .map(lp -> lp.getText(value))
233:                         .orElseGet(() -> getLabel((Enumerator) value));
234:         }
235:
236:         private String getLabel(Enumerator enumValue) {
237:                 final String typeName = attribute.getEType().getName();
238:
239:                 return editBundle
240:                         .map(eB -> l10n.getString(eB, String.format(LOCALIZATION_KEY_TEMPLATE, typeName, enumValue.getName())))
241:                         .orElse(enumValue.getLiteral());
242:         }
243:
244:         @Override
245:         public void instantiate(EStructuralFeature feature, ViewModelContext viewModelContext) {
246:                 if (l10n == null) {
247:                         l10n = viewModelContext.getService(EMFFormsLocalizationService.class);
248:                 }
249:                 attribute = (EAttribute) feature;
250:
251:                 try {
252:                         editBundle = Optional.of(bundleResolver.getEditBundle(feature.getEType()));
253:                 } catch (final NoBundleFoundException ex) {
254:                         viewModelContext.getService(ReportService.class)
255:                                 .report(new AbstractReport(
256:                                         MessageFormat.format(
257:                                                 "No edit bundle was found for EEnum ''{0}''. Hence, its literals cannot be internationalized for feature ''{1}''.", //$NON-NLS-1$
258:                                                 feature.getEType().getName(), feature.getName()),
259:                                         IStatus.WARNING));
260:                         editBundle = Optional.empty();
261:                 }
262:
263:                 final List<?> choices = getChoiceOfValues();
264:                 viewer.getCCombo().setVisibleItemCount(min(choices.size(), 8));
265:                 final Point emptyViewerSize = viewer.getCCombo().computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
266:                 minWidth = choices.stream()
267:                         .mapToInt(value -> getTextWidth(getFormatedString(value), viewer.getCCombo().getFont()))
268:                         .reduce(50, Math::max);
269:                 minWidth += emptyViewerSize.x;
270:         }
271:
272:         @SuppressWarnings("rawtypes")
273:         @Override
274:         public IValueProperty getValueProperty() {
275:                 return new ComboValueProperty();
276:         }
277:
278:         @Override
279:         public void activate(ColumnViewerEditorActivationEvent actEvent) {
280:                 viewer.setInput(getChoiceOfValues());
281:                 source.ifPresent(obj -> {
282:                         viewer.getCCombo().setText(getFormatedString(obj.eGet(attribute)));
283:                 });
284:
285:                 super.activate(actEvent);
286:
287:                 if (actEvent.eventType == ColumnViewerEditorActivationEvent.KEY_PRESSED) {
288:                         final CCombo control = (CCombo) getControl();
289:                         if (control != null && Character.isLetterOrDigit(actEvent.character)) {
290:                                 viewer.getBuffer().reset();
291:                                 // key pressed is not fired during activation
292:                                 viewer.getBuffer().addLast(actEvent.character);
293:                         }
294:                 }
295:         }
296:
297:         @Override
298:         public void deactivate() {
299:                 super.deactivate();
300:
301:                 // Forget the source previously inferred
302:                 source = Optional.empty();
303:         }
304:
305:         @Override
306:         public int getColumnWidthWeight() {
307:                 return 100;
308:         }
309:
310:         @Override
311:         public int getMinWidth() {
312:                 return minWidth;
313:         }
314:
315:         @Override
316:         public EEnum getEEnum() {
317:                 return (EEnum) attribute.getEType();
318:         }
319:
320:         @Override
321:         public Image getImage(Object value) {
322:                 return null;
323:         }
324:
325:         @Override
326:         public void setEditable(boolean editable) {
327:                 viewer.getCCombo().setEnabled(editable);
328:         }
329:
330:         // Infer the source model element from the EMF binding in the
331:         // context that is for our attribute
332:         private Optional<EObject> inferSource(DataBindingContext context) {
333:                 return ((List<?>) context.getBindings()).stream()
334:                         .map(Binding.class::cast)
335:                         .map(Binding::getModel)
336:                         .filter(IEMFObservable.class::isInstance).map(IEMFObservable.class::cast)
337:                         .filter(obs -> obs.getStructuralFeature() == attribute)
338:                         .map(IEMFObservable::getObserved)
339:                         .map(EObject.class::cast) // Can't observe a feature of a non-EObject
340:                         .findAny();
341:         }
342:
343:         /**
344:          * @return The current source EObject
345:          */
346:         protected Optional<EObject> getSource() {
347:                 return source;
348:         }
349:
350:         /**
351:          * @return The {@link IItemPropertyDescriptor} descriptor of the current source if it is available
352:          */
353:         protected Optional<IItemPropertyDescriptor> getPropertyDescriptor() {
354:                 return getSource().flatMap(source -> getPropertyDescriptor(source, attribute.getName()));
355:         }
356:
357:         @Override
358:         protected Object doGetValue() {
359:                 return viewer.getStructuredSelection().getFirstElement();
360:         }
361:
362:         @Override
363:         protected void doSetValue(Object value) {
364:                 viewer.setSelection(value == null ? StructuredSelection.EMPTY : new StructuredSelection(value));
365:         }
366:
367:         @Override
368:         protected void doSetFocus() {
369:                 final CCombo combo = viewer.getCCombo();
370:                 if (combo == null || combo.isDisposed()) {
371:                         return;
372:                 }
373:
374:                 combo.setFocus();
375:
376:                 // Remove text selection and move the cursor to the end.
377:                 final String text = combo.getText();
378:                 if (text != null) {
379:                         combo.setSelection(new Point(text.length(), text.length()));
380:                 }
381:         }
382:
383:         /**
384:          * Obtains the EMF.Edit property descriptor for the named property of the {@code object}.
385:          *
386:          * @param object an object
387:          * @param propertyName a property to access
388:          * @return its descriptor
389:          */
390:         static Optional<IItemPropertyDescriptor> getPropertyDescriptor(EObject object, String propertyName) {
391:                 return EMFUtils.adapt(object, IItemPropertySource.class)
392:                         .map(propertySource -> propertySource.getPropertyDescriptor(object, propertyName));
393:         }
394:
395:         //
396:         // Nested types
397:         //
398:
399:         /**
400:          * Label provider for enumeration values.
401:          */
402:         private class EnumLabelProvider extends LabelProvider {
403:                 EnumLabelProvider() {
404:                         super();
405:                 }
406:
407:                 @Override
408:                 public String getText(Object element) {
409:                         return getFormatedString(element);
410:                 }
411:
412:         }
413:
414:         /**
415:          * Observable value of the combo.
416:          */
417:         private class ComboValueProperty extends SimpleValueProperty<Object, Object> {
418:
419:                 @Override
420:                 public Object getValueType() {
421:                         return CCombo.class;
422:                 }
423:
424:                 @Override
425:                 protected Object doGetValue(Object source) {
426:                         return ItemProviderEnumCellEditor.this.getValue();
427:                 }
428:
429:                 @Override
430:                 protected void doSetValue(Object source, Object value) {
431:                         ItemProviderEnumCellEditor.this.doSetValue(value);
432:                 }
433:
434:                 @SuppressWarnings("rawtypes")
435:                 @Override
436:                 public IObservableValue observe(Object source) {
437:                         if (source != ItemProviderEnumCellEditor.this) {
438:                                 return Observables.constantObservableValue(null);
439:                         }
440:
441:                         return ViewerProperties.singleSelection().observe(viewer);
442:                 }
443:
444:                 @Override
445:                 public INativePropertyListener<Object> adaptListener(
446:                         ISimplePropertyListener<Object, ValueDiff<? extends Object>> listener) {
447:                         return null;
448:                 }
449:         }
450:
451: }