/*******************************************************************************
* Copyright (c) 2006-2013 The RCP Company 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:
* The RCP Company - initial API and implementation
*******************************************************************************/
package com.rcpcompany.uibindings.financial;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import com.rcpcompany.uibindings.IDisposable;
import com.rcpcompany.uibindings.IUIBindingDecoratorExtenderContext;
import com.rcpcompany.uibindings.IValueBinding;
import com.rcpcompany.uibindings.decorators.extenders.AbstractUIBindingDecoratorExtender;
import com.rcpcompany.uibindings.financial.internal.Activator;
import com.rcpcompany.utils.logging.LogUtils;
/**
* Extender that will format a financial number as follows:
* <ul>
* <li>when the number increases it should be green with an up-arrow</li>
* <li>when the number decreases it should be red with a down-arrow</li>
* <li>after a short while - a preference - it should go back to black</li>
* </ul>
*
* @author Tonny Madsen, The RCP Company
*/
public class FinancialUpDownExtender extends AbstractUIBindingDecoratorExtender {
/**
* Argument name to enable the financial up/down arrow for numbers.
* <p>
* The argument value is a {@link Boolean}.Defaults to <code>false</code>.
*/
public static final String ARG_FINANCIAL_UP_DOWN = "financialUpDown";
/**
* {@link JFaceResources} identity of image used for "up".
*/
public static final String UP_ARROW = FinancialUpDownExtender.class.getName() + "$up";
/**
* {@link JFaceResources} identity of image used for "down".
*/
public static final String DOWN_ARROW = FinancialUpDownExtender.class.getName() + "$down";
/**
* The timeout for the up or down trends.
*
* TODO: change to preference
*/
public static final int TIMEOUT = 2000;
public FinancialUpDownExtender() {
final ImageRegistry colorRegistry = JFaceResources.getImageRegistry();
colorRegistry.put(UP_ARROW, AbstractUIPlugin.imageDescriptorFromPlugin(Activator.ID, "/images/up-arrow.png"));
colorRegistry.put(DOWN_ARROW,
AbstractUIPlugin.imageDescriptorFromPlugin(Activator.ID, "/images/down-arrow.png"));
}
@Override
public boolean isEnabled(IValueBinding binding) {
if (!binding.getArgument(ARG_FINANCIAL_UP_DOWN, Boolean.class, false)) return false;
if (binding.getModelObservableValue() == null) return false;
return true;
}
@Override
public void extend(IUIBindingDecoratorExtenderContext context) {
final IValueBinding binding = context.getBinding();
BindingData data = binding.getService(BindingData.class);
if (data == null) {
data = new BindingData(binding);
}
data.extend(context);
}
/**
* Per binding specific data.
*/
public class BindingData implements IDisposable {
/**
* The binding
*/
private final IValueBinding myBinding;
/**
* The observable value for the binding
*/
private final IObservableValue myObservableValue;
/**
* The currently reported trend for this binding
*/
protected int myCurrentTrend = 0;
/**
* The time-out for the current trend. 0 if no timeout is needed.
*/
protected long myCurrentTimeout = 0;
/**
* The last known value for the binding
*/
private Object myValue;
private DisplayTimeout myDisplayTimeout;
/**
* Constructs and returns a new data record for the specified binding.
*
* @param binding the binding
*/
public BindingData(IValueBinding binding) {
myBinding = binding;
/*
* Tested in isEnabled(...)
*/
myObservableValue = binding.getModelObservableValue();
myValue = myObservableValue.getValue();
myBinding.registerService(this);
}
@Override
public void dispose() {
myBinding.unregisterService(this);
}
/**
* Extends the specified binding context.
*
* @param context the context to extend
*/
public void extend(IUIBindingDecoratorExtenderContext context) {
int trend = getTrend();
/*
* Within the timeout, we keep the same trend.
*/
if (trend == 0) {
if (System.currentTimeMillis() < myCurrentTimeout) {
// Old trend
trend = myCurrentTrend;
} else
// No old or new trends
return;
} else {
// New trend
if (myDisplayTimeout != null) {
myDisplayTimeout.cancelled = true;
myDisplayTimeout = null;
}
myDisplayTimeout = new DisplayTimeout();
Display.getDefault().timerExec(TIMEOUT, myDisplayTimeout);
}
switch (trend) {
case -1:
context.setForegound(Display.getDefault().getSystemColor(SWT.COLOR_DARK_GREEN));
context.setImage(JFaceResources.getImage(DOWN_ARROW));
break;
case 1:
context.setForegound(Display.getDefault().getSystemColor(SWT.COLOR_DARK_RED));
context.setImage(JFaceResources.getImage(UP_ARROW));
break;
case 0:
break;
default:
LogUtils.error(this, "Trend value illegal: " + trend);
break;
}
}
protected class DisplayTimeout implements Runnable {
public boolean cancelled = false;
@Override
public void run() {
if (cancelled) return;
/*
* Repaint the field!
*/
myBinding.updateBinding();
}
}
/**
* Returns the current trend for this data item.
* <p>
* The rules are:
* <ul>
* <li>-1 if it is decreasing</li>
* <li>+1 if it is increasing</li>
* <li>0 if it is the same</li>
* <li>0 if either the old or the new value is <code>null</code></li>
* </ul>
* <p>
* If the old value implements the {@link Comparable} interface this is used. Otherwise TODO
*
* @return the trend
*/
private int getTrend() {
final Object newValue = myObservableValue.getValue();
try {
if (newValue == null) return 0;
if (myValue == null) return 0;
if (myValue == newValue) return 0;
if (newValue.equals(myValue)) return 0;
if (myValue instanceof Comparable) return ((Comparable) myValue).compareTo(newValue);
final Object valueType = myObservableValue.getValueType();
LogUtils.debug(valueType, "Cannot compare type " + valueType);
return 0;
} finally {
myValue = newValue;
}
}
}
}