blob: c2f012722a1c8e5364b7c60cbaeeaac4435d5ad7 [file] [log] [blame]
/******************************************************************************
* Copyright (c) 2003, 2008 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
****************************************************************************/
package org.eclipse.gmf.runtime.diagram.ui.properties.sections;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.operations.IOperationHistory;
import org.eclipse.core.commands.operations.OperationHistoryFactory;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.ecore.EAnnotation;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.emf.edit.domain.IEditingDomainProvider;
import org.eclipse.emf.transaction.DemultiplexingListener;
import org.eclipse.emf.transaction.NotificationFilter;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.emf.transaction.util.TransactionUtil;
import org.eclipse.emf.workspace.util.WorkspaceSynchronizer;
import org.eclipse.gmf.runtime.common.core.command.CommandResult;
import org.eclipse.gmf.runtime.common.core.command.CompositeCommand;
import org.eclipse.gmf.runtime.common.core.command.ICommand;
import org.eclipse.gmf.runtime.common.core.command.UnexecutableCommand;
import org.eclipse.gmf.runtime.common.core.util.Log;
import org.eclipse.gmf.runtime.common.core.util.Trace;
import org.eclipse.gmf.runtime.common.ui.services.properties.PropertiesServiceAdapterFactory;
import org.eclipse.gmf.runtime.diagram.ui.editparts.GroupEditPart;
import org.eclipse.gmf.runtime.diagram.ui.parts.DiagramEditor;
import org.eclipse.gmf.runtime.diagram.ui.properties.internal.DiagramPropertiesDebugOptions;
import org.eclipse.gmf.runtime.diagram.ui.properties.internal.DiagramPropertiesPlugin;
import org.eclipse.gmf.runtime.diagram.ui.properties.internal.DiagramPropertiesStatusCodes;
import org.eclipse.gmf.runtime.diagram.ui.properties.internal.l10n.DiagramUIPropertiesMessages;
import org.eclipse.gmf.runtime.diagram.ui.properties.util.SectionUpdateRequestCollapser;
import org.eclipse.gmf.runtime.diagram.ui.properties.views.IReadOnlyDiagramPropertySheetPageContributor;
import org.eclipse.gmf.runtime.diagram.ui.properties.views.PropertiesBrowserPage;
import org.eclipse.gmf.runtime.emf.commands.core.command.AbstractTransactionalCommand;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.views.properties.tabbed.AbstractPropertySection;
import org.eclipse.ui.views.properties.tabbed.ITabbedPropertySheetPageContributor;
import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetPage;
/**
* An abstract implementation of a section in a tab in the tabbed property sheet
* page for modeler.
*
* @author Anthony Hunter <a
* href="mailto:anthonyh@ca.ibm.com">anthonyh@ca.ibm.com </a>
*/
public abstract class AbstractModelerPropertySection
extends AbstractPropertySection {
private TabbedPropertySheetPage tabbedPropertySheetPage;
/**
* model event listener
*/
protected DemultiplexingListener eventListener = new DemultiplexingListener(getFilter()) {
protected void handleNotification(TransactionalEditingDomain domain,
Notification notification) {
update(domain, notification);
}
};
// properties provider to obtain properties of the objects on the list
protected static final PropertiesServiceAdapterFactory propertiesProvider = new PropertiesServiceAdapterFactory();
private boolean bIsCommandInProgress = false;
/** value changed string */
static protected String VALUE_CHANGED_STRING = DiagramUIPropertiesMessages.AbstractPropertySection_UndoIntervalPropertyString;
/** object currently selected on either a diagram or a ME - a view */
protected List input;
/** eObject should gradually replace EElement */
protected EObject eObject;
private List eObjectList = new ArrayList();
/**
* a flag indicating if this property section got disposed
*/
protected boolean disposed = false;
private TransactionalEditingDomain editingDomain = null;
/* (non-Javadoc)
* @see org.eclipse.ui.views.properties.tabbed.ISection#setInput(org.eclipse.ui.IWorkbenchPart, org.eclipse.jface.viewers.ISelection)
*/
public void setInput(IWorkbenchPart part, ISelection selection) {
super.setInput(part, selection);
if (!(selection instanceof IStructuredSelection)
|| selection.equals(getInput()))
return;
/*
* Update editing domain
*/
IEditingDomainProvider provider = (IEditingDomainProvider) part
.getAdapter(IEditingDomainProvider.class);
if (provider != null) {
EditingDomain theEditingDomain = provider.getEditingDomain();
if (theEditingDomain instanceof TransactionalEditingDomain) {
setEditingDomain((TransactionalEditingDomain) theEditingDomain);
}
}
input = new ArrayList();
eObjectList = new ArrayList();
for (Iterator it = ((IStructuredSelection) selection).iterator(); it
.hasNext();) {
Object next = it.next();
if (digIntoGroups() && next instanceof GroupEditPart) {
for (Iterator iter = ((GroupEditPart) next)
.getShapeChildren().iterator(); iter.hasNext();) {
Object childEP = iter.next();
// unwrap down to EObject and add to the eObjects list
if (addToEObjectList(childEP)) {
input.add(childEP);
}
continue;
}
}
// unwrap down to EObject and add to the eObjects list
if (addToEObjectList(next)) {
input.add(next);
}
}
// RATLC000524513 Sometimes there is no eobject. For example if user
// creates a constraint,
// on a class there will be a connection shown on the diagram which
// connects the constraint
// with the class. The user can select this connection, even though it
// does not have an
// underlying eobject. Comments are similar. In this case we show only
// the appearanced tab.
if (false == eObjectList.isEmpty())
setEObject((EObject) eObjectList.get(0));
}
/**
* Override to return true to have this property section work on the shapes
* in a <code>GroupEditPart</code> as if the shapes were multi-selected.
*
* @return true if this property section is to dig into the shapes of groups
*/
protected boolean digIntoGroups() {
return false;
}
/**
* Add next object in the selection to the list of EObjects if this object
* could be adapted to an <code>EObject</code>
* @param object the object to add
* @return - true if the object is added, false otherwise
*/
protected boolean addToEObjectList(Object object) {
EObject adapted = unwrap(object);
if (adapted != null){
getEObjectList().add(adapted);
return true;
}
return false;
}
/**
* Unwarp the ME or diagram object down to the underlaying UML element
*
* @param object -
* object from a diagram or ME
* @return - underlaying UML element
*/
protected EObject unwrap(Object object) {
return adapt(object);
}
/**
* Adapt the object to an EObject - if possible
*
* @param object
* object from a diagram or ME
* @return EObject
*/
protected EObject adapt(Object object) {
if (object instanceof IAdaptable) {
return (EObject) ((IAdaptable) object).getAdapter(EObject.class);
}
return null;
}
/**
* Determines if the page is displaying properties for this element
*
* @param notification
* The notification
* @param element
* The element to be tested
* @return 'true' if the page is displaying properties for this element
*/
protected boolean isCurrentSelection(Notification notification,
EObject element) {
if (element == null)
return false;
if (eObjectList.contains(element))
return true;
if (eObjectList.size() > 0) {
EObject eventObject = element;
// check for annotations
if (element instanceof EAnnotation) {
eventObject = element.eContainer();
} else {
EObject container = element.eContainer();
if (container != null && container instanceof EAnnotation) {
eventObject = container.eContainer();
}
}
if (notification.getFeature() == EcorePackage.Literals.EANNOTATION__EMODEL_ELEMENT) {
Object oldObj = notification.getOldValue();
Object newObj = notification.getNewValue();
if (oldObj instanceof EObject && newObj == null) {
// the annotation has been removed - check the old owner
eventObject = (EObject) oldObj;
}
}
if (eventObject != element) {
return eObjectList.contains(eventObject);
}
}
return false;
}
/**
* A utility method allows execute a piece of code wrapping it in the read
* call to the model.
*
* @param code -
* Runnable code to execute
*/
protected void executeAsReadAction(final Runnable code) {
try {
TransactionalEditingDomain domain = getEditingDomain();
if (domain != null) {
getEditingDomain().runExclusive(code);
} else {
// if we are not in a transactional editing domain context, then
// there is no notion of exclusivity. Just run
code.run();
}
} catch (InterruptedException e) {
Trace.catching(DiagramPropertiesPlugin.getDefault(),
DiagramPropertiesDebugOptions.EXCEPTIONS_CATCHING, getClass(),
"executeAsReadAction", e); //$NON-NLS-1$
}
}
/**
* A utility method allows execute a list of commands by wrapping them\ in a
* composite command.
*
* @param commands -
* List of commands to execute
*/
protected CommandResult executeAsCompositeCommand(String actionName,
List commands) {
if (true == bIsCommandInProgress)
return null;
bIsCommandInProgress = true;
CompositeCommand command = new CompositeCommand(actionName, commands);
IOperationHistory history = OperationHistoryFactory.getOperationHistory();
try {
IStatus status = history.execute(command,
new NullProgressMonitor(), null);
if (status.getCode() == DiagramPropertiesStatusCodes.CANCELLED
|| status.getSeverity() == IStatus.CANCEL
|| status.getSeverity() == IStatus.ERROR) {
refresh();
}
} catch (ExecutionException e) {
Trace.catching(DiagramPropertiesPlugin.getDefault(),
DiagramPropertiesDebugOptions.EXCEPTIONS_CATCHING, getClass(),
"executeAsCompositeCommand", e); //$NON-NLS-1$
Log.error(DiagramPropertiesPlugin.getDefault(),
DiagramPropertiesStatusCodes.IGNORED_EXCEPTION_WARNING, e
.getLocalizedMessage(), e);
}
bIsCommandInProgress = false;
return command.getCommandResult();
}
/**
* Returns currently selected view object
*
* @return Returns the input.
*/
public List getInput() {
return input;
}
/* (non-Javadoc)
* @see org.eclipse.ui.views.properties.tabbed.ISection#aboutToBeHidden()
*/
public void aboutToBeHidden() {
super.aboutToBeHidden();
TransactionalEditingDomain theEditingDomain = getEditingDomain();
if (theEditingDomain != null) {
theEditingDomain.removeResourceSetListener(getEventListener());
}
}
/* (non-Javadoc)
* @see org.eclipse.ui.views.properties.tabbed.ISection#aboutToBeShown()
*/
public void aboutToBeShown() {
super.aboutToBeShown();
TransactionalEditingDomain theEditingDomain = getEditingDomain();
if (theEditingDomain != null) {
theEditingDomain.addResourceSetListener(getEventListener());
}
}
/* (non-Javadoc)
* @see org.eclipse.ui.views.properties.tabbed.ISection#dispose()
*/
public void dispose() {
super.dispose();
/*
* if (getUpdateRequestCollapser() != null) {
* getUpdateRequestCollapser().stop(); updateRequestCollapser = null; }
*/
if (eObjectList != null) {
eObjectList.clear();
}
eObject = null;
disposed = true;
}
/**
* Returns currently selected view object
*
* @return Returns the input.
*/
protected Object getPrimarySelection() {
return (getInput() != null && !getInput().isEmpty() ? getInput().get(0)
: null);
}
/**
* @return Returns the eObject.
*/
protected EObject getEObject() {
return eObject;
}
/**
* @param object
* The eObject to set.
*/
protected void setEObject(EObject object) {
this.eObject = object;
}
/**
* Subclasses overriding this method should remember to override
* {@link #update(TransactionalEditingDomain, Notification)} as required.
* The default implementation of
* {@link #update(TransactionalEditingDomain, Notification)} will only
* update if the notifier is an <code>EObject</code>.
*
* @return the filter for events used by my <code>eventListener</code>.
*/
public NotificationFilter getFilter() {
return NotificationFilter.createEventTypeFilter(Notification.SET).or(
NotificationFilter.createEventTypeFilter(Notification.UNSET)).and(
NotificationFilter.createNotifierTypeFilter(EObject.class));
}
/**
* Update if nessesary, upon receiving the model event. This event will only
* be processed when the reciever is visible (the default behavior is not to
* listen to the model events while not showing). Therefore it is safe to
* refresh the UI. Sublclasses, which will choose to override event
* listening behavior should take into account that the model events are
* sent all the time - regardless of the section visibility. Thus special
* care should be taken by the section that will choose to listen to such
* events all the time. Also, the default implementation of this method
* synchronizes on the GUI thread. The subclasses that overwrite it should
* do the same if they perform any GUI work (because events may be sent from
* a non-GUI thread).
*
* @see #aboutToBeShown()
* @see #aboutToBeHidden()
*
* @param notification notification object
* @param element element that has changed
*/
public void update(final Notification notification, final EObject element) {
if (!isDisposed() && isCurrentSelection(notification, element)
&& !isNotifierDeleted(notification)) {
postUpdateRequest(new Runnable() {
public void run() {
if (!isDisposed()
&& isCurrentSelection(notification, element)
&& !isNotifierDeleted(notification))
refresh();
}
});
}
}
/**
* Updates me if the notifier is an <code>EObject</code> by calling
* {@link #update(Notification, EObject)}. Does nothing otherwise.
* Subclasses should override this method if they need to update based on
* non-EObject notifiers.
*
* @param domain
* the editing domain
* @param notification
* the event notification
*/
protected void update(TransactionalEditingDomain domain, Notification notification) {
Object notifier = notification.getNotifier();
if (notifier instanceof EObject) {
update(notification, (EObject) notifier);
}
}
/**
* Returns whether or not the notifier for a particular notification has been
* deleted from its parent.
*
* This is a fix for RATLC00535181. What happens is that during deletion of
* an element from the diagram, the element first deletes related elements
* which causes a modification of the element itself. When the modification occurs
* the event handling mechanism posts a request to the UI queue to refresh the UI.
* A race condition occurs where by the time the posted request runs, the element
* in question may or may not have already been deleted from its container. If
* the element has been deleted from its container, we should not refresh the
* property section.
*
* @param notification
* @return <code>true</code> if notification has been deleted from its parent, <code>false</code> otherwise
*/
protected boolean isNotifierDeleted(Notification notification) {
if (!(notification.getNotifier() instanceof EObject)) {
return false;
}
EObject obj = (EObject)notification.getNotifier();
return obj.eResource() == null;
}
/**
* Use requset collapser to post update requests.
*
* @param updateRequest -
* runnable update code
*/
protected void postUpdateRequest(Runnable updateRequest) {
getUpdateRequestCollapser().postRequest(this, updateRequest);
}
/* (non-Javadoc)
* @see org.eclipse.gmf.runtime.emf.core.edit.IDemuxedMListener#handleElementModifiedEvent(org.eclipse.emf.common.notify.Notification, org.eclipse.emf.ecore.EObject)
*/
public void handleElementModifiedEvent(final Notification notification,
final EObject element) {
update(notification, element);
}
/**
* @return Returns the eObjectList.
*/
protected List getEObjectList() {
return eObjectList;
}
/**
* @return Returns the disposed.
*/
protected boolean isDisposed() {
return disposed;
}
/**
* @return Returns the eventListener.
*/
protected DemultiplexingListener getEventListener() {
return eventListener;
}
/**
* @return Returns a command
*/
protected ICommand createCommand(String name, Resource res,
final Runnable runnable) {
return createCommandInternal(name, res, runnable);
}
/**
* @return Returns a command
*/
protected ICommand createCommand(String name, EObject res,
final Runnable runnable) {
return createCommandInternal(name, res.eResource(), runnable);
}
/**
* @return Returns a command
*/
private ICommand createCommandInternal(String name, Resource res,
final Runnable runnable) {
if (res == null) {
return UnexecutableCommand.INSTANCE;
}
ICommand command = new AbstractTransactionalCommand(getEditingDomain(), name,
Collections.singletonList(WorkspaceSynchronizer.getFile(res))) {
protected CommandResult doExecuteWithResult(
IProgressMonitor monitor, IAdaptable info)
throws ExecutionException {
runnable.run();
return CommandResult.newOKCommandResult();
}
};
return command;
}
/**
* Gets the editing domain from my EObject input.
*
* @return my editing domain
*/
protected TransactionalEditingDomain getEditingDomain() {
if (editingDomain == null) {
EObject eObjectInput = getEObject();
if (eObjectInput != null) {
editingDomain = TransactionUtil.getEditingDomain(eObjectInput);
} else if (!getEObjectList().isEmpty()) {
editingDomain = TransactionUtil.getEditingDomain(getEObjectList().get(0));
}
}
return editingDomain;
}
/**
* Sets the editingDomain.
* @param editingDomain The editingDomain to set.
*/
protected void setEditingDomain(TransactionalEditingDomain editingDomain) {
this.editingDomain = editingDomain;
}
/* (non-Javadoc)
* @see org.eclipse.ui.views.properties.tabbed.ISection#createControls(org.eclipse.swt.widgets.Composite, org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetPage)
*/
public void createControls(Composite parent,
TabbedPropertySheetPage aTabbedPropertySheetPage) {
super.createControls(parent, aTabbedPropertySheetPage);
this.tabbedPropertySheetPage = aTabbedPropertySheetPage;
}
/**
* Determine if the property sheet page contributor is read only.
*
* Topic and Browse diagrams have properties that are read only, even
* theough the selection may be modifiable.
*
* @return <code>true</code> if the contributor is read only.
*/
protected boolean isReadOnly() {
ITabbedPropertySheetPageContributor contributor = null;
if (tabbedPropertySheetPage instanceof PropertiesBrowserPage) {
contributor = ((PropertiesBrowserPage) tabbedPropertySheetPage).getContributor();
} else if (tabbedPropertySheetPage instanceof IAdaptable) {
contributor = (ITabbedPropertySheetPageContributor)
((IAdaptable)tabbedPropertySheetPage).getAdapter(ITabbedPropertySheetPageContributor.class);
}
if (contributor != null &&
(contributor instanceof IReadOnlyDiagramPropertySheetPageContributor
|| (contributor instanceof DiagramEditor && !((DiagramEditor) contributor).isWritable()))) {
return true;
}
return false;
}
/**
* Get the standard label width when labels for sections line up on the left
* hand side of the composite. We line up to a fixed position, but if a
* string is wider than the fixed position, then we use that widest string.
*
* @param parent
* The parent composite used to create a GC.
* @param labels
* The list of labels.
* @return the standard label width.
*/
protected int getStandardLabelWidth(Composite parent, String[] labels) {
int standardLabelWidth = STANDARD_LABEL_WIDTH;
GC gc = new GC(parent);
int indent = gc.textExtent("XXX").x; //$NON-NLS-1$
for (int i = 0; i < labels.length; i++) {
int width = gc.textExtent(labels[i]).x;
if (width + indent > standardLabelWidth) {
standardLabelWidth = width + indent;
}
}
gc.dispose();
return standardLabelWidth;
}
/**
* @return Returns the updateRequestCollapser.
*/
protected SectionUpdateRequestCollapser getUpdateRequestCollapser() {
return DiagramPropertiesPlugin.getDefault()
.getUpdateRequestCollapser();
}
}