/*
* Carrot2 project.
*
* Copyright (C) 2002-2016, Dawid Weiss, Stanisław Osiński.
* All rights reserved.
*
* Refer to the full license file "carrot2.LICENSE"
* in the root folder of the repository checkout or at:
* http://www.carrot2.org/carrot2.LICENSE
*/
package org.carrot2.workbench.editors.impl.numeric;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.carrot2.workbench.core.helpers.GUIFactory;
import org.carrot2.workbench.editors.*;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.*;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.*;
/**
* Base class for unbounded editors.
*/
abstract class UnboundedEditorBase<T extends Number> extends AttributeEditorAdapter
{
/**
* Text box for editing.
*/
protected Text text;
/** Last valid value cache. */
private T lastValidValue;
/**
* A temporary flag used to avoid event looping.
*/
private boolean duringSelection;
protected String tooltip;
protected T min;
protected T max;
protected T pageIncrement;
/*
*
*/
@Override
protected AttributeEditorInfo init(Map<String,Object> defaultValues)
{
return new AttributeEditorInfo(1, false);
}
/*
* Return the current editor value.
*/
@Override
public Object getValue()
{
return lastValidValue;
}
/*
*
*/
@Override
public void setValue(Object object)
{
if (object != null && object.equals(getValue()))
{
return;
}
if (object != null && !(object instanceof Number))
{
return;
}
if (object == null)
{
propagateNewValue(null);
}
else
{
propagateNewValue(to_s((Number) object));
}
}
/*
*
*/
@Override
public void createEditor(Composite parent, int gridColumns)
{
createText(parent, gridColumns);
}
/**
* Create the scale control.
*/
private void createText(Composite parent, int gridColumns)
{
final GridDataFactory factory = GUIFactory.editorGridData();
final GridData layoutData = factory.create();
layoutData.horizontalSpan = gridColumns;
layoutData.grabExcessHorizontalSpace = false;
layoutData.verticalAlignment = SWT.CENTER;
text = new Text(parent, SWT.LEAD | SWT.SINGLE | SWT.BORDER);
text.setLayoutData(layoutData);
/*
* Hook event listener.
*/
text.addModifyListener(new ModifyListener()
{
public void modifyText(ModifyEvent e)
{
/*
* Propagate valid intermediate values as soon
* as the user types them in, but do not
* refresh {@link #text}, as it causes selection caret to be reset.
*/
if (isValid(text.getText()))
{
propagateNewValue(text.getText(), false);
}
}
});
text.addFocusListener(new FocusAdapter()
{
public void focusLost(FocusEvent e)
{
/*
* Update with last valid value on focus lost (if the intermediate
* value was for some reason invalid, for example consisted
* only of a '-' sign, it will be reverted to the last valid value.
*/
propagateNewValue(to_s((Number) lastValidValue));
}
});
text.addVerifyListener(new VerifyListener()
{
/*
* Allow only these modifications that result in a valid value or
* a temporary string that can lead to one.
*/
public void verifyText(VerifyEvent e)
{
if (duringSelection) return;
final String currentText = text.getText();
final String newText = currentText.substring(0, e.start) + e.text
+ currentText.substring(e.end);
e.doit = StringUtils.isEmpty(newText) || isValidForEditing(newText);
}
});
text.addListener(SWT.MouseWheel, new Listener()
{
/*
* On mouse wheel, update value by page increment and
* stop wheel event's propagation.
*/
public void handleEvent(Event event)
{
event.doit = false;
if (getValue() != null)
{
doPageIncrement(event.count > 0);
}
}
});
text.setToolTipText(tooltip);
}
/**
* {@Link #propagateNewValue(String, boolean)} and refresh {@link #text}'s value.
*/
protected final void propagateNewValue(String value)
{
propagateNewValue(value, true);
}
/**
* Propagates value change event to all listeners and updates GUI widgets.
*/
protected final void propagateNewValue(String value, boolean refreshTextBox)
{
if (!this.duringSelection)
{
this.duringSelection = true;
if (refreshTextBox)
{
this.text.setText(value == null ? "" : value);
}
if (isValid(value))
{
try
{
this.lastValidValue = toRange(to_v(value));
fireAttributeChanged(new AttributeEvent(this));
}
catch (NumberFormatException e)
{
// Just skip.
}
}
this.duringSelection = false;
}
}
/*
*
*/
protected abstract void doPageIncrement(boolean positive);
/*
*
*/
protected abstract T toRange(T d);
/**
* Should return <code>true</code> if and only if the value is valid
* and will parse with {@link #to_v}.
*/
protected abstract boolean isValid(String value);
/**
* Should return <code>true</code> if the value is valid as a temporal value of the
* input text editor, but may not validate with {@link #isValid(String)}. Examples:
* <code>-</code>, <code>+</code> and an empty string are all valid temporary
* states, but not numbers.
*/
protected abstract boolean isValidForEditing(String value);
/**
* Parse the number and return its string representation.
*/
protected abstract String to_s(Number object);
/*
*
*/
protected abstract T to_v(String value);
}