diff --git a/res/layout-land/number_picker.xml b/res/layout-land/number_picker.xml
new file mode 100644
index 00000000..cad57c54
--- /dev/null
+++ b/res/layout-land/number_picker.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index fa76f4f6..5fbeb4ad 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -1,7 +1,12 @@
-
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml
index 8f3ebff2..a09699a8 100644
--- a/res/xml/preferences.xml
+++ b/res/xml/preferences.xml
@@ -1,15 +1,27 @@
-
+
-
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/org/solovyev/android/calculator/DragButtonCalibrationActivity.java b/src/main/java/org/solovyev/android/calculator/DragButtonCalibrationActivity.java
index be578578..50eb77e8 100644
--- a/src/main/java/org/solovyev/android/calculator/DragButtonCalibrationActivity.java
+++ b/src/main/java/org/solovyev/android/calculator/DragButtonCalibrationActivity.java
@@ -21,6 +21,7 @@ import org.solovyev.android.view.widgets.*;
import org.solovyev.common.collections.ManyValuedHashMap;
import org.solovyev.common.collections.ManyValuedMap;
import org.solovyev.common.utils.Interval;
+import org.solovyev.common.utils.IntervalImpl;
import org.solovyev.common.utils.MathUtils;
import org.solovyev.common.utils.Point2d;
@@ -237,7 +238,7 @@ public class DragButtonCalibrationActivity extends Activity {
final float max = preferences.getFloat(preferenceType.name() + "_" + dragDirection.name() + "_" + PREFERENCES_MAX, defaultMax);
if (min != DEFAULT_VALUE && max != DEFAULT_VALUE) {
- final DragPreference directionPreference = new DragPreference(dragDirection, new Interval(min, max));
+ final DragPreference directionPreference = new DragPreference(dragDirection, new IntervalImpl(min, max));
Preference preference = result.getPreferencesMap().get(preferenceType);
if (preference == null) {
@@ -262,10 +263,10 @@ public class DragButtonCalibrationActivity extends Activity {
private DragDirection direction;
@NotNull
- private Interval interval;
+ private Interval interval;
- public DragPreference(@NotNull DragDirection direction, @NotNull Interval interval) {
+ public DragPreference(@NotNull DragDirection direction, @NotNull Interval interval) {
this.direction = direction;
this.interval = interval;
}
@@ -280,11 +281,11 @@ public class DragButtonCalibrationActivity extends Activity {
}
@NotNull
- public Interval getInterval() {
+ public Interval getInterval() {
return interval;
}
- public void setInterval(@NotNull Interval interval) {
+ public void setInterval(@NotNull Interval interval) {
this.interval = interval;
}
}
diff --git a/src/main/java/org/solovyev/android/view/prefs/AbstractDialogPreference.java b/src/main/java/org/solovyev/android/view/prefs/AbstractDialogPreference.java
index 6176da0f..ff309979 100644
--- a/src/main/java/org/solovyev/android/view/prefs/AbstractDialogPreference.java
+++ b/src/main/java/org/solovyev/android/view/prefs/AbstractDialogPreference.java
@@ -4,10 +4,10 @@ import android.content.Context;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.view.Gravity;
-import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
/**
* User: serso
@@ -20,19 +20,19 @@ public class AbstractDialogPreference extends DialogPreference {
protected static final String androidns = "http://schemas.android.com/apk/res/android";
@NotNull
- protected TextView splashText, valueText;
+ protected TextView valueTextView;
@NotNull
protected final Context context;
- protected String dialogMessage, suffix;
+ @Nullable
+ protected String dialogMessage;
public AbstractDialogPreference(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
dialogMessage = attrs.getAttributeValue(androidns, "dialogMessage");
- suffix = attrs.getAttributeValue(androidns, "text");
}
@Override
@@ -42,18 +42,19 @@ public class AbstractDialogPreference extends DialogPreference {
layout.setOrientation(LinearLayout.VERTICAL);
layout.setPadding(6, 6, 6, 6);
- splashText = new TextView(context);
- if (dialogMessage != null)
+ final TextView splashText = new TextView(context);
+ if (dialogMessage != null) {
splashText.setText(dialogMessage);
+ }
layout.addView(splashText);
- valueText = new TextView(context);
- valueText.setGravity(Gravity.CENTER_HORIZONTAL);
- valueText.setTextSize(32);
+ valueTextView = new TextView(context);
+ valueTextView.setGravity(Gravity.CENTER_HORIZONTAL);
+ valueTextView.setTextSize(32);
params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.FILL_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
- layout.addView(valueText, params);
+ layout.addView(valueTextView, params);
return layout;
}
diff --git a/src/main/java/org/solovyev/android/view/prefs/IntegerRangeSeekBarPreference.java b/src/main/java/org/solovyev/android/view/prefs/IntegerRangeSeekBarPreference.java
new file mode 100644
index 00000000..d6a17a58
--- /dev/null
+++ b/src/main/java/org/solovyev/android/view/prefs/IntegerRangeSeekBarPreference.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2009-2011. Created by serso aka se.solovyev.
+ * For more information, please, contact se.solovyev@gmail.com
+ * or visit http://se.solovyev.org
+ */
+
+package org.solovyev.android.view.prefs;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import org.jetbrains.annotations.NotNull;
+import org.solovyev.common.utils.Converter;
+
+/**
+ * User: serso
+ * Date: 9/19/11
+ * Time: 10:04 PM
+ */
+public class IntegerRangeSeekBarPreference extends RangeSeekBarPreference {
+
+ public IntegerRangeSeekBarPreference(@NotNull Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @NotNull
+ @Override
+ Converter getConverter() {
+ return new Converter() {
+ @NotNull
+ @Override
+ public Integer convert(@NotNull String value) {
+ return Integer.valueOf(value);
+ }
+ };
+ }
+}
diff --git a/src/main/java/org/solovyev/android/view/prefs/RangeSeekBarPreference.java b/src/main/java/org/solovyev/android/view/prefs/RangeSeekBarPreference.java
new file mode 100644
index 00000000..79748b74
--- /dev/null
+++ b/src/main/java/org/solovyev/android/view/prefs/RangeSeekBarPreference.java
@@ -0,0 +1,117 @@
+package org.solovyev.android.view.prefs;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+import org.jetbrains.annotations.NotNull;
+import org.solovyev.android.view.widgets.AbstractRangeSeekBar;
+import org.solovyev.android.view.widgets.NumberRangeSeekBar;
+import org.solovyev.common.utils.CollectionsUtils;
+import org.solovyev.common.utils.Converter;
+import org.solovyev.common.utils.StringMapper;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * User: serso
+ * Date: 9/19/11
+ * Time: 12:27 PM
+ */
+public abstract class RangeSeekBarPreference extends AbstractDialogPreference implements AbstractRangeSeekBar.OnRangeSeekBarChangeListener {
+
+ public final static String localNameSpace = "http://schemas.android.com/apk/res/org.solovyev.android.calculator";
+
+ @NotNull
+ private AbstractRangeSeekBar rangeSeekBar;
+
+ @NotNull
+ private T min;
+
+ @NotNull
+ private T max;
+
+ @NotNull
+ private T selectedMin;
+
+ @NotNull
+ private T selectedMax;
+
+
+ public RangeSeekBarPreference(@NotNull Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+
+ final Converter c = getConverter();
+
+ String minString = attrs.getAttributeValue(localNameSpace, "min");
+ min = c.convert(minString == null ? "0" : minString);
+ String maxString = attrs.getAttributeValue(localNameSpace, "max");
+ max = c.convert(maxString == null ? "100" : maxString);
+
+
+ this.rangeSeekBar = new NumberRangeSeekBar(min, max, context);
+ rangeSeekBar.setOnRangeSeekBarChangeListener(this);
+ }
+
+ public void setMin(@NotNull String min) {
+ this.min = getConverter().convert(min);
+ }
+
+ public void setMax(@NotNull String max) {
+ this.max = getConverter().convert(max);
+ }
+
+ @NotNull
+ abstract Converter getConverter();
+
+ @Override
+ protected LinearLayout onCreateDialogView() {
+ final LinearLayout result = super.onCreateDialogView();
+
+ this.rangeSeekBar = new NumberRangeSeekBar(min, max, context);
+ rangeSeekBar.setOnRangeSeekBarChangeListener(this);
+ initRangeSeekBar();
+
+ result.addView(rangeSeekBar);
+
+ return result;
+ }
+
+ @Override
+ protected void onBindDialogView(View v) {
+ super.onBindDialogView(v);
+ initRangeSeekBar();
+ }
+
+ private void initRangeSeekBar() {
+ rangeSeekBar.setSelectedMinValue(selectedMin);
+ rangeSeekBar.setSelectedMaxValue(selectedMax);
+ }
+
+ @Override
+ protected void onSetInitialValue(boolean restore, Object defaultValue) {
+ super.onSetInitialValue(restore, defaultValue);
+
+ final List values;
+ if (restore) {
+ values = CollectionsUtils.split(getPersistedString("0;100"), ";", new StringMapper());
+ } else {
+ values = CollectionsUtils.split(String.valueOf(defaultValue), ";", new StringMapper());
+ }
+
+ selectedMin = getConverter().convert(values.get(0));
+ selectedMax = getConverter().convert(values.get(1));
+ }
+
+
+ @Override
+ public void rangeSeekBarValuesChanged(T minValue, T maxValue) {
+ final String value = CollectionsUtils.formatValue(Arrays.asList(String.valueOf(minValue), String.valueOf(maxValue)), ";", new StringMapper());
+ if (shouldPersist()) {
+ persistString(value);
+ }
+ callChangeListener(value);
+ }
+}
diff --git a/src/main/java/org/solovyev/android/view/prefs/RangeSeekPreference.java b/src/main/java/org/solovyev/android/view/prefs/RangeSeekPreference.java
deleted file mode 100644
index 8486b3ab..00000000
--- a/src/main/java/org/solovyev/android/view/prefs/RangeSeekPreference.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package org.solovyev.android.view.prefs;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.LinearLayout;
-import org.jetbrains.annotations.NotNull;
-import org.solovyev.android.view.widgets.AbstractRangeSeekBar;
-import org.solovyev.android.view.widgets.NumberRangeSeekBar;
-
-/**
- * User: serso
- * Date: 9/19/11
- * Time: 12:27 PM
- */
-public abstract class RangeSeekPreference extends AbstractDialogPreference implements AbstractRangeSeekBar.OnRangeSeekBarChangeListener {
-
- @NotNull
- private final AbstractRangeSeekBar rangeSeekBar;
-
- public RangeSeekPreference(@NotNull Context context, AttributeSet attrs) {
- super(context, attrs);
- this.rangeSeekBar = new NumberRangeSeekBar(getMinValue(), getMaxValue(), context);
- rangeSeekBar.setOnRangeSeekBarChangeListener(this);
- }
-
- @NotNull
- abstract T getMinValue();
-
- @NotNull
- abstract T getMaxValue();
-
- @Override
- protected LinearLayout onCreateDialogView() {
- final LinearLayout result = super.onCreateDialogView();
-
- result.addView(rangeSeekBar);
-
- return result;
- }
-
- @Override
- public void rangeSeekBarValuesChanged(T minValue, T maxValue) {
- //To change body of implemented methods use File | Settings | File Templates.
- }
-}
diff --git a/src/main/java/org/solovyev/android/view/prefs/SeekBarPreference.java b/src/main/java/org/solovyev/android/view/prefs/SeekBarPreference.java
index e7c4b61c..9398cd3b 100644
--- a/src/main/java/org/solovyev/android/view/prefs/SeekBarPreference.java
+++ b/src/main/java/org/solovyev/android/view/prefs/SeekBarPreference.java
@@ -6,15 +6,13 @@
package org.solovyev.android.view.prefs;
-import android.preference.DialogPreference;
import android.content.Context;
import android.util.AttributeSet;
-import android.view.Gravity;
import android.view.View;
import android.widget.SeekBar;
-import android.widget.TextView;
import android.widget.LinearLayout;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
/* The following code was written by Matthew Wiggins
@@ -30,17 +28,20 @@ public class SeekBarPreference extends AbstractDialogPreference implements SeekB
private int defaultValue, max, value = 0;
+ @Nullable
+ protected String valueText;
+
public SeekBarPreference(Context context, AttributeSet attrs) {
super(context, attrs);
defaultValue = attrs.getAttributeIntValue(androidns, "defaultValue", 0);
max = attrs.getAttributeIntValue(androidns, "max", 100);
-
+ valueText = attrs.getAttributeValue(androidns, "text");
}
@Override
protected LinearLayout onCreateDialogView() {
- final LinearLayout layout = onCreateDialogView();
+ final LinearLayout layout = super.onCreateDialogView();
seekBar = new SeekBar(context);
seekBar.setOnSeekBarChangeListener(this);
@@ -49,17 +50,20 @@ public class SeekBarPreference extends AbstractDialogPreference implements SeekB
if (shouldPersist())
value = getPersistedInt(defaultValue);
- seekBar.setMax(max);
- seekBar.setProgress(value);
+ initSeekBar();
return layout;
}
+ private void initSeekBar() {
+ seekBar.setMax(max);
+ seekBar.setProgress(value);
+ }
+
@Override
protected void onBindDialogView(View v) {
super.onBindDialogView(v);
- seekBar.setMax(max);
- seekBar.setProgress(value);
+ initSeekBar();
}
@Override
@@ -73,7 +77,7 @@ public class SeekBarPreference extends AbstractDialogPreference implements SeekB
public void onProgressChanged(SeekBar seek, int value, boolean fromTouch) {
String t = String.valueOf(value);
- valueText.setText(suffix == null ? t : t.concat(suffix));
+ valueTextView.setText(valueText == null ? t : t.concat(valueText));
if (shouldPersist())
persistInt(value);
callChangeListener(new Integer(value));
diff --git a/src/main/java/org/solovyev/android/view/widgets/AbstractRangeSeekBar.java b/src/main/java/org/solovyev/android/view/widgets/AbstractRangeSeekBar.java
index 29e46d1c..6afc59ef 100644
--- a/src/main/java/org/solovyev/android/view/widgets/AbstractRangeSeekBar.java
+++ b/src/main/java/org/solovyev/android/view/widgets/AbstractRangeSeekBar.java
@@ -14,6 +14,7 @@ import android.widget.ImageView;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.android.calculator.R;
+import org.solovyev.common.math.LinearNormalizer;
import org.solovyev.common.utils.Converter;
/**
@@ -53,7 +54,8 @@ public abstract class AbstractRangeSeekBar extends ImageView {
@NotNull
private final T minValue, maxValue;
- private final double dMinValue, dMaxValue;
+ @NotNull
+ private final LinearNormalizer normalizer;
private double normalizedMinValue = 0d;
@@ -84,8 +86,7 @@ public abstract class AbstractRangeSeekBar extends ImageView {
this.toDoubleConverter = getToDoubleConverter();
this.toTConverter = getToTConverter();
- dMinValue = toDoubleConverter.convert(minValue);
- dMaxValue = toDoubleConverter.convert(maxValue);
+ normalizer = new LinearNormalizer(toDoubleConverter.convert(minValue), toDoubleConverter.convert(maxValue));
}
@NotNull
@@ -141,13 +142,8 @@ public abstract class AbstractRangeSeekBar extends ImageView {
*
* @param value The Number value to set the minimum value to. Will be clamped to given absolute minimum/maximum range.
*/
- public void setSelectedMinValue(T value) {
- // in case absoluteMinValue == absoluteMaxValue, avoid division by zero when normalizing.
- if (0 == (dMaxValue - dMinValue)) {
- setNormalizedMinValue(0d);
- } else {
- setNormalizedMinValue(normalizeValue(value));
- }
+ public void setSelectedMinValue(@NotNull T value) {
+ setNormalizedMinValue(normalizeValue(value));
}
/**
@@ -164,13 +160,8 @@ public abstract class AbstractRangeSeekBar extends ImageView {
*
* @param value The Number value to set the maximum value to. Will be clamped to given absolute minimum/maximum range.
*/
- public void setSelectedMaxValue(T value) {
- // in case absoluteMinValue == absoluteMaxValue, avoid division by zero when normalizing.
- if (0 == (dMaxValue - dMinValue)) {
- setNormalizedMaxValue(1d);
- } else {
- setNormalizedMaxValue(normalizeValue(value));
- }
+ public void setSelectedMaxValue(@NotNull T value) {
+ setNormalizedMaxValue(normalizeValue(value));
}
/**
@@ -330,7 +321,7 @@ public abstract class AbstractRangeSeekBar extends ImageView {
*/
@SuppressWarnings("unchecked")
private T denormalizeValue(double normalized) {
- return toTConverter.convert(dMinValue + normalized * (dMaxValue - dMinValue));
+ return toTConverter.convert(normalizer.denormalize(normalized));
}
/**
@@ -340,8 +331,7 @@ public abstract class AbstractRangeSeekBar extends ImageView {
* @return The normalized double.
*/
private double normalizeValue(T value) {
- assert 0 != dMaxValue - dMinValue;
- return (toDoubleConverter.convert(value) - dMinValue) / (dMaxValue - dMinValue);
+ return normalizer.normalize(toDoubleConverter.convert(value));
}
/**
@@ -390,27 +380,4 @@ public abstract class AbstractRangeSeekBar extends ImageView {
MIN, MAX
}
- private class LinearNormalizer {
-
- private final double minValue;
- private final double maxValue;
-
- private LinearNormalizer(double minValue, double maxValue) {
- this.minValue = minValue;
- this.maxValue = maxValue;
- }
-
- double normalize(double value){
- if ((dMaxValue - dMinValue) != 0d) {
- return (value - dMinValue) / (dMaxValue - dMinValue);
- } else {
- return 1d;
- }
- }
-
- double denormalize(double value){
- return dMinValue + value * (dMaxValue - dMinValue);
- }
-
- }
}
diff --git a/src/main/java/org/solovyev/android/view/widgets/NumberRangeSeekBar.java b/src/main/java/org/solovyev/android/view/widgets/NumberRangeSeekBar.java
index 386d5b32..0f4cf9f9 100644
--- a/src/main/java/org/solovyev/android/view/widgets/NumberRangeSeekBar.java
+++ b/src/main/java/org/solovyev/android/view/widgets/NumberRangeSeekBar.java
@@ -2,13 +2,9 @@ package org.solovyev.android.view.widgets;
import android.content.Context;
import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
import org.solovyev.common.utils.Converter;
-import org.solovyev.common.utils.Mapper;
import org.solovyev.common.utils.NumberValuer;
-import java.math.BigDecimal;
-
/**
* User: serso
* Date: 9/19/11
@@ -38,6 +34,7 @@ public class NumberRangeSeekBar extends AbstractRangeSeekBar getToTConverter() {
return new Converter() {
+ @NotNull
@Override
public T convert(@NotNull Double aDouble) {
return (T) numberType.toNumber(aDouble);
@@ -52,60 +49,4 @@ public class NumberRangeSeekBar extends AbstractRangeSeekBar NumberType fromNumber(E value) throws IllegalArgumentException {
-
- for (NumberType numberType : NumberType.values()) {
- if (numberType.underlyingClass.isInstance(value)) {
- return numberType;
- }
- }
-
- throw new IllegalArgumentException("Number class '" + value.getClass().getName() + "' is not supported");
- }
-
- public T toNumber(double value) {
-
- switch (this) {
- case LONG:
- return (T)new Long((long) value);
- case DOUBLE:
- return (T)new Double(value);
- case INTEGER:
- return (T)new Integer((int) value);
- case FLOAT:
- return (T)new Float((float) value);
- case SHORT:
- return (T)new Short((short) value);
- case BYTE:
- return (T)new Byte((byte) value);
- case BIG_DECIMAL:
- return (T)new BigDecimal(value);
- }
-
- throw new InstantiationError("can't convert " + this + " to a Number object");
- }
- }
}
diff --git a/src/main/java/org/solovyev/android/view/widgets/NumberType.java b/src/main/java/org/solovyev/android/view/widgets/NumberType.java
new file mode 100644
index 00000000..be4ead69
--- /dev/null
+++ b/src/main/java/org/solovyev/android/view/widgets/NumberType.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2009-2011. Created by serso aka se.solovyev.
+ * For more information, please, contact se.solovyev@gmail.com
+ * or visit http://se.solovyev.org
+ */
+
+package org.solovyev.android.view.widgets;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.math.BigDecimal;
+
+/**
+ * Utility enumeration used to convert between Numbers and doubles.
+ *
+ * @author Stephan Tittel (stephan.tittel@kom.tu-darmstadt.de)
+ */
+enum NumberType {
+
+ LONG(Long.class),
+ DOUBLE(Double.class),
+ INTEGER(Integer.class),
+ FLOAT(Float.class),
+ SHORT(Short.class),
+ BYTE(Byte.class),
+ BIG_DECIMAL(BigDecimal.class);
+
+ @NotNull
+ private final Class underlyingClass;
+
+ NumberType(@NotNull Class underlyingClass) {
+ this.underlyingClass = underlyingClass;
+ }
+
+ @NotNull
+ public static NumberType fromNumber(E value) throws IllegalArgumentException {
+
+ for (NumberType numberType : NumberType.values()) {
+ if (numberType.underlyingClass.isInstance(value)) {
+ return numberType;
+ }
+ }
+
+ throw new IllegalArgumentException("Number class '" + value.getClass().getName() + "' is not supported");
+ }
+
+ public T toNumber(double value) {
+
+ switch (this) {
+ case LONG:
+ return (T)new Long((long) value);
+ case DOUBLE:
+ return (T)new Double(value);
+ case INTEGER:
+ return (T)new Integer((int) value);
+ case FLOAT:
+ return (T)new Float((float) value);
+ case SHORT:
+ return (T)new Short((short) value);
+ case BYTE:
+ return (T)new Byte((byte) value);
+ case BIG_DECIMAL:
+ return (T)new BigDecimal(value);
+ }
+
+ throw new InstantiationError("can't convert " + this + " to a Number object");
+ }
+}
diff --git a/src/main/java/org/solovyev/android/view/widgets/SimpleOnDragListener.java b/src/main/java/org/solovyev/android/view/widgets/SimpleOnDragListener.java
index 4f12f3a8..13a7b109 100644
--- a/src/main/java/org/solovyev/android/view/widgets/SimpleOnDragListener.java
+++ b/src/main/java/org/solovyev/android/view/widgets/SimpleOnDragListener.java
@@ -85,8 +85,8 @@ public class SimpleOnDragListener implements OnDragListener, DragPreferencesChan
return result;
}
- private boolean isInInterval(@NotNull Interval interval, float value) {
- return interval.getStart() - MathUtils.MIN_AMOUNT <= value && value <= interval.getEnd() + MathUtils.MIN_AMOUNT;
+ private boolean isInInterval(@NotNull Interval interval, float value) {
+ return interval.getLeftBorder() - MathUtils.MIN_AMOUNT <= value && value <= interval.getRightBorder() + MathUtils.MIN_AMOUNT;
}
@Override
diff --git a/src/main/java/org/solovyev/common/math/LinearNormalizer.java b/src/main/java/org/solovyev/common/math/LinearNormalizer.java
new file mode 100644
index 00000000..c0b3a3af
--- /dev/null
+++ b/src/main/java/org/solovyev/common/math/LinearNormalizer.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2009-2011. Created by serso aka se.solovyev.
+ * For more information, please, contact se.solovyev@gmail.com
+ * or visit http://se.solovyev.org
+ */
+
+package org.solovyev.common.math;
+
+/**
+* User: serso
+* Date: 9/19/11
+* Time: 9:31 PM
+*/
+public class LinearNormalizer {
+
+ private final double min;
+ private final double max;
+
+ public LinearNormalizer(double min, double max) {
+ this.min = min;
+ this.max = max;
+ }
+
+ public double normalize(double value){
+ if ((max - min) != 0d) {
+ return (value - min) / (max - min);
+ } else {
+ return 1d;
+ }
+ }
+
+ public double denormalize(double value){
+ return min + value * (max - min);
+ }
+
+}