/*******************************************************************************
* Copyright (c) 2000, 2016 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.gef.mvc.fx.ui.properties;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.operations.ICompositeOperation;
import org.eclipse.core.commands.operations.IOperationHistory;
import org.eclipse.core.commands.operations.IOperationHistoryListener;
import org.eclipse.core.commands.operations.IUndoContext;
import org.eclipse.core.commands.operations.OperationHistoryEvent;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.gef.mvc.fx.operations.ForwardUndoCompositeOperation;
import org.eclipse.gef.mvc.fx.operations.ITransactionalOperation;
import org.eclipse.gef.mvc.fx.operations.ReverseUndoCompositeOperation;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.views.properties.IPropertySource;
import org.eclipse.ui.views.properties.PropertySheetEntry;
/**
* <p>
* UndoablePropertySheetEntry provides undo support for changes made to
* IPropertySources by the PropertySheetViewer. Clients can construct a
* {@link org.eclipse.ui.views.properties.PropertySheetPage} and use this class
* as the root entry. All changes made to property sources displayed on that
* page will be done using the provided {@link IOperationHistory}.
* <p>
* <b>NOTE:</b> If you intend to use an IPropertySourceProvider for a
* PropertySheetPage whose root entry is an instance of of
* UndoablePropertySheetEntry, you should set the IPropertySourceProvider on
* that root entry, rather than the PropertySheetPage.
*/
public class UndoablePropertySheetEntry extends PropertySheetEntry {
private IWorkbenchPart workbenchPart;
private IOperationHistory operationHistory;
private IOperationHistoryListener operationHistoryListener;
private IUndoContext undoContext;
/**
* Constructs a non-root, i.e. child entry, which may obtain the
* {@link IOperationHistory} from its parent.
*
*/
private UndoablePropertySheetEntry() {
}
/**
* Constructs a new root entry.
*
* @param workbenchPart
* The {@link IWorkbenchPart} to adapt for an
* {@link IPropertySource}, in case no values are provided.
* @param operationHistory
* The {@link IOperationHistory} to use.
* @param undoContext
* The {@link IUndoContext} to use.
*/
public UndoablePropertySheetEntry(IWorkbenchPart workbenchPart,
IOperationHistory operationHistory, IUndoContext undoContext) {
this.workbenchPart = workbenchPart;
this.operationHistory = operationHistory;
this.undoContext = undoContext;
this.operationHistoryListener = new IOperationHistoryListener() {
@Override
public void historyNotification(OperationHistoryEvent event) {
refreshFromRoot();
}
};
this.operationHistory
.addOperationHistoryListener(operationHistoryListener);
}
/**
* @see org.eclipse.ui.views.properties.PropertySheetEntry#createChildEntry()
*/
@Override
protected PropertySheetEntry createChildEntry() {
return new UndoablePropertySheetEntry();
}
/**
* @see org.eclipse.ui.views.properties.IPropertySheetEntry#dispose()
*/
@Override
public void dispose() {
if (operationHistory != null) {
operationHistory
.removeOperationHistoryListener(operationHistoryListener);
}
super.dispose();
}
/**
* Returns the {@link IOperationHistory} that is used by this entry. It is
* obtained from the parent in case the entry is not a root entry.
*
* @return the {@link IOperationHistory} to be used.
*/
protected IOperationHistory getOperationHistory() {
// only the root has, and is listening too, the IOperationHistory
if (getParent() != null) {
return ((UndoablePropertySheetEntry) getParent())
.getOperationHistory();
}
return operationHistory;
}
@Override
protected IPropertySource getPropertySource(Object object) {
if (object instanceof IPropertySource) {
return (IPropertySource) object;
}
return super.getPropertySource(object);
}
/**
* @see org.eclipse.ui.views.properties.IPropertySheetEntry#resetPropertyValue()
*/
@Override
public void resetPropertyValue() {
ICompositeOperation cc = new ReverseUndoCompositeOperation("");
if (getParent() == null) {
// root does not have a default value
return;
}
// Use our parent's values to reset our values.
boolean change = false;
Object[] objects = getParent().getValues();
for (int i = 0; i < objects.length; i++) {
IPropertySource source = getPropertySource(objects[i]);
if (source.isPropertySet(getDescriptor().getId())) {
SetPropertyValueOperation restoreCmd = new SetPropertyValueOperation(
getDescriptor().getDisplayName(), source,
getDescriptor().getId(),
SetPropertyValueOperation.DEFAULT_VALUE);
cc.add(restoreCmd);
change = true;
}
}
if (change) {
try {
getOperationHistory().execute(cc, new NullProgressMonitor(),
null);
} catch (ExecutionException e) {
e.printStackTrace();
}
refreshFromRoot();
}
}
@Override
public void setValues(Object[] objects) {
if (objects == null || objects.length == 0) {
if (workbenchPart != null) {
IPropertySource source = (IPropertySource) workbenchPart
.getAdapter(IPropertySource.class);
if (source != null) {
// wrap source itself; it will be unwrapped by super
// implementation and then passed to
// #getPropertySource(Object), which will return it, so the
// editable value can be retrieved from it
objects = new Object[] { source };
}
}
}
super.setValues(objects);
}
/**
* @see PropertySheetEntry#valueChanged(PropertySheetEntry)
*/
@Override
protected void valueChanged(PropertySheetEntry child) {
// the update of values into a command and pass that to our parent (or
// execute it on the operation history, if we have no parent)
ForwardUndoCompositeOperation compositeOperation = new ForwardUndoCompositeOperation(
"Update child property values"); // TODO: externalize string
for (int i = 0; i < getValues().length; i++) {
SetPropertyValueOperation setOperation = new SetPropertyValueOperation(
child.getDisplayName(), getPropertySource(getValues()[i]),
((UndoablePropertySheetEntry) child).getDescriptor()
.getId(),
child.getValues()[i]);
compositeOperation.add(setOperation);
}
valueChanged((UndoablePropertySheetEntry) child,
compositeOperation.unwrap(true));
}
/**
* Update parent entry about change, being encapsulated into the given
* operation.
*
* @param child
* The child entry that changed.
* @param operation
* An operation encapsulating the change.
*/
protected void valueChanged(UndoablePropertySheetEntry child,
ITransactionalOperation operation) {
// inform our parent
if (getParent() != null) {
((UndoablePropertySheetEntry) getParent()).valueChanged(this,
operation);
} else {
// I am the root entry
try {
operation.addContext(undoContext);
operationHistory.execute(operation, new NullProgressMonitor(),
null);
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}