package org.marketcetera.photon.views;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import org.eclipse.core.databinding.*;
import org.eclipse.core.databinding.beans.BeansObservables;
import org.eclipse.core.databinding.conversion.Converter;
import org.eclipse.core.databinding.conversion.NumberToStringConverter;
import org.eclipse.core.databinding.observable.IObservable;
import org.eclipse.core.databinding.observable.list.IListChangeListener;
import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.core.databinding.observable.list.ListChangeEvent;
import org.eclipse.core.databinding.observable.list.ListDiffEntry;
import org.eclipse.core.databinding.observable.value.ComputedValue;
import org.eclipse.core.databinding.observable.value.DecoratingObservableValue;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.observable.value.IValueChangeListener;
import org.eclipse.core.databinding.observable.value.ValueChangeEvent;
import org.eclipse.core.databinding.validation.ValidationStatus;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jface.databinding.swt.SWTObservables;
import org.eclipse.jface.databinding.viewers.ObservableListContentProvider;
import org.eclipse.jface.databinding.viewers.ObservableMapLabelProvider;
import org.eclipse.jface.databinding.viewers.ViewersObservables;
import org.eclipse.jface.fieldassist.FieldDecorationRegistry;
import org.eclipse.jface.viewers.*;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.forms.widgets.ScrolledForm;
import org.marketcetera.algo.BrokerAlgo;
import org.marketcetera.algo.BrokerAlgoTag;
import org.marketcetera.core.CoreException;
import org.marketcetera.photon.BrokerManager;
import org.marketcetera.photon.BrokerManager.Broker;
import org.marketcetera.photon.BrokerManager.BrokerLabelProvider;
import org.marketcetera.photon.PhotonPlugin;
import org.marketcetera.photon.commons.databinding.TypedConverter;
import org.marketcetera.photon.commons.databinding.TypedObservableValue;
import org.marketcetera.photon.commons.ui.databinding.RequiredFieldSupport;
import org.marketcetera.photon.commons.ui.databinding.UpdateStrategyFactory;
import org.marketcetera.photon.ui.databinding.StatusToImageConverter;
import org.marketcetera.photon.views.providers.AlgoTableColumnEdditorSupport;
import org.marketcetera.photon.views.providers.AlgoTableObservableMapLabelProvider;
import org.marketcetera.trade.BrokerID;
import org.marketcetera.trade.NewOrReplaceOrder;
import org.marketcetera.trade.OrderReplace;
import org.marketcetera.trade.OrderSingle;
import org.marketcetera.util.misc.ClassVersion;
import com.ibm.icu.text.NumberFormat;
/* $License$ */
/**
* This is the abstract base class for all order ticket views. It is responsible
* for setting up the databindings for the "common" order ticket fields, such as
* side, price, and time in force.
*
* It also is responsible for managing the "custom fields" for order messages
* that can be set by the user in the preferences dialog, and activated in the
* order ticket.
*
* @author gmiller
* @author <a href="mailto:will@marketcetera.com">Will Horn</a>
* @since 0.6.0
*/
@ClassVersion("$Id: OrderTicketView.java 16899 2014-05-11 16:03:04Z colin $")
public abstract class OrderTicketView<M extends OrderTicketModel, T extends IOrderTicket>
extends XSWTView<T> {
private static final String CUSTOM_FIELD_VIEW_SAVED_STATE_KEY_PREFIX = "CUSTOM_FIELD_CHECKED_STATE_OF_"; //$NON-NLS-1$
private final Class<T> mTicketClass;
private final ObservablesManager mObservablesManager = new ObservablesManager();
private final M mModel;
private IMemento mMemento;
private ComboViewer mAvailableBrokersViewer;
private CheckboxTableViewer mCustomFieldsTableViewer;
private ComboViewer mAvailableAlgosViewer;
private TableViewer mAlgoTagsTableViewer;
private ComboViewer mSideComboViewer;
private ComboViewer mTimeInForceComboViewer;
private ComboViewer mOrderTypeComboViewer;
private IValueChangeListener mFocusListener;
/**
* Constructor.
*
* @param ticketClass
* type of ticket class
* @param model
* the ticket model
*/
protected OrderTicketView(Class<T> ticketClass, M model) {
mTicketClass = ticketClass;
mModel = model;
}
@Override
public void init(IViewSite site, IMemento memento) throws PartInitException {
super.init(site, memento);
mMemento = memento;
}
@Override
protected Class<T> getXSWTInterfaceClass() {
return mTicketClass;
}
/**
* Returns the view model.
*
* @return the view model
*/
protected M getModel() {
return mModel;
}
/**
* Returns the {@link ObservablesManager} that will clean up managed
* observables.
*
* @return the observables manager
*/
public ObservablesManager getObservablesManager() {
return mObservablesManager;
}
@Override
protected void finishUI() {
T ticket = getXSWTView();
/*
* Set background of error message area.
*/
Color bg = ticket.getForm().getParent().getBackground();
ticket.getErrorIconLabel().setBackground(bg);
ticket.getErrorMessageLabel().setBackground(bg);
/*
* Set up viewers.
*/
initViewers(ticket);
/*
* Additional widget customizations.
*/
customizeWidgets(ticket);
/*
* Handle clear button click.
*/
ticket.getClearButton().addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
getModel().clearOrderMessage();
}
});
/*
* Handle send button click.
*/
ticket.getSendButton().addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
handleSend();
}
});
/*
* Bind to model.
*/
try {
bindFormTitle();
bindMessage();
bindCustomFields();
bindAlgoTags();
} catch (Exception e) {
PhotonPlugin.getMainConsoleLogger().error(
Messages.ORDER_TICKET_VIEW_CANNOT_BIND_TO_TICKET.getText(),
e);
}
/*
* Initialize validation (error message area).
*/
initValidation();
/*
* Control focus when the model's order changes.
*/
mFocusListener = new IValueChangeListener() {
@Override
public void handleValueChange(ValueChangeEvent event) {
setFocus();
}
};
getModel().getOrderObservable().addValueChangeListener(mFocusListener);
ticket.getForm().reflow(true);
}
/**
* Customize the widgets.
*
* @param ticket
* the order ticket.
*/
protected void customizeWidgets(T ticket) {
/*
* Update size of text fields since default will be small.
*/
updateSize(ticket.getQuantityText(), 10);
updateSize(ticket.getSymbolText(), 10);
updateSize(ticket.getPriceText(), 10);
updateSize(ticket.getAccountText(), 10);
/*
* Customize text fields to auto select the text on focus to make it
* easy to change the value.
*/
selectOnFocus(ticket.getQuantityText());
selectOnFocus(ticket.getSymbolText());
selectOnFocus(ticket.getPriceText());
selectOnFocus(ticket.getAccountText());
selectOnFocus(ticket.getDisplayQuantityText());
/*
* If the ticket has no errors, enter on these fields will trigger a
* send.
*/
addSendOrderListener(ticket.getSideCombo());
addSendOrderListener(ticket.getQuantityText());
addSendOrderListener(ticket.getDisplayQuantityText());
addSendOrderListener(ticket.getSymbolText());
addSendOrderListener(ticket.getOrderTypeCombo());
addSendOrderListener(ticket.getPriceText());
addSendOrderListener(ticket.getBrokerCombo());
addSendOrderListener(ticket.getTifCombo());
addSendOrderListener(ticket.getAccountText());
}
/**
* Set up viewers.
*
* @param ticket
*/
protected void initViewers(T ticket) {
/*
* Side combo based on Side enum.
*/
mSideComboViewer = new ComboViewer(ticket.getSideCombo());
mSideComboViewer.setContentProvider(new ArrayContentProvider());
mSideComboViewer.setInput(getModel().getValidSideValues());
/*
* Order type combo based on OrderType enum.
*/
mOrderTypeComboViewer = new ComboViewer(ticket.getOrderTypeCombo());
mOrderTypeComboViewer.setContentProvider(new ArrayContentProvider());
mOrderTypeComboViewer.setInput(getModel().getValidOrderTypeValues());
/*
* Broker combo based on available brokers.
*/
mAvailableBrokersViewer = new ComboViewer(ticket.getBrokerCombo());
mAvailableBrokersViewer.setContentProvider(new ObservableListContentProvider());
mAvailableBrokersViewer.setLabelProvider(new BrokerLabelProvider());
mAvailableBrokersViewer.setInput(getModel().getValidBrokers());
// watches broker combo and sets currently selected broker appropriately
mAvailableBrokersViewer.addSelectionChangedListener(new ISelectionChangedListener() {
@Override
public void selectionChanged(SelectionChangedEvent inEvent)
{
Broker broker = (Broker)((StructuredSelection)inEvent.getSelection()).getFirstElement();
if(broker == null || BrokerManager.AUTO_SELECT_BROKER.getName().equals(broker.getName())) {
getModel().setSelectedBroker(BrokerManager.AUTO_SELECT_BROKER);
getModel().setSelectedAlgo(null);
} else {
getModel().setSelectedBroker(BrokerManager.getCurrent().getBroker(broker.getId()));
}
}
});
/*
* broker algos based on selected broker
*/
mAvailableAlgosViewer = new ComboViewer(ticket.getAlgoCombo());
mAvailableAlgosViewer.setContentProvider(new ObservableListContentProvider());
mAvailableAlgosViewer.setLabelProvider(new AlgoLabelProvider());
mAvailableAlgosViewer.setInput(getModel().getValidAlgos());
mAvailableAlgosViewer.addSelectionChangedListener(new ISelectionChangedListener() {
@Override
public void selectionChanged(SelectionChangedEvent inEvent)
{
Object selectedObject = ((StructuredSelection)inEvent.getSelection()).getFirstElement();
BrokerAlgo brokerAlgo = null;
if(selectedObject != null && selectedObject instanceof BrokerAlgo) {
brokerAlgo = (BrokerAlgo)selectedObject;
}
getModel().setSelectedAlgo(brokerAlgo);
}
});
/*
* Time in Force combo based on TimeInForce enum.
*
* An extra blank entry is added since the field is optional.
*/
mTimeInForceComboViewer = new ComboViewer(ticket.getTifCombo());
mTimeInForceComboViewer.setContentProvider(new ArrayContentProvider());
mTimeInForceComboViewer.setInput(getModel().getValidTimeInForceValues());
/*
* Custom fields table.
*
* Input is bound to model in bindCustomFields.
*/
mCustomFieldsTableViewer = new CheckboxTableViewer(ticket.getCustomFieldsTable());
ObservableListContentProvider contentProvider = new ObservableListContentProvider();
mCustomFieldsTableViewer.setContentProvider(contentProvider);
mCustomFieldsTableViewer.setLabelProvider(new ObservableMapLabelProvider(BeansObservables.observeMaps(contentProvider.getKnownElements(),
CustomField.class,
new String[] { "keyString", "valueString" })));//$NON-NLS-1$ //$NON-NLS-2$
mAlgoTagsTableViewer = new TableViewer(ticket.getAlgoTagsTable());
ObservableListContentProvider algoTagsContentProvider = new ObservableListContentProvider();
TableViewerColumn valueColumn = new TableViewerColumn(mAlgoTagsTableViewer, mAlgoTagsTableViewer.getTable().getColumns()[1]);
mAlgoTagsTableViewer.setContentProvider(algoTagsContentProvider);
mAlgoTagsTableViewer.setLabelProvider(new AlgoTableObservableMapLabelProvider(BeansObservables.observeMaps(algoTagsContentProvider.getKnownElements(),
BrokerAlgoTag.class,
new String[] { "tagSpec", "value" })));//$NON-NLS-1$ //$NON-NLS-2$
valueColumn.setEditingSupport(new AlgoTableColumnEdditorSupport(mAlgoTagsTableViewer));
}
/**
* Get the UI string to show for a "new order" message.
*
* @return the UI string
*/
protected abstract String getNewOrderString();
/**
* Get the UI string to show for a "replace" message.
*
* @return the UI string
*/
protected abstract String getReplaceOrderString();
/**
* Bind the top level form title to show different text depending on the
* order type.
*/
protected void bindFormTitle() {
TypedObservableValue<String> formTextObservable = new TypedObservableValue<String>(
String.class) {
@Override
protected String doGetValue() {
return getXSWTView().getForm().getText();
}
@Override
protected void doSetTypedValue(String value) {
getXSWTView().getForm().setText(value);
}
};
getObservablesManager().addObservable(formTextObservable);
getDataBindingContext().bindValue(
formTextObservable,
getModel().getOrderObservable(),
null,
new UpdateValueStrategy().setConverter(new Converter(
NewOrReplaceOrder.class, String.class) {
public Object convert(Object fromObject) {
if (fromObject instanceof OrderReplace) {
return getReplaceOrderString();
} else if (fromObject instanceof OrderSingle) {
return getNewOrderString();
} else {
return null;
}
}
}));
}
/**
* Binds the UI to the model.
*/
protected void bindMessage() {
final DataBindingContext dbc = getDataBindingContext();
final OrderTicketModel model = getModel();
final IOrderTicket ticket = getXSWTView();
/*
* Side
*/
bindRequiredCombo(mSideComboViewer, model.getSide(),
Messages.ORDER_TICKET_VIEW_SIDE__LABEL.getText());
enableForNewOrderOnly(mSideComboViewer.getControl());
/*
* Quantity
*/
bindRequiredDecimal(ticket.getQuantityText(), model.getQuantity(),
Messages.ORDER_TICKET_VIEW_QUANTITY__LABEL.getText());
/*
* Display Quantity
*/
bindText(ticket.getDisplayQuantityText(), model.getDisplayQuantity());
/*
* Symbol
*/
bindRequiredText(ticket.getSymbolText(), getModel().getSymbol(),
Messages.ORDER_TICKET_VIEW_SYMBOL__LABEL.getText());
enableForNewOrderOnly(ticket.getSymbolText());
/*
* Order Type
*/
bindRequiredCombo(mOrderTypeComboViewer, model.getOrderType(),
Messages.ORDER_TICKET_VIEW_ORDER_TYPE__LABEL.getText());
/*
* Price
*
* Need custom required field logic since price is only required for
* limit orders.
*/
{
Binding binding = bindDecimal(ticket.getPriceText(), model
.getPrice(), Messages.ORDER_TICKET_VIEW_PRICE__LABEL
.getText());
/*
* RequiredFieldSupport reports an error if the value is null or
* empty string. We want this behavior when the order is a limit
* order, but not when it is a market order (since empty string is
* correct as the price is uneditable. So we decorate the observable
* and pass the decorated one to RequiredFieldsupport.
*/
IObservableValue priceDecorator = new DecoratingObservableValue(
(IObservableValue) binding.getTarget(), false) {
@Override
public Object getValue() {
Object actualValue = super.getValue();
if ("".equals(actualValue) //$NON-NLS-1$
&& !model.isLimitOrder().getTypedValue()) {
/*
* Return an object to "trick" RequiredFieldSupport to
* not error.
*/
return new Object();
}
return actualValue;
}
};
RequiredFieldSupport.initFor(dbc, priceDecorator,
Messages.ORDER_TICKET_VIEW_PRICE__LABEL.getText(), false,
SWT.BOTTOM | SWT.LEFT, binding);
dbc.bindValue(SWTObservables.observeEnabled(ticket.getPriceText()),
model.isLimitOrder());
}
/*
* Broker
*
* Custom binding logic required since the viewer list can dynamically
* change.
*/
{
IObservableValue target = ViewersObservables
.observeSingleSelection(mAvailableBrokersViewer);
/*
* Bind the target (combo) to the model, but use POLICY_ON_REQUEST
* for target-to-model binding since we don't want the model to
* change simply because a broker went down. The target-to-model
* updates are handled manually below.
*/
final Binding binding = dbc.bindValue(target, model.getBrokerId(),
new UpdateValueStrategy(
UpdateValueStrategy.POLICY_ON_REQUEST)
.setConverter(new TypedConverter<Broker, BrokerID>(
Broker.class, BrokerID.class) {
@Override
public BrokerID doConvert(Broker fromObject) {
return fromObject.getId();
}
}), new UpdateValueStrategy()
.setConverter(new TypedConverter<BrokerID, Broker>(
BrokerID.class, Broker.class) {
@Override
public Broker doConvert(BrokerID fromObject) {
return BrokerManager.getCurrent()
.getBroker(fromObject);
}
}));
/*
* If the target changes and the new value is not null, then this
* was a user selection and the model should be updated.
*/
target.addValueChangeListener(new IValueChangeListener() {
@Override
public void handleValueChange(ValueChangeEvent event) {
if (event.diff.getNewValue() != null) {
binding.updateTargetToModel();
}
}
});
/*
* When the broker list changes, we force a model-to-target update
* to ensure the two are in sync if possible.
*/
final IListChangeListener listener = new IListChangeListener() {
@Override
public void handleListChange(ListChangeEvent event) {
binding.updateModelToTarget();
}
};
BrokerManager.getCurrent().getAvailableBrokers()
.addListChangeListener(listener);
/*
* Need to remove the listener when the widget is disposed.
*/
mAvailableBrokersViewer.getControl().addDisposeListener(
new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent e) {
BrokerManager.getCurrent().getAvailableBrokers()
.removeListChangeListener(listener);
}
});
/*
* If the model has a broker id, but the target doesn't have a
* corresponding entry, the target will be null which needs to
* generate an error.
*/
setRequired(binding, Messages.ORDER_TICKET_VIEW_BROKER__LABEL
.getText());
}
enableForNewOrderOnly(mAvailableBrokersViewer.getControl());
/*
* broker algos
*/
bindCombo(mAvailableAlgosViewer,
model.getBrokerAlgo());
/*
* Time in Force
*/
bindCombo(mTimeInForceComboViewer, model.getTimeInForce());
/*
* Account
*/
bindText(getXSWTView().getAccountText(), model.getAccount());
}
/**
* Listener for changes on element from algo tags list.
*/
private PropertyChangeListener algoTagsListChanged = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
BrokerAlgoTag tag = (BrokerAlgoTag)evt.getSource();
int index = getModel().getAlgoTagsList().indexOf(tag);
getModel().getAlgoTagsList().set(index, tag);
}
};
/**
* Binds the algo tags on the model to the view.
*/
protected void bindAlgoTags()
{
M model = getModel();
mAlgoTagsTableViewer.setInput(model.getAlgoTagsList());
model.getAlgoTagsList().addListChangeListener(new IListChangeListener() {
public void handleListChange(ListChangeEvent event)
{
for(ListDiffEntry entry:event.diff.getDifferences()){
if(entry.isAddition()){
BrokerAlgoTag tag = (BrokerAlgoTag)entry.getElement();
tag.addPropertyChangeListener(algoTagsListChanged);
}else{
BrokerAlgoTag tag = (BrokerAlgoTag)entry.getElement();
tag.removePropertyChangeListener(algoTagsListChanged);
}
}
}
});
//Add validation for algo tags list
getDataBindingContext().addValidationStatusProvider(new ValidationStatusProvider() {
@Override
public IObservableValue getValidationStatus() {
return new ComputedValue() {
@Override
protected Object calculate() {
for(Object object: getModel().getAlgoTagsList()){
BrokerAlgoTag algoTag = (BrokerAlgoTag)object;
try{
algoTag.validate();
}catch (CoreException e){
return ValidationStatus.error(e.getLocalizedMessage());
}
}
return ValidationStatus.OK_STATUS;
}
};
}
@Override
public IObservableList getTargets() {
return ViewersObservables.observeMultiSelection(mAlgoTagsTableViewer);
}
@Override
public IObservableList getModels() {
return getModel().getAlgoTagsList();
}
});
}
/**
* Bind the custom fields on the model to the view.
*/
protected void bindCustomFields() {
M model = getModel();
mCustomFieldsTableViewer.setInput(model.getCustomFieldsList());
mCustomFieldsTableViewer
.addCheckStateListener(new ICheckStateListener() {
public void checkStateChanged(CheckStateChangedEvent event) {
Object source = event.getElement();
((CustomField) source).setEnabled(event.getChecked());
}
});
model.getCustomFieldsList().addListChangeListener(
new IListChangeListener() {
public void handleListChange(ListChangeEvent event) {
ScrolledForm theForm = getXSWTView().getForm();
if (!theForm.isDisposed()) {
ListDiffEntry[] differences = event.diff
.getDifferences();
for (ListDiffEntry listDiffEntry : differences) {
if (listDiffEntry.isAddition()) {
CustomField customField = (CustomField) listDiffEntry
.getElement();
String key = CUSTOM_FIELD_VIEW_SAVED_STATE_KEY_PREFIX
+ customField.getKeyString();
IMemento theMemento = getMemento();
if (theMemento != null
&& theMemento.getInteger(key) != null) {
boolean itemChecked = (theMemento
.getInteger(key).intValue() != 0);
customField.setEnabled(itemChecked);
}
}
}
theForm.reflow(true);
}
}
});
}
/**
* Initialization the validation (error message area) of the view.
*/
protected void initValidation() {
DataBindingContext dbc = getDataBindingContext();
AggregateValidationStatus aggregateValidationStatus = new AggregateValidationStatus(
dbc, AggregateValidationStatus.MAX_SEVERITY);
dbc.bindValue(SWTObservables.observeText(getXSWTView()
.getErrorMessageLabel()), aggregateValidationStatus);
dbc.bindValue(SWTObservables.observeImage(getXSWTView()
.getErrorIconLabel()), aggregateValidationStatus, null,
new UpdateValueStrategy()
.setConverter(new StatusToImageConverter()));
dbc.bindValue(SWTObservables.observeEnabled(getXSWTView()
.getSendButton()), aggregateValidationStatus, null,
new UpdateValueStrategy()
.setConverter(new TypedConverter<IStatus, Boolean>(
IStatus.class, Boolean.class) {
@Override
public Boolean doConvert(IStatus fromObject) {
return fromObject.getSeverity() < IStatus.ERROR;
}
}));
}
/**
* Binds a combo viewer to a model field that is required.
*
* @param viewer
* the viewer
* @param model
* the model observable
* @return the binding
*/
protected Binding bindCombo(ComboViewer viewer, IObservableValue model) {
DataBindingContext dbc = getDataBindingContext();
IObservableValue target = ViewersObservables
.observeSingleSelection(viewer);
return dbc.bindValue(target, model, new UpdateValueStrategy()
.setConverter(new Converter(target.getValueType(), model
.getValueType()) {
@Override
public Object convert(Object fromObject) {
return fromObject instanceof OrderTicketModel.NullSentinel ? null
: fromObject;
}
}), null);
}
/**
* Binds a combo viewer and makes it required.
*
* @param viewer
* the viewer
* @param model
* the model observable
* @param description
* the description for error messages
* @return the binding
*/
protected Binding bindRequiredCombo(ComboViewer viewer,
IObservableValue model, String description) {
DataBindingContext dbc = getDataBindingContext();
IObservableValue target = ViewersObservables
.observeSingleSelection(viewer);
Binding binding = dbc.bindValue(target, model);
setRequired(binding, description);
return binding;
}
/**
* Binds a text widget to a BigDecimal value.
*
* @param text
* the widget
* @param model
* the model observable
* @param description
* the description for error messages
* @return the binding
*/
protected Binding bindDecimal(Text text, IObservableValue model,
String description) {
DataBindingContext dbc = getDataBindingContext();
IObservableValue target = SWTObservables.observeText(text, SWT.Modify);
NumberFormat numberFormat = NumberFormat.getInstance();
numberFormat.setGroupingUsed(false);
numberFormat.setMaximumFractionDigits(6);
return dbc.bindValue(target, model, UpdateStrategyFactory
.withConvertErrorMessage(new UpdateValueStrategy(),
Messages.ORDER_TICKET_VIEW_NOT_DECIMAL_ERROR
.getText(description)),
new UpdateValueStrategy().setConverter(NumberToStringConverter
.fromBigDecimal(numberFormat)));
}
/**
* Binds a text widget to a BigDecimal value and makes it required.
*
* @param text
* the widget
* @param model
* the model observable
* @param description
* the description for error messages
* @return the binding
*/
protected Binding bindRequiredDecimal(Text text, IObservableValue model,
String description) {
Binding binding = bindDecimal(text, model, description);
setRequired(binding, description);
return binding;
}
/**
* Binds a text widget to the model.
*
* @param text
* the widget
* @param model
* the model observable
* @return the binding
*/
protected Binding bindText(Text text, IObservableValue model) {
DataBindingContext dbc = getDataBindingContext();
IObservableValue target = SWTObservables.observeText(text, SWT.Modify);
UpdateValueStrategy targetToModel = null;
if (model.getValueType() == String.class) {
/*
* Clearing a text box should set the model to null, not empty
* string.
*/
targetToModel = new UpdateValueStrategy()
.setConverter(new TypedConverter<String, String>(
String.class, String.class) {
@Override
protected String doConvert(String fromObject) {
if (fromObject != null && fromObject.isEmpty()) {
return null;
}
return fromObject;
}
});
}
return dbc.bindValue(target, model, targetToModel, null);
}
/**
* Binds a text widget and makes it required.
*
* @param text
* the widget
* @param model
* the model observable
* @param description
* the description for error messages
* @return the binding
*/
protected Binding bindRequiredText(Text text, IObservableValue model,
String description) {
Binding binding = bindText(text, model);
setRequired(binding, description);
return binding;
}
/**
* Add required semantics to a binding.
*
* @param binding
* the binding
* @param description
* the description for error messages
*/
protected void setRequired(Binding binding, String description) {
RequiredFieldSupport.initFor(getDataBindingContext(), binding
.getTarget(), description, false, SWT.BOTTOM | SWT.LEFT,
binding);
}
/**
* Add required semantics to a control.
*
* @param target
* the control's observable
* @param description
* the description for error messages
* @param binding
* a binding that also contributes validation status, can be null
*/
protected void setRequired(IObservable target, String description,
Binding binding) {
RequiredFieldSupport.initFor(getDataBindingContext(), target,
description, false, SWT.BOTTOM | SWT.LEFT, binding);
}
/**
* Configures a control to be enabled only when model contains a new order
* (as opposed to a replace order).
*
* @param control
* the control
*/
protected void enableForNewOrderOnly(Control control) {
getDataBindingContext().bindValue(
SWTObservables.observeEnabled(control),
getModel().getOrderObservable(),
null,
new UpdateValueStrategy().setConverter(new Converter(
NewOrReplaceOrder.class, Boolean.class) {
@Override
public Object convert(Object fromObject) {
return fromObject instanceof OrderSingle;
}
}));
}
/**
* Customizes a text widget to select the entire text when it receives focus
* (makes it easy to change).
*
* @param text
* the widget
*/
protected void selectOnFocus(Text text) {
text.addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
((Text) e.widget).selectAll();
}
});
}
/**
* Hook up a listener to the targetControl that listens for {@link SWT#CR}
* and invokes {@link #handleSend()}.
*
* @param targetControl
* the control to hook up
*/
protected void addSendOrderListener(Control targetControl) {
targetControl.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e) {
if (e.character == SWT.CR) {
if (getXSWTView().getSendButton().isEnabled()) {
handleSend();
}
}
}
});
}
/**
* This method "completes" the message by calling
* {@link OrderTicketModel#completeMessage()}, sends the order via the
* controller, then resets the message in the view model.
*/
protected void handleSend() {
try {
// TODO: this logic should probably be in the controller
PhotonPlugin plugin = PhotonPlugin.getDefault();
mModel.completeMessage();
NewOrReplaceOrder orderMessage = mModel.getOrderObservable()
.getTypedValue();
plugin.getPhotonController().sendOrderChecked(orderMessage);
mModel.clearOrderMessage();
} catch (Exception e) {
String errorMessage = e.getLocalizedMessage();
PhotonPlugin.getMainConsoleLogger().error(errorMessage);
showErrorMessage(errorMessage, IStatus.ERROR);
}
}
/**
* Show the given error message in this order ticket's error display area.
*
* @param errorMessage
* the text of the error message
* @param severity
* the severity of the error message, see {@link IStatus}
*/
protected void showErrorMessage(String errorMessage, int severity) {
Label errorMessageLabel = getXSWTView().getErrorMessageLabel();
Label errorIconLabel = getXSWTView().getErrorIconLabel();
if (errorMessage == null) {
errorMessageLabel.setText(""); //$NON-NLS-1$
errorIconLabel.setImage(null);
} else {
errorMessageLabel.setText(errorMessage);
if (severity == IStatus.OK) {
errorIconLabel.setImage(null);
} else {
if (severity == IStatus.ERROR)
errorIconLabel.setImage(FieldDecorationRegistry
.getDefault().getFieldDecoration(
FieldDecorationRegistry.DEC_ERROR)
.getImage());
else
errorIconLabel.setImage(FieldDecorationRegistry
.getDefault().getFieldDecoration(
FieldDecorationRegistry.DEC_WARNING)
.getImage());
}
}
}
/**
* Get the memento used for storing preferences and state for this view.
*
* @return the memento
*/
protected IMemento getMemento() {
return mMemento;
}
/**
* Stores the checked state of each of the custom fields in the view.
*/
@Override
public void saveState(IMemento memento) {
TableItem[] items = getXSWTView().getCustomFieldsTable().getItems();
for (int i = 0; i < items.length; i++) {
TableItem item = items[i];
String key = OrderTicketView.CUSTOM_FIELD_VIEW_SAVED_STATE_KEY_PREFIX
+ item.getText(1);
memento.putInteger(key, (item.getChecked() ? 1 : 0));
}
}
/**
* Set the focus on the Side control (in the case of a new order) or the
* Quantity control (in the case of a replace order).
*/
@Override
public void setFocus() {
IOrderTicket ticket = getXSWTView();
if (ticket.getSideCombo().isEnabled()) {
ticket.getSideCombo().setFocus();
} else {
ticket.getQuantityText().setFocus();
}
}
@Override
public void dispose() {
getModel().getOrderObservable().removeValueChangeListener(
mFocusListener);
mObservablesManager.dispose();
super.dispose();
}
/**
*
*
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: OrderTicketView.java 16899 2014-05-11 16:03:04Z colin $
* @since 2.4.0
*/
@ClassVersion("$Id: OrderTicketView.java 16899 2014-05-11 16:03:04Z colin $")
public final static class AlgoLabelProvider
extends LabelProvider
{
/* (non-Javadoc)
* @see org.eclipse.jface.viewers.LabelProvider#getText(java.lang.Object)
*/
@Override
public String getText(Object inElement)
{
if(inElement == null || !(inElement instanceof BrokerAlgo)) {
return null;
}
return ((BrokerAlgo)inElement).getAlgoSpec().getName();
}
}
}