Directory renamed
This commit is contained in:
@@ -0,0 +1,851 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
||||
* For more information, please, contact se.solovyev@gmail.com
|
||||
*/
|
||||
|
||||
package org.solovyev.android.calculator;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.os.Vibrator;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.text.ClipboardManager;
|
||||
import android.text.Html;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.*;
|
||||
import android.widget.*;
|
||||
import com.google.ads.AdView;
|
||||
import jscl.AngleUnit;
|
||||
import jscl.NumeralBase;
|
||||
import net.robotmedia.billing.BillingController;
|
||||
import net.robotmedia.billing.IBillingObserver;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.AndroidUtils;
|
||||
import org.solovyev.android.FontSizeAdjuster;
|
||||
import org.solovyev.android.ResourceCache;
|
||||
import org.solovyev.android.calculator.about.CalculatorReleaseNotesActivity;
|
||||
import org.solovyev.android.calculator.history.CalculatorHistory;
|
||||
import org.solovyev.android.calculator.history.CalculatorHistoryState;
|
||||
import org.solovyev.android.calculator.math.MathType;
|
||||
import org.solovyev.android.calculator.model.CalculatorEngine;
|
||||
import org.solovyev.android.calculator.view.AngleUnitsButton;
|
||||
import org.solovyev.android.calculator.view.NumeralBasesButton;
|
||||
import org.solovyev.android.history.HistoryDragProcessor;
|
||||
import org.solovyev.android.prefs.BooleanPreference;
|
||||
import org.solovyev.android.prefs.IntegerPreference;
|
||||
import org.solovyev.android.prefs.Preference;
|
||||
import org.solovyev.android.prefs.StringPreference;
|
||||
import org.solovyev.android.view.ColorButton;
|
||||
import org.solovyev.android.view.VibratorContainer;
|
||||
import org.solovyev.android.view.drag.*;
|
||||
import org.solovyev.common.utils.Announcer;
|
||||
import org.solovyev.common.utils.Point2d;
|
||||
import org.solovyev.common.utils.StringUtils;
|
||||
import org.solovyev.common.utils.history.HistoryAction;
|
||||
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.util.Locale;
|
||||
|
||||
public class CalculatorActivity extends Activity implements FontSizeAdjuster, SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
private static final int HVGA_WIDTH_PIXELS = 320;
|
||||
|
||||
@Nullable
|
||||
private IBillingObserver billingObserver;
|
||||
|
||||
public static enum Theme {
|
||||
|
||||
default_theme(ThemeType.other, R.style.default_theme),
|
||||
violet_theme(ThemeType.other, R.style.violet_theme),
|
||||
light_blue_theme(ThemeType.other, R.style.light_blue_theme),
|
||||
metro_blue_theme(ThemeType.metro, R.style.metro_blue_theme),
|
||||
metro_purple_theme(ThemeType.metro, R.style.metro_purple_theme),
|
||||
metro_green_theme(ThemeType.metro, R.style.metro_green_theme);
|
||||
|
||||
@NotNull
|
||||
private final ThemeType themeType;
|
||||
|
||||
@NotNull
|
||||
private final Integer themeId;
|
||||
|
||||
Theme(@NotNull ThemeType themeType, Integer themeId) {
|
||||
this.themeType = themeType;
|
||||
this.themeId = themeId;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ThemeType getThemeType() {
|
||||
return themeType;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Integer getThemeId() {
|
||||
return themeId;
|
||||
}
|
||||
}
|
||||
|
||||
public static enum ThemeType {
|
||||
metro,
|
||||
other
|
||||
}
|
||||
|
||||
public static enum Layout {
|
||||
main_calculator(R.layout.main_calculator),
|
||||
main_cellphone(R.layout.main_cellphone),
|
||||
simple(R.layout.main_calculator);
|
||||
|
||||
private final int layoutId;
|
||||
|
||||
Layout(int layoutId) {
|
||||
this.layoutId = layoutId;
|
||||
}
|
||||
|
||||
public int getLayoutId() {
|
||||
return layoutId;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Preferences {
|
||||
@NotNull
|
||||
private static final String APP_VERSION_P_KEY = "application.version";
|
||||
private static final int APP_VERSION_DEFAULT = -1;
|
||||
public static final Preference<Integer> appVersion = new IntegerPreference(APP_VERSION_P_KEY, APP_VERSION_DEFAULT);
|
||||
|
||||
private static final Preference<Integer> appOpenedCounter = new IntegerPreference(APP_OPENED_COUNTER_P_KEY, APP_OPENED_COUNTER_P_DEFAULT);
|
||||
private static final Preference<Boolean> feedbackWindowShown = new BooleanPreference(FEEDBACK_WINDOW_SHOWN_P_KEY, FEEDBACK_WINDOW_SHOWN_P_DEFAULT);
|
||||
private static final Preference<Theme> theme = StringPreference.newInstance(THEME_P_KEY, THEME_P_DEFAULT, Theme.class);
|
||||
private static final Preference<Layout> layout = StringPreference.newInstance(LAYOUT_P_KEY, LAYOUT_P_DEFAULT, Layout.class);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static final String LAYOUT_P_KEY = "org.solovyev.android.calculator.CalculatorActivity_calc_layout";
|
||||
private static final Layout LAYOUT_P_DEFAULT = Layout.main_calculator;
|
||||
|
||||
@NotNull
|
||||
private static final String THEME_P_KEY = "org.solovyev.android.calculator.CalculatorActivity_calc_theme";
|
||||
private static final Theme THEME_P_DEFAULT = Theme.metro_blue_theme;
|
||||
|
||||
@NotNull
|
||||
private static final String APP_OPENED_COUNTER_P_KEY = "app_opened_counter";
|
||||
private static final Integer APP_OPENED_COUNTER_P_DEFAULT = 0;
|
||||
|
||||
@NotNull
|
||||
public static final String FEEDBACK_WINDOW_SHOWN_P_KEY = "feedback_window_shown";
|
||||
public static final boolean FEEDBACK_WINDOW_SHOWN_P_DEFAULT = false;
|
||||
|
||||
@NotNull
|
||||
public static final String SHOW_RELEASE_NOTES_P_KEY = "org.solovyev.android.calculator.CalculatorActivity_show_release_notes";
|
||||
public static final boolean SHOW_RELEASE_NOTES_P_DEFAULT = true;
|
||||
|
||||
@NotNull
|
||||
public static final String USE_BACK_AS_PREV_P_KEY = "org.solovyev.android.calculator.CalculatorActivity_use_back_button_as_prev";
|
||||
public static final boolean USE_BACK_AS_PREV_DEFAULT = false;
|
||||
|
||||
@NotNull
|
||||
private final Announcer<DragPreferencesChangeListener> dpclRegister = new Announcer<DragPreferencesChangeListener>(DragPreferencesChangeListener.class);
|
||||
|
||||
@NotNull
|
||||
private CalculatorModel calculatorModel;
|
||||
|
||||
private volatile boolean initialized;
|
||||
|
||||
@NotNull
|
||||
private Theme theme;
|
||||
|
||||
@NotNull
|
||||
private Layout layout;
|
||||
|
||||
@Nullable
|
||||
private Vibrator vibrator;
|
||||
|
||||
private boolean useBackAsPrev = USE_BACK_AS_PREV_DEFAULT;
|
||||
|
||||
@Nullable
|
||||
private AdView adView;
|
||||
|
||||
/**
|
||||
* Called when the activity is first created.
|
||||
*/
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
|
||||
CalculatorApplication.registerOnRemoteStackTrace();
|
||||
|
||||
final boolean customTitleSupported = requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
|
||||
|
||||
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
|
||||
setDefaultValues(preferences);
|
||||
|
||||
setTheme(preferences);
|
||||
super.onCreate(savedInstanceState);
|
||||
setLayout(preferences);
|
||||
|
||||
if (customTitleSupported) {
|
||||
try {
|
||||
getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.calc_title);
|
||||
final CalculatorAdditionalTitle additionalAdditionalTitleText = (CalculatorAdditionalTitle)findViewById(R.id.additional_title_text);
|
||||
additionalAdditionalTitleText.init(preferences);
|
||||
preferences.registerOnSharedPreferenceChangeListener(additionalAdditionalTitleText);
|
||||
} catch (ClassCastException e) {
|
||||
// super fix for issue with class cast in android.view.Window.setFeatureInt() (see app error reports)
|
||||
Log.e(CalculatorActivity.class.getName(), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
ResourceCache.instance.initCaptions(CalculatorApplication.getInstance(), R.string.class);
|
||||
|
||||
billingObserver = new CalculatorBillingObserver(this);
|
||||
BillingController.registerObserver(billingObserver);
|
||||
|
||||
firstTimeInit(preferences);
|
||||
|
||||
// init billing controller
|
||||
BillingController.checkBillingSupported(this);
|
||||
|
||||
vibrator = (Vibrator) this.getSystemService(VIBRATOR_SERVICE);
|
||||
|
||||
CalculatorHistory.instance.load(this, preferences);
|
||||
calculatorModel = CalculatorModel.instance.init(this, preferences, CalculatorEngine.instance);
|
||||
|
||||
dpclRegister.clear();
|
||||
|
||||
final SimpleOnDragListener.Preferences dragPreferences = SimpleOnDragListener.getPreferences(preferences, this);
|
||||
|
||||
setOnDragListeners(dragPreferences, preferences);
|
||||
|
||||
final OnDragListener historyOnDragListener = new OnDragListenerVibrator(newOnDragListener(new HistoryDragProcessor<CalculatorHistoryState>(this.calculatorModel), dragPreferences), vibrator, preferences);
|
||||
((DragButton) findViewById(R.id.historyButton)).setOnDragListener(historyOnDragListener);
|
||||
|
||||
((DragButton) findViewById(R.id.subtractionButton)).setOnDragListener(new OnDragListenerVibrator(newOnDragListener(new SimpleOnDragListener.DragProcessor() {
|
||||
@Override
|
||||
public boolean processDragEvent(@NotNull DragDirection dragDirection, @NotNull DragButton dragButton, @NotNull Point2d startPoint2d, @NotNull MotionEvent motionEvent) {
|
||||
if (dragDirection == DragDirection.down) {
|
||||
operatorsButtonClickHandler(dragButton);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}, dragPreferences), vibrator, preferences));
|
||||
|
||||
|
||||
final OnDragListener toPositionOnDragListener = new OnDragListenerVibrator(new SimpleOnDragListener(new CursorDragProcessor(calculatorModel), dragPreferences), vibrator, preferences);
|
||||
((DragButton) findViewById(R.id.rightButton)).setOnDragListener(toPositionOnDragListener);
|
||||
((DragButton) findViewById(R.id.leftButton)).setOnDragListener(toPositionOnDragListener);
|
||||
|
||||
final DragButton equalsButton = (DragButton) findViewById(R.id.equalsButton);
|
||||
if (equalsButton != null) {
|
||||
equalsButton.setOnDragListener(new OnDragListenerVibrator(newOnDragListener(new EvalDragProcessor(calculatorModel), dragPreferences), vibrator, preferences));
|
||||
}
|
||||
|
||||
final AngleUnitsButton angleUnitsButton = (AngleUnitsButton) findViewById(R.id.sixDigitButton);
|
||||
if (angleUnitsButton != null) {
|
||||
angleUnitsButton.setOnDragListener(new OnDragListenerVibrator(newOnDragListener(new AngleUnitsChanger(), dragPreferences), vibrator, preferences));
|
||||
}
|
||||
|
||||
final NumeralBasesButton clearButton = (NumeralBasesButton) findViewById(R.id.clearButton);
|
||||
if (clearButton != null) {
|
||||
clearButton.setOnDragListener(new OnDragListenerVibrator(newOnDragListener(new NumeralBasesChanger(), dragPreferences), vibrator, preferences));
|
||||
}
|
||||
|
||||
final DragButton varsButton = (DragButton) findViewById(R.id.varsButton);
|
||||
if (varsButton != null) {
|
||||
varsButton.setOnDragListener(new OnDragListenerVibrator(newOnDragListener(new VarsDragProcessor(), dragPreferences), vibrator, preferences));
|
||||
}
|
||||
|
||||
final DragButton roundBracketsButton = (DragButton) findViewById(R.id.roundBracketsButton);
|
||||
if ( roundBracketsButton != null ) {
|
||||
roundBracketsButton.setOnDragListener(new OnDragListenerVibrator(newOnDragListener(new RoundBracketsDragProcessor(), dragPreferences), vibrator, preferences));
|
||||
}
|
||||
|
||||
|
||||
CalculatorEngine.instance.softReset(this, preferences);
|
||||
|
||||
initMultiplicationButton();
|
||||
|
||||
if (theme.getThemeType() == ThemeType.metro) {
|
||||
// for metro themes we should turn off magic flames
|
||||
AndroidUtils.processViewsOfType(this.getWindow().getDecorView(), ColorButton.class, new AndroidUtils.ViewProcessor<ColorButton>() {
|
||||
@Override
|
||||
public void process(@NotNull ColorButton colorButton) {
|
||||
colorButton.setDrawMagicFlame(false);
|
||||
}
|
||||
});
|
||||
|
||||
fixMargins(2, 2);
|
||||
} else {
|
||||
fixMargins(1, 1);
|
||||
}
|
||||
|
||||
if (layout == Layout.simple) {
|
||||
toggleButtonDirectionText(R.id.oneDigitButton, false, DragDirection.left, DragDirection.up, DragDirection.down);
|
||||
toggleButtonDirectionText(R.id.twoDigitButton, false, DragDirection.left, DragDirection.up, DragDirection.down);
|
||||
toggleButtonDirectionText(R.id.threeDigitButton, false, DragDirection.left, DragDirection.up, DragDirection.down);
|
||||
|
||||
toggleButtonDirectionText(R.id.sixDigitButton, false, DragDirection.left, DragDirection.up, DragDirection.down);
|
||||
toggleButtonDirectionText(R.id.sevenDigitButton, false, DragDirection.left, DragDirection.up, DragDirection.down);
|
||||
toggleButtonDirectionText(R.id.eightDigitButton, false, DragDirection.left, DragDirection.up, DragDirection.down);
|
||||
|
||||
toggleButtonDirectionText(R.id.clearButton, false, DragDirection.left, DragDirection.up, DragDirection.down);
|
||||
|
||||
toggleButtonDirectionText(R.id.fourDigitButton, false, DragDirection.left, DragDirection.down);
|
||||
toggleButtonDirectionText(R.id.fiveDigitButton, false, DragDirection.left, DragDirection.down);
|
||||
|
||||
toggleButtonDirectionText(R.id.nineDigitButton, false, DragDirection.left);
|
||||
|
||||
toggleButtonDirectionText(R.id.multiplicationButton, false, DragDirection.left);
|
||||
toggleButtonDirectionText(R.id.plusButton, false, DragDirection.down, DragDirection.up);
|
||||
|
||||
}
|
||||
|
||||
//adView = AndroidUtils.createAndInflateAdView(this, R.id.ad_parent_view, ADMOB_USER_ID);
|
||||
|
||||
preferences.registerOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
|
||||
private void toggleButtonDirectionText(int id, boolean showDirectionText, @NotNull DragDirection... dragDirections) {
|
||||
final View v = findViewById(id);
|
||||
if (v instanceof DirectionDragButton ) {
|
||||
final DirectionDragButton button = (DirectionDragButton)v;
|
||||
for (DragDirection dragDirection : dragDirections) {
|
||||
button.showDirectionText(showDirectionText, dragDirection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void fixMargins(int marginLeft, int marginBottom) {
|
||||
// sad but true
|
||||
|
||||
final View equalsButton = findViewById(R.id.equalsButton);
|
||||
final View rightButton = findViewById(R.id.rightButton);
|
||||
final View leftButton = findViewById(R.id.leftButton);
|
||||
final View clearButton = findViewById(R.id.clearButton);
|
||||
final View eraseButton = findViewById(R.id.eraseButton);
|
||||
|
||||
int orientation = getResources().getConfiguration().orientation;
|
||||
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
setMarginsForView(equalsButton, marginLeft, marginBottom);
|
||||
setMarginsForView(calculatorModel.getDisplay(), marginLeft, marginBottom);
|
||||
} else if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
setMarginsForView(leftButton, marginLeft, marginBottom);
|
||||
setMarginsForView(eraseButton, marginLeft, marginBottom);
|
||||
setMarginsForView(clearButton, marginLeft, marginBottom);
|
||||
setMarginsForView(rightButton, marginLeft, marginBottom);
|
||||
// magic magic magic
|
||||
setMarginsForView(calculatorModel.getDisplay(), 3 * marginLeft, marginBottom);
|
||||
}
|
||||
}
|
||||
|
||||
private void setMarginsForView(@NotNull View view, int marginLeft, int marginBottom) {
|
||||
// IMPORTANT: this is workaround for probably android bug
|
||||
// currently margin values set in styles are not applied for some reasons to the views (using include tag) => set them manually
|
||||
|
||||
final DisplayMetrics dm = getResources().getDisplayMetrics();
|
||||
if (view.getLayoutParams() instanceof LinearLayout.LayoutParams) {
|
||||
final LinearLayout.LayoutParams oldParams = (LinearLayout.LayoutParams) view.getLayoutParams();
|
||||
final LinearLayout.LayoutParams newParams = new LinearLayout.LayoutParams(oldParams.width, oldParams.height, oldParams.weight);
|
||||
newParams.setMargins(AndroidUtils.toPixels(dm, marginLeft), 0, 0, AndroidUtils.toPixels(dm, marginBottom));
|
||||
view.setLayoutParams(newParams);
|
||||
}
|
||||
}
|
||||
|
||||
private class AngleUnitsChanger implements SimpleOnDragListener.DragProcessor {
|
||||
|
||||
private final DigitButtonDragProcessor processor = new DigitButtonDragProcessor(calculatorModel);
|
||||
|
||||
@Override
|
||||
public boolean processDragEvent(@NotNull DragDirection dragDirection,
|
||||
@NotNull DragButton dragButton,
|
||||
@NotNull Point2d startPoint2d,
|
||||
@NotNull MotionEvent motionEvent) {
|
||||
boolean result = false;
|
||||
|
||||
if (dragButton instanceof AngleUnitsButton) {
|
||||
if (dragDirection != DragDirection.left ) {
|
||||
final String directionText = ((AngleUnitsButton) dragButton).getText(dragDirection);
|
||||
if ( directionText != null ) {
|
||||
try {
|
||||
|
||||
final AngleUnit angleUnits = AngleUnit.valueOf(directionText);
|
||||
|
||||
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(CalculatorActivity.this);
|
||||
|
||||
CalculatorEngine.Preferences.angleUnit.putPreference(preferences, angleUnits);
|
||||
|
||||
Toast.makeText(CalculatorActivity.this, CalculatorActivity.this.getString(R.string.c_angle_units_changed_to, angleUnits.name()), Toast.LENGTH_LONG).show();
|
||||
|
||||
result = true;
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.d(this.getClass().getName(), "Unsupported angle units: " + directionText);
|
||||
}
|
||||
}
|
||||
} else if ( dragDirection == DragDirection.left ) {
|
||||
result = processor.processDragEvent(dragDirection, dragButton, startPoint2d, motionEvent);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private class NumeralBasesChanger implements SimpleOnDragListener.DragProcessor {
|
||||
|
||||
@Override
|
||||
public boolean processDragEvent(@NotNull DragDirection dragDirection,
|
||||
@NotNull DragButton dragButton,
|
||||
@NotNull Point2d startPoint2d,
|
||||
@NotNull MotionEvent motionEvent) {
|
||||
boolean result = false;
|
||||
|
||||
if ( dragButton instanceof NumeralBasesButton ) {
|
||||
final String directionText = ((NumeralBasesButton) dragButton).getText(dragDirection);
|
||||
if ( directionText != null ) {
|
||||
try {
|
||||
|
||||
final NumeralBase numeralBase = NumeralBase.valueOf(directionText);
|
||||
|
||||
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(CalculatorActivity.this);
|
||||
CalculatorEngine.Preferences.numeralBase.putPreference(preferences, numeralBase);
|
||||
|
||||
Toast.makeText(CalculatorActivity.this, CalculatorActivity.this.getString(R.string.c_numeral_base_changed_to, numeralBase.name()), Toast.LENGTH_LONG).show();
|
||||
|
||||
result = true;
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.d(this.getClass().getName(), "Unsupported numeral base: " + directionText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class VarsDragProcessor implements SimpleOnDragListener.DragProcessor {
|
||||
|
||||
@Override
|
||||
public boolean processDragEvent(@NotNull DragDirection dragDirection,
|
||||
@NotNull DragButton dragButton,
|
||||
@NotNull Point2d startPoint2d,
|
||||
@NotNull MotionEvent motionEvent) {
|
||||
boolean result = false;
|
||||
|
||||
if (dragDirection == DragDirection.up) {
|
||||
CalculatorActivityLauncher.createVar(CalculatorActivity.this, CalculatorActivity.this.calculatorModel);
|
||||
result = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private void setDefaultValues(@NotNull SharedPreferences preferences) {
|
||||
if (!CalculatorEngine.Preferences.groupingSeparator.isSet(preferences)) {
|
||||
final Locale locale = Locale.getDefault();
|
||||
if (locale != null) {
|
||||
final DecimalFormatSymbols decimalFormatSymbols = new DecimalFormatSymbols(locale);
|
||||
int index = MathType.grouping_separator.getTokens().indexOf(String.valueOf(decimalFormatSymbols.getGroupingSeparator()));
|
||||
final String groupingSeparator;
|
||||
if (index >= 0) {
|
||||
groupingSeparator = MathType.grouping_separator.getTokens().get(index);
|
||||
} else {
|
||||
groupingSeparator = " ";
|
||||
}
|
||||
|
||||
CalculatorEngine.Preferences.groupingSeparator.putPreference(preferences, groupingSeparator);
|
||||
}
|
||||
}
|
||||
|
||||
if (!CalculatorEngine.Preferences.angleUnit.isSet(preferences)) {
|
||||
CalculatorEngine.Preferences.angleUnit.putDefault(preferences);
|
||||
}
|
||||
|
||||
if (!CalculatorEngine.Preferences.numeralBase.isSet(preferences)) {
|
||||
CalculatorEngine.Preferences.numeralBase.putDefault(preferences);
|
||||
}
|
||||
|
||||
if (!CalculatorEngine.Preferences.multiplicationSign.isSet(preferences)) {
|
||||
if ( AndroidUtils.isPhoneModel(AndroidUtils.PhoneModel.samsung_galaxy_s) || AndroidUtils.isPhoneModel(AndroidUtils.PhoneModel.samsung_galaxy_s_2) ) {
|
||||
// workaround ofr samsung galaxy s phones
|
||||
CalculatorEngine.Preferences.multiplicationSign.putPreference(preferences, "*");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private synchronized void setOnDragListeners(@NotNull SimpleOnDragListener.Preferences dragPreferences, @NotNull SharedPreferences preferences) {
|
||||
final OnDragListener onDragListener = new OnDragListenerVibrator(newOnDragListener(new DigitButtonDragProcessor(calculatorModel), dragPreferences), vibrator, preferences);
|
||||
|
||||
for (Integer dragButtonId : ResourceCache.instance.getDragButtonIds()) {
|
||||
((DragButton) findViewById(dragButtonId)).setOnDragListener(onDragListener);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private SimpleOnDragListener newOnDragListener(@NotNull SimpleOnDragListener.DragProcessor dragProcessor,
|
||||
@NotNull SimpleOnDragListener.Preferences dragPreferences) {
|
||||
final SimpleOnDragListener onDragListener = new SimpleOnDragListener(dragProcessor, dragPreferences);
|
||||
dpclRegister.addListener(onDragListener);
|
||||
return onDragListener;
|
||||
}
|
||||
|
||||
private class OnDragListenerVibrator extends OnDragListenerWrapper {
|
||||
|
||||
private static final float VIBRATION_TIME_SCALE = 0.5f;
|
||||
|
||||
@NotNull
|
||||
private final VibratorContainer vibrator;
|
||||
|
||||
public OnDragListenerVibrator(@NotNull OnDragListener onDragListener,
|
||||
@Nullable Vibrator vibrator,
|
||||
@NotNull SharedPreferences preferences) {
|
||||
super(onDragListener);
|
||||
this.vibrator = new VibratorContainer(vibrator, preferences, VIBRATION_TIME_SCALE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDrag(@NotNull DragButton dragButton, @NotNull org.solovyev.android.view.drag.DragEvent event) {
|
||||
boolean result = super.onDrag(dragButton, event);
|
||||
|
||||
if (result) {
|
||||
vibrator.vibrate();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private synchronized void setLayout(@NotNull SharedPreferences preferences) {
|
||||
try {
|
||||
layout = Preferences.layout.getPreference(preferences);
|
||||
} catch (IllegalArgumentException e) {
|
||||
layout = LAYOUT_P_DEFAULT;
|
||||
}
|
||||
|
||||
setContentView(layout.getLayoutId());
|
||||
}
|
||||
|
||||
private synchronized void setTheme(@NotNull SharedPreferences preferences) {
|
||||
try {
|
||||
theme = Preferences.theme.getPreference(preferences);
|
||||
} catch (IllegalArgumentException e) {
|
||||
theme = THEME_P_DEFAULT;
|
||||
}
|
||||
|
||||
setTheme(theme.getThemeId());
|
||||
}
|
||||
|
||||
private synchronized void firstTimeInit(@NotNull SharedPreferences preferences) {
|
||||
if (!initialized) {
|
||||
final Integer appOpenedCounter = Preferences.appOpenedCounter.getPreference(preferences);
|
||||
if (appOpenedCounter != null) {
|
||||
Preferences.appOpenedCounter.putPreference(preferences, appOpenedCounter + 1);
|
||||
}
|
||||
|
||||
final int savedVersion = Preferences.appVersion.getPreference(preferences);
|
||||
|
||||
final int appVersion = AndroidUtils.getAppVersionCode(this, CalculatorActivity.class.getPackage().getName());
|
||||
|
||||
Preferences.appVersion.putPreference(preferences, appVersion);
|
||||
|
||||
boolean dialogShown = false;
|
||||
if (savedVersion == Preferences.APP_VERSION_DEFAULT) {
|
||||
// new start
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(this).setMessage(R.string.c_first_start_text);
|
||||
builder.setPositiveButton(android.R.string.ok, null);
|
||||
builder.setTitle(R.string.c_first_start_text_title);
|
||||
builder.create().show();
|
||||
dialogShown = true;
|
||||
} else {
|
||||
if (savedVersion < appVersion) {
|
||||
final boolean showReleaseNotes = preferences.getBoolean(SHOW_RELEASE_NOTES_P_KEY, SHOW_RELEASE_NOTES_P_DEFAULT);
|
||||
if (showReleaseNotes) {
|
||||
final String releaseNotes = CalculatorReleaseNotesActivity.getReleaseNotes(this, savedVersion + 1);
|
||||
if (!StringUtils.isEmpty(releaseNotes)) {
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(this).setMessage(Html.fromHtml(releaseNotes));
|
||||
builder.setPositiveButton(android.R.string.ok, null);
|
||||
builder.setTitle(R.string.c_release_notes);
|
||||
builder.create().show();
|
||||
dialogShown = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Log.d(this.getClass().getName(), "Application was opened " + appOpenedCounter + " time!");
|
||||
if (!dialogShown) {
|
||||
if ( appOpenedCounter != null && appOpenedCounter > 10 ) {
|
||||
final Boolean feedbackWindowShown = Preferences.feedbackWindowShown.getPreference(preferences);
|
||||
if ( feedbackWindowShown != null && !feedbackWindowShown ) {
|
||||
final LayoutInflater layoutInflater = (LayoutInflater) this.getSystemService(LAYOUT_INFLATER_SERVICE);
|
||||
final View view = layoutInflater.inflate(R.layout.feedback, null);
|
||||
|
||||
final TextView feedbackTextView = (TextView) view.findViewById(R.id.feedbackText);
|
||||
feedbackTextView.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(this).setView(view);
|
||||
builder.setPositiveButton(android.R.string.ok, null);
|
||||
builder.create().show();
|
||||
|
||||
dialogShown = true;
|
||||
Preferences.feedbackWindowShown.putPreference(preferences, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ResourceCache.instance.init(R.id.class, this);
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"UnusedDeclaration"})
|
||||
public void elementaryButtonClickHandler(@NotNull View v) {
|
||||
throw new UnsupportedOperationException("Not implemented yet!");
|
||||
}
|
||||
|
||||
@SuppressWarnings({"UnusedDeclaration"})
|
||||
public void numericButtonClickHandler(@NotNull View v) {
|
||||
this.calculatorModel.evaluate();
|
||||
}
|
||||
|
||||
@SuppressWarnings({"UnusedDeclaration"})
|
||||
public void historyButtonClickHandler(@NotNull View v) {
|
||||
CalculatorActivityLauncher.showHistory(this);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"UnusedDeclaration"})
|
||||
public void eraseButtonClickHandler(@NotNull View v) {
|
||||
calculatorModel.doTextOperation(new CalculatorModel.TextOperation() {
|
||||
@Override
|
||||
public void doOperation(@NotNull EditText editor) {
|
||||
if (editor.getSelectionStart() > 0) {
|
||||
editor.getText().delete(editor.getSelectionStart() - 1, editor.getSelectionStart());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings({"UnusedDeclaration"})
|
||||
public void simplifyButtonClickHandler(@NotNull View v) {
|
||||
throw new UnsupportedOperationException("Not implemented yet!");
|
||||
}
|
||||
|
||||
@SuppressWarnings({"UnusedDeclaration"})
|
||||
public void moveLeftButtonClickHandler(@NotNull View v) {
|
||||
calculatorModel.moveCursorLeft();
|
||||
}
|
||||
|
||||
@SuppressWarnings({"UnusedDeclaration"})
|
||||
public void moveRightButtonClickHandler(@NotNull View v) {
|
||||
calculatorModel.moveCursorRight();
|
||||
}
|
||||
|
||||
@SuppressWarnings({"UnusedDeclaration"})
|
||||
public void pasteButtonClickHandler(@NotNull View v) {
|
||||
calculatorModel.doTextOperation(new CalculatorModel.TextOperation() {
|
||||
@Override
|
||||
public void doOperation(@NotNull EditText editor) {
|
||||
final ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
|
||||
if (clipboard.hasText()) {
|
||||
editor.getText().insert(editor.getSelectionStart(), clipboard.getText());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings({"UnusedDeclaration"})
|
||||
public void copyButtonClickHandler(@NotNull View v) {
|
||||
calculatorModel.copyResult(this);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"UnusedDeclaration"})
|
||||
public void clearButtonClickHandler(@NotNull View v) {
|
||||
calculatorModel.clear();
|
||||
}
|
||||
|
||||
@SuppressWarnings({"UnusedDeclaration"})
|
||||
public void digitButtonClickHandler(@NotNull View v) {
|
||||
Log.d(String.valueOf(v.getId()), "digitButtonClickHandler() for: " + v.getId() + ". Pressed: " + v.isPressed());
|
||||
calculatorModel.processDigitButtonAction(((DirectionDragButton) v).getText().toString());
|
||||
}
|
||||
|
||||
@SuppressWarnings({"UnusedDeclaration"})
|
||||
public void functionsButtonClickHandler(@NotNull View v) {
|
||||
CalculatorActivityLauncher.showFunctions(this);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"UnusedDeclaration"})
|
||||
public void operatorsButtonClickHandler(@NotNull View v) {
|
||||
CalculatorActivityLauncher.showOperators(this);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"UnusedDeclaration"})
|
||||
public void varsButtonClickHandler(@NotNull View v) {
|
||||
CalculatorActivityLauncher.showVars(this);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"UnusedDeclaration"})
|
||||
public void donateButtonClickHandler(@NotNull View v) {
|
||||
CalculatorApplication.showDonationDialog(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
if (useBackAsPrev) {
|
||||
calculatorModel.doHistoryAction(HistoryAction.undo);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
final MenuInflater menuInflater = getMenuInflater();
|
||||
menuInflater.inflate(R.menu.main_menu, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
boolean result;
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case R.id.main_menu_item_settings:
|
||||
CalculatorActivityLauncher.showSettings(this);
|
||||
result = true;
|
||||
break;
|
||||
case R.id.main_menu_item_history:
|
||||
CalculatorActivityLauncher.showHistory(this);
|
||||
result = true;
|
||||
break;
|
||||
case R.id.main_menu_item_about:
|
||||
CalculatorActivityLauncher.showAbout(this);
|
||||
result = true;
|
||||
break;
|
||||
case R.id.main_menu_item_help:
|
||||
CalculatorActivityLauncher.showHelp(this);
|
||||
result = true;
|
||||
break;
|
||||
case R.id.main_menu_item_exit:
|
||||
this.finish();
|
||||
result = true;
|
||||
break;
|
||||
default:
|
||||
result = super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* The font sizes in the layout files are specified for a HVGA display.
|
||||
* Adjust the font sizes accordingly if we are running on a different
|
||||
* display.
|
||||
*/
|
||||
@Override
|
||||
public void adjustFontSize(@NotNull TextView view) {
|
||||
float fontPixelSize = view.getTextSize();
|
||||
Display display = getWindowManager().getDefaultDisplay();
|
||||
int h = Math.min(display.getWidth(), display.getHeight());
|
||||
float ratio = (float) h / HVGA_WIDTH_PIXELS;
|
||||
view.setTextSize(TypedValue.COMPLEX_UNIT_PX, fontPixelSize * ratio);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
|
||||
final Layout newLayout = Preferences.layout.getPreference(preferences);
|
||||
final Theme newTheme = Preferences.theme.getPreference(preferences);
|
||||
if (!theme.equals(newTheme) || !layout.equals(newLayout)) {
|
||||
AndroidUtils.restartActivity(this);
|
||||
}
|
||||
|
||||
calculatorModel = CalculatorModel.instance.init(this, preferences, CalculatorEngine.instance);
|
||||
calculatorModel.evaluate(calculatorModel.getDisplay().getJsclOperation());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
if ( adView != null ) {
|
||||
adView.destroy();
|
||||
}
|
||||
|
||||
if (billingObserver != null) {
|
||||
BillingController.unregisterObserver(billingObserver);
|
||||
}
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences, @Nullable String key) {
|
||||
if (key != null && key.startsWith("org.solovyev.android.calculator.DragButtonCalibrationActivity")) {
|
||||
dpclRegister.announce().onDragPreferencesChange(SimpleOnDragListener.getPreferences(preferences, this));
|
||||
}
|
||||
|
||||
if (CalculatorEngine.Preferences.getPreferenceKeys().contains(key)) {
|
||||
CalculatorEngine.instance.softReset(this, preferences);
|
||||
|
||||
// reevaluate in order to update values (in case of preferences changed from the main window, like numeral bases and angle units)
|
||||
this.calculatorModel.evaluate();
|
||||
}
|
||||
|
||||
if ( USE_BACK_AS_PREV_P_KEY.equals(key) ) {
|
||||
useBackAsPrev = preferences.getBoolean(USE_BACK_AS_PREV_P_KEY, USE_BACK_AS_PREV_DEFAULT);
|
||||
}
|
||||
|
||||
if ( CalculatorEngine.Preferences.multiplicationSign.getKey().equals(key) ) {
|
||||
initMultiplicationButton();
|
||||
}
|
||||
}
|
||||
|
||||
private void initMultiplicationButton() {
|
||||
final View multiplicationButton = findViewById(R.id.multiplicationButton);
|
||||
if ( multiplicationButton instanceof Button) {
|
||||
((Button) multiplicationButton).setText(CalculatorEngine.instance.getMultiplicationSign());
|
||||
}
|
||||
}
|
||||
|
||||
private static class RoundBracketsDragProcessor implements SimpleOnDragListener.DragProcessor {
|
||||
@Override
|
||||
public boolean processDragEvent(@NotNull DragDirection dragDirection, @NotNull DragButton dragButton, @NotNull Point2d startPoint2d, @NotNull MotionEvent motionEvent) {
|
||||
boolean result = false;
|
||||
if ( dragDirection == DragDirection.left ) {
|
||||
CalculatorModel.instance.doTextOperation(new CalculatorModel.TextOperation() {
|
||||
@Override
|
||||
public void doOperation(@NotNull EditText editor) {
|
||||
final int cursorPosition = editor.getSelectionStart();
|
||||
final StringBuilder text = new StringBuilder("(");
|
||||
final String oldText = editor.getText().toString();
|
||||
text.append(oldText.substring(0, cursorPosition));
|
||||
text.append(")");
|
||||
text.append(oldText.substring(cursorPosition));
|
||||
editor.setText(text);
|
||||
editor.setSelection(cursorPosition + 2);
|
||||
}
|
||||
});
|
||||
result = true;
|
||||
} else {
|
||||
result = new DigitButtonDragProcessor(CalculatorModel.instance).processDragEvent(dragDirection, dragButton, startPoint2d, motionEvent);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,81 @@
|
||||
package org.solovyev.android.calculator;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.widget.Toast;
|
||||
import jscl.math.Generic;
|
||||
import jscl.math.function.Constant;
|
||||
import org.achartengine.ChartFactory;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.solovyev.android.calculator.about.CalculatorAboutTabActivity;
|
||||
import org.solovyev.android.calculator.help.CalculatorHelpTabActivity;
|
||||
import org.solovyev.android.calculator.history.CalculatorHistoryTabActivity;
|
||||
import org.solovyev.android.calculator.math.edit.CalculatorFunctionsTabActivity;
|
||||
import org.solovyev.android.calculator.math.edit.CalculatorOperatorsActivity;
|
||||
import org.solovyev.android.calculator.math.edit.CalculatorVarsTabActivity;
|
||||
import org.solovyev.android.calculator.math.edit.CalculatorVarsActivity;
|
||||
import org.solovyev.android.calculator.plot.CalculatorPlotActivity;
|
||||
import org.solovyev.common.utils.StringUtils;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 11/2/11
|
||||
* Time: 2:18 PM
|
||||
*/
|
||||
public class CalculatorActivityLauncher {
|
||||
|
||||
public static void showHistory(@NotNull final Context context) {
|
||||
context.startActivity(new Intent(context, CalculatorHistoryTabActivity.class));
|
||||
}
|
||||
|
||||
public static void showHelp(@NotNull final Context context) {
|
||||
context.startActivity(new Intent(context, CalculatorHelpTabActivity.class));
|
||||
}
|
||||
|
||||
public static void showSettings(@NotNull final Context context) {
|
||||
context.startActivity(new Intent(context, CalculatorPreferencesActivity.class));
|
||||
}
|
||||
|
||||
public static void showAbout(@NotNull final Context context) {
|
||||
context.startActivity(new Intent(context, CalculatorAboutTabActivity.class));
|
||||
}
|
||||
|
||||
public static void showFunctions(@NotNull final Context context) {
|
||||
context.startActivity(new Intent(context, CalculatorFunctionsTabActivity.class));
|
||||
}
|
||||
|
||||
public static void showOperators(@NotNull final Context context) {
|
||||
context.startActivity(new Intent(context, CalculatorOperatorsActivity.class));
|
||||
}
|
||||
|
||||
public static void showVars(@NotNull final Context context) {
|
||||
context.startActivity(new Intent(context, CalculatorVarsTabActivity.class));
|
||||
}
|
||||
|
||||
public static void plotGraph(@NotNull final Context context, @NotNull Generic generic, @NotNull Constant constant){
|
||||
final Intent intent = new Intent();
|
||||
intent.putExtra(ChartFactory.TITLE, context.getString(R.string.c_graph));
|
||||
intent.putExtra(CalculatorPlotActivity.INPUT, new CalculatorPlotActivity.Input(generic.toString(), constant.getName()));
|
||||
intent.setClass(context, CalculatorPlotActivity.class);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
public static void createVar(@NotNull final Context context, @NotNull CalculatorModel calculatorModel) {
|
||||
if (calculatorModel.getDisplay().isValid() ) {
|
||||
final String varValue = calculatorModel.getDisplay().getText().toString();
|
||||
if (!StringUtils.isEmpty(varValue)) {
|
||||
if (CalculatorVarsActivity.isValidValue(varValue)) {
|
||||
final Intent intent = new Intent(context, CalculatorVarsTabActivity.class);
|
||||
intent.putExtra(CalculatorVarsActivity.CREATE_VAR_EXTRA_STRING, varValue);
|
||||
context.startActivity(intent);
|
||||
} else {
|
||||
Toast.makeText(context, R.string.c_not_valid_result, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(context, R.string.c_empty_var_error, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(context, R.string.c_not_valid_result, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.calculator;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.TextView;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.calculator.model.CalculatorEngine;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/10/11
|
||||
* Time: 10:34 PM
|
||||
*/
|
||||
public class CalculatorAdditionalTitle extends TextView implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
public CalculatorAdditionalTitle(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public CalculatorAdditionalTitle(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public CalculatorAdditionalTitle(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
public void init(@NotNull SharedPreferences preferences) {
|
||||
onSharedPreferenceChanged(preferences, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences, @Nullable String key) {
|
||||
setText(CalculatorEngine.instance.getNumeralBaseFromPrefs(preferences) + " / " + CalculatorEngine.instance.getAngleUnitsFromPrefs(preferences));
|
||||
}
|
||||
}
|
@@ -0,0 +1,92 @@
|
||||
package org.solovyev.android.calculator;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import net.robotmedia.billing.BillingController;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.solovyev.android.ads.AdsController;
|
||||
import org.solovyev.android.calculator.model.CalculatorEngine;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/1/11
|
||||
* Time: 1:21 PM
|
||||
*/
|
||||
public class CalculatorApplication extends android.app.Application {
|
||||
|
||||
private static final String paypalDonateUrl = "https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=se%2esolovyev%40gmail%2ecom&lc=RU&item_name=Android%20Calculator¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted";
|
||||
|
||||
public static final String AD_FREE_PRODUCT_ID = "ad_free";
|
||||
public static final String AD_FREE_P_KEY = "org.solovyev.android.calculator_ad_free";
|
||||
|
||||
public static final String ADMOB_USER_ID = "a14f02cf9c80cbc";
|
||||
public static final String REMOTE_STACK_TRACE_URL = "http://calculatorpp.com/crash_reports/upload.php";
|
||||
|
||||
@NotNull
|
||||
private static CalculatorApplication instance;
|
||||
|
||||
public CalculatorApplication() {
|
||||
instance = this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static CalculatorApplication getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
AdsController.getInstance().init(ADMOB_USER_ID, AD_FREE_PRODUCT_ID, new BillingController.IConfiguration() {
|
||||
|
||||
@Override
|
||||
public byte[] getObfuscationSalt() {
|
||||
return new byte[]{81, -114, 32, -127, -32, -104, -40, -15, -47, 57, -13, -41, -33, 67, -114, 7, -11, 53, 126, 82};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPublicKey() {
|
||||
return CalculatorSecurity.getPK();
|
||||
}
|
||||
});
|
||||
|
||||
CalculatorEngine.instance.init(this, PreferenceManager.getDefaultSharedPreferences(this));
|
||||
|
||||
}
|
||||
|
||||
public static void showDonationDialog(@NotNull final Context context) {
|
||||
final LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(LAYOUT_INFLATER_SERVICE);
|
||||
final View view = layoutInflater.inflate(R.layout.donate, null);
|
||||
|
||||
final TextView donate = (TextView) view.findViewById(R.id.donateText);
|
||||
donate.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(context)
|
||||
.setCancelable(true)
|
||||
.setNegativeButton(R.string.c_cancel, null)
|
||||
.setPositiveButton(R.string.c_donate, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
final Intent i = new Intent(Intent.ACTION_VIEW);
|
||||
i.setData(Uri.parse(paypalDonateUrl));
|
||||
context.startActivity(i);
|
||||
}
|
||||
})
|
||||
.setView(view);
|
||||
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
public static void registerOnRemoteStackTrace() {
|
||||
//Thread.setDefaultUncaughtExceptionHandler(new CustomExceptionHandler(null, REMOTE_STACK_TRACE_URL));
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2012. 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.calculator;
|
||||
|
||||
import android.app.Activity;
|
||||
import net.robotmedia.billing.ResponseCode;
|
||||
import net.robotmedia.billing.helper.AbstractBillingObserver;
|
||||
import net.robotmedia.billing.model.Transaction;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 1/5/12
|
||||
* Time: 4:51 PM
|
||||
*/
|
||||
public class CalculatorBillingObserver extends AbstractBillingObserver {
|
||||
|
||||
public CalculatorBillingObserver(@NotNull Activity activity) {
|
||||
super(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCheckBillingSupportedResponse(boolean supported) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPurchaseIntentFailure(@NotNull String s, @NotNull ResponseCode responseCode) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPurchaseStateChanged(@NotNull String itemId, @NotNull Transaction.PurchaseState state) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPurchaseResponse(@NotNull String itemId, @NotNull ResponseCode response) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
||||
* For more information, please, contact se.solovyev@gmail.com
|
||||
*/
|
||||
|
||||
package org.solovyev.android.calculator;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.text.Html;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import jscl.math.Generic;
|
||||
import jscl.math.function.Constant;
|
||||
import jscl.math.function.IConstant;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.calculator.jscl.JsclOperation;
|
||||
import org.solovyev.android.calculator.model.CalculatorEngine;
|
||||
import org.solovyev.android.calculator.model.CalculatorParseException;
|
||||
import org.solovyev.android.calculator.model.TextProcessor;
|
||||
import org.solovyev.android.calculator.view.TextHighlighter;
|
||||
import org.solovyev.android.menu.AMenuItem;
|
||||
import org.solovyev.android.view.AutoResizeTextView;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 9/17/11
|
||||
* Time: 10:58 PM
|
||||
*/
|
||||
public class CalculatorDisplay extends AutoResizeTextView implements ICalculatorDisplay{
|
||||
|
||||
public static enum Menu implements AMenuItem<CalculatorDisplay> {
|
||||
to_bin(0) {
|
||||
@Override
|
||||
public void doAction(@NotNull CalculatorDisplay data, @NotNull Context context) {
|
||||
//To change body of implemented methods use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isItemVisibleFor(@NotNull Generic generic, @NotNull JsclOperation operation) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
plot(R.string.c_plot_graph) {
|
||||
@Override
|
||||
public void doAction(@NotNull CalculatorDisplay data, @NotNull Context context) {
|
||||
|
||||
//To change body of implemented methods use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isItemVisibleFor(@NotNull Generic generic, @NotNull JsclOperation operation) {
|
||||
boolean result = false;
|
||||
|
||||
if (operation == JsclOperation.simplify) {
|
||||
final Set<Constant> notSystemConstants = new HashSet<Constant>();
|
||||
for (Constant constant : generic.getConstants()) {
|
||||
IConstant var = CalculatorEngine.instance.getVarsRegistry().get(constant.getName());
|
||||
if (var != null && !var.isSystem() && !var.isDefined()) {
|
||||
notSystemConstants.add(constant);
|
||||
}
|
||||
}
|
||||
|
||||
if (notSystemConstants.size() == 1) {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
private final int captionId;
|
||||
|
||||
Menu(int captionId) {
|
||||
this.captionId = captionId;
|
||||
}
|
||||
|
||||
public final boolean isItemVisible(@NotNull CalculatorDisplay display) {
|
||||
//noinspection ConstantConditions
|
||||
return display.isValid() && display.getGenericResult() != null && isItemVisibleFor(display.getGenericResult(), display.getJsclOperation());
|
||||
}
|
||||
|
||||
protected boolean isItemVisibleFor(@NotNull Generic generic, @NotNull JsclOperation operation) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getCaption(@NotNull Context context) {
|
||||
return context.getString(captionId);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean valid = true;
|
||||
|
||||
@Nullable
|
||||
private String errorMessage;
|
||||
|
||||
@NotNull
|
||||
private JsclOperation jsclOperation = JsclOperation.numeric;
|
||||
|
||||
@NotNull
|
||||
private final static TextProcessor<TextHighlighter.Result, String> textHighlighter = new TextHighlighter(Color.WHITE, false, CalculatorEngine.instance.getEngine());
|
||||
|
||||
@Nullable
|
||||
private Generic genericResult;
|
||||
|
||||
public CalculatorDisplay(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public CalculatorDisplay(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public CalculatorDisplay(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return valid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValid(boolean valid) {
|
||||
this.valid = valid;
|
||||
if (valid) {
|
||||
errorMessage = null;
|
||||
setTextColor(getResources().getColor(R.color.default_text_color));
|
||||
} else {
|
||||
setTextColor(getResources().getColor(R.color.display_error_text_color));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setErrorMessage(@Nullable String errorMessage) {
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setJsclOperation(@NotNull JsclOperation jsclOperation) {
|
||||
this.jsclOperation = jsclOperation;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public JsclOperation getJsclOperation() {
|
||||
return jsclOperation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setText(CharSequence text, BufferType type) {
|
||||
super.setText(text, type);
|
||||
|
||||
setValid(true);
|
||||
}
|
||||
|
||||
public synchronized void redraw() {
|
||||
if (isValid()) {
|
||||
String text = getText().toString();
|
||||
|
||||
Log.d(this.getClass().getName(), text);
|
||||
|
||||
try {
|
||||
TextHighlighter.Result result = textHighlighter.process(text);
|
||||
text = result.toString();
|
||||
} catch (CalculatorParseException e) {
|
||||
Log.e(this.getClass().getName(), e.getMessage(), e);
|
||||
}
|
||||
|
||||
Log.d(this.getClass().getName(), text);
|
||||
super.setText(Html.fromHtml(text), BufferType.EDITABLE);
|
||||
}
|
||||
|
||||
// todo serso: think where to move it (keep in mind org.solovyev.android.view.AutoResizeTextView.resetTextSize())
|
||||
setAddEllipsis(false);
|
||||
setMinTextSize(10);
|
||||
resizeText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGenericResult(@Nullable Generic genericResult) {
|
||||
this.genericResult = genericResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Generic getGenericResult() {
|
||||
return genericResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSelection() {
|
||||
return this.getSelectionStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelection(int selection) {
|
||||
// not supported by TextView
|
||||
}
|
||||
}
|
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
||||
* For more information, please, contact se.solovyev@gmail.com
|
||||
*/
|
||||
|
||||
package org.solovyev.android.calculator;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Color;
|
||||
import android.os.Build;
|
||||
import android.text.Html;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.ContextMenu;
|
||||
import android.widget.EditText;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.solovyev.android.calculator.model.CalculatorEngine;
|
||||
import org.solovyev.android.calculator.model.CalculatorParseException;
|
||||
import org.solovyev.android.calculator.model.TextProcessor;
|
||||
import org.solovyev.android.calculator.view.TextHighlighter;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 9/17/11
|
||||
* Time: 12:25 AM
|
||||
*/
|
||||
public class CalculatorEditor extends EditText implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
private static final String CALC_COLOR_DISPLAY_KEY = "org.solovyev.android.calculator.CalculatorModel_color_display";
|
||||
private static final boolean CALC_COLOR_DISPLAY_DEFAULT = true;
|
||||
|
||||
private boolean highlightText = true;
|
||||
|
||||
@NotNull
|
||||
private final static TextProcessor<TextHighlighter.Result, String> textHighlighter = new TextHighlighter(Color.WHITE, true, CalculatorEngine.instance.getEngine());
|
||||
|
||||
public CalculatorEditor(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public CalculatorEditor(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public CalculatorEditor(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCheckIsTextEditor() {
|
||||
// Main goal of this implementation is to hide android soft keyboard from appearing when working with text input
|
||||
|
||||
if ( Build.VERSION.SDK_INT >= 11 ) {
|
||||
// fix for missing cursor in android 3 and higher
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreateContextMenu(ContextMenu menu) {
|
||||
super.onCreateContextMenu(menu);
|
||||
|
||||
menu.removeItem(android.R.id.selectAll);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setText(CharSequence text, BufferType type) {
|
||||
super.setText(text, type);
|
||||
}
|
||||
|
||||
public synchronized void redraw() {
|
||||
String text = getText().toString();
|
||||
|
||||
int selectionStart = getSelectionStart();
|
||||
int selectionEnd = getSelectionEnd();
|
||||
|
||||
if (highlightText) {
|
||||
|
||||
Log.d(this.getClass().getName(), text);
|
||||
|
||||
try {
|
||||
final TextHighlighter.Result result = textHighlighter.process(text);
|
||||
selectionStart += result.getOffset();
|
||||
selectionEnd += result.getOffset();
|
||||
text = result.toString();
|
||||
} catch (CalculatorParseException e) {
|
||||
Log.e(this.getClass().getName(), e.getMessage(), e);
|
||||
}
|
||||
|
||||
Log.d(this.getClass().getName(), text);
|
||||
super.setText(Html.fromHtml(text), BufferType.EDITABLE);
|
||||
} else {
|
||||
super.setText(text, BufferType.EDITABLE);
|
||||
}
|
||||
|
||||
Log.d(this.getClass().getName(), getText().toString());
|
||||
|
||||
int length = getText().length();
|
||||
setSelection(Math.max(Math.min(length, selectionStart), 0), Math.max(Math.min(length, selectionEnd), 0));
|
||||
}
|
||||
|
||||
public boolean isHighlightText() {
|
||||
return highlightText;
|
||||
}
|
||||
|
||||
public void setHighlightText(boolean highlightText) {
|
||||
this.highlightText = highlightText;
|
||||
redraw();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||
if (CALC_COLOR_DISPLAY_KEY.equals(key)) {
|
||||
this.setHighlightText(preferences.getBoolean(CALC_COLOR_DISPLAY_KEY, CALC_COLOR_DISPLAY_DEFAULT));
|
||||
}
|
||||
}
|
||||
|
||||
public void init(@NotNull SharedPreferences preferences) {
|
||||
onSharedPreferenceChanged(preferences, CALC_COLOR_DISPLAY_KEY);
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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.calculator;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 10/24/11
|
||||
* Time: 9:55 PM
|
||||
*/
|
||||
public interface CalculatorEngineControl {
|
||||
|
||||
void evaluate();
|
||||
|
||||
void simplify();
|
||||
}
|
@@ -0,0 +1,497 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
||||
* For more information, please, contact se.solovyev@gmail.com
|
||||
*/
|
||||
|
||||
package org.solovyev.android.calculator;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Handler;
|
||||
import android.text.ClipboardManager;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import jscl.math.Generic;
|
||||
import jscl.math.function.Constant;
|
||||
import jscl.math.function.IConstant;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.CursorControl;
|
||||
import org.solovyev.android.history.HistoryControl;
|
||||
import org.solovyev.android.calculator.history.CalculatorHistory;
|
||||
import org.solovyev.android.calculator.history.CalculatorHistoryState;
|
||||
import org.solovyev.android.calculator.history.TextViewEditorAdapter;
|
||||
import org.solovyev.android.calculator.jscl.JsclOperation;
|
||||
import org.solovyev.android.calculator.math.MathType;
|
||||
import org.solovyev.android.calculator.model.CalculatorEngine;
|
||||
import org.solovyev.android.calculator.model.CalculatorEvalException;
|
||||
import org.solovyev.android.calculator.model.CalculatorParseException;
|
||||
import org.solovyev.android.menu.AMenuBuilder;
|
||||
import org.solovyev.android.menu.AMenuItem;
|
||||
import org.solovyev.common.msg.Message;
|
||||
import org.solovyev.common.utils.CollectionsUtils;
|
||||
import org.solovyev.common.utils.MutableObject;
|
||||
import org.solovyev.common.utils.StringUtils;
|
||||
import org.solovyev.common.utils.history.HistoryAction;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 9/12/11
|
||||
* Time: 11:15 PM
|
||||
*/
|
||||
public enum CalculatorModel implements CursorControl, HistoryControl<CalculatorHistoryState>, CalculatorEngineControl {
|
||||
|
||||
instance;
|
||||
|
||||
// millis to wait before evaluation after user edit action
|
||||
public static final int EVAL_DELAY_MILLIS = 0;
|
||||
|
||||
@NotNull
|
||||
private CalculatorEditor editor;
|
||||
|
||||
@NotNull
|
||||
private CalculatorDisplay display;
|
||||
|
||||
@NotNull
|
||||
private CalculatorEngine calculatorEngine;
|
||||
|
||||
public CalculatorModel init(@NotNull final Activity activity, @NotNull SharedPreferences preferences, @NotNull CalculatorEngine calculator) {
|
||||
Log.d(this.getClass().getName(), "CalculatorModel initialization with activity: " + activity);
|
||||
this.calculatorEngine = calculator;
|
||||
|
||||
this.editor = (CalculatorEditor) activity.findViewById(R.id.calculatorEditor);
|
||||
this.editor.init(preferences);
|
||||
preferences.registerOnSharedPreferenceChangeListener(editor);
|
||||
|
||||
this.display = (CalculatorDisplay) activity.findViewById(R.id.calculatorDisplay);
|
||||
this.display.setOnClickListener(new CalculatorDisplayOnClickListener(activity));
|
||||
|
||||
final CalculatorHistoryState lastState = CalculatorHistory.instance.getLastHistoryState();
|
||||
if (lastState == null) {
|
||||
saveHistoryState();
|
||||
} else {
|
||||
setCurrentHistoryState(lastState);
|
||||
}
|
||||
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private static void showEvaluationError(@NotNull Activity activity, @NotNull final String errorMessage) {
|
||||
final LayoutInflater layoutInflater = (LayoutInflater) activity.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
|
||||
|
||||
final View errorMessageView = layoutInflater.inflate(R.layout.display_error_message, null);
|
||||
((TextView) errorMessageView.findViewById(R.id.error_message_text_view)).setText(errorMessage);
|
||||
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(activity)
|
||||
.setPositiveButton(R.string.c_cancel, null)
|
||||
.setView(errorMessageView);
|
||||
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
public void copyResult(@NotNull Context context) {
|
||||
copyResult(context, display);
|
||||
}
|
||||
|
||||
public static void copyResult(@NotNull Context context, @NotNull final CalculatorDisplay display) {
|
||||
if (display.isValid()) {
|
||||
final CharSequence text = display.getText();
|
||||
if (!StringUtils.isEmpty(text)) {
|
||||
final ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Activity.CLIPBOARD_SERVICE);
|
||||
clipboard.setText(text.toString());
|
||||
Toast.makeText(context, context.getText(R.string.c_result_copied), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void saveHistoryState() {
|
||||
CalculatorHistory.instance.addState(getCurrentHistoryState());
|
||||
}
|
||||
|
||||
public void setCursorOnStart() {
|
||||
editor.setSelection(0);
|
||||
}
|
||||
|
||||
public void setCursorOnEnd() {
|
||||
editor.setSelection(editor.getText().length());
|
||||
}
|
||||
|
||||
public void moveCursorLeft() {
|
||||
if (editor.getSelectionStart() > 0) {
|
||||
editor.setSelection(editor.getSelectionStart() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
public void moveCursorRight() {
|
||||
if (editor.getSelectionStart() < editor.getText().length()) {
|
||||
editor.setSelection(editor.getSelectionStart() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
public void doTextOperation(@NotNull TextOperation operation) {
|
||||
doTextOperation(operation, true);
|
||||
}
|
||||
|
||||
public void doTextOperation(@NotNull TextOperation operation, boolean delayEvaluate) {
|
||||
doTextOperation(operation, delayEvaluate, JsclOperation.numeric, false);
|
||||
}
|
||||
|
||||
public void doTextOperation(@NotNull TextOperation operation, boolean delayEvaluate, @NotNull JsclOperation jsclOperation, boolean forceEval) {
|
||||
final String editorStateBefore = this.editor.getText().toString();
|
||||
|
||||
Log.d(CalculatorModel.class.getName(), "Editor state changed before '" + editorStateBefore + "'");
|
||||
operation.doOperation(this.editor);
|
||||
//Log.d(CalculatorModel.class.getName(), "Doing text operation" + StringUtils.fromStackTrace(Thread.currentThread().getStackTrace()));
|
||||
|
||||
final String editorStateAfter = this.editor.getText().toString();
|
||||
if (forceEval ||!editorStateBefore.equals(editorStateAfter)) {
|
||||
|
||||
editor.redraw();
|
||||
|
||||
evaluate(delayEvaluate, editorStateAfter, jsclOperation, null);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private final static MutableObject<Runnable> pendingOperation = new MutableObject<Runnable>();
|
||||
|
||||
private void evaluate(boolean delayEvaluate,
|
||||
@NotNull final String expression,
|
||||
@NotNull final JsclOperation operation,
|
||||
@Nullable CalculatorHistoryState historyState) {
|
||||
|
||||
final CalculatorHistoryState localHistoryState;
|
||||
if (historyState == null) {
|
||||
//this.display.setText("");
|
||||
localHistoryState = getCurrentHistoryState();
|
||||
} else {
|
||||
this.display.setText(historyState.getDisplayState().getEditorState().getText());
|
||||
localHistoryState = historyState;
|
||||
}
|
||||
|
||||
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
|
||||
evaluate(expression, operation, this);
|
||||
|
||||
if (pendingOperation.getObject() == this) {
|
||||
// todo serso: of course there is small probability that someone will set pendingOperation after if statement but before .setObject(null)
|
||||
pendingOperation.setObject(null);
|
||||
localHistoryState.setDisplayState(getCurrentHistoryState().getDisplayState());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (delayEvaluate) {
|
||||
if (historyState == null) {
|
||||
CalculatorHistory.instance.addState(localHistoryState);
|
||||
}
|
||||
new Handler().postDelayed(pendingOperation.getObject(), EVAL_DELAY_MILLIS);
|
||||
} else {
|
||||
pendingOperation.getObject().run();
|
||||
if (historyState == null) {
|
||||
CalculatorHistory.instance.addState(localHistoryState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evaluate() {
|
||||
evaluate(false, this.editor.getText().toString(), JsclOperation.numeric, null);
|
||||
}
|
||||
|
||||
public void evaluate(@NotNull JsclOperation operation) {
|
||||
evaluate(false, this.editor.getText().toString(), operation, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void simplify() {
|
||||
evaluate(false, this.editor.getText().toString(), JsclOperation.simplify, null);
|
||||
}
|
||||
|
||||
private void evaluate(@Nullable final String expression,
|
||||
@NotNull JsclOperation operation,
|
||||
@NotNull Runnable currentRunner) {
|
||||
|
||||
if (!StringUtils.isEmpty(expression)) {
|
||||
try {
|
||||
Log.d(CalculatorModel.class.getName(), "Trying to evaluate '" + operation + "': " + expression /*+ StringUtils.fromStackTrace(Thread.currentThread().getStackTrace())*/);
|
||||
final CalculatorEngine.Result result = calculatorEngine.evaluate(operation, expression);
|
||||
|
||||
// todo serso: second condition might replaced with expression.equals(this.editor.getText().toString()) ONLY if expression will be formatted with text highlighter
|
||||
if (currentRunner == pendingOperation.getObject() && this.editor.getText().length() > 0) {
|
||||
display.setText(result.getResult());
|
||||
} else {
|
||||
display.setText("");
|
||||
}
|
||||
display.setJsclOperation(result.getUserOperation());
|
||||
display.setGenericResult(result.getGenericResult());
|
||||
} catch (CalculatorParseException e) {
|
||||
handleEvaluationException(expression, display, operation, e);
|
||||
} catch (CalculatorEvalException e) {
|
||||
handleEvaluationException(expression, display, operation, e);
|
||||
}
|
||||
} else {
|
||||
this.display.setText("");
|
||||
this.display.setJsclOperation(operation);
|
||||
this.display.setGenericResult(null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
this.display.redraw();
|
||||
}
|
||||
|
||||
private void handleEvaluationException(@NotNull String expression,
|
||||
@NotNull CalculatorDisplay localDisplay,
|
||||
@NotNull JsclOperation operation,
|
||||
@NotNull Message e) {
|
||||
Log.d(CalculatorModel.class.getName(), "Evaluation failed for : " + expression + ". Error message: " + e);
|
||||
if ( StringUtils.isEmpty(localDisplay.getText()) ) {
|
||||
// if previous display state was empty -> show error
|
||||
localDisplay.setText(R.string.c_syntax_error);
|
||||
} else {
|
||||
// show previous result instead of error caption (actually previous result will be greyed)
|
||||
}
|
||||
localDisplay.setJsclOperation(operation);
|
||||
localDisplay.setGenericResult(null);
|
||||
localDisplay.setValid(false);
|
||||
localDisplay.setErrorMessage(e.getLocalizedMessage());
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
if (!StringUtils.isEmpty(editor.getText()) || !StringUtils.isEmpty(display.getText())) {
|
||||
editor.getText().clear();
|
||||
display.setText("");
|
||||
saveHistoryState();
|
||||
}
|
||||
}
|
||||
|
||||
public void processDigitButtonAction(@Nullable final String text) {
|
||||
processDigitButtonAction(text, true);
|
||||
}
|
||||
|
||||
public void processDigitButtonAction(@Nullable final String text, boolean delayEvaluate) {
|
||||
|
||||
if (!StringUtils.isEmpty(text)) {
|
||||
doTextOperation(new CalculatorModel.TextOperation() {
|
||||
|
||||
@Override
|
||||
public void doOperation(@NotNull EditText editor) {
|
||||
int cursorPositionOffset = 0;
|
||||
final StringBuilder textToBeInserted = new StringBuilder(text);
|
||||
|
||||
final MathType.Result mathType = MathType.getType(text, 0, false);
|
||||
switch (mathType.getMathType()) {
|
||||
case function:
|
||||
textToBeInserted.append("()");
|
||||
cursorPositionOffset = -1;
|
||||
break;
|
||||
case operator:
|
||||
textToBeInserted.append("()");
|
||||
cursorPositionOffset = -1;
|
||||
break;
|
||||
case comma:
|
||||
textToBeInserted.append(" ");
|
||||
break;
|
||||
}
|
||||
|
||||
if (cursorPositionOffset == 0) {
|
||||
if (MathType.openGroupSymbols.contains(text)) {
|
||||
cursorPositionOffset = -1;
|
||||
}
|
||||
}
|
||||
|
||||
editor.getText().insert(editor.getSelectionStart(), textToBeInserted.toString());
|
||||
editor.setSelection(editor.getSelectionStart() + cursorPositionOffset, editor.getSelectionEnd() + cursorPositionOffset);
|
||||
}
|
||||
}, delayEvaluate);
|
||||
}
|
||||
}
|
||||
|
||||
public static interface TextOperation {
|
||||
|
||||
void doOperation(@NotNull EditText editor);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doHistoryAction(@NotNull HistoryAction historyAction) {
|
||||
synchronized (CalculatorHistory.instance) {
|
||||
if (CalculatorHistory.instance.isActionAvailable(historyAction)) {
|
||||
final CalculatorHistoryState newState = CalculatorHistory.instance.doAction(historyAction, getCurrentHistoryState());
|
||||
if (newState != null) {
|
||||
setCurrentHistoryState(newState);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCurrentHistoryState(@NotNull CalculatorHistoryState editorHistoryState) {
|
||||
synchronized (CalculatorHistory.instance) {
|
||||
Log.d(this.getClass().getName(), "Saved history found: " + editorHistoryState);
|
||||
|
||||
editorHistoryState.setValuesFromHistory(new TextViewEditorAdapter(this.editor), this.display);
|
||||
|
||||
final String expression = this.editor.getText().toString();
|
||||
if ( !StringUtils.isEmpty(expression) ) {
|
||||
if ( StringUtils.isEmpty(this.display.getText().toString()) ) {
|
||||
evaluate(false, expression, this.display.getJsclOperation(), editorHistoryState);
|
||||
}
|
||||
}
|
||||
|
||||
editor.redraw();
|
||||
display.redraw();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public CalculatorHistoryState getCurrentHistoryState() {
|
||||
synchronized (CalculatorHistory.instance) {
|
||||
return CalculatorHistoryState.newInstance(new TextViewEditorAdapter(this.editor), this.display);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CalculatorDisplay getDisplay() {
|
||||
return display;
|
||||
}
|
||||
|
||||
private static class CalculatorDisplayOnClickListener implements View.OnClickListener {
|
||||
|
||||
@NotNull
|
||||
private final Activity activity;
|
||||
|
||||
public CalculatorDisplayOnClickListener(@NotNull Activity activity) {
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (v instanceof CalculatorDisplay) {
|
||||
final CalculatorDisplay cd = (CalculatorDisplay)v;
|
||||
if (cd.isValid()) {
|
||||
switch (cd.getJsclOperation()) {
|
||||
case simplify:
|
||||
final Generic genericResult = cd.getGenericResult();
|
||||
if ( genericResult != null ) {
|
||||
final Set<Constant> notSystemConstants = new HashSet<Constant>();
|
||||
for (Constant constant : genericResult.getConstants()) {
|
||||
IConstant var = CalculatorEngine.instance.getVarsRegistry().get(constant.getName());
|
||||
if (var != null && !var.isSystem() && !var.isDefined()) {
|
||||
notSystemConstants.add(constant);
|
||||
}
|
||||
}
|
||||
|
||||
if ( notSystemConstants.size() > 0 ) {
|
||||
if (notSystemConstants.size() > 1) {
|
||||
copyResult(activity, cd);
|
||||
} else {
|
||||
final AMenuBuilder<CalculatorDisplayMenuItem, CalculatorDisplayMenuData> menuBuilder = AMenuBuilder.newInstance(activity, CalculatorDisplayMenuItem.class);
|
||||
menuBuilder.create(new CalculatorDisplayMenuData(cd, genericResult, notSystemConstants)).show();
|
||||
}
|
||||
} else {
|
||||
copyResult(activity, cd);
|
||||
}
|
||||
} else {
|
||||
copyResult(activity, cd);
|
||||
}
|
||||
break;
|
||||
case elementary:
|
||||
copyResult(activity, cd);
|
||||
break;
|
||||
case numeric:
|
||||
copyResult(activity, cd);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
final String errorMessage = cd.getErrorMessage();
|
||||
if ( errorMessage != null ) {
|
||||
showEvaluationError(activity, errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class CalculatorDisplayMenuData {
|
||||
|
||||
@NotNull
|
||||
private final CalculatorDisplay display;
|
||||
|
||||
@NotNull
|
||||
private final Generic result;
|
||||
|
||||
@NotNull
|
||||
private final Set<Constant> notSystemConstants;
|
||||
|
||||
private CalculatorDisplayMenuData(@NotNull CalculatorDisplay display, @NotNull Generic result, @NotNull Set<Constant> notSystemConstants) {
|
||||
this.display = display;
|
||||
this.result = result;
|
||||
this.notSystemConstants = notSystemConstants;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CalculatorDisplay getDisplay() {
|
||||
return display;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Generic getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Set<Constant> getNotSystemConstants() {
|
||||
return notSystemConstants;
|
||||
}
|
||||
}
|
||||
|
||||
private static enum CalculatorDisplayMenuItem implements AMenuItem<CalculatorDisplayMenuData> {
|
||||
plot(R.string.c_plot){
|
||||
@Override
|
||||
public void doAction(@NotNull CalculatorDisplayMenuData data, @NotNull Context context) {
|
||||
final Constant constant = CollectionsUtils.getFirstCollectionElement(data.getNotSystemConstants());
|
||||
assert constant != null;
|
||||
CalculatorActivityLauncher.plotGraph(context, data.getResult(), constant);
|
||||
}
|
||||
},
|
||||
copy(R.string.c_copy){
|
||||
@Override
|
||||
public void doAction(@NotNull CalculatorDisplayMenuData data, @NotNull Context context) {
|
||||
copyResult(context, data.getDisplay());
|
||||
}
|
||||
};
|
||||
|
||||
private final int captionId;
|
||||
|
||||
CalculatorDisplayMenuItem(@NotNull int captionId) {
|
||||
this.captionId = captionId;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getCaption(@NotNull Context context) {
|
||||
return context.getString(captionId);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
||||
* For more information, please, contact se.solovyev@gmail.com
|
||||
*/
|
||||
|
||||
package org.solovyev.android.calculator;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceActivity;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
import net.robotmedia.billing.BillingController;
|
||||
import net.robotmedia.billing.IBillingObserver;
|
||||
import net.robotmedia.billing.ResponseCode;
|
||||
import net.robotmedia.billing.helper.AbstractBillingObserver;
|
||||
import net.robotmedia.billing.model.Transaction;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.solovyev.android.AndroidUtils;
|
||||
import org.solovyev.android.ads.AdsController;
|
||||
import org.solovyev.android.calculator.model.CalculatorEngine;
|
||||
import org.solovyev.android.view.VibratorContainer;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 7/16/11
|
||||
* Time: 6:37 PM
|
||||
*/
|
||||
public class CalculatorPreferencesActivity extends PreferenceActivity implements SharedPreferences.OnSharedPreferenceChangeListener, IBillingObserver {
|
||||
|
||||
public static final String CLEAR_BILLING_INFO = "clear_billing_info";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
addPreferencesFromResource(R.xml.preferences);
|
||||
|
||||
final Preference adFreePreference = findPreference(CalculatorApplication.AD_FREE_P_KEY);
|
||||
adFreePreference.setEnabled(false);
|
||||
|
||||
// observer must be set before net.robotmedia.billing.BillingController.checkBillingSupported()
|
||||
BillingController.registerObserver(this);
|
||||
|
||||
BillingController.checkBillingSupported(CalculatorPreferencesActivity.this);
|
||||
|
||||
final SharedPreferences preferences = getPreferenceManager().getSharedPreferences();
|
||||
preferences.registerOnSharedPreferenceChangeListener(this);
|
||||
onSharedPreferenceChanged(preferences, CalculatorEngine.Preferences.roundResult.getKey());
|
||||
onSharedPreferenceChanged(preferences, VibratorContainer.HAPTIC_FEEDBACK_P_KEY);
|
||||
|
||||
final Preference clearBillingInfoPreference = findPreference(CLEAR_BILLING_INFO);
|
||||
if (clearBillingInfoPreference != null) {
|
||||
clearBillingInfoPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
|
||||
Toast.makeText(CalculatorPreferencesActivity.this, R.string.c_calc_clearing, Toast.LENGTH_SHORT).show();
|
||||
|
||||
removeBillingInformation(CalculatorPreferencesActivity.this, PreferenceManager.getDefaultSharedPreferences(CalculatorPreferencesActivity.this));
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static void removeBillingInformation(@NotNull Context context, @NotNull SharedPreferences preferences) {
|
||||
final SharedPreferences.Editor editor = preferences.edit();
|
||||
editor.putBoolean(AbstractBillingObserver.KEY_TRANSACTIONS_RESTORED, false);
|
||||
editor.commit();
|
||||
|
||||
BillingController.dropBillingData(context);
|
||||
}
|
||||
|
||||
private void setAdFreeAction() {
|
||||
final Preference adFreePreference = findPreference(CalculatorApplication.AD_FREE_P_KEY);
|
||||
|
||||
if (!AdsController.getInstance().isAdFree(this)) {
|
||||
Log.d(CalculatorPreferencesActivity.class.getName(), "Ad free is not purchased - enable preference!");
|
||||
|
||||
adFreePreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
|
||||
// check billing availability
|
||||
if (BillingController.checkBillingSupported(CalculatorPreferencesActivity.this) != BillingController.BillingStatus.SUPPORTED) {
|
||||
Log.d(CalculatorPreferencesActivity.class.getName(), "Billing is not supported - warn user!");
|
||||
// warn about not supported billing
|
||||
new AlertDialog.Builder(CalculatorPreferencesActivity.this).setTitle(R.string.c_error).setMessage(R.string.c_billing_error).create().show();
|
||||
} else {
|
||||
Log.d(CalculatorPreferencesActivity.class.getName(), "Billing is supported - continue!");
|
||||
if (!AdsController.getInstance().isAdFree(CalculatorPreferencesActivity.this)) {
|
||||
Log.d(CalculatorPreferencesActivity.class.getName(), "Item not purchased - try to purchase!");
|
||||
|
||||
// not purchased => purchasing
|
||||
Toast.makeText(CalculatorPreferencesActivity.this, R.string.c_calc_purchasing, Toast.LENGTH_SHORT).show();
|
||||
|
||||
// show purchase window for user
|
||||
BillingController.requestPurchase(CalculatorPreferencesActivity.this, CalculatorApplication.AD_FREE_PRODUCT_ID, true);
|
||||
} else {
|
||||
// disable preference
|
||||
adFreePreference.setEnabled(false);
|
||||
// and show message to user
|
||||
Toast.makeText(CalculatorPreferencesActivity.this, R.string.c_calc_already_purchased, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
adFreePreference.setEnabled(true);
|
||||
} else {
|
||||
Log.d(CalculatorPreferencesActivity.class.getName(), "Ad free is not purchased - disable preference!");
|
||||
adFreePreference.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
BillingController.unregisterObserver(this);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||
if (CalculatorEngine.Preferences.roundResult.getKey().equals(key)) {
|
||||
findPreference(CalculatorEngine.Preferences.precision.getKey()).setEnabled(preferences.getBoolean(key, CalculatorEngine.Preferences.roundResult.getDefaultValue()));
|
||||
} else if (VibratorContainer.HAPTIC_FEEDBACK_P_KEY.equals(key)) {
|
||||
findPreference(VibratorContainer.HAPTIC_FEEDBACK_DURATION_P_KEY).setEnabled(preferences.getBoolean(key, VibratorContainer.HAPTIC_FEEDBACK_DEFAULT));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCheckBillingSupportedResponse(boolean supported) {
|
||||
if (supported) {
|
||||
setAdFreeAction();
|
||||
} else {
|
||||
final Preference adFreePreference = findPreference(CalculatorApplication.AD_FREE_P_KEY);
|
||||
adFreePreference.setEnabled(false);
|
||||
Log.d(CalculatorPreferencesActivity.class.getName(), "Billing is not supported!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPurchaseIntentOK(@NotNull String productId, @NotNull PendingIntent purchaseIntent) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPurchaseIntentFailure(@NotNull String productId, @NotNull ResponseCode responseCode) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPurchaseStateChanged(@NotNull String itemId, @NotNull Transaction.PurchaseState state) {
|
||||
if (CalculatorApplication.AD_FREE_PRODUCT_ID.equals(itemId)) {
|
||||
final Preference adFreePreference = findPreference(CalculatorApplication.AD_FREE_P_KEY);
|
||||
if (adFreePreference != null) {
|
||||
switch (state) {
|
||||
case PURCHASED:
|
||||
adFreePreference.setEnabled(false);
|
||||
// restart activity to disable ads
|
||||
AndroidUtils.restartActivity(this);
|
||||
break;
|
||||
case CANCELLED:
|
||||
adFreePreference.setEnabled(true);
|
||||
break;
|
||||
case REFUNDED:
|
||||
adFreePreference.setEnabled(true);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPurchaseResponse(@NotNull String itemId, @NotNull ResponseCode response) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransactionsRestored() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onErrorRestoreTransactions(@NotNull ResponseCode responseCode) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2012. 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.calculator;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 1/4/12
|
||||
* Time: 1:23 AM
|
||||
*/
|
||||
public final class CalculatorSecurity {
|
||||
|
||||
private CalculatorSecurity() {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String getPK() {
|
||||
final StringBuilder result = new StringBuilder();
|
||||
|
||||
result.append("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A");
|
||||
result.append("MIIBCgKCAQEAquP2a7dEhTaJEQeXtSyreH5dCmTDOd");
|
||||
result.append("dElCfg0ijOeB8JTxBiJTXLWnLA0kMaT/sRXswUaYI61YCQOoik82");
|
||||
result.append("qrFH7W4+OFtiLb8WGX+YPEpQQ/IBZu9qm3xzS9Nolu79EBff0/CLa1FuT9RtjO");
|
||||
result.append("iTW8Q0VP9meQdJEkfqJEyVCgHain+MGoQaRXI45EzkYmkz8TBx6X6aJF5NBAXnAWeyD0wPX1");
|
||||
result.append("uedHH7+LgLcjnPVw82YjyJSzYnaaD2GX0Y7PGoFe6J5K4yJGGX5mih45pe2HWcG5lAkQhu1uX2hCcCBdF3");
|
||||
result.append("W7paRq9mJvCsbn+BNTh9gq8QKui0ltmiWpa5U+/9L+FQIDAQAB");
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.calculator;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.common.utils.Finder;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 10/3/11
|
||||
* Time: 12:54 AM
|
||||
*/
|
||||
public class CharacterAtPositionFinder implements Finder<Character> {
|
||||
|
||||
private int i;
|
||||
|
||||
@NotNull
|
||||
private final String targetString;
|
||||
|
||||
public CharacterAtPositionFinder(@NotNull String targetString, int i) {
|
||||
this.targetString = targetString;
|
||||
this.i = i;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFound(@Nullable Character s) {
|
||||
return s != null && s.equals(targetString.charAt(i));
|
||||
}
|
||||
|
||||
public void setI(int i) {
|
||||
this.i = i;
|
||||
}
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
||||
* For more information, please, contact se.solovyev@gmail.com
|
||||
*/
|
||||
|
||||
package org.solovyev.android.calculator;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.solovyev.android.CursorControl;
|
||||
import org.solovyev.android.view.drag.DragDirection;
|
||||
import org.solovyev.android.view.drag.SimpleOnDragListener;
|
||||
import org.solovyev.android.view.drag.DirectionDragButton;
|
||||
import org.solovyev.android.view.drag.DragButton;
|
||||
import org.solovyev.common.utils.Point2d;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 9/16/11
|
||||
* Time: 11:45 PM
|
||||
*/
|
||||
public class CursorDragProcessor implements SimpleOnDragListener.DragProcessor{
|
||||
|
||||
@NotNull
|
||||
private final CursorControl cursorControl;
|
||||
|
||||
public CursorDragProcessor(@NotNull CursorControl cursorControl) {
|
||||
this.cursorControl = cursorControl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean processDragEvent(@NotNull DragDirection dragDirection, @NotNull DragButton dragButton, @NotNull Point2d startPoint2d, @NotNull MotionEvent motionEvent) {
|
||||
boolean result = false;
|
||||
|
||||
if (dragButton instanceof DirectionDragButton) {
|
||||
String text = ((DirectionDragButton) dragButton).getText(dragDirection);
|
||||
if ("◀◀".equals(text)) {
|
||||
cursorControl.setCursorOnStart();
|
||||
result = true;
|
||||
} else if ("▶▶".equals(text)) {
|
||||
cursorControl.setCursorOnEnd();
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
||||
* For more information, please, contact se.solovyev@gmail.com
|
||||
*/
|
||||
|
||||
package org.solovyev.android.calculator;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.solovyev.android.view.drag.DragDirection;
|
||||
import org.solovyev.android.view.drag.SimpleOnDragListener;
|
||||
import org.solovyev.android.view.drag.DirectionDragButton;
|
||||
import org.solovyev.android.view.drag.DragButton;
|
||||
import org.solovyev.common.utils.Point2d;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 9/16/11
|
||||
* Time: 11:48 PM
|
||||
*/
|
||||
public class DigitButtonDragProcessor implements SimpleOnDragListener.DragProcessor {
|
||||
|
||||
@NotNull
|
||||
private final CalculatorModel calculatorModel;
|
||||
|
||||
public DigitButtonDragProcessor(@NotNull CalculatorModel calculatorModel) {
|
||||
this.calculatorModel = calculatorModel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean processDragEvent(@NotNull DragDirection dragDirection, @NotNull DragButton dragButton, @NotNull Point2d startPoint2d, @NotNull MotionEvent motionEvent) {
|
||||
assert dragButton instanceof DirectionDragButton;
|
||||
calculatorModel.processDigitButtonAction(((DirectionDragButton) dragButton).getText(dragDirection));
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.calculator;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.solovyev.android.view.drag.DragDirection;
|
||||
import org.solovyev.android.view.drag.SimpleOnDragListener;
|
||||
import org.solovyev.android.view.drag.DirectionDragButton;
|
||||
import org.solovyev.android.view.drag.DragButton;
|
||||
import org.solovyev.common.utils.Point2d;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 10/24/11
|
||||
* Time: 9:52 PM
|
||||
*/
|
||||
public class EvalDragProcessor implements SimpleOnDragListener.DragProcessor {
|
||||
@NotNull
|
||||
private final CalculatorEngineControl calculatorControl;
|
||||
|
||||
public EvalDragProcessor(@NotNull CalculatorEngineControl calculatorControl) {
|
||||
this.calculatorControl = calculatorControl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean processDragEvent(@NotNull DragDirection dragDirection, @NotNull DragButton dragButton, @NotNull Point2d startPoint2d, @NotNull MotionEvent motionEvent) {
|
||||
boolean result = false;
|
||||
|
||||
if (dragButton instanceof DirectionDragButton) {
|
||||
String text = ((DirectionDragButton) dragButton).getText(dragDirection);
|
||||
if ("≡".equals(text)) {
|
||||
calculatorControl.simplify();
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.calculator;
|
||||
|
||||
import jscl.math.Generic;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.calculator.history.Editor;
|
||||
import org.solovyev.android.calculator.jscl.JsclOperation;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/17/11
|
||||
* Time: 9:45 PM
|
||||
*/
|
||||
public interface ICalculatorDisplay extends Editor{
|
||||
|
||||
boolean isValid();
|
||||
|
||||
void setValid(boolean valid);
|
||||
|
||||
@Nullable
|
||||
String getErrorMessage();
|
||||
|
||||
void setErrorMessage(@Nullable String errorMessage);
|
||||
|
||||
void setJsclOperation(@NotNull JsclOperation jsclOperation);
|
||||
|
||||
@NotNull
|
||||
JsclOperation getJsclOperation();
|
||||
|
||||
void setGenericResult(@Nullable Generic genericResult);
|
||||
|
||||
@Nullable
|
||||
Generic getGenericResult();
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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.calculator.about;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.widget.TextView;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.calculator.R;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/24/11
|
||||
* Time: 11:55 PM
|
||||
*/
|
||||
public class CalculatorAboutActivity extends Activity {
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.about);
|
||||
|
||||
final TextView about = (TextView) findViewById(R.id.aboutTextView);
|
||||
about.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
}
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
||||
* For more information, please, contact se.solovyev@gmail.com
|
||||
*/
|
||||
|
||||
package org.solovyev.android.calculator.about;
|
||||
|
||||
import android.app.TabActivity;
|
||||
import android.os.Bundle;
|
||||
import android.widget.TabHost;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.AndroidUtils;
|
||||
import org.solovyev.android.LastTabSaver;
|
||||
import org.solovyev.android.calculator.R;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 9/16/11
|
||||
* Time: 11:52 PM
|
||||
*/
|
||||
public class CalculatorAboutTabActivity extends TabActivity {
|
||||
|
||||
@Nullable
|
||||
private LastTabSaver lastTabSaver;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.tabs);
|
||||
|
||||
final TabHost tabHost = getTabHost();
|
||||
|
||||
AndroidUtils.addTab(this, tabHost, "about", R.string.c_about, CalculatorAboutActivity.class);
|
||||
AndroidUtils.addTab(this, tabHost, "release_notes", R.string.c_release_notes, CalculatorReleaseNotesActivity.class);
|
||||
|
||||
lastTabSaver = new LastTabSaver(this, "about");
|
||||
|
||||
AndroidUtils.centerAndWrapTabsFor(tabHost);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
if ( this.lastTabSaver != null ) {
|
||||
this.lastTabSaver.destroy();
|
||||
}
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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.calculator.about;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.text.Html;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.widget.TextView;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.solovyev.android.AndroidUtils;
|
||||
import org.solovyev.android.ResourceCache;
|
||||
import org.solovyev.android.calculator.CalculatorActivity;
|
||||
import org.solovyev.android.calculator.R;
|
||||
import org.solovyev.common.utils.StringUtils;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/25/11
|
||||
* Time: 12:00 AM
|
||||
*/
|
||||
public class CalculatorReleaseNotesActivity extends Activity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.release_notes);
|
||||
|
||||
final TextView releaseNotes = (TextView) findViewById(R.id.releaseNotesTextView);
|
||||
releaseNotes.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
|
||||
releaseNotes.setText(Html.fromHtml(getReleaseNotes(this)));
|
||||
|
||||
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String getReleaseNotes(@NotNull Context context) {
|
||||
return getReleaseNotes(context, 0);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String getReleaseNotes(@NotNull Context context, int minVersion) {
|
||||
final StringBuilder result = new StringBuilder();
|
||||
|
||||
final String releaseNotesForTitle = context.getString(R.string.c_release_notes_for_title);
|
||||
final int version = AndroidUtils.getAppVersionCode(context, CalculatorActivity.class.getPackage().getName());
|
||||
|
||||
boolean first = true;
|
||||
for ( int i = version; i >= minVersion; i-- ) {
|
||||
String releaseNotesForVersion = ResourceCache.instance.getCaption("c_release_notes_for_" + i);
|
||||
if (!StringUtils.isEmpty(releaseNotesForVersion)){
|
||||
assert releaseNotesForVersion != null;
|
||||
if ( !first ) {
|
||||
result.append("<br/><br/>");
|
||||
} else {
|
||||
first = false;
|
||||
}
|
||||
releaseNotesForVersion = releaseNotesForVersion.replace("\n", "<br/>");
|
||||
result.append("<b>").append(releaseNotesForTitle).append(i).append("</b><br/><br/>");
|
||||
result.append(releaseNotesForVersion);
|
||||
}
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2012. 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.calculator.help;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import com.google.ads.AdView;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.ads.AdsController;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 1/4/12
|
||||
* Time: 12:34 AM
|
||||
*/
|
||||
public class AbstractHelpActivity extends Activity {
|
||||
|
||||
private final int layoutId;
|
||||
|
||||
@Nullable
|
||||
private AdView adView;
|
||||
|
||||
protected AbstractHelpActivity(int layoutId) {
|
||||
this.layoutId = layoutId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(layoutId);
|
||||
|
||||
// do not inflate ad in help (as some problems were encountered dut to ScrollView - no space for ad banner)
|
||||
adView = AdsController.getInstance().inflateAd(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
if (this.adView != null) {
|
||||
this.adView.destroy();
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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.calculator.help;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.TabActivity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.widget.TabHost;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.AndroidUtils;
|
||||
import org.solovyev.android.LastTabSaver;
|
||||
import org.solovyev.android.calculator.R;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 11/19/11
|
||||
* Time: 11:35 AM
|
||||
*/
|
||||
public class CalculatorHelpTabActivity extends TabActivity {
|
||||
|
||||
@Nullable
|
||||
private LastTabSaver lastTabSaver;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.tabs);
|
||||
|
||||
final TabHost tabHost = getTabHost();
|
||||
|
||||
createTab(tabHost, "faq", R.string.c_faq, HelpFaqActivity.class);
|
||||
createTab(tabHost, "hints", R.string.c_hints, HelpHintsActivity.class);
|
||||
createTab(tabHost, "screens", R.string.c_screens, HelpScreensActivity.class);
|
||||
|
||||
this.lastTabSaver = new LastTabSaver(this, "faq");
|
||||
|
||||
AndroidUtils.centerAndWrapTabsFor(tabHost);
|
||||
}
|
||||
|
||||
private void createTab(@NotNull TabHost tabHost,
|
||||
@NotNull String tabId,
|
||||
int tabCaptionId,
|
||||
@NotNull Class<? extends Activity> activityClass) {
|
||||
|
||||
TabHost.TabSpec spec;
|
||||
|
||||
final Intent intent = new Intent().setClass(this, activityClass);
|
||||
|
||||
// Initialize a TabSpec for each tab and add it to the TabHost
|
||||
spec = tabHost.newTabSpec(tabId).setIndicator(getString(tabCaptionId)).setContent(intent);
|
||||
|
||||
tabHost.addTab(spec);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
if (this.lastTabSaver != null) {
|
||||
this.lastTabSaver.destroy();
|
||||
}
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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.calculator.help;
|
||||
|
||||
import org.solovyev.android.calculator.R;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 11/19/11
|
||||
* Time: 11:37 AM
|
||||
*/
|
||||
public class HelpFaqActivity extends AbstractHelpActivity {
|
||||
|
||||
public HelpFaqActivity() {
|
||||
super(R.layout.help_faq);
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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.calculator.help;
|
||||
|
||||
import org.solovyev.android.calculator.R;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 11/19/11
|
||||
* Time: 11:37 AM
|
||||
*/
|
||||
public class HelpHintsActivity extends AbstractHelpActivity {
|
||||
|
||||
public HelpHintsActivity() {
|
||||
super(R.layout.help_hints);
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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.calculator.help;
|
||||
|
||||
import org.solovyev.android.calculator.R;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 11/19/11
|
||||
* Time: 11:38 AM
|
||||
*/
|
||||
public class HelpScreensActivity extends AbstractHelpActivity{
|
||||
|
||||
public HelpScreensActivity() {
|
||||
super(R.layout.help_screens);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
* 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.calculator.history;
|
||||
|
||||
import android.app.ListActivity;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ListView;
|
||||
import com.google.ads.AdView;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.ads.AdsController;
|
||||
import org.solovyev.android.calculator.CalculatorModel;
|
||||
import org.solovyev.android.calculator.R;
|
||||
import org.solovyev.android.calculator.jscl.JsclOperation;
|
||||
import org.solovyev.android.menu.AMenuBuilder;
|
||||
import org.solovyev.android.menu.MenuImpl;
|
||||
import org.solovyev.common.utils.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 10/15/11
|
||||
* Time: 1:13 PM
|
||||
*/
|
||||
public abstract class AbstractHistoryActivity extends ListActivity {
|
||||
|
||||
public static final Comparator<CalculatorHistoryState> COMPARATOR = new Comparator<CalculatorHistoryState>() {
|
||||
@Override
|
||||
public int compare(CalculatorHistoryState state1, CalculatorHistoryState state2) {
|
||||
if (state1.isSaved() == state2.isSaved()) {
|
||||
long l = state2.getTime() - state1.getTime();
|
||||
return l > 0l ? 1 : (l < 0l ? -1 : 0);
|
||||
} else if (state1.isSaved()) {
|
||||
return -1;
|
||||
} else if (state2.isSaved()) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@NotNull
|
||||
private ArrayAdapter<CalculatorHistoryState> adapter;
|
||||
|
||||
@Nullable
|
||||
private AdView adView;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.history_activity);
|
||||
|
||||
adView = AdsController.getInstance().inflateAd(this);
|
||||
|
||||
adapter = new HistoryArrayAdapter(this, getLayoutId(), R.id.history_item, new ArrayList<CalculatorHistoryState>());
|
||||
setListAdapter(adapter);
|
||||
|
||||
final ListView lv = getListView();
|
||||
lv.setTextFilterEnabled(true);
|
||||
|
||||
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
public void onItemClick(final AdapterView<?> parent,
|
||||
final View view,
|
||||
final int position,
|
||||
final long id) {
|
||||
|
||||
useHistoryItem((CalculatorHistoryState) parent.getItemAtPosition(position), AbstractHistoryActivity.this);
|
||||
}
|
||||
});
|
||||
|
||||
lv.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
|
||||
@Override
|
||||
public boolean onItemLongClick(AdapterView<?> parent, View view, final int position, long id) {
|
||||
final CalculatorHistoryState historyState = (CalculatorHistoryState) parent.getItemAtPosition(position);
|
||||
|
||||
final Context context = AbstractHistoryActivity.this;
|
||||
|
||||
final HistoryItemMenuData data = new HistoryItemMenuData(historyState, adapter);
|
||||
|
||||
final List<HistoryItemMenuItem> menuItems = CollectionsUtils.asList(HistoryItemMenuItem.values());
|
||||
|
||||
if (historyState.isSaved()) {
|
||||
menuItems.remove(HistoryItemMenuItem.save);
|
||||
} else {
|
||||
if (isAlreadySaved(historyState)) {
|
||||
menuItems.remove(HistoryItemMenuItem.save);
|
||||
}
|
||||
menuItems.remove(HistoryItemMenuItem.remove);
|
||||
menuItems.remove(HistoryItemMenuItem.edit);
|
||||
}
|
||||
|
||||
if (historyState.getDisplayState().isValid() && StringUtils.isEmpty(historyState.getDisplayState().getEditorState().getText())) {
|
||||
menuItems.remove(HistoryItemMenuItem.copy_result);
|
||||
}
|
||||
|
||||
final AMenuBuilder<HistoryItemMenuItem, HistoryItemMenuData> menuBuilder = AMenuBuilder.newInstance(context, MenuImpl.newInstance(menuItems));
|
||||
menuBuilder.create(data).show();
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
if ( this.adView != null ) {
|
||||
this.adView.destroy();
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
protected abstract int getLayoutId();
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
final List<CalculatorHistoryState> historyList = getHistoryList();
|
||||
try {
|
||||
this.adapter.setNotifyOnChange(false);
|
||||
this.adapter.clear();
|
||||
for (CalculatorHistoryState historyState : historyList) {
|
||||
this.adapter.add(historyState);
|
||||
}
|
||||
} finally {
|
||||
this.adapter.setNotifyOnChange(true);
|
||||
}
|
||||
|
||||
this.adapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public static boolean isAlreadySaved(@NotNull CalculatorHistoryState historyState) {
|
||||
assert !historyState.isSaved();
|
||||
|
||||
boolean result = false;
|
||||
try {
|
||||
historyState.setSaved(true);
|
||||
if ( CollectionsUtils.contains(historyState, CalculatorHistory.instance.getSavedHistory(), new Equalizer<CalculatorHistoryState>() {
|
||||
@Override
|
||||
public boolean equals(@Nullable CalculatorHistoryState first, @Nullable CalculatorHistoryState second) {
|
||||
return first != null && second != null &&
|
||||
first.getTime() == second.getTime() &&
|
||||
first.getDisplayState().equals(second.getDisplayState()) &&
|
||||
first.getEditorState().equals(second.getEditorState());
|
||||
}
|
||||
}) ) {
|
||||
result = true;
|
||||
}
|
||||
} finally {
|
||||
historyState.setSaved(false);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void useHistoryItem(@NotNull final CalculatorHistoryState historyState, @NotNull AbstractHistoryActivity activity) {
|
||||
|
||||
// before evaluating history item - clear display (in order to get Error message in display if evaluation fail)
|
||||
CalculatorModel.instance.getDisplay().setText("");
|
||||
CalculatorModel.instance.doTextOperation(new CalculatorModel.TextOperation() {
|
||||
@Override
|
||||
public void doOperation(@NotNull EditText editor) {
|
||||
final EditorHistoryState editorState = historyState.getEditorState();
|
||||
editor.setText(editorState.getText());
|
||||
editor.setSelection(editorState.getCursorPosition());
|
||||
}
|
||||
}, false, historyState.getDisplayState().getJsclOperation(), true);
|
||||
|
||||
CalculatorModel.instance.setCursorOnEnd();
|
||||
|
||||
activity.finish();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<CalculatorHistoryState> getHistoryList() {
|
||||
final List<CalculatorHistoryState> calculatorHistoryStates = getHistoryItems();
|
||||
|
||||
Collections.sort(calculatorHistoryStates, COMPARATOR);
|
||||
|
||||
final FilterRulesChain<CalculatorHistoryState> filterRulesChain = new FilterRulesChain<CalculatorHistoryState>();
|
||||
filterRulesChain.addFilterRule(new FilterRule<CalculatorHistoryState>() {
|
||||
@Override
|
||||
public boolean isFiltered(CalculatorHistoryState object) {
|
||||
return object == null || StringUtils.isEmpty(object.getEditorState().getText());
|
||||
}
|
||||
});
|
||||
|
||||
new Filter<CalculatorHistoryState>(filterRulesChain).filter(calculatorHistoryStates.iterator());
|
||||
|
||||
return calculatorHistoryStates;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected abstract List<CalculatorHistoryState> getHistoryItems();
|
||||
|
||||
@NotNull
|
||||
public static String getHistoryText(@NotNull CalculatorHistoryState state) {
|
||||
final StringBuilder result = new StringBuilder();
|
||||
result.append(state.getEditorState().getText());
|
||||
result.append(getIdentitySign(state.getDisplayState().getJsclOperation()));
|
||||
final String expressionResult = state.getDisplayState().getEditorState().getText();
|
||||
if (expressionResult != null) {
|
||||
result.append(expressionResult);
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String getIdentitySign(@NotNull JsclOperation jsclOperation) {
|
||||
return jsclOperation == JsclOperation.simplify ? "≡" : "=";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(android.view.Menu menu) {
|
||||
final MenuInflater menuInflater = getMenuInflater();
|
||||
menuInflater.inflate(R.menu.history_menu, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
boolean result;
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case R.id.history_menu_clear_history:
|
||||
clearHistory();
|
||||
result = true;
|
||||
break;
|
||||
default:
|
||||
result = super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected abstract void clearHistory();
|
||||
|
||||
@NotNull
|
||||
protected ArrayAdapter<CalculatorHistoryState> getAdapter() {
|
||||
return adapter;
|
||||
}
|
||||
}
|
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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.calculator.history;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.simpleframework.xml.Element;
|
||||
import org.simpleframework.xml.Transient;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 10/15/11
|
||||
* Time: 1:45 PM
|
||||
*/
|
||||
public class AbstractHistoryState implements Cloneable{
|
||||
|
||||
@Element
|
||||
private long time = new Date().getTime();
|
||||
|
||||
@Element(required = false)
|
||||
@Nullable
|
||||
private String comment;
|
||||
|
||||
@Transient
|
||||
private boolean saved;
|
||||
|
||||
@Transient
|
||||
private int id = 0;
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public long getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public void setTime(long time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getComment() {
|
||||
return comment;
|
||||
}
|
||||
|
||||
public void setComment(@Nullable String comment) {
|
||||
this.comment = comment;
|
||||
}
|
||||
|
||||
public boolean isSaved() {
|
||||
return saved;
|
||||
}
|
||||
|
||||
public void setSaved(boolean saved) {
|
||||
this.saved = saved;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractHistoryState clone() {
|
||||
AbstractHistoryState clone;
|
||||
|
||||
try {
|
||||
clone = (AbstractHistoryState)super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new UnsupportedOperationException(e);
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
}
|
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
||||
* For more information, please, contact se.solovyev@gmail.com
|
||||
*/
|
||||
|
||||
package org.solovyev.android.calculator.history;
|
||||
|
||||
import jscl.math.Generic;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.simpleframework.xml.Element;
|
||||
import org.simpleframework.xml.Root;
|
||||
import org.simpleframework.xml.Transient;
|
||||
import org.solovyev.android.calculator.ICalculatorDisplay;
|
||||
import org.solovyev.android.calculator.jscl.JsclOperation;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 9/17/11
|
||||
* Time: 11:05 PM
|
||||
*/
|
||||
|
||||
@Root
|
||||
public class CalculatorDisplayHistoryState implements Cloneable {
|
||||
|
||||
@Transient
|
||||
private boolean valid = true;
|
||||
|
||||
@Transient
|
||||
@Nullable
|
||||
private String errorMessage = null;
|
||||
|
||||
@Element
|
||||
@NotNull
|
||||
private EditorHistoryState editorState;
|
||||
|
||||
@Element
|
||||
@NotNull
|
||||
private JsclOperation jsclOperation;
|
||||
|
||||
@Transient
|
||||
@Nullable
|
||||
private Generic genericResult;
|
||||
|
||||
private CalculatorDisplayHistoryState() {
|
||||
// for xml
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static CalculatorDisplayHistoryState newInstance(@NotNull ICalculatorDisplay display) {
|
||||
final CalculatorDisplayHistoryState result = new CalculatorDisplayHistoryState();
|
||||
|
||||
result.editorState = EditorHistoryState.newInstance(display);
|
||||
result.valid = display.isValid();
|
||||
result.jsclOperation = display.getJsclOperation();
|
||||
result.genericResult = display.getGenericResult();
|
||||
result.errorMessage = display.getErrorMessage();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void setValuesFromHistory(@NotNull ICalculatorDisplay display) {
|
||||
this.getEditorState().setValuesFromHistory(display);
|
||||
display.setValid(this.isValid());
|
||||
display.setErrorMessage(this.getErrorMessage());
|
||||
display.setJsclOperation(this.getJsclOperation());
|
||||
display.setGenericResult(this.getGenericResult());
|
||||
}
|
||||
|
||||
|
||||
public boolean isValid() {
|
||||
return valid;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public EditorHistoryState getEditorState() {
|
||||
return editorState;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public JsclOperation getJsclOperation() {
|
||||
return jsclOperation;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Generic getGenericResult() {
|
||||
return genericResult;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
CalculatorDisplayHistoryState that = (CalculatorDisplayHistoryState) o;
|
||||
|
||||
if (!editorState.equals(that.editorState)) return false;
|
||||
if (jsclOperation != that.jsclOperation) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = editorState.hashCode();
|
||||
result = 31 * result + jsclOperation.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CalculatorDisplayHistoryState{" +
|
||||
"valid=" + valid +
|
||||
", errorMessage='" + errorMessage + '\'' +
|
||||
", editorHistoryState=" + editorState +
|
||||
", jsclOperation=" + jsclOperation +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CalculatorDisplayHistoryState clone() {
|
||||
try {
|
||||
final CalculatorDisplayHistoryState clone = (CalculatorDisplayHistoryState) super.clone();
|
||||
|
||||
clone.editorState = this.editorState.clone();
|
||||
|
||||
return clone;
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* 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.calculator.history;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.calculator.R;
|
||||
import org.solovyev.common.utils.history.HistoryAction;
|
||||
import org.solovyev.common.utils.history.HistoryHelper;
|
||||
import org.solovyev.common.utils.history.SimpleHistoryHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 10/9/11
|
||||
* Time: 6:35 PM
|
||||
*/
|
||||
public enum CalculatorHistory implements HistoryHelper<CalculatorHistoryState> {
|
||||
|
||||
instance;
|
||||
|
||||
// todo serso: not synchronized
|
||||
private int counter = 0;
|
||||
|
||||
@NotNull
|
||||
private final HistoryHelper<CalculatorHistoryState> history = new SimpleHistoryHelper<CalculatorHistoryState>();
|
||||
|
||||
@NotNull
|
||||
private final List<CalculatorHistoryState> savedHistory = new ArrayList<CalculatorHistoryState> ();
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return this.history.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CalculatorHistoryState getLastHistoryState() {
|
||||
return this.history.getLastHistoryState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUndoAvailable() {
|
||||
return history.isUndoAvailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CalculatorHistoryState undo(@Nullable CalculatorHistoryState currentState) {
|
||||
return history.undo(currentState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRedoAvailable() {
|
||||
return history.isRedoAvailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CalculatorHistoryState redo(@Nullable CalculatorHistoryState currentState) {
|
||||
return history.redo(currentState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActionAvailable(@NotNull HistoryAction historyAction) {
|
||||
return history.isActionAvailable(historyAction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CalculatorHistoryState doAction(@NotNull HistoryAction historyAction, @Nullable CalculatorHistoryState currentState) {
|
||||
return history.doAction(historyAction, currentState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addState(@Nullable CalculatorHistoryState currentState) {
|
||||
history.addState(currentState);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<CalculatorHistoryState> getStates() {
|
||||
return history.getStates();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
this.history.clear();
|
||||
}
|
||||
|
||||
public void load(@Nullable Context context, @Nullable SharedPreferences preferences) {
|
||||
if (context != null && preferences != null) {
|
||||
final String value = preferences.getString(context.getString(R.string.p_calc_history), null);
|
||||
this.savedHistory.clear();
|
||||
HistoryUtils.fromXml(value, this.savedHistory);
|
||||
for (CalculatorHistoryState historyState : savedHistory) {
|
||||
historyState.setSaved(true);
|
||||
historyState.setId(counter++);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void save(@NotNull Context context) {
|
||||
final SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
final SharedPreferences.Editor editor = settings.edit();
|
||||
|
||||
editor.putString(context.getString(R.string.p_calc_history), HistoryUtils.toXml(this.savedHistory));
|
||||
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<CalculatorHistoryState> getSavedHistory() {
|
||||
return Collections.unmodifiableList(savedHistory);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CalculatorHistoryState addSavedState(@NotNull CalculatorHistoryState historyState) {
|
||||
if (historyState.isSaved()) {
|
||||
return historyState;
|
||||
} else {
|
||||
final CalculatorHistoryState savedState = historyState.clone();
|
||||
|
||||
savedState.setId(counter++);
|
||||
savedState.setSaved(true);
|
||||
|
||||
savedHistory.add(savedState);
|
||||
|
||||
return savedState;
|
||||
}
|
||||
}
|
||||
|
||||
public void clearSavedHistory(@NotNull Context context) {
|
||||
this.savedHistory.clear();
|
||||
save(context);
|
||||
}
|
||||
|
||||
public void removeSavedHistory(@NotNull CalculatorHistoryState historyState, @NotNull Context context) {
|
||||
historyState.setSaved(false);
|
||||
this.savedHistory.remove(historyState);
|
||||
save(context);
|
||||
}
|
||||
}
|
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
||||
* For more information, please, contact se.solovyev@gmail.com
|
||||
*/
|
||||
|
||||
package org.solovyev.android.calculator.history;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.simpleframework.xml.Element;
|
||||
import org.simpleframework.xml.Root;
|
||||
import org.solovyev.android.calculator.ICalculatorDisplay;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 9/11/11
|
||||
* Time: 12:16 AM
|
||||
*/
|
||||
|
||||
@Root
|
||||
public class CalculatorHistoryState extends AbstractHistoryState {
|
||||
|
||||
@Element
|
||||
@NotNull
|
||||
private EditorHistoryState editorState;
|
||||
|
||||
@Element
|
||||
@NotNull
|
||||
private CalculatorDisplayHistoryState displayState;
|
||||
|
||||
private CalculatorHistoryState() {
|
||||
// for xml
|
||||
}
|
||||
|
||||
private CalculatorHistoryState(@NotNull EditorHistoryState editorState,
|
||||
@NotNull CalculatorDisplayHistoryState displayState) {
|
||||
this.editorState = editorState;
|
||||
this.displayState = displayState;
|
||||
}
|
||||
|
||||
public static CalculatorHistoryState newInstance(@NotNull Editor editor, @NotNull ICalculatorDisplay display) {
|
||||
final EditorHistoryState editorHistoryState = EditorHistoryState.newInstance(editor);
|
||||
final CalculatorDisplayHistoryState displayHistoryState = CalculatorDisplayHistoryState.newInstance(display);
|
||||
return new CalculatorHistoryState(editorHistoryState, displayHistoryState);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public EditorHistoryState getEditorState() {
|
||||
return editorState;
|
||||
}
|
||||
|
||||
public void setEditorState(@NotNull EditorHistoryState editorState) {
|
||||
this.editorState = editorState;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CalculatorDisplayHistoryState getDisplayState() {
|
||||
return displayState;
|
||||
}
|
||||
|
||||
public void setDisplayState(@NotNull CalculatorDisplayHistoryState displayState) {
|
||||
this.displayState = displayState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CalculatorHistoryState{" +
|
||||
"editorState=" + editorState +
|
||||
", displayState=" + displayState +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
CalculatorHistoryState that = (CalculatorHistoryState) o;
|
||||
|
||||
if (this.isSaved() != that.isSaved()) return false;
|
||||
if (this.getId() != that.getId()) return false;
|
||||
if (!displayState.equals(that.displayState)) return false;
|
||||
if (!editorState.equals(that.editorState)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = Boolean.valueOf(isSaved()).hashCode();
|
||||
result = 31 * result + getId();
|
||||
result = 31 * result + editorState.hashCode();
|
||||
result = 31 * result + displayState.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
public void setValuesFromHistory(@NotNull Editor editor, @NotNull ICalculatorDisplay display) {
|
||||
this.getEditorState().setValuesFromHistory(editor);
|
||||
this.getDisplayState().setValuesFromHistory(display);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CalculatorHistoryState clone() {
|
||||
final CalculatorHistoryState clone = (CalculatorHistoryState)super.clone();
|
||||
|
||||
clone.editorState = this.editorState.clone();
|
||||
clone.displayState = this.displayState.clone();
|
||||
|
||||
return clone;
|
||||
}
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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.calculator.history;
|
||||
|
||||
import android.app.TabActivity;
|
||||
import android.os.Bundle;
|
||||
import android.widget.TabHost;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.AndroidUtils;
|
||||
import org.solovyev.android.LastTabSaver;
|
||||
import org.solovyev.android.calculator.R;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/18/11
|
||||
* Time: 7:37 PM
|
||||
*/
|
||||
public class CalculatorHistoryTabActivity extends TabActivity {
|
||||
|
||||
@Nullable
|
||||
private LastTabSaver lastTabSaver;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.tabs);
|
||||
|
||||
final TabHost tabHost = getTabHost();
|
||||
|
||||
AndroidUtils.addTab(this, tabHost, "saved_history", R.string.c_saved_history, SavedHistoryActivityTab.class);
|
||||
AndroidUtils.addTab(this, tabHost, "history", R.string.c_history, HistoryActivityTab.class);
|
||||
|
||||
this.lastTabSaver = new LastTabSaver(this, "saved_history");
|
||||
|
||||
AndroidUtils.centerAndWrapTabsFor(tabHost);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
if ( this.lastTabSaver != null ) {
|
||||
this.lastTabSaver.destroy();
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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.calculator.history;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/17/11
|
||||
* Time: 9:37 PM
|
||||
*/
|
||||
public interface Editor {
|
||||
|
||||
@Nullable
|
||||
CharSequence getText();
|
||||
|
||||
void setText(@Nullable CharSequence text);
|
||||
|
||||
int getSelection();
|
||||
|
||||
void setSelection(int selection);
|
||||
|
||||
}
|
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
||||
* For more information, please, contact se.solovyev@gmail.com
|
||||
*/
|
||||
|
||||
package org.solovyev.android.calculator.history;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.simpleframework.xml.Element;
|
||||
import org.simpleframework.xml.Root;
|
||||
|
||||
@Root
|
||||
public class EditorHistoryState implements Cloneable{
|
||||
|
||||
@Element
|
||||
private int cursorPosition;
|
||||
|
||||
@Element(required = false)
|
||||
@Nullable
|
||||
private String text;
|
||||
|
||||
private EditorHistoryState() {
|
||||
// for xml
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static EditorHistoryState newInstance(@NotNull Editor editor) {
|
||||
final EditorHistoryState result = new EditorHistoryState();
|
||||
|
||||
result.text = String.valueOf(editor.getText());
|
||||
result.cursorPosition = editor.getSelection();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void setValuesFromHistory(@NotNull Editor editor) {
|
||||
editor.setText(this.getText());
|
||||
editor.setSelection(this.getCursorPosition());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public int getCursorPosition() {
|
||||
return cursorPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof EditorHistoryState)) return false;
|
||||
|
||||
EditorHistoryState that = (EditorHistoryState) o;
|
||||
|
||||
if (cursorPosition != that.cursorPosition) return false;
|
||||
if (text != null ? !text.equals(that.text) : that.text != null) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = cursorPosition;
|
||||
result = 31 * result + (text != null ? text.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "EditorHistoryState{" +
|
||||
"cursorPosition=" + cursorPosition +
|
||||
", text='" + text + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EditorHistoryState clone() {
|
||||
try {
|
||||
return (EditorHistoryState)super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new UnsupportedOperationException(e);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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.calculator.history;
|
||||
|
||||
import org.simpleframework.xml.ElementList;
|
||||
import org.simpleframework.xml.Root;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/17/11
|
||||
* Time: 9:30 PM
|
||||
*/
|
||||
|
||||
@Root
|
||||
public class History {
|
||||
|
||||
@ElementList
|
||||
private List<CalculatorHistoryState> historyItems = new ArrayList<CalculatorHistoryState>();
|
||||
|
||||
public History() {
|
||||
}
|
||||
|
||||
public List<CalculatorHistoryState> getHistoryItems() {
|
||||
return historyItems;
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.calculator.history;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.solovyev.android.calculator.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/18/11
|
||||
* Time: 7:39 PM
|
||||
*/
|
||||
public class HistoryActivityTab extends AbstractHistoryActivity {
|
||||
@Override
|
||||
protected int getLayoutId() {
|
||||
return R.layout.history;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected List<CalculatorHistoryState> getHistoryItems() {
|
||||
return new ArrayList<CalculatorHistoryState>(CalculatorHistory.instance.getStates());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void clearHistory() {
|
||||
CalculatorHistory.instance.clear();
|
||||
getAdapter().clear();
|
||||
}
|
||||
}
|
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.calculator.history;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.TextView;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.solovyev.android.calculator.R;
|
||||
import org.solovyev.common.utils.StringUtils;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/18/11
|
||||
* Time: 7:39 PM
|
||||
*/
|
||||
public class HistoryArrayAdapter extends ArrayAdapter<CalculatorHistoryState> {
|
||||
|
||||
HistoryArrayAdapter(Context context, int resource, int textViewResourceId, @NotNull List<CalculatorHistoryState> historyList) {
|
||||
super(context, resource, textViewResourceId, historyList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
final ViewGroup result = (ViewGroup) super.getView(position, convertView, parent);
|
||||
|
||||
final CalculatorHistoryState state = getItem(position);
|
||||
|
||||
final TextView time = (TextView) result.findViewById(R.id.history_time);
|
||||
time.setText(new SimpleDateFormat().format(new Date(state.getTime())));
|
||||
|
||||
final TextView editor = (TextView) result.findViewById(R.id.history_item);
|
||||
editor.setText(AbstractHistoryActivity.getHistoryText(state));
|
||||
|
||||
final TextView commentView = (TextView) result.findViewById(R.id.history_item_comment);
|
||||
if (commentView != null) {
|
||||
final String comment = state.getComment();
|
||||
if (!StringUtils.isEmpty(comment)) {
|
||||
commentView.setText(comment);
|
||||
} else {
|
||||
commentView.setText("");
|
||||
}
|
||||
}
|
||||
|
||||
final TextView status = (TextView) result.findViewById(R.id.history_item_status);
|
||||
if (status != null) {
|
||||
if (state.isSaved()) {
|
||||
status.setText(getContext().getString(R.string.c_history_item_saved));
|
||||
} else {
|
||||
if ( AbstractHistoryActivity.isAlreadySaved(state) ) {
|
||||
status.setText(getContext().getString(R.string.c_history_item_already_saved));
|
||||
} else {
|
||||
status.setText(getContext().getString(R.string.c_history_item_not_saved));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyDataSetChanged() {
|
||||
this.setNotifyOnChange(false);
|
||||
this.sort(AbstractHistoryActivity.COMPARATOR);
|
||||
this.setNotifyOnChange(true);
|
||||
super.notifyDataSetChanged();
|
||||
}
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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.calculator.history;
|
||||
|
||||
import android.widget.ArrayAdapter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/18/11
|
||||
* Time: 3:10 PM
|
||||
*/
|
||||
public class HistoryItemMenuData {
|
||||
|
||||
@NotNull
|
||||
private final ArrayAdapter<CalculatorHistoryState> adapter;
|
||||
|
||||
@NotNull
|
||||
private final CalculatorHistoryState historyState;
|
||||
|
||||
public HistoryItemMenuData(@NotNull CalculatorHistoryState historyState, ArrayAdapter<CalculatorHistoryState> adapter) {
|
||||
this.historyState = historyState;
|
||||
this.adapter = adapter;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CalculatorHistoryState getHistoryState() {
|
||||
return historyState;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ArrayAdapter<CalculatorHistoryState> getAdapter() {
|
||||
return adapter;
|
||||
}
|
||||
}
|
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* 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.calculator.history;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.text.ClipboardManager;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.solovyev.android.calculator.R;
|
||||
import org.solovyev.android.menu.AMenuItem;
|
||||
import org.solovyev.common.utils.StringUtils;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/18/11
|
||||
* Time: 3:09 PM
|
||||
*/
|
||||
public enum HistoryItemMenuItem implements AMenuItem<HistoryItemMenuData> {
|
||||
|
||||
use(R.string.c_use) {
|
||||
@Override
|
||||
public void doAction(@NotNull HistoryItemMenuData data, @NotNull Context context) {
|
||||
if (context instanceof AbstractHistoryActivity) {
|
||||
AbstractHistoryActivity.useHistoryItem(data.getHistoryState(), (AbstractHistoryActivity) context);
|
||||
} else {
|
||||
Log.e(HistoryItemMenuItem.class.getName(), AbstractHistoryActivity.class + " must be passed as context!");
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
copy_expression(R.string.c_copy_expression) {
|
||||
@Override
|
||||
public void doAction(@NotNull HistoryItemMenuData data, @NotNull Context context) {
|
||||
final CalculatorHistoryState calculatorHistoryState = data.getHistoryState();
|
||||
final String text = calculatorHistoryState.getEditorState().getText();
|
||||
if (!StringUtils.isEmpty(text)) {
|
||||
final ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Activity.CLIPBOARD_SERVICE);
|
||||
clipboard.setText(text);
|
||||
Toast.makeText(context, context.getText(R.string.c_expression_copied), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
copy_result(R.string.c_copy_result) {
|
||||
@Override
|
||||
public void doAction(@NotNull HistoryItemMenuData data, @NotNull Context context) {
|
||||
final CalculatorHistoryState calculatorHistoryState = data.getHistoryState();
|
||||
final String text = calculatorHistoryState.getDisplayState().getEditorState().getText();
|
||||
if (!StringUtils.isEmpty(text)) {
|
||||
final ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Activity.CLIPBOARD_SERVICE);
|
||||
clipboard.setText(text);
|
||||
Toast.makeText(context, context.getText(R.string.c_result_copied), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
save(R.string.c_save) {
|
||||
@Override
|
||||
public void doAction(@NotNull final HistoryItemMenuData data, @NotNull final Context context) {
|
||||
final CalculatorHistoryState historyState = data.getHistoryState();
|
||||
if (!historyState.isSaved()) {
|
||||
createEditHistoryDialog(data, context, true);
|
||||
} else {
|
||||
Toast.makeText(context, context.getText(R.string.c_history_already_saved), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
edit(R.string.c_edit) {
|
||||
@Override
|
||||
public void doAction(@NotNull final HistoryItemMenuData data, @NotNull final Context context) {
|
||||
final CalculatorHistoryState historyState = data.getHistoryState();
|
||||
if (historyState.isSaved()) {
|
||||
createEditHistoryDialog(data, context, false);
|
||||
} else {
|
||||
Toast.makeText(context, context.getText(R.string.c_history_must_be_saved), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
remove(R.string.c_remove) {
|
||||
@Override
|
||||
public void doAction(@NotNull HistoryItemMenuData data, @NotNull Context context) {
|
||||
final CalculatorHistoryState historyState = data.getHistoryState();
|
||||
if (historyState.isSaved()) {
|
||||
data.getAdapter().remove(historyState);
|
||||
CalculatorHistory.instance.removeSavedHistory(historyState, context);
|
||||
Toast.makeText(context, context.getText(R.string.c_history_was_removed), Toast.LENGTH_LONG).show();
|
||||
data.getAdapter().notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private static void createEditHistoryDialog(@NotNull final HistoryItemMenuData data, @NotNull final Context context, final boolean save) {
|
||||
final CalculatorHistoryState historyState = data.getHistoryState();
|
||||
|
||||
final LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
final View editView = layoutInflater.inflate(R.layout.history_edit, null);
|
||||
final TextView historyExpression = (TextView)editView.findViewById(R.id.history_edit_expression);
|
||||
historyExpression.setText(AbstractHistoryActivity.getHistoryText(historyState));
|
||||
|
||||
final EditText comment = (EditText)editView.findViewById(R.id.history_edit_comment);
|
||||
comment.setText(historyState.getComment());
|
||||
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(context)
|
||||
.setTitle(save ? R.string.c_save_history : R.string.c_edit_history)
|
||||
.setCancelable(true)
|
||||
.setNegativeButton(R.string.c_cancel, null)
|
||||
.setPositiveButton(R.string.c_save, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (save) {
|
||||
final CalculatorHistoryState savedHistoryItem = CalculatorHistory.instance.addSavedState(historyState);
|
||||
savedHistoryItem.setComment(comment.getText().toString());
|
||||
CalculatorHistory.instance.save(context);
|
||||
// we don't need to add element to the adapter as adapter of another activity must be updated and not this
|
||||
//data.getAdapter().add(savedHistoryItem);
|
||||
} else {
|
||||
historyState.setComment(comment.getText().toString());
|
||||
CalculatorHistory.instance.save(context);
|
||||
}
|
||||
data.getAdapter().notifyDataSetChanged();
|
||||
Toast.makeText(context, context.getText(R.string.c_history_saved), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
})
|
||||
.setView(editView);
|
||||
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
private final int captionId;
|
||||
|
||||
private HistoryItemMenuItem(int captionId) {
|
||||
this.captionId = captionId;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getCaption(@NotNull Context context) {
|
||||
return context.getString(captionId);
|
||||
}
|
||||
}
|
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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.calculator.history;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.simpleframework.xml.Serializer;
|
||||
import org.simpleframework.xml.core.Persister;
|
||||
|
||||
import java.io.StringWriter;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/17/11
|
||||
* Time: 9:59 PM
|
||||
*/
|
||||
class HistoryUtils {
|
||||
|
||||
// not intended for instantiation
|
||||
private HistoryUtils() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
public static void fromXml(@Nullable String xml, @NotNull List<CalculatorHistoryState> historyItems) {
|
||||
if (xml != null) {
|
||||
final Serializer serializer = new Persister();
|
||||
try {
|
||||
final History history = serializer.read(History.class, xml);
|
||||
for (CalculatorHistoryState historyItem : history.getHistoryItems()) {
|
||||
historyItems.add(historyItem);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String toXml(@NotNull List<CalculatorHistoryState> historyItems) {
|
||||
final History history = new History();
|
||||
for (CalculatorHistoryState historyState : historyItems) {
|
||||
if (historyState.isSaved()) {
|
||||
history.getHistoryItems().add(historyState);
|
||||
}
|
||||
}
|
||||
|
||||
final StringWriter xml = new StringWriter();
|
||||
final Serializer serializer = new Persister();
|
||||
try {
|
||||
serializer.write(history, xml);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return xml.toString();
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.calculator.history;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.solovyev.android.calculator.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/18/11
|
||||
* Time: 7:40 PM
|
||||
*/
|
||||
public class SavedHistoryActivityTab extends AbstractHistoryActivity {
|
||||
@Override
|
||||
protected int getLayoutId() {
|
||||
return R.layout.saved_history;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected List<CalculatorHistoryState> getHistoryItems() {
|
||||
return new ArrayList<CalculatorHistoryState>(CalculatorHistory.instance.getSavedHistory());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void clearHistory() {
|
||||
CalculatorHistory.instance.clearSavedHistory(this);
|
||||
getAdapter().clear();
|
||||
}
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.calculator.history;
|
||||
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/17/11
|
||||
* Time: 9:39 PM
|
||||
*/
|
||||
public class TextViewEditorAdapter implements Editor {
|
||||
|
||||
@NotNull
|
||||
private final TextView textView;
|
||||
|
||||
public TextViewEditorAdapter(@NotNull TextView textView) {
|
||||
this.textView = textView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getText() {
|
||||
return textView.getText().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setText(@Nullable CharSequence text) {
|
||||
textView.setText(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSelection() {
|
||||
return textView.getSelectionStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelection(int selection) {
|
||||
if ( textView instanceof EditText ) {
|
||||
((EditText) textView).setSelection(selection);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.calculator.jscl;
|
||||
|
||||
import jscl.math.Generic;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.solovyev.android.calculator.model.CalculatorParseException;
|
||||
import org.solovyev.android.calculator.model.TextProcessor;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 10/6/11
|
||||
* Time: 9:48 PM
|
||||
*/
|
||||
class FromJsclNumericTextProcessor implements TextProcessor<String, Generic> {
|
||||
|
||||
public static final FromJsclNumericTextProcessor instance = new FromJsclNumericTextProcessor();
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String process(@NotNull Generic numeric) throws CalculatorParseException {
|
||||
return numeric.toString().replace("*", "");
|
||||
}
|
||||
}
|
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
||||
* For more information, please, contact se.solovyev@gmail.com
|
||||
*/
|
||||
|
||||
package org.solovyev.android.calculator.jscl;
|
||||
|
||||
|
||||
import jscl.math.Generic;
|
||||
import jscl.text.ParseException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.solovyev.android.calculator.model.CalculatorEngine;
|
||||
import org.solovyev.android.calculator.model.DummyTextProcessor;
|
||||
import org.solovyev.android.calculator.model.FromJsclSimplifyTextProcessor;
|
||||
import org.solovyev.android.calculator.model.TextProcessor;
|
||||
|
||||
public enum JsclOperation {
|
||||
|
||||
simplify,
|
||||
elementary,
|
||||
numeric;
|
||||
|
||||
JsclOperation() {
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
public TextProcessor<String, Generic> getFromProcessor() {
|
||||
switch (this) {
|
||||
case simplify:
|
||||
return FromJsclSimplifyTextProcessor.instance;
|
||||
case elementary:
|
||||
return DummyTextProcessor.instance;
|
||||
case numeric:
|
||||
return FromJsclNumericTextProcessor.instance;
|
||||
default:
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public final String evaluate(@NotNull String expression) throws ParseException {
|
||||
switch (this) {
|
||||
case simplify:
|
||||
return CalculatorEngine.instance.getEngine().simplify(expression);
|
||||
case elementary:
|
||||
return CalculatorEngine.instance.getEngine().elementary(expression);
|
||||
case numeric:
|
||||
return CalculatorEngine.instance.getEngine().evaluate(expression);
|
||||
default:
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public final Generic evaluateGeneric(@NotNull String expression) throws ParseException {
|
||||
switch (this) {
|
||||
case simplify:
|
||||
return CalculatorEngine.instance.getEngine().simplifyGeneric(expression);
|
||||
case elementary:
|
||||
return CalculatorEngine.instance.getEngine().elementaryGeneric(expression);
|
||||
case numeric:
|
||||
return CalculatorEngine.instance.getEngine().evaluateGeneric(expression);
|
||||
default:
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,448 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
||||
* For more information, please, contact se.solovyev@gmail.com
|
||||
*/
|
||||
|
||||
package org.solovyev.android.calculator.math;
|
||||
|
||||
import jscl.JsclMathEngine;
|
||||
import jscl.NumeralBase;
|
||||
import jscl.math.function.Constants;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.common.StartsWithFinder;
|
||||
import org.solovyev.android.calculator.model.CalculatorEngine;
|
||||
import org.solovyev.android.calculator.model.CalculatorParseException;
|
||||
import org.solovyev.common.utils.CollectionsUtils;
|
||||
import org.solovyev.common.utils.Finder;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
|
||||
public enum MathType {
|
||||
|
||||
numeral_base(50, true, false, MathGroupType.number) {
|
||||
|
||||
private final List<String> tokens = new ArrayList<String>(10);
|
||||
{
|
||||
for (NumeralBase numeralBase : NumeralBase.values()) {
|
||||
final String jsclPrefix = numeralBase.getJsclPrefix();
|
||||
if (jsclPrefix != null) {
|
||||
tokens.add(jsclPrefix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<String> getTokens() {
|
||||
return tokens;
|
||||
}
|
||||
},
|
||||
|
||||
dot(200, true, true, MathGroupType.number, ".") {
|
||||
@Override
|
||||
public boolean isNeedMultiplicationSignBefore(@NotNull MathType mathTypeBefore) {
|
||||
return super.isNeedMultiplicationSignBefore(mathTypeBefore) && mathTypeBefore != digit;
|
||||
}
|
||||
},
|
||||
|
||||
grouping_separator(250, false, false, MathGroupType.number, "'", " "){
|
||||
@Override
|
||||
public int processToJscl(@NotNull StringBuilder result, int i, @NotNull String match) throws CalculatorParseException {
|
||||
return i;
|
||||
}
|
||||
},
|
||||
|
||||
power_10(300, false, false, MathGroupType.number, "E"),
|
||||
|
||||
postfix_function(400, false, true, MathGroupType.function) {
|
||||
@NotNull
|
||||
@Override
|
||||
public List<String> getTokens() {
|
||||
return CalculatorEngine.instance.getPostfixFunctionsRegistry().getNames();
|
||||
}
|
||||
},
|
||||
|
||||
unary_operation(500, false, false, MathGroupType.operation, "-", "="),
|
||||
binary_operation(600, false, false, MathGroupType.operation, "-", "+", "*", "×", "∙", "/", "^") {
|
||||
@Override
|
||||
protected String getSubstituteToJscl(@NotNull String match) {
|
||||
if (match.equals("×") || match.equals("∙")) {
|
||||
return "*";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
open_group_symbol(800, true, false, MathGroupType.other, "[", "(", "{") {
|
||||
@Override
|
||||
public boolean isNeedMultiplicationSignBefore(@NotNull MathType mathTypeBefore) {
|
||||
return super.isNeedMultiplicationSignBefore(mathTypeBefore) && mathTypeBefore != function && mathTypeBefore != operator;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSubstituteToJscl(@NotNull String match) {
|
||||
return "(";
|
||||
}
|
||||
},
|
||||
|
||||
close_group_symbol(900, false, true, MathGroupType.other, "]", ")", "}") {
|
||||
@Override
|
||||
public boolean isNeedMultiplicationSignBefore(@NotNull MathType mathTypeBefore) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSubstituteToJscl(@NotNull String match) {
|
||||
return ")";
|
||||
}
|
||||
},
|
||||
|
||||
function(1000, true, true, MathGroupType.function) {
|
||||
@NotNull
|
||||
@Override
|
||||
public List<String> getTokens() {
|
||||
return CalculatorEngine.instance.getFunctionsRegistry().getNames();
|
||||
}
|
||||
},
|
||||
|
||||
operator(1050, true, true, MathGroupType.function) {
|
||||
@NotNull
|
||||
@Override
|
||||
public List<String> getTokens() {
|
||||
return CalculatorEngine.instance.getOperatorsRegistry().getNames();
|
||||
}
|
||||
},
|
||||
|
||||
constant(1100, true, true, MathGroupType.other) {
|
||||
@NotNull
|
||||
@Override
|
||||
public List<String> getTokens() {
|
||||
return CalculatorEngine.instance.getVarsRegistry().getNames();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSubstituteFromJscl(@NotNull String match) {
|
||||
return Constants.INF_2.getName().equals(match) ? MathType.INFINITY : super.getSubstituteFromJscl(match);
|
||||
}
|
||||
},
|
||||
|
||||
digit(1125, true, true, MathGroupType.number) {
|
||||
|
||||
private final List<String> tokens = new ArrayList<String>(16);
|
||||
{
|
||||
for (Character character : NumeralBase.hex.getAcceptableCharacters()) {
|
||||
tokens.add(character.toString());
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public boolean isNeedMultiplicationSignBefore(@NotNull MathType mathTypeBefore) {
|
||||
return super.isNeedMultiplicationSignBefore(mathTypeBefore) && mathTypeBefore != digit && mathTypeBefore != dot /*&& mathTypeBefore != numeral_base*/;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<String> getTokens() {
|
||||
return tokens;
|
||||
}
|
||||
},
|
||||
|
||||
comma(1150, false, false, MathGroupType.other, ","),
|
||||
|
||||
text(1200, false, false, MathGroupType.other) {
|
||||
@Override
|
||||
public int processToJscl(@NotNull StringBuilder result, int i, @NotNull String match) {
|
||||
if (match.length() > 0) {
|
||||
result.append(match.charAt(0));
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int processFromJscl(@NotNull StringBuilder result, int i, @NotNull String match) {
|
||||
if (match.length() > 0) {
|
||||
result.append(match.charAt(0));
|
||||
}
|
||||
return i;
|
||||
}
|
||||
};
|
||||
|
||||
public static enum MathGroupType {
|
||||
function,
|
||||
number,
|
||||
operation,
|
||||
other
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private final List<String> tokens;
|
||||
|
||||
@NotNull
|
||||
private final Integer priority;
|
||||
|
||||
private final boolean needMultiplicationSignBefore;
|
||||
|
||||
private final boolean needMultiplicationSignAfter;
|
||||
|
||||
@NotNull
|
||||
private final MathGroupType groupType;
|
||||
|
||||
MathType(@NotNull Integer priority,
|
||||
boolean needMultiplicationSignBefore,
|
||||
boolean needMultiplicationSignAfter,
|
||||
@NotNull MathGroupType groupType,
|
||||
@NotNull String... tokens) {
|
||||
this(priority, needMultiplicationSignBefore, needMultiplicationSignAfter, groupType, CollectionsUtils.asList(tokens));
|
||||
}
|
||||
|
||||
MathType(@NotNull Integer priority,
|
||||
boolean needMultiplicationSignBefore,
|
||||
boolean needMultiplicationSignAfter,
|
||||
@NotNull MathGroupType groupType,
|
||||
@NotNull List<String> tokens) {
|
||||
this.priority = priority;
|
||||
this.needMultiplicationSignBefore = needMultiplicationSignBefore;
|
||||
this.needMultiplicationSignAfter = needMultiplicationSignAfter;
|
||||
this.groupType = groupType;
|
||||
this.tokens = Collections.unmodifiableList(tokens);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public MathGroupType getGroupType() {
|
||||
return groupType;
|
||||
}
|
||||
|
||||
/* public static int getPostfixFunctionStart(@NotNull CharSequence s, int position) throws ParseException {
|
||||
assert s.length() > position;
|
||||
|
||||
int numberOfOpenGroups = 0;
|
||||
int result = position;
|
||||
for (; result >= 0; result--) {
|
||||
|
||||
final MathType mathType = getType(s.toString(), result).getMathType();
|
||||
|
||||
if (CollectionsUtils.contains(mathType, digit, dot, grouping_separator, power_10)) {
|
||||
// continue
|
||||
} else if (mathType == close_group_symbol) {
|
||||
numberOfOpenGroups++;
|
||||
} else if (mathType == open_group_symbol) {
|
||||
if (numberOfOpenGroups > 0) {
|
||||
numberOfOpenGroups--;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (stop(s, numberOfOpenGroups, result)) break;
|
||||
}
|
||||
}
|
||||
|
||||
if (numberOfOpenGroups != 0){
|
||||
throw new ParseException("Could not find start of prefix function!");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static boolean stop(CharSequence s, int numberOfOpenGroups, int i) {
|
||||
if (numberOfOpenGroups == 0) {
|
||||
if (i > 0) {
|
||||
final EndsWithFinder endsWithFinder = new EndsWithFinder(s);
|
||||
endsWithFinder.setI(i + 1);
|
||||
if (!CollectionsUtils.contains(function.getTokens(), FilterType.included, endsWithFinder)) {
|
||||
MathType type = getType(s.toString(), i).getMathType();
|
||||
if (type != constant) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}*/
|
||||
|
||||
@NotNull
|
||||
public List<String> getTokens() {
|
||||
return tokens;
|
||||
}
|
||||
|
||||
private boolean isNeedMultiplicationSignBefore() {
|
||||
return needMultiplicationSignBefore;
|
||||
}
|
||||
|
||||
private boolean isNeedMultiplicationSignAfter() {
|
||||
return needMultiplicationSignAfter;
|
||||
}
|
||||
|
||||
public boolean isNeedMultiplicationSignBefore(@NotNull MathType mathTypeBefore) {
|
||||
return needMultiplicationSignBefore && mathTypeBefore.isNeedMultiplicationSignAfter();
|
||||
}
|
||||
|
||||
public int processToJscl(@NotNull StringBuilder result, int i, @NotNull String match) throws CalculatorParseException {
|
||||
final String substitute = getSubstituteToJscl(match);
|
||||
result.append(substitute == null ? match : substitute);
|
||||
return returnI(i, match);
|
||||
}
|
||||
|
||||
protected int returnI(int i, @NotNull String match) {
|
||||
if (match.length() > 1) {
|
||||
return i + match.length() - 1;
|
||||
} else {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
public int processFromJscl(@NotNull StringBuilder result, int i, @NotNull String match) {
|
||||
final String substitute = getSubstituteFromJscl(match);
|
||||
result.append(substitute == null ? match : substitute);
|
||||
return returnI(i, match);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected String getSubstituteFromJscl(@NotNull String match) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected String getSubstituteToJscl(@NotNull String match) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static final List<String> openGroupSymbols = Arrays.asList("[]", "()", "{}");
|
||||
|
||||
public final static Character POWER_10 = 'E';
|
||||
|
||||
public static final String IMAGINARY_NUMBER = "i";
|
||||
public static final String IMAGINARY_NUMBER_JSCL = "√(-1)";
|
||||
|
||||
public static final String PI = "π";
|
||||
public static final String E = "e";
|
||||
public static final String C = "c";
|
||||
public static final Double C_VALUE = 299792458d;
|
||||
public static final String G = "G";
|
||||
public static final Double G_VALUE = 6.6738480E-11;
|
||||
public static final String H_REDUCED = "h";
|
||||
public static final Double H_REDUCED_VALUE = 6.6260695729E-34 / ( 2 * Math.PI );
|
||||
public final static String NAN = "NaN";
|
||||
|
||||
public final static String INFINITY = "∞";
|
||||
public final static String INFINITY_JSCL = "Infinity";
|
||||
|
||||
|
||||
/**
|
||||
* Method determines mathematical entity type for text substring starting from ith index
|
||||
*
|
||||
*
|
||||
* @param text analyzed text
|
||||
* @param i index which points to start of substring
|
||||
* @param hexMode
|
||||
* @return math entity type of substring starting from ith index of specified text
|
||||
*/
|
||||
@NotNull
|
||||
public static Result getType(@NotNull String text, int i, boolean hexMode) {
|
||||
if (i < 0) {
|
||||
throw new IllegalArgumentException("I must be more or equals to 0.");
|
||||
} else if (i >= text.length() && i != 0) {
|
||||
throw new IllegalArgumentException("I must be less than size of text.");
|
||||
} else if (i == 0 && text.length() == 0) {
|
||||
return new Result(MathType.text, text);
|
||||
}
|
||||
|
||||
final StartsWithFinder startsWithFinder = new StartsWithFinder(text, i);
|
||||
|
||||
for (MathType mathType : getMathTypesByPriority()) {
|
||||
final String s = CollectionsUtils.find(mathType.getTokens(), startsWithFinder);
|
||||
if (s != null) {
|
||||
if ( s.length() == 1 ) {
|
||||
if (hexMode || JsclMathEngine.instance.getNumeralBase() == NumeralBase.hex) {
|
||||
final Character ch = s.charAt(0);
|
||||
if ( NumeralBase.hex.getAcceptableCharacters().contains(ch) ) {
|
||||
return new Result(MathType.digit, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new Result(mathType, s);
|
||||
}
|
||||
}
|
||||
|
||||
return new Result(MathType.text, text.substring(i));
|
||||
}
|
||||
|
||||
|
||||
private static List<MathType> mathTypesByPriority;
|
||||
|
||||
@NotNull
|
||||
private static List<MathType> getMathTypesByPriority() {
|
||||
if (mathTypesByPriority == null) {
|
||||
final List<MathType> result = CollectionsUtils.asList(MathType.values());
|
||||
|
||||
Collections.sort(result, new Comparator<MathType>() {
|
||||
@Override
|
||||
public int compare(MathType l, MathType r) {
|
||||
return l.priority.compareTo(r.priority);
|
||||
}
|
||||
});
|
||||
|
||||
mathTypesByPriority = result;
|
||||
}
|
||||
|
||||
return mathTypesByPriority;
|
||||
}
|
||||
|
||||
public static class Result {
|
||||
|
||||
@NotNull
|
||||
private final MathType mathType;
|
||||
|
||||
@NotNull
|
||||
private final String match;
|
||||
|
||||
public Result(@NotNull MathType mathType, @NotNull String match) {
|
||||
this.mathType = mathType;
|
||||
|
||||
this.match = match;
|
||||
}
|
||||
|
||||
public int processToJscl(@NotNull StringBuilder result, int i) throws CalculatorParseException {
|
||||
return mathType.processToJscl(result, i, match);
|
||||
}
|
||||
|
||||
public int processFromJscl(@NotNull StringBuilder result, int i) {
|
||||
return mathType.processFromJscl(result, i, match);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getMatch() {
|
||||
return match;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public MathType getMathType() {
|
||||
return mathType;
|
||||
}
|
||||
}
|
||||
|
||||
private static class EndsWithFinder implements Finder<String> {
|
||||
|
||||
private int i;
|
||||
|
||||
@NotNull
|
||||
private final CharSequence targetString;
|
||||
|
||||
private EndsWithFinder(@NotNull CharSequence targetString) {
|
||||
this.targetString = targetString;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFound(@Nullable String s) {
|
||||
return targetString.subSequence(0, i).toString().endsWith(s);
|
||||
}
|
||||
|
||||
public void setI(int i) {
|
||||
this.i = i;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,276 @@
|
||||
/*
|
||||
* 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.calculator.math.edit;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ListActivity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.*;
|
||||
import com.google.ads.AdView;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.ads.AdsController;
|
||||
import org.solovyev.android.calculator.CalculatorModel;
|
||||
import org.solovyev.android.calculator.R;
|
||||
import org.solovyev.android.calculator.model.AndroidMathRegistry;
|
||||
import org.solovyev.android.menu.AMenuBuilder;
|
||||
import org.solovyev.android.menu.AMenuItem;
|
||||
import org.solovyev.android.menu.MenuImpl;
|
||||
import org.solovyev.common.math.MathEntity;
|
||||
import org.solovyev.common.utils.EqualsTool;
|
||||
import org.solovyev.common.utils.Filter;
|
||||
import org.solovyev.common.utils.FilterRule;
|
||||
import org.solovyev.common.utils.StringUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/21/11
|
||||
* Time: 9:24 PM
|
||||
*/
|
||||
public abstract class AbstractMathEntityListActivity<T extends MathEntity> extends ListActivity {
|
||||
|
||||
public static final String MATH_ENTITY_CATEGORY_EXTRA_STRING = "org.solovyev.android.calculator.CalculatorVarsActivity_math_entity_category";
|
||||
|
||||
protected final static List<Character> acceptableChars = Arrays.asList(StringUtils.toObject("1234567890abcdefghijklmnopqrstuvwxyzйцукенгшщзхъфывапролджэячсмитьбюё_".toCharArray()));
|
||||
|
||||
@Nullable
|
||||
private MathEntityArrayAdapter<T> adapter;
|
||||
|
||||
@Nullable
|
||||
private String category;
|
||||
|
||||
@Nullable
|
||||
private AdView adView;
|
||||
|
||||
static void createTab(@NotNull Context context,
|
||||
@NotNull TabHost tabHost,
|
||||
@NotNull String tabId,
|
||||
@NotNull String categoryId,
|
||||
int tabCaptionId,
|
||||
@NotNull Class<? extends Activity> activityClass,
|
||||
@Nullable Intent parentIntent) {
|
||||
|
||||
TabHost.TabSpec spec;
|
||||
|
||||
final Intent intent;
|
||||
if (parentIntent != null) {
|
||||
intent = new Intent(parentIntent);
|
||||
} else {
|
||||
intent = new Intent();
|
||||
}
|
||||
intent.setClass(context, activityClass);
|
||||
intent.putExtra(MATH_ENTITY_CATEGORY_EXTRA_STRING, categoryId);
|
||||
|
||||
// Initialize a TabSpec for each tab and add it to the TabHost
|
||||
spec = tabHost.newTabSpec(tabId).setIndicator(context.getString(tabCaptionId)).setContent(intent);
|
||||
|
||||
tabHost.addTab(spec);
|
||||
}
|
||||
|
||||
protected int getLayoutId() {
|
||||
return R.layout.math_entities;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(getLayoutId());
|
||||
|
||||
adView = AdsController.getInstance().inflateAd(this);
|
||||
|
||||
final Intent intent = getIntent();
|
||||
if ( intent != null ) {
|
||||
category = intent.getStringExtra(MATH_ENTITY_CATEGORY_EXTRA_STRING);
|
||||
}
|
||||
|
||||
final ListView lv = getListView();
|
||||
lv.setTextFilterEnabled(true);
|
||||
|
||||
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
public void onItemClick(final AdapterView<?> parent,
|
||||
final View view,
|
||||
final int position,
|
||||
final long id) {
|
||||
|
||||
CalculatorModel.instance.processDigitButtonAction(((MathEntity) parent.getItemAtPosition(position)).getName(), false);
|
||||
|
||||
AbstractMathEntityListActivity.this.finish();
|
||||
}
|
||||
});
|
||||
|
||||
getListView().setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
|
||||
@Override
|
||||
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
final T item = (T) parent.getItemAtPosition(position);
|
||||
|
||||
final List<AMenuItem<T>> menuItems = getMenuItemsOnLongClick(item);
|
||||
|
||||
if (!menuItems.isEmpty()) {
|
||||
final AMenuBuilder<AMenuItem<T>, T> menuBuilder = AMenuBuilder.newInstance(AbstractMathEntityListActivity.this, MenuImpl.newInstance(menuItems));
|
||||
menuBuilder.create(item).show();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
if (this.adView != null) {
|
||||
this.adView.destroy();
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected abstract List<AMenuItem<T>> getMenuItemsOnLongClick(@NotNull T item);
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
adapter = new MathEntityArrayAdapter<T>(getDescriptionGetter(), this, R.layout.math_entity, R.id.math_entity_text, getMathEntitiesByCategory());
|
||||
setListAdapter(adapter);
|
||||
|
||||
sort();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<T> getMathEntitiesByCategory() {
|
||||
final List<T> result = getMathEntities();
|
||||
|
||||
new Filter<T>(new FilterRule<T>() {
|
||||
@Override
|
||||
public boolean isFiltered(T t) {
|
||||
return !isInCategory(t);
|
||||
}
|
||||
}).filter(result.iterator());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected boolean isInCategory(@Nullable T t) {
|
||||
return t != null && EqualsTool.areEqual(getMathEntityCategory(t), category);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected abstract MathEntityDescriptionGetter getDescriptionGetter();
|
||||
|
||||
@NotNull
|
||||
protected abstract List<T> getMathEntities();
|
||||
|
||||
@Nullable
|
||||
abstract String getMathEntityCategory(@NotNull T t);
|
||||
|
||||
protected void sort() {
|
||||
final MathEntityArrayAdapter<T> localAdapter = adapter;
|
||||
if (localAdapter != null) {
|
||||
localAdapter.sort(new Comparator<T>() {
|
||||
@Override
|
||||
public int compare(T function1, T function2) {
|
||||
return function1.getName().compareTo(function2.getName());
|
||||
}
|
||||
});
|
||||
|
||||
localAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
protected static class MathEntityArrayAdapter<T extends MathEntity> extends ArrayAdapter<T> {
|
||||
|
||||
@NotNull
|
||||
private final MathEntityDescriptionGetter descriptionGetter;
|
||||
|
||||
private MathEntityArrayAdapter(@NotNull MathEntityDescriptionGetter descriptionGetter,
|
||||
@NotNull Context context,
|
||||
int resource,
|
||||
int textViewResourceId,
|
||||
@NotNull List<T> objects) {
|
||||
|
||||
super(context, resource, textViewResourceId, objects);
|
||||
this.descriptionGetter = descriptionGetter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
final ViewGroup result = (ViewGroup) super.getView(position, convertView, parent);
|
||||
|
||||
final T mathEntity = getItem(position);
|
||||
|
||||
final String mathEntityDescription = descriptionGetter.getDescription(getContext(), mathEntity.getName());
|
||||
if (!StringUtils.isEmpty(mathEntityDescription)) {
|
||||
TextView description = (TextView) result.findViewById(R.id.math_entity_description);
|
||||
if (description == null) {
|
||||
final LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService(LAYOUT_INFLATER_SERVICE);
|
||||
final ViewGroup itemView = (ViewGroup) layoutInflater.inflate(R.layout.math_entity, null);
|
||||
description = (TextView) itemView.findViewById(R.id.math_entity_description);
|
||||
itemView.removeView(description);
|
||||
result.addView(description);
|
||||
}
|
||||
description.setText(mathEntityDescription);
|
||||
} else {
|
||||
TextView description = (TextView) result.findViewById(R.id.math_entity_description);
|
||||
if (description != null) {
|
||||
result.removeView(description);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
protected static class MathEntityDescriptionGetterImpl implements MathEntityDescriptionGetter {
|
||||
|
||||
@NotNull
|
||||
private final AndroidMathRegistry<?> mathRegistry;
|
||||
|
||||
public MathEntityDescriptionGetterImpl(@NotNull AndroidMathRegistry<?> mathRegistry) {
|
||||
this.mathRegistry = mathRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription(@NotNull Context context, @NotNull String mathEntityName) {
|
||||
return this.mathRegistry.getDescription(context, mathEntityName);
|
||||
}
|
||||
}
|
||||
|
||||
protected static interface MathEntityDescriptionGetter {
|
||||
|
||||
@Nullable
|
||||
String getDescription(@NotNull Context context, @NotNull String mathEntityName);
|
||||
}
|
||||
|
||||
public void addToAdapter(@NotNull T mathEntity) {
|
||||
if (this.adapter != null) {
|
||||
this.adapter.add(mathEntity);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeFromAdapter(@NotNull T mathEntity) {
|
||||
if (this.adapter != null) {
|
||||
this.adapter.remove(mathEntity);
|
||||
}
|
||||
}
|
||||
|
||||
public void notifyAdapter() {
|
||||
if (this.adapter != null) {
|
||||
this.adapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
* 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.calculator.math.edit;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.text.ClipboardManager;
|
||||
import jscl.math.function.Function;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.solovyev.android.calculator.CalculatorModel;
|
||||
import org.solovyev.android.calculator.R;
|
||||
import org.solovyev.android.calculator.model.CalculatorEngine;
|
||||
import org.solovyev.android.menu.AMenuItem;
|
||||
import org.solovyev.common.utils.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 10/29/11
|
||||
* Time: 4:55 PM
|
||||
*/
|
||||
public class CalculatorFunctionsActivity extends AbstractMathEntityListActivity<Function> {
|
||||
|
||||
private static enum LongClickMenuItem implements AMenuItem<Function>{
|
||||
use(R.string.c_use) {
|
||||
@Override
|
||||
public void doAction(@NotNull Function data, @NotNull Context context) {
|
||||
CalculatorModel.instance.processDigitButtonAction(data.getName(), false);
|
||||
if (context instanceof Activity) {
|
||||
((Activity) context).finish();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/*edit(R.string.c_edit) {
|
||||
@Override
|
||||
public void doAction(@NotNull Function data, @NotNull Context context) {
|
||||
if (context instanceof AbstractMathEntityListActivity) {
|
||||
}
|
||||
}
|
||||
},*/
|
||||
|
||||
copy_description(R.string.c_copy_description) {
|
||||
@Override
|
||||
public void doAction(@NotNull Function data, @NotNull Context context) {
|
||||
final String text = CalculatorEngine.instance.getFunctionsRegistry().getDescription(context, data.getName());
|
||||
if (!StringUtils.isEmpty(text)) {
|
||||
final ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Activity.CLIPBOARD_SERVICE);
|
||||
clipboard.setText(text);
|
||||
}
|
||||
}
|
||||
};
|
||||
private final int captionId;
|
||||
|
||||
LongClickMenuItem(int captionId) {
|
||||
this.captionId = captionId;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getCaption(@NotNull Context context) {
|
||||
return context.getString(captionId);
|
||||
}
|
||||
}
|
||||
|
||||
public static final String CREATE_FUN_EXTRA_STRING = "org.solovyev.android.calculator.math.edit.CalculatorFunctionsTabActivity_create_fun";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
/*getListView().setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
|
||||
@Override
|
||||
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
final Function function = (Function) parent.getItemAtPosition(position);
|
||||
if (function instanceof CustomFunction) {
|
||||
createEditVariableDialog(CalculatorFunctionsTabActivity.this,
|
||||
((CustomFunction) function),
|
||||
function.getName(),
|
||||
((CustomFunction) function).getContent(),
|
||||
((CustomFunction) function).getParameterNames(),
|
||||
null);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});*/
|
||||
|
||||
/*final Intent intent = getIntent();
|
||||
if (intent != null) {
|
||||
final String varValue = intent.getStringExtra(CREATE_FUN_EXTRA_STRING);
|
||||
if (!StringUtils.isEmpty(varValue)) {
|
||||
createEditVariableDialog(this, null, null, varValue, null, null);
|
||||
|
||||
// in order to stop intent for other tabs
|
||||
intent.removeExtra(CREATE_FUN_EXTRA_STRING);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected List<AMenuItem<Function>> getMenuItemsOnLongClick(@NotNull Function item) {
|
||||
List<AMenuItem<Function>> result = new ArrayList<AMenuItem<Function>>(Arrays.asList(LongClickMenuItem.values()));
|
||||
|
||||
if ( StringUtils.isEmpty(CalculatorEngine.instance.getFunctionsRegistry().getDescription(this, item.getName())) ) {
|
||||
result.remove(LongClickMenuItem.copy_description);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* private static void createEditVariableDialog(@NotNull final AbstractMathEntityListActivity<Function> activity,
|
||||
@Nullable final CustomFunction function,
|
||||
@Nullable final String name,
|
||||
@Nullable final String expression,
|
||||
@Nullable final String[] parameterNames,
|
||||
@Nullable final String description) {
|
||||
if (function == null || !function.isSystem()) {
|
||||
|
||||
final LayoutInflater layoutInflater = (LayoutInflater) activity.getSystemService(LAYOUT_INFLATER_SERVICE);
|
||||
final View editView = layoutInflater.inflate(R.layout.var_edit, null);
|
||||
|
||||
final String errorMsg = activity.getString(R.string.c_char_is_not_accepted);
|
||||
|
||||
final EditText editName = (EditText) editView.findViewById(R.id.var_edit_name);
|
||||
editName.setText(name);
|
||||
editName.addTextChangedListener(new TextWatcher() {
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
if (!acceptableChars.contains(c)) {
|
||||
s.delete(i, i + 1);
|
||||
Toast.makeText(activity, String.format(errorMsg, c), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
final EditText editValue = (EditText) editView.findViewById(R.id.var_edit_value);
|
||||
if (!StringUtils.isEmpty(expression)) {
|
||||
editValue.setText(expression);
|
||||
}
|
||||
|
||||
final EditText editDescription = (EditText) editView.findViewById(R.id.var_edit_description);
|
||||
editDescription.setText(description);
|
||||
|
||||
final CustomFunction.Builder functionBuilder;
|
||||
if (function != null) {
|
||||
functionBuilder = new CustomFunction.Builder(function);
|
||||
} else {
|
||||
functionBuilder = new CustomFunction.Builder();
|
||||
}
|
||||
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(activity)
|
||||
.setCancelable(true)
|
||||
.setNegativeButton(R.string.c_cancel, null)
|
||||
.setPositiveButton(R.string.c_save, new FunctionEditorSaver(functionBuilder, function, editView, activity, CalculatorEngine.instance.getFunctionsRegistry(), new FunctionEditorSaver.EditorCreator<Function>() {
|
||||
|
||||
@Override
|
||||
public void showEditor(@NotNull AbstractMathEntityListActivity<Function> activity, @Nullable CustomFunction editedInstance, @Nullable String name, @Nullable String value, @Nullable String[] parameterNames, @Nullable String description) {
|
||||
createEditVariableDialog(activity, editedInstance, name, value, parameterNames, description);
|
||||
}
|
||||
}))
|
||||
.setView(editView);
|
||||
|
||||
if (function != null) {
|
||||
// EDIT mode
|
||||
|
||||
builder.setTitle(R.string.c_var_edit_var);
|
||||
builder.setNeutralButton(R.string.c_remove, new MathEntityRemover<Function>(function, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
createEditVariableDialog(activity, function, name, expression, parameterNames, description);
|
||||
}
|
||||
}, CalculatorEngine.instance.getFunctionsRegistry(), activity));
|
||||
} else {
|
||||
// CREATE mode
|
||||
|
||||
builder.setTitle(R.string.c_var_create_var);
|
||||
}
|
||||
|
||||
builder.create().show();
|
||||
} else {
|
||||
Toast.makeText(activity, activity.getString(R.string.c_sys_var_cannot_be_changed), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}*/
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected MathEntityDescriptionGetter getDescriptionGetter() {
|
||||
return new MathEntityDescriptionGetterImpl(CalculatorEngine.instance.getFunctionsRegistry());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected List<Function> getMathEntities() {
|
||||
return new ArrayList<Function>(CalculatorEngine.instance.getFunctionsRegistry().getEntities());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMathEntityCategory(@NotNull Function function) {
|
||||
return CalculatorEngine.instance.getFunctionsRegistry().getCategory(function);
|
||||
}
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.calculator.math.edit;
|
||||
|
||||
import android.app.TabActivity;
|
||||
import android.os.Bundle;
|
||||
import android.widget.TabHost;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.AndroidUtils;
|
||||
import org.solovyev.android.LastTabSaver;
|
||||
import org.solovyev.android.calculator.R;
|
||||
import org.solovyev.android.calculator.model.AndroidFunctionsMathRegistry;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/21/11
|
||||
* Time: 10:33 PM
|
||||
*/
|
||||
public class CalculatorFunctionsTabActivity extends TabActivity {
|
||||
|
||||
@Nullable
|
||||
private LastTabSaver lastTabSaver;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.tabs);
|
||||
|
||||
final TabHost tabHost = getTabHost();
|
||||
|
||||
for (AndroidFunctionsMathRegistry.Category category : AndroidFunctionsMathRegistry.Category.getCategoriesByTabOrder()) {
|
||||
AbstractMathEntityListActivity.createTab(this, tabHost, category.name(), category.name(), category.getCaptionId(), CalculatorFunctionsActivity.class, null);
|
||||
}
|
||||
|
||||
this.lastTabSaver = new LastTabSaver(this, AndroidFunctionsMathRegistry.Category.common.name());
|
||||
|
||||
AndroidUtils.centerAndWrapTabsFor(tabHost);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
if (lastTabSaver != null) {
|
||||
this.lastTabSaver.destroy();
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
@@ -0,0 +1,117 @@
|
||||
package org.solovyev.android.calculator.math.edit;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.text.ClipboardManager;
|
||||
import jscl.math.operator.Operator;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.solovyev.android.calculator.CalculatorModel;
|
||||
import org.solovyev.android.calculator.R;
|
||||
import org.solovyev.android.calculator.model.CalculatorEngine;
|
||||
import org.solovyev.android.menu.AMenuItem;
|
||||
import org.solovyev.common.utils.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 11/17/11
|
||||
* Time: 1:53 PM
|
||||
*/
|
||||
|
||||
public class CalculatorOperatorsActivity extends AbstractMathEntityListActivity<Operator> {
|
||||
|
||||
private static enum LongClickMenuItem implements AMenuItem<Operator> {
|
||||
|
||||
use(R.string.c_use) {
|
||||
@Override
|
||||
public void doAction(@NotNull Operator data, @NotNull Context context) {
|
||||
CalculatorModel.instance.processDigitButtonAction(data.getName(), false);
|
||||
if (context instanceof Activity) {
|
||||
((Activity) context).finish();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
copy_description(R.string.c_copy_description) {
|
||||
@Override
|
||||
public void doAction(@NotNull Operator data, @NotNull Context context) {
|
||||
final String text = OperatorDescriptionGetter.instance.getDescription(context, data.getName());
|
||||
if (!StringUtils.isEmpty(text)) {
|
||||
final ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Activity.CLIPBOARD_SERVICE);
|
||||
clipboard.setText(text);
|
||||
}
|
||||
}
|
||||
};
|
||||
private final int captionId;
|
||||
|
||||
LongClickMenuItem(int captionId) {
|
||||
this.captionId = captionId;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getCaption(@NotNull Context context) {
|
||||
return context.getString(captionId);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected List<AMenuItem<Operator>> getMenuItemsOnLongClick(@NotNull Operator item) {
|
||||
final List<AMenuItem<Operator>> result = new ArrayList<AMenuItem<Operator>>(Arrays.asList(LongClickMenuItem.values()));
|
||||
|
||||
if ( StringUtils.isEmpty(OperatorDescriptionGetter.instance.getDescription(this, item.getName())) ) {
|
||||
result.remove(LongClickMenuItem.copy_description);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected MathEntityDescriptionGetter getDescriptionGetter() {
|
||||
return OperatorDescriptionGetter.instance;
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected List<Operator> getMathEntities() {
|
||||
final List<Operator> result = new ArrayList<Operator>();
|
||||
|
||||
result.addAll(CalculatorEngine.instance.getOperatorsRegistry().getEntities());
|
||||
result.addAll(CalculatorEngine.instance.getPostfixFunctionsRegistry().getEntities());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMathEntityCategory(@NotNull Operator operator) {
|
||||
String result = CalculatorEngine.instance.getOperatorsRegistry().getCategory(operator);
|
||||
if (result == null) {
|
||||
result = CalculatorEngine.instance.getPostfixFunctionsRegistry().getCategory(operator);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static enum OperatorDescriptionGetter implements MathEntityDescriptionGetter {
|
||||
|
||||
instance;
|
||||
|
||||
@Override
|
||||
public String getDescription(@NotNull Context context, @NotNull String mathEntityName) {
|
||||
String result = CalculatorEngine.instance.getOperatorsRegistry().getDescription(context, mathEntityName);
|
||||
if (StringUtils.isEmpty(result)) {
|
||||
result = CalculatorEngine.instance.getPostfixFunctionsRegistry().getDescription(context, mathEntityName);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,295 @@
|
||||
/*
|
||||
* 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.calculator.math.edit;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.ClipboardManager;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.*;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
import jscl.math.function.IConstant;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.calculator.CalculatorModel;
|
||||
import org.solovyev.android.calculator.R;
|
||||
import org.solovyev.android.calculator.math.MathType;
|
||||
import org.solovyev.android.calculator.model.CalculatorEngine;
|
||||
import org.solovyev.android.calculator.model.Var;
|
||||
import org.solovyev.android.menu.AMenuItem;
|
||||
import org.solovyev.common.utils.CollectionsUtils;
|
||||
import org.solovyev.common.utils.Finder;
|
||||
import org.solovyev.common.utils.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 9/28/11
|
||||
* Time: 10:55 PM
|
||||
*/
|
||||
public class CalculatorVarsActivity extends AbstractMathEntityListActivity<IConstant> {
|
||||
|
||||
private static enum LongClickMenuItem implements AMenuItem<IConstant>{
|
||||
use(R.string.c_use) {
|
||||
@Override
|
||||
public void doAction(@NotNull IConstant data, @NotNull Context context) {
|
||||
CalculatorModel.instance.processDigitButtonAction(data.getName(), false);
|
||||
if (context instanceof Activity) {
|
||||
((Activity) context).finish();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
edit(R.string.c_edit) {
|
||||
@Override
|
||||
public void doAction(@NotNull IConstant data, @NotNull Context context) {
|
||||
if (context instanceof AbstractMathEntityListActivity) {
|
||||
createEditVariableDialog((AbstractMathEntityListActivity<IConstant>)context, data, data.getName(), StringUtils.getNotEmpty(data.getValue(), ""), data.getDescription());
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
remove(R.string.c_remove) {
|
||||
@Override
|
||||
public void doAction(@NotNull IConstant data, @NotNull Context context) {
|
||||
if (context instanceof AbstractMathEntityListActivity) {
|
||||
new MathEntityRemover<IConstant>(data, null, CalculatorEngine.instance.getVarsRegistry(), ((AbstractMathEntityListActivity<IConstant>) context)).showConfirmationDialog();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
copy_value(R.string.c_copy_value) {
|
||||
@Override
|
||||
public void doAction(@NotNull IConstant data, @NotNull Context context) {
|
||||
final String text = data.getValue();
|
||||
if (!StringUtils.isEmpty(text)) {
|
||||
final ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Activity.CLIPBOARD_SERVICE);
|
||||
clipboard.setText(text);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
copy_description(R.string.c_copy_description) {
|
||||
@Override
|
||||
public void doAction(@NotNull IConstant data, @NotNull Context context) {
|
||||
final String text = CalculatorEngine.instance.getVarsRegistry().getDescription(context, data.getName());
|
||||
if (!StringUtils.isEmpty(text)) {
|
||||
final ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Activity.CLIPBOARD_SERVICE);
|
||||
clipboard.setText(text);
|
||||
}
|
||||
}
|
||||
};
|
||||
private final int captionId;
|
||||
|
||||
LongClickMenuItem(int captionId) {
|
||||
this.captionId = captionId;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getCaption(@NotNull Context context) {
|
||||
return context.getString(captionId);
|
||||
}
|
||||
}
|
||||
|
||||
public static final String CREATE_VAR_EXTRA_STRING = "org.solovyev.android.calculator.math.edit.CalculatorVarsTabActivity_create_var";
|
||||
|
||||
@Override
|
||||
protected int getLayoutId() {
|
||||
return R.layout.vars;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
final Intent intent = getIntent();
|
||||
if (intent != null) {
|
||||
final String varValue = intent.getStringExtra(CREATE_VAR_EXTRA_STRING);
|
||||
if (!StringUtils.isEmpty(varValue)) {
|
||||
createEditVariableDialog(this, null, null, varValue, null);
|
||||
|
||||
// in order to stop intent for other tabs
|
||||
intent.removeExtra(CREATE_VAR_EXTRA_STRING);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected List<AMenuItem<IConstant>> getMenuItemsOnLongClick(@NotNull IConstant item) {
|
||||
final List<AMenuItem<IConstant>> result = new ArrayList<AMenuItem<IConstant>>(Arrays.asList(LongClickMenuItem.values()));
|
||||
|
||||
if ( item.isSystem() ) {
|
||||
result.remove(LongClickMenuItem.edit);
|
||||
result.remove(LongClickMenuItem.remove);
|
||||
}
|
||||
|
||||
if ( StringUtils.isEmpty(CalculatorEngine.instance.getVarsRegistry().getDescription(this, item.getName())) ) {
|
||||
result.remove(LongClickMenuItem.copy_description);
|
||||
}
|
||||
|
||||
if ( StringUtils.isEmpty(item.getValue()) ) {
|
||||
result.remove(LongClickMenuItem.copy_value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected MathEntityDescriptionGetter getDescriptionGetter() {
|
||||
return new MathEntityDescriptionGetterImpl(CalculatorEngine.instance.getVarsRegistry());
|
||||
}
|
||||
|
||||
@SuppressWarnings({"UnusedDeclaration"})
|
||||
public void addVarButtonClickHandler(@NotNull View v) {
|
||||
createEditVariableDialog(this, null, null, null, null);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected List<IConstant> getMathEntities() {
|
||||
final List<IConstant> result = new ArrayList<IConstant>(CalculatorEngine.instance.getVarsRegistry().getEntities());
|
||||
|
||||
CollectionsUtils.removeAll(result, new Finder<IConstant>() {
|
||||
@Override
|
||||
public boolean isFound(@Nullable IConstant var) {
|
||||
return var != null && CollectionsUtils.contains(var.getName(), MathType.INFINITY_JSCL, MathType.NAN);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMathEntityCategory(@NotNull IConstant var) {
|
||||
return CalculatorEngine.instance.getVarsRegistry().getCategory(var);
|
||||
}
|
||||
|
||||
private static void createEditVariableDialog(@NotNull final AbstractMathEntityListActivity<IConstant> activity,
|
||||
@Nullable final IConstant var,
|
||||
@Nullable final String name,
|
||||
@Nullable final String value,
|
||||
@Nullable final String description) {
|
||||
if (var == null || !var.isSystem()) {
|
||||
|
||||
final LayoutInflater layoutInflater = (LayoutInflater) activity.getSystemService(LAYOUT_INFLATER_SERVICE);
|
||||
final View editView = layoutInflater.inflate(R.layout.var_edit, null);
|
||||
|
||||
final String errorMsg = activity.getString(R.string.c_char_is_not_accepted);
|
||||
|
||||
final EditText editName = (EditText) editView.findViewById(R.id.var_edit_name);
|
||||
editName.setText(name);
|
||||
editName.addTextChangedListener(new TextWatcher() {
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
if (!acceptableChars.contains(c)) {
|
||||
s.delete(i, i + 1);
|
||||
Toast.makeText(activity, String.format(errorMsg, c), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
final EditText editValue = (EditText) editView.findViewById(R.id.var_edit_value);
|
||||
if (!StringUtils.isEmpty(value)) {
|
||||
editValue.setText(value);
|
||||
}
|
||||
|
||||
final EditText editDescription = (EditText) editView.findViewById(R.id.var_edit_description);
|
||||
editDescription.setText(description);
|
||||
|
||||
final Var.Builder varBuilder;
|
||||
if (var != null) {
|
||||
varBuilder = new Var.Builder(var);
|
||||
} else {
|
||||
varBuilder = new Var.Builder();
|
||||
}
|
||||
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(activity)
|
||||
.setCancelable(true)
|
||||
.setNegativeButton(R.string.c_cancel, null)
|
||||
.setPositiveButton(R.string.c_save, new VarEditorSaver<IConstant>(varBuilder, var, editView, activity, CalculatorEngine.instance.getVarsRegistry(), new VarEditorSaver.EditorCreator<IConstant>() {
|
||||
@Override
|
||||
public void showEditor(@NotNull AbstractMathEntityListActivity<IConstant> activity, @Nullable IConstant editedInstance, @Nullable String name, @Nullable String value, @Nullable String description) {
|
||||
createEditVariableDialog(activity, editedInstance, name, value, description);
|
||||
}
|
||||
}))
|
||||
.setView(editView);
|
||||
|
||||
if (var != null) {
|
||||
// EDIT mode
|
||||
|
||||
builder.setTitle(R.string.c_var_edit_var);
|
||||
builder.setNeutralButton(R.string.c_remove, new MathEntityRemover<IConstant>(var, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
createEditVariableDialog(activity, var, name, value, description);
|
||||
}
|
||||
}, CalculatorEngine.instance.getVarsRegistry(), activity));
|
||||
} else {
|
||||
// CREATE mode
|
||||
|
||||
builder.setTitle(R.string.c_var_create_var);
|
||||
}
|
||||
|
||||
builder.create().show();
|
||||
} else {
|
||||
Toast.makeText(activity, activity.getString(R.string.c_sys_var_cannot_be_changed), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isValidValue(@NotNull String value) {
|
||||
// now every string might be constant
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
final MenuInflater menuInflater = getMenuInflater();
|
||||
menuInflater.inflate(R.menu.var_menu, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
boolean result;
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case R.id.var_menu_add_var:
|
||||
createEditVariableDialog(this, null, null, null, null);
|
||||
result = true;
|
||||
break;
|
||||
default:
|
||||
result = super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.calculator.math.edit;
|
||||
|
||||
import android.app.TabActivity;
|
||||
import android.os.Bundle;
|
||||
import android.widget.TabHost;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.AndroidUtils;
|
||||
import org.solovyev.android.LastTabSaver;
|
||||
import org.solovyev.android.calculator.R;
|
||||
import org.solovyev.android.calculator.model.VarCategory;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/21/11
|
||||
* Time: 11:05 PM
|
||||
*/
|
||||
public class CalculatorVarsTabActivity extends TabActivity {
|
||||
|
||||
@Nullable
|
||||
private LastTabSaver lastTabSaver;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.tabs);
|
||||
|
||||
final TabHost tabHost = getTabHost();
|
||||
|
||||
for (VarCategory category : VarCategory.getCategoriesByTabOrder()) {
|
||||
if (category == VarCategory.my) {
|
||||
AbstractMathEntityListActivity.createTab(this, tabHost, category.name(), category.name(), category.getCaptionId(), CalculatorVarsActivity.class, getIntent());
|
||||
} else {
|
||||
AbstractMathEntityListActivity.createTab(this, tabHost, category.name(), category.name(), category.getCaptionId(), CalculatorVarsActivity.class, null);
|
||||
}
|
||||
}
|
||||
|
||||
this.lastTabSaver = new LastTabSaver(this, VarCategory.my.name());
|
||||
|
||||
AndroidUtils.centerAndWrapTabsFor(tabHost);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
if (lastTabSaver != null) {
|
||||
lastTabSaver.destroy();
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* 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.calculator.math.edit;
|
||||
|
||||
import android.content.DialogInterface;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
import jscl.math.function.CustomFunction;
|
||||
import jscl.math.function.Function;
|
||||
import jscl.text.Identifier;
|
||||
import jscl.text.MutableInt;
|
||||
import jscl.text.ParseException;
|
||||
import jscl.text.Parser;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.calculator.R;
|
||||
import org.solovyev.android.calculator.math.MathType;
|
||||
import org.solovyev.android.calculator.model.AndroidMathRegistry;
|
||||
import org.solovyev.android.calculator.model.CalculatorEngine;
|
||||
import org.solovyev.android.calculator.model.MathEntityBuilder;
|
||||
import org.solovyev.common.math.MathEntity;
|
||||
import org.solovyev.common.utils.StringUtils;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/22/11
|
||||
* Time: 11:26 PM
|
||||
*/
|
||||
public class FunctionEditorSaver implements DialogInterface.OnClickListener{
|
||||
|
||||
public static interface EditorCreator<T extends MathEntity> {
|
||||
void showEditor(@NotNull AbstractMathEntityListActivity<T> activity,
|
||||
@Nullable CustomFunction editedInstance,
|
||||
@Nullable String name,
|
||||
@Nullable String value,
|
||||
@Nullable String[] parameterNames,
|
||||
@Nullable String description);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private final EditorCreator<Function> editorCreator;
|
||||
|
||||
@NotNull
|
||||
private final MathEntityBuilder<CustomFunction> varBuilder;
|
||||
|
||||
@Nullable
|
||||
private final CustomFunction editedInstance;
|
||||
|
||||
@NotNull
|
||||
private final AndroidMathRegistry<Function> mathRegistry;
|
||||
|
||||
@NotNull
|
||||
private final AbstractMathEntityListActivity<Function> activity;
|
||||
|
||||
@NotNull
|
||||
private View editView;
|
||||
|
||||
public FunctionEditorSaver(@NotNull MathEntityBuilder<CustomFunction> varBuilder,
|
||||
@Nullable CustomFunction editedInstance,
|
||||
@NotNull View editView,
|
||||
@NotNull AbstractMathEntityListActivity<Function> activity,
|
||||
@NotNull AndroidMathRegistry<Function> mathRegistry,
|
||||
@NotNull EditorCreator<Function> editorCreator) {
|
||||
this.varBuilder = varBuilder;
|
||||
this.editedInstance = editedInstance;
|
||||
this.editView = editView;
|
||||
this.activity = activity;
|
||||
this.mathRegistry = mathRegistry;
|
||||
this.editorCreator = editorCreator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (which == DialogInterface.BUTTON_POSITIVE) {
|
||||
final Integer error;
|
||||
|
||||
final EditText editName = (EditText) editView.findViewById(R.id.var_edit_name);
|
||||
String name = editName.getText().toString();
|
||||
|
||||
final EditText editValue = (EditText) editView.findViewById(R.id.var_edit_value);
|
||||
String value = editValue.getText().toString();
|
||||
|
||||
final EditText editDescription = (EditText) editView.findViewById(R.id.var_edit_description);
|
||||
String description = editDescription.getText().toString();
|
||||
|
||||
if (isValidName(name)) {
|
||||
|
||||
boolean canBeSaved = false;
|
||||
|
||||
final Function entityFromRegistry = mathRegistry.get(name);
|
||||
if (entityFromRegistry == null) {
|
||||
canBeSaved = true;
|
||||
} else if (editedInstance != null && entityFromRegistry.getId().equals(editedInstance.getId())) {
|
||||
canBeSaved = true;
|
||||
}
|
||||
|
||||
if (canBeSaved) {
|
||||
final MathType.Result mathType = MathType.getType(name, 0, false);
|
||||
|
||||
if (mathType.getMathType() == MathType.text || mathType.getMathType() == MathType.constant) {
|
||||
|
||||
if (StringUtils.isEmpty(value)) {
|
||||
// value is empty => undefined variable
|
||||
varBuilder.setName(name);
|
||||
varBuilder.setDescription(description);
|
||||
varBuilder.setValue(null);
|
||||
error = null;
|
||||
} else {
|
||||
// value is not empty => must be a number
|
||||
boolean valid = CalculatorVarsActivity.isValidValue(value);
|
||||
|
||||
if (valid) {
|
||||
varBuilder.setName(name);
|
||||
varBuilder.setDescription(description);
|
||||
varBuilder.setValue(value);
|
||||
error = null;
|
||||
} else {
|
||||
error = R.string.c_value_is_not_a_number;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error = R.string.c_var_name_clashes;
|
||||
}
|
||||
} else {
|
||||
error = R.string.c_var_already_exists;
|
||||
}
|
||||
} else {
|
||||
error = R.string.c_name_is_not_valid;
|
||||
}
|
||||
|
||||
if (error != null) {
|
||||
Toast.makeText(activity, activity.getString(error), Toast.LENGTH_LONG).show();
|
||||
editorCreator.showEditor(activity, editedInstance, name, value, null, description);
|
||||
} else {
|
||||
final Function addedVar = mathRegistry.add(varBuilder);
|
||||
if (activity.isInCategory(addedVar)) {
|
||||
if (editedInstance != null) {
|
||||
activity.removeFromAdapter(editedInstance);
|
||||
}
|
||||
activity.addToAdapter(addedVar);
|
||||
}
|
||||
|
||||
mathRegistry.save(activity);
|
||||
|
||||
if (activity.isInCategory(addedVar)) {
|
||||
activity.sort();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean isValidName(@Nullable String name) {
|
||||
boolean result = false;
|
||||
|
||||
if (!StringUtils.isEmpty(name)) {
|
||||
try {
|
||||
assert name != null;
|
||||
Identifier.parser.parse(Parser.Parameters.newInstance(name, new MutableInt(0), CalculatorEngine.instance.getEngine()), null);
|
||||
result = true;
|
||||
} catch (ParseException e) {
|
||||
// not valid name;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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.calculator.math.edit;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.widget.TextView;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.calculator.R;
|
||||
import org.solovyev.android.calculator.model.AndroidMathRegistry;
|
||||
import org.solovyev.common.math.MathEntity;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/22/11
|
||||
* Time: 9:36 PM
|
||||
*/
|
||||
class MathEntityRemover<T extends MathEntity> implements DialogInterface.OnClickListener {
|
||||
|
||||
@NotNull
|
||||
private final T mathEntity;
|
||||
|
||||
@Nullable
|
||||
private final DialogInterface.OnClickListener callbackOnCancel;
|
||||
|
||||
private final boolean confirmed;
|
||||
|
||||
@NotNull
|
||||
private final AndroidMathRegistry<? super T> varsRegistry;
|
||||
|
||||
@NotNull
|
||||
private final AbstractMathEntityListActivity<T> activity;
|
||||
|
||||
public MathEntityRemover(@NotNull T mathEntity,
|
||||
@Nullable DialogInterface.OnClickListener callbackOnCancel,
|
||||
@NotNull AndroidMathRegistry<? super T> varsRegistry,
|
||||
@NotNull AbstractMathEntityListActivity<T> activity) {
|
||||
this(mathEntity, callbackOnCancel, false, varsRegistry, activity);
|
||||
}
|
||||
|
||||
public MathEntityRemover(@NotNull T mathEntity,
|
||||
@Nullable DialogInterface.OnClickListener callbackOnCancel,
|
||||
boolean confirmed,
|
||||
@NotNull AndroidMathRegistry<? super T> varsRegistry,
|
||||
@NotNull AbstractMathEntityListActivity<T> activity) {
|
||||
this.mathEntity = mathEntity;
|
||||
this.callbackOnCancel = callbackOnCancel;
|
||||
this.confirmed = confirmed;
|
||||
this.varsRegistry = varsRegistry;
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (!confirmed) {
|
||||
showConfirmationDialog();
|
||||
} else {
|
||||
if (activity.isInCategory(mathEntity)) {
|
||||
activity.removeFromAdapter(mathEntity);
|
||||
}
|
||||
|
||||
varsRegistry.remove(mathEntity);
|
||||
varsRegistry.save(activity);
|
||||
if (activity.isInCategory(mathEntity)) {
|
||||
activity.notifyAdapter();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void showConfirmationDialog() {
|
||||
final TextView question = new TextView(activity);
|
||||
question.setText(String.format(activity.getString(R.string.c_var_removal_confirmation_question), mathEntity.getName()));
|
||||
question.setPadding(6, 6, 6, 6);
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(activity)
|
||||
.setCancelable(true)
|
||||
.setView(question)
|
||||
.setTitle(R.string.c_var_removal_confirmation)
|
||||
.setNegativeButton(R.string.c_no, callbackOnCancel)
|
||||
.setPositiveButton(R.string.c_yes, new MathEntityRemover<T>(mathEntity, callbackOnCancel, true, varsRegistry, activity));
|
||||
|
||||
builder.create().show();
|
||||
}
|
||||
}
|
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* 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.calculator.math.edit;
|
||||
|
||||
import android.content.DialogInterface;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
import jscl.text.Identifier;
|
||||
import jscl.text.MutableInt;
|
||||
import jscl.text.ParseException;
|
||||
import jscl.text.Parser;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.calculator.R;
|
||||
import org.solovyev.android.calculator.math.MathType;
|
||||
import org.solovyev.android.calculator.model.AndroidMathRegistry;
|
||||
import org.solovyev.android.calculator.model.CalculatorEngine;
|
||||
import org.solovyev.android.calculator.model.MathEntityBuilder;
|
||||
import org.solovyev.common.math.MathEntity;
|
||||
import org.solovyev.common.utils.StringUtils;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/22/11
|
||||
* Time: 9:52 PM
|
||||
*/
|
||||
class VarEditorSaver<T extends MathEntity> implements DialogInterface.OnClickListener {
|
||||
|
||||
public static interface EditorCreator<T extends MathEntity> {
|
||||
void showEditor(@NotNull AbstractMathEntityListActivity<T> activity,
|
||||
@Nullable T editedInstance,
|
||||
@Nullable String name,
|
||||
@Nullable String value,
|
||||
@Nullable String description);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private final EditorCreator<T> editorCreator;
|
||||
|
||||
@NotNull
|
||||
private final MathEntityBuilder<? extends T> varBuilder;
|
||||
|
||||
@Nullable
|
||||
private final T editedInstance;
|
||||
|
||||
@NotNull
|
||||
private final AndroidMathRegistry<T> mathRegistry;
|
||||
|
||||
@NotNull
|
||||
private final AbstractMathEntityListActivity<T> activity;
|
||||
|
||||
@NotNull
|
||||
private View editView;
|
||||
|
||||
public VarEditorSaver(@NotNull MathEntityBuilder<? extends T> varBuilder,
|
||||
@Nullable T editedInstance,
|
||||
@NotNull View editView,
|
||||
@NotNull AbstractMathEntityListActivity<T> activity,
|
||||
@NotNull AndroidMathRegistry<T> mathRegistry,
|
||||
@NotNull EditorCreator<T> editorCreator) {
|
||||
this.varBuilder = varBuilder;
|
||||
this.editedInstance = editedInstance;
|
||||
this.editView = editView;
|
||||
this.activity = activity;
|
||||
this.mathRegistry = mathRegistry;
|
||||
this.editorCreator = editorCreator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (which == DialogInterface.BUTTON_POSITIVE) {
|
||||
final Integer error;
|
||||
|
||||
final EditText editName = (EditText) editView.findViewById(R.id.var_edit_name);
|
||||
String name = editName.getText().toString();
|
||||
|
||||
final EditText editValue = (EditText) editView.findViewById(R.id.var_edit_value);
|
||||
String value = editValue.getText().toString();
|
||||
|
||||
final EditText editDescription = (EditText) editView.findViewById(R.id.var_edit_description);
|
||||
String description = editDescription.getText().toString();
|
||||
|
||||
if (isValidName(name)) {
|
||||
|
||||
boolean canBeSaved = false;
|
||||
|
||||
final T entityFromRegistry = mathRegistry.get(name);
|
||||
if (entityFromRegistry == null) {
|
||||
canBeSaved = true;
|
||||
} else if (editedInstance != null && entityFromRegistry.getId().equals(editedInstance.getId())) {
|
||||
canBeSaved = true;
|
||||
}
|
||||
|
||||
if (canBeSaved) {
|
||||
final MathType.Result mathType = MathType.getType(name, 0, false);
|
||||
|
||||
if (mathType.getMathType() == MathType.text || mathType.getMathType() == MathType.constant) {
|
||||
|
||||
if (StringUtils.isEmpty(value)) {
|
||||
// value is empty => undefined variable
|
||||
varBuilder.setName(name);
|
||||
varBuilder.setDescription(description);
|
||||
varBuilder.setValue(null);
|
||||
error = null;
|
||||
} else {
|
||||
// value is not empty => must be a number
|
||||
boolean valid = CalculatorVarsActivity.isValidValue(value);
|
||||
|
||||
if (valid) {
|
||||
varBuilder.setName(name);
|
||||
varBuilder.setDescription(description);
|
||||
varBuilder.setValue(value);
|
||||
error = null;
|
||||
} else {
|
||||
error = R.string.c_value_is_not_a_number;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error = R.string.c_var_name_clashes;
|
||||
}
|
||||
} else {
|
||||
error = R.string.c_var_already_exists;
|
||||
}
|
||||
} else {
|
||||
error = R.string.c_name_is_not_valid;
|
||||
}
|
||||
|
||||
if (error != null) {
|
||||
Toast.makeText(activity, activity.getString(error), Toast.LENGTH_LONG).show();
|
||||
editorCreator.showEditor(activity, editedInstance, name, value, description);
|
||||
} else {
|
||||
final T addedVar = mathRegistry.add(varBuilder);
|
||||
if (activity.isInCategory(addedVar)) {
|
||||
if (editedInstance != null) {
|
||||
activity.removeFromAdapter(editedInstance);
|
||||
}
|
||||
activity.addToAdapter(addedVar);
|
||||
}
|
||||
|
||||
mathRegistry.save(activity);
|
||||
|
||||
if (activity.isInCategory(addedVar)) {
|
||||
activity.sort();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean isValidName(@Nullable String name) {
|
||||
boolean result = false;
|
||||
|
||||
if (!StringUtils.isEmpty(name)) {
|
||||
try {
|
||||
assert name != null;
|
||||
Identifier.parser.parse(Parser.Parameters.newInstance(name, new MutableInt(0), CalculatorEngine.instance.getEngine()), null);
|
||||
result = true;
|
||||
} catch (ParseException e) {
|
||||
// not valid name;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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.calculator.model;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.simpleframework.xml.Element;
|
||||
import org.simpleframework.xml.Root;
|
||||
import org.solovyev.common.text.StringMapper;
|
||||
import org.solovyev.common.utils.CollectionsUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/22/11
|
||||
* Time: 5:25 PM
|
||||
*/
|
||||
|
||||
@Root
|
||||
public class AFunction implements MathPersistenceEntity {
|
||||
|
||||
@Element
|
||||
@NotNull
|
||||
private String name;
|
||||
|
||||
@Element
|
||||
@NotNull
|
||||
private String content;
|
||||
|
||||
|
||||
@Element(required = false)
|
||||
@Nullable
|
||||
private String parameterNames;
|
||||
|
||||
|
||||
@NotNull
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(@NotNull String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(@NotNull String content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getParameterNames() {
|
||||
return parameterNames;
|
||||
}
|
||||
|
||||
public void setParameterNames(@Nullable String[] parameterNames) {
|
||||
this.parameterNames = CollectionsUtils.formatValue(CollectionsUtils.asList(parameterNames), ";", new StringMapper());
|
||||
}
|
||||
|
||||
public void setParameterNames(@Nullable String parameterNames) {
|
||||
this.parameterNames = parameterNames;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String[] getParameterNamesAsArray() {
|
||||
final List<String> parameterNamesAsList = CollectionsUtils.split(parameterNames, ";");
|
||||
return parameterNamesAsList.toArray(new String[parameterNamesAsList.size()]);
|
||||
}
|
||||
}
|
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* 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.calculator.model;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.simpleframework.xml.Serializer;
|
||||
import org.simpleframework.xml.core.Persister;
|
||||
import org.solovyev.android.ResourceCache;
|
||||
import org.solovyev.common.definitions.IBuilder;
|
||||
import org.solovyev.common.math.MathEntity;
|
||||
import org.solovyev.common.math.MathRegistry;
|
||||
|
||||
import java.io.StringWriter;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 10/30/11
|
||||
* Time: 1:03 AM
|
||||
*/
|
||||
public abstract class AbstractAndroidMathRegistry<T extends MathEntity, P extends MathPersistenceEntity> implements AndroidMathRegistry<T> {
|
||||
|
||||
@NotNull
|
||||
private final MathRegistry<T> mathRegistry;
|
||||
|
||||
@NotNull
|
||||
private final String prefix;
|
||||
|
||||
protected AbstractAndroidMathRegistry(@NotNull MathRegistry<T> mathRegistry, @NotNull String prefix) {
|
||||
this.mathRegistry = mathRegistry;
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected abstract Map<String, String> getSubstitutes();
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getDescription(@NotNull Context context, @NotNull String mathEntityName) {
|
||||
final String stringName;
|
||||
|
||||
final Map<String, String> substitutes = getSubstitutes();
|
||||
final String substitute = substitutes.get(mathEntityName);
|
||||
if (substitute == null) {
|
||||
stringName = prefix + mathEntityName;
|
||||
} else {
|
||||
stringName = prefix + substitute;
|
||||
}
|
||||
|
||||
return ResourceCache.instance.getCaption(stringName);
|
||||
}
|
||||
|
||||
public synchronized void load(@Nullable Context context, @Nullable SharedPreferences preferences) {
|
||||
|
||||
if (context != null && preferences != null) {
|
||||
final Integer preferenceStringId = getPreferenceStringId();
|
||||
if (preferenceStringId != null) {
|
||||
final String value = preferences.getString(context.getString(preferenceStringId), null);
|
||||
if (value != null) {
|
||||
final Serializer serializer = new Persister();
|
||||
try {
|
||||
final MathEntityPersistenceContainer<P> persistenceContainer = serializer.read(getPersistenceContainerClass(), value);
|
||||
for (P entity : persistenceContainer.getEntities()) {
|
||||
if (!contains(entity.getName())) {
|
||||
add(createBuilder(entity));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*Log.d(AndroidVarsRegistry.class.getName(), vars.size() + " variables registered!");
|
||||
for (Var var : vars) {
|
||||
Log.d(AndroidVarsRegistry.class.getName(), var.toString());
|
||||
}*/
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected abstract IBuilder<? extends T> createBuilder(@NotNull P entity);
|
||||
|
||||
@NotNull
|
||||
protected abstract Class<? extends MathEntityPersistenceContainer<P>> getPersistenceContainerClass();
|
||||
|
||||
@Nullable
|
||||
protected abstract Integer getPreferenceStringId();
|
||||
|
||||
|
||||
@Override
|
||||
public synchronized void save(@NotNull Context context) {
|
||||
final Integer preferenceStringId = getPreferenceStringId();
|
||||
|
||||
if (preferenceStringId != null) {
|
||||
final SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
final SharedPreferences.Editor editor = settings.edit();
|
||||
|
||||
final MathEntityPersistenceContainer<P> container = createPersistenceContainer();
|
||||
for (T entity : this.getEntities()) {
|
||||
if (!entity.isSystem()) {
|
||||
final P persistenceEntity = transform(entity);
|
||||
if (persistenceEntity != null) {
|
||||
container.getEntities().add(persistenceEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final StringWriter sw = new StringWriter();
|
||||
final Serializer serializer = new Persister();
|
||||
try {
|
||||
serializer.write(container, sw);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
editor.putString(context.getString(preferenceStringId), sw.toString());
|
||||
|
||||
editor.commit();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected abstract P transform(@NotNull T entity);
|
||||
|
||||
@NotNull
|
||||
protected abstract MathEntityPersistenceContainer<P> createPersistenceContainer();
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<T> getEntities() {
|
||||
return mathRegistry.getEntities();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<T> getSystemEntities() {
|
||||
return mathRegistry.getSystemEntities();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T add(@NotNull IBuilder<? extends T> IBuilder) {
|
||||
return mathRegistry.add(IBuilder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(@NotNull T var) {
|
||||
mathRegistry.remove(var);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<String> getNames() {
|
||||
return mathRegistry.getNames();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(@NotNull String name) {
|
||||
return mathRegistry.contains(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get(@NotNull String name) {
|
||||
return mathRegistry.get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getById(@NotNull Integer id) {
|
||||
return mathRegistry.getById(id);
|
||||
}
|
||||
}
|
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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.calculator.model;
|
||||
|
||||
import jscl.MathEngine;
|
||||
import jscl.NumeralBase;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.calculator.math.MathType;
|
||||
import org.solovyev.common.utils.StringUtils;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/15/11
|
||||
* Time: 9:01 PM
|
||||
*/
|
||||
public abstract class AbstractNumberBuilder {
|
||||
|
||||
@NotNull
|
||||
protected final MathEngine engine;
|
||||
|
||||
@Nullable
|
||||
protected StringBuilder numberBuilder = null;
|
||||
|
||||
@Nullable
|
||||
protected NumeralBase nb;
|
||||
|
||||
protected AbstractNumberBuilder(@NotNull MathEngine engine) {
|
||||
this.engine = engine;
|
||||
this.nb = engine.getNumeralBase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method determines if we can continue to process current number
|
||||
*
|
||||
* @param mathTypeResult current math type result
|
||||
* @return true if we can continue of processing of current number, if false - new number should be constructed
|
||||
*/
|
||||
protected boolean canContinue(@NotNull MathType.Result mathTypeResult) {
|
||||
return ((mathTypeResult.getMathType().getGroupType() == MathType.MathGroupType.number && !spaceBefore(mathTypeResult) && numeralBaseCheck(mathTypeResult) && numeralBaseInTheStart(mathTypeResult.getMathType()) || isSignAfterE(mathTypeResult)));
|
||||
}
|
||||
|
||||
private boolean spaceBefore(@NotNull MathType.Result mathTypeResult) {
|
||||
return numberBuilder == null && StringUtils.isEmpty(mathTypeResult.getMatch().trim());
|
||||
}
|
||||
|
||||
private boolean numeralBaseInTheStart(@NotNull MathType mathType) {
|
||||
return mathType != MathType.numeral_base || numberBuilder == null;
|
||||
}
|
||||
|
||||
private boolean numeralBaseCheck(@NotNull MathType.Result mathType) {
|
||||
return mathType.getMathType() != MathType.digit || getNumeralBase().getAcceptableCharacters().contains(mathType.getMatch().charAt(0));
|
||||
}
|
||||
|
||||
private boolean isSignAfterE(@NotNull MathType.Result mathTypeResult) {
|
||||
if ("-".equals(mathTypeResult.getMatch()) || "+".equals(mathTypeResult.getMatch())) {
|
||||
final StringBuilder localNb = numberBuilder;
|
||||
if (localNb != null && localNb.length() > 0) {
|
||||
if (localNb.charAt(localNb.length() - 1) == MathType.POWER_10) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isHexMode() {
|
||||
return nb == NumeralBase.hex || (nb == null && engine.getNumeralBase() == NumeralBase.hex);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected NumeralBase getNumeralBase() {
|
||||
return nb == null ? engine.getNumeralBase() : nb;
|
||||
}
|
||||
}
|
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* 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.calculator.model;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import jscl.math.function.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.calculator.R;
|
||||
import org.solovyev.common.definitions.IBuilder;
|
||||
import org.solovyev.common.math.MathRegistry;
|
||||
import org.solovyev.common.utils.CollectionsUtils;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 11/17/11
|
||||
* Time: 11:28 PM
|
||||
*/
|
||||
public class AndroidFunctionsMathRegistry extends AbstractAndroidMathRegistry<Function, AFunction> {
|
||||
|
||||
public static enum Category {
|
||||
|
||||
trigonometric(R.string.c_fun_category_trig, 100){
|
||||
@Override
|
||||
boolean isInCategory(@NotNull Function function) {
|
||||
return (function instanceof Trigonometric || function instanceof ArcTrigonometric) && !hyperbolic_trigonometric.isInCategory(function);
|
||||
}
|
||||
},
|
||||
|
||||
hyperbolic_trigonometric(R.string.c_fun_category_hyper_trig, 300) {
|
||||
|
||||
private final List<String> names = Arrays.asList("sinh", "cosh", "tanh", "coth", "asinh", "acosh", "atanh", "acoth");
|
||||
|
||||
@Override
|
||||
boolean isInCategory(@NotNull Function function) {
|
||||
return names.contains(function.getName());
|
||||
}
|
||||
},
|
||||
|
||||
comparison(R.string.c_fun_category_comparison, 200) {
|
||||
@Override
|
||||
boolean isInCategory(@NotNull Function function) {
|
||||
return function instanceof Comparison;
|
||||
}
|
||||
},
|
||||
|
||||
my(R.string.c_fun_category_my, 0) {
|
||||
@Override
|
||||
boolean isInCategory(@NotNull Function function) {
|
||||
return !function.isSystem();
|
||||
}
|
||||
},
|
||||
|
||||
common(R.string.c_fun_category_common, 50) {
|
||||
@Override
|
||||
boolean isInCategory(@NotNull Function function) {
|
||||
for (Category category : values()) {
|
||||
if ( category != this ) {
|
||||
if ( category.isInCategory(function) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
private final int captionId;
|
||||
|
||||
private final int tabOrder;
|
||||
|
||||
Category(int captionId, int tabOrder) {
|
||||
this.captionId = captionId;
|
||||
this.tabOrder = tabOrder;
|
||||
}
|
||||
|
||||
public int getCaptionId() {
|
||||
return captionId;
|
||||
}
|
||||
|
||||
abstract boolean isInCategory(@NotNull Function function);
|
||||
|
||||
@NotNull
|
||||
public static List<Category> getCategoriesByTabOrder() {
|
||||
final List<Category> result = CollectionsUtils.asList(Category.values());
|
||||
|
||||
Collections.sort(result, new Comparator<Category>() {
|
||||
@Override
|
||||
public int compare(Category category, Category category1) {
|
||||
return category.tabOrder - category1.tabOrder;
|
||||
}
|
||||
});
|
||||
|
||||
// todo serso: current solution (as creating functions is not implemented yet)
|
||||
result.remove(my);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static final Map<String, String> substitutes = new HashMap<String, String>();
|
||||
static {
|
||||
substitutes.put("√", "sqrt");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static final String FUNCTION_DESCRIPTION_PREFIX = "c_fun_description_";
|
||||
|
||||
public AndroidFunctionsMathRegistry(@NotNull MathRegistry<jscl.math.function.Function> functionsRegistry) {
|
||||
super(functionsRegistry, FUNCTION_DESCRIPTION_PREFIX);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load(@Nullable Context context, @Nullable SharedPreferences preferences) {
|
||||
super.load(context, preferences);
|
||||
|
||||
add(new CustomFunction.Builder(true, "log", new String[]{"base", "x"}, "ln(x)/ln(base)"));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected Map<String, String> getSubstitutes() {
|
||||
return substitutes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory(@NotNull Function function) {
|
||||
for (Category category : Category.values()) {
|
||||
if ( category.isInCategory(function) ) {
|
||||
return category.name();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected IBuilder<? extends Function> createBuilder(@NotNull AFunction entity) {
|
||||
return new CustomFunction.Builder(entity.getName(), entity.getParameterNamesAsArray(), entity.getContent());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected Class<? extends MathEntityPersistenceContainer<AFunction>> getPersistenceContainerClass() {
|
||||
return Functions.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer getPreferenceStringId() {
|
||||
return R.string.p_calc_functions;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AFunction transform(@NotNull Function entity) {
|
||||
if (entity instanceof CustomFunction) {
|
||||
final AFunction result = new AFunction();
|
||||
result.setName(entity.getName());
|
||||
result.setContent(((CustomFunction) entity).getContent());
|
||||
result.setParameterNames(((CustomFunction) entity).getParameterNames());
|
||||
return result;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected MathEntityPersistenceContainer<AFunction> createPersistenceContainer() {
|
||||
return new Functions();
|
||||
}
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.calculator.model;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.common.math.MathEntity;
|
||||
import org.solovyev.common.math.MathRegistry;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 10/30/11
|
||||
* Time: 1:02 AM
|
||||
*/
|
||||
public interface AndroidMathRegistry<T extends MathEntity> extends MathRegistry<T> {
|
||||
|
||||
@Nullable
|
||||
String getDescription(@NotNull Context context, @NotNull String mathEntityName);
|
||||
|
||||
@Nullable
|
||||
String getCategory(@NotNull T mathEntity);
|
||||
|
||||
void load(@Nullable Context context, @Nullable SharedPreferences preferences);
|
||||
|
||||
void save(@NotNull Context context);
|
||||
}
|
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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.calculator.model;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import jscl.math.operator.Operator;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.common.definitions.IBuilder;
|
||||
import org.solovyev.common.math.MathRegistry;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 11/17/11
|
||||
* Time: 11:29 PM
|
||||
*/
|
||||
public class AndroidOperatorsMathRegistry extends AbstractAndroidMathRegistry<Operator, MathPersistenceEntity> {
|
||||
|
||||
@NotNull
|
||||
private static final Map<String, String> substitutes = new HashMap<String, String>();
|
||||
static {
|
||||
substitutes.put("Σ", "sum");
|
||||
substitutes.put("∏", "product");
|
||||
substitutes.put("∂", "derivative");
|
||||
substitutes.put("∫ab", "integral_ab");
|
||||
substitutes.put("∫", "integral");
|
||||
substitutes.put("Σ", "sum");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static final String OPERATOR_DESCRIPTION_PREFIX = "c_op_description_";
|
||||
|
||||
protected AndroidOperatorsMathRegistry(@NotNull MathRegistry<Operator> functionsRegistry) {
|
||||
super(functionsRegistry, OPERATOR_DESCRIPTION_PREFIX);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected Map<String, String> getSubstitutes() {
|
||||
return substitutes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory(@NotNull Operator mathEntity) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load(@Nullable Context context, @Nullable SharedPreferences preferences) {
|
||||
// not supported yet
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected IBuilder<? extends Operator> createBuilder(@NotNull MathPersistenceEntity entity) {
|
||||
return null; //To change body of implemented methods use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected Class<? extends MathEntityPersistenceContainer<MathPersistenceEntity>> getPersistenceContainerClass() {
|
||||
return null; //To change body of implemented methods use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer getPreferenceStringId() {
|
||||
return null; //To change body of implemented methods use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(@NotNull Context context) {
|
||||
// not supported yet
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MathPersistenceEntity transform(@NotNull Operator entity) {
|
||||
return null; //To change body of implemented methods use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected MathEntityPersistenceContainer<MathPersistenceEntity> createPersistenceContainer() {
|
||||
return null; //To change body of implemented methods use File | Settings | File Templates.
|
||||
}
|
||||
}
|
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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.calculator.model;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import jscl.math.operator.Operator;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.common.definitions.IBuilder;
|
||||
import org.solovyev.common.math.MathRegistry;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 11/19/11
|
||||
* Time: 1:48 PM
|
||||
*/
|
||||
public class AndroidPostfixFunctionsRegistry extends AbstractAndroidMathRegistry<Operator, MathPersistenceEntity> {
|
||||
|
||||
@NotNull
|
||||
private static final Map<String, String> substitutes = new HashMap<String, String>();
|
||||
static {
|
||||
substitutes.put("%", "percent");
|
||||
substitutes.put("!", "factorial");
|
||||
substitutes.put("!!", "double_factorial");
|
||||
substitutes.put("°", "degree");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static final String POSTFIX_FUNCTION_DESCRIPTION_PREFIX = "c_pf_description_";
|
||||
|
||||
protected AndroidPostfixFunctionsRegistry(@NotNull MathRegistry<Operator> functionsRegistry) {
|
||||
super(functionsRegistry, POSTFIX_FUNCTION_DESCRIPTION_PREFIX);
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected Map<String, String> getSubstitutes() {
|
||||
return substitutes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory(@NotNull Operator mathEntity) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load(@Nullable Context context, @Nullable SharedPreferences preferences) {
|
||||
// not supported yet
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected IBuilder<? extends Operator> createBuilder(@NotNull MathPersistenceEntity entity) {
|
||||
return null; //To change body of implemented methods use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected Class<? extends MathEntityPersistenceContainer<MathPersistenceEntity>> getPersistenceContainerClass() {
|
||||
return null; //To change body of implemented methods use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer getPreferenceStringId() {
|
||||
return null; //To change body of implemented methods use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(@NotNull Context context) {
|
||||
// not supported yet
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MathPersistenceEntity transform(@NotNull Operator entity) {
|
||||
return null; //To change body of implemented methods use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected MathEntityPersistenceContainer<MathPersistenceEntity> createPersistenceContainer() {
|
||||
return null; //To change body of implemented methods use File | Settings | File Templates.
|
||||
}
|
||||
}
|
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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.calculator.model;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import jscl.math.function.IConstant;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.calculator.R;
|
||||
import org.solovyev.common.definitions.IBuilder;
|
||||
import org.solovyev.common.math.MathRegistry;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 9/29/11
|
||||
* Time: 4:57 PM
|
||||
*/
|
||||
class AndroidVarsRegistryImpl extends AbstractAndroidMathRegistry<IConstant, Var> {
|
||||
|
||||
@NotNull
|
||||
private static final Map<String, String> substitutes = new HashMap<String, String>();
|
||||
static {
|
||||
substitutes.put("π", "pi");
|
||||
substitutes.put("Π", "PI");
|
||||
substitutes.put("∞", "inf");
|
||||
substitutes.put("h", "h_reduced");
|
||||
substitutes.put("NaN", "nan");
|
||||
}
|
||||
|
||||
protected AndroidVarsRegistryImpl(@NotNull MathRegistry<IConstant> mathRegistry) {
|
||||
super(mathRegistry, "c_var_description_");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected Map<String, String> getSubstitutes() {
|
||||
return substitutes;
|
||||
}
|
||||
|
||||
public synchronized void load(@Nullable Context context, @Nullable SharedPreferences preferences) {
|
||||
super.load(context, preferences);
|
||||
|
||||
tryToAddAuxVar("x");
|
||||
tryToAddAuxVar("y");
|
||||
tryToAddAuxVar("t");
|
||||
tryToAddAuxVar("j");
|
||||
|
||||
|
||||
/*Log.d(AndroidVarsRegistry.class.getName(), vars.size() + " variables registered!");
|
||||
for (Var var : vars) {
|
||||
Log.d(AndroidVarsRegistry.class.getName(), var.toString());
|
||||
}*/
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected IBuilder<? extends IConstant> createBuilder(@NotNull Var entity) {
|
||||
return new Var.Builder(entity);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected Class<? extends MathEntityPersistenceContainer<Var>> getPersistenceContainerClass() {
|
||||
return Vars.class;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected MathEntityPersistenceContainer<Var> createPersistenceContainer() {
|
||||
return new Vars();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected Integer getPreferenceStringId() {
|
||||
return R.string.p_calc_vars;
|
||||
}
|
||||
|
||||
private void tryToAddAuxVar(@NotNull String name) {
|
||||
if ( !contains(name) ) {
|
||||
add(new Var.Builder(name, (String)null));
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected Var transform(@NotNull IConstant entity) {
|
||||
if (entity instanceof Var) {
|
||||
return (Var) entity;
|
||||
} else {
|
||||
return new Var.Builder(entity).create();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription(@NotNull Context context, @NotNull String mathEntityName) {
|
||||
final IConstant var = get(mathEntityName);
|
||||
if (var != null && !var.isSystem()) {
|
||||
return var.getDescription();
|
||||
} else {
|
||||
return super.getDescription(context, mathEntityName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory(@NotNull IConstant var) {
|
||||
for (VarCategory category : VarCategory.values()) {
|
||||
if ( category.isInCategory(var) ) {
|
||||
return category.name();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@@ -0,0 +1,422 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
||||
* For more information, please, contact se.solovyev@gmail.com
|
||||
*/
|
||||
|
||||
package org.solovyev.android.calculator.model;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import jscl.*;
|
||||
import jscl.math.Generic;
|
||||
import jscl.math.function.Function;
|
||||
import jscl.math.function.IConstant;
|
||||
import jscl.math.operator.Operator;
|
||||
import jscl.text.ParseInterruptedException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.calculator.jscl.JsclOperation;
|
||||
import org.solovyev.android.prefs.BooleanPreference;
|
||||
import org.solovyev.common.text.EnumMapper;
|
||||
import org.solovyev.android.prefs.Preference;
|
||||
import org.solovyev.android.prefs.StringPreference;
|
||||
import org.solovyev.common.msg.MessageRegistry;
|
||||
import org.solovyev.common.text.NumberMapper;
|
||||
import org.solovyev.common.utils.MutableObject;
|
||||
import org.solovyev.common.utils.StringUtils;
|
||||
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 9/12/11
|
||||
* Time: 11:38 PM
|
||||
*/
|
||||
|
||||
public enum CalculatorEngine {
|
||||
|
||||
instance;
|
||||
|
||||
private static final String GROUPING_SEPARATOR_P_KEY = "org.solovyev.android.calculator.CalculatorActivity_calc_grouping_separator";
|
||||
|
||||
private static final String MULTIPLICATION_SIGN_P_KEY = "org.solovyev.android.calculator.CalculatorActivity_calc_multiplication_sign";
|
||||
private static final String MULTIPLICATION_SIGN_DEFAULT = "×";
|
||||
|
||||
private static final String MAX_CALCULATION_TIME_P_KEY = "calculation.max_calculation_time";
|
||||
private static final String MAX_CALCULATION_TIME_DEFAULT = "5";
|
||||
|
||||
private static final String SCIENCE_NOTATION_P_KEY = "calculation.output.science_notation";
|
||||
private static final boolean SCIENCE_NOTATION_DEFAULT = false;
|
||||
|
||||
private static final String ROUND_RESULT_P_KEY = "org.solovyev.android.calculator.CalculatorModel_round_result";
|
||||
private static final boolean ROUND_RESULT_DEFAULT = true;
|
||||
|
||||
private static final String RESULT_PRECISION_P_KEY = "org.solovyev.android.calculator.CalculatorModel_result_precision";
|
||||
private static final String RESULT_PRECISION_DEFAULT = "5";
|
||||
|
||||
private static final String NUMERAL_BASES_P_KEY = "org.solovyev.android.calculator.CalculatorActivity_numeral_bases";
|
||||
private static final String NUMERAL_BASES_DEFAULT = "dec";
|
||||
|
||||
private static final String ANGLE_UNITS_P_KEY = "org.solovyev.android.calculator.CalculatorActivity_angle_units";
|
||||
private static final String ANGLE_UNITS_DEFAULT = "deg";
|
||||
|
||||
public static class Preferences {
|
||||
public static final Preference<String> groupingSeparator = StringPreference.newInstance(GROUPING_SEPARATOR_P_KEY, JsclMathEngine.GROUPING_SEPARATOR_DEFAULT);
|
||||
public static final Preference<String> multiplicationSign = StringPreference.newInstance(MULTIPLICATION_SIGN_P_KEY, MULTIPLICATION_SIGN_DEFAULT);
|
||||
public static final Preference<Integer> precision = StringPreference.newInstance(RESULT_PRECISION_P_KEY, RESULT_PRECISION_DEFAULT, new NumberMapper<Integer>(Integer.class));
|
||||
public static final Preference<Boolean> roundResult = new BooleanPreference(ROUND_RESULT_P_KEY, ROUND_RESULT_DEFAULT);
|
||||
public static final Preference<NumeralBase> numeralBase = StringPreference.newInstance(NUMERAL_BASES_P_KEY, NUMERAL_BASES_DEFAULT, EnumMapper.newInstance(NumeralBase.class));
|
||||
public static final Preference<AngleUnit> angleUnit = StringPreference.newInstance(ANGLE_UNITS_P_KEY, ANGLE_UNITS_DEFAULT, EnumMapper.newInstance(AngleUnit.class));
|
||||
public static final Preference<Boolean> scienceNotation = new BooleanPreference(SCIENCE_NOTATION_P_KEY, SCIENCE_NOTATION_DEFAULT);
|
||||
public static final Preference<Integer> maxCalculationTime = StringPreference.newInstance(MAX_CALCULATION_TIME_P_KEY, MAX_CALCULATION_TIME_DEFAULT, new NumberMapper<Integer>(Integer.class));
|
||||
|
||||
private static final List<String> preferenceKeys = new ArrayList<String>();
|
||||
|
||||
static {
|
||||
preferenceKeys.add(groupingSeparator.getKey());
|
||||
preferenceKeys.add(multiplicationSign.getKey());
|
||||
preferenceKeys.add(precision.getKey());
|
||||
preferenceKeys.add(roundResult.getKey());
|
||||
preferenceKeys.add(numeralBase.getKey());
|
||||
preferenceKeys.add(angleUnit.getKey());
|
||||
preferenceKeys.add(scienceNotation.getKey());
|
||||
preferenceKeys.add(maxCalculationTime.getKey());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static List<String> getPreferenceKeys() {
|
||||
return Collections.unmodifiableList(preferenceKeys);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private final Object lock = new Object();
|
||||
|
||||
@NotNull
|
||||
private MathEngine engine = JsclMathEngine.instance;
|
||||
|
||||
@NotNull
|
||||
public final TextProcessor<PreparedExpression, String> preprocessor = new ToJsclTextProcessor();
|
||||
|
||||
@NotNull
|
||||
private final AndroidMathRegistry<IConstant> varsRegistry = new AndroidVarsRegistryImpl(engine.getConstantsRegistry());
|
||||
|
||||
@NotNull
|
||||
private final AndroidMathRegistry<jscl.math.function.Function> functionsRegistry = new AndroidFunctionsMathRegistry(engine.getFunctionsRegistry());
|
||||
|
||||
@NotNull
|
||||
private final AndroidMathRegistry<Operator> operatorsRegistry = new AndroidOperatorsMathRegistry(engine.getOperatorsRegistry());
|
||||
|
||||
private final AndroidMathRegistry<Operator> postfixFunctionsRegistry = new AndroidPostfixFunctionsRegistry(engine.getPostfixFunctionsRegistry());
|
||||
|
||||
@NotNull
|
||||
private ThreadKiller threadKiller = new AndroidThreadKiller();
|
||||
|
||||
// calculation thread timeout in seconds, after timeout thread would be interrupted
|
||||
private int timeout = Integer.valueOf(MAX_CALCULATION_TIME_DEFAULT);
|
||||
|
||||
@NotNull
|
||||
private String multiplicationSign = MULTIPLICATION_SIGN_DEFAULT;
|
||||
|
||||
CalculatorEngine() {
|
||||
this.engine.setRoundResult(true);
|
||||
this.engine.setUseGroupingSeparator(true);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getMultiplicationSign() {
|
||||
return multiplicationSign;
|
||||
}
|
||||
|
||||
public void setMultiplicationSign(@NotNull String multiplicationSign) {
|
||||
this.multiplicationSign = multiplicationSign;
|
||||
}
|
||||
|
||||
public static class Result {
|
||||
|
||||
@NotNull
|
||||
private Generic genericResult;
|
||||
|
||||
@NotNull
|
||||
private String result;
|
||||
|
||||
@NotNull
|
||||
private JsclOperation userOperation;
|
||||
|
||||
public Result(@NotNull String result, @NotNull JsclOperation userOperation, @NotNull Generic genericResult) {
|
||||
this.result = result;
|
||||
this.userOperation = userOperation;
|
||||
this.genericResult = genericResult;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public JsclOperation getUserOperation() {
|
||||
return userOperation;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Generic getGenericResult() {
|
||||
return genericResult;
|
||||
}
|
||||
}
|
||||
|
||||
public Result evaluate(@NotNull JsclOperation operation,
|
||||
@NotNull String expression) throws CalculatorParseException, CalculatorEvalException {
|
||||
return evaluate(operation, expression, null);
|
||||
}
|
||||
|
||||
public Result evaluate(@NotNull final JsclOperation operation,
|
||||
@NotNull String expression,
|
||||
@Nullable MessageRegistry mr) throws CalculatorParseException, CalculatorEvalException {
|
||||
synchronized (lock) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
final PreparedExpression preparedExpression = preprocessor.process(expression);
|
||||
sb.append(preparedExpression);
|
||||
|
||||
//Log.d(CalculatorEngine.class.getName(), "Preprocessed expression: " + preparedExpression);
|
||||
/*if (operation == JsclOperation.numeric && preparedExpression.isExistsUndefinedVar()) {
|
||||
operation = JsclOperation.simplify;
|
||||
|
||||
if (mr != null) {
|
||||
final String undefinedVars = CollectionsUtils.formatValue(preparedExpression.getUndefinedVars(), ", ", new Formatter<Var>() {
|
||||
@Override
|
||||
public String formatValue(@Nullable Var var) throws IllegalArgumentException {
|
||||
return var != null ? var.getName() : "";
|
||||
}
|
||||
});
|
||||
|
||||
mr.addMessage(new AndroidMessage(R.string.c_simplify_instead_of_numeric, MessageType.info, undefinedVars));
|
||||
}
|
||||
}*/
|
||||
|
||||
final String jsclExpression = sb.toString();
|
||||
|
||||
final MutableObject<Generic> calculationResult = new MutableObject<Generic>(null);
|
||||
final MutableObject<CalculatorParseException> parseException = new MutableObject<CalculatorParseException>(null);
|
||||
final MutableObject<CalculatorEvalException> evalException = new MutableObject<CalculatorEvalException>(null);
|
||||
final MutableObject<Thread> calculationThread = new MutableObject<Thread>(null);
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final Thread thread = Thread.currentThread();
|
||||
try {
|
||||
//Log.d(CalculatorEngine.class.getName(), "Calculation thread started work: " + thread.getName());
|
||||
//System.out.println(jsclExpression);
|
||||
calculationThread.setObject(thread);
|
||||
final Generic genericResult = operation.evaluateGeneric(jsclExpression);
|
||||
|
||||
// NOTE: toString() method must be called here as ArithmeticOperationException may occur in it (just to avoid later check!)
|
||||
genericResult.toString();
|
||||
|
||||
calculationResult.setObject(genericResult);
|
||||
} catch (AbstractJsclArithmeticException e) {
|
||||
evalException.setObject(new CalculatorEvalException(e, e, jsclExpression));
|
||||
} catch (ArithmeticException e) {
|
||||
//System.out.println(e.getMessage());
|
||||
parseException.setObject(new CalculatorParseException(Messages.msg_1, jsclExpression, e.getMessage()));
|
||||
} catch (StackOverflowError e) {
|
||||
//System.out.println(StringUtils.fromStackTrace(e.getStackTrace()));
|
||||
parseException.setObject(new CalculatorParseException(Messages.msg_2, jsclExpression));
|
||||
} catch (jscl.text.ParseException e) {
|
||||
//System.out.println(e.getMessage());
|
||||
parseException.setObject(new CalculatorParseException(e));
|
||||
} catch (ParseInterruptedException e) {
|
||||
//System.out.println(e.getMessage());
|
||||
// do nothing - we ourselves interrupt the calculations
|
||||
} finally {
|
||||
//Log.d(CalculatorEngine.class.getName(), "Calculation thread ended work: " + thread.getName());
|
||||
calculationThread.setObject(null);
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
|
||||
try {
|
||||
//Log.d(CalculatorEngine.class.getName(), "Main thread is waiting: " + Thread.currentThread().getName());
|
||||
latch.await(timeout, TimeUnit.SECONDS);
|
||||
//Log.d(CalculatorEngine.class.getName(), "Main thread got up: " + Thread.currentThread().getName());
|
||||
|
||||
final CalculatorParseException parseExceptionObject = parseException.getObject();
|
||||
final CalculatorEvalException evalExceptionObject = evalException.getObject();
|
||||
final Object calculationResultLocal = calculationResult.getObject();
|
||||
final Thread calculationThreadLocal = calculationThread.getObject();
|
||||
|
||||
if (calculationThreadLocal != null) {
|
||||
// todo serso: interrupt doesn't stop the thread but it MUST be killed
|
||||
threadKiller.killThread(calculationThreadLocal);
|
||||
//calculationThreadLocal.stop();
|
||||
}
|
||||
|
||||
if (parseExceptionObject != null || evalExceptionObject != null) {
|
||||
if (operation == JsclOperation.numeric &&
|
||||
(preparedExpression.isExistsUndefinedVar() || (evalExceptionObject != null && evalExceptionObject.getCause() instanceof NumeralBaseException))) {
|
||||
return evaluate(JsclOperation.simplify, expression, mr);
|
||||
}
|
||||
|
||||
if (parseExceptionObject != null) {
|
||||
throw parseExceptionObject;
|
||||
} else {
|
||||
throw evalExceptionObject;
|
||||
}
|
||||
}
|
||||
|
||||
if (calculationResultLocal == null) {
|
||||
throw new CalculatorParseException(Messages.msg_3, jsclExpression);
|
||||
}
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
throw new CalculatorParseException(Messages.msg_4, jsclExpression);
|
||||
}
|
||||
|
||||
final Generic genericResult = calculationResult.getObject();
|
||||
|
||||
return new Result(operation.getFromProcessor().process(genericResult), operation, genericResult);
|
||||
}
|
||||
}
|
||||
|
||||
public void setPrecision(int precision) {
|
||||
this.getEngine().setPrecision(precision);
|
||||
}
|
||||
|
||||
public void setRoundResult(boolean roundResult) {
|
||||
this.getEngine().setRoundResult(roundResult);
|
||||
}
|
||||
|
||||
public void init(@Nullable Context context, @Nullable SharedPreferences preferences) {
|
||||
synchronized (lock) {
|
||||
reset(context, preferences);
|
||||
}
|
||||
}
|
||||
|
||||
public void reset(@Nullable Context context, @Nullable SharedPreferences preferences) {
|
||||
synchronized (lock) {
|
||||
softReset(context, preferences);
|
||||
|
||||
varsRegistry.load(context, preferences);
|
||||
functionsRegistry.load(context, preferences);
|
||||
operatorsRegistry.load(context, preferences);
|
||||
postfixFunctionsRegistry.load(context, preferences);
|
||||
}
|
||||
}
|
||||
|
||||
public void softReset(@Nullable Context context, @Nullable SharedPreferences preferences) {
|
||||
synchronized (lock) {
|
||||
if (preferences != null) {
|
||||
this.setPrecision(Preferences.precision.getPreference(preferences));
|
||||
this.setRoundResult(Preferences.roundResult.getPreference(preferences));
|
||||
this.setAngleUnits(getAngleUnitsFromPrefs(preferences));
|
||||
this.setNumeralBase(getNumeralBaseFromPrefs(preferences));
|
||||
this.setMultiplicationSign(Preferences.multiplicationSign.getPreference(preferences));
|
||||
this.setScienceNotation(Preferences.scienceNotation.getPreference(preferences));
|
||||
this.setTimeout(Preferences.maxCalculationTime.getPreference(preferences));
|
||||
|
||||
final String groupingSeparator = Preferences.groupingSeparator.getPreference(preferences);
|
||||
if (StringUtils.isEmpty(groupingSeparator)) {
|
||||
this.getEngine().setUseGroupingSeparator(false);
|
||||
} else {
|
||||
this.getEngine().setUseGroupingSeparator(true);
|
||||
this.getEngine().setGroupingSeparator(groupingSeparator.charAt(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
public NumeralBase getNumeralBaseFromPrefs(@NotNull SharedPreferences preferences) {
|
||||
return Preferences.numeralBase.getPreference(preferences);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public AngleUnit getAngleUnitsFromPrefs(@NotNull SharedPreferences preferences) {
|
||||
return Preferences.angleUnit.getPreference(preferences);
|
||||
}
|
||||
|
||||
//for tests only
|
||||
void setDecimalGroupSymbols(@NotNull DecimalFormatSymbols decimalGroupSymbols) {
|
||||
synchronized (lock) {
|
||||
this.getEngine().setDecimalGroupSymbols(decimalGroupSymbols);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public AndroidMathRegistry<IConstant> getVarsRegistry() {
|
||||
return varsRegistry;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public AndroidMathRegistry<Function> getFunctionsRegistry() {
|
||||
return functionsRegistry;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public AndroidMathRegistry<Operator> getOperatorsRegistry() {
|
||||
return operatorsRegistry;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public AndroidMathRegistry<Operator> getPostfixFunctionsRegistry() {
|
||||
return postfixFunctionsRegistry;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public MathEngine getEngine() {
|
||||
return engine;
|
||||
}
|
||||
|
||||
// package protected for tests
|
||||
void setTimeout(int timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
public void setAngleUnits(@NotNull AngleUnit angleUnits) {
|
||||
getEngine().setAngleUnits(angleUnits);
|
||||
}
|
||||
|
||||
public void setScienceNotation(boolean scienceNotation) {
|
||||
getEngine().setScienceNotation(scienceNotation);
|
||||
}
|
||||
|
||||
public void setNumeralBase(@NotNull NumeralBase numeralBase) {
|
||||
getEngine().setNumeralBase(numeralBase);
|
||||
}
|
||||
|
||||
// for tests only
|
||||
void setThreadKiller(@NotNull ThreadKiller threadKiller) {
|
||||
this.threadKiller = threadKiller;
|
||||
}
|
||||
|
||||
private static interface ThreadKiller {
|
||||
void killThread(@NotNull Thread thread);
|
||||
}
|
||||
|
||||
private static class AndroidThreadKiller implements ThreadKiller {
|
||||
@Override
|
||||
public void killThread(@NotNull Thread thread) {
|
||||
thread.setPriority(Thread.MIN_PRIORITY);
|
||||
thread.interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
public static class ThreadKillerImpl implements ThreadKiller {
|
||||
@Override
|
||||
public void killThread(@NotNull Thread thread) {
|
||||
thread.setPriority(Thread.MIN_PRIORITY);
|
||||
thread.stop();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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.calculator.model;
|
||||
|
||||
import jscl.AbstractJsclArithmeticException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.solovyev.common.exceptions.SersoException;
|
||||
import org.solovyev.common.msg.Message;
|
||||
import org.solovyev.common.msg.MessageType;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/8/11
|
||||
* Time: 1:27 AM
|
||||
*/
|
||||
public class CalculatorEvalException extends SersoException implements Message {
|
||||
|
||||
@NotNull
|
||||
private final Message message;
|
||||
|
||||
@NotNull
|
||||
private final String expression;
|
||||
|
||||
public CalculatorEvalException(@NotNull Message message, @NotNull Throwable cause, String expression) {
|
||||
super(cause);
|
||||
this.message = message;
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
public String getExpression() {
|
||||
return expression;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getMessageCode() {
|
||||
return this.message.getMessageCode();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<Object> getParameters() {
|
||||
return this.message.getParameters();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public MessageType getMessageType() {
|
||||
return this.message.getMessageType();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getLocalizedMessage() {
|
||||
return this.message.getLocalizedMessage(Locale.getDefault());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getLocalizedMessage(@NotNull Locale locale) {
|
||||
return this.message.getLocalizedMessage(locale);
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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.calculator.model;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.msg.AndroidMessage;
|
||||
import org.solovyev.common.exceptions.SersoException;
|
||||
import org.solovyev.common.msg.Message;
|
||||
import org.solovyev.common.msg.MessageType;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 10/6/11
|
||||
* Time: 9:25 PM
|
||||
*/
|
||||
public class CalculatorParseException extends SersoException implements Message {
|
||||
|
||||
@NotNull
|
||||
private final Message message;
|
||||
|
||||
@NotNull
|
||||
private final String expression;
|
||||
|
||||
@Nullable
|
||||
private final Integer position;
|
||||
|
||||
public CalculatorParseException(@NotNull jscl.text.ParseException jsclParseException) {
|
||||
this.message = jsclParseException;
|
||||
this.expression = jsclParseException.getExpression();
|
||||
this.position = jsclParseException.getPosition();
|
||||
}
|
||||
|
||||
public CalculatorParseException(@NotNull String messageId, @Nullable Integer position, @NotNull String expression, Object... parameters) {
|
||||
this.message = new AndroidMessage(messageId, MessageType.error, parameters);
|
||||
this.expression = expression;
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
public CalculatorParseException(@NotNull String messageId, @NotNull String expression, Object... parameters) {
|
||||
this(messageId, null, expression, parameters);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getExpression() {
|
||||
return expression;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getMessageCode() {
|
||||
return this.message.getMessageCode();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<Object> getParameters() {
|
||||
return this.message.getParameters();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public MessageType getMessageType() {
|
||||
return this.message.getMessageType();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String getLocalizedMessage() {
|
||||
return this.message.getLocalizedMessage(Locale.getDefault());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getLocalizedMessage(@NotNull Locale locale) {
|
||||
return this.message.getLocalizedMessage(locale);
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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.calculator.model;
|
||||
|
||||
import jscl.math.Generic;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 10/18/11
|
||||
* Time: 10:39 PM
|
||||
*/
|
||||
public enum DummyTextProcessor implements TextProcessor<String, Generic> {
|
||||
|
||||
instance;
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String process(@NotNull Generic s) throws CalculatorParseException {
|
||||
return s.toString();
|
||||
}
|
||||
}
|
@@ -0,0 +1,92 @@
|
||||
package org.solovyev.android.calculator.model;
|
||||
|
||||
import jscl.math.Generic;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.calculator.math.MathType;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 10/20/11
|
||||
* Time: 2:59 PM
|
||||
*/
|
||||
public class FromJsclSimplifyTextProcessor implements TextProcessor<String, Generic> {
|
||||
|
||||
public static final FromJsclSimplifyTextProcessor instance = new FromJsclSimplifyTextProcessor();
|
||||
|
||||
public FromJsclSimplifyTextProcessor() {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String process(@NotNull Generic from) throws CalculatorParseException {
|
||||
return removeMultiplicationSigns(from.toString());
|
||||
}
|
||||
|
||||
public String process(@NotNull String s) {
|
||||
return removeMultiplicationSigns(s);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String removeMultiplicationSigns(String s) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
MathType.Result mathTypeBefore;
|
||||
MathType.Result mathType = null;
|
||||
MathType.Result mathTypeAfter = null;
|
||||
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
mathTypeBefore = mathType;
|
||||
if (mathTypeAfter == null) {
|
||||
mathType = MathType.getType(s, i, false);
|
||||
} else {
|
||||
mathType = mathTypeAfter;
|
||||
}
|
||||
|
||||
char ch = s.charAt(i);
|
||||
if (ch == '*') {
|
||||
if (i + 1 < s.length()) {
|
||||
mathTypeAfter = MathType.getType(s, i + 1, false);
|
||||
} else {
|
||||
mathTypeAfter = null;
|
||||
}
|
||||
|
||||
if (needMultiplicationSign(mathTypeBefore == null ? null : mathTypeBefore.getMathType(), mathTypeAfter == null ? null : mathTypeAfter.getMathType())) {
|
||||
sb.append(CalculatorEngine.instance.getMultiplicationSign());
|
||||
}
|
||||
|
||||
} else {
|
||||
if (mathType.getMathType() == MathType.constant || mathType.getMathType() == MathType.function || mathType.getMathType() == MathType.operator) {
|
||||
sb.append(mathType.getMatch());
|
||||
i += mathType.getMatch().length() - 1;
|
||||
} else {
|
||||
sb.append(ch);
|
||||
}
|
||||
mathTypeAfter = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private final List<MathType> mathTypes = Arrays.asList(MathType.function, MathType.constant);
|
||||
|
||||
private boolean needMultiplicationSign(@Nullable MathType mathTypeBefore, @Nullable MathType mathTypeAfter) {
|
||||
if (mathTypeBefore == null || mathTypeAfter == null) {
|
||||
return true;
|
||||
} else if (mathTypes.contains(mathTypeBefore) || mathTypes.contains(mathTypeAfter)) {
|
||||
return false;
|
||||
} else if ( mathTypeBefore == MathType.close_group_symbol ) {
|
||||
return false;
|
||||
} else if ( mathTypeAfter == MathType.open_group_symbol ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
package org.solovyev.android.calculator.model;
|
||||
|
||||
import jscl.math.function.CustomFunction;
|
||||
import jscl.math.function.Function;
|
||||
import jscl.math.function.IConstant;
|
||||
import org.simpleframework.xml.ElementList;
|
||||
import org.simpleframework.xml.Root;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/22/11
|
||||
* Time: 5:15 PM
|
||||
*/
|
||||
@Root
|
||||
public class Functions implements MathEntityPersistenceContainer<AFunction> {
|
||||
|
||||
@ElementList(type = CustomFunction.class)
|
||||
private List<AFunction> functions = new ArrayList<AFunction>();
|
||||
|
||||
public Functions() {
|
||||
}
|
||||
|
||||
public List<AFunction> getEntities() {
|
||||
return functions;
|
||||
}
|
||||
}
|
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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.calculator.model;
|
||||
|
||||
import jscl.MathEngine;
|
||||
import jscl.NumeralBase;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.solovyev.android.calculator.math.MathType;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/15/11
|
||||
* Time: 8:33 PM
|
||||
*/
|
||||
|
||||
public class LiteNumberBuilder extends AbstractNumberBuilder {
|
||||
|
||||
public LiteNumberBuilder(@NotNull MathEngine engine) {
|
||||
super(engine);
|
||||
this.nb = engine.getNumeralBase();
|
||||
}
|
||||
|
||||
public void process(@NotNull MathType.Result mathTypeResult) {
|
||||
if (canContinue(mathTypeResult)) {
|
||||
// let's continue building number
|
||||
if (numberBuilder == null) {
|
||||
// if new number => create new builder
|
||||
numberBuilder = new StringBuilder();
|
||||
}
|
||||
|
||||
if (mathTypeResult.getMathType() != MathType.numeral_base) {
|
||||
// just add matching string
|
||||
numberBuilder.append(mathTypeResult.getMatch());
|
||||
} else {
|
||||
// set explicitly numeral base (do not include it into number)
|
||||
nb = NumeralBase.getByPrefix(mathTypeResult.getMatch());
|
||||
}
|
||||
|
||||
} else {
|
||||
// process current number (and go to the next one)
|
||||
if (numberBuilder != null) {
|
||||
numberBuilder = null;
|
||||
|
||||
// must set default numeral base (exit numeral base mode)
|
||||
nb = engine.getNumeralBase();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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.calculator.model;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.common.definitions.IBuilder;
|
||||
import org.solovyev.common.math.MathEntity;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/22/11
|
||||
* Time: 9:21 PM
|
||||
*/
|
||||
public interface MathEntityBuilder<T extends MathEntity> extends IBuilder<T> {
|
||||
|
||||
@NotNull
|
||||
public MathEntityBuilder<T> setName(@NotNull String name);
|
||||
|
||||
@NotNull
|
||||
public MathEntityBuilder<T> setDescription(@Nullable String description);
|
||||
|
||||
@NotNull
|
||||
public MathEntityBuilder<T> setValue(@Nullable String value);
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
package org.solovyev.android.calculator.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/22/11
|
||||
* Time: 5:03 PM
|
||||
*/
|
||||
public interface MathEntityPersistenceContainer<T extends MathPersistenceEntity> {
|
||||
public List<T> getEntities();
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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.calculator.model;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/22/11
|
||||
* Time: 5:27 PM
|
||||
*/
|
||||
public interface MathPersistenceEntity {
|
||||
|
||||
@NotNull
|
||||
String getName();
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
package org.solovyev.android.calculator.model;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 11/25/11
|
||||
* Time: 1:40 PM
|
||||
*/
|
||||
public final class Messages {
|
||||
|
||||
|
||||
// not intended for instantiation
|
||||
private Messages() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
/** Arithmetic error occurred: {0} */
|
||||
public static final String msg_1 = "msg_1";
|
||||
|
||||
/** Too complex expression */
|
||||
public static final String msg_2 = "msg_2";
|
||||
|
||||
/** Too long execution time - check the expression */
|
||||
public static final String msg_3 = "msg_3";
|
||||
|
||||
/** Evaluation was cancelled */
|
||||
public static final String msg_4 = "msg_4";
|
||||
|
||||
/** No parameters are specified for function: {0} */
|
||||
public static final String msg_5 = "msg_5";
|
||||
|
||||
/** Infinite loop is detected in expression */
|
||||
public static final String msg_6 = "msg_6";
|
||||
|
||||
}
|
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* 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.calculator.model;
|
||||
|
||||
import jscl.MathContext;
|
||||
import jscl.MathEngine;
|
||||
import jscl.NumeralBase;
|
||||
import jscl.math.function.IConstant;
|
||||
import jscl.math.numeric.Real;
|
||||
import jscl.text.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.calculator.math.MathType;
|
||||
import org.solovyev.common.utils.CollectionsUtils;
|
||||
import org.solovyev.common.utils.Finder;
|
||||
import org.solovyev.common.utils.MutableObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 10/23/11
|
||||
* Time: 2:57 PM
|
||||
*/
|
||||
public class NumberBuilder extends AbstractNumberBuilder {
|
||||
|
||||
public NumberBuilder(@NotNull MathEngine engine) {
|
||||
super(engine);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method replaces number in text according to some rules (e.g. formatting)
|
||||
*
|
||||
* @param text text where number can be replaced
|
||||
* @param mathTypeResult math type result of current token
|
||||
* @param offset offset between new number length and old number length (newNumberLength - oldNumberLength)
|
||||
*
|
||||
*
|
||||
* @return new math type result (as one can be changed due to substituting of number with constant)
|
||||
*/
|
||||
@NotNull
|
||||
public MathType.Result process(@NotNull StringBuilder text, @NotNull MathType.Result mathTypeResult, @Nullable MutableObject<Integer> offset) {
|
||||
final MathType.Result possibleResult;
|
||||
if (canContinue(mathTypeResult)) {
|
||||
// let's continue building number
|
||||
if (numberBuilder == null) {
|
||||
// if new number => create new builder
|
||||
numberBuilder = new StringBuilder();
|
||||
}
|
||||
|
||||
if (mathTypeResult.getMathType() != MathType.numeral_base) {
|
||||
// just add matching string
|
||||
numberBuilder.append(mathTypeResult.getMatch());
|
||||
} else {
|
||||
// set explicitly numeral base (do not include it into number)
|
||||
nb = NumeralBase.getByPrefix(mathTypeResult.getMatch());
|
||||
}
|
||||
|
||||
possibleResult = null;
|
||||
} else {
|
||||
// process current number (and go to the next one)
|
||||
possibleResult = processNumber(text, offset);
|
||||
}
|
||||
|
||||
return possibleResult == null ? mathTypeResult : possibleResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method replaces number in text according to some rules (e.g. formatting)
|
||||
*
|
||||
* @param text text where number can be replaced
|
||||
* @param offset offset between new number length and old number length (newNumberLength - oldNumberLength)
|
||||
*
|
||||
* @return new math type result (as one can be changed due to substituting of number with constant)
|
||||
*/
|
||||
@Nullable
|
||||
public MathType.Result processNumber(@NotNull StringBuilder text, @Nullable MutableObject<Integer> offset) {
|
||||
// total number of trimmed chars
|
||||
int trimmedChars = 0;
|
||||
|
||||
String number = null;
|
||||
|
||||
// save numeral base (as later it might be replaced)
|
||||
final NumeralBase localNb = getNumeralBase();
|
||||
|
||||
if (numberBuilder != null) {
|
||||
try {
|
||||
number = numberBuilder.toString();
|
||||
|
||||
// let's get rid of unnecessary characters (grouping separators, + after E)
|
||||
final List<String> tokens = new ArrayList<String>();
|
||||
tokens.addAll(MathType.grouping_separator.getTokens());
|
||||
// + after E can be omitted: 10+E = 10E (NOTE: - cannot be omitted )
|
||||
tokens.add("+");
|
||||
for (String groupingSeparator : tokens) {
|
||||
final String trimmedNumber = number.replace(groupingSeparator, "");
|
||||
trimmedChars += number.length() - trimmedNumber.length();
|
||||
number = trimmedNumber;
|
||||
}
|
||||
|
||||
// check if number still valid
|
||||
toDouble(number, getNumeralBase(), engine);
|
||||
|
||||
} catch (NumberFormatException e) {
|
||||
// number is not valid => stop
|
||||
number = null;
|
||||
}
|
||||
|
||||
numberBuilder = null;
|
||||
|
||||
// must set default numeral base (exit numeral base mode)
|
||||
nb = engine.getNumeralBase();
|
||||
}
|
||||
|
||||
return replaceNumberInText(text, number, trimmedChars, offset, localNb, engine);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static MathType.Result replaceNumberInText(@NotNull StringBuilder text,
|
||||
@Nullable String number,
|
||||
int trimmedChars,
|
||||
@Nullable MutableObject<Integer> offset,
|
||||
@NotNull NumeralBase nb,
|
||||
@NotNull final MathEngine engine) {
|
||||
MathType.Result result = null;
|
||||
|
||||
if (number != null) {
|
||||
// in any case remove old number from text
|
||||
final int oldNumberLength = number.length() + trimmedChars;
|
||||
text.delete(text.length() - oldNumberLength, text.length());
|
||||
|
||||
final String newNumber = formatNumber(number, nb, engine);
|
||||
if (offset != null) {
|
||||
// register offset between old number and new number
|
||||
offset.setObject(newNumber.length() - oldNumberLength);
|
||||
}
|
||||
text.append(newNumber);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String formatNumber(@NotNull String number, @NotNull NumeralBase nb, @NotNull MathEngine engine) {
|
||||
String result;
|
||||
|
||||
int indexOfDot = number.indexOf('.');
|
||||
|
||||
if (indexOfDot < 0) {
|
||||
int indexOfE;
|
||||
if (nb == NumeralBase.hex) {
|
||||
indexOfE = -1;
|
||||
} else {
|
||||
indexOfE = number.indexOf(MathType.POWER_10);
|
||||
}
|
||||
if (indexOfE < 0) {
|
||||
result = engine.addGroupingSeparators(nb, number);
|
||||
} else {
|
||||
final String partBeforeE;
|
||||
if (indexOfE != 0) {
|
||||
partBeforeE = engine.addGroupingSeparators(nb, number.substring(0, indexOfE));
|
||||
} else {
|
||||
partBeforeE = "";
|
||||
}
|
||||
result = partBeforeE + number.substring(indexOfE);
|
||||
}
|
||||
} else {
|
||||
final String integerPart;
|
||||
if (indexOfDot != 0) {
|
||||
integerPart = engine.addGroupingSeparators(nb, number.substring(0, indexOfDot));
|
||||
} else {
|
||||
integerPart = "";
|
||||
}
|
||||
result = integerPart + number.substring(indexOfDot);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Double toDouble(@NotNull String s, @NotNull NumeralBase nb, @NotNull final MathContext mc) throws NumberFormatException {
|
||||
final NumeralBase defaultNb = mc.getNumeralBase();
|
||||
try {
|
||||
mc.setNumeralBase(nb);
|
||||
|
||||
try {
|
||||
return JsclIntegerParser.parser.parse(Parser.Parameters.newInstance(s, new MutableInt(0), mc), null).content().doubleValue();
|
||||
} catch (ParseException e) {
|
||||
try {
|
||||
return ((Real) DoubleParser.parser.parse(Parser.Parameters.newInstance(s, new MutableInt(0), mc), null).content()).doubleValue();
|
||||
} catch (ParseException e1) {
|
||||
throw new NumberFormatException();
|
||||
}
|
||||
}
|
||||
|
||||
} finally {
|
||||
mc.setNumeralBase(defaultNb);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.calculator.model;
|
||||
|
||||
import jscl.math.function.IConstant;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 10/18/11
|
||||
* Time: 10:07 PM
|
||||
*/
|
||||
public class PreparedExpression implements CharSequence{
|
||||
|
||||
@NotNull
|
||||
private String expression;
|
||||
|
||||
@NotNull
|
||||
private List<IConstant> undefinedVars;
|
||||
|
||||
public PreparedExpression(@NotNull String expression, @NotNull List<IConstant> undefinedVars) {
|
||||
this.expression = expression;
|
||||
this.undefinedVars = undefinedVars;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getExpression() {
|
||||
return expression;
|
||||
}
|
||||
|
||||
public boolean isExistsUndefinedVar() {
|
||||
return !this.undefinedVars.isEmpty();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<IConstant> getUndefinedVars() {
|
||||
return undefinedVars;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int length() {
|
||||
return expression.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public char charAt(int i) {
|
||||
return expression.charAt(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence subSequence(int i, int i1) {
|
||||
return expression.subSequence(i, i1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.expression;
|
||||
}
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
package org.solovyev.android.calculator.model;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 9/26/11
|
||||
* Time: 12:12 PM
|
||||
*/
|
||||
public interface TextProcessor<TO extends CharSequence, FROM> {
|
||||
|
||||
@NotNull
|
||||
TO process(@NotNull FROM from) throws CalculatorParseException;
|
||||
}
|
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* 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.calculator.model;
|
||||
|
||||
import jscl.math.function.IConstant;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.solovyev.common.StartsWithFinder;
|
||||
import org.solovyev.android.calculator.math.MathType;
|
||||
import org.solovyev.common.utils.CollectionsUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class ToJsclTextProcessor implements TextProcessor<PreparedExpression, String> {
|
||||
|
||||
@NotNull
|
||||
private static final Integer MAX_DEPTH = 20;
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public PreparedExpression process(@NotNull String s) throws CalculatorParseException {
|
||||
return processWithDepth(s, 0, new ArrayList<IConstant>());
|
||||
}
|
||||
|
||||
private static PreparedExpression processWithDepth(@NotNull String s, int depth, @NotNull List<IConstant> undefinedVars) throws CalculatorParseException {
|
||||
return replaceVariables(processExpression(s).toString(), depth, undefinedVars);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static StringBuilder processExpression(@NotNull String s) throws CalculatorParseException {
|
||||
final StartsWithFinder startsWithFinder = new StartsWithFinder(s, 0);
|
||||
final StringBuilder result = new StringBuilder();
|
||||
|
||||
MathType.Result mathTypeResult = null;
|
||||
MathType.Result mathTypeBefore;
|
||||
|
||||
final LiteNumberBuilder nb = new LiteNumberBuilder(CalculatorEngine.instance.getEngine());
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
if (s.charAt(i) == ' ') continue;
|
||||
startsWithFinder.setI(i);
|
||||
|
||||
mathTypeBefore = mathTypeResult == null ? null : mathTypeResult;
|
||||
|
||||
mathTypeResult = MathType.getType(s, i, nb.isHexMode());
|
||||
|
||||
nb.process(mathTypeResult);
|
||||
|
||||
if (mathTypeBefore != null) {
|
||||
|
||||
final MathType current = mathTypeResult.getMathType();
|
||||
|
||||
if (current.isNeedMultiplicationSignBefore(mathTypeBefore.getMathType())) {
|
||||
result.append("*");
|
||||
}
|
||||
}
|
||||
|
||||
if (mathTypeBefore != null &&
|
||||
(mathTypeBefore.getMathType() == MathType.function || mathTypeBefore.getMathType() == MathType.operator) &&
|
||||
CollectionsUtils.find(MathType.openGroupSymbols, startsWithFinder) != null) {
|
||||
throw new CalculatorParseException(Messages.msg_5, i, s, mathTypeBefore.getMatch());
|
||||
}
|
||||
|
||||
i = mathTypeResult.processToJscl(result, i);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static PreparedExpression replaceVariables(@NotNull final String s, int depth, @NotNull List<IConstant> undefinedVars) throws CalculatorParseException {
|
||||
if (depth >= MAX_DEPTH) {
|
||||
throw new CalculatorParseException(Messages.msg_6, s);
|
||||
} else {
|
||||
depth++;
|
||||
}
|
||||
|
||||
final StartsWithFinder startsWithFinder = new StartsWithFinder(s, 0);
|
||||
|
||||
final StringBuilder result = new StringBuilder();
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
startsWithFinder.setI(i);
|
||||
|
||||
int offset = 0;
|
||||
String functionName = CollectionsUtils.find(MathType.function.getTokens(), startsWithFinder);
|
||||
if (functionName == null) {
|
||||
String operatorName = CollectionsUtils.find(MathType.operator.getTokens(), startsWithFinder);
|
||||
if (operatorName == null) {
|
||||
String varName = CollectionsUtils.find(CalculatorEngine.instance.getVarsRegistry().getNames(), startsWithFinder);
|
||||
if (varName != null) {
|
||||
final IConstant var = CalculatorEngine.instance.getVarsRegistry().get(varName);
|
||||
if (var != null) {
|
||||
if (!var.isDefined()) {
|
||||
undefinedVars.add(var);
|
||||
result.append(varName);
|
||||
offset = varName.length();
|
||||
} else {
|
||||
final String value = var.getValue();
|
||||
assert value != null;
|
||||
|
||||
if ( var.getDoubleValue() != null ) {
|
||||
//result.append(value);
|
||||
// NOTE: append varName as JSCL engine will convert it to double if needed
|
||||
result.append(varName);
|
||||
} else {
|
||||
result.append("(").append(processWithDepth(value, depth, undefinedVars)).append(")");
|
||||
}
|
||||
offset = varName.length();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result.append(operatorName);
|
||||
offset = operatorName.length();
|
||||
}
|
||||
} else {
|
||||
result.append(functionName);
|
||||
offset = functionName.length();
|
||||
}
|
||||
|
||||
|
||||
if (offset == 0) {
|
||||
result.append(s.charAt(i));
|
||||
} else {
|
||||
i += offset - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return new PreparedExpression(result.toString(), undefinedVars);
|
||||
}
|
||||
}
|
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
* 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.calculator.model;
|
||||
|
||||
import jscl.math.function.Constant;
|
||||
import jscl.math.function.ExtendedConstant;
|
||||
import jscl.math.function.IConstant;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.simpleframework.xml.Element;
|
||||
import org.simpleframework.xml.Root;
|
||||
import org.simpleframework.xml.Transient;
|
||||
import org.solovyev.common.definitions.IBuilder;
|
||||
import org.solovyev.common.math.MathEntity;
|
||||
import org.solovyev.common.utils.StringUtils;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 9/28/11
|
||||
* Time: 11:22 PM
|
||||
*/
|
||||
|
||||
@Root
|
||||
public class Var implements IConstant, MathPersistenceEntity {
|
||||
|
||||
@Transient
|
||||
private Integer id;
|
||||
|
||||
@Element
|
||||
@NotNull
|
||||
private String name;
|
||||
|
||||
@Element(required = false)
|
||||
@Nullable
|
||||
private String value;
|
||||
|
||||
@Element
|
||||
private boolean system;
|
||||
|
||||
@Element(required = false)
|
||||
@Nullable
|
||||
private String description;
|
||||
|
||||
@Transient
|
||||
private Constant constant;
|
||||
|
||||
public static class Builder implements IBuilder<Var>, MathEntityBuilder<Var> {
|
||||
|
||||
@NotNull
|
||||
private String name;
|
||||
|
||||
@Nullable
|
||||
private String value;
|
||||
|
||||
private boolean system = false;
|
||||
|
||||
@Nullable
|
||||
private String description;
|
||||
|
||||
@Nullable
|
||||
private Integer id;
|
||||
|
||||
public Builder() {
|
||||
}
|
||||
|
||||
public Builder(@NotNull Var var) {
|
||||
this.name = var.name;
|
||||
this.value = var.value;
|
||||
this.system = var.system;
|
||||
this.description = var.description;
|
||||
this.id = var.id;
|
||||
}
|
||||
|
||||
public Builder(@NotNull IConstant iConstant) {
|
||||
this.name = iConstant.getName();
|
||||
|
||||
this.value = iConstant.getValue();
|
||||
|
||||
this.system = iConstant.isSystem();
|
||||
this.description = iConstant.getDescription();
|
||||
if (iConstant.isIdDefined()) {
|
||||
this.id = iConstant.getId();
|
||||
}
|
||||
}
|
||||
|
||||
public Builder(@NotNull String name, @NotNull Double value) {
|
||||
this(name, String.valueOf(value));
|
||||
}
|
||||
|
||||
public Builder(@NotNull String name, @Nullable String value) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
public Builder setName(@NotNull String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Builder setValue(@Nullable String value) {
|
||||
this.value = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
protected Builder setSystem(boolean system) {
|
||||
this.system = system;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Builder setDescription(@Nullable String description) {
|
||||
this.description = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Var create() {
|
||||
final Var result;
|
||||
if (id != null) {
|
||||
result = new Var(id);
|
||||
} else {
|
||||
result = new Var();
|
||||
}
|
||||
|
||||
result.name = name;
|
||||
result.value = value;
|
||||
result.system = system;
|
||||
result.description = description;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private Var() {
|
||||
}
|
||||
|
||||
private Var(@NotNull Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public void copy(@NotNull MathEntity o) {
|
||||
if (o instanceof IConstant) {
|
||||
final IConstant that = ((IConstant) o);
|
||||
this.name = that.getName();
|
||||
this.value = that.getValue();
|
||||
this.description = that.getDescription();
|
||||
this.system = that.isSystem();
|
||||
if (that.isIdDefined()) {
|
||||
this.id = that.getId();
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Trying to make a copy of unsupported type: " + o.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Double getDoubleValue() {
|
||||
Double result = null;
|
||||
if (value != null) {
|
||||
try {
|
||||
result = Double.valueOf(value);
|
||||
} catch (NumberFormatException e) {
|
||||
// do nothing - string is not a double
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String toJava() {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
public boolean isSystem() {
|
||||
return system;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Integer getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIdDefined() {
|
||||
return this.id != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setId(@NotNull Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Constant getConstant() {
|
||||
if (constant == null) {
|
||||
constant = new Constant(this.name);
|
||||
}
|
||||
return constant;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDefined() {
|
||||
return !StringUtils.isEmpty(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return ExtendedConstant.toString(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Var var = (Var) o;
|
||||
|
||||
if (!name.equals(var.name)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return name.hashCode();
|
||||
}
|
||||
}
|
@@ -0,0 +1,61 @@
|
||||
package org.solovyev.android.calculator.model;
|
||||
|
||||
import jscl.math.function.IConstant;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.solovyev.android.calculator.R;
|
||||
import org.solovyev.common.utils.CollectionsUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/22/11
|
||||
* Time: 4:25 PM
|
||||
*/
|
||||
public enum VarCategory {
|
||||
|
||||
system(R.string.c_var_system, 100){
|
||||
@Override
|
||||
boolean isInCategory(@NotNull IConstant var) {
|
||||
return var.isSystem();
|
||||
}
|
||||
},
|
||||
|
||||
my(R.string.c_var_my, 0) {
|
||||
@Override
|
||||
boolean isInCategory(@NotNull IConstant var) {
|
||||
return !var.isSystem();
|
||||
}
|
||||
};
|
||||
|
||||
private final int captionId;
|
||||
|
||||
private final int tabOrder;
|
||||
|
||||
VarCategory(int captionId, int tabOrder) {
|
||||
this.captionId = captionId;
|
||||
this.tabOrder = tabOrder;
|
||||
}
|
||||
|
||||
public int getCaptionId() {
|
||||
return captionId;
|
||||
}
|
||||
|
||||
abstract boolean isInCategory(@NotNull IConstant var);
|
||||
|
||||
@NotNull
|
||||
public static List<VarCategory> getCategoriesByTabOrder() {
|
||||
final List<VarCategory> result = CollectionsUtils.asList(VarCategory.values());
|
||||
|
||||
Collections.sort(result, new Comparator<VarCategory>() {
|
||||
@Override
|
||||
public int compare(VarCategory category, VarCategory category1) {
|
||||
return category.tabOrder - category1.tabOrder;
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
package org.solovyev.android.calculator.model;
|
||||
|
||||
import jscl.math.function.IConstant;
|
||||
import org.simpleframework.xml.ElementList;
|
||||
import org.simpleframework.xml.Root;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 9/29/11
|
||||
* Time: 5:19 PM
|
||||
*/
|
||||
|
||||
@Root
|
||||
public class Vars implements MathEntityPersistenceContainer<Var> {
|
||||
|
||||
@ElementList(type = Var.class)
|
||||
private List<Var> vars = new ArrayList<Var>();
|
||||
|
||||
public Vars() {
|
||||
}
|
||||
|
||||
public List<Var> getEntities() {
|
||||
return vars;
|
||||
}
|
||||
}
|
@@ -0,0 +1,353 @@
|
||||
/*
|
||||
* 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.calculator.plot;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.widget.Toast;
|
||||
import jscl.math.Expression;
|
||||
import jscl.math.Generic;
|
||||
import jscl.math.function.Constant;
|
||||
import jscl.text.ParseException;
|
||||
import org.achartengine.ChartFactory;
|
||||
import org.achartengine.GraphicalView;
|
||||
import org.achartengine.chart.CubicLineChart;
|
||||
import org.achartengine.chart.PointStyle;
|
||||
import org.achartengine.chart.XYChart;
|
||||
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.achartengine.tools.PanListener;
|
||||
import org.achartengine.tools.ZoomEvent;
|
||||
import org.achartengine.tools.ZoomListener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.calculator.R;
|
||||
import org.solovyev.common.utils.MutableObject;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/1/11
|
||||
* Time: 12:40 AM
|
||||
*/
|
||||
public class CalculatorPlotActivity extends Activity {
|
||||
|
||||
private static final String TAG = CalculatorPlotActivity.class.getSimpleName();
|
||||
|
||||
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 = 200;
|
||||
|
||||
private XYChart chart;
|
||||
|
||||
/**
|
||||
* The encapsulated graphical view.
|
||||
*/
|
||||
private GraphicalView graphicalView;
|
||||
|
||||
@NotNull
|
||||
private Generic expression;
|
||||
|
||||
@NotNull
|
||||
private Constant variable;
|
||||
|
||||
@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);
|
||||
|
||||
final Object lastNonConfigurationInstance = getLastNonConfigurationInstance();
|
||||
setGraphicalView(lastNonConfigurationInstance instanceof PlotBoundaries ? (PlotBoundaries)lastNonConfigurationInstance : null);
|
||||
|
||||
} catch (ParseException e) {
|
||||
Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
} catch (ArithmeticException e) {
|
||||
Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
private void setGraphicalView(@Nullable PlotBoundaries plotBoundaries) {
|
||||
double minValue = plotBoundaries == null ? DEFAULT_MIN_NUMBER : plotBoundaries.xMin;
|
||||
double maxValue = plotBoundaries == null ? DEFAULT_MAX_NUMBER : plotBoundaries.xMax;
|
||||
|
||||
final ViewGroup graphContainer = (ViewGroup) findViewById(R.id.plot_view_container);
|
||||
|
||||
if (graphicalView != null) {
|
||||
graphContainer.removeView(graphicalView);
|
||||
}
|
||||
|
||||
chart = prepareChart(minValue, maxValue, expression, variable);
|
||||
|
||||
// reverting boundaries (as in prepareChart() we add some cached values )
|
||||
double minX = Double.MAX_VALUE;
|
||||
double minY = Double.MAX_VALUE;
|
||||
|
||||
double maxX = Double.MIN_VALUE;
|
||||
double maxY = Double.MIN_VALUE;
|
||||
|
||||
for (XYSeries series : chart.getDataset().getSeries()) {
|
||||
minX = Math.min(minX, series.getMinX());
|
||||
minY = Math.min(minY, series.getMinY());
|
||||
maxX = Math.max(maxX, series.getMaxX());
|
||||
maxY = Math.max(maxY, series.getMaxY());
|
||||
}
|
||||
|
||||
Log.d(CalculatorPlotActivity.class.getName(), "min x: " + minX + ", min y: " + minY + ", max x: " + maxX + ", max y: " + maxY);
|
||||
Log.d(CalculatorPlotActivity.class.getName(), "Plot boundaries are " + plotBoundaries);
|
||||
|
||||
|
||||
if (plotBoundaries == null) {
|
||||
chart.getRenderer().setXAxisMin(Math.max(minX, minValue));
|
||||
chart.getRenderer().setYAxisMin(Math.max(minY, minValue));
|
||||
chart.getRenderer().setXAxisMax(Math.min(maxX, maxValue));
|
||||
chart.getRenderer().setYAxisMax(Math.min(maxY, maxValue));
|
||||
} else {
|
||||
chart.getRenderer().setXAxisMin(plotBoundaries.xMin);
|
||||
chart.getRenderer().setYAxisMin(plotBoundaries.yMin);
|
||||
chart.getRenderer().setXAxisMax(plotBoundaries.xMax);
|
||||
chart.getRenderer().setYAxisMax(plotBoundaries.yMax);
|
||||
}
|
||||
|
||||
graphicalView = new GraphicalView(this, chart);
|
||||
|
||||
graphicalView.addZoomListener(new ZoomListener() {
|
||||
@Override
|
||||
public void zoomApplied(ZoomEvent e) {
|
||||
updateDataSets(chart);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void zoomReset() {
|
||||
updateDataSets(chart);
|
||||
}
|
||||
}, true, true);
|
||||
|
||||
graphicalView.addPanListener(new PanListener() {
|
||||
@Override
|
||||
public void panApplied() {
|
||||
Log.d(TAG, "org.achartengine.tools.PanListener.panApplied");
|
||||
updateDataSets(chart);
|
||||
}
|
||||
|
||||
});
|
||||
graphContainer.addView(graphicalView);
|
||||
|
||||
updateDataSets(chart, 50);
|
||||
}
|
||||
|
||||
|
||||
private void updateDataSets(@NotNull final XYChart chart) {
|
||||
updateDataSets(chart, EVAL_DELAY_MILLIS);
|
||||
}
|
||||
|
||||
private void updateDataSets(@NotNull final XYChart chart, long millisToWait) {
|
||||
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) {
|
||||
|
||||
Log.d(TAG, "org.solovyev.android.calculator.plot.CalculatorPlotActivity.updateDataSets");
|
||||
|
||||
final XYMultipleSeriesRenderer dr = chart.getRenderer();
|
||||
|
||||
//Log.d(CalculatorPlotActivity.class.getName(), "x = [" + dr.getXAxisMin() + ", " + dr.getXAxisMax() + "], y = [" + dr.getYAxisMin() + ", " + dr.getYAxisMax() + "]");
|
||||
|
||||
final MyXYSeries realSeries = (MyXYSeries)chart.getDataset().getSeriesAt(0);
|
||||
|
||||
final MyXYSeries imagSeries;
|
||||
if (chart.getDataset().getSeriesCount() > 1) {
|
||||
imagSeries = (MyXYSeries)chart.getDataset().getSeriesAt(1);
|
||||
} else {
|
||||
imagSeries = new MyXYSeries(getImagFunctionName(CalculatorPlotActivity.this.variable), DEFAULT_NUMBER_OF_STEPS * 2);
|
||||
}
|
||||
|
||||
try {
|
||||
if (PlotUtils.addXY(dr.getXAxisMin(), dr.getXAxisMax(), expression, variable, realSeries, imagSeries, true, DEFAULT_NUMBER_OF_STEPS)) {
|
||||
if (chart.getDataset().getSeriesCount() <= 1) {
|
||||
chart.getDataset().addSeries(imagSeries);
|
||||
chart.getRenderer().addSeriesRenderer(createImagRenderer());
|
||||
}
|
||||
}
|
||||
} catch (ArithmeticException e) {
|
||||
// todo serso: translate
|
||||
Toast.makeText(CalculatorPlotActivity.this, "Arithmetic error: " + e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
|
||||
CalculatorPlotActivity.this.finish();
|
||||
}
|
||||
|
||||
if (pendingOperation.getObject() == this) {
|
||||
graphicalView.repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
new Handler().postDelayed(pendingOperation.getObject(), millisToWait);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String getImagFunctionName(@NotNull Constant variable) {
|
||||
return "g(" + variable.getName() +")" + " = " + "Im(ƒ(" + variable.getName() +"))";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String getRealFunctionName(@NotNull Generic expression, @NotNull Constant variable) {
|
||||
return "ƒ(" + variable.getName() +")" + " = " + expression.toString();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private final static MutableObject<Runnable> pendingOperation = new MutableObject<Runnable>();
|
||||
|
||||
private static XYChart prepareChart(final double minValue, final double maxValue, @NotNull final Generic expression, @NotNull final Constant variable) {
|
||||
final MyXYSeries realSeries = new MyXYSeries(getRealFunctionName(expression, variable), DEFAULT_NUMBER_OF_STEPS * 2);
|
||||
final MyXYSeries imagSeries = new MyXYSeries(getImagFunctionName(variable), DEFAULT_NUMBER_OF_STEPS * 2);
|
||||
|
||||
boolean imagExists = PlotUtils.addXY(minValue, maxValue, expression, variable, realSeries, imagSeries, false, DEFAULT_NUMBER_OF_STEPS);
|
||||
|
||||
final XYMultipleSeriesDataset data = new XYMultipleSeriesDataset();
|
||||
data.addSeries(realSeries);
|
||||
if (imagExists) {
|
||||
data.addSeries(imagSeries);
|
||||
}
|
||||
|
||||
final XYMultipleSeriesRenderer renderer = new XYMultipleSeriesRenderer();
|
||||
renderer.setShowGrid(true);
|
||||
renderer.setXTitle(variable.getName());
|
||||
renderer.setYTitle("f(" + variable.getName() + ")");
|
||||
renderer.setChartTitleTextSize(20);
|
||||
|
||||
renderer.setZoomEnabled(true);
|
||||
renderer.setZoomButtonsVisible(true);
|
||||
|
||||
renderer.addSeriesRenderer(createCommonRenderer());
|
||||
if (imagExists) {
|
||||
renderer.addSeriesRenderer(createImagRenderer());
|
||||
}
|
||||
|
||||
return new CubicLineChart(data, renderer, 0.1f);
|
||||
}
|
||||
|
||||
private static XYSeriesRenderer createImagRenderer() {
|
||||
final XYSeriesRenderer imagRenderer = createCommonRenderer();
|
||||
imagRenderer.setStroke(BasicStroke.DASHED);
|
||||
imagRenderer.setColor(Color.LTGRAY);
|
||||
return imagRenderer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object onRetainNonConfigurationInstance() {
|
||||
return new PlotBoundaries(chart.getRenderer());
|
||||
}
|
||||
|
||||
private static final class PlotBoundaries implements Serializable {
|
||||
|
||||
private final double xMin;
|
||||
private final double xMax;
|
||||
private final double yMin;
|
||||
private final double yMax;
|
||||
|
||||
public PlotBoundaries(@NotNull XYMultipleSeriesRenderer renderer) {
|
||||
this.xMin = renderer.getXAxisMin();
|
||||
this.yMin = renderer.getYAxisMin();
|
||||
this.xMax = renderer.getXAxisMax();
|
||||
this.yMax = renderer.getYAxisMax();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PlotBoundaries{" +
|
||||
"yMax=" + yMax +
|
||||
", yMin=" + yMin +
|
||||
", xMax=" + xMax +
|
||||
", xMin=" + xMin +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
private static XYSeriesRenderer createCommonRenderer() {
|
||||
final XYSeriesRenderer renderer = new XYSeriesRenderer();
|
||||
renderer.setFillPoints(true);
|
||||
renderer.setPointStyle(PointStyle.POINT);
|
||||
renderer.setLineWidth(3);
|
||||
renderer.setColor(Color.WHITE);
|
||||
renderer.setStroke(BasicStroke.SOLID);
|
||||
return renderer;
|
||||
}
|
||||
|
||||
public void zoomInClickHandler(@NotNull View v) {
|
||||
this.graphicalView.zoomIn();
|
||||
}
|
||||
|
||||
public void zoomOutClickHandler(@NotNull View v) {
|
||||
this.graphicalView.zoomOut();
|
||||
}
|
||||
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* 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.calculator.plot;
|
||||
|
||||
import org.achartengine.model.XYSeries;
|
||||
import org.achartengine.util.MathHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/5/11
|
||||
* Time: 8:43 PM
|
||||
*/
|
||||
|
||||
/**
|
||||
* BEST SOLUTION IS TO MODIFY LIBRARY CLASS
|
||||
* NOTE: this class is a copy of XYSeries with some modifications:
|
||||
* 1. Possibility to insert point in th emiddle og the range
|
||||
*/
|
||||
|
||||
public class MyXYSeries extends XYSeries {
|
||||
/**
|
||||
* The series title.
|
||||
*/
|
||||
private String mTitle;
|
||||
/**
|
||||
* A list to contain the values for the X axis.
|
||||
*/
|
||||
private List<Double> mX = new ArrayList<Double>();
|
||||
/**
|
||||
* A list to contain the values for the Y axis.
|
||||
*/
|
||||
private List<Double> mY = new ArrayList<Double>();
|
||||
/**
|
||||
* The minimum value for the X axis.
|
||||
*/
|
||||
private double mMinX = MathHelper.NULL_VALUE;
|
||||
/**
|
||||
* The maximum value for the X axis.
|
||||
*/
|
||||
private double mMaxX = -MathHelper.NULL_VALUE;
|
||||
/**
|
||||
* The minimum value for the Y axis.
|
||||
*/
|
||||
private double mMinY = MathHelper.NULL_VALUE;
|
||||
/**
|
||||
* The maximum value for the Y axis.
|
||||
*/
|
||||
private double mMaxY = -MathHelper.NULL_VALUE;
|
||||
/**
|
||||
* The scale number for this series.
|
||||
*/
|
||||
private int mScaleNumber;
|
||||
|
||||
/**
|
||||
* Builds a new XY series.
|
||||
*
|
||||
* @param title the series title.
|
||||
*/
|
||||
public MyXYSeries(String title) {
|
||||
this(title, 10);
|
||||
}
|
||||
|
||||
public MyXYSeries(String title, int initialCapacity) {
|
||||
super(title, 0);
|
||||
|
||||
this.mX = new ArrayList<Double>(initialCapacity);
|
||||
this.mY = new ArrayList<Double>(initialCapacity);
|
||||
|
||||
mTitle = title;
|
||||
mScaleNumber = 0;
|
||||
|
||||
initRange();
|
||||
}
|
||||
|
||||
public int getScaleNumber() {
|
||||
return mScaleNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the range for both axes.
|
||||
*/
|
||||
private void initRange() {
|
||||
mMinX = MathHelper.NULL_VALUE;
|
||||
mMaxX = -MathHelper.NULL_VALUE;
|
||||
mMinY = MathHelper.NULL_VALUE;
|
||||
mMaxY = -MathHelper.NULL_VALUE;
|
||||
int length = getItemCount();
|
||||
for (int k = 0; k < length; k++) {
|
||||
double x = getX(k);
|
||||
double y = getY(k);
|
||||
updateRange(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the range on both axes.
|
||||
*
|
||||
* @param x the new x value
|
||||
* @param y the new y value
|
||||
*/
|
||||
private void updateRange(double x, double y) {
|
||||
mMinX = Math.min(mMinX, x);
|
||||
mMaxX = Math.max(mMaxX, x);
|
||||
mMinY = Math.min(mMinY, y);
|
||||
mMaxY = Math.max(mMaxY, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the series title.
|
||||
*
|
||||
* @return the series title
|
||||
*/
|
||||
public String getTitle() {
|
||||
return mTitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the series title.
|
||||
*
|
||||
* @param title the series title
|
||||
*/
|
||||
public void setTitle(String title) {
|
||||
mTitle = title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new value to the series.
|
||||
*
|
||||
* @param x the value for the X axis
|
||||
* @param y the value for the Y axis
|
||||
*/
|
||||
public void add(double x, double y) {
|
||||
boolean added = false;
|
||||
for (int i = 0; i < mX.size(); i++ ) {
|
||||
if ( mX.get(i) > x ) {
|
||||
mX.add(i, x);
|
||||
mY.add(i, y);
|
||||
added = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !added ) {
|
||||
mX.add(x);
|
||||
mY.add(y);
|
||||
}
|
||||
|
||||
updateRange(x, y);
|
||||
}
|
||||
|
||||
|
||||
public boolean needToAdd(double density, double x) {
|
||||
boolean result = true;
|
||||
|
||||
for (Double x1 : mX) {
|
||||
if (Math.abs(x - x1) < density) {
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an existing value from the series.
|
||||
*
|
||||
* @param index the index in the series of the value to remove
|
||||
*/
|
||||
public void remove(int index) {
|
||||
double removedX = mX.remove(index);
|
||||
double removedY = mY.remove(index);
|
||||
if (removedX == mMinX || removedX == mMaxX || removedY == mMinY || removedY == mMaxY) {
|
||||
initRange();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all the existing values from the series.
|
||||
*/
|
||||
public void clear() {
|
||||
mX.clear();
|
||||
mY.clear();
|
||||
initRange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the X axis value at the specified index.
|
||||
*
|
||||
* @param index the index
|
||||
* @return the X value
|
||||
*/
|
||||
public double getX(int index) {
|
||||
return mX.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Y axis value at the specified index.
|
||||
*
|
||||
* @param index the index
|
||||
* @return the Y value
|
||||
*/
|
||||
public double getY(int index) {
|
||||
return mY.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the series item count.
|
||||
*
|
||||
* @return the series item count
|
||||
*/
|
||||
public int getItemCount() {
|
||||
return mX == null ? 0 : mX.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum value on the X axis.
|
||||
*
|
||||
* @return the X axis minimum value
|
||||
*/
|
||||
public double getMinX() {
|
||||
return mMinX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum value on the Y axis.
|
||||
*
|
||||
* @return the Y axis minimum value
|
||||
*/
|
||||
public double getMinY() {
|
||||
return mMinY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum value on the X axis.
|
||||
*
|
||||
* @return the X axis maximum value
|
||||
*/
|
||||
public double getMaxX() {
|
||||
return mMaxX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum value on the Y axis.
|
||||
*
|
||||
* @return the Y axis maximum value
|
||||
*/
|
||||
public double getMaxY() {
|
||||
return mMaxY;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* 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.calculator.plot;
|
||||
|
||||
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 org.achartengine.util.MathHelper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/5/11
|
||||
* Time: 8:58 PM
|
||||
*/
|
||||
public final class PlotUtils {
|
||||
|
||||
private static final double MAX_Y_DIFF = Math.pow(10, 6);
|
||||
|
||||
// not intended for instantiation
|
||||
private PlotUtils() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
public static boolean addXY(double minValue,
|
||||
double maxValue,
|
||||
@NotNull Generic expression,
|
||||
@NotNull Constant variable,
|
||||
@NotNull MyXYSeries realSeries,
|
||||
@NotNull MyXYSeries imagSeries,
|
||||
boolean addExtra,
|
||||
int numberOfSteps) throws ArithmeticException {
|
||||
|
||||
boolean imagExists = false;
|
||||
|
||||
double min = Math.min(minValue, maxValue);
|
||||
double max = Math.max(minValue, maxValue);
|
||||
double dist = max - min;
|
||||
if (addExtra) {
|
||||
min = min - dist;
|
||||
max = max + dist;
|
||||
}
|
||||
|
||||
final double step = Math.max( dist / numberOfSteps, 0.000000001);
|
||||
|
||||
Double prevRealY = null;
|
||||
Double prevX = null;
|
||||
Double prevImagY = null;
|
||||
|
||||
double x = min;
|
||||
while (x <= max) {
|
||||
|
||||
boolean needToCalculateRealY = realSeries.needToAdd(step, x);
|
||||
|
||||
if (needToCalculateRealY) {
|
||||
final Complex c = calculatorExpression(expression, variable, x);
|
||||
Double y = prepareY(c.realPart());
|
||||
if (y != null) {
|
||||
addSingularityPoint(realSeries, prevX, x, prevRealY, y);
|
||||
realSeries.add(x, y);
|
||||
prevRealY = y;
|
||||
prevX = x;
|
||||
}
|
||||
|
||||
boolean needToCalculateImagY = imagSeries.needToAdd(step, x);
|
||||
if (needToCalculateImagY) {
|
||||
y = prepareY(c.imaginaryPart());
|
||||
if (y != null) {
|
||||
addSingularityPoint(imagSeries, prevX, x, prevImagY, y);
|
||||
imagSeries.add(x, y);
|
||||
prevImagY = y;
|
||||
prevX = x;
|
||||
}
|
||||
if (c.imaginaryPart() != 0d) {
|
||||
imagExists = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
boolean needToCalculateImagY = imagSeries.needToAdd(step, x);
|
||||
if (needToCalculateImagY) {
|
||||
final Complex c = calculatorExpression(expression, variable, x);
|
||||
Double y = prepareY(c.imaginaryPart());
|
||||
if (y != null) {
|
||||
addSingularityPoint(imagSeries, prevX, x, prevImagY, y);
|
||||
imagSeries.add(x, y);
|
||||
prevImagY = y;
|
||||
prevX = x;
|
||||
}
|
||||
if (c.imaginaryPart() != 0d) {
|
||||
imagExists = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
x += step;
|
||||
}
|
||||
|
||||
return imagExists;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Complex calculatorExpression(@NotNull Generic expression, @NotNull Constant variable, double x) {
|
||||
return unwrap(expression.substitute(variable, Expression.valueOf(x)).numeric());
|
||||
}
|
||||
|
||||
public static void addSingularityPoint(@NotNull MyXYSeries series, @Nullable Double prevX, @NotNull Double x, @Nullable Double prevY, @NotNull Double y) {
|
||||
if (prevX != null && prevY != null) {
|
||||
// y or prevY should be more than 1d because if they are too small false singularity may occur (e.g., 1/0.000000000000000001)
|
||||
if ( (Math.abs(y) >= 1d && Math.abs(prevY / y) > MAX_Y_DIFF) || (Math.abs(prevY) >= 1d && Math.abs(y / prevY) > MAX_Y_DIFF)) {
|
||||
//Log.d(CalculatorPlotActivity.class.getName(), "Singularity! Prev point: (" + prevX + ", " + prevY + "), current point: (" +x+ ", " + y +")" );
|
||||
//Log.d(CalculatorPlotActivity.class.getName(), String.valueOf(prevX + Math.abs(x - prevX) / 2) + ", null");
|
||||
series.add(prevX + Math.abs(x - prevX) / 2, MathHelper.NULL_VALUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Double prepareY(double y) {
|
||||
if (Double.isNaN(y)) {
|
||||
return null;
|
||||
} else {
|
||||
return y;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public 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
|
||||
public 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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.calculator.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Paint;
|
||||
import android.text.TextPaint;
|
||||
import android.util.AttributeSet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.solovyev.android.calculator.R;
|
||||
import org.solovyev.android.calculator.model.CalculatorEngine;
|
||||
import org.solovyev.android.view.drag.DirectionDragButton;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 11/22/11
|
||||
* Time: 2:42 PM
|
||||
*/
|
||||
public class AngleUnitsButton extends DirectionDragButton {
|
||||
|
||||
public AngleUnitsButton(Context context, @NotNull AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDirectionTextPaint(@NotNull Paint basePaint,
|
||||
@NotNull DirectionTextData directionTextData,
|
||||
@NotNull Resources resources) {
|
||||
super.initDirectionTextPaint(basePaint, directionTextData, resources);
|
||||
|
||||
final TextPaint directionTextPaint = directionTextData.getPaint();
|
||||
if (CalculatorEngine.instance.getEngine().getAngleUnits().name().equals(directionTextData.getText())) {
|
||||
directionTextPaint.setColor(resources.getColor(R.color.selected_angle_unit_text_color));
|
||||
} else {
|
||||
directionTextPaint.setColor(resources.getColor(R.color.default_text_color));
|
||||
directionTextPaint.setAlpha(getDirectionTextAlpha());
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.calculator.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Paint;
|
||||
import android.text.TextPaint;
|
||||
import android.util.AttributeSet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.solovyev.android.calculator.R;
|
||||
import org.solovyev.android.calculator.model.CalculatorEngine;
|
||||
import org.solovyev.android.view.drag.DirectionDragButton;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/8/11
|
||||
* Time: 2:22 AM
|
||||
*/
|
||||
public class NumeralBasesButton extends DirectionDragButton {
|
||||
|
||||
public NumeralBasesButton(Context context, @NotNull AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDirectionTextPaint(@NotNull Paint basePaint,
|
||||
@NotNull DirectionTextData directionTextData,
|
||||
@NotNull Resources resources) {
|
||||
super.initDirectionTextPaint(basePaint, directionTextData, resources);
|
||||
|
||||
final TextPaint directionTextPaint = directionTextData.getPaint();
|
||||
if (CalculatorEngine.instance.getEngine().getNumeralBase().name().equals(directionTextData.getText())) {
|
||||
directionTextPaint.setColor(resources.getColor(R.color.selected_angle_unit_text_color));
|
||||
} else {
|
||||
directionTextPaint.setColor(resources.getColor(R.color.default_text_color));
|
||||
directionTextPaint.setAlpha(getDirectionTextAlpha());
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* 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.calculator.view;
|
||||
|
||||
import jscl.MathContext;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.calculator.math.MathType;
|
||||
import org.solovyev.android.calculator.model.*;
|
||||
import org.solovyev.common.utils.MutableObject;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 10/12/11
|
||||
* Time: 9:47 PM
|
||||
*/
|
||||
public class TextHighlighter implements TextProcessor<TextHighlighter.Result, String> {
|
||||
|
||||
private static final Map<String, String> nbFontAttributes = new HashMap<String, String>();
|
||||
|
||||
static {
|
||||
nbFontAttributes.put("color", "#008000");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public final MathContext mathContext;
|
||||
|
||||
public static class Result implements CharSequence {
|
||||
|
||||
@NotNull
|
||||
private final String string;
|
||||
|
||||
private final int offset;
|
||||
|
||||
public Result(@NotNull String string, int offset) {
|
||||
this.string = string;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int length() {
|
||||
return string.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public char charAt(int i) {
|
||||
return string.charAt(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence subSequence(int i, int i1) {
|
||||
return string.subSequence(i, i1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return string;
|
||||
}
|
||||
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
}
|
||||
|
||||
private final int color;
|
||||
private final int colorRed;
|
||||
private final int colorGreen;
|
||||
private final int colorBlue;
|
||||
private final boolean formatNumber;
|
||||
|
||||
public TextHighlighter(int baseColor, boolean formatNumber, @NotNull MathContext mathContext) {
|
||||
this.color = baseColor;
|
||||
this.formatNumber = formatNumber;
|
||||
this.mathContext = mathContext;
|
||||
//this.colorRed = Color.red(baseColor);
|
||||
this.colorRed = (baseColor >> 16) & 0xFF;
|
||||
//this.colorGreen = Color.green(baseColor);
|
||||
this.colorGreen = (color >> 8) & 0xFF;
|
||||
//this.colorBlue = Color.blue(baseColor);
|
||||
this.colorBlue = color & 0xFF;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Result process(@NotNull String text) throws CalculatorParseException {
|
||||
final String result;
|
||||
|
||||
int maxNumberOfOpenGroupSymbols = 0;
|
||||
int numberOfOpenGroupSymbols = 0;
|
||||
|
||||
final StringBuilder text1 = new StringBuilder();
|
||||
|
||||
int resultOffset = 0;
|
||||
|
||||
final AbstractNumberBuilder numberBuilder;
|
||||
if (!formatNumber) {
|
||||
numberBuilder = new LiteNumberBuilder(CalculatorEngine.instance.getEngine());
|
||||
} else {
|
||||
numberBuilder = new NumberBuilder(CalculatorEngine.instance.getEngine());
|
||||
}
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
MathType.Result mathType = MathType.getType(text, i, numberBuilder.isHexMode());
|
||||
|
||||
if (numberBuilder instanceof NumberBuilder) {
|
||||
final MutableObject<Integer> numberOffset = new MutableObject<Integer>(0);
|
||||
((NumberBuilder) numberBuilder).process(text1, mathType, numberOffset);
|
||||
resultOffset += numberOffset.getObject();
|
||||
} else {
|
||||
((LiteNumberBuilder) numberBuilder).process(mathType);
|
||||
}
|
||||
|
||||
final String match = mathType.getMatch();
|
||||
switch (mathType.getMathType()) {
|
||||
case open_group_symbol:
|
||||
numberOfOpenGroupSymbols++;
|
||||
maxNumberOfOpenGroupSymbols = Math.max(maxNumberOfOpenGroupSymbols, numberOfOpenGroupSymbols);
|
||||
text1.append(text.charAt(i));
|
||||
break;
|
||||
case close_group_symbol:
|
||||
numberOfOpenGroupSymbols--;
|
||||
text1.append(text.charAt(i));
|
||||
break;
|
||||
case operator:
|
||||
text1.append(match);
|
||||
if (match.length() > 1) {
|
||||
i += match.length() - 1;
|
||||
}
|
||||
break;
|
||||
case function:
|
||||
i = processHighlightedText(text1, i, match, "i", null);
|
||||
break;
|
||||
case constant:
|
||||
i = processHighlightedText(text1, i, match, "b", null);
|
||||
break;
|
||||
case numeral_base:
|
||||
i = processHighlightedText(text1, i, match, "b", null);
|
||||
break;
|
||||
default:
|
||||
if (mathType.getMathType() == MathType.text || match.length() <= 1) {
|
||||
text1.append(text.charAt(i));
|
||||
} else {
|
||||
text1.append(match);
|
||||
i += match.length() - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (numberBuilder instanceof NumberBuilder) {
|
||||
final MutableObject<Integer> numberOffset = new MutableObject<Integer>(0);
|
||||
((NumberBuilder) numberBuilder).processNumber(text1, numberOffset);
|
||||
resultOffset += numberOffset.getObject();
|
||||
}
|
||||
|
||||
if (maxNumberOfOpenGroupSymbols > 0) {
|
||||
|
||||
final StringBuilder text2 = new StringBuilder();
|
||||
|
||||
String s = text1.toString();
|
||||
int i = processBracketGroup(text2, s, 0, 0, maxNumberOfOpenGroupSymbols);
|
||||
for (; i < s.length(); i++) {
|
||||
text2.append(s.charAt(i));
|
||||
}
|
||||
|
||||
//Log.d(CalculatorEditor.class.getName(), text2.toString());
|
||||
|
||||
result = text2.toString();
|
||||
} else {
|
||||
result = text1.toString();
|
||||
}
|
||||
|
||||
return new Result(result, resultOffset);
|
||||
}
|
||||
|
||||
private int processHighlightedText(@NotNull StringBuilder result, int i, @NotNull String match, @NotNull String tag, @Nullable Map<String, String> tagAttributes) {
|
||||
result.append("<").append(tag);
|
||||
|
||||
if (tagAttributes != null) {
|
||||
for (Map.Entry<String, String> entry : tagAttributes.entrySet()) {
|
||||
// attr1="attr1_value" attr2="attr2_value"
|
||||
result.append(" ").append(entry.getKey()).append("=\"").append(entry.getValue()).append("\"");
|
||||
}
|
||||
}
|
||||
|
||||
result.append(">").append(match).append("</").append(tag).append(">");
|
||||
if (match.length() > 1) {
|
||||
return i + match.length() - 1;
|
||||
} else {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
private int processBracketGroup(@NotNull StringBuilder result, @NotNull String s, int i, int numberOfOpenings, int maxNumberOfGroups) {
|
||||
|
||||
result.append("<font color=\"").append(getColor(maxNumberOfGroups, numberOfOpenings)).append("\">");
|
||||
|
||||
for (; i < s.length(); i++) {
|
||||
char ch = s.charAt(i);
|
||||
|
||||
if (MathType.open_group_symbol.getTokens().contains(String.valueOf(ch))) {
|
||||
result.append(ch);
|
||||
result.append("</font>");
|
||||
i = processBracketGroup(result, s, i + 1, numberOfOpenings + 1, maxNumberOfGroups);
|
||||
result.append("<font color=\"").append(getColor(maxNumberOfGroups, numberOfOpenings)).append("\">");
|
||||
if (i < s.length() && MathType.close_group_symbol.getTokens().contains(String.valueOf(s.charAt(i)))) {
|
||||
result.append(s.charAt(i));
|
||||
}
|
||||
} else if (MathType.close_group_symbol.getTokens().contains(String.valueOf(ch))) {
|
||||
break;
|
||||
} else {
|
||||
result.append(ch);
|
||||
}
|
||||
}
|
||||
|
||||
result.append("</font>");
|
||||
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
private String getColor(int totalNumberOfOpenings, int numberOfOpenings) {
|
||||
double c = 0.8;
|
||||
|
||||
int offset = ((int) (255 * c)) * numberOfOpenings / (totalNumberOfOpenings + 1);
|
||||
|
||||
// for tests:
|
||||
// innt result = Color.rgb(BASE_COLOUR_RED_COMPONENT - offset, BASE_COLOUR_GREEN_COMPONENT - offset, BASE_COLOUR_BLUE_COMPONENT - offset);
|
||||
int result = (0xFF << 24) | ((colorRed - offset) << 16) | ((colorGreen - offset) << 8) | (colorBlue - offset);
|
||||
|
||||
return "#" + Integer.toHexString(result).substring(2);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user