Skip to content

Package: TreeMasterDetailComposite$3

TreeMasterDetailComposite$3

nameinstructionbranchcomplexitylinemethod
doubleClick(DoubleClickEvent)
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%
{...}
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%

Coverage

1: /*******************************************************************************
2: * Copyright (c) 2011-2020 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: * Clemens Elflein - initial API and implementation
13: * Johannes Faltermeier - initial API and implementation
14: * Christian W. Damus - bugs 533568, 545460, 527686, 548592, 559116
15: ******************************************************************************/
16: package org.eclipse.emfforms.spi.swt.treemasterdetail;
17:
18: import static org.eclipse.emfforms.spi.localization.LocalizationServiceHelper.getString;
19:
20: import java.util.Iterator;
21: import java.util.LinkedHashSet;
22: import java.util.Set;
23: import java.util.concurrent.CompletableFuture;
24:
25: import org.eclipse.core.databinding.observable.value.IObservableValue;
26: import org.eclipse.core.internal.databinding.observable.DelayedObservableValue;
27: import org.eclipse.emf.common.command.Command;
28: import org.eclipse.emf.common.command.CompoundCommand;
29: import org.eclipse.emf.common.notify.Notification;
30: import org.eclipse.emf.common.notify.impl.AdapterImpl;
31: import org.eclipse.emf.ecore.EAttribute;
32: import org.eclipse.emf.ecore.EObject;
33: import org.eclipse.emf.ecore.EStructuralFeature;
34: import org.eclipse.emf.ecore.resource.Resource;
35: import org.eclipse.emf.ecore.util.EcoreUtil;
36: import org.eclipse.emf.ecp.common.spi.UniqueSetting;
37: import org.eclipse.emf.ecp.ui.view.swt.ECPSWTView;
38: import org.eclipse.emf.ecp.ui.view.swt.ECPSWTViewRenderer;
39: import org.eclipse.emf.ecp.view.spi.common.callback.ViewModelPropertiesUpdateCallback;
40: import org.eclipse.emf.ecp.view.spi.context.ViewModelContext;
41: import org.eclipse.emf.ecp.view.spi.context.ViewModelContextFactory;
42: import org.eclipse.emf.ecp.view.spi.model.VView;
43: import org.eclipse.emf.ecp.view.spi.swt.masterdetail.DetailViewCache;
44: import org.eclipse.emf.ecp.view.spi.swt.masterdetail.DetailViewManager;
45: import org.eclipse.emf.ecp.view.spi.swt.selection.IMasterDetailSelectionProvider;
46: import org.eclipse.emf.ecp.view.spi.swt.selection.MasterDetailFocusAdapter;
47: import org.eclipse.emf.ecp.view.spi.swt.selection.MasterDetailSelectionProvider;
48: import org.eclipse.emf.ecp.view.treemasterdetail.model.VTreeMasterDetail;
49: import org.eclipse.emf.edit.command.AddCommand;
50: import org.eclipse.emf.edit.command.DeleteCommand;
51: import org.eclipse.emf.edit.command.SetCommand;
52: import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
53: import org.eclipse.emf.edit.domain.EditingDomain;
54: import org.eclipse.emf.edit.domain.IEditingDomainProvider;
55: import org.eclipse.emfforms.spi.core.services.reveal.EMFFormsRevealService;
56: import org.eclipse.emfforms.spi.swt.treemasterdetail.util.DetailPanelRenderingFinishedCallback;
57: import org.eclipse.emfforms.spi.swt.treemasterdetail.util.RootObject;
58: import org.eclipse.jface.databinding.viewers.typed.ViewerProperties;
59: import org.eclipse.jface.layout.GridLayoutFactory;
60: import org.eclipse.jface.viewers.DoubleClickEvent;
61: import org.eclipse.jface.viewers.IDoubleClickListener;
62: import org.eclipse.jface.viewers.ISelection;
63: import org.eclipse.jface.viewers.ISelectionProvider;
64: import org.eclipse.jface.viewers.IStructuredSelection;
65: import org.eclipse.jface.viewers.ITreeContentProvider;
66: import org.eclipse.jface.viewers.StructuredSelection;
67: import org.eclipse.jface.viewers.TreeViewer;
68: import org.eclipse.swt.SWT;
69: import org.eclipse.swt.custom.ScrolledComposite;
70: import org.eclipse.swt.events.DisposeEvent;
71: import org.eclipse.swt.events.DisposeListener;
72: import org.eclipse.swt.events.KeyAdapter;
73: import org.eclipse.swt.events.KeyEvent;
74: import org.eclipse.swt.layout.FormAttachment;
75: import org.eclipse.swt.layout.FormData;
76: import org.eclipse.swt.layout.FormLayout;
77: import org.eclipse.swt.widgets.Composite;
78: import org.eclipse.swt.widgets.Control;
79: import org.eclipse.swt.widgets.Display;
80: import org.eclipse.swt.widgets.Event;
81: import org.eclipse.swt.widgets.Listener;
82: import org.eclipse.swt.widgets.Sash;
83:
84: /**
85: * The Class MasterDetailRenderer.
86: * It is the base renderer for the editor.
87: *
88: * It takes any object as input and renders a tree on the left-hand side.
89: * When selecting an item in the tree (that is an EObject) EMF-Forms is used to render the detail pane on the right-hand
90: * side
91: *
92: * MasterDetailRenderer implements IEditingDomainProvider to allow Undo/Redo/Copy/Cut/Paste actions to be performed
93: * externally.
94: *
95: * MasterDetailRenderer provides an ISelectionProvider to get the currently selected items in the tree
96: *
97: */
98: @SuppressWarnings("restriction")
99: public class TreeMasterDetailComposite extends Composite implements IEditingDomainProvider {
100:
101:         /** The input. */
102:         private final Object input;
103:
104:         /** The editing domain. */
105:         private final EditingDomain editingDomain;
106:
107:         /** The tree viewer. */
108:         private TreeViewer treeViewer;
109:
110:         /** The selection provider. */
111:         private IMasterDetailSelectionProvider selectionProvider;
112:
113:         /** The vertical sash. */
114:         private Sash verticalSash;
115:
116:         /** The detail scrollable composite. */
117:         private Composite detailComposite;
118:
119:         /** Manager of the currently rendered ECPSWTView with caching. */
120:         private DetailViewManager detailManager;
121:
122:         private final String selectNodeMessage = getString(getClass(), "selectNodeMessage"); //$NON-NLS-1$
123:         private final String loadingMessage = getString(getClass(), "loadingMessage"); //$NON-NLS-1$
124:
125:         private Object lastRenderedObject;
126:
127:         private final TreeMasterDetailSWTCustomization customization;
128:
129:         /** the delay between a selection change and the start of the rendering. */
130:         private final int renderDelay;
131:
132:         private ViewModelPropertiesUpdateCallback viewModelPropertiesUpdateCallback;
133:         private final Set<DetailPanelRenderingFinishedCallback> detailPanelRenderingFinishedCallbacks = new LinkedHashSet<DetailPanelRenderingFinishedCallback>();
134:
135:         /**
136:          * Default constructor.
137:          *
138:          * @param parent the parent composite
139:          * @param style the style bits
140:          * @param input the input for the tree
141:          * @param customization the customization
142:          * @param renderDelay the delay between a selection change and updating the detail
143:          */
144:         /* package */ TreeMasterDetailComposite(Composite parent, int style, Object input,
145:                 TreeMasterDetailSWTCustomization customization, int renderDelay) {
146:                 super(parent, style);
147:                 this.input = input;
148:                 if (input instanceof Resource) {
149:                         editingDomain = AdapterFactoryEditingDomain.getEditingDomainFor(((Resource) input).getContents().get(0));
150:                 } else if (input instanceof RootObject) {
151:                         editingDomain = AdapterFactoryEditingDomain.getEditingDomainFor(RootObject.class.cast(input).getRoot());
152:                 } else {
153:                         editingDomain = AdapterFactoryEditingDomain.getEditingDomainFor(input);
154:                 }
155:                 this.renderDelay = renderDelay;
156:                 this.customization = customization;
157:
158:                 renderControl(customization);
159:
160:                 parent.addDisposeListener(new DisposeListener() {
161:
162:                         @Override
163:                         public void widgetDisposed(DisposeEvent e) {
164:                                 TreeMasterDetailComposite.this.dispose();
165:                         }
166:                 });
167:         }
168:
169:         private Control renderControl(TreeMasterDetailSWTCustomization buildBehaviour) {
170:                 // Create the Form with two panels and a header
171:                 setLayout(new FormLayout());
172:
173:                 // Create the Separator
174:                 verticalSash = createSash(this, buildBehaviour);
175:
176:                 // Create the Tree
177:                 final Composite treeComposite = new Composite(this, SWT.NONE);
178:                 addTreeViewerLayoutData(treeComposite, verticalSash);
179:                 GridLayoutFactory.fillDefaults().numColumns(1).applyTo(treeComposite);
180:                 treeViewer = TreeViewerSWTFactory.createTreeViewer(treeComposite, input, customization);
181:                 selectionProvider = new MasterDetailSelectionProvider(treeViewer);
182:                 treeViewer.getControl().addFocusListener(
183:                         new MasterDetailFocusAdapter(selectionProvider, () -> detailManager.getDetailContainer()));
184:
185:                 // Create detail composite
186:                 detailComposite = buildBehaviour.createDetailComposite(this);
187:                 addDetailCompositeLayoutData(detailComposite, verticalSash);
188:                 Composite detailParent = detailComposite;
189:                 if (detailParent instanceof ScrolledComposite) {
190:                         final Composite detailPanel = new Composite(detailParent, SWT.BORDER);
191:                         ((ScrolledComposite) detailParent).setContent(detailPanel);
192:                         detailParent = detailPanel;
193:                 }
194:                 detailManager = new DetailViewManager(detailParent);
195:                 detailManager.setNoDetailMessage(selectNodeMessage);
196:                 detailManager.layoutDetailParent(detailParent);
197:
198:                 /* enable optional delayed update mechanism */
199:                 IObservableValue<?> treeViewerSelectionObservable = ViewerProperties.singleSelection().observe(treeViewer);
200:                 if (renderDelay > 0) {
201:                         treeViewerSelectionObservable = new DelayedObservableValue<>(renderDelay,
202:                                 treeViewerSelectionObservable);
203:                 }
204:                 treeViewerSelectionObservable.addChangeListener(__ -> doUpdateDetailPanel(false));
205:
206:                 final IObservableValue<?> observableToDispose = treeViewerSelectionObservable;
207:                 treeComposite.addDisposeListener(__ -> observableToDispose.dispose());
208:
209:                 /* add key listener to switch focus on enter */
210:                 treeViewer.getTree().addKeyListener(new KeyAdapter() {
211:
212:                         @Override
213:                         public void keyReleased(KeyEvent e) {
214:                                 if (e.keyCode == SWT.CR || e.keyCode == SWT.LF) {
215:                                         doUpdateDetailPanel(true);
216:                                 }
217:                         }
218:
219:                 });
220:
221:                 /* add double click listener to switch focus on enter */
222:                 treeViewer.addDoubleClickListener(new IDoubleClickListener() {
223:
224:                         @Override
225:                         public void doubleClick(DoubleClickEvent event) {
226:                                 doUpdateDetailPanel(true);
227:                         }
228:                 });
229:
230:                 updateDetailPanel(false);
231:
232:                 return this;
233:         }
234:
235:         private void setFocusToDetail() {
236:                 detailManager.setFocus();
237:         }
238:
239:         private void addDetailCompositeLayoutData(Composite detailComposite, Sash verticalSash) {
240:                 final FormData detailFormData = new FormData();
241:                 detailFormData.left = new FormAttachment(verticalSash, 2);
242:                 detailFormData.top = new FormAttachment(0, 5);
243:                 detailFormData.bottom = new FormAttachment(100, -5);
244:                 detailFormData.right = new FormAttachment(100, -5);
245:                 detailComposite.setLayoutData(detailFormData);
246:         }
247:
248:         private void addTreeViewerLayoutData(Composite treeComposite, Sash verticalSash) {
249:                 final FormData treeFormData = new FormData();
250:                 treeFormData.bottom = new FormAttachment(100, -5);
251:                 treeFormData.left = new FormAttachment(0, 5);
252:                 treeFormData.right = new FormAttachment(verticalSash, -2);
253:                 treeFormData.top = new FormAttachment(0, 5);
254:                 treeComposite.setLayoutData(treeFormData);
255:         }
256:
257:         private Sash createSash(final Composite parent, TreeWidthProvider buildBehaviour) {
258:                 final Sash sash = new Sash(parent, SWT.VERTICAL);
259:
260:                 // Make the left panel 300px wide and put it below the header
261:                 final FormData sashFormData = new FormData();
262:                 sashFormData.bottom = new FormAttachment(100, -5);
263:                 sashFormData.left = new FormAttachment(0, buildBehaviour.getInitialTreeWidth());
264:                 sashFormData.top = new FormAttachment(0, 5);
265:                 sash.setLayoutData(sashFormData);
266:
267:                 // As soon as the sash is moved, layout the parent to reflect the changes
268:                 sash.addListener(SWT.Selection, new Listener() {
269:                         @Override
270:                         public void handleEvent(Event e) {
271:                                 sash.setLocation(e.x, e.y);
272:
273:                                 final FormData sashFormData = new FormData();
274:                                 sashFormData.bottom = new FormAttachment(100, -5);
275:                                 sashFormData.left = new FormAttachment(0, e.x);
276:                                 sashFormData.top = new FormAttachment(0, 5);
277:                                 sash.setLayoutData(sashFormData);
278:                                 parent.layout(true);
279:                         }
280:                 });
281:                 return sash;
282:         }
283:
284:         // TODO JF this needs to be refactored, when used as the replacement for the treemasterdetail renderer.
285:         // selection modification required as well as adjusting the loading properties
286:         /**
287:          * Updates the detail panel of the tree master detail.
288:          *
289:          * @param setFocusToDetail <code>true</code> if the focus should be moved to the detail panel
290:          *
291:          * @since 1.11
292:          */
293:         public void updateDetailPanel(final boolean setFocusToDetail) {
294:                 // Create a new detail panel in the scrollable composite. Disposes any old panels.
295:                 // createDetailPanel();
296:                 // TODO create detail panel at the right location
297:                 final IStructuredSelection selection = (StructuredSelection) treeViewer.getSelection();
298:                 final Object selectedObject = getSelectedObject(selection);
299:                 detailManager.cacheCurrentDetail();
300:
301:                 boolean asyncRendering = false;
302:                 ViewModelContext context = null;
303:                 if (selectedObject instanceof EObject) {
304:                         lastRenderedObject = selectedObject;
305:                         final EObject eObject = EObject.class.cast(selectedObject);
306:
307:                         if (detailManager.isCached(eObject)) {
308:                                 // It's ready to present (no async needed)
309:                                 context = detailManager.activate(eObject).getViewModelContext();
310:
311:                                 updateScrolledComposite();
312:                         } else {
313:                                 if (viewModelPropertiesUpdateCallback != null) {
314:                                         viewModelPropertiesUpdateCallback.updateViewModelProperties(detailManager.getDetailProperties());
315:                                 }
316:                                 // Check, if the selected object would be rendered using a TreeMasterDetail. If so, render the provided
317:                                 // detail view.
318:                                 final VView view = detailManager.getDetailView(eObject);
319:                                 if (view.getChildren().size() > 0 && view.getChildren().get(0) instanceof VTreeMasterDetail) {
320:                                         // Yes, we need to render this node differently
321:                                         final VView treeDetailView = VTreeMasterDetail.class.cast(view.getChildren().get(0))
322:                                                 .getDetailView();
323:                                         // Even if the TMD composite is not configured as read-only honor the effective read-only
324:                                         // configuration of the loaded detail view
325:                                         treeDetailView.setReadonly(treeDetailView.isEffectivelyReadonly() || customization.isReadOnly());
326:                                         context = ViewModelContextFactory.INSTANCE.createViewModelContext(treeDetailView, eObject);
327:                                         detailManager.render(context, ECPSWTViewRenderer.INSTANCE::render);
328:                                 } else {
329:                                         // No, everything is fine
330:                                         detailManager.setNoDetailMessage(loadingMessage);
331:                                         asyncRendering = true;
332:                                         Display.getDefault().asyncExec(new UpdateDetailRunnable(setFocusToDetail, eObject));
333:                                 }
334:                                 // After rendering the Forms, compute the size of the form. So the scroll container knows when to scroll
335:                                 updateScrolledComposite();
336:                         }
337:                 } else {
338:                         renderEmptyDetailPanel();
339:                 }
340:
341:                 /*
342:                  * Notify the callbacks that the rendering has been finished.
343:                  * In case of async rendering, the async process needs to notify the callbacks.
344:                  */
345:                 if (!asyncRendering) {
346:                         for (final DetailPanelRenderingFinishedCallback callback : detailPanelRenderingFinishedCallbacks) {
347:                                 callback.renderingFinished(context, selectedObject);
348:                         }
349:                 }
350:         }
351:
352:         private Object getSelectedObject(IStructuredSelection selection) {
353:                 // Get the selected object, if it is an EObject, render the details using EMF Forms
354:                 Object selectedObject = selection != null ? selection.getFirstElement() : null;
355:                 if (customization.enableVerticalCopy() && selectedObject instanceof EObject && selection.size() > 1) {
356:                         boolean allOfSameType = true;
357:                         final EObject dummy = EcoreUtil.create(((EObject) selectedObject).eClass());
358:
359:                         final Iterator<?> iterator = selection.iterator();
360:                         final Set<EObject> selectedEObjects = new LinkedHashSet<EObject>();
361:                         while (iterator.hasNext()) {
362:                                 final EObject eObject = (EObject) iterator.next();
363:                                 allOfSameType &= eObject.eClass() == dummy.eClass();
364:                                 if (allOfSameType) {
365:                                         for (final EAttribute attribute : dummy.eClass().getEAllAttributes()) {
366:                                                 if (eObject == selectedObject) {
367:                                                         dummy.eSet(attribute, eObject.eGet(attribute));
368:                                                 } else if (dummy.eGet(attribute) != null
369:                                                         && !dummy.eGet(attribute).equals(eObject.eGet(attribute))) {
370:                                                         dummy.eUnset(attribute);
371:                                                 }
372:                                         }
373:                                         selectedEObjects.add(eObject);
374:                                 } else {
375:                                         break;
376:                                 }
377:                         }
378:                         if (allOfSameType) {
379:                                 selectedObject = dummy;
380:                                 dummy.eAdapters().add(new MultiEditAdapter(selectedEObjects, dummy));
381:                         }
382:                 }
383:                 return selectedObject;
384:         }
385:
386:         private void updateScrolledComposite() {
387:                 if (ScrolledComposite.class.isInstance(detailComposite)) {
388:                         ScrolledComposite.class.cast(detailComposite)
389:                                 .setMinSize(detailManager.getDetailContainer().computeSize(SWT.DEFAULT, SWT.DEFAULT));
390:                 }
391:         }
392:
393:         private void renderEmptyDetailPanel() {
394:                 lastRenderedObject = null;
395:                 detailManager.cacheCurrentDetail();
396:
397:                 updateScrolledComposite();
398:         }
399:
400:         @Override
401:         public void dispose() {
402:                 detailManager.dispose();
403:                 customization.dispose();
404:                 super.dispose();
405:         }
406:
407:         /**
408:          * Gets the current selection.
409:          *
410:          * @return the current selection
411:          */
412:         public Object getCurrentSelection() {
413:                 if (!(treeViewer.getSelection() instanceof StructuredSelection)) {
414:                         return null;
415:                 }
416:                 return ((StructuredSelection) treeViewer.getSelection()).getFirstElement();
417:         }
418:
419:         /**
420:          * Sets the selection.
421:          *
422:          * @param structuredSelection the new selection
423:          * @since 1.9
424:          */
425:         public void setSelection(ISelection structuredSelection) {
426:                 treeViewer.setSelection(structuredSelection);
427:         }
428:
429:         /**
430:          * Gets the tree viewer.
431:          *
432:          * @return the tree viewer (which is a selection provider)
433:          *
434:          * @deprecated Use the {@link #getMasterDetailSelectionProvider() master-detail selection provider}, instead},
435:          * or {@link #refresh()} to force a refresh of the tree, or {@link #selectAndReveal(Object)}
436:          * to select and reveal some object in my tree
437:          * @see #getMasterDetailSelectionProvider()
438:          */
439:         @Deprecated
440:         public TreeViewer getSelectionProvider() {
441:                 return treeViewer;
442:         }
443:
444:         /**
445:          * Get the master/detail-aware selection provider.
446:          *
447:          * @return a selection provider that is aware of the user's focus on either the
448:          * master tree or the detail view
449:          * @since 1.21
450:          */
451:         public ISelectionProvider getMasterDetailSelectionProvider() {
452:                 return selectionProvider;
453:         }
454:
455:         /**
456:          * Request a refresh of my tree.
457:          *
458:          * @since 1.22
459:          */
460:         public void refresh() {
461:                 if (treeViewer != null) {
462:                         treeViewer.refresh();
463:                 }
464:         }
465:
466:         /**
467:          * Select and reveal a {@code selection} in my tree. If the argument is an {@link UniqueSetting},
468:          * then the {@linkplain UniqueSetting#getEObject() owner} of the setting will be revealed and the
469:          * control that edits the {@linkplain UniqueSetting#getEStructuralFeature() setting} will be
470:          * revealed and focused (if possible) in the object's detail view.
471:          *
472:          * @param selection the objet to select and reveal
473:          * @return {@code true} if the {@code selection} was revealed; {@code false}, otherwise, including
474:          * the case where the nearest parent object up the tree was revealed instead
475:          * @since 1.22
476:          */
477:         public boolean selectAndReveal(final Object selection) {
478:                 boolean result = false;
479:
480:                 Object toReveal = selection;
481:                 final EStructuralFeature feature;
482:
483:                 if (selection instanceof UniqueSetting) {
484:                         final UniqueSetting setting = (UniqueSetting) selection;
485:                         toReveal = setting.getEObject();
486:                         feature = setting.getEStructuralFeature();
487:                 } else if (selection instanceof EStructuralFeature.Setting) {
488:                         final EStructuralFeature.Setting setting = (EStructuralFeature.Setting) selection;
489:                         toReveal = setting.getEObject();
490:                         feature = setting.getEStructuralFeature();
491:                 } else {
492:                         feature = null;
493:                 }
494:
495:                 if (feature != null) {
496:                         final CompletableFuture<ECPSWTView> renderedDetail = new CompletableFuture<>();
497:                         final DetailPanelRenderingFinishedCallback detailReady = __ -> renderedDetail
498:                                 .complete(detailManager.getCurrentDetail());
499:                         registerDetailPanelRenderingFinishedCallback(detailReady);
500:
501:                         final EObject owner = (EObject) toReveal;
502:                         result = selectAndRevealInTree(owner);
503:                         if (result) {
504:                                 renderedDetail.whenComplete((view, e) -> {
505:                                         unregisterDetailPanelRenderingFinishedCallback(detailReady);
506:                                         revealInDetail(view, owner, feature);
507:                                 });
508:                         } else {
509:                                 // Won't need the call-back so remove it now
510:                                 renderedDetail.cancel(false);
511:                                 unregisterDetailPanelRenderingFinishedCallback(detailReady);
512:                         }
513:
514:                         // Do we already have the detail?
515:                         final ECPSWTView currentDetail = detailManager.getCurrentDetail();
516:                         if (currentDetail != null && currentDetail.getViewModelContext().getDomainModel() == owner) {
517:                                 // There won't be an asynchronous rendering to wait for
518:                                 renderedDetail.complete(currentDetail);
519:                         }
520:                 } else {
521:                         result = selectAndRevealInTree(toReveal);
522:                 }
523:
524:                 return result;
525:         }
526:
527:         private boolean selectAndRevealInTree(final Object selection) {
528:                 if (treeViewer == null) {
529:                         return false;
530:                 }
531:
532:                 boolean result = false;
533:
534:                 // Try to reveal the 'selection' in the tree. If it isn't in the
535:                 // tree, then search up the content provider to find the nearest
536:                 // object that can be revealed and select that, or give up
537:                 for (Object objectToReveal = selection; objectToReveal != null;) {
538:                         treeViewer.reveal(objectToReveal);
539:                         if (treeViewer.testFindItem(objectToReveal) != null) {
540:                                 // Select it and we're done
541:                                 treeViewer.setSelection(new StructuredSelection(objectToReveal));
542:                                 result = objectToReveal == selection;
543:                                 break;
544:                         }
545:
546:                         // Look up the content tree for an object to reveal
547:                         objectToReveal = ((ITreeContentProvider) treeViewer.getContentProvider()).getParent(objectToReveal);
548:                 }
549:
550:                 return result;
551:         }
552:
553:         private void revealInDetail(ECPSWTView detail, EObject object, EStructuralFeature feature) {
554:                 final ViewModelContext context = detail.getViewModelContext();
555:                 if (!context.hasService(EMFFormsRevealService.class)) {
556:                         // Nothing to do
557:                         return;
558:                 }
559:
560:                 final EMFFormsRevealService reveal = context.getService(EMFFormsRevealService.class);
561:                 reveal.reveal(object, feature);
562:         }
563:
564:         /**
565:          * Gets the editing domain.
566:          *
567:          * @return the editing domain
568:          */
569:         @Override
570:         public EditingDomain getEditingDomain() {
571:                 return editingDomain;
572:         }
573:
574:         /**
575:          * Allows to set a different input for the treeviewer.
576:          *
577:          * @param input the new input
578:          */
579:         public void setInput(Object input) {
580:                 treeViewer.setInput(input);
581:         }
582:
583:         /**
584:          * Allows to override the default cache implementation by the provided one.
585:          *
586:          * @param cache The {@link TreeMasterDetailCache} to use.
587:          * @since 1.9
588:          *
589:          * @deprecated As of 1.22, use the {@link #setCache(DetailViewCache)} API, instead
590:          */
591:         @Deprecated
592:         public void setCache(TreeMasterDetailCache cache) {
593:                 setCache((DetailViewCache) cache);
594:         }
595:
596:         /**
597:          * Override the default cache implementation.
598:          *
599:          * @param cache the {@link DetailViewCache} to use, or {@code null} to use no cache
600:          * @since 1.22
601:          */
602:         public void setCache(DetailViewCache cache) {
603:                 detailManager.setCache(cache);
604:         }
605:
606:         private void doUpdateDetailPanel(boolean setFocusToDetail) {
607:                 if (lastRenderedObject == getCurrentSelection()) {
608:                         if (setFocusToDetail) {
609:                                 setFocusToDetail();
610:                         }
611:                         /*
612:                          * possible when e.g. a double click or enter has forced an instant rendering and the delay update kicks in.
613:                          */
614:                         return;
615:                 }
616:                 updateDetailPanel(setFocusToDetail);
617:         }
618:
619:         /**
620:          * Returns whether I am read-only.
621:          *
622:          * @return <code>true</code> if read-only
623:          * @since 1.22
624:          * @see TreeMasterDetailSWTBuilder#customizeReadOnly(boolean)
625:          */
626:         public boolean isReadOnly() {
627:                 return customization.isReadOnly();
628:         }
629:
630:         /**
631:          * Adapter which listens to changes and delegates the notification to other EObjects.
632:          *
633:          * @author Eugen Neufeld
634:          *
635:          */
636:         private final class MultiEditAdapter extends AdapterImpl {
637:                 private final Set<EObject> selectedEObjects;
638:                 private final EObject dummy;
639:
640:                 private MultiEditAdapter(Set<EObject> selectedEObjects, EObject dummy) {
641:                         this.selectedEObjects = selectedEObjects;
642:                         this.dummy = dummy;
643:                 }
644:
645:                 @Override
646:                 public void notifyChanged(Notification notification) {
647:                         if (dummy.eClass().getEAllAttributes().contains(notification.getFeature())) {
648:                                 final CompoundCommand cc = new CompoundCommand();
649:                                 for (final EObject selected : selectedEObjects) {
650:                                         Command command = null;
651:                                         switch (notification.getEventType()) {
652:                                         case Notification.SET:
653:                                                 command = SetCommand.create(editingDomain, selected,
654:                                                         notification.getFeature(), notification.getNewValue());
655:                                                 break;
656:                                         case Notification.UNSET:
657:                                                 command = SetCommand.create(editingDomain, selected,
658:                                                         notification.getFeature(), SetCommand.UNSET_VALUE);
659:                                                 break;
660:                                         case Notification.ADD:
661:                                         case Notification.ADD_MANY:
662:                                                 command = AddCommand.create(editingDomain, selected,
663:                                                         notification.getFeature(), notification.getNewValue());
664:                                                 break;
665:                                         case Notification.REMOVE:
666:                                         case Notification.REMOVE_MANY:
667:                                                 command = DeleteCommand.create(editingDomain, notification.getOldValue());
668:                                                 break;
669:                                         default:
670:                                                 continue;
671:                                         }
672:                                         cc.append(command);
673:                                 }
674:                                 editingDomain.getCommandStack().execute(cc);
675:                         }
676:                 }
677:         }
678:
679:         /**
680:          * Runnable which updates the detail panel.
681:          */
682:         private final class UpdateDetailRunnable implements Runnable {
683:                 private final boolean setFocusToDetail;
684:                 private final EObject eObject;
685:
686:                 UpdateDetailRunnable(boolean setFocusToDetail, EObject eObject) {
687:                         super();
688:
689:                         this.setFocusToDetail = setFocusToDetail;
690:                         this.eObject = eObject;
691:                 }
692:
693:                 @Override
694:                 public void run() {
695:                         if (detailManager.isDisposed()) {
696:                                 // We've been disposed. Nothing to do
697:                                 return;
698:                         }
699:
700:                         if (viewModelPropertiesUpdateCallback != null) {
701:                                 viewModelPropertiesUpdateCallback.updateViewModelProperties(detailManager.getDetailProperties());
702:                         }
703:                         final VView view = detailManager.getDetailView(eObject);
704:                         // Even if the TMD is not configured as read-only honor the effective read-only
705:                         // configuration of the loaded view
706:                         view.setReadonly(view.isEffectivelyReadonly() || customization.isReadOnly());
707:                         final ViewModelContext modelContext = ViewModelContextFactory.INSTANCE
708:                                 .createViewModelContext(
709:                                         view, eObject, customization.getViewModelServices(view, eObject));
710:
711:                         detailManager.setNoDetailMessage(selectNodeMessage);
712:                         if (detailManager.isDisposed()) {
713:                                 return;
714:                         }
715:                         detailManager.render(modelContext, ECPSWTViewRenderer.INSTANCE::render);
716:                         updateScrolledComposite();
717:                         if (setFocusToDetail) {
718:                                 setFocusToDetail();
719:                         }
720:                         // notify callbacks that the rendering was finished
721:                         for (final DetailPanelRenderingFinishedCallback callback : detailPanelRenderingFinishedCallbacks) {
722:                                 callback.renderingFinished(modelContext, eObject);
723:                         }
724:                 }
725:         }
726:
727:         /**
728:          * Adds a {@link ViewModelPropertiesUpdateCallback}.
729:          *
730:          * @param viewModelPropertiesUpdateCallback the callback
731:          * @since 1.11
732:          */
733:         public void addViewModelPropertiesUpdateCallback(
734:                 ViewModelPropertiesUpdateCallback viewModelPropertiesUpdateCallback) {
735:                 this.viewModelPropertiesUpdateCallback = viewModelPropertiesUpdateCallback;
736:         }
737:
738:         /**
739:          * Register a callback that is notified whenever the rendering of a detail panel is finished.
740:          *
741:          * @param detailPanelRenderingFinishedCallback the callback
742:          * @return <code>true</code> if the callback has been added, <code>false</code> if it was already registered
743:          * @since 1.13
744:          */
745:         public boolean registerDetailPanelRenderingFinishedCallback(
746:                 DetailPanelRenderingFinishedCallback detailPanelRenderingFinishedCallback) {
747:                 return detailPanelRenderingFinishedCallbacks.add(detailPanelRenderingFinishedCallback);
748:         }
749:
750:         /**
751:          * Register a callback that is notified whenever the rendering of a detail panel is finished.
752:          *
753:          * @param detailPanelRenderingFinishedCallback the callback
754:          * @return <code>true</code> if the callback has been removed, <code>false</code> if it was not registered
755:          * @since 1.13
756:          */
757:         public boolean unregisterDetailPanelRenderingFinishedCallback(
758:                 DetailPanelRenderingFinishedCallback detailPanelRenderingFinishedCallback) {
759:                 return detailPanelRenderingFinishedCallbacks.remove(detailPanelRenderingFinishedCallback);
760:         }
761: }