blob: b6728d8574a61f55ffb48ad93588ccbdaed5d7b8 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011-2020 EclipseSource Muenchen GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Clemens Elflein - initial API and implementation
* Johannes Faltermeier - initial API and implementation
* Christian W. Damus - bugs 533568, 545460, 527686, 548592, 559116
******************************************************************************/
package org.eclipse.emfforms.spi.swt.treemasterdetail;
import static org.eclipse.emfforms.spi.localization.LocalizationServiceHelper.getString;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.internal.databinding.observable.DelayedObservableValue;
import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.command.CompoundCommand;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.impl.AdapterImpl;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecp.common.spi.UniqueSetting;
import org.eclipse.emf.ecp.ui.view.swt.ECPSWTView;
import org.eclipse.emf.ecp.ui.view.swt.ECPSWTViewRenderer;
import org.eclipse.emf.ecp.view.spi.common.callback.ViewModelPropertiesUpdateCallback;
import org.eclipse.emf.ecp.view.spi.context.ViewModelContext;
import org.eclipse.emf.ecp.view.spi.context.ViewModelContextFactory;
import org.eclipse.emf.ecp.view.spi.model.VView;
import org.eclipse.emf.ecp.view.spi.swt.masterdetail.DetailViewCache;
import org.eclipse.emf.ecp.view.spi.swt.masterdetail.DetailViewManager;
import org.eclipse.emf.ecp.view.spi.swt.selection.IMasterDetailSelectionProvider;
import org.eclipse.emf.ecp.view.spi.swt.selection.MasterDetailFocusAdapter;
import org.eclipse.emf.ecp.view.spi.swt.selection.MasterDetailSelectionProvider;
import org.eclipse.emf.ecp.view.treemasterdetail.model.VTreeMasterDetail;
import org.eclipse.emf.edit.command.AddCommand;
import org.eclipse.emf.edit.command.DeleteCommand;
import org.eclipse.emf.edit.command.SetCommand;
import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.emf.edit.domain.IEditingDomainProvider;
import org.eclipse.emfforms.spi.core.services.reveal.EMFFormsRevealService;
import org.eclipse.emfforms.spi.swt.treemasterdetail.util.DetailPanelRenderingFinishedCallback;
import org.eclipse.emfforms.spi.swt.treemasterdetail.util.RootObject;
import org.eclipse.jface.databinding.viewers.ViewersObservables;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Sash;
/**
* The Class MasterDetailRenderer.
* It is the base renderer for the editor.
*
* It takes any object as input and renders a tree on the left-hand side.
* When selecting an item in the tree (that is an EObject) EMF-Forms is used to render the detail pane on the right-hand
* side
*
* MasterDetailRenderer implements IEditingDomainProvider to allow Undo/Redo/Copy/Cut/Paste actions to be performed
* externally.
*
* MasterDetailRenderer provides an ISelectionProvider to get the currently selected items in the tree
*
*/
@SuppressWarnings("restriction")
public class TreeMasterDetailComposite extends Composite implements IEditingDomainProvider {
/** The input. */
private final Object input;
/** The editing domain. */
private final EditingDomain editingDomain;
/** The tree viewer. */
private TreeViewer treeViewer;
/** The selection provider. */
private IMasterDetailSelectionProvider selectionProvider;
/** The vertical sash. */
private Sash verticalSash;
/** The detail scrollable composite. */
private Composite detailComposite;
/** Manager of the currently rendered ECPSWTView with caching. */
private DetailViewManager detailManager;
private final String selectNodeMessage = getString(getClass(), "selectNodeMessage"); //$NON-NLS-1$
private final String loadingMessage = getString(getClass(), "loadingMessage"); //$NON-NLS-1$
private Object lastRenderedObject;
private final TreeMasterDetailSWTCustomization customization;
/** the delay between a selection change and the start of the rendering. */
private final int renderDelay;
private ViewModelPropertiesUpdateCallback viewModelPropertiesUpdateCallback;
private final Set<DetailPanelRenderingFinishedCallback> detailPanelRenderingFinishedCallbacks = new LinkedHashSet<DetailPanelRenderingFinishedCallback>();
/**
* Default constructor.
*
* @param parent the parent composite
* @param style the style bits
* @param input the input for the tree
* @param customization the customization
* @param renderDelay the delay between a selection change and updating the detail
*/
/* package */ TreeMasterDetailComposite(Composite parent, int style, Object input,
TreeMasterDetailSWTCustomization customization, int renderDelay) {
super(parent, style);
this.input = input;
if (input instanceof Resource) {
editingDomain = AdapterFactoryEditingDomain.getEditingDomainFor(((Resource) input).getContents().get(0));
} else if (input instanceof RootObject) {
editingDomain = AdapterFactoryEditingDomain.getEditingDomainFor(RootObject.class.cast(input).getRoot());
} else {
editingDomain = AdapterFactoryEditingDomain.getEditingDomainFor(input);
}
this.renderDelay = renderDelay;
this.customization = customization;
renderControl(customization);
parent.addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent e) {
TreeMasterDetailComposite.this.dispose();
}
});
}
private Control renderControl(TreeMasterDetailSWTCustomization buildBehaviour) {
// Create the Form with two panels and a header
setLayout(new FormLayout());
// Create the Separator
verticalSash = createSash(this, buildBehaviour);
// Create the Tree
final Composite treeComposite = new Composite(this, SWT.NONE);
addTreeViewerLayoutData(treeComposite, verticalSash);
GridLayoutFactory.fillDefaults().numColumns(1).applyTo(treeComposite);
treeViewer = TreeViewerSWTFactory.createTreeViewer(treeComposite, input, customization);
selectionProvider = new MasterDetailSelectionProvider(treeViewer);
treeViewer.getControl().addFocusListener(
new MasterDetailFocusAdapter(selectionProvider, () -> detailManager.getDetailContainer()));
// Create detail composite
detailComposite = buildBehaviour.createDetailComposite(this);
addDetailCompositeLayoutData(detailComposite, verticalSash);
Composite detailParent = detailComposite;
if (detailParent instanceof ScrolledComposite) {
final Composite detailPanel = new Composite(detailParent, SWT.BORDER);
((ScrolledComposite) detailParent).setContent(detailPanel);
detailParent = detailPanel;
}
detailManager = new DetailViewManager(detailParent);
detailManager.setNoDetailMessage(selectNodeMessage);
detailManager.layoutDetailParent(detailParent);
/* enable optional delayed update mechanism */
IObservableValue<?> treeViewerSelectionObservable = ViewersObservables
.observeSingleSelection(treeViewer);
if (renderDelay > 0) {
treeViewerSelectionObservable = new DelayedObservableValue<>(renderDelay,
treeViewerSelectionObservable);
}
treeViewerSelectionObservable.addChangeListener(__ -> doUpdateDetailPanel(false));
final IObservableValue<?> observableToDispose = treeViewerSelectionObservable;
treeComposite.addDisposeListener(__ -> observableToDispose.dispose());
/* add key listener to switch focus on enter */
treeViewer.getTree().addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e) {
if (e.keyCode == SWT.CR || e.keyCode == SWT.LF) {
doUpdateDetailPanel(true);
}
}
});
/* add double click listener to switch focus on enter */
treeViewer.addDoubleClickListener(new IDoubleClickListener() {
@Override
public void doubleClick(DoubleClickEvent event) {
doUpdateDetailPanel(true);
}
});
updateDetailPanel(false);
return this;
}
private void setFocusToDetail() {
detailManager.setFocus();
}
private void addDetailCompositeLayoutData(Composite detailComposite, Sash verticalSash) {
final FormData detailFormData = new FormData();
detailFormData.left = new FormAttachment(verticalSash, 2);
detailFormData.top = new FormAttachment(0, 5);
detailFormData.bottom = new FormAttachment(100, -5);
detailFormData.right = new FormAttachment(100, -5);
detailComposite.setLayoutData(detailFormData);
}
private void addTreeViewerLayoutData(Composite treeComposite, Sash verticalSash) {
final FormData treeFormData = new FormData();
treeFormData.bottom = new FormAttachment(100, -5);
treeFormData.left = new FormAttachment(0, 5);
treeFormData.right = new FormAttachment(verticalSash, -2);
treeFormData.top = new FormAttachment(0, 5);
treeComposite.setLayoutData(treeFormData);
}
private Sash createSash(final Composite parent, TreeWidthProvider buildBehaviour) {
final Sash sash = new Sash(parent, SWT.VERTICAL);
// Make the left panel 300px wide and put it below the header
final FormData sashFormData = new FormData();
sashFormData.bottom = new FormAttachment(100, -5);
sashFormData.left = new FormAttachment(0, buildBehaviour.getInitialTreeWidth());
sashFormData.top = new FormAttachment(0, 5);
sash.setLayoutData(sashFormData);
// As soon as the sash is moved, layout the parent to reflect the changes
sash.addListener(SWT.Selection, new Listener() {
@Override
public void handleEvent(Event e) {
sash.setLocation(e.x, e.y);
final FormData sashFormData = new FormData();
sashFormData.bottom = new FormAttachment(100, -5);
sashFormData.left = new FormAttachment(0, e.x);
sashFormData.top = new FormAttachment(0, 5);
sash.setLayoutData(sashFormData);
parent.layout(true);
}
});
return sash;
}
// TODO JF this needs to be refactored, when used as the replacement for the treemasterdetail renderer.
// selection modification required as well as adjusting the loading properties
/**
* Updates the detail panel of the tree master detail.
*
* @param setFocusToDetail <code>true</code> if the focus should be moved to the detail panel
*
* @since 1.11
*/
public void updateDetailPanel(final boolean setFocusToDetail) {
// Create a new detail panel in the scrollable composite. Disposes any old panels.
// createDetailPanel();
// TODO create detail panel at the right location
final IStructuredSelection selection = (StructuredSelection) treeViewer.getSelection();
final Object selectedObject = getSelectedObject(selection);
detailManager.cacheCurrentDetail();
boolean asyncRendering = false;
ViewModelContext context = null;
if (selectedObject instanceof EObject) {
lastRenderedObject = selectedObject;
final EObject eObject = EObject.class.cast(selectedObject);
if (detailManager.isCached(eObject)) {
// It's ready to present (no async needed)
context = detailManager.activate(eObject).getViewModelContext();
updateScrolledComposite();
} else {
if (viewModelPropertiesUpdateCallback != null) {
viewModelPropertiesUpdateCallback.updateViewModelProperties(detailManager.getDetailProperties());
}
// Check, if the selected object would be rendered using a TreeMasterDetail. If so, render the provided
// detail view.
final VView view = detailManager.getDetailView(eObject);
if (view.getChildren().size() > 0 && view.getChildren().get(0) instanceof VTreeMasterDetail) {
// Yes, we need to render this node differently
final VView treeDetailView = VTreeMasterDetail.class.cast(view.getChildren().get(0))
.getDetailView();
// Even if the TMD composite is not configured as read-only honor the effective read-only
// configuration of the loaded detail view
treeDetailView.setReadonly(treeDetailView.isEffectivelyReadonly() || customization.isReadOnly());
context = ViewModelContextFactory.INSTANCE.createViewModelContext(treeDetailView, eObject);
detailManager.render(context, ECPSWTViewRenderer.INSTANCE::render);
} else {
// No, everything is fine
detailManager.setNoDetailMessage(loadingMessage);
asyncRendering = true;
Display.getDefault().asyncExec(new UpdateDetailRunnable(setFocusToDetail, eObject));
}
// After rendering the Forms, compute the size of the form. So the scroll container knows when to scroll
updateScrolledComposite();
}
} else {
renderEmptyDetailPanel();
}
/*
* Notify the callbacks that the rendering has been finished.
* In case of async rendering, the async process needs to notify the callbacks.
*/
if (!asyncRendering) {
for (final DetailPanelRenderingFinishedCallback callback : detailPanelRenderingFinishedCallbacks) {
callback.renderingFinished(context, selectedObject);
}
}
}
private Object getSelectedObject(IStructuredSelection selection) {
// Get the selected object, if it is an EObject, render the details using EMF Forms
Object selectedObject = selection != null ? selection.getFirstElement() : null;
if (customization.enableVerticalCopy() && selectedObject instanceof EObject && selection.size() > 1) {
boolean allOfSameType = true;
final EObject dummy = EcoreUtil.create(((EObject) selectedObject).eClass());
final Iterator<?> iterator = selection.iterator();
final Set<EObject> selectedEObjects = new LinkedHashSet<EObject>();
while (iterator.hasNext()) {
final EObject eObject = (EObject) iterator.next();
allOfSameType &= eObject.eClass() == dummy.eClass();
if (allOfSameType) {
for (final EAttribute attribute : dummy.eClass().getEAllAttributes()) {
if (eObject == selectedObject) {
dummy.eSet(attribute, eObject.eGet(attribute));
} else if (dummy.eGet(attribute) != null
&& !dummy.eGet(attribute).equals(eObject.eGet(attribute))) {
dummy.eUnset(attribute);
}
}
selectedEObjects.add(eObject);
} else {
break;
}
}
if (allOfSameType) {
selectedObject = dummy;
dummy.eAdapters().add(new MultiEditAdapter(selectedEObjects, dummy));
}
}
return selectedObject;
}
private void updateScrolledComposite() {
if (ScrolledComposite.class.isInstance(detailComposite)) {
ScrolledComposite.class.cast(detailComposite)
.setMinSize(detailManager.getDetailContainer().computeSize(SWT.DEFAULT, SWT.DEFAULT));
}
}
private void renderEmptyDetailPanel() {
lastRenderedObject = null;
detailManager.cacheCurrentDetail();
updateScrolledComposite();
}
@Override
public void dispose() {
detailManager.dispose();
customization.dispose();
super.dispose();
}
/**
* Gets the current selection.
*
* @return the current selection
*/
public Object getCurrentSelection() {
if (!(treeViewer.getSelection() instanceof StructuredSelection)) {
return null;
}
return ((StructuredSelection) treeViewer.getSelection()).getFirstElement();
}
/**
* Sets the selection.
*
* @param structuredSelection the new selection
* @since 1.9
*/
public void setSelection(ISelection structuredSelection) {
treeViewer.setSelection(structuredSelection);
}
/**
* Gets the tree viewer.
*
* @return the tree viewer (which is a selection provider)
*
* @deprecated Use the {@link #getMasterDetailSelectionProvider() master-detail selection provider}, instead},
* or {@link #refresh()} to force a refresh of the tree, or {@link #selectAndReveal(Object)}
* to select and reveal some object in my tree
* @see #getMasterDetailSelectionProvider()
*/
@Deprecated
public TreeViewer getSelectionProvider() {
return treeViewer;
}
/**
* Get the master/detail-aware selection provider.
*
* @return a selection provider that is aware of the user's focus on either the
* master tree or the detail view
* @since 1.21
*/
public ISelectionProvider getMasterDetailSelectionProvider() {
return selectionProvider;
}
/**
* Request a refresh of my tree.
*
* @since 1.22
*/
public void refresh() {
if (treeViewer != null) {
treeViewer.refresh();
}
}
/**
* Select and reveal a {@code selection} in my tree. If the argument is an {@link UniqueSetting},
* then the {@linkplain UniqueSetting#getEObject() owner} of the setting will be revealed and the
* control that edits the {@linkplain UniqueSetting#getEStructuralFeature() setting} will be
* revealed and focused (if possible) in the object's detail view.
*
* @param selection the objet to select and reveal
* @return {@code true} if the {@code selection} was revealed; {@code false}, otherwise, including
* the case where the nearest parent object up the tree was revealed instead
* @since 1.22
*/
public boolean selectAndReveal(final Object selection) {
boolean result = false;
Object toReveal = selection;
final EStructuralFeature feature;
if (selection instanceof UniqueSetting) {
final UniqueSetting setting = (UniqueSetting) selection;
toReveal = setting.getEObject();
feature = setting.getEStructuralFeature();
} else if (selection instanceof EStructuralFeature.Setting) {
final EStructuralFeature.Setting setting = (EStructuralFeature.Setting) selection;
toReveal = setting.getEObject();
feature = setting.getEStructuralFeature();
} else {
feature = null;
}
if (feature != null) {
final CompletableFuture<ECPSWTView> renderedDetail = new CompletableFuture<>();
final DetailPanelRenderingFinishedCallback detailReady = __ -> renderedDetail
.complete(detailManager.getCurrentDetail());
registerDetailPanelRenderingFinishedCallback(detailReady);
final EObject owner = (EObject) toReveal;
result = selectAndRevealInTree(owner);
if (result) {
renderedDetail.whenComplete((view, e) -> {
unregisterDetailPanelRenderingFinishedCallback(detailReady);
revealInDetail(view, owner, feature);
});
} else {
// Won't need the call-back so remove it now
renderedDetail.cancel(false);
unregisterDetailPanelRenderingFinishedCallback(detailReady);
}
// Do we already have the detail?
final ECPSWTView currentDetail = detailManager.getCurrentDetail();
if (currentDetail != null && currentDetail.getViewModelContext().getDomainModel() == owner) {
// There won't be an asynchronous rendering to wait for
renderedDetail.complete(currentDetail);
}
} else {
result = selectAndRevealInTree(toReveal);
}
return result;
}
private boolean selectAndRevealInTree(final Object selection) {
if (treeViewer == null) {
return false;
}
boolean result = false;
// Try to reveal the 'selection' in the tree. If it isn't in the
// tree, then search up the content provider to find the nearest
// object that can be revealed and select that, or give up
for (Object objectToReveal = selection; objectToReveal != null;) {
treeViewer.reveal(objectToReveal);
if (treeViewer.testFindItem(objectToReveal) != null) {
// Select it and we're done
treeViewer.setSelection(new StructuredSelection(objectToReveal));
result = objectToReveal == selection;
break;
}
// Look up the content tree for an object to reveal
objectToReveal = ((ITreeContentProvider) treeViewer.getContentProvider()).getParent(objectToReveal);
}
return result;
}
private void revealInDetail(ECPSWTView detail, EObject object, EStructuralFeature feature) {
final ViewModelContext context = detail.getViewModelContext();
if (!context.hasService(EMFFormsRevealService.class)) {
// Nothing to do
return;
}
final EMFFormsRevealService reveal = context.getService(EMFFormsRevealService.class);
reveal.reveal(object, feature);
}
/**
* Gets the editing domain.
*
* @return the editing domain
*/
@Override
public EditingDomain getEditingDomain() {
return editingDomain;
}
/**
* Allows to set a different input for the treeviewer.
*
* @param input the new input
*/
public void setInput(Object input) {
treeViewer.setInput(input);
}
/**
* Allows to override the default cache implementation by the provided one.
*
* @param cache The {@link TreeMasterDetailCache} to use.
* @since 1.9
*
* @deprecated As of 1.22, use the {@link #setCache(DetailViewCache)} API, instead
*/
@Deprecated
public void setCache(TreeMasterDetailCache cache) {
setCache((DetailViewCache) cache);
}
/**
* Override the default cache implementation.
*
* @param cache the {@link DetailViewCache} to use, or {@code null} to use no cache
* @since 1.22
*/
public void setCache(DetailViewCache cache) {
detailManager.setCache(cache);
}
private void doUpdateDetailPanel(boolean setFocusToDetail) {
if (lastRenderedObject == getCurrentSelection()) {
if (setFocusToDetail) {
setFocusToDetail();
}
/*
* possible when e.g. a double click or enter has forced an instant rendering and the delay update kicks in.
*/
return;
}
updateDetailPanel(setFocusToDetail);
}
/**
* Returns whether I am read-only.
*
* @return <code>true</code> if read-only
* @since 1.22
* @see TreeMasterDetailSWTBuilder#customizeReadOnly(boolean)
*/
public boolean isReadOnly() {
return customization.isReadOnly();
}
/**
* Adapter which listens to changes and delegates the notification to other EObjects.
*
* @author Eugen Neufeld
*
*/
private final class MultiEditAdapter extends AdapterImpl {
private final Set<EObject> selectedEObjects;
private final EObject dummy;
private MultiEditAdapter(Set<EObject> selectedEObjects, EObject dummy) {
this.selectedEObjects = selectedEObjects;
this.dummy = dummy;
}
@Override
public void notifyChanged(Notification notification) {
if (dummy.eClass().getEAllAttributes().contains(notification.getFeature())) {
final CompoundCommand cc = new CompoundCommand();
for (final EObject selected : selectedEObjects) {
Command command = null;
switch (notification.getEventType()) {
case Notification.SET:
command = SetCommand.create(editingDomain, selected,
notification.getFeature(), notification.getNewValue());
break;
case Notification.UNSET:
command = SetCommand.create(editingDomain, selected,
notification.getFeature(), SetCommand.UNSET_VALUE);
break;
case Notification.ADD:
case Notification.ADD_MANY:
command = AddCommand.create(editingDomain, selected,
notification.getFeature(), notification.getNewValue());
break;
case Notification.REMOVE:
case Notification.REMOVE_MANY:
command = DeleteCommand.create(editingDomain, notification.getOldValue());
break;
default:
continue;
}
cc.append(command);
}
editingDomain.getCommandStack().execute(cc);
}
}
}
/**
* Runnable which updates the detail panel.
*/
private final class UpdateDetailRunnable implements Runnable {
private final boolean setFocusToDetail;
private final EObject eObject;
UpdateDetailRunnable(boolean setFocusToDetail, EObject eObject) {
super();
this.setFocusToDetail = setFocusToDetail;
this.eObject = eObject;
}
@Override
public void run() {
if (detailManager.isDisposed()) {
// We've been disposed. Nothing to do
return;
}
if (viewModelPropertiesUpdateCallback != null) {
viewModelPropertiesUpdateCallback.updateViewModelProperties(detailManager.getDetailProperties());
}
final VView view = detailManager.getDetailView(eObject);
// Even if the TMD is not configured as read-only honor the effective read-only
// configuration of the loaded view
view.setReadonly(view.isEffectivelyReadonly() || customization.isReadOnly());
final ViewModelContext modelContext = ViewModelContextFactory.INSTANCE
.createViewModelContext(
view, eObject, customization.getViewModelServices(view, eObject));
detailManager.setNoDetailMessage(selectNodeMessage);
if (detailManager.isDisposed()) {
return;
}
detailManager.render(modelContext, ECPSWTViewRenderer.INSTANCE::render);
updateScrolledComposite();
if (setFocusToDetail) {
setFocusToDetail();
}
// notify callbacks that the rendering was finished
for (final DetailPanelRenderingFinishedCallback callback : detailPanelRenderingFinishedCallbacks) {
callback.renderingFinished(modelContext, eObject);
}
}
}
/**
* Adds a {@link ViewModelPropertiesUpdateCallback}.
*
* @param viewModelPropertiesUpdateCallback the callback
* @since 1.11
*/
public void addViewModelPropertiesUpdateCallback(
ViewModelPropertiesUpdateCallback viewModelPropertiesUpdateCallback) {
this.viewModelPropertiesUpdateCallback = viewModelPropertiesUpdateCallback;
}
/**
* Register a callback that is notified whenever the rendering of a detail panel is finished.
*
* @param detailPanelRenderingFinishedCallback the callback
* @return <code>true</code> if the callback has been added, <code>false</code> if it was already registered
* @since 1.13
*/
public boolean registerDetailPanelRenderingFinishedCallback(
DetailPanelRenderingFinishedCallback detailPanelRenderingFinishedCallback) {
return detailPanelRenderingFinishedCallbacks.add(detailPanelRenderingFinishedCallback);
}
/**
* Register a callback that is notified whenever the rendering of a detail panel is finished.
*
* @param detailPanelRenderingFinishedCallback the callback
* @return <code>true</code> if the callback has been removed, <code>false</code> if it was not registered
* @since 1.13
*/
public boolean unregisterDetailPanelRenderingFinishedCallback(
DetailPanelRenderingFinishedCallback detailPanelRenderingFinishedCallback) {
return detailPanelRenderingFinishedCallbacks.remove(detailPanelRenderingFinishedCallback);
}
}