Skip to content

Package: DetailViewManager

DetailViewManager

nameinstructionbranchcomplexitylinemethod
DetailViewManager(Composite)
M: 0 C: 5
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 2
100%
M: 0 C: 1
100%
DetailViewManager(Composite, Function)
M: 0 C: 53
100%
M: 0 C: 2
100%
M: 0 C: 2
100%
M: 0 C: 12
100%
M: 0 C: 1
100%
activate(EObject)
M: 5 C: 32
86%
M: 1 C: 3
75%
M: 1 C: 2
67%
M: 1 C: 10
91%
M: 0 C: 1
100%
cacheCurrentDetail()
M: 0 C: 4
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 2
100%
M: 0 C: 1
100%
cacheCurrentDetail(boolean)
M: 0 C: 34
100%
M: 0 C: 10
100%
M: 0 C: 6
100%
M: 0 C: 11
100%
M: 0 C: 1
100%
cacheView(ECPSWTView)
M: 0 C: 23
100%
M: 1 C: 3
75%
M: 1 C: 2
67%
M: 0 C: 6
100%
M: 0 C: 1
100%
clear()
M: 0 C: 8
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 4
100%
M: 0 C: 1
100%
dispose()
M: 0 C: 15
100%
M: 0 C: 2
100%
M: 0 C: 2
100%
M: 0 C: 6
100%
M: 0 C: 1
100%
disposeViews()
M: 0 C: 13
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 4
100%
M: 0 C: 1
100%
doRender(ViewModelContext, DetailRenderingFunction, boolean)
M: 6 C: 81
93%
M: 2 C: 6
75%
M: 2 C: 3
60%
M: 2 C: 18
90%
M: 0 C: 1
100%
getCachedView(EObject)
M: 0 C: 5
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
getCurrentDetail()
M: 0 C: 3
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
getDetailContainer()
M: 0 C: 3
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
getDetailProperties()
M: 0 C: 3
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
getDetailView(EObject)
M: 0 C: 6
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
getDetailView(EObject, VViewModelProperties)
M: 0 C: 33
100%
M: 0 C: 4
100%
M: 0 C: 3
100%
M: 0 C: 10
100%
M: 0 C: 1
100%
getDetailView(ViewModelContext, EObject)
M: 6 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
getDetailView(ViewModelContext, EObject, Consumer)
M: 0 C: 35
100%
M: 1 C: 1
50%
M: 1 C: 1
50%
M: 0 C: 8
100%
M: 0 C: 1
100%
getNoDetailView()
M: 0 C: 73
100%
M: 0 C: 4
100%
M: 0 C: 3
100%
M: 0 C: 18
100%
M: 0 C: 1
100%
getNoDetailsControl()
M: 0 C: 32
100%
M: 0 C: 2
100%
M: 0 C: 2
100%
M: 0 C: 7
100%
M: 0 C: 1
100%
initializeOriginalReadOnly(VElement)
M: 0 C: 18
100%
M: 0 C: 4
100%
M: 0 C: 3
100%
M: 0 C: 3
100%
M: 0 C: 1
100%
isCached(EObject)
M: 0 C: 5
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
isDisposed()
M: 0 C: 3
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
lambda$0(EObject)
M: 0 C: 2
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
lambda$1(DisposeEvent)
M: 0 C: 3
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
lambda$3(Composite, ViewModelContext)
M: 5 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
lambda$4(DetailRenderingFunction, ViewModelContext, Boolean)
M: 0 C: 7
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
layoutDetailParent(Composite)
M: 0 C: 22
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 6
100%
M: 0 C: 1
100%
render(ViewModelContext, DetailRenderingFunction)
M: 0 C: 15
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 2
100%
M: 0 C: 1
100%
render(ViewModelContext, VElement, EObject)
M: 0 C: 62
100%
M: 2 C: 10
83%
M: 2 C: 5
71%
M: 0 C: 11
100%
M: 0 C: 1
100%
setCache(DetailViewCache)
M: 1 C: 16
94%
M: 2 C: 4
67%
M: 2 C: 2
50%
M: 0 C: 4
100%
M: 0 C: 1
100%
setCurrentDetail(ECPSWTView)
M: 1 C: 8
89%
M: 1 C: 1
50%
M: 1 C: 1
50%
M: 1 C: 3
75%
M: 0 C: 1
100%
setDetailReadOnly(boolean)
M: 0 C: 52
100%
M: 2 C: 10
83%
M: 2 C: 5
71%
M: 0 C: 12
100%
M: 0 C: 1
100%
setFocus()
M: 0 C: 14
100%
M: 1 C: 3
75%
M: 1 C: 2
67%
M: 0 C: 3
100%
M: 0 C: 1
100%
setNoDetailMessage(String)
M: 1 C: 30
97%
M: 2 C: 4
67%
M: 2 C: 2
50%
M: 0 C: 8
100%
M: 0 C: 1
100%
showDetail(Control)
M: 0 C: 33
100%
M: 1 C: 5
83%
M: 1 C: 3
75%
M: 0 C: 8
100%
M: 0 C: 1
100%
wasOriginallyReadOnly(VElement)
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) 2019 Christian W. Damus 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: * Christian W. Damus - initial API and implementation
13: ******************************************************************************/
14: package org.eclipse.emf.ecp.view.spi.swt.masterdetail;
15:
16: import java.util.ArrayList;
17: import java.util.HashSet;
18: import java.util.List;
19: import java.util.Map;
20: import java.util.Set;
21: import java.util.WeakHashMap;
22: import java.util.function.BiFunction;
23: import java.util.function.Consumer;
24: import java.util.function.Function;
25:
26: import org.eclipse.emf.ecore.EObject;
27: import org.eclipse.emf.ecore.util.EcoreUtil;
28: import org.eclipse.emf.ecp.ui.view.ECPRendererException;
29: import org.eclipse.emf.ecp.ui.view.swt.ECPSWTView;
30: import org.eclipse.emf.ecp.ui.view.swt.ECPSWTViewRenderer;
31: import org.eclipse.emf.ecp.ui.view.swt.RenderFailureView;
32: import org.eclipse.emf.ecp.view.internal.swt.Activator;
33: import org.eclipse.emf.ecp.view.spi.context.ViewModelContext;
34: import org.eclipse.emf.ecp.view.spi.context.ViewModelContextDisposeListener;
35: import org.eclipse.emf.ecp.view.spi.context.ViewModelContextFactory;
36: import org.eclipse.emf.ecp.view.spi.label.model.VLabel;
37: import org.eclipse.emf.ecp.view.spi.label.model.VLabelFactory;
38: import org.eclipse.emf.ecp.view.spi.label.model.VLabelPackage;
39: import org.eclipse.emf.ecp.view.spi.model.LocalizationAdapter;
40: import org.eclipse.emf.ecp.view.spi.model.VElement;
41: import org.eclipse.emf.ecp.view.spi.model.VView;
42: import org.eclipse.emf.ecp.view.spi.model.VViewFactory;
43: import org.eclipse.emf.ecp.view.spi.model.VViewModelLoadingProperties;
44: import org.eclipse.emf.ecp.view.spi.model.VViewModelProperties;
45: import org.eclipse.emf.ecp.view.spi.model.util.ViewModelPropertiesHelper;
46: import org.eclipse.emf.ecp.view.spi.provider.ViewProviderHelper;
47: import org.eclipse.emf.ecp.view.spi.swt.layout.PageLayout;
48: import org.eclipse.emf.ecp.view.spi.swt.reporting.RenderingFailedReport;
49: import org.eclipse.emf.emfforms.spi.view.annotation.model.VAnnotation;
50: import org.eclipse.emf.emfforms.spi.view.annotation.model.VAnnotationFactory;
51: import org.eclipse.emfforms.spi.common.report.ReportService;
52: import org.eclipse.emfforms.spi.localization.LocalizationServiceHelper;
53: import org.eclipse.swt.SWT;
54: import org.eclipse.swt.custom.ScrolledComposite;
55: import org.eclipse.swt.layout.FillLayout;
56: import org.eclipse.swt.layout.GridLayout;
57: import org.eclipse.swt.widgets.Composite;
58: import org.eclipse.swt.widgets.Control;
59: import org.eclipse.swt.widgets.Widget;
60:
61: /**
62: * A manager for the SWT renderings of detail views, with caching.
63: *
64: * @since 1.22
65: */
66: public class DetailViewManager implements DetailViewCache {
67:
68:         /**
69:          * Name of a boolean-valued {@linkplain VViewModelProperties view model property}
70:          * indicating that the view model is being rendered as a detail in a master-detail
71:          * presentation. It is useful to distinguish this case in enablement/visibility
72:          * rules, templates, etc. from the general case of a view rendering.
73:          */
74:         public static final String DETAIL_PROPERTY = "detail"; //$NON-NLS-1$
75:
76:         private DetailViewCache cache = DetailViewCache.EMPTY;
77:
78:         private final Set<ECPSWTView> views = new HashSet<>();
79:         private final Composite detailStack;
80:         private final PageLayout detailLayout;
81:
82:         /** The currently presented detail view. */
83:         private ECPSWTView currentDetailView;
84:
85:         /** Remember intrinsic read-only state of detail views (as specified in the model). */
86:         private final Map<VElement, Boolean> originalDetailViewReadOnly = new WeakHashMap<>();
87:
88:         /**
89:          * Encapsulation of the mechanism of the last rendering performed.
90:          * Inputs are the detail context and whether the rendering should be read-only.
91:          */
92:         private BiFunction<ViewModelContext, Boolean, ECPSWTView> currentRenderer;
93:
94:         /** View-model properties for the detail view. */
95:         private final VViewModelLoadingProperties detailProperties = VViewFactory.eINSTANCE
96:                 .createViewModelLoadingProperties();
97:
98:         private final Function<? super EObject, ? extends VView> detailViewFunction;
99:
100:         /** View to present when there is no detail view provided by the framework. */
101:         private VView noDetails;
102:         private ECPSWTView renderedNoDetails;
103:
104:         private String noDetailMessage;
105:
106:         private boolean disposed;
107:
108:         /**
109:          * Initializes me.
110:          *
111:          * @param parent the composite in which to create the detail container
112:          */
113:         public DetailViewManager(Composite parent) {
114:                 this(parent, null);
115:         }
116:
117:         /**
118:          * Initializes me with explicit detail views. When the detail function can provide a
119:          * detail view, then the framework's pluggable view providers are not consulted for it.
120:          *
121:          * @param parent the composite in which to create the detail container
122:          * @param detailView the detail-view provider function
123:          */
124:         public DetailViewManager(Composite parent, Function<? super EObject, ? extends VView> detailView) {
125:                 super();
126:
127:                 detailStack = new Composite(parent, SWT.NONE);
128:                 detailLayout = new PageLayout(detailStack);
129:
130:                 detailProperties.addNonInheritableProperty(DETAIL_PROPERTY, true);
131:•                detailViewFunction = detailView != null ? detailView : __ -> null;
132:
133:                 detailStack.addDisposeListener(__ -> dispose());
134:         }
135:
136:         /**
137:          * Dispose of me and my UI resources.
138:          */
139:         public void dispose() {
140:                 disposed = true;
141:
142:                 clear();
143:
144:•                if (renderedNoDetails != null) {
145:                         renderedNoDetails.dispose();
146:                 }
147:
148:                 detailStack.dispose();
149:         }
150:
151:         /**
152:          * Queries whether I have been disposed.
153:          *
154:          * @return whether I am disposed
155:          */
156:         public boolean isDisposed() {
157:                 return disposed;
158:         }
159:
160:         private void disposeViews() {
161:                 // Copy 'views' set because disposal of the contexts will try to remove from it
162:                 final List<ECPSWTView> toDispose = new ArrayList<>(views);
163:                 views.clear();
164:                 toDispose.forEach(ECPSWTView::dispose);
165:         }
166:
167:         /**
168:          * Obtain the control that contains the rendered details. This is useful, for example,
169:          * to set layout parameters on it in the context of its parent composite.
170:          *
171:          * @return the container of the rendered details
172:          */
173:         public Control getDetailContainer() {
174:                 return detailStack;
175:         }
176:
177:         /**
178:          * Set the detail-view cache to use. If some other cache was being used, any rendered
179:          * controls that it maintained {@linkplain Widget#dispose() will be disposed}.
180:          *
181:          * @param cache the {@link DetailViewCache} to use, or {@code null} to use no cache
182:          */
183:         public void setCache(DetailViewCache cache) {
184:•                if (cache != this.cache && this.cache != null) {
185:                         disposeViews();
186:                 }
187:
188:•                this.cache = cache != null ? cache : DetailViewCache.EMPTY;
189:         }
190:
191:         /**
192:          * Present the {@linkplain #isCached(EObject) previously cached} view for an object.
193:          *
194:          * @param eObject the object to present
195:          *
196:          * @return the re-rendered view
197:          *
198:          * @throws IllegalStateException if there is no view cached for the object
199:          * @see #isCached(EObject)
200:          */
201:         public ECPSWTView activate(EObject eObject) {
202:                 cacheCurrentDetail(false);
203:
204:                 final ECPSWTView cached = cache.getCachedView(eObject);
205:
206:•                if (cached == null) {
207:                         throw new IllegalStateException("not cached"); //$NON-NLS-1$
208:                 }
209:
210:                 final ViewModelContext context = cached.getViewModelContext();
211:                 context.reactivate();
212:•                if (context.getDomainModel() != eObject) {
213:                         context.changeDomainModel(eObject);
214:                 }
215:
216:                 // the "renderer" function in this case shouldn't be invoked
217:                 final ECPSWTView result = doRender(context, (p, c) -> {
218:                         throw new IllegalStateException("not cached"); //$NON-NLS-1$
219:                 }, context.getViewModel().isReadonly());
220:
221:                 return result;
222:         }
223:
224:         /**
225:          * Render the detail view for a given {@code context} in the specified {@code parent} composite.
226:          * If a {@linkplain #isCached(EObject) cached rendering} is available for the domain model,
227:          * then it is re-used and the {@code context} is {@linkplain ViewModelContext#dispose() disposed}.
228:          * Otherwise, the supplied rendering function is used to render the detail view.
229:          *
230:          * @param context the context to present in the detail view
231:          * @param renderer if needed to render a new detail view control
232:          * @return the rendered view
233:          */
234:         public ECPSWTView render(ViewModelContext context, DetailRenderingFunction renderer) {
235:                 currentRenderer = (detail, readOnly) -> doRender(detail, renderer, readOnly);
236:                 return currentRenderer.apply(context, context.getViewModel().isReadonly());
237:         }
238:
239:         private ECPSWTView doRender(ViewModelContext context, DetailRenderingFunction renderer, boolean readOnly) {
240:                 cacheCurrentDetail(false);
241:
242:                 final EObject domainModel = context.getDomainModel();
243:•                ECPSWTView result = isCached(domainModel) ? getCachedView(domainModel) : null;
244:
245:•                if (result == null) {
246:                         try {
247:                                 result = renderer.render(detailStack, context);
248:                                 initializeOriginalReadOnly(result.getViewModelContext().getViewModel());
249:                         } catch (final ECPRendererException e) {
250:                                 final ReportService reportService = context.getService(ReportService.class);
251:                                 reportService.report(new RenderingFailedReport(e));
252:
253:                                 result = new RenderFailureView(detailStack, context, e);
254:                         }
255:
256:                         // Track it for eventual disposal
257:                         context.registerDisposeListener(new ContextDisposeListener(result));
258:                 } else {
259:•                        if (result.getViewModelContext().getDomainModel() != domainModel) {
260:                                 result.getViewModelContext().changeDomainModel(domainModel);
261:                         }
262:
263:•                        if (context != result.getViewModelContext()) {
264:                                 // Don't need this context
265:                                 context.dispose();
266:                         }
267:                 }
268:
269:                 setCurrentDetail(result);
270:                 showDetail(result.getSWTControl());
271:
272:                 // It's currently showing, so don't track it for disposal on flushing the cache
273:                 views.remove(result);
274:
275:                 return result;
276:         }
277:
278:         private void setCurrentDetail(ECPSWTView ecpView) {
279:•                if (currentDetailView == ecpView) {
280:                         return; // Nothing to change
281:                 }
282:
283:                 currentDetailView = ecpView;
284:         }
285:
286:         private void showDetail(Control control) {
287:                 detailLayout.showPage(control);
288:
289:                 // Recompute scroll layout, if applicable
290:•                for (Composite composite = detailStack; composite != null; composite = composite.getParent()) {
291:•                        if (composite.getParent() instanceof ScrolledComposite) {
292:                                 final ScrolledComposite scrollPane = (ScrolledComposite) composite.getParent();
293:•                                if (composite == scrollPane.getContent()) {
294:                                         scrollPane.setMinSize(composite.computeSize(SWT.DEFAULT, SWT.DEFAULT));
295:                                 }
296:                                 break;
297:                         }
298:                 }
299:         }
300:
301:         /**
302:          * Render the detail view for a given {@code object} in the specified master context.
303:          *
304:          * @param masterContext the master context in which the {@code object} is selected
305:          * @param masterView the master view in which context to render the detail view
306:          * @param object the selected object for which to present the detail
307:          * @return the rendered view
308:          */
309:         public ECPSWTView render(ViewModelContext masterContext, VElement masterView, EObject object) {
310:                 cacheCurrentDetail(false);
311:
312:                 ECPSWTView result;
313:                 final VView detail = getDetailView(object);
314:
315:                 // Set the detail view to read only if the master view is read only or disabled
316:•                final boolean detailReadOnly = detail.isEffectivelyReadonly() || !detail.isEffectivelyEnabled();
317:•                detail.setReadonly(detailReadOnly || !masterView.isEffectivelyEnabled() || masterView.isEffectivelyReadonly());
318:
319:•                if (isCached(object)) {
320:                         result = activate(object);
321:                         result.getViewModelContext().changeDomainModel(object);
322:                 } else {
323:                         final ViewModelContext childContext = masterContext.getChildContext(object, masterView, detail);
324:                         result = render(childContext, ECPSWTViewRenderer.INSTANCE::render);
325:                 }
326:
327:                 return result;
328:         }
329:
330:         /**
331:          * Obtain the currently presented detail view.
332:          *
333:          * @return the current rendered detail view, or {@code null} if none
334:          */
335:         public final ECPSWTView getCurrentDetail() {
336:                 return currentDetailView;
337:         }
338:
339:         /**
340:          * Cache the currently presented detail view, if any, and remove it from the UI.
341:          */
342:         public void cacheCurrentDetail() {
343:                 cacheCurrentDetail(true);
344:         }
345:
346:         private void cacheCurrentDetail(boolean showEmpty) {
347:•                if (currentDetailView != null) {
348:                         final ECPSWTView ecpView = currentDetailView;
349:                         setCurrentDetail(null);
350:
351:•                        if (isDisposed()) {
352:                                 // Just dispose the detail, then, because we can't cache it
353:                                 ecpView.dispose();
354:                                 // but we need to show something ...
355:                                 showEmpty = true;
356:•                        } else if (!cacheView(ecpView)) {
357:                                 // The current detail view was disposed, so show this instead
358:                                 showEmpty = true;
359:                         }
360:
361:                 }
362:
363:•                if (showEmpty && !isDisposed()) {
364:                         showDetail(getNoDetailsControl().getSWTControl());
365:                 }
366:         }
367:
368:         /**
369:          * Set focus to the currently presented detail view.
370:          */
371:         public void setFocus() {
372:•                if (currentDetailView != null && !currentDetailView.getSWTControl().isDisposed()) {
373:                         currentDetailView.getSWTControl().setFocus();
374:                 }
375:         }
376:
377:         /**
378:          * Obtain the view-model properties used for loading the detail view model.
379:          * Clients may update these properties but must not change the {@link #DETAIL_PROPERTY detail property}.
380:          *
381:          * @return the view-model properties for the detail view
382:          */
383:         public VViewModelProperties getDetailProperties() {
384:                 return detailProperties;
385:         }
386:
387:         /**
388:          * Obtain the detail view in the context of the given master view-model context for a selected
389:          * {@code object} in the master view.
390:          *
391:          * @param masterContext the master view-model context
392:          * @param object the selection in the master view for which to get a detail view
393:          * @return the detail view
394:          */
395:         public VView getDetailView(ViewModelContext masterContext, EObject object) {
396:                 return getDetailView(masterContext, object, null);
397:         }
398:
399:         /**
400:          * Obtain the detail view in the context of the given master view-model context for a selected
401:          * {@code object} in the master view.
402:          *
403:          * @param masterContext the master view-model context
404:          * @param object the selection in the master view for which to get a detail view
405:          * @param propertiesManipulator an optional hook with which to inject properties to assist/filter the detail view
406:          * model resolution
407:          *
408:          * @return the detail view
409:          */
410:         public VView getDetailView(ViewModelContext masterContext, EObject object,
411:                 Consumer<? super VViewModelProperties> propertiesManipulator) {
412:
413:                 final VElement viewModel = masterContext.getViewModel();
414:                 final VViewModelProperties properties = ViewModelPropertiesHelper
415:                         .getInhertitedPropertiesOrEmpty(viewModel);
416:
417:                 // Inject client properties
418:•                if (propertiesManipulator != null) {
419:                         propertiesManipulator.accept(properties);
420:                 }
421:
422:                 // Inject our properties
423:                 detailProperties.getNonInheritableProperties().map().forEach(properties::addNonInheritableProperty);
424:                 detailProperties.getInheritableProperties().map().forEach(properties::addInheritableProperty);
425:
426:                 return ViewProviderHelper.getView(object, properties);
427:         }
428:
429:         /**
430:          * Obtain the detail view for an {@code object} selected in the master view. This method
431:          * is useful for master-detail presentations in which the master is not managed by a
432:          * Form in a {@link ViewModelContext} but explicitly by some custom UI control.
433:          *
434:          * @param object the master view selection
435:          * @return the detail view for the {@code object}
436:          */
437:         public VView getDetailView(EObject object) {
438:                 return getDetailView(object, getDetailProperties());
439:         }
440:
441:         /**
442:          * Set the current detail view read-only or not, unless it is intrinsically read-only,
443:          * in which case it will remain so (this method will have no effect).
444:          *
445:          * @param readOnly {@code true} to set the detail view read-only;
446:          * {@code false} to let its intrinsic read-only state prevail
447:          */
448:         public void setDetailReadOnly(boolean readOnly) {
449:•                if (currentDetailView != null && !currentDetailView.getSWTControl().isDisposed()) {
450:                         final ViewModelContext detailContext = currentDetailView.getViewModelContext();
451:                         final VElement currentDetailVView = detailContext.getViewModel();
452:
453:•                        if (readOnly != currentDetailVView.isReadonly()) {
454:•                                currentDetailVView.setReadonly(readOnly || wasOriginallyReadOnly(currentDetailVView));
455:
456:                                 // Need to rebuild the details because they can be rendered quite
457:                                 // differently in read-only mode as in writable mode. The
458:                                 // renderer operation will compute the new read-only state
459:•                                if (currentRenderer != null) {
460:                                         // Don't let this detail context be disposed by zero reference count
461:                                         detailContext.addContextUser(currentRenderer);
462:                                         try {
463:                                                 clear();
464:                                                 currentRenderer.apply(detailContext, readOnly);
465:                                         } finally {
466:                                                 detailContext.removeContextUser(currentRenderer);
467:                                         }
468:                                 }
469:                         }
470:
471:                 }
472:         }
473:
474:         private boolean wasOriginallyReadOnly(VElement detailView) {
475:                 return originalDetailViewReadOnly.getOrDefault(detailView, false);
476:         }
477:
478:         /**
479:          * Get the view provided by the framework for details of the given {@code object}.
480:          *
481:          * @param object the object for which to get the detail view
482:          * @param properties the properties to use for loading the view
483:          * @return the detail view (never {@code null}, even if the framework provides nothing)
484:          */
485:         protected VView getDetailView(EObject object, VViewModelProperties properties) {
486:                 VView result = detailViewFunction.apply(object);
487:•                if (result != null) {
488:                         // Have an explicit view
489:                         result.setLoadingProperties(EcoreUtil.copy(properties));
490:                 } else {
491:                         // Go to the framework to get a provided view
492:                         result = ViewProviderHelper.getView(object, properties);
493:
494:•                        if (result == null) {
495:                                 // Nothing provided. Make that clear to the user
496:                                 result = getNoDetailView();
497:                                 result.setLoadingProperties(EcoreUtil.copy(properties));
498:                         }
499:                 }
500:
501:                 initializeOriginalReadOnly(result);
502:
503:                 return result;
504:         }
505:
506:         /**
507:          * Store the original intrinsic read-only state if this is the first we see of
508:          * the given detail view.
509:          *
510:          * @param detailView a detail view that was or will be rendered
511:          */
512:         private void initializeOriginalReadOnly(VElement detailView) {
513:•                final boolean readOnly = detailView.isEffectivelyReadonly() || !detailView.isEffectivelyEnabled();
514:                 originalDetailViewReadOnly.putIfAbsent(detailView, readOnly);
515:         }
516:
517:         /**
518:          * Obtain a view to use as placeholder when the framework provides no view for the
519:          * detail of the selection or when there is no selection.
520:          *
521:          * @return the no-details placeholder view
522:          */
523:         protected VView getNoDetailView() {
524:•                if (noDetails == null) {
525:                         noDetails = VViewFactory.eINSTANCE.createView();
526:                         noDetails.setUuid(Integer.toHexString(noDetails.hashCode()));
527:                         final VLabel label = VLabelFactory.eINSTANCE.createLabel();
528:                         label.setUuid(Integer.toHexString(label.hashCode()));
529:                         label.setName("%noDetails"); //$NON-NLS-1$
530:
531:                         // Annotate the label to let it match our styling template
532:                         final VAnnotation annotation = VAnnotationFactory.eINSTANCE.createAnnotation();
533:                         annotation.setKey("detail"); //$NON-NLS-1$
534:                         annotation.setValue("noDetails"); //$NON-NLS-1$
535:                         label.getAttachments().add(annotation);
536:
537:                         noDetails.getChildren().add(label);
538:                 }
539:
540:                 final VView result = EcoreUtil.copy(noDetails);
541:•                if (noDetailMessage != null) {
542:                         // Replace the label name
543:                         final VLabel label = (VLabel) EcoreUtil.getObjectByType(result.getChildren(),
544:                                 VLabelPackage.Literals.LABEL);
545:                         label.setName(noDetailMessage);
546:                 }
547:
548:                 result.eAdapters().add(new LocalizationAdapter() {
549:
550:                         @Override
551:                         public String localize(String key) {
552:                                 return LocalizationServiceHelper.getString(Activator.getDefault().getBundle(),
553:                                         key);
554:                         }
555:                 });
556:
557:                 return result;
558:         }
559:
560:         /**
561:          * Obtain the renderer control showing a hint that there is no selection or
562:          * no details for the current selection.
563:          *
564:          * @return the no-details control
565:          */
566:         protected ECPSWTView getNoDetailsControl() {
567:•                if (renderedNoDetails == null) {
568:                         final VView view = getNoDetailView();
569:
570:                         // The view is just a static label, so it doesn't matter that it renders itself
571:                         final ViewModelContext context = ViewModelContextFactory.INSTANCE.createViewModelContext(view, view);
572:
573:                         try {
574:                                 renderedNoDetails = ECPSWTViewRenderer.INSTANCE.render(detailStack, context);
575:                         } catch (final ECPRendererException e) {
576:                                 renderedNoDetails = new RenderFailureView(detailStack, context, e);
577:                         }
578:                 }
579:
580:                 return renderedNoDetails;
581:         }
582:
583:         /**
584:          * Apply a reasonable default layout for the parent {@code composite} of a detail container
585:          * where the only thing contained in that parent is the detail container.
586:          *
587:          * @param composite the detail parent composite
588:          * @return the {@code composite}, for convenience of call chaining
589:          */
590:         public Composite layoutDetailParent(Composite composite) {
591:                 final FillLayout layout = new FillLayout(SWT.VERTICAL);
592:
593:                 // Use the same margins as are defauls for the grid layout
594:                 final GridLayout gridLayout = new GridLayout();
595:                 layout.marginHeight = gridLayout.marginHeight;
596:                 layout.marginWidth = gridLayout.marginWidth;
597:
598:                 composite.setLayout(layout);
599:                 return composite;
600:         }
601:
602:         /**
603:          * Set a message to display to the user when there is no detail to show, either
604:          * because there is no selection in the master or because it has no details.
605:          *
606:          * @param noDetailMessage the "no details" message to set, or {@code null}
607:          * for the default localized string
608:          * ("No selection or no details available for selection.")
609:          */
610:         public void setNoDetailMessage(String noDetailMessage) {
611:                 this.noDetailMessage = noDetailMessage;
612:
613:                 // Have we already renderered the "no details" view? If so, poke it
614:•                if (renderedNoDetails != null) {
615:                         // Rebuild it
616:•                        final boolean wasCurrent = detailLayout.getCurrentPage() == renderedNoDetails.getSWTControl();
617:                         renderedNoDetails.dispose();
618:                         renderedNoDetails = null;
619:
620:•                        if (wasCurrent) {
621:                                 showDetail(getNoDetailsControl().getSWTControl());
622:                         }
623:                 }
624:         }
625:
626:         //
627:         // DetailViewCache protocol
628:         //
629:
630:         @Override
631:         public boolean isCached(EObject selection) {
632:                 return cache.isCached(selection);
633:         }
634:
635:         @Override
636:         public ECPSWTView getCachedView(EObject selection) {
637:                 return cache.getCachedView(selection);
638:         }
639:
640:         @Override
641:         public boolean cacheView(ECPSWTView ecpView) {
642:                 final Control control = ecpView.getSWTControl();
643:
644:                 ecpView.getViewModelContext().pause();
645:                 final boolean result = cache.cacheView(ecpView);
646:
647:                 // Track it if it was successfully cached
648:•                if (result && !control.isDisposed()) {
649:                         views.add(ecpView);
650:                 }
651:
652:                 return result;
653:         }
654:
655:         @Override
656:         public void clear() {
657:                 cacheCurrentDetail();
658:                 disposeViews();
659:                 cache.clear();
660:         }
661:
662:         //
663:         // Nested types
664:         //
665:
666:         /**
667:          * Listener for disposal of the current view context.
668:          */
669:         private final class ContextDisposeListener implements ViewModelContextDisposeListener {
670:
671:                 private final ECPSWTView ecpView;
672:
673:                 ContextDisposeListener(ECPSWTView ecpView) {
674:                         super();
675:
676:                         this.ecpView = ecpView;
677:                 }
678:
679:                 @Override
680:                 public void contextDisposed(ViewModelContext viewModelContext) {
681:                         ecpView.dispose();
682:
683:                         if (ecpView == currentDetailView) {
684:                                 // The current context is disposed, so this will result in
685:                                 // its rendering being disposed and the no-details control
686:                                 // being shown in its place
687:                                 cacheCurrentDetail();
688:                         } else {
689:                                 views.remove(ecpView);
690:                         }
691:                 }
692:
693:         }
694:
695: }