/*******************************************************************************
* 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 java.text.MessageFormat;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.operations.AbstractOperation;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.gef.mvc.fx.operations.ITransactionalOperation;
import org.eclipse.gef.mvc.fx.ui.Messages;
import org.eclipse.ui.views.properties.IPropertyDescriptor;
import org.eclipse.ui.views.properties.IPropertySource;
import org.eclipse.ui.views.properties.IPropertySource2;
/**
* An {@link ITransactionalOperation} used to set or reset the value of a
* property.
*
* @author pshah
* @author anyssen
*
*/
public class SetPropertyValueOperation extends AbstractOperation
implements ITransactionalOperation {
/**
* Value constant to indicate that the property is to be reset to its
* default value during execute/redo and undo.
*/
protected static final Object DEFAULT_VALUE = new Object();
private static IPropertyDescriptor getPropertyDescriptor(
IPropertySource propertySource, Object propertyId) {
for (IPropertyDescriptor propertyDescriptor : propertySource
.getPropertyDescriptors()) {
if (propertyDescriptor.getId().equals(propertyId)) {
return propertyDescriptor;
}
}
return null;
}
private static String getValueLabel(IPropertySource propertySource,
Object propertyId, Object newValue) {
IPropertyDescriptor propertyDescriptor = getPropertyDescriptor(
propertySource, propertyId);
return propertyDescriptor.getLabelProvider().getText(newValue);
}
/** content-relevant-property */
private boolean isContentRelevant = true;
/** the value to set for the property */
private Object newValue;
/** the old value of the property prior to executing this command */
private Object oldValue;
/** the id of the property whose value has to be set */
private Object propertyId;
/** the property source whose property has to be set */
private IPropertySource propertySource;
/**
* Constructs a new {@link SetPropertyValueOperation}.
*
* @param propertyLabel
* A label to identify the property whose value is set by this
* command.
* @param propertySource
* The property source which provides the property, whose value
* is to be set.
* @param propertyId
* The id of the property whose value is to be set.
* @param newValue
* The new value to set for the property or
* {@link #DEFAULT_VALUE} to indicate that the property should be
* reset.
*/
public SetPropertyValueOperation(String propertyLabel,
IPropertySource propertySource, Object propertyId,
Object newValue) {
super(MessageFormat
.format(Messages.SetPropertyValueCommand_Label,
new Object[] { propertyLabel, getValueLabel(
propertySource, propertyId, newValue) })
.trim());
this.propertySource = propertySource;
this.propertyId = propertyId;
this.newValue = newValue;
}
@Override
public boolean canExecute() {
if (propertySource == null || propertyId == null) {
return false;
}
if (newValue == DEFAULT_VALUE) {
// we may only reset a property to its default value if it supports
// the notion of a default value and it does not already have this
// value
boolean canExecute = propertySource.isPropertySet(propertyId);
if (propertySource instanceof IPropertySource2) {
canExecute &= (((IPropertySource2) propertySource)
.isPropertyResettable(propertyId));
}
return canExecute;
}
return true;
}
@Override
public IStatus execute(IProgressMonitor monitor, IAdaptable info)
throws ExecutionException {
/*
* Fix for bug #54250 IPropertySource.isPropertySet(String) returns
* false both when there is no default value, and when there is a
* default value and the property is set to that value. To correctly
* determine if a reset should be done during undo, we compare the
* return value of isPropertySet(String) before and after
* setPropertyValue(...) is invoked. If they are different (it must have
* been false before and true after -- it cannot be the other way
* around), then that means we need to reset.
*/
boolean wasPropertySet = propertySource.isPropertySet(propertyId);
oldValue = unwrapValue(propertySource.getPropertyValue(propertyId));
// set value of property to new value or reset the value, if specified
if (newValue == DEFAULT_VALUE) {
propertySource.resetPropertyValue(propertyId);
} else {
propertySource.setPropertyValue(propertyId, unwrapValue(newValue));
}
// check if property was set to its default value before (so it will
// have to be resetted during undo); note that if the new value is
// DEFAULT_VALUE the old value may not have been the default value as
// well, as the command would not be executable in this case.
if (propertySource instanceof IPropertySource2) {
if (!wasPropertySet && ((IPropertySource2) propertySource)
.isPropertyResettable(propertyId)) {
oldValue = DEFAULT_VALUE;
}
} else {
if (!wasPropertySet && propertySource.isPropertySet(propertyId)) {
oldValue = DEFAULT_VALUE;
}
}
// TODO: infer a proper status
return Status.OK_STATUS;
}
/**
* Returns the new value to be set for the property when executing or
* redoing.
*
* @return the new value or {@link #DEFAULT_VALUE} to indicate that the
* default value should be set as the new value.
*/
public Object getNewValue() {
return newValue;
}
/**
* After the command has been executed or redone, returns the old value of
* the property or {@link #DEFAULT_VALUE} if the property did not have a
* value before.
*
* @return the old value of the property or {@link #DEFAULT_VALUE}.
*/
public Object getOldValue() {
return oldValue;
}
/**
* Returns the id by which to identify the property whose value is to be
* set.
*
* @return the id of the property whose value is to be set.
*/
public Object getPropertyId() {
return propertyId;
}
/**
* Returns the {@link IPropertySource} which provides the property, whose
* value is to be set.
*
* @return the {@link IPropertySource} which provides the property.
*/
public IPropertySource getPropertySource() {
return propertySource;
}
@Override
public boolean isContentRelevant() {
return isContentRelevant;
}
@Override
public boolean isNoOp() {
return oldValue == newValue
|| (oldValue != null && oldValue.equals(newValue));
}
@Override
public IStatus redo(IProgressMonitor monitor, IAdaptable info)
throws ExecutionException {
return execute(monitor, info);
}
/**
* Sets the content-relevancy of this operation to the given value.
*
* @param isContentRelevant
* <code>true</code> if this operation is content-relevant,
* <code>false</code> otherwise.
*/
public void setContentRelevant(boolean isContentRelevant) {
this.isContentRelevant = isContentRelevant;
}
@Override
public IStatus undo(IProgressMonitor monitor, IAdaptable info)
throws ExecutionException {
if (oldValue == DEFAULT_VALUE) {
propertySource.resetPropertyValue(propertyId);
} else {
propertySource.setPropertyValue(propertyId, oldValue);
}
return Status.OK_STATUS;
}
private Object unwrapValue(Object value) {
if (value instanceof IPropertySource) {
return ((IPropertySource) value).getEditableValue();
}
return value;
}
}