diff --git a/calculatorpp/res/xml/preferences.xml b/calculatorpp/res/xml/preferences.xml index 77fe5063..12fe7b25 100644 --- a/calculatorpp/res/xml/preferences.xml +++ b/calculatorpp/res/xml/preferences.xml @@ -1,186 +1,186 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/calculatorpp/src/main/java/org/solovyev/android/calculator/CalculatorActivity.java b/calculatorpp/src/main/java/org/solovyev/android/calculator/CalculatorActivity.java index 2c72260f..608a3aa4 100644 --- a/calculatorpp/src/main/java/org/solovyev/android/calculator/CalculatorActivity.java +++ b/calculatorpp/src/main/java/org/solovyev/android/calculator/CalculatorActivity.java @@ -1,754 +1,775 @@ -/* - * 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.*; -import android.content.pm.ActivityInfo; -import android.content.res.Configuration; -import android.os.Bundle; -import android.os.IBinder; -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 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.LocalBinder; -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.model.CalculatorEngine; -import org.solovyev.android.calculator.view.AngleUnitsButton; -import org.solovyev.android.calculator.view.CalculatorAdditionalTitle; -import org.solovyev.android.calculator.view.NumeralBasesButton; -import org.solovyev.android.calculator.view.OnDragListenerVibrator; -import org.solovyev.android.history.HistoryDragProcessor; -import org.solovyev.android.menu.ActivityMenu; -import org.solovyev.android.menu.LayoutActivityMenu; -import org.solovyev.android.prefs.Preference; -import org.solovyev.android.view.ColorButton; -import org.solovyev.android.view.drag.*; -import org.solovyev.common.Announcer; -import org.solovyev.common.equals.EqualsTool; -import org.solovyev.common.math.Point2d; -import org.solovyev.common.text.StringUtils; -import org.solovyev.common.history.HistoryAction; - -public class CalculatorActivity extends Activity implements FontSizeAdjuster, SharedPreferences.OnSharedPreferenceChangeListener, ServiceConnection { - - @NotNull - public static final String TAG = "Calculator++"; - - private static final int HVGA_WIDTH_PIXELS = 320; - - @Nullable - private IBillingObserver billingObserver; - - @Nullable - private ICalculationService calculationService; - - @NotNull - private final Announcer dpclRegister = new Announcer(DragPreferencesChangeListener.class); - - @NotNull - private CalculatorModel calculatorModel; - - private volatile boolean initialized; - - @NotNull - private CalculatorPreferences.Gui.Theme theme; - - @NotNull - private CalculatorPreferences.Gui.Layout layout; - - @Nullable - private Vibrator vibrator; - - private boolean useBackAsPrev; - - @NotNull - private NumeralBaseButtons numeralBaseButtons = new NumeralBaseButtons(); - - @NotNull - private ActivityMenu menu = LayoutActivityMenu.newInstance(R.menu.main_menu, CalculatorMenu.class); - - /** - * 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); - - CalculatorPreferences.setDefaultValues(preferences); - - setTheme(preferences); - super.onCreate(savedInstanceState); - setLayout(preferences); - - bindService(new Intent(this, CalculationServiceImpl.class), this, Context.BIND_AUTO_CREATE); - - 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(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(); - - fixThemeParameters(true); - - if (layout == CalculatorPreferences.Gui.Layout.simple) { - toggleButtonDirectionText(R.id.oneDigitButton, false, DragDirection.up, DragDirection.down); - toggleButtonDirectionText(R.id.twoDigitButton, false, DragDirection.up, DragDirection.down); - toggleButtonDirectionText(R.id.threeDigitButton, false, DragDirection.up, DragDirection.down); - - toggleButtonDirectionText(R.id.sixDigitButton, false, 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.down); - toggleButtonDirectionText(R.id.fiveDigitButton, false, 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); - } - - numeralBaseButtons.toggleNumericDigits(this, preferences); - - toggleOrientationChange(preferences); - - toggleEqualsButton(preferences); - - preferences.registerOnSharedPreferenceChangeListener(this); - } - - private void fixThemeParameters(boolean fixMagicFlames) { - if (theme.getThemeType() == CalculatorPreferences.Gui.ThemeType.metro) { - - if (fixMagicFlames) { - // for metro themes we should turn off magic flames - AndroidUtils.processViewsOfType(this.getWindow().getDecorView(), ColorButton.class, new AndroidUtils.ViewProcessor() { - @Override - public void process(@NotNull ColorButton colorButton) { - colorButton.setDrawMagicFlame(false); - } - }); - } - - fixMargins(2, 2); - } else { - fixMargins(1, 1); - } - } - - 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 = AndroidUtils.getScreenOrientation(this); - 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 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; - } - - @Override - public void onServiceConnected(ComponentName componentName, IBinder binder) { - if (binder instanceof LocalBinder) { - calculationService = (ICalculationService)((LocalBinder) binder).getService(); - } - } - - @Override - public void onServiceDisconnected(ComponentName componentName) { - } - - - private synchronized void setLayout(@NotNull SharedPreferences preferences) { - layout = CalculatorPreferences.Gui.layout.getPreferenceNoError(preferences); - - setContentView(layout.getLayoutId()); - } - - private synchronized void setTheme(@NotNull SharedPreferences preferences) { - theme = CalculatorPreferences.Gui.theme.getPreferenceNoError(preferences); - - setTheme(theme.getThemeId()); - } - - private synchronized void firstTimeInit(@NotNull SharedPreferences preferences) { - if (!initialized) { - this.useBackAsPrev = CalculatorPreferences.Gui.usePrevAsBack.getPreference(preferences); - - final Integer appOpenedCounter = CalculatorPreferences.appOpenedCounter.getPreference(preferences); - if (appOpenedCounter != null) { - CalculatorPreferences.appOpenedCounter.putPreference(preferences, appOpenedCounter + 1); - } - - final Integer savedVersion = CalculatorPreferences.appVersion.getPreference(preferences); - - final int appVersion = AndroidUtils.getAppVersionCode(this, CalculatorActivity.class.getPackage().getName()); - - CalculatorPreferences.appVersion.putPreference(preferences, appVersion); - - boolean dialogShown = false; - if (EqualsTool.areEqual(savedVersion, CalculatorPreferences.appVersion.getDefaultValue())) { - // 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 = CalculatorPreferences.Gui.showReleaseNotes.getPreference(preferences); - 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 ) { - dialogShown = showSpecialWindow(preferences, CalculatorPreferences.Gui.feedbackWindowShown, R.layout.feedback, R.id.feedbackText); - } - } - - if ( !dialogShown ) { - dialogShown = showSpecialWindow(preferences, CalculatorPreferences.Gui.notesppAnnounceShown, R.layout.notespp_announce, R.id.notespp_announce); - } - - ResourceCache.instance.initCaptions(this, R.id.class); - - initialized = true; - } - } - - private boolean showSpecialWindow(@NotNull SharedPreferences preferences, @NotNull Preference specialWindowShownPref, int layoutId, int textViewId) { - boolean result = false; - - final Boolean specialWindowShown = specialWindowShownPref.getPreference(preferences); - if ( specialWindowShown != null && !specialWindowShown ) { - final LayoutInflater layoutInflater = (LayoutInflater) this.getSystemService(LAYOUT_INFLATER_SERVICE); - final View view = layoutInflater.inflate(layoutId, null); - - final TextView feedbackTextView = (TextView) view.findViewById(textViewId); - feedbackTextView.setMovementMethod(LinkMovementMethod.getInstance()); - - final AlertDialog.Builder builder = new AlertDialog.Builder(this).setView(view); - builder.setPositiveButton(android.R.string.ok, null); - builder.create().show(); - - result = true; - specialWindowShownPref.putPreference(preferences, true); - } - - return result; - } - - @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()); - if (((ColorButton) v).isShowText()) { - calculatorModel.processDigitButtonAction(((ColorButton) 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) { - return this.menu.onCreateOptionsMenu(this, menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - return menu.onOptionsItemSelected(this, item); - } - - /** - * 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 CalculatorPreferences.Gui.Layout newLayout = CalculatorPreferences.Gui.layout.getPreference(preferences); - final CalculatorPreferences.Gui.Theme newTheme = CalculatorPreferences.Gui.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 (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 ( CalculatorPreferences.Gui.usePrevAsBack.getKey().equals(key) ) { - useBackAsPrev = CalculatorPreferences.Gui.usePrevAsBack.getPreference(preferences); - } - - if (CalculatorEngine.Preferences.numeralBase.getKey().equals(key)) { - numeralBaseButtons.toggleNumericDigits(this, preferences); - } - - if ( CalculatorEngine.Preferences.multiplicationSign.getKey().equals(key) ) { - initMultiplicationButton(); - } - - if ( CalculatorPreferences.Gui.autoOrientation.getKey().equals(key) ) { - toggleOrientationChange(preferences); - } - - if ( CalculatorPreferences.Gui.showEqualsButton.getKey().equals(key) ) { - toggleEqualsButton(preferences); - } - } - - private void toggleEqualsButton(@Nullable SharedPreferences preferences) { - preferences = preferences == null ? PreferenceManager.getDefaultSharedPreferences(this) : preferences; - - - if (AndroidUtils.getScreenOrientation(this) == Configuration.ORIENTATION_PORTRAIT || !CalculatorPreferences.Gui.autoOrientation.getPreference(preferences)) { - final Display display = this.getWindowManager().getDefaultDisplay(); - - final DragButton button = (DragButton)findViewById(R.id.equalsButton); - if (CalculatorPreferences.Gui.showEqualsButton.getPreference(preferences)) { - button.setLayoutParams(new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.FILL_PARENT, 1f)); - if (display.getWidth() <= 480) { - // mobile phones - calculatorModel.getDisplay().setBackgroundDrawable(null); - } - } else { - button.setLayoutParams(new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.FILL_PARENT, 0f)); - if (display.getWidth() <= 480) { - // mobile phones - calculatorModel.getDisplay().setBackgroundDrawable(this.getResources().getDrawable(R.drawable.equals9)); - } - } - fixThemeParameters(false); - } - } - - private void toggleOrientationChange(@Nullable SharedPreferences preferences) { - preferences = preferences == null ? PreferenceManager.getDefaultSharedPreferences(this) : preferences; - if (CalculatorPreferences.Gui.autoOrientation.getPreference(preferences)) { - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); - } else { - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); - } - } - - 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; - } - } +/* + * 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.*; +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; +import android.os.Bundle; +import android.os.IBinder; +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 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.LocalBinder; +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.model.CalculatorEngine; +import org.solovyev.android.calculator.view.AngleUnitsButton; +import org.solovyev.android.calculator.view.CalculatorAdditionalTitle; +import org.solovyev.android.calculator.view.NumeralBasesButton; +import org.solovyev.android.calculator.view.OnDragListenerVibrator; +import org.solovyev.android.history.HistoryDragProcessor; +import org.solovyev.android.menu.ActivityMenu; +import org.solovyev.android.menu.LayoutActivityMenu; +import org.solovyev.android.prefs.Preference; +import org.solovyev.android.view.ColorButton; +import org.solovyev.android.view.drag.*; +import org.solovyev.common.Announcer; +import org.solovyev.common.equals.EqualsTool; +import org.solovyev.common.math.Point2d; +import org.solovyev.common.text.StringUtils; +import org.solovyev.common.history.HistoryAction; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; + +public class CalculatorActivity extends Activity implements FontSizeAdjuster, SharedPreferences.OnSharedPreferenceChangeListener, ServiceConnection { + + @NotNull + public static final String TAG = "Calculator++"; + + private static final int HVGA_WIDTH_PIXELS = 320; + + @Nullable + private IBillingObserver billingObserver; + + @Nullable + private ICalculationService calculationService; + + @NotNull + private final Announcer dpclRegister = new Announcer(DragPreferencesChangeListener.class); + + @NotNull + private CalculatorModel calculatorModel; + + private volatile boolean initialized; + + @NotNull + private CalculatorPreferences.Gui.Theme theme; + + @NotNull + private CalculatorPreferences.Gui.Layout layout; + + @Nullable + private Vibrator vibrator; + + private boolean useBackAsPrev; + + @NotNull + private NumeralBaseButtons numeralBaseButtons = new NumeralBaseButtons(); + + @NotNull + private ActivityMenu menu = LayoutActivityMenu.newInstance(R.menu.main_menu, CalculatorMenu.class); + + /** + * 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); + + CalculatorPreferences.setDefaultValues(preferences); + + setTheme(preferences); + super.onCreate(savedInstanceState); + setLayout(preferences); + + bindService(new Intent(this, CalculationServiceImpl.class), this, Context.BIND_AUTO_CREATE); + + 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); + } + } + + 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(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(); + + fixThemeParameters(true); + + if (layout == CalculatorPreferences.Gui.Layout.simple) { + toggleButtonDirectionText(R.id.oneDigitButton, false, DragDirection.up, DragDirection.down); + toggleButtonDirectionText(R.id.twoDigitButton, false, DragDirection.up, DragDirection.down); + toggleButtonDirectionText(R.id.threeDigitButton, false, DragDirection.up, DragDirection.down); + + toggleButtonDirectionText(R.id.sixDigitButton, false, 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.down); + toggleButtonDirectionText(R.id.fiveDigitButton, false, 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); + } + + numeralBaseButtons.toggleNumericDigits(this, preferences); + + toggleOrientationChange(preferences); + + toggleEqualsButton(preferences); + + preferences.registerOnSharedPreferenceChangeListener(this); + } + + private void fixThemeParameters(boolean fixMagicFlames) { + if (theme.getThemeType() == CalculatorPreferences.Gui.ThemeType.metro) { + + if (fixMagicFlames) { + // for metro themes we should turn off magic flames + AndroidUtils.processViewsOfType(this.getWindow().getDecorView(), ColorButton.class, new AndroidUtils.ViewProcessor() { + @Override + public void process(@NotNull ColorButton colorButton) { + colorButton.setDrawMagicFlame(false); + } + }); + } + + fixMargins(2, 2); + } else { + fixMargins(1, 1); + } + } + + 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 = AndroidUtils.getScreenOrientation(this); + 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 synchronized void setOnDragListeners(@NotNull SimpleOnDragListener.Preferences dragPreferences, @NotNull SharedPreferences preferences) { + final OnDragListener onDragListener = new OnDragListenerVibrator(newOnDragListener(new DigitButtonDragProcessor(calculatorModel), dragPreferences), vibrator, preferences); + + final List dragButtonIds = new ArrayList(); + final List buttonIds = new ArrayList(); + + for (Field field : R.id.class.getDeclaredFields()) { + int modifiers = field.getModifiers(); + if (Modifier.isFinal(modifiers) && Modifier.isStatic(modifiers)) { + try { + int viewId = field.getInt(R.id.class); + final View view = this.findViewById(viewId); + if (view instanceof DragButton) { + dragButtonIds.add(viewId); + } + if (view instanceof Button) { + buttonIds.add(viewId); + } + } catch (IllegalAccessException e) { + Log.e(R.id.class.getName(), e.getMessage()); + } + } + } + + for (Integer dragButtonId : dragButtonIds) { + ((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; + } + + @Override + public void onServiceConnected(ComponentName componentName, IBinder binder) { + if (binder instanceof LocalBinder) { + calculationService = (ICalculationService)((LocalBinder) binder).getService(); + } + } + + @Override + public void onServiceDisconnected(ComponentName componentName) { + } + + + private synchronized void setLayout(@NotNull SharedPreferences preferences) { + layout = CalculatorPreferences.Gui.layout.getPreferenceNoError(preferences); + + setContentView(layout.getLayoutId()); + } + + private synchronized void setTheme(@NotNull SharedPreferences preferences) { + theme = CalculatorPreferences.Gui.theme.getPreferenceNoError(preferences); + + setTheme(theme.getThemeId()); + } + + private synchronized void firstTimeInit(@NotNull SharedPreferences preferences) { + if (!initialized) { + this.useBackAsPrev = CalculatorPreferences.Gui.usePrevAsBack.getPreference(preferences); + + final Integer appOpenedCounter = CalculatorPreferences.appOpenedCounter.getPreference(preferences); + if (appOpenedCounter != null) { + CalculatorPreferences.appOpenedCounter.putPreference(preferences, appOpenedCounter + 1); + } + + final Integer savedVersion = CalculatorPreferences.appVersion.getPreference(preferences); + + final int appVersion = AndroidUtils.getAppVersionCode(this, CalculatorActivity.class.getPackage().getName()); + + CalculatorPreferences.appVersion.putPreference(preferences, appVersion); + + boolean dialogShown = false; + if (EqualsTool.areEqual(savedVersion, CalculatorPreferences.appVersion.getDefaultValue())) { + // 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 = CalculatorPreferences.Gui.showReleaseNotes.getPreference(preferences); + 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 ) { + dialogShown = showSpecialWindow(preferences, CalculatorPreferences.Gui.feedbackWindowShown, R.layout.feedback, R.id.feedbackText); + } + } + + if ( !dialogShown ) { + dialogShown = showSpecialWindow(preferences, CalculatorPreferences.Gui.notesppAnnounceShown, R.layout.notespp_announce, R.id.notespp_announce); + } + + initialized = true; + } + } + + private boolean showSpecialWindow(@NotNull SharedPreferences preferences, @NotNull Preference specialWindowShownPref, int layoutId, int textViewId) { + boolean result = false; + + final Boolean specialWindowShown = specialWindowShownPref.getPreference(preferences); + if ( specialWindowShown != null && !specialWindowShown ) { + final LayoutInflater layoutInflater = (LayoutInflater) this.getSystemService(LAYOUT_INFLATER_SERVICE); + final View view = layoutInflater.inflate(layoutId, null); + + final TextView feedbackTextView = (TextView) view.findViewById(textViewId); + feedbackTextView.setMovementMethod(LinkMovementMethod.getInstance()); + + final AlertDialog.Builder builder = new AlertDialog.Builder(this).setView(view); + builder.setPositiveButton(android.R.string.ok, null); + builder.create().show(); + + result = true; + specialWindowShownPref.putPreference(preferences, true); + } + + return result; + } + + @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()); + if (((ColorButton) v).isShowText()) { + calculatorModel.processDigitButtonAction(((ColorButton) 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) { + return this.menu.onCreateOptionsMenu(this, menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + return menu.onOptionsItemSelected(this, item); + } + + /** + * 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 CalculatorPreferences.Gui.Layout newLayout = CalculatorPreferences.Gui.layout.getPreference(preferences); + final CalculatorPreferences.Gui.Theme newTheme = CalculatorPreferences.Gui.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 (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 ( CalculatorPreferences.Gui.usePrevAsBack.getKey().equals(key) ) { + useBackAsPrev = CalculatorPreferences.Gui.usePrevAsBack.getPreference(preferences); + } + + if (CalculatorEngine.Preferences.numeralBase.getKey().equals(key)) { + numeralBaseButtons.toggleNumericDigits(this, preferences); + } + + if ( CalculatorEngine.Preferences.multiplicationSign.getKey().equals(key) ) { + initMultiplicationButton(); + } + + if ( CalculatorPreferences.Gui.autoOrientation.getKey().equals(key) ) { + toggleOrientationChange(preferences); + } + + if ( CalculatorPreferences.Gui.showEqualsButton.getKey().equals(key) ) { + toggleEqualsButton(preferences); + } + } + + private void toggleEqualsButton(@Nullable SharedPreferences preferences) { + preferences = preferences == null ? PreferenceManager.getDefaultSharedPreferences(this) : preferences; + + + if (AndroidUtils.getScreenOrientation(this) == Configuration.ORIENTATION_PORTRAIT || !CalculatorPreferences.Gui.autoOrientation.getPreference(preferences)) { + final Display display = this.getWindowManager().getDefaultDisplay(); + + final DragButton button = (DragButton)findViewById(R.id.equalsButton); + if (CalculatorPreferences.Gui.showEqualsButton.getPreference(preferences)) { + button.setLayoutParams(new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.FILL_PARENT, 1f)); + if (display.getWidth() <= 480) { + // mobile phones + calculatorModel.getDisplay().setBackgroundDrawable(null); + } + } else { + button.setLayoutParams(new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.FILL_PARENT, 0f)); + if (display.getWidth() <= 480) { + // mobile phones + calculatorModel.getDisplay().setBackgroundDrawable(this.getResources().getDrawable(R.drawable.equals9)); + } + } + fixThemeParameters(false); + } + } + + private void toggleOrientationChange(@Nullable SharedPreferences preferences) { + preferences = preferences == null ? PreferenceManager.getDefaultSharedPreferences(this) : preferences; + if (CalculatorPreferences.Gui.autoOrientation.getPreference(preferences)) { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); + } else { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + } + } + + 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; + } + } } \ No newline at end of file diff --git a/calculatorpp/src/main/java/org/solovyev/android/calculator/about/CalculatorReleaseNotesActivity.java b/calculatorpp/src/main/java/org/solovyev/android/calculator/about/CalculatorReleaseNotesActivity.java index 130d96d1..0186cca5 100644 --- a/calculatorpp/src/main/java/org/solovyev/android/calculator/about/CalculatorReleaseNotesActivity.java +++ b/calculatorpp/src/main/java/org/solovyev/android/calculator/about/CalculatorReleaseNotesActivity.java @@ -1,76 +1,75 @@ -/* - * 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.content.res.Resources; -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.text.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()); - - final TextHelper textHelper = new TextHelper(context.getResources(), R.class.getPackage().getName()); - - boolean first = true; - for ( int i = version; i >= minVersion; i-- ) { - String releaseNotesForVersion = textHelper.getText("c_release_notes_for_" + i); - if (!StringUtils.isEmpty(releaseNotesForVersion)){ - assert releaseNotesForVersion != null; - if ( !first ) { - result.append("

"); - } else { - first = false; - } - releaseNotesForVersion = releaseNotesForVersion.replace("\n", "
"); - result.append("").append(releaseNotesForTitle).append(i).append("

"); - result.append(releaseNotesForVersion); - } - } - - return result.toString(); - } -} +/* + * 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.content.res.Resources; +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.calculator.CalculatorActivity; +import org.solovyev.android.calculator.R; +import org.solovyev.common.text.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()); + + final TextHelper textHelper = new TextHelper(context.getResources(), R.class.getPackage().getName()); + + boolean first = true; + for ( int i = version; i >= minVersion; i-- ) { + String releaseNotesForVersion = textHelper.getText("c_release_notes_for_" + i); + if (!StringUtils.isEmpty(releaseNotesForVersion)){ + assert releaseNotesForVersion != null; + if ( !first ) { + result.append("

"); + } else { + first = false; + } + releaseNotesForVersion = releaseNotesForVersion.replace("\n", "
"); + result.append("").append(releaseNotesForTitle).append(i).append("

"); + result.append(releaseNotesForVersion); + } + } + + return result.toString(); + } +} diff --git a/calculatorpp/src/main/java/org/solovyev/android/calculator/model/CalculatorEngine.java b/calculatorpp/src/main/java/org/solovyev/android/calculator/model/CalculatorEngine.java index c39e09a3..68bcc9b8 100644 --- a/calculatorpp/src/main/java/org/solovyev/android/calculator/model/CalculatorEngine.java +++ b/calculatorpp/src/main/java/org/solovyev/android/calculator/model/CalculatorEngine.java @@ -1,423 +1,425 @@ -/* - * 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.android.prefs.Preference; -import org.solovyev.android.prefs.StringPreference; -import org.solovyev.common.MutableObject; -import org.solovyev.common.msg.MessageRegistry; -import org.solovyev.common.text.EnumMapper; -import org.solovyev.common.text.NumberMapper; -import org.solovyev.common.text.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 groupingSeparator = StringPreference.newInstance(GROUPING_SEPARATOR_P_KEY, JsclMathEngine.GROUPING_SEPARATOR_DEFAULT); - public static final Preference multiplicationSign = StringPreference.newInstance(MULTIPLICATION_SIGN_P_KEY, MULTIPLICATION_SIGN_DEFAULT); - public static final Preference precision = StringPreference.newInstance(RESULT_PRECISION_P_KEY, RESULT_PRECISION_DEFAULT, new NumberMapper(Integer.class)); - public static final Preference roundResult = new BooleanPreference(ROUND_RESULT_P_KEY, ROUND_RESULT_DEFAULT); - public static final Preference numeralBase = StringPreference.newInstance(NUMERAL_BASES_P_KEY, NUMERAL_BASES_DEFAULT, EnumMapper.newInstance(NumeralBase.class)); - public static final Preference angleUnit = StringPreference.newInstance(ANGLE_UNITS_P_KEY, ANGLE_UNITS_DEFAULT, EnumMapper.newInstance(AngleUnit.class)); - public static final Preference scienceNotation = new BooleanPreference(SCIENCE_NOTATION_P_KEY, SCIENCE_NOTATION_DEFAULT); - public static final Preference maxCalculationTime = StringPreference.newInstance(MAX_CALCULATION_TIME_P_KEY, MAX_CALCULATION_TIME_DEFAULT, new NumberMapper(Integer.class)); - - private static final List preferenceKeys = new ArrayList(); - - 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 getPreferenceKeys() { - return Collections.unmodifiableList(preferenceKeys); - } - } - - @NotNull - private final Object lock = new Object(); - - @NotNull - private MathEngine engine = JsclMathEngine.instance; - - @NotNull - public final TextProcessor preprocessor = ToJsclTextProcessor.getInstance(); - - @NotNull - private final AndroidMathRegistry varsRegistry = new AndroidVarsRegistryImpl(engine.getConstantsRegistry()); - - @NotNull - private final AndroidMathRegistry functionsRegistry = new AndroidFunctionsMathRegistry(engine.getFunctionsRegistry()); - - @NotNull - private final AndroidMathRegistry operatorsRegistry = new AndroidOperatorsMathRegistry(engine.getOperatorsRegistry()); - - private final AndroidMathRegistry postfixFunctionsRegistry = new AndroidPostfixFunctionsRegistry(engine.getPostfixFunctionsRegistry()); - - @Nullable - 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() { - @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 calculationResult = new MutableObject(null); - final MutableObject parseException = new MutableObject(null); - final MutableObject evalException = new MutableObject(null); - final MutableObject calculationThread = new MutableObject(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) { - if (threadKiller != null) { - 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 getVarsRegistry() { - return varsRegistry; - } - - @NotNull - public AndroidMathRegistry getFunctionsRegistry() { - return functionsRegistry; - } - - @NotNull - public AndroidMathRegistry getOperatorsRegistry() { - return operatorsRegistry; - } - - @NotNull - public AndroidMathRegistry 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(@Nullable 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(); - } - } -} +/* + * 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.CalculatorApplication; +import org.solovyev.android.calculator.R; +import org.solovyev.android.calculator.jscl.JsclOperation; +import org.solovyev.android.prefs.BooleanPreference; +import org.solovyev.android.prefs.Preference; +import org.solovyev.android.prefs.StringPreference; +import org.solovyev.common.MutableObject; +import org.solovyev.common.msg.MessageRegistry; +import org.solovyev.common.text.EnumMapper; +import org.solovyev.common.text.NumberMapper; +import org.solovyev.common.text.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 groupingSeparator = StringPreference.newInstance(GROUPING_SEPARATOR_P_KEY, JsclMathEngine.GROUPING_SEPARATOR_DEFAULT); + public static final Preference multiplicationSign = StringPreference.newInstance(MULTIPLICATION_SIGN_P_KEY, MULTIPLICATION_SIGN_DEFAULT); + public static final Preference precision = StringPreference.newInstance(RESULT_PRECISION_P_KEY, RESULT_PRECISION_DEFAULT, new NumberMapper(Integer.class)); + public static final Preference roundResult = new BooleanPreference(ROUND_RESULT_P_KEY, ROUND_RESULT_DEFAULT); + public static final Preference numeralBase = StringPreference.newInstance(NUMERAL_BASES_P_KEY, NUMERAL_BASES_DEFAULT, EnumMapper.newInstance(NumeralBase.class)); + public static final Preference angleUnit = StringPreference.newInstance(ANGLE_UNITS_P_KEY, ANGLE_UNITS_DEFAULT, EnumMapper.newInstance(AngleUnit.class)); + public static final Preference scienceNotation = new BooleanPreference(SCIENCE_NOTATION_P_KEY, SCIENCE_NOTATION_DEFAULT); + public static final Preference maxCalculationTime = StringPreference.newInstance(MAX_CALCULATION_TIME_P_KEY, MAX_CALCULATION_TIME_DEFAULT, new NumberMapper(Integer.class)); + + private static final List preferenceKeys = new ArrayList(); + + 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 getPreferenceKeys() { + return Collections.unmodifiableList(preferenceKeys); + } + } + + @NotNull + private final Object lock = new Object(); + + @NotNull + private MathEngine engine = JsclMathEngine.instance; + + @NotNull + public final TextProcessor preprocessor = ToJsclTextProcessor.getInstance(); + + @NotNull + private final AndroidMathRegistry varsRegistry = new AndroidVarsRegistryImpl(engine.getConstantsRegistry()); + + @NotNull + private final AndroidMathRegistry functionsRegistry = new AndroidFunctionsMathRegistry(engine.getFunctionsRegistry()); + + @NotNull + private final AndroidMathRegistry operatorsRegistry = new AndroidOperatorsMathRegistry(engine.getOperatorsRegistry()); + + private final AndroidMathRegistry postfixFunctionsRegistry = new AndroidPostfixFunctionsRegistry(engine.getPostfixFunctionsRegistry()); + + @Nullable + 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() { + @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 calculationResult = new MutableObject(null); + final MutableObject parseException = new MutableObject(null); + final MutableObject evalException = new MutableObject(null); + final MutableObject calculationThread = new MutableObject(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(R.string.msg_1, CalculatorApplication.getInstance(), jsclExpression, e.getMessage())); + } catch (StackOverflowError e) { + //System.out.println(StringUtils.fromStackTrace(e.getStackTrace())); + parseException.setObject(new CalculatorParseException(R.string.msg_2, CalculatorApplication.getInstance(), 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) { + if (threadKiller != null) { + 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(R.string.msg_3, CalculatorApplication.getInstance(), jsclExpression); + } + + } catch (InterruptedException e) { + throw new CalculatorParseException(R.string.msg_4, CalculatorApplication.getInstance(), 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 getVarsRegistry() { + return varsRegistry; + } + + @NotNull + public AndroidMathRegistry getFunctionsRegistry() { + return functionsRegistry; + } + + @NotNull + public AndroidMathRegistry getOperatorsRegistry() { + return operatorsRegistry; + } + + @NotNull + public AndroidMathRegistry 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(@Nullable 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(); + } + } +} diff --git a/calculatorpp/src/main/java/org/solovyev/android/calculator/model/CalculatorParseException.java b/calculatorpp/src/main/java/org/solovyev/android/calculator/model/CalculatorParseException.java index 176e1492..e164578f 100644 --- a/calculatorpp/src/main/java/org/solovyev/android/calculator/model/CalculatorParseException.java +++ b/calculatorpp/src/main/java/org/solovyev/android/calculator/model/CalculatorParseException.java @@ -1,90 +1,91 @@ -/* - * 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 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); - } -} +/* + * 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.app.Application; +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 Integer messageId, @NotNull Application application, @Nullable Integer position, @NotNull String expression, Object... parameters) { + this.message = new AndroidMessage(messageId, MessageType.error, application, parameters); + this.expression = expression; + this.position = position; + } + + public CalculatorParseException(@NotNull Integer messageId, @NotNull Application application, @NotNull String expression, Object... parameters) { + this(messageId, application, 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 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); + } +} diff --git a/calculatorpp/src/main/java/org/solovyev/android/calculator/model/ToJsclTextProcessor.java b/calculatorpp/src/main/java/org/solovyev/android/calculator/model/ToJsclTextProcessor.java index 4430f6b2..d12f3ca2 100644 --- a/calculatorpp/src/main/java/org/solovyev/android/calculator/model/ToJsclTextProcessor.java +++ b/calculatorpp/src/main/java/org/solovyev/android/calculator/model/ToJsclTextProcessor.java @@ -1,145 +1,147 @@ -/* - * 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.collections.CollectionsUtils; - -import java.util.ArrayList; -import java.util.List; - -public class ToJsclTextProcessor implements TextProcessor { - - @NotNull - private static final Integer MAX_DEPTH = 20; - - @NotNull - private static final TextProcessor instance = new ToJsclTextProcessor(); - - private ToJsclTextProcessor() { - } - - - @NotNull - public static TextProcessor getInstance() { - return instance; - } - - @Override - @NotNull - public PreparedExpression process(@NotNull String s) throws CalculatorParseException { - return processWithDepth(s, 0, new ArrayList()); - } - - private static PreparedExpression processWithDepth(@NotNull String s, int depth, @NotNull List 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 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); - } -} +/* + * 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.android.calculator.CalculatorApplication; +import org.solovyev.android.calculator.R; +import org.solovyev.common.StartsWithFinder; +import org.solovyev.android.calculator.math.MathType; +import org.solovyev.common.collections.CollectionsUtils; + +import java.util.ArrayList; +import java.util.List; + +public class ToJsclTextProcessor implements TextProcessor { + + @NotNull + private static final Integer MAX_DEPTH = 20; + + @NotNull + private static final TextProcessor instance = new ToJsclTextProcessor(); + + private ToJsclTextProcessor() { + } + + + @NotNull + public static TextProcessor getInstance() { + return instance; + } + + @Override + @NotNull + public PreparedExpression process(@NotNull String s) throws CalculatorParseException { + return processWithDepth(s, 0, new ArrayList()); + } + + private static PreparedExpression processWithDepth(@NotNull String s, int depth, @NotNull List 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(R.string.msg_5, CalculatorApplication.getInstance(), i, s, mathTypeBefore.getMatch()); + } + + i = mathTypeResult.processToJscl(result, i); + } + return result; + } + + @NotNull + private static PreparedExpression replaceVariables(@NotNull final String s, int depth, @NotNull List undefinedVars) throws CalculatorParseException { + if (depth >= MAX_DEPTH) { + throw new CalculatorParseException(R.string.msg_6, CalculatorApplication.getInstance(), 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); + } +}