From 38152fe80d3103753884ac311e3a3b9ced413947 Mon Sep 17 00:00:00 2001 From: Sergey Solovyev Date: Sun, 18 Dec 2011 00:47:58 +0400 Subject: [PATCH] android_calculator-4: Extend history functionality --- res/values/default_values.xml | 1 + res/values/strings.xml | 3 + .../calculator/AbstractHistoryState.java | 27 -- .../calculator/CalculatorActivity.java | 1 + .../android/calculator/CalculatorDisplay.java | 20 +- .../android/calculator/CalculatorHistory.java | 83 ----- .../calculator/CalculatorHistoryActivity.java | 79 ++++- .../android/calculator/CalculatorModel.java | 32 +- .../calculator/ICalculatorDisplay.java | 40 +++ .../history/AbstractHistoryState.java | 59 ++++ .../CalculatorDisplayHistoryState.java | 44 ++- .../calculator/history/CalculatorHistory.java | 113 +++++++ .../{ => history}/CalculatorHistoryState.java | 28 +- .../android/calculator/history/Editor.java | 27 ++ .../{ => history}/EditorHistoryState.java | 21 +- .../android/calculator/history/History.java | 33 ++ .../calculator/history/HistoryUtils.java | 61 ++++ .../history/TextViewEditorAdapter.java | 49 +++ .../jscl/FromJsclNumericTextProcessor.java | 2 + .../calculator/jscl/JsclOperation.java | 89 +++-- .../calculator/model/AndroidVarsRegistry.java | 2 +- .../model/AndroidVarsRegistryImpl.java | 2 +- .../calculator/model/CalculatorEngine.java | 2 +- .../model/FromJsclSimplifyTextProcessor.java | 2 + .../org/solovyev/common/SameEqualizer.java | 29 ++ .../calculator/history/HistoryUtilsTest.java | 306 ++++++++++++++++++ 26 files changed, 940 insertions(+), 215 deletions(-) delete mode 100644 src/main/java/org/solovyev/android/calculator/AbstractHistoryState.java delete mode 100644 src/main/java/org/solovyev/android/calculator/CalculatorHistory.java create mode 100644 src/main/java/org/solovyev/android/calculator/ICalculatorDisplay.java create mode 100644 src/main/java/org/solovyev/android/calculator/history/AbstractHistoryState.java rename src/main/java/org/solovyev/android/calculator/{ => history}/CalculatorDisplayHistoryState.java (66%) create mode 100644 src/main/java/org/solovyev/android/calculator/history/CalculatorHistory.java rename src/main/java/org/solovyev/android/calculator/{ => history}/CalculatorHistoryState.java (63%) create mode 100644 src/main/java/org/solovyev/android/calculator/history/Editor.java rename src/main/java/org/solovyev/android/calculator/{ => history}/EditorHistoryState.java (71%) create mode 100644 src/main/java/org/solovyev/android/calculator/history/History.java create mode 100644 src/main/java/org/solovyev/android/calculator/history/HistoryUtils.java create mode 100644 src/main/java/org/solovyev/android/calculator/history/TextViewEditorAdapter.java create mode 100644 src/main/java/org/solovyev/common/SameEqualizer.java create mode 100644 src/test/java/org/solovyev/android/calculator/history/HistoryUtilsTest.java diff --git a/res/values/default_values.xml b/res/values/default_values.xml index e6ebd39e..ae2146e9 100644 --- a/res/values/default_values.xml +++ b/res/values/default_values.xml @@ -28,6 +28,7 @@ true org.solovyev.android.calculator.CalculatorModel_vars + org.solovyev.android.calculator.CalculatorModel_history org.solovyev.android.calculator.CalculatorActivity_angle_units deg diff --git a/res/values/strings.xml b/res/values/strings.xml index 304fe2fd..6caaac91 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -299,4 +299,7 @@ Check the \'Round result\' preference in application settings - it should be tur Swipe distance for buttons Sets swipe distance for buttons that support additional swipe actions + Comment + Saved + diff --git a/src/main/java/org/solovyev/android/calculator/AbstractHistoryState.java b/src/main/java/org/solovyev/android/calculator/AbstractHistoryState.java deleted file mode 100644 index d702df76..00000000 --- a/src/main/java/org/solovyev/android/calculator/AbstractHistoryState.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2009-2011. Created by serso aka se.solovyev. - * For more information, please, contact se.solovyev@gmail.com - * or visit http://se.solovyev.org - */ - -package org.solovyev.android.calculator; - -import org.jetbrains.annotations.NotNull; - -import java.util.Date; - -/** - * User: serso - * Date: 10/15/11 - * Time: 1:45 PM - */ -public class AbstractHistoryState { - - @NotNull - private final Date time = new Date(); - - @NotNull - public Date getTime() { - return time; - } -} diff --git a/src/main/java/org/solovyev/android/calculator/CalculatorActivity.java b/src/main/java/org/solovyev/android/calculator/CalculatorActivity.java index 24aa94a9..c70de6ae 100644 --- a/src/main/java/org/solovyev/android/calculator/CalculatorActivity.java +++ b/src/main/java/org/solovyev/android/calculator/CalculatorActivity.java @@ -26,6 +26,7 @@ import jscl.AngleUnit; import jscl.NumeralBase; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.solovyev.android.calculator.history.CalculatorHistoryState; import org.solovyev.android.calculator.math.MathType; import org.solovyev.android.calculator.model.CalculatorEngine; import org.solovyev.android.view.FontSizeAdjuster; diff --git a/src/main/java/org/solovyev/android/calculator/CalculatorDisplay.java b/src/main/java/org/solovyev/android/calculator/CalculatorDisplay.java index 32566ee5..c9d48ac6 100644 --- a/src/main/java/org/solovyev/android/calculator/CalculatorDisplay.java +++ b/src/main/java/org/solovyev/android/calculator/CalculatorDisplay.java @@ -24,7 +24,7 @@ import org.solovyev.android.view.AutoResizeTextView; * Date: 9/17/11 * Time: 10:58 PM */ -public class CalculatorDisplay extends AutoResizeTextView { +public class CalculatorDisplay extends AutoResizeTextView implements ICalculatorDisplay{ private boolean valid = true; @@ -52,10 +52,12 @@ public class CalculatorDisplay extends AutoResizeTextView { super(context, attrs, defStyle); } + @Override public boolean isValid() { return valid; } + @Override public void setValid(boolean valid) { this.valid = valid; if (valid) { @@ -63,19 +65,23 @@ public class CalculatorDisplay extends AutoResizeTextView { } } + @Override @Nullable public String getErrorMessage() { return errorMessage; } + @Override public void setErrorMessage(@Nullable String errorMessage) { this.errorMessage = errorMessage; } + @Override public void setJsclOperation(@NotNull JsclOperation jsclOperation) { this.jsclOperation = jsclOperation; } + @Override @NotNull public JsclOperation getJsclOperation() { return jsclOperation; @@ -111,12 +117,24 @@ public class CalculatorDisplay extends AutoResizeTextView { resizeText(); } + @Override public void setGenericResult(@Nullable Generic genericResult) { this.genericResult = genericResult; } + @Override @Nullable public Generic getGenericResult() { return genericResult; } + + @Override + public int getSelection() { + return this.getSelectionStart(); + } + + @Override + public void setSelection(int selection) { + // not supported by TextView + } } diff --git a/src/main/java/org/solovyev/android/calculator/CalculatorHistory.java b/src/main/java/org/solovyev/android/calculator/CalculatorHistory.java deleted file mode 100644 index af285c06..00000000 --- a/src/main/java/org/solovyev/android/calculator/CalculatorHistory.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2009-2011. Created by serso aka se.solovyev. - * For more information, please, contact se.solovyev@gmail.com - * or visit http://se.solovyev.org - */ - -package org.solovyev.android.calculator; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.solovyev.common.utils.history.HistoryAction; -import org.solovyev.common.utils.history.HistoryHelper; -import org.solovyev.common.utils.history.SimpleHistoryHelper; - -import java.util.List; - -/** - * User: serso - * Date: 10/9/11 - * Time: 6:35 PM - */ -public enum CalculatorHistory implements HistoryHelper { - - instance; - - private final HistoryHelper historyHelper = new SimpleHistoryHelper(); - - @Override - public boolean isEmpty() { - return this.historyHelper.isEmpty(); - } - - @Override - public CalculatorHistoryState getLastHistoryState() { - return this.historyHelper.getLastHistoryState(); - } - - @Override - public boolean isUndoAvailable() { - return historyHelper.isUndoAvailable(); - } - - @Override - public CalculatorHistoryState undo(@Nullable CalculatorHistoryState currentState) { - return historyHelper.undo(currentState); - } - - @Override - public boolean isRedoAvailable() { - return historyHelper.isRedoAvailable(); - } - - @Override - public CalculatorHistoryState redo(@Nullable CalculatorHistoryState currentState) { - return historyHelper.redo(currentState); - } - - @Override - public boolean isActionAvailable(@NotNull HistoryAction historyAction) { - return historyHelper.isActionAvailable(historyAction); - } - - @Override - public CalculatorHistoryState doAction(@NotNull HistoryAction historyAction, @Nullable CalculatorHistoryState currentState) { - return historyHelper.doAction(historyAction, currentState); - } - - @Override - public void addState(@Nullable CalculatorHistoryState currentState) { - historyHelper.addState(currentState); - } - - @NotNull - @Override - public List getStates() { - return historyHelper.getStates(); - } - - @Override - public void clear() { - this.historyHelper.clear(); - } -} diff --git a/src/main/java/org/solovyev/android/calculator/CalculatorHistoryActivity.java b/src/main/java/org/solovyev/android/calculator/CalculatorHistoryActivity.java index 5c952df6..bee0c42d 100644 --- a/src/main/java/org/solovyev/android/calculator/CalculatorHistoryActivity.java +++ b/src/main/java/org/solovyev/android/calculator/CalculatorHistoryActivity.java @@ -6,13 +6,20 @@ package org.solovyev.android.calculator; +import android.app.AlertDialog; import android.app.ListActivity; import android.content.Context; +import android.content.DialogInterface; import android.os.Bundle; +import android.preference.PreferenceManager; import android.view.*; import android.widget.*; import org.jetbrains.annotations.NotNull; +import org.solovyev.android.calculator.history.CalculatorHistory; +import org.solovyev.android.calculator.history.CalculatorHistoryState; +import org.solovyev.android.calculator.history.EditorHistoryState; import org.solovyev.android.calculator.jscl.JsclOperation; +import org.solovyev.android.view.prefs.ResourceCache; import org.solovyev.common.utils.Filter; import org.solovyev.common.utils.FilterRule; import org.solovyev.common.utils.FilterRulesChain; @@ -31,6 +38,9 @@ import java.util.List; */ public class CalculatorHistoryActivity extends ListActivity { + @NotNull + private HistoryArrayAdapter adapter; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -43,7 +53,8 @@ public class CalculatorHistoryActivity extends ListActivity { this.finish(); } - setListAdapter(new HistoryArrayAdapter(this, R.layout.history, R.id.history_item, historyList)); + adapter = new HistoryArrayAdapter(this, R.layout.history, R.id.history_item, historyList); + setListAdapter(adapter); final ListView lv = getListView(); lv.setTextFilterEnabled(true); @@ -70,15 +81,62 @@ public class CalculatorHistoryActivity extends ListActivity { CalculatorHistoryActivity.this.finish(); } }); + + lv.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { + @Override + public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { + final CalculatorHistoryState historyState = (CalculatorHistoryState) parent.getItemAtPosition(position); + + final Context context = CalculatorHistoryActivity.this; + + final CharSequence[] items = {context.getText(R.string.c_save), context.getText(R.string.c_remove)}; + + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setItems(items, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + if (item == 0) { + if (!historyState.isSaved()) { + historyState.setSaved(true); + CalculatorHistory.instance.getSavedHistory().addState(historyState); + CalculatorHistory.instance.save(context); + CalculatorHistoryActivity.this.adapter.notifyDataSetChanged(); + Toast.makeText(context, "History item was successfully saved!", Toast.LENGTH_LONG).show(); + } else { + Toast.makeText(context, "History item was already saved!", Toast.LENGTH_LONG).show(); + } + } else if (item == 1) { + if (historyState.isSaved()) { + historyState.setSaved(false); + CalculatorHistory.instance.save(context); + CalculatorHistory.instance.getSavedHistory().clear(); + CalculatorHistory.instance.load(context, PreferenceManager.getDefaultSharedPreferences(context)); + CalculatorHistoryActivity.this.adapter.notifyDataSetChanged(); + Toast.makeText(context, "History item was removed!", Toast.LENGTH_LONG).show(); + } + } + } + }); + builder.create().show(); + return true; + } + }); } private static List getHistoryList() { final List calculatorHistoryStates = new ArrayList(CalculatorHistory.instance.getStates()); + calculatorHistoryStates.addAll(CalculatorHistory.instance.getSavedHistory().getStates()); Collections.sort(calculatorHistoryStates, new Comparator() { @Override public int compare(CalculatorHistoryState state1, CalculatorHistoryState state2) { - return state2.getTime().compareTo(state1.getTime()); + if ( state1.isSaved() == state2.isSaved() ) { + return state2.getTime().compareTo(state1.getTime()); + } else if ( state1.isSaved() ) { + return -1; + } else if ( state2.isSaved() ) { + return 1; + } + return 0; } }); @@ -111,7 +169,21 @@ public class CalculatorHistoryActivity extends ListActivity { time.setText(new SimpleDateFormat().format(state.getTime())); final TextView editor = (TextView) result.findViewById(R.id.history_item); - editor.setText(state.getEditorState().getText() + getIdentitySign(state.getDisplayState().getJsclOperation()) + state.getDisplayState().getEditorHistoryState().getText()); + final StringBuilder historyText = new StringBuilder(); + historyText.append(state.getEditorState().getText()); + historyText.append(getIdentitySign(state.getDisplayState().getJsclOperation())); + historyText.append(state.getDisplayState().getEditorState().getText()); + final String comment = state.getComment(); + if (!StringUtils.isEmpty(comment)) { + historyText.append("\n"); + historyText.append(ResourceCache.instance.getCaption("c_comment")).append(": "); + historyText.append(comment); + } + if ( state.isSaved() ) { + historyText.append("\n"); + historyText.append(ResourceCache.instance.getCaption("c_history_item_saved")); + } + editor.setText(historyText); return result; } @@ -147,6 +219,7 @@ public class CalculatorHistoryActivity extends ListActivity { private void clearHistory() { CalculatorHistory.instance.clear(); + Toast.makeText(this, R.string.c_history_is_empty, Toast.LENGTH_SHORT).show(); this.finish(); } diff --git a/src/main/java/org/solovyev/android/calculator/CalculatorModel.java b/src/main/java/org/solovyev/android/calculator/CalculatorModel.java index 7e69fa90..659ab3c6 100644 --- a/src/main/java/org/solovyev/android/calculator/CalculatorModel.java +++ b/src/main/java/org/solovyev/android/calculator/CalculatorModel.java @@ -22,6 +22,9 @@ import jscl.math.Generic; import jscl.math.function.Constant; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.solovyev.android.calculator.history.CalculatorHistory; +import org.solovyev.android.calculator.history.CalculatorHistoryState; +import org.solovyev.android.calculator.history.TextViewEditorAdapter; import org.solovyev.android.calculator.jscl.JsclOperation; import org.solovyev.android.calculator.math.MathType; import org.solovyev.android.calculator.model.CalculatorEngine; @@ -335,8 +338,8 @@ public enum CalculatorModel implements CursorControl, HistoryControl\n" + + " \n" + + ""; + + private static final String toXml1 = "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " 3\n" + + " 1+1\n" + + " \n" + + " \n" + + " \n" + + " 1\n" + + " Error\n" + + " \n" + + " simplify\n" + + " \n" + + " \n" + + " \n" + + ""; + + private static final String toXml2 = "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " 3\n" + + " 1+1\n" + + " \n" + + " \n" + + " \n" + + " 1\n" + + " Error\n" + + " \n" + + " simplify\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " 2\n" + + " 5/6\n" + + " \n" + + " \n" + + " \n" + + " 3\n" + + " 5/6\n" + + " \n" + + " numeric\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " 1\n" + + " null\n" + + " \n" + + " \n" + + " \n" + + " 1\n" + + " Error\n" + + " \n" + + " elementary\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " 0\n" + + " 4+5/35sin(41)+dfdsfsdfs\n" + + " \n" + + " \n" + + " \n" + + " 1\n" + + " 4+5/35sin(41)+dfdsfsdfs\n" + + " \n" + + " numeric\n" + + " \n" + + " \n" + + " \n" + + ""; + + @Test + public void testToXml() throws Exception { + final Date date = new Date(100000000); + + HistoryHelper history = new SimpleHistoryHelper(); + + ICalculatorDisplay calculatorDisplay = new TestCalculatorDisplay(); + calculatorDisplay.setErrorMessage("error_msg1"); + calculatorDisplay.setText("Error"); + calculatorDisplay.setSelection(1); + calculatorDisplay.setJsclOperation(JsclOperation.simplify); + + Editor calculatorEditor = new TestEditor(); + calculatorEditor.setSelection(3); + calculatorEditor.setText("1+1"); + + CalculatorHistoryState state = CalculatorHistoryState.newInstance(calculatorEditor, calculatorDisplay); + state.setTime(date); + history.addState(state); + + Assert.assertEquals(emptyHistory, HistoryUtils.toXml(history)); + + + state.setSaved(true); + + Assert.assertEquals(toXml1, HistoryUtils.toXml(history)); + + calculatorDisplay = new TestCalculatorDisplay(); + calculatorDisplay.setErrorMessage(null); + calculatorDisplay.setText("5/6"); + calculatorDisplay.setSelection(3); + calculatorDisplay.setJsclOperation(JsclOperation.numeric); + + calculatorEditor = new TestEditor(); + calculatorEditor.setSelection(2); + calculatorEditor.setText("5/6"); + + state = CalculatorHistoryState.newInstance(calculatorEditor, calculatorDisplay); + state.setSaved(true); + state.setTime(date); + history.addState(state); + + calculatorDisplay = new TestCalculatorDisplay(); + calculatorDisplay.setErrorMessage("error_msg2"); + calculatorDisplay.setText("Error"); + calculatorDisplay.setSelection(1); + calculatorDisplay.setJsclOperation(JsclOperation.elementary); + + calculatorEditor = new TestEditor(); + calculatorEditor.setSelection(1); + calculatorEditor.setText(null); + + state = CalculatorHistoryState.newInstance(calculatorEditor, calculatorDisplay); + state.setSaved(true); + state.setTime(date); + history.addState(state); + + calculatorDisplay = new TestCalculatorDisplay(); + calculatorDisplay.setErrorMessage(null); + calculatorDisplay.setText("4+5/35sin(41)+dfdsfsdfs"); + calculatorDisplay.setSelection(1); + calculatorDisplay.setJsclOperation(JsclOperation.numeric); + + calculatorEditor = new TestEditor(); + calculatorEditor.setSelection(0); + calculatorEditor.setText("4+5/35sin(41)+dfdsfsdfs"); + + state = CalculatorHistoryState.newInstance(calculatorEditor, calculatorDisplay); + state.setSaved(true); + state.setTime(date); + history.addState(state); + + String xml = HistoryUtils.toXml(history); + Assert.assertEquals(toXml2, xml); + + final HistoryHelper historyFromXml = new SimpleHistoryHelper(); + HistoryUtils.fromXml(xml, historyFromXml); + + Assert.assertEquals(history.getStates().size(), historyFromXml.getStates().size()); + + Assert.assertTrue(EqualsTool.areEqual(history.getStates(), historyFromXml.getStates(), new CollectionEqualizer(null))); + } + + + private static class TestCalculatorDisplay implements ICalculatorDisplay { + + @NotNull + private final TestEditor testEditor = new TestEditor(); + + private boolean valid; + + private String errorMessage; + + private JsclOperation operation; + + private Generic genericResult; + + @Override + public boolean isValid() { + return this.valid; + } + + @Override + public void setValid(boolean valid) { + this.valid = valid; + } + + @Override + public String getErrorMessage() { + return this.errorMessage; + } + + @Override + public void setErrorMessage(@Nullable String errorMessage) { + this.errorMessage = errorMessage; + } + + @Override + public void setJsclOperation(@NotNull JsclOperation jsclOperation) { + this.operation = jsclOperation; + } + + @NotNull + @Override + public JsclOperation getJsclOperation() { + return this.operation; + } + + @Override + public void setGenericResult(@Nullable Generic genericResult) { + this.genericResult = genericResult; + } + + @Override + public Generic getGenericResult() { + return this.genericResult; + } + + @Override + public CharSequence getText() { + return this.testEditor.getText(); + } + + @Override + public void setText(@Nullable CharSequence text) { + this.testEditor.setText(text); + } + + @Override + public int getSelection() { + return this.testEditor.getSelection(); + } + + @Override + public void setSelection(int selection) { + this.testEditor.setSelection(selection); + } + } + + private static class TestEditor implements Editor { + + @Nullable + private CharSequence text; + + private int selection; + + @Nullable + @Override + public CharSequence getText() { + return this.text; + } + + @Override + public void setText(@Nullable CharSequence text) { + this.text = text; + } + + @Override + public int getSelection() { + return this.selection; + } + + @Override + public void setSelection(int selection) { + this.selection = selection; + } + } +}