diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 63493307..5e2d0bb4 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -12,7 +12,8 @@
a:targetSdkVersion="8"/>
+ a:label="@string/c_app_name"
+ a:name=".ApplicationContext">
@@ -30,8 +31,7 @@
-
+ a:configChanges="orientation|keyboardHidden"/>
-
+ a:configChanges="orientation|keyboardHidden"/>
-
+ a:configChanges="orientation|keyboardHidden"/>
-
+ a:configChanges="orientation|keyboardHidden"/>
-
+ a:label="@string/c_plot_graph"/>
\ No newline at end of file
diff --git a/res/layout-land/calc_plot_view.xml b/res/layout-land/calc_plot_view.xml
new file mode 100644
index 00000000..a9380099
--- /dev/null
+++ b/res/layout-land/calc_plot_view.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout-port/calc_plot_view.xml b/res/layout-port/calc_plot_view.xml
new file mode 100644
index 00000000..1ca5792f
--- /dev/null
+++ b/res/layout-port/calc_plot_view.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 6ea43e89..1f3a2be3 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -277,5 +277,7 @@ Check the \'Round result\' preference in application settings - it should be tur
Infinite loop is detected in expression
Graph
+ From
+ To
diff --git a/src/main/java/org/solovyev/android/calculator/ApplicationContext.java b/src/main/java/org/solovyev/android/calculator/ApplicationContext.java
new file mode 100644
index 00000000..2d440493
--- /dev/null
+++ b/src/main/java/org/solovyev/android/calculator/ApplicationContext.java
@@ -0,0 +1,23 @@
+package org.solovyev.android.calculator;
+
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * User: serso
+ * Date: 12/1/11
+ * Time: 1:21 PM
+ */
+public class ApplicationContext extends android.app.Application {
+
+ @NotNull
+ private static ApplicationContext instance;
+
+ public ApplicationContext() {
+ instance = this;
+ }
+
+ @NotNull
+ public static ApplicationContext getInstance() {
+ return instance;
+ }
+}
diff --git a/src/main/java/org/solovyev/android/calculator/CalculatorActivity.java b/src/main/java/org/solovyev/android/calculator/CalculatorActivity.java
index 536f969a..229dc526 100644
--- a/src/main/java/org/solovyev/android/calculator/CalculatorActivity.java
+++ b/src/main/java/org/solovyev/android/calculator/CalculatorActivity.java
@@ -74,7 +74,7 @@ public class CalculatorActivity extends Activity implements FontSizeAdjuster, Sh
super.onCreate(savedInstanceState);
setLayout(preferences);
- ResourceCache.instance.initCaptions(R.string.class, this);
+ ResourceCache.instance.initCaptions(ApplicationContext.getInstance(), R.string.class);
firstTimeInit(preferences);
vibrator = (Vibrator) this.getSystemService(VIBRATOR_SERVICE);
diff --git a/src/main/java/org/solovyev/android/calculator/CalculatorActivityLauncher.java b/src/main/java/org/solovyev/android/calculator/CalculatorActivityLauncher.java
index 4c06922f..9954faa5 100644
--- a/src/main/java/org/solovyev/android/calculator/CalculatorActivityLauncher.java
+++ b/src/main/java/org/solovyev/android/calculator/CalculatorActivityLauncher.java
@@ -16,7 +16,9 @@ import org.achartengine.model.XYMultipleSeriesDataset;
import org.achartengine.model.XYSeries;
import org.achartengine.renderer.XYMultipleSeriesRenderer;
import org.achartengine.renderer.XYSeriesRenderer;
+import org.achartengine.util.MathHelper;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import org.solovyev.android.calculator.help.HelpActivity;
import org.solovyev.common.utils.StringUtils;
@@ -56,47 +58,12 @@ public class CalculatorActivityLauncher {
}
public static void plotGraph(@NotNull final Context context, @NotNull Generic generic, @NotNull Constant constant) throws ArithmeticException {
-
- final XYSeries series = new XYSeries(generic.toString());
-
- final double min = -10;
- final double max = 10;
- final double step = 0.5;
- double x = min;
- while (x <= max) {
- Generic numeric = generic.substitute(constant, Expression.valueOf(x)).numeric();
- series.add(x, unwrap(numeric));
- x += step;
- }
- final XYMultipleSeriesDataset data = new XYMultipleSeriesDataset();
- data.addSeries(series);
- final XYMultipleSeriesRenderer renderer = new XYMultipleSeriesRenderer();
- renderer.addSeriesRenderer(new XYSeriesRenderer());
- final Intent intent = ChartFactory.getLineChartIntent(context, data, renderer);
+ final Intent intent = new Intent();
+ intent.putExtra(CalculatorPlotActivity.INPUT, new CalculatorPlotActivity.Input(generic.toString(), constant.getName()));
intent.setClass(context, CalculatorPlotActivity.class);
context.startActivity(intent);
}
- private static double unwrap(Generic numeric) {
- if ( numeric instanceof JsclInteger) {
- return ((JsclInteger) numeric).intValue();
- } else if ( numeric instanceof NumericWrapper ) {
- return unwrap(((NumericWrapper) numeric).content());
- } else {
- throw new ArithmeticException();
- }
- }
-
- private static double unwrap(Numeric content) {
- if (content instanceof Real) {
- return ((Real) content).doubleValue();
- } else if ( content instanceof Complex) {
- return ((Complex) content).realPart();
- } else {
- throw new ArithmeticException();
- }
- }
-
public static void createVar(@NotNull final Context context, @NotNull CalculatorModel calculatorModel) {
if (calculatorModel.getDisplay().isValid() ) {
final String varValue = calculatorModel.getDisplay().getText().toString();
diff --git a/src/main/java/org/solovyev/android/calculator/CalculatorPlotActivity.java b/src/main/java/org/solovyev/android/calculator/CalculatorPlotActivity.java
index 26dc7939..ff0f0564 100644
--- a/src/main/java/org/solovyev/android/calculator/CalculatorPlotActivity.java
+++ b/src/main/java/org/solovyev/android/calculator/CalculatorPlotActivity.java
@@ -6,12 +6,262 @@
package org.solovyev.android.calculator;
-import org.achartengine.GraphicalActivity;
+import android.app.Activity;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.Toast;
+import jscl.math.Expression;
+import jscl.math.Generic;
+import jscl.math.JsclInteger;
+import jscl.math.NumericWrapper;
+import jscl.math.function.Constant;
+import jscl.math.numeric.Complex;
+import jscl.math.numeric.Numeric;
+import jscl.math.numeric.Real;
+import jscl.text.ParseException;
+import org.achartengine.ChartFactory;
+import org.achartengine.GraphicalView;
+import org.achartengine.chart.AbstractChart;
+import org.achartengine.chart.LineChart;
+import org.achartengine.model.XYMultipleSeriesDataset;
+import org.achartengine.model.XYSeries;
+import org.achartengine.renderer.BasicStroke;
+import org.achartengine.renderer.XYMultipleSeriesRenderer;
+import org.achartengine.renderer.XYSeriesRenderer;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.solovyev.android.view.widgets.NumberPicker;
+import org.solovyev.common.utils.MutableObject;
+
+import java.io.Serializable;
/**
* User: serso
* Date: 12/1/11
* Time: 12:40 AM
*/
-public class CalculatorPlotActivity extends GraphicalActivity{
+public class CalculatorPlotActivity extends Activity {
+
+ private static final int DEFAULT_NUMBER_OF_STEPS = 100;
+
+ private static final int DEFAULT_MIN_NUMBER = -10;
+
+ private static final int DEFAULT_MAX_NUMBER = 10;
+
+ public static final String INPUT = "org.solovyev.android.calculator.CalculatorPlotActivity_input";
+
+ public static final long EVAL_DELAY_MILLIS = 1000;
+
+ /**
+ * The encapsulated graphical view.
+ */
+ private GraphicalView graphicalView;
+
+ @NotNull
+ private Generic expression;
+
+ @NotNull
+ private Constant variable;
+
+ private double minValue = DEFAULT_MIN_NUMBER;
+
+ private double maxValue = DEFAULT_MAX_NUMBER;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Bundle extras = getIntent().getExtras();
+
+ final Input input = (Input) extras.getSerializable(INPUT);
+
+ try {
+ this.expression = Expression.valueOf(input.getExpression());
+ this.variable = new Constant(input.getVariableName());
+
+ String title = extras.getString(ChartFactory.TITLE);
+ if (title == null) {
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ } else if (title.length() > 0) {
+ setTitle(title);
+ }
+
+ setContentView(R.layout.calc_plot_view);
+
+ setGraphicalView(minValue, maxValue);
+
+ final NumberPicker minXNumberPicker = (NumberPicker)findViewById(R.id.plot_x_min_value);
+ final NumberPicker maxXNumberPicker = (NumberPicker)findViewById(R.id.plot_x_max_value);
+
+ minXNumberPicker.setRange(Integer.MIN_VALUE, Integer.MAX_VALUE);
+ minXNumberPicker.setCurrent(DEFAULT_MIN_NUMBER);
+ maxXNumberPicker.setRange(Integer.MIN_VALUE, Integer.MAX_VALUE);
+ maxXNumberPicker.setCurrent(DEFAULT_MAX_NUMBER);
+
+
+ minXNumberPicker.setOnChangeListener(new BoundariesChangeListener(true));
+ maxXNumberPicker.setOnChangeListener(new BoundariesChangeListener(false));
+
+ } catch (ParseException e) {
+ Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
+ finish();
+ }
+ }
+
+ private void setGraphicalView(final double minValue, final double maxValue) {
+ final ViewGroup graphContainer = (ViewGroup) findViewById(R.id.plot_graph_container);
+
+ if (graphicalView != null) {
+ graphContainer.removeView(graphicalView);
+ }
+
+ graphicalView = new GraphicalView(this, prepareChart(minValue, maxValue, expression, variable));
+ graphContainer.addView(graphicalView);
+ }
+
+ @NotNull
+ private final static MutableObject pendingOperation = new MutableObject();
+
+ private class BoundariesChangeListener implements NumberPicker.OnChangedListener {
+
+ private boolean min;
+
+ private BoundariesChangeListener(boolean min) {
+ this.min = min;
+ }
+
+
+ @Override
+ public void onChanged(NumberPicker picker, int oldVal, final int newVal) {
+ if (min) {
+ minValue = newVal;
+ } else {
+ maxValue = newVal;
+ }
+
+ pendingOperation.setObject(new Runnable() {
+ @Override
+ public void run() {
+ // allow only one runner at one time
+ synchronized (pendingOperation) {
+ //lock all operations with history
+ if (pendingOperation.getObject() == this) {
+ // actually nothing shall be logged while text operations are done
+ setGraphicalView(CalculatorPlotActivity.this.minValue, CalculatorPlotActivity.this.maxValue);
+ }
+ }
+ }
+ });
+
+ new Handler().postDelayed(pendingOperation.getObject(), EVAL_DELAY_MILLIS);
+ }
+ }
+
+ private static AbstractChart prepareChart(final double minValue, final double maxValue, @NotNull final Generic expression, @NotNull final Constant variable) {
+ final XYSeries realSeries = new XYSeries(expression.toString());
+ final XYSeries imagSeries = new XYSeries("Im(" + expression.toString() + ")");
+
+ boolean imagExists = false;
+
+ final double min = Math.min(minValue, maxValue);
+ final double max = Math.max(minValue, maxValue);
+ final int numberOfSteps = DEFAULT_NUMBER_OF_STEPS;
+ final double step = Math.max((max - min) / numberOfSteps, 0.001);
+ double x = min;
+ while (x <= max) {
+ Generic numeric = expression.substitute(variable, Expression.valueOf(x)).numeric();
+ final Complex c = unwrap(numeric);
+ realSeries.add(x, prepareY(c.realPart()));
+ imagSeries.add(x, prepareY(c.imaginaryPart()));
+ if (c.imaginaryPart() != 0d) {
+ imagExists = true;
+ }
+ x += step;
+ }
+
+ final XYMultipleSeriesDataset data = new XYMultipleSeriesDataset();
+ data.addSeries(realSeries);
+ if (imagExists) {
+ data.addSeries(imagSeries);
+ }
+
+ final XYMultipleSeriesRenderer renderer = new XYMultipleSeriesRenderer();
+ renderer.setZoomEnabled(false);
+ renderer.setZoomEnabled(false, false);
+ renderer.addSeriesRenderer(createCommonRenderer());
+ renderer.setPanEnabled(false);
+ renderer.setPanEnabled(false, false);
+ if (imagExists) {
+ final XYSeriesRenderer imagRenderer = createCommonRenderer();
+ imagRenderer.setStroke(BasicStroke.DOTTED);
+ renderer.addSeriesRenderer(imagRenderer);
+ }
+
+ return new LineChart(data, renderer);
+ }
+
+ @NotNull
+ private static XYSeriesRenderer createCommonRenderer() {
+ final XYSeriesRenderer renderer = new XYSeriesRenderer();
+ renderer.setColor(Color.WHITE);
+ renderer.setStroke(BasicStroke.SOLID);
+ return renderer;
+ }
+
+ private static double prepareY(double y) {
+ if (Double.isNaN(y) || Double.isInfinite(y)) {
+ return 0d;
+ } else {
+ return y;
+ }
+ }
+
+ @NotNull
+ private static Complex unwrap(@Nullable Generic numeric) {
+ if (numeric instanceof JsclInteger) {
+ return Complex.valueOf(((JsclInteger) numeric).intValue(), 0d);
+ } else if (numeric instanceof NumericWrapper) {
+ return unwrap(((NumericWrapper) numeric).content());
+ } else {
+ throw new ArithmeticException();
+ }
+ }
+
+ @NotNull
+ private static Complex unwrap(@Nullable Numeric content) {
+ if (content instanceof Real) {
+ return Complex.valueOf(((Real) content).doubleValue(), 0d);
+ } else if (content instanceof Complex) {
+ return ((Complex) content);
+ } else {
+ throw new ArithmeticException();
+ }
+ }
+
+
+ public static class Input implements Serializable {
+
+ @NotNull
+ private String expression;
+
+ @NotNull
+ private String variableName;
+
+ public Input(@NotNull String expression, @NotNull String variableName) {
+ this.expression = expression;
+ this.variableName = variableName;
+ }
+
+ @NotNull
+ public String getExpression() {
+ return expression;
+ }
+
+ @NotNull
+ public String getVariableName() {
+ return variableName;
+ }
+ }
}
diff --git a/src/main/java/org/solovyev/android/calculator/jscl/FromJsclNumericTextProcessor.java b/src/main/java/org/solovyev/android/calculator/jscl/FromJsclNumericTextProcessor.java
index 88b6410c..a03206b5 100644
--- a/src/main/java/org/solovyev/android/calculator/jscl/FromJsclNumericTextProcessor.java
+++ b/src/main/java/org/solovyev/android/calculator/jscl/FromJsclNumericTextProcessor.java
@@ -6,7 +6,7 @@
package org.solovyev.android.calculator.jscl;
-import jscl.text.Messages;
+import jscl.text.msg.Messages;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.android.calculator.math.MathType;
diff --git a/src/main/java/org/solovyev/android/view/AutoResizeTextView.java b/src/main/java/org/solovyev/android/view/AutoResizeTextView.java
index 317f8f0e..1840209e 100644
--- a/src/main/java/org/solovyev/android/view/AutoResizeTextView.java
+++ b/src/main/java/org/solovyev/android/view/AutoResizeTextView.java
@@ -29,6 +29,8 @@ public class AutoResizeTextView extends TextView {
// Minimum text size for this text view
public static final float MIN_TEXT_SIZE = 20;
+ private float initialTextSize = 100;
+
// Interface for resize notifications
public interface OnTextResizeListener {
public void onTextResize(TextView textView, float oldSize, float newSize);
@@ -207,7 +209,8 @@ public class AutoResizeTextView extends TextView {
Log.d(this.getClass().getName(), "Old text size: " + oldTextSize);
// If there is a max text size set, use the lesser of that and the default text size
- float newTextSize = 100;
+ // todo serso: +2 is a workaround => to be checked boundary constraints
+ float newTextSize = initialTextSize + 2;
int newTextHeight;
@@ -227,7 +230,7 @@ public class AutoResizeTextView extends TextView {
if (newTextSize <= minTextSize) {
break;
}
- newTextSize = Math.max(newTextSize - 2, minTextSize);
+ newTextSize = Math.max(newTextSize - 1, minTextSize);
newTextHeight = getTextRect(text, textPaint, width, newTextSize);
logDimensions(newTextSize, newTextHeight);
}
@@ -236,7 +239,7 @@ public class AutoResizeTextView extends TextView {
if (newTextSize <= minTextSize) {
break;
}
- newTextSize = Math.max(newTextSize + 2, minTextSize);
+ newTextSize = Math.max(newTextSize + 1, minTextSize);
newTextHeight = getTextRect(text, textPaint, width, newTextSize);
logDimensions(newTextSize, newTextHeight);
}
@@ -247,6 +250,8 @@ public class AutoResizeTextView extends TextView {
}
}
+ initialTextSize = newTextSize;
+
// If we had reached our minimum text size and still don't fit, append an ellipsis
if (addEllipsis && newTextSize == minTextSize && newTextHeight > height) {
// Draw using a static layout
diff --git a/src/main/java/org/solovyev/android/view/prefs/ResourceCache.java b/src/main/java/org/solovyev/android/view/prefs/ResourceCache.java
index cd4bf37f..9dcf45fb 100644
--- a/src/main/java/org/solovyev/android/view/prefs/ResourceCache.java
+++ b/src/main/java/org/solovyev/android/view/prefs/ResourceCache.java
@@ -7,11 +7,14 @@
package org.solovyev.android.view.prefs;
import android.app.Activity;
+import android.content.Context;
+import android.content.res.Resources;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import org.solovyev.android.calculator.CalculatorActivity;
import org.solovyev.android.view.widgets.DragButton;
import java.lang.reflect.Field;
@@ -33,7 +36,13 @@ public enum ResourceCache {
// ids of buttons in R.class
private List buttonIds = null;
- private static final Map> captions = new HashMap>();
+ // first map: key: language id, value: map of captions and translations
+ // second mal: key: caption id, value: translation
+ private final Map> captions = new HashMap>();
+
+ private Class> resourceClass;
+
+ private Context context;
public List getDragButtonIds() {
return dragButtonIds;
@@ -43,11 +52,28 @@ public enum ResourceCache {
return buttonIds;
}
- public void initCaptions(@NotNull Class> resourceClass, @NotNull Activity activity) {
- final Locale locale = Locale.getDefault();
+ /**
+ * Method load captions for default locale using android R class
+ * @param context STATIC CONTEXT
+ * @param resourceClass class of captions in android (SUBCLASS of R class)
+ */
+ public void initCaptions(@NotNull Context context, @NotNull Class> resourceClass) {
+ initCaptions(context, resourceClass, Locale.getDefault());
+ }
- if (!captions.containsKey(locale.getLanguage())) {
+ /**
+ * Method load captions for specified locale using android R class
+ * @param context STATIC CONTEXT
+ * @param resourceClass class of captions in android (SUBCLASS of R class)
+ * @param locale language to be used for translation
+ */
+ public void initCaptions(@NotNull Context context, @NotNull Class> resourceClass, @NotNull Locale locale) {
+ assert this.resourceClass == null || this.resourceClass.equals(resourceClass);
+ this.context = context;
+ this.resourceClass = resourceClass;
+
+ if (!initialized(locale)) {
final Map captionsByLanguage = new HashMap();
for (Field field : resourceClass.getDeclaredFields()) {
@@ -55,7 +81,7 @@ public enum ResourceCache {
if (Modifier.isFinal(modifiers) && Modifier.isStatic(modifiers)) {
try {
int captionId = field.getInt(resourceClass);
- captionsByLanguage.put(field.getName(), activity.getString(captionId));
+ captionsByLanguage.put(field.getName(), context.getString(captionId));
} catch (IllegalAccessException e) {
Log.e(ResourceCache.class.getName(), e.getMessage());
}
@@ -66,13 +92,39 @@ public enum ResourceCache {
}
}
+ private boolean initialized(@NotNull Locale locale) {
+ return captions.containsKey(locale.getLanguage());
+ }
+
+ /**
+ * @param captionId id of caption to be translated
+ * @return translation by caption id in default language, null if no translation in default language present
+ */
@Nullable
public String getCaption(@NotNull String captionId) {
- final Locale locale = Locale.getDefault();
+ return getCaption(captionId, Locale.getDefault());
+ }
- final Map captionsByLanguage = captions.get(locale.getLanguage());
+
+ /**
+ * @param captionId id of caption to be translated
+ * @param locale language to be used for translation
+ * @return translation by caption id in specified language, null if no translation in specified language present
+ */
+ @Nullable
+ public String getCaption(@NotNull String captionId, @NotNull final Locale locale) {
+ Map captionsByLanguage = captions.get(locale.getLanguage());
if (captionsByLanguage != null) {
return captionsByLanguage.get(captionId);
+ } else {
+ assert resourceClass != null && context != null;
+
+ initCaptions(context, resourceClass, locale);
+
+ captionsByLanguage = captions.get(locale.getLanguage());
+ if (captionsByLanguage != null) {
+ return captionsByLanguage.get(captionId);
+ }
}
return null;
diff --git a/src/main/java/org/solovyev/android/view/widgets/NumberPicker.java b/src/main/java/org/solovyev/android/view/widgets/NumberPicker.java
index 61d427ae..728161a0 100644
--- a/src/main/java/org/solovyev/android/view/widgets/NumberPicker.java
+++ b/src/main/java/org/solovyev/android/view/widgets/NumberPicker.java
@@ -412,7 +412,7 @@ public class NumberPicker extends LinearLayout {
}
private static final char[] DIGIT_CHARACTERS = new char[] {
- '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
+ '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
};
private NumberPickerButton mIncrementButton;