This commit is contained in:
Sergey Solovyev 2011-12-01 17:57:42 +04:00
parent 7822663989
commit def00244a5
12 changed files with 454 additions and 64 deletions

View File

@ -12,7 +12,8 @@
a:targetSdkVersion="8"/>
<application a:icon="@drawable/icon"
a:label="@string/c_app_name">
a:label="@string/c_app_name"
a:name=".ApplicationContext">
<activity a:name=".CalculatorActivity"
a:label="@string/c_app_name">
@ -30,8 +31,7 @@
<activity a:name=".CalculatorHistoryActivity"
a:label="@string/c_app_history"
a:configChanges="orientation|keyboardHidden">
</activity>
a:configChanges="orientation|keyboardHidden"/>
<activity a:name=".AboutActivity"
a:label="@string/c_about"
@ -55,23 +55,18 @@
<activity a:name=".CalculatorFunctionsActivity"
a:label="@string/c_functions"
a:configChanges="orientation|keyboardHidden">
</activity>
a:configChanges="orientation|keyboardHidden"/>
<activity a:name=".CalculatorOperatorsActivity"
a:label="@string/c_operators"
a:configChanges="orientation|keyboardHidden">
</activity>
a:configChanges="orientation|keyboardHidden"/>
<activity a:name=".CalculatorVarsActivity"
a:label="@string/c_vars_and_constants"
a:configChanges="orientation|keyboardHidden">
</activity>
a:configChanges="orientation|keyboardHidden"/>
<activity a:name=".CalculatorPlotActivity"
a:label="@string/c_plot_graph"
a:configChanges="orientation|keyboardHidden">
</activity>
a:label="@string/c_plot_graph"/>
</application>
</manifest>

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:a="http://schemas.android.com/apk/res/android"
a:id="@+id/plot_view_container"
a:orientation="horizontal"
a:layout_width="fill_parent"
a:layout_height="fill_parent">
<LinearLayout
a:id="@+id/plot_graph_container"
a:orientation="vertical"
a:layout_width="wrap_content"
a:layout_height="wrap_content"
a:layout_weight="3">
</LinearLayout>
<LinearLayout
a:orientation="vertical"
a:layout_width="wrap_content"
a:layout_height="wrap_content"
a:layout_weight="1">
<TextView a:layout_height="wrap_content"
a:layout_width="fill_parent"
a:padding="6dp"
style="@style/default_text_size"
a:text="@string/c_min_x_value"/>
<org.solovyev.android.view.widgets.NumberPicker
a:id="@+id/plot_x_min_value"
a:layout_width="wrap_content"
a:layout_height="wrap_content"/>
<TextView a:layout_height="wrap_content"
a:layout_width="fill_parent"
a:padding="6dp"
style="@style/default_text_size"
a:text="@string/c_max_x_value"/>
<org.solovyev.android.view.widgets.NumberPicker
a:id="@+id/plot_x_max_value"
a:layout_width="wrap_content"
a:layout_height="wrap_content"/>
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:a="http://schemas.android.com/apk/res/android"
a:id="@+id/plot_view_container"
a:orientation="vertical"
a:layout_width="fill_parent"
a:layout_height="fill_parent">
<LinearLayout
a:id="@+id/plot_graph_container"
a:orientation="horizontal"
a:layout_width="wrap_content"
a:layout_height="wrap_content"
a:layout_weight="3">
</LinearLayout>
<LinearLayout
a:orientation="horizontal"
a:layout_width="wrap_content"
a:layout_height="wrap_content"
a:layout_weight="1">
<TextView a:layout_height="wrap_content"
a:layout_width="fill_parent"
a:padding="6dp"
style="@style/default_text_size"
a:text="@string/c_min_x_value"/>
<org.solovyev.android.view.widgets.NumberPicker
a:id="@+id/plot_x_min_value"
a:layout_width="wrap_content"
a:layout_height="wrap_content"/>
<TextView a:layout_height="wrap_content"
a:layout_width="fill_parent"
a:padding="6dp"
style="@style/default_text_size"
a:text="@string/c_max_x_value"/>
<org.solovyev.android.view.widgets.NumberPicker
a:id="@+id/plot_x_max_value"
a:layout_width="wrap_content"
a:layout_height="wrap_content"/>
</LinearLayout>
</LinearLayout>

View File

@ -277,5 +277,7 @@ Check the \'Round result\' preference in application settings - it should be tur
<string name="msg_6">Infinite loop is detected in expression</string>
<string name="c_plot_graph">Graph</string>
<string name="c_min_x_value">From</string>
<string name="c_max_x_value">To</string>
</resources>

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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();

View File

@ -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<Runnable> pendingOperation = new MutableObject<Runnable>();
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;
}
}
}

View File

@ -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;

View File

@ -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

View File

@ -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<Integer> buttonIds = null;
private static final Map<String, Map<String, String>> captions = new HashMap<String, Map<String, String>>();
// first map: key: language id, value: map of captions and translations
// second mal: key: caption id, value: translation
private final Map<String, Map<String, String>> captions = new HashMap<String, Map<String, String>>();
private Class<?> resourceClass;
private Context context;
public List<Integer> 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<String, String> captionsByLanguage = new HashMap<String, String>();
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<String, String> 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<String, String> 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;

View File

@ -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;