/*
* 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.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.*;
/**
* Common class for both floating point (with arbitrary precision of decimal digits) and
* integer, non-negative numeric ranges.
* <p>
* The widget is composed of a {@link Scale} and a {@link Spinner}, both of which can be
* used to modify the current value of the attribute.
*/
abstract class NumericRangeEditorBase extends AttributeEditorAdapter
{
/** */
private Scale scale;
/** */
private Spinner spinner;
/**
* A temporary flag used to avoid event looping.
*/
private boolean duringSelection;
/*
* Numeric ranges.
*/
private int min;
private int max;
private int precisionDigits;
private int increment;
private int pageIncrement;
private boolean minBounded;
private boolean maxBounded;
/**
* Value multiplier needed to convert between fixed precision floating point values
* and integers.
*/
private final double multiplier;
/** Tooltip with allowed range. */
private String tooltip;
private int maxSpinnerWidth;
/**
* A copy of the current value of this editor. Added because it turns out
* spinner widgets emit delayed events (and cause extra events to be fired because
* they don't update their state immediately).
*/
private int currentValue;
/**
* @param precisionDigits Number of digits after decimal separator.
*/
public NumericRangeEditorBase(int precisionDigits)
{
this.precisionDigits = precisionDigits;
this.multiplier = Math.pow(10, precisionDigits);
}
@Override
protected AttributeEditorInfo init(Map<String,Object> defaultValues)
{
return new AttributeEditorInfo(2, false);
}
/**
* Initialize numeric ranges, according to the descriptor's definition.
*/
protected final void setRanges(int min, int max, int increment, int pageIncrement)
{
this.min = min;
this.minBounded = (min != Integer.MIN_VALUE);
this.max = max;
this.maxBounded = (max != Integer.MAX_VALUE);
this.increment = increment;
this.pageIncrement = pageIncrement;
if (!minBounded && !maxBounded)
{
this.tooltip = "Valid range: unbounded";
}
else
{
this.tooltip = "Valid range: [" + to_s(min) + "; " + to_s(max) + "]";
}
}
/*
* Return the current editor value.
*/
@Override
public Object getValue()
{
return currentValue;
}
/*
*
*/
@Override
public void createEditor(Composite parent, int gridColumns)
{
final GridDataFactory factory = GUIFactory.editorGridData();
final GridData spinnerLayoutData = factory.create();
spinnerLayoutData.horizontalSpan = 1;
spinnerLayoutData.grabExcessHorizontalSpace = false;
spinnerLayoutData.verticalAlignment = SWT.CENTER;
final GridData scaleLayoutData = factory.span(gridColumns - 1, 1).grab(true,
false).create();
if (minBounded && maxBounded)
{
createScale(parent);
scale.setLayoutData(scaleLayoutData);
}
else
{
spinnerLayoutData.horizontalSpan = 2;
spinnerLayoutData.grabExcessHorizontalSpace = true;
}
createSpinner(parent);
spinner.setSelection(spinner.getMaximum());
spinnerLayoutData.widthHint = maxSpinnerWidth;
spinnerLayoutData.horizontalAlignment = SWT.FILL;
spinner.setLayoutData(spinnerLayoutData);
}
/**
* Create the scale control.
*/
private void createScale(Composite holder)
{
scale = new Scale(holder, SWT.HORIZONTAL);
scale.setMaximum(max);
scale.setMinimum(min);
scale.setIncrement(increment);
scale.setPageIncrement(pageIncrement);
scale.setToolTipText(tooltip);
/*
* Hook event listener to the scale component.
*/
scale.addSelectionListener(new SelectionAdapter()
{
public void widgetSelected(SelectionEvent e)
{
if (scale.getSelection() != currentValue)
propagateNewValue(scale.getSelection());
}
});
scale.addMouseWheelListener(new MouseWheelListener()
{
public void mouseScrolled(MouseEvent e)
{
if (scale.getSelection() != currentValue)
propagateNewValue(scale.getSelection());
}
});
}
/**
* Create the spinner control.
*/
private void createSpinner(Composite holder)
{
spinner = new Spinner(holder, SWT.BORDER);
/*
* Calculate maximum spinner width (consistently among all editors).
*/
spinner.setMaximum(1000);
spinner.setMinimum(0);
spinner.setDigits(2);
this.maxSpinnerWidth = spinner.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;
/*
* Now proceed with setting the actual values.
*/
spinner.setMaximum(max);
spinner.setMinimum(min);
spinner.setSelection(min);
spinner.setDigits(precisionDigits);
spinner.setToolTipText(tooltip);
if (minBounded && maxBounded)
{
spinner.setIncrement(increment);
spinner.setPageIncrement(pageIncrement);
}
spinner.addSelectionListener(new SelectionAdapter()
{
public void widgetSelected(SelectionEvent e)
{
if (currentValue != spinner.getSelection())
propagateNewValue(spinner.getSelection());
}
});
}
/**
* Propagates value change event to all listeners and updates
* GUI widgets.
*/
protected final void propagateNewValue(int value)
{
if (!this.duringSelection)
{
this.duringSelection = true;
this.currentValue = value;
if (spinner != null && spinner.getSelection() != value)
{
spinner.setSelection(value);
}
if (scale != null && scale.getSelection() != value)
{
scale.setSelection(value);
}
this.duringSelection = false;
fireAttributeChanged(new AttributeEvent(this));
}
}
/**
* Convert between double values and integer values (taking into account precision
* shift).
*/
protected final int to_i(double v)
{
if (v == Double.MIN_VALUE)
{
return Integer.MIN_VALUE;
}
if (v == Double.MAX_VALUE)
{
return Integer.MAX_VALUE;
}
if (Double.isNaN(v))
{
return 0;
}
return (int) Math.round(v * multiplier);
}
/**
* Convert between double values and integer values (taking into account precision
* shift).
*/
protected final double to_d(int i)
{
return i / multiplier;
}
/*
* Converts the given argument to a human-readable string.
*/
private String to_s(int v)
{
if (v == Integer.MIN_VALUE)
{
return "-\u221E";
}
else if (v == Integer.MAX_VALUE)
{
return "\u221E";
}
else
{
return String.format("%." + precisionDigits + "f", to_d(v));
}
}
}