diff --git a/app/build.gradle b/app/build.gradle index 8be66346..87dd0323 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -34,7 +34,6 @@ android { versionCode 148 versionName '2.2.1' testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" - generatedDensities = [] } buildTypes { release { @@ -58,17 +57,13 @@ android { sourceCompatibility JavaVersion.VERSION_1_7 targetCompatibility JavaVersion.VERSION_1_7 } - // This is handled for you by the 2.0+ Gradle Plugin - aaptOptions { - additionalParameters "--no-version-vectors" - } } dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') - compile 'com.android.support:support-v4:23.2.1' - compile 'com.android.support:appcompat-v7:23.2.1' - compile 'com.android.support:design:23.2.1' + compile 'com.android.support:support-v4:23.3.0' + compile 'com.android.support:appcompat-v7:23.3.0' + compile 'com.android.support:design:23.3.0' compile('ch.acra:acra:4.7.0') { exclude group: 'org.json' } @@ -105,7 +100,7 @@ dependencies { testCompile 'org.skyscreamer:jsonassert:1.2.3' testCompile(name: 'org.apache.http.legacy', ext: 'jar') - androidTestCompile 'com.android.support:support-annotations:23.2.1' + androidTestCompile 'com.android.support:support-annotations:23.3.0' androidTestCompile 'com.android.support.test:runner:0.4.1' androidTestCompile 'com.android.support.test:rules:0.4.1' androidTestCompile 'org.hamcrest:hamcrest-library:1.3' diff --git a/app/src/main/assets/fonts/Roboto-Regular.ttf b/app/src/main/assets/fonts/Roboto-Regular.ttf index 0e58508a..cae96938 100644 Binary files a/app/src/main/assets/fonts/Roboto-Regular.ttf and b/app/src/main/assets/fonts/Roboto-Regular.ttf differ diff --git a/app/src/main/java/org/solovyev/android/calculator/Keyboard.java b/app/src/main/java/org/solovyev/android/calculator/Keyboard.java index 7d610c20..4b37ea06 100644 --- a/app/src/main/java/org/solovyev/android/calculator/Keyboard.java +++ b/app/src/main/java/org/solovyev/android/calculator/Keyboard.java @@ -25,11 +25,15 @@ package org.solovyev.android.calculator; import android.content.SharedPreferences; import android.support.annotation.NonNull; import android.text.TextUtils; +import android.util.Log; import com.squareup.otto.Bus; import dagger.Lazy; +import jscl.math.Expression; +import jscl.math.Generic; import org.solovyev.android.Check; import org.solovyev.android.calculator.buttons.CppSpecialButton; import org.solovyev.android.calculator.ga.Ga; +import org.solovyev.android.calculator.history.History; import org.solovyev.android.calculator.math.MathType; import org.solovyev.android.calculator.memory.Memory; @@ -49,6 +53,8 @@ public class Keyboard implements SharedPreferences.OnSharedPreferenceChangeListe @Inject Display display; @Inject + History history; + @Inject Lazy memory; @Inject Calculator calculator; @@ -74,10 +80,21 @@ public class Keyboard implements SharedPreferences.OnSharedPreferenceChangeListe return vibrateOnKeypress; } - public boolean buttonPressed(@Nullable final String text) { + public boolean buttonPressed(@Nullable String text) { if (TextUtils.isEmpty(text)) { return false; } + + if (text.length() == 1) { + final char glyph = text.charAt(0); + final CppSpecialButton button = CppSpecialButton.getByGlyph(glyph); + if (button != null) { + ga.onButtonPressed(button.action); + handleSpecialAction(button); + return true; + } + } + ga.onButtonPressed(text); if (!processSpecialAction(text)) { processText(prepareText(text)); @@ -127,20 +144,32 @@ public class Keyboard implements SharedPreferences.OnSharedPreferenceChangeListe if (button == null) { return false; } - onSpecialButtonPressed(button); + handleSpecialAction(button); return true; } - private void onSpecialButtonPressed(@NonNull CppSpecialButton button) { + private void handleSpecialAction(@NonNull CppSpecialButton button) { switch (button) { case history: launcher.showHistory(); break; + case history_undo: + history.undo(); + break; + case history_redo: + history.redo(); + break; case cursor_right: - moveCursorRight(); + editor.moveCursorRight(); + break; + case cursor_to_end: + editor.setCursorOnEnd(); break; case cursor_left: - moveCursorLeft(); + editor.moveCursorLeft(); + break; + case cursor_to_start: + editor.setCursorOnStart(); break; case settings: launcher.showSettings(); @@ -154,24 +183,48 @@ public class Keyboard implements SharedPreferences.OnSharedPreferenceChangeListe case memory: memory.get().requestValue(); break; + case memory_plus: + handleMemoryButton(true); + break; + case memory_minus: + handleMemoryButton(false); + break; + case memory_clear: + memory.get().clear(); + break; case erase: editor.erase(); break; case paste: - pasteButtonPressed(); + final String text = clipboard.get().getText(); + if (!TextUtils.isEmpty(text)) { + editor.insert(text); + } break; case copy: - copyButtonPressed(); + bus.get().post(new Display.CopyOperation()); + break; + case brackets_wrap: + handleBracketsWrap(); break; case equals: equalsButtonPressed(); break; case clear: - clearButtonPressed(); + editor.clear(); break; case functions: launcher.showFunctions(); break; + case function_add: + launcher.showFunctionEditor(); + break; + case var_add: + launcher.showConstantEditor(); + break; + case plot_add: + launcher.plotDisplayedExpression(); + break; case open_app: launcher.openApp(); break; @@ -181,6 +234,9 @@ public class Keyboard implements SharedPreferences.OnSharedPreferenceChangeListe case operators: launcher.showOperators(); break; + case simplify: + calculator.simplify(); + break; default: Check.shouldNotHappen(); } @@ -200,36 +256,36 @@ public class Keyboard implements SharedPreferences.OnSharedPreferenceChangeListe editor.setText(state.text); } - public void roundBracketsButtonPressed() { - EditorState viewState = editor.getState(); - - final int cursorPosition = viewState.selection; - final CharSequence oldText = viewState.text; - + public void handleBracketsWrap() { + final EditorState state = editor.getState(); + final int cursorPosition = state.selection; + final CharSequence oldText = state.text; editor.setText("(" + oldText.subSequence(0, cursorPosition) + ")" + oldText.subSequence(cursorPosition, oldText.length()), cursorPosition + 2); } - public void pasteButtonPressed() { - final String text = clipboard.get().getText(); - if (!TextUtils.isEmpty(text)) { - editor.insert(text); + private boolean handleMemoryButton(boolean plus) { + final DisplayState state = display.getState(); + if (!state.valid) { + return false; } - } - - public void clearButtonPressed() { - editor.clear(); - } - - public void copyButtonPressed() { - bus.get().post(new Display.CopyOperation()); - } - - public void moveCursorLeft() { - editor.moveCursorLeft(); - } - - public void moveCursorRight() { - editor.moveCursorRight(); + Generic value = state.getResult(); + if (value == null) { + try { + value = Expression.valueOf(state.text); + } catch (jscl.text.ParseException e) { + Log.w(App.TAG, e.getMessage(), e); + } + } + if (value == null) { + memory.get().requestShow(); + return false; + } + if (plus) { + memory.get().add(value); + } else { + memory.get().subtract(value); + } + return true; } @Override diff --git a/app/src/main/java/org/solovyev/android/calculator/Preferences.java b/app/src/main/java/org/solovyev/android/calculator/Preferences.java index 95a4c794..0622d50e 100644 --- a/app/src/main/java/org/solovyev/android/calculator/Preferences.java +++ b/app/src/main/java/org/solovyev/android/calculator/Preferences.java @@ -33,8 +33,6 @@ import android.support.annotation.*; import android.support.v7.view.ContextThemeWrapper; import android.text.TextUtils; import android.util.SparseArray; -import jscl.AngleUnit; -import jscl.NumeralBase; import org.solovyev.android.Check; import org.solovyev.android.calculator.about.AboutActivity; import org.solovyev.android.calculator.functions.FunctionsActivity; @@ -42,6 +40,7 @@ import org.solovyev.android.calculator.history.HistoryActivity; import org.solovyev.android.calculator.language.Languages; import org.solovyev.android.calculator.math.MathType; import org.solovyev.android.calculator.operators.OperatorsActivity; +import org.solovyev.android.calculator.preferences.PreferenceEntry; import org.solovyev.android.calculator.preferences.PreferencesActivity; import org.solovyev.android.calculator.variables.VariablesActivity; import org.solovyev.android.calculator.wizard.WizardActivity; @@ -285,7 +284,7 @@ public final class Preferences { return mode.getPreferenceNoError(preferences); } - public enum Theme { + public enum Theme implements PreferenceEntry { default_theme(R.style.Cpp_Theme_Gray), violet_theme(R.style.Cpp_Theme_Violet), @@ -294,12 +293,19 @@ public final class Preferences { metro_purple_theme(R.string.p_metro_purple_theme, R.style.Cpp_Theme_Metro_Purple, R.style.Cpp_Theme_Metro_Purple_Calculator, R.style.Cpp_Theme_Wizard, R.style.Cpp_Theme_Metro_Purple_Dialog, R.style.Cpp_Theme_Material_Dialog_Alert), metro_green_theme(R.string.p_metro_green_theme, R.style.Cpp_Theme_Metro_Green, R.style.Cpp_Theme_Metro_Green_Calculator, R.style.Cpp_Theme_Wizard, R.style.Cpp_Theme_Metro_Green_Dialog, R.style.Cpp_Theme_Material_Dialog_Alert), material_theme(R.string.cpp_theme_dark, R.style.Cpp_Theme_Material, R.style.Cpp_Theme_Material_Calculator), + material_black_theme(R.string.cpp_theme_black, R.style.Cpp_Theme_Material_Black, R.style.Cpp_Theme_Material_Black_Calculator) { + @NonNull + @Override + public String getName(@NonNull Context context) { + return context.getString(name, material_theme.getName(context)); + } + }, material_light_theme(R.string.cpp_theme_light, R.style.Cpp_Theme_Material_Light, R.style.Cpp_Theme_Material_Light_Calculator, R.style.Cpp_Theme_Wizard_Light, R.style.Cpp_Theme_Material_Light_Dialog, R.style.Cpp_Theme_Material_Light_Dialog_Alert); private static final SparseArray textColors = new SparseArray<>(); @StringRes - public final int name; + protected final int name; @StyleRes public final int theme; @StyleRes @@ -373,6 +379,18 @@ public final class Preferences { } return textColor; } + + @Override + @NonNull + public String getName(@NonNull Context context) { + return context.getString(name); + } + + @NonNull + @Override + public CharSequence getId() { + return name(); + } } public enum Mode { diff --git a/app/src/main/java/org/solovyev/android/calculator/buttons/CppButton.java b/app/src/main/java/org/solovyev/android/calculator/buttons/CppButton.java index 2a5c9cef..a8e57189 100644 --- a/app/src/main/java/org/solovyev/android/calculator/buttons/CppButton.java +++ b/app/src/main/java/org/solovyev/android/calculator/buttons/CppButton.java @@ -51,6 +51,7 @@ public enum CppButton { period(R.id.cpp_button_period, "."), brackets(R.id.cpp_button_round_brackets, "()"), + memory(R.id.cpp_button_memory, CppSpecialButton.memory), settings(R.id.cpp_button_settings, CppSpecialButton.settings), settings_widget(R.id.cpp_button_settings_widget, CppSpecialButton.settings_widget), like(R.id.cpp_button_like, CppSpecialButton.like), @@ -65,7 +66,7 @@ public enum CppButton { history(R.id.cpp_button_history, CppSpecialButton.history), /*operations*/ - multiplication(R.id.cpp_button_multiplication, "*"), + multiplication(R.id.cpp_button_multiplication, "×"), division(R.id.cpp_button_division, "/"), plus(R.id.cpp_button_plus, "+"), subtraction(R.id.cpp_button_subtraction, "−"), diff --git a/app/src/main/java/org/solovyev/android/calculator/buttons/CppSpecialButton.java b/app/src/main/java/org/solovyev/android/calculator/buttons/CppSpecialButton.java index b999dc15..ae1c4324 100644 --- a/app/src/main/java/org/solovyev/android/calculator/buttons/CppSpecialButton.java +++ b/app/src/main/java/org/solovyev/android/calculator/buttons/CppSpecialButton.java @@ -29,48 +29,99 @@ import javax.annotation.Nullable; import java.util.HashMap; import java.util.Map; +// see values/text_glyphs.xml for glyph constants public enum CppSpecialButton { history("history"), + history_undo("↶", '\ue007'), + history_redo("↷", '\ue008'), cursor_right("▷"), + cursor_to_end(">>"), cursor_left("◁"), + cursor_to_start("<<"), settings("settings"), settings_widget("settings_widget"), like("like"), memory("memory"), + memory_plus("M+"), + memory_minus("M-"), + memory_clear("MC"), erase("erase"), - paste("paste"), - copy("copy"), + paste("paste", '\uE000'), + copy("copy", '\uE001'), + brackets_wrap("(…)"), equals("="), clear("clear"), functions("functions"), + function_add("+ƒ"), + var_add("+π"), + plot_add("+plot", '\uE009'), open_app("open_app"), vars("vars"), - operators("operators"); + operators("operators"), + simplify("≡"); @Nonnull - private static Map buttonsByActions = new HashMap<>(); + private static final Map buttonsByActions = new HashMap<>(); + @Nonnull + private static final CppSpecialButton[] buttonsByGlyphs = new CppSpecialButton[values().length]; + private static final char FIRST_GLYPH = '\uE000'; @Nonnull public final String action; + public final char glyph; CppSpecialButton(@Nonnull String action) { + this(action, (char) 0); + } + + CppSpecialButton(@Nonnull String action, char glyph) { this.action = action; + this.glyph = glyph; } @Nullable public static CppSpecialButton getByAction(@Nonnull String action) { - initButtonsByActionsMap(); + initButtonsByActions(); return buttonsByActions.get(action); } - private static void initButtonsByActionsMap() { + private static void initButtonsByActions() { Check.isMainThread(); if (!buttonsByActions.isEmpty()) { return; } - for (CppSpecialButton specialButton : values()) { - buttonsByActions.put(specialButton.action, specialButton); + for (CppSpecialButton button : values()) { + buttonsByActions.put(button.action, button); + } + } + + @Nullable + public static CppSpecialButton getByGlyph(char glyph) { + initButtonsByGlyphs(); + final int position = glyphToPosition(glyph); + if (position < 0 || position >= buttonsByGlyphs.length) { + return null; + } + return buttonsByGlyphs[position]; + } + + private static int glyphToPosition(char glyph) { + return glyph - FIRST_GLYPH; + } + + private static void initButtonsByGlyphs() { + Check.isMainThread(); + if (buttonsByGlyphs[0] != null) { + return; + } + for (CppSpecialButton button : values()) { + if(button.glyph == 0) { + continue; + } + final int position = glyphToPosition(button.glyph); + Check.isNull(buttonsByGlyphs[position], "Glyph is already taken, glyph=" + button.glyph); + buttonsByGlyphs[position] = button; } } diff --git a/app/src/main/java/org/solovyev/android/calculator/keyboard/BaseKeyboardUi.java b/app/src/main/java/org/solovyev/android/calculator/keyboard/BaseKeyboardUi.java index c9adcd43..9e71605d 100644 --- a/app/src/main/java/org/solovyev/android/calculator/keyboard/BaseKeyboardUi.java +++ b/app/src/main/java/org/solovyev/android/calculator/keyboard/BaseKeyboardUi.java @@ -7,14 +7,16 @@ import android.content.SharedPreferences; import android.graphics.Typeface; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.TypedValue; import android.view.View; import android.widget.ImageView; import android.widget.TextView; import dagger.Lazy; +import org.solovyev.android.Check; import org.solovyev.android.calculator.*; -import org.solovyev.android.calculator.buttons.CppSpecialButton; +import org.solovyev.android.calculator.buttons.CppButton; import org.solovyev.android.calculator.memory.Memory; import org.solovyev.android.views.Adjuster; import org.solovyev.android.views.dragbutton.*; @@ -65,7 +67,18 @@ public abstract class BaseKeyboardUi implements SharedPreferences.OnSharedPrefer listener = new DirectionDragListener(application) { @Override protected boolean onDrag(@NonNull View view, @NonNull DragEvent event, @NonNull DragDirection direction) { - return Drag.hasDirectionText(view, direction) && BaseKeyboardUi.this.onDrag(view, direction); + if (!Drag.hasDirectionText(view, direction)) { + return false; + } + final DirectionDragView dragView = (DirectionDragView) view; + final String text = dragView.getText(direction).getValue(); + if (TextUtils.isEmpty(text)) { + // hasDirectionText should return false for empty text + Check.shouldNotHappen(); + return false; + } + keyboard.buttonPressed(text); + return true; } }; textScale = getTextScale(application); @@ -82,7 +95,15 @@ public abstract class BaseKeyboardUi implements SharedPreferences.OnSharedPrefer } } - protected abstract boolean onDrag(@NonNull View view, @NonNull DragDirection direction); + @Override + public void onClick(View v) { + final CppButton button = CppButton.getById(v.getId()); + if (button == null) { + Check.shouldNotHappen(); + return; + } + onClick(v, button.action); + } public void onCreateView(@Nonnull Activity activity, @Nonnull View view) { cast(activity.getApplication()).getComponent().inject(this); @@ -194,10 +215,6 @@ public abstract class BaseKeyboardUi implements SharedPreferences.OnSharedPrefer v.performHapticFeedback(KEYBOARD_TAP, FLAG_IGNORE_GLOBAL_SETTING | FLAG_IGNORE_VIEW_SETTING); } - protected final void onClick(@Nonnull View v, @Nonnull CppSpecialButton b) { - onClick(v, b.action); - } - private static class AdjusterHelper implements Adjuster.Helper { public static AdjusterHelper instance = new AdjusterHelper(); diff --git a/app/src/main/java/org/solovyev/android/calculator/keyboard/KeyboardUi.java b/app/src/main/java/org/solovyev/android/calculator/keyboard/KeyboardUi.java index 735a8cba..7c45b836 100644 --- a/app/src/main/java/org/solovyev/android/calculator/keyboard/KeyboardUi.java +++ b/app/src/main/java/org/solovyev/android/calculator/keyboard/KeyboardUi.java @@ -3,31 +3,23 @@ package org.solovyev.android.calculator.keyboard; import android.app.Activity; import android.app.Application; import android.content.SharedPreferences; -import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.util.Log; import android.view.View; -import android.widget.Button; import android.widget.ImageButton; import butterknife.Bind; import butterknife.ButterKnife; import jscl.NumeralBase; -import jscl.math.Expression; -import jscl.math.Generic; -import org.solovyev.android.calculator.*; -import org.solovyev.android.calculator.buttons.CppSpecialButton; -import org.solovyev.android.calculator.history.History; -import org.solovyev.android.calculator.view.AngleUnitsButton; -import org.solovyev.android.calculator.view.NumeralBasesButton; +import org.solovyev.android.calculator.Display; +import org.solovyev.android.calculator.Engine; +import org.solovyev.android.calculator.R; import org.solovyev.android.views.dragbutton.DirectionDragButton; import org.solovyev.android.views.dragbutton.DirectionDragImageButton; -import org.solovyev.android.views.dragbutton.DirectionDragView; -import org.solovyev.android.views.dragbutton.DragDirection; import javax.annotation.Nonnull; import javax.inject.Inject; -import static org.solovyev.android.calculator.Engine.Preferences.*; +import static org.solovyev.android.calculator.Engine.Preferences.multiplicationSign; +import static org.solovyev.android.calculator.Engine.Preferences.numeralBase; import static org.solovyev.android.views.dragbutton.DragDirection.*; public class KeyboardUi extends BaseKeyboardUi { @@ -53,10 +45,6 @@ public class KeyboardUi extends BaseKeyboardUi { @Bind(R.id.cpp_button_9) public DirectionDragButton button9; @Inject - History history; - @Inject - ActivityLauncher launcher; - @Inject Engine engine; @Inject Display display; @@ -85,10 +73,10 @@ public class KeyboardUi extends BaseKeyboardUi { DirectionDragButton bracketsButton; @Nullable @Bind(R.id.cpp_button_copy) - NumeralBasesButton copyButton; + DirectionDragImageButton copyButton; @Nullable @Bind(R.id.cpp_button_paste) - AngleUnitsButton pasteButton; + DirectionDragImageButton pasteButton; @Nullable @Bind(R.id.cpp_button_like) ImageButton likeButton; @@ -146,11 +134,9 @@ public class KeyboardUi extends BaseKeyboardUi { if (copyButton != null) { prepareButton(copyButton); - copyButton.setNumeralBase(numeralBase.getPreference(preferences)); } if (pasteButton != null) { prepareButton(pasteButton); - pasteButton.setAngleUnit(angleUnit.getPreference(preferences)); } prepareButton(likeButton); prepareButton(memoryButton); @@ -182,133 +168,11 @@ public class KeyboardUi extends BaseKeyboardUi { @Override public void onSharedPreferenceChanged(SharedPreferences preferences, String key) { super.onSharedPreferenceChanged(preferences, key); - if (angleUnit.isSameKey(key) && pasteButton != null) { - pasteButton.setAngleUnit(angleUnit.getPreference(preferences)); - } if (numeralBase.isSameKey(key)) { toggleNumericDigits(); - if (copyButton != null) { - copyButton.setNumeralBase(numeralBase.getPreference(preferences)); - } } if (multiplicationSign.isSameKey(key)) { multiplicationButton.setText(multiplicationSign.getPreference(preferences)); } } - - @Override - public void onClick(View v) { - switch (v.getId()) { - case R.id.cpp_button_0: - case R.id.cpp_button_1: - case R.id.cpp_button_2: - case R.id.cpp_button_3: - case R.id.cpp_button_4: - case R.id.cpp_button_5: - case R.id.cpp_button_6: - case R.id.cpp_button_7: - case R.id.cpp_button_8: - case R.id.cpp_button_9: - case R.id.cpp_button_division: - case R.id.cpp_button_period: - case R.id.cpp_button_percent: - case R.id.cpp_button_subtraction: - case R.id.cpp_button_multiplication: - case R.id.cpp_button_plus: - case R.id.cpp_button_round_brackets: - onClick(v, ((Button) v).getText().toString()); - break; - case R.id.cpp_button_functions: - onClick(v, CppSpecialButton.functions); - break; - case R.id.cpp_button_history: - onClick(v, CppSpecialButton.history); - break; - case R.id.cpp_button_paste: - onClick(v, CppSpecialButton.paste); - break; - case R.id.cpp_button_copy: - onClick(v, CppSpecialButton.copy); - break; - case R.id.cpp_button_like: - onClick(v, CppSpecialButton.like); - break; - case R.id.cpp_button_memory: - onClick(v, CppSpecialButton.memory); - break; - case R.id.cpp_button_operators: - onClick(v, CppSpecialButton.operators); - break; - case R.id.cpp_button_vars: - onClick(v, CppSpecialButton.vars); - break; - } - } - - @Override - protected boolean onDrag(@NonNull View view, @NonNull DragDirection direction) { - switch (view.getId()) { - case R.id.cpp_button_functions: - if (direction == up) { - launcher.showFunctionEditor(); - return true; - } else if (direction == down) { - launcher.showConstantEditor(); - return true; - } - return false; - case R.id.cpp_button_history: - if (direction == up) { - history.undo(); - return true; - } else if (direction == down) { - history.redo(); - return true; - } - return false; - case R.id.cpp_button_memory: - return processMemoryButton(direction); - case R.id.cpp_button_round_brackets: - if (direction == left) { - keyboard.roundBracketsButtonPressed(); - return true; - } - return processDefault(direction, (DirectionDragView) view); - default: - return processDefault(direction, (DirectionDragView) view); - } - } - - private boolean processMemoryButton(@NonNull DragDirection direction) { - final DisplayState state = display.getState(); - if (!state.valid) { - return false; - } - Generic value = state.getResult(); - if (value == null) { - try { - value = Expression.valueOf(state.text); - } catch (jscl.text.ParseException e) { - Log.w(App.TAG, e.getMessage(), e); - } - } - if (value == null) { - memory.get().requestShow(); - return false; - } - switch (direction) { - case up: - memory.get().add(value); - return true; - case down: - memory.get().subtract(value); - return true; - } - return false; - } - - private boolean processDefault(@Nonnull DragDirection direction, @Nonnull DirectionDragView button) { - final String text = button.getText(direction).getValue(); - return keyboard.buttonPressed(text); - } } \ No newline at end of file diff --git a/app/src/main/java/org/solovyev/android/calculator/keyboard/PartialKeyboardUi.java b/app/src/main/java/org/solovyev/android/calculator/keyboard/PartialKeyboardUi.java index e9889b76..94a4c58a 100644 --- a/app/src/main/java/org/solovyev/android/calculator/keyboard/PartialKeyboardUi.java +++ b/app/src/main/java/org/solovyev/android/calculator/keyboard/PartialKeyboardUi.java @@ -10,18 +10,15 @@ import android.widget.ImageButton; import butterknife.Bind; import butterknife.ButterKnife; import org.solovyev.android.calculator.R; -import org.solovyev.android.calculator.buttons.CppSpecialButton; import org.solovyev.android.calculator.view.EditorLongClickEraser; import org.solovyev.android.views.dragbutton.DirectionDragButton; import org.solovyev.android.views.dragbutton.DirectionDragImageButton; -import org.solovyev.android.views.dragbutton.DragDirection; import javax.annotation.Nonnull; import javax.inject.Inject; import static org.solovyev.android.calculator.Preferences.Gui.vibrateOnKeypress; import static org.solovyev.android.views.dragbutton.DragDirection.down; -import static org.solovyev.android.views.dragbutton.DragDirection.up; public class PartialKeyboardUi extends BaseKeyboardUi { @@ -73,54 +70,4 @@ public class PartialKeyboardUi extends BaseKeyboardUi { longClickEraser.setVibrateOnKeypress(vibrateOnKeypress.getPreference(preferences)); } } - - @Override - protected boolean onDrag(@NonNull View view, @NonNull DragDirection direction) { - switch (view.getId()) { - case R.id.cpp_button_right: - editor.setCursorOnEnd(); - return true; - case R.id.cpp_button_left: - editor.setCursorOnStart(); - return true; - case R.id.cpp_button_clear: - if(direction == up) { - memory.get().clear(); - return true; - } - return false; - case R.id.cpp_button_equals: - if (direction == down) { - launcher.plotDisplayedExpression(); - return true; - } else if (direction == up) { - calculator.simplify(); - return true; - } - - return false; - } - return false; - } - - @Override - public void onClick(View v) { - switch (v.getId()) { - case R.id.cpp_button_left: - onClick(v, CppSpecialButton.cursor_left); - break; - case R.id.cpp_button_right: - onClick(v, CppSpecialButton.cursor_right); - break; - case R.id.cpp_button_clear: - onClick(v, CppSpecialButton.clear); - break; - case R.id.cpp_button_erase: - onClick(v, CppSpecialButton.erase); - break; - case R.id.cpp_button_equals: - onClick(v, CppSpecialButton.equals); - break; - } - } } diff --git a/app/src/main/java/org/solovyev/android/calculator/language/Language.java b/app/src/main/java/org/solovyev/android/calculator/language/Language.java index dab1dfb9..21d0a2a0 100644 --- a/app/src/main/java/org/solovyev/android/calculator/language/Language.java +++ b/app/src/main/java/org/solovyev/android/calculator/language/Language.java @@ -1,15 +1,15 @@ package org.solovyev.android.calculator.language; import android.content.Context; +import android.support.annotation.NonNull; import android.text.TextUtils; - import org.solovyev.android.calculator.R; - -import java.util.Locale; +import org.solovyev.android.calculator.preferences.PreferenceEntry; import javax.annotation.Nonnull; +import java.util.Locale; -public final class Language { +public final class Language implements PreferenceEntry{ @Nonnull public final String code; @@ -39,6 +39,7 @@ public final class Language { return locale.getDisplayName(locale); } + @Override @Nonnull public String getName(@Nonnull Context context) { if (!isSystem()) { @@ -48,6 +49,12 @@ public final class Language { } } + @NonNull + @Override + public CharSequence getId() { + return code; + } + public boolean isSystem() { return code.equals(Languages.SYSTEM_LANGUAGE_CODE); } diff --git a/app/src/main/java/org/solovyev/android/calculator/preferences/PreferenceEntry.java b/app/src/main/java/org/solovyev/android/calculator/preferences/PreferenceEntry.java new file mode 100644 index 00000000..21aebcb2 --- /dev/null +++ b/app/src/main/java/org/solovyev/android/calculator/preferences/PreferenceEntry.java @@ -0,0 +1,12 @@ +package org.solovyev.android.calculator.preferences; + +import android.content.Context; +import android.support.annotation.NonNull; + +public interface PreferenceEntry { + @NonNull + CharSequence getName(@NonNull Context context); + + @NonNull + CharSequence getId(); +} diff --git a/app/src/main/java/org/solovyev/android/calculator/preferences/PreferencesFragment.java b/app/src/main/java/org/solovyev/android/calculator/preferences/PreferencesFragment.java index c40ae3f3..e18bd7cf 100644 --- a/app/src/main/java/org/solovyev/android/calculator/preferences/PreferencesFragment.java +++ b/app/src/main/java/org/solovyev/android/calculator/preferences/PreferencesFragment.java @@ -1,5 +1,6 @@ package org.solovyev.android.calculator.preferences; +import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.Build; @@ -13,6 +14,7 @@ import android.widget.ListView; import org.solovyev.android.calculator.AdView; import org.solovyev.android.calculator.Engine; import org.solovyev.android.calculator.Preferences; +import org.solovyev.android.calculator.Preferences.Gui.Theme; import org.solovyev.android.calculator.R; import org.solovyev.android.calculator.language.Language; import org.solovyev.android.calculator.language.Languages; @@ -25,6 +27,7 @@ import org.solovyev.android.wizard.Wizards; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.inject.Inject; +import java.util.Arrays; import java.util.List; import static org.solovyev.android.calculator.App.cast; @@ -154,32 +157,50 @@ public class PreferencesFragment extends org.solovyev.android.material.preferenc return; } final ListPreference theme = (ListPreference) preferenceManager.findPreference(Preferences.Gui.theme.getKey()); - theme.setSummary(Preferences.Gui.getTheme(preferences).name); + final FragmentActivity context = getActivity(); + populate(theme, + Theme.material_theme, + Theme.material_black_theme, + Theme.material_light_theme, + Theme.metro_blue_theme, + Theme.metro_green_theme, + Theme.metro_purple_theme); + theme.setSummary(Preferences.Gui.getTheme(preferences).getName(context)); theme.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { - theme.setSummary(Preferences.Gui.Theme.valueOf((String) newValue).name); + final Theme newTheme = Theme.valueOf((String) newValue); + theme.setSummary(newTheme.getName(context)); return true; } }); } + private static void populate(@Nonnull ListPreference preference, @Nonnull PreferenceEntry... entries) { + populate(preference, Arrays.asList(entries)); + } + + private static void populate(@Nonnull ListPreference preference, @Nonnull List entries) { + final int size = entries.size(); + final CharSequence[] e = new CharSequence[size]; + final CharSequence[] v = new CharSequence[size]; + final Context context = preference.getContext(); + for (int i = 0; i < size; i++) { + final PreferenceEntry entry = entries.get(i); + e[i] = entry.getName(context); + v[i] = entry.getId(); + } + preference.setEntries(e); + preference.setEntryValues(v); + } + private void prepareLanguagePreference(int preference) { if (preference != R.xml.preferences_appearance) { return; } final ListPreference language = (ListPreference) preferenceManager.findPreference(Preferences.Gui.language.getKey()); - final List languagesList = languages.getList(); - final CharSequence[] entries = new CharSequence[languagesList.size()]; - final CharSequence[] entryValues = new CharSequence[languagesList.size()]; - for (int i = 0; i < languagesList.size(); i++) { - final Language l = languagesList.get(i); - entries[i] = l.getName(getActivity()); - entryValues[i] = l.code; - } - language.setEntries(entries); - language.setEntryValues(entryValues); + populate(language, languages.getList()); language.setSummary(languages.getCurrent().getName(getActivity())); language.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { @Override diff --git a/app/src/main/java/org/solovyev/android/calculator/view/AngleUnitsButton.java b/app/src/main/java/org/solovyev/android/calculator/view/AngleUnitsButton.java deleted file mode 100644 index 12769efc..00000000 --- a/app/src/main/java/org/solovyev/android/calculator/view/AngleUnitsButton.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2013 serso aka se.solovyev - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * Contact details - * - * Email: se.solovyev@gmail.com - * Site: http://se.solovyev.org - */ - -package org.solovyev.android.calculator.view; - -import android.content.Context; -import android.support.v4.content.ContextCompat; -import android.util.AttributeSet; -import jscl.AngleUnit; -import org.solovyev.android.calculator.R; -import org.solovyev.android.views.dragbutton.DirectionDragImageButton; -import org.solovyev.android.views.dragbutton.DirectionTextView; -import org.solovyev.android.views.dragbutton.DragDirection; - -import javax.annotation.Nonnull; - -public class AngleUnitsButton extends DirectionDragImageButton { - - @Nonnull - private AngleUnit angleUnit = AngleUnit.deg; - - public AngleUnitsButton(Context context, @Nonnull AttributeSet attrs) { - super(context, attrs); - updateDirectionColors(); - } - - boolean isCurrentAngleUnits(@Nonnull String directionText) { - return angleUnit.name().equals(directionText); - } - - public void setAngleUnit(@Nonnull AngleUnit angleUnit) { - if (this.angleUnit == angleUnit) { - return; - } - this.angleUnit = angleUnit; - updateDirectionColors(); - } - - private void updateDirectionColors() { - for (DragDirection direction : DragDirection.values()) { - final DirectionTextView.Text text = getText(direction); - if (isCurrentAngleUnits(text.getValue())) { - text.setColor(ContextCompat.getColor(getContext(), R.color.yellow_100), 1f); - } else { - text.setColor(ContextCompat.getColor(getContext(), R.color.cpp_text), DirectionTextView.DEF_ALPHA); - } - } - } -} diff --git a/app/src/main/java/org/solovyev/android/calculator/view/NumeralBasesButton.java b/app/src/main/java/org/solovyev/android/calculator/view/NumeralBasesButton.java deleted file mode 100644 index 5c489004..00000000 --- a/app/src/main/java/org/solovyev/android/calculator/view/NumeralBasesButton.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2013 serso aka se.solovyev - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * Contact details - * - * Email: se.solovyev@gmail.com - * Site: http://se.solovyev.org - */ - -package org.solovyev.android.calculator.view; - -import android.content.Context; -import android.support.v4.content.ContextCompat; -import android.util.AttributeSet; -import jscl.NumeralBase; -import org.solovyev.android.calculator.R; -import org.solovyev.android.views.dragbutton.DirectionDragImageButton; -import org.solovyev.android.views.dragbutton.DirectionTextView; -import org.solovyev.android.views.dragbutton.DragDirection; - -import javax.annotation.Nonnull; - -public class NumeralBasesButton extends DirectionDragImageButton { - - @Nonnull - private NumeralBase numeralBase = NumeralBase.dec; - - public NumeralBasesButton(Context context, @Nonnull AttributeSet attrs) { - super(context, attrs); - updateDirectionColors(); - } - - boolean isCurrentNumberBase(@Nonnull String directionText) { - return numeralBase.name().equals(directionText); - } - - public void setNumeralBase(@Nonnull NumeralBase numeralBase) { - if (this.numeralBase == numeralBase) { - return; - } - this.numeralBase = numeralBase; - updateDirectionColors(); - } - - private void updateDirectionColors() { - for (DragDirection direction : DragDirection.values()) { - final DirectionTextView.Text text = getText(direction); - if (isCurrentNumberBase(text.getValue())) { - text.setColor(ContextCompat.getColor(getContext(), R.color.yellow_100), 1f); - } else { - text.setColor(ContextCompat.getColor(getContext(), R.color.cpp_text), DirectionTextView.DEF_ALPHA); - } - } - } -} diff --git a/app/src/main/java/org/solovyev/android/calculator/wizard/ChooseThemeWizardStep.java b/app/src/main/java/org/solovyev/android/calculator/wizard/ChooseThemeWizardStep.java index f29670a8..78244e8b 100644 --- a/app/src/main/java/org/solovyev/android/calculator/wizard/ChooseThemeWizardStep.java +++ b/app/src/main/java/org/solovyev/android/calculator/wizard/ChooseThemeWizardStep.java @@ -23,7 +23,6 @@ package org.solovyev.android.calculator.wizard; import android.os.Bundle; -import android.support.annotation.StringRes; import android.support.v7.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; @@ -58,11 +57,12 @@ public class ChooseThemeWizardStep extends WizardFragment implements AdapterView final Preferences.Gui.Theme theme = Preferences.Gui.getTheme(preferences); final Spinner spinner = (Spinner) root.findViewById(R.id.wizard_theme_spinner); themes.clear(); - themes.add(new ThemeUi(Preferences.Gui.Theme.material_theme, R.string.cpp_theme_dark)); - themes.add(new ThemeUi(Preferences.Gui.Theme.material_light_theme, R.string.cpp_theme_light)); - themes.add(new ThemeUi(Preferences.Gui.Theme.metro_blue_theme, R.string.p_metro_blue_theme)); - themes.add(new ThemeUi(Preferences.Gui.Theme.metro_green_theme, R.string.p_metro_green_theme)); - themes.add(new ThemeUi(Preferences.Gui.Theme.metro_purple_theme, R.string.p_metro_purple_theme)); + themes.add(new ThemeUi(Preferences.Gui.Theme.material_theme)); + themes.add(new ThemeUi(Preferences.Gui.Theme.material_black_theme)); + themes.add(new ThemeUi(Preferences.Gui.Theme.material_light_theme)); + themes.add(new ThemeUi(Preferences.Gui.Theme.metro_blue_theme)); + themes.add(new ThemeUi(Preferences.Gui.Theme.metro_green_theme)); + themes.add(new ThemeUi(Preferences.Gui.Theme.metro_purple_theme)); adapter = new WizardArrayAdapter<>(getActivity(), themes); spinner.setAdapter(adapter); spinner.setSelection(findPosition(theme)); @@ -111,9 +111,9 @@ public class ChooseThemeWizardStep extends WizardFragment implements AdapterView @Nonnull final String name; - public ThemeUi(@Nonnull Preferences.Gui.Theme theme, @StringRes int name) { + public ThemeUi(@Nonnull Preferences.Gui.Theme theme) { this.theme = theme; - this.name = getString(name); + this.name = theme.getName(getActivity()); } @Override diff --git a/app/src/main/java/org/solovyev/android/text/method/NumberInputFilter.java b/app/src/main/java/org/solovyev/android/text/method/NumberInputFilter.java index 5600c096..f7114173 100644 --- a/app/src/main/java/org/solovyev/android/text/method/NumberInputFilter.java +++ b/app/src/main/java/org/solovyev/android/text/method/NumberInputFilter.java @@ -61,7 +61,7 @@ public class NumberInputFilter implements InputFilter { findChars(dest, dend, dest.length(), end - start, CHARS); SpannableStringBuilder filtered = null; - for (int i = end - 1; i >= start; i--) { + for (int i = start; i < end; i++) { final char c = source.charAt(i); boolean filter = false; @@ -93,6 +93,9 @@ public class NumberInputFilter implements InputFilter { } else if (CHARS[CHAR_POINT] >= 0 && CHARS[CHAR_POINT] > i + dstart) { // no exponent before decimal point filter = true; + } else if (i + dstart == 0) { + // exponent can't be first + filter = true; } else { CHARS[CHAR_EXP] = i + dstart; } diff --git a/app/src/main/java/org/solovyev/android/views/dragbutton/DragView.java b/app/src/main/java/org/solovyev/android/views/dragbutton/DragView.java index a7a139d5..06c3813a 100644 --- a/app/src/main/java/org/solovyev/android/views/dragbutton/DragView.java +++ b/app/src/main/java/org/solovyev/android/views/dragbutton/DragView.java @@ -3,6 +3,7 @@ package org.solovyev.android.views.dragbutton; import android.support.annotation.Nullable; public interface DragView { + int getId(); void setOnDragListener(@Nullable DragListener listener); void setVibrateOnDrag(boolean vibrateOnDrag); } diff --git a/app/src/main/res/drawable-hdpi/ic_keyboard_arrow_left_white_48dp.png b/app/src/main/res/drawable-hdpi/ic_keyboard_arrow_left_white_48dp.png deleted file mode 100644 index d8af01c8..00000000 Binary files a/app/src/main/res/drawable-hdpi/ic_keyboard_arrow_left_white_48dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_keyboard_arrow_right_white_48dp.png b/app/src/main/res/drawable-hdpi/ic_keyboard_arrow_right_white_48dp.png deleted file mode 100644 index e9bc3889..00000000 Binary files a/app/src/main/res/drawable-hdpi/ic_keyboard_arrow_right_white_48dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_keyboard_arrow_left_white_48dp.png b/app/src/main/res/drawable-mdpi/ic_keyboard_arrow_left_white_48dp.png deleted file mode 100644 index 2b4d614a..00000000 Binary files a/app/src/main/res/drawable-mdpi/ic_keyboard_arrow_left_white_48dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_keyboard_arrow_right_white_48dp.png b/app/src/main/res/drawable-mdpi/ic_keyboard_arrow_right_white_48dp.png deleted file mode 100644 index aa55964e..00000000 Binary files a/app/src/main/res/drawable-mdpi/ic_keyboard_arrow_right_white_48dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-v21/material_button_black.xml b/app/src/main/res/drawable-v21/material_button_black.xml new file mode 100644 index 00000000..360413b6 --- /dev/null +++ b/app/src/main/res/drawable-v21/material_button_black.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/material_button_black_lighter.xml b/app/src/main/res/drawable-v21/material_button_black_lighter.xml new file mode 100644 index 00000000..d7fc0204 --- /dev/null +++ b/app/src/main/res/drawable-v21/material_button_black_lighter.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/material_button_deep_blue.xml b/app/src/main/res/drawable-v21/material_button_deep_blue.xml new file mode 100644 index 00000000..68efa4b2 --- /dev/null +++ b/app/src/main/res/drawable-v21/material_button_deep_blue.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable-v21/material_button_deep_blue_lighter.xml b/app/src/main/res/drawable-v21/material_button_deep_blue_lighter.xml new file mode 100644 index 00000000..e4679c5b --- /dev/null +++ b/app/src/main/res/drawable-v21/material_button_deep_blue_lighter.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable-xhdpi/ic_keyboard_arrow_left_white_48dp.png b/app/src/main/res/drawable-xhdpi/ic_keyboard_arrow_left_white_48dp.png deleted file mode 100644 index 36cfbc9a..00000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_keyboard_arrow_left_white_48dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_keyboard_arrow_right_white_48dp.png b/app/src/main/res/drawable-xhdpi/ic_keyboard_arrow_right_white_48dp.png deleted file mode 100644 index 13341cbb..00000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_keyboard_arrow_right_white_48dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_keyboard_arrow_left_white_48dp.png b/app/src/main/res/drawable-xxhdpi/ic_keyboard_arrow_left_white_48dp.png deleted file mode 100644 index 76004a9e..00000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_keyboard_arrow_left_white_48dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_keyboard_arrow_right_white_48dp.png b/app/src/main/res/drawable-xxhdpi/ic_keyboard_arrow_right_white_48dp.png deleted file mode 100644 index 25503d1b..00000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_keyboard_arrow_right_white_48dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_keyboard_arrow_left_white_48dp.png b/app/src/main/res/drawable-xxxhdpi/ic_keyboard_arrow_left_white_48dp.png deleted file mode 100644 index 598eb81a..00000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_keyboard_arrow_left_white_48dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_keyboard_arrow_right_white_48dp.png b/app/src/main/res/drawable-xxxhdpi/ic_keyboard_arrow_right_white_48dp.png deleted file mode 100644 index 034a3eff..00000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_keyboard_arrow_right_white_48dp.png and /dev/null differ diff --git a/app/src/main/res/drawable/ic_chevron_left_24dp.xml b/app/src/main/res/drawable/ic_chevron_left_white_24dp.xml similarity index 55% rename from app/src/main/res/drawable/ic_chevron_left_24dp.xml rename to app/src/main/res/drawable/ic_chevron_left_white_24dp.xml index c339eccc..a32c74ac 100644 --- a/app/src/main/res/drawable/ic_chevron_left_24dp.xml +++ b/app/src/main/res/drawable/ic_chevron_left_white_24dp.xml @@ -1,9 +1,9 @@ + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + android:pathData="M15.41,7.41L14,6l-6,6 6,6 1.41,-1.41L10.83,12z" /> diff --git a/app/src/main/res/drawable/ic_favorite_border_white_48dp.xml b/app/src/main/res/drawable/ic_favorite_border_white_48dp.xml new file mode 100644 index 00000000..534260f9 --- /dev/null +++ b/app/src/main/res/drawable/ic_favorite_border_white_48dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_keyboard_arrow_left_white_48dp.xml b/app/src/main/res/drawable/ic_keyboard_arrow_left_white_48dp.xml new file mode 100644 index 00000000..b215b841 --- /dev/null +++ b/app/src/main/res/drawable/ic_keyboard_arrow_left_white_48dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_keyboard_arrow_right_white_48dp.xml b/app/src/main/res/drawable/ic_keyboard_arrow_right_white_48dp.xml new file mode 100644 index 00000000..7c99a11f --- /dev/null +++ b/app/src/main/res/drawable/ic_keyboard_arrow_right_white_48dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/material_button_black.xml b/app/src/main/res/drawable/material_button_black.xml new file mode 100644 index 00000000..5d7dd74b --- /dev/null +++ b/app/src/main/res/drawable/material_button_black.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/material_button_black_lighter.xml b/app/src/main/res/drawable/material_button_black_lighter.xml new file mode 100644 index 00000000..610a3db0 --- /dev/null +++ b/app/src/main/res/drawable/material_button_black_lighter.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/material_button_deep_blue.xml b/app/src/main/res/drawable/material_button_deep_blue.xml new file mode 100644 index 00000000..5880c0de --- /dev/null +++ b/app/src/main/res/drawable/material_button_deep_blue.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/material_button_deep_blue_lighter.xml b/app/src/main/res/drawable/material_button_deep_blue_lighter.xml new file mode 100644 index 00000000..000bc4a6 --- /dev/null +++ b/app/src/main/res/drawable/material_button_deep_blue_lighter.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-land/cpp_app_keyboard.xml b/app/src/main/res/layout-land/cpp_app_keyboard.xml index 8b210807..e40d246f 100644 --- a/app/src/main/res/layout-land/cpp_app_keyboard.xml +++ b/app/src/main/res/layout-land/cpp_app_keyboard.xml @@ -84,7 +84,7 @@ a:baselineAligned="false" a:orientation="horizontal"> - + diff --git a/app/src/main/res/layout/cpp_app_button_copy.xml b/app/src/main/res/layout/cpp_app_button_copy.xml index 5a26e8b6..abf9433b 100644 --- a/app/src/main/res/layout/cpp_app_button_copy.xml +++ b/app/src/main/res/layout/cpp_app_button_copy.xml @@ -22,7 +22,7 @@ ~ Site: http://se.solovyev.org --> - \ No newline at end of file diff --git a/app/src/main/res/layout/cpp_app_button_history.xml b/app/src/main/res/layout/cpp_app_button_history.xml index 1b29e5fc..a7291251 100644 --- a/app/src/main/res/layout/cpp_app_button_history.xml +++ b/app/src/main/res/layout/cpp_app_button_history.xml @@ -28,5 +28,5 @@ style="?attr/cpp_button_style_control" a:src="@drawable/ic_history_white_48dp" app:directionTextScale="0.5" - app:directionTextDown="@string/cpp_kb_redo" - app:directionTextUp="@string/cpp_kb_undo" /> \ No newline at end of file + app:directionTextDown="@string/cpp_glyph_redo" + app:directionTextUp="@string/cpp_glyph_undo" /> \ No newline at end of file diff --git a/app/src/main/res/layout/cpp_app_button_left.xml b/app/src/main/res/layout/cpp_app_button_left.xml index f616ebbd..845176fa 100644 --- a/app/src/main/res/layout/cpp_app_button_left.xml +++ b/app/src/main/res/layout/cpp_app_button_left.xml @@ -29,4 +29,5 @@ xmlns:tools="http://schemas.android.com/tools" a:src="@drawable/ic_keyboard_arrow_left_white_48dp" c:directionTextUp="<<" + c:directionTextDown="@string/cpp_glyph_copy" tools:ignore="HardcodedText" /> \ No newline at end of file diff --git a/app/src/main/res/layout/cpp_app_button_like.xml b/app/src/main/res/layout/cpp_app_button_like.xml new file mode 100644 index 00000000..88497724 --- /dev/null +++ b/app/src/main/res/layout/cpp_app_button_like.xml @@ -0,0 +1,29 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/cpp_app_button_paste.xml b/app/src/main/res/layout/cpp_app_button_paste.xml index b995e86f..718e80dc 100644 --- a/app/src/main/res/layout/cpp_app_button_paste.xml +++ b/app/src/main/res/layout/cpp_app_button_paste.xml @@ -22,7 +22,7 @@ ~ Site: http://se.solovyev.org --> - \ No newline at end of file diff --git a/app/src/main/res/menu/main.xml b/app/src/main/res/menu/main.xml index 5e940722..d7e3281c 100644 --- a/app/src/main/res/menu/main.xml +++ b/app/src/main/res/menu/main.xml @@ -8,7 +8,7 @@ app:showAsAction="never"> - - @string/cpp_theme_dark - @string/cpp_theme_light - @string/p_metro_blue_theme - @string/p_metro_green_theme - @string/p_metro_purple_theme - - - material_theme - material_light_theme - metro_blue_theme - metro_green_theme - metro_purple_theme - - @string/p_use_app_theme @string/cpp_theme_dark diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 14a03463..2efa67f2 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -47,6 +47,11 @@ #313131 #212121 #101010 + #0a0a0a + + #06224d + #082a5e + #0a3980 #0D47A1 #1565C0 diff --git a/app/src/main/res/values/text_glyphs.xml b/app/src/main/res/values/text_glyphs.xml new file mode 100644 index 00000000..93c71380 --- /dev/null +++ b/app/src/main/res/values/text_glyphs.xml @@ -0,0 +1,13 @@ + + + "\ue000" + "\ue001" + "\ue002" + "\ue003" + "\ue004" + "\ue005" + "\ue006" + "\ue007" + "\ue008" + "\ue009" + \ No newline at end of file diff --git a/app/src/main/res/values/text_non_translatable.xml b/app/src/main/res/values/text_non_translatable.xml index 9e49f240..c7026931 100644 --- a/app/src/main/res/values/text_non_translatable.xml +++ b/app/src/main/res/values/text_non_translatable.xml @@ -16,6 +16,7 @@ f(x, y) αβγ E + %1$s (AMOLED) 0 1 diff --git a/app/src/main/res/values/theme_material_black.xml b/app/src/main/res/values/theme_material_black.xml new file mode 100644 index 00000000..4fcb3932 --- /dev/null +++ b/app/src/main/res/values/theme_material_black.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/preferences_appearance.xml b/app/src/main/res/xml/preferences_appearance.xml index 9c1fa08e..687d40cf 100644 --- a/app/src/main/res/xml/preferences_appearance.xml +++ b/app/src/main/res/xml/preferences_appearance.xml @@ -35,8 +35,6 @@ a:title="@string/cpp_prefs_vibrate_on_keypress" /> diff --git a/app/src/test/java/org/solovyev/android/calculator/buttons/CppSpecialButtonTest.java b/app/src/test/java/org/solovyev/android/calculator/buttons/CppSpecialButtonTest.java new file mode 100644 index 00000000..e26e7818 --- /dev/null +++ b/app/src/test/java/org/solovyev/android/calculator/buttons/CppSpecialButtonTest.java @@ -0,0 +1,20 @@ +package org.solovyev.android.calculator.buttons; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class CppSpecialButtonTest { + + @Test + public void testShouldReturnButtonByGlyph() throws Exception { + assertEquals(CppSpecialButton.copy, CppSpecialButton.getByGlyph(CppSpecialButton.copy.glyph)); + assertEquals(CppSpecialButton.paste, CppSpecialButton.getByGlyph(CppSpecialButton.paste.glyph)); + } + + @Test + public void testShouldReturnNullForButtonWithoutGlyph() throws Exception { + assertNull(CppSpecialButton.getByGlyph(CppSpecialButton.brackets_wrap.glyph)); + } +} \ No newline at end of file diff --git a/app/src/test/java/org/solovyev/android/calculator/view/AngleUnitsButtonTest.java b/app/src/test/java/org/solovyev/android/calculator/view/AngleUnitsButtonTest.java deleted file mode 100644 index fac85081..00000000 --- a/app/src/test/java/org/solovyev/android/calculator/view/AngleUnitsButtonTest.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.solovyev.android.calculator.view; - -import android.app.Activity; -import android.os.Build; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mockito; -import org.robolectric.Robolectric; -import org.robolectric.RobolectricGradleTestRunner; -import org.robolectric.Shadows; -import org.robolectric.annotation.Config; -import org.robolectric.res.Attribute; -import org.robolectric.shadows.ShadowActivity; -import org.solovyev.android.calculator.BuildConfig; - -import java.util.ArrayList; - -import static jscl.AngleUnit.deg; -import static jscl.AngleUnit.grad; -import static jscl.AngleUnit.rad; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -@Config(constants = BuildConfig.class, sdk = Build.VERSION_CODES.LOLLIPOP) -@RunWith(RobolectricGradleTestRunner.class) -public class AngleUnitsButtonTest { - - private AngleUnitsButton button; - - @Before - public void setUp() throws Exception { - final Activity context = Robolectric.buildActivity(Activity.class).create().get(); - final ShadowActivity activity = Shadows.shadowOf(context); - button = new AngleUnitsButton(context, activity.createAttributeSet(new ArrayList(), AngleUnitsButton.class)); - } - - @Test - public void testIsCurrentAngleUnits() throws Exception { - button.setAngleUnit(rad); - assertTrue(button.isCurrentAngleUnits(rad.name())); - assertFalse(button.isCurrentAngleUnits(deg.name())); - assertFalse(button.isCurrentAngleUnits(grad.name())); - } - - @Test - public void testInvalidateShouldBeCalledOnlyWhenChangeIsDone() throws Exception { - button.setAngleUnit(rad); - - button = Mockito.spy(button); - - button.setAngleUnit(deg); - verify(button, times(1)).invalidate(); - - button.setAngleUnit(deg); - verify(button, times(1)).invalidate(); - } -} diff --git a/app/src/test/java/org/solovyev/android/calculator/view/NumeralBasesButtonTest.java b/app/src/test/java/org/solovyev/android/calculator/view/NumeralBasesButtonTest.java deleted file mode 100644 index 41d91c70..00000000 --- a/app/src/test/java/org/solovyev/android/calculator/view/NumeralBasesButtonTest.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.solovyev.android.calculator.view; - -import android.app.Activity; -import android.os.Build; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mockito; -import org.robolectric.Robolectric; -import org.robolectric.RobolectricGradleTestRunner; -import org.robolectric.Shadows; -import org.robolectric.annotation.Config; -import org.robolectric.res.Attribute; -import org.robolectric.shadows.ShadowActivity; -import org.solovyev.android.calculator.BuildConfig; - -import java.util.ArrayList; - -import static jscl.NumeralBase.bin; -import static jscl.NumeralBase.dec; -import static jscl.NumeralBase.hex; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -@Config(constants = BuildConfig.class, sdk = Build.VERSION_CODES.LOLLIPOP) -@RunWith(RobolectricGradleTestRunner.class) -public class NumeralBasesButtonTest { - - private NumeralBasesButton button; - - @Before - public void setUp() throws Exception { - final Activity context = Robolectric.buildActivity(Activity.class).create().get(); - final ShadowActivity activity = Shadows.shadowOf(context); - button = new NumeralBasesButton(context, activity.createAttributeSet(new ArrayList(), NumeralBasesButton.class)); - } - - @Test - public void testIsCurrentNumeralBase() throws Exception { - button.setNumeralBase(dec); - assertTrue(button.isCurrentNumberBase(dec.name())); - assertFalse(button.isCurrentNumberBase(hex.name())); - assertFalse(button.isCurrentNumberBase(bin.name())); - } - - @Test - public void testInvalidateShouldBeCalledOnlyWhenChangeIsDone() throws Exception { - button.setNumeralBase(dec); - - button = Mockito.spy(button); - - button.setNumeralBase(hex); - verify(button, times(1)).invalidate(); - - button.setNumeralBase(hex); - verify(button, times(1)).invalidate(); - } -} diff --git a/app/src/test/java/org/solovyev/android/text/method/NumberInputFilterTest.java b/app/src/test/java/org/solovyev/android/text/method/NumberInputFilterTest.java new file mode 100644 index 00000000..f32c0282 --- /dev/null +++ b/app/src/test/java/org/solovyev/android/text/method/NumberInputFilterTest.java @@ -0,0 +1,112 @@ +package org.solovyev.android.text.method; + +import android.os.Build; +import android.text.Editable; +import android.text.InputFilter; +import android.text.SpannableStringBuilder; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricGradleTestRunner; +import org.robolectric.annotation.Config; +import org.solovyev.android.calculator.BuildConfig; + +import static org.junit.Assert.assertEquals; + +@Config(constants = BuildConfig.class, sdk = Build.VERSION_CODES.LOLLIPOP) +@RunWith(RobolectricGradleTestRunner.class) +public class NumberInputFilterTest { + + private Editable editable; + + @Before + public void setUp() throws Exception { + editable = new SpannableStringBuilder(); + editable.setFilters(new InputFilter[]{new NumberInputFilter()}); + } + + @Test + public void testShouldNotInsertExponentInTheBeginning() throws Exception { + editable.insert(0, "E"); + assertEquals("", editable.toString()); + } + + @Test + public void testShouldInsertExponentAtTheEnd() throws Exception { + editable.insert(0, "1"); + editable.insert(1, "E"); + assertEquals("1E", editable.toString()); + } + + @Test + public void testShouldNotInsertSecondMinusSign() throws Exception { + editable.insert(0, "-"); + editable.insert(1, "-"); + assertEquals("-", editable.toString()); + } + + @Test + public void testShouldNotInsertTwoMinusSigns() throws Exception { + editable.insert(0, "--"); + assertEquals("-", editable.toString()); + } + + @Test + public void testShouldInsertSecondMinusSignAfterExponent() throws Exception { + editable.insert(0, "-"); + editable.insert(1, "E"); + editable.insert(2, "-"); + assertEquals("-E-", editable.toString()); + } + + @Test + public void testShouldInsertSecondMinusSignAlongWithExponent() throws Exception { + editable.insert(0, "-"); + editable.insert(1, "E-"); + assertEquals("-E-", editable.toString()); + } + + @Test + public void testShouldNotInsertMinusSignBeforeExistingMinusSIgn() throws Exception { + editable.insert(0, "-"); + editable.insert(0, "-"); + assertEquals("-", editable.toString()); + } + + @Test + public void testShouldNotInsertSecondDecimalPoint() throws Exception { + editable.insert(0, "0.2"); + editable.insert(3, "."); + assertEquals("0.2", editable.toString()); + } + + @Test + public void testShouldNotInsertTwoDecimalPoints() throws Exception { + editable.insert(0, ".."); + assertEquals(".", editable.toString()); + } + + @Test + public void testShouldNotInsertDecimalPointAfterExponent() throws Exception { + editable.insert(0, "2E"); + editable.insert(2, "."); + assertEquals("2E", editable.toString()); + + editable.clear(); + editable.insert(0, "2E."); + assertEquals("2E", editable.toString()); + } + + @Test + public void testShouldNotInsertTwoExcponents() throws Exception { + editable.insert(0, "2EE"); + assertEquals("2E", editable.toString()); + } + + @Test + public void testShouldNotInsertExponentBeforeDecimalPoint() throws Exception { + editable.insert(0, "0.2"); + editable.insert(0, "E"); + assertEquals("0.2", editable.toString()); + } +} \ No newline at end of file diff --git a/jscl/src/main/java/jscl/JsclMathEngine.java b/jscl/src/main/java/jscl/JsclMathEngine.java index f58333f0..66508e4f 100644 --- a/jscl/src/main/java/jscl/JsclMathEngine.java +++ b/jscl/src/main/java/jscl/JsclMathEngine.java @@ -9,7 +9,6 @@ import jscl.math.operator.Percent; import jscl.math.operator.Rand; import jscl.math.operator.matrix.OperatorsRegistry; import jscl.text.ParseException; -import midpcalc.Real; import org.solovyev.common.NumberFormatter; import org.solovyev.common.math.MathRegistry; import org.solovyev.common.msg.MessageRegistry; @@ -18,6 +17,7 @@ import org.solovyev.common.msg.Messages; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.math.BigDecimal; +import java.math.BigInteger; import java.util.List; import static midpcalc.Real.NumberFormat.*; @@ -144,16 +144,16 @@ public class JsclMathEngine implements MathEngine { } @Nonnull - public String format(@Nonnull Double value) throws NumeralBaseException { + public String format(double value) throws NumeralBaseException { return format(value, numeralBase); } @Nonnull - public String format(@Nonnull Double value, @Nonnull NumeralBase nb) throws NumeralBaseException { - if (value.isInfinite()) { + public String format(double value, @Nonnull NumeralBase nb) throws NumeralBaseException { + if (Double.isInfinite(value)) { return formatInfinity(value); } - if (value.isNaN()) { + if (Double.isNaN(value)) { // return "NaN" return String.valueOf(value); } @@ -167,11 +167,15 @@ public class JsclMathEngine implements MathEngine { return constant.getName(); } } + return prepareNumberFormatter(nb).format(value, nb.radix).toString(); + } + + private NumberFormatter prepareNumberFormatter(@Nonnull NumeralBase nb) { final NumberFormatter nf = numberFormatter.get(); - nf.setGroupingSeparator(useGroupingSeparator ? groupingSeparator : NumberFormatter.NO_GROUPING); + nf.setGroupingSeparator(useGroupingSeparator ? getGroupingSeparatorChar(nb) : NumberFormatter.NO_GROUPING); nf.setPrecision(roundResult ? precision : NumberFormatter.NO_ROUNDING); switch (numberFormat) { - case Real.NumberFormat.FSE_ENG: + case FSE_ENG: nf.useEngineeringFormat(NumberFormatter.DEFAULT_MAGNITUDE); break; case FSE_SCI: @@ -181,18 +185,36 @@ public class JsclMathEngine implements MathEngine { nf.useSimpleFormat(); break; } - return nf.format(value, nb.radix).toString(); + return nf; + } + + @Override + public String format(@Nonnull BigInteger value) throws NumeralBaseException { + return format(value, numeralBase); + } + + @Nonnull + public String format(@Nonnull BigInteger value, @Nonnull NumeralBase nb) throws NumeralBaseException { + if (nb == NumeralBase.dec) { + if (BigInteger.ZERO.equals(value)) { + return "0"; + } + } + return prepareNumberFormatter(nb).format(value, nb.radix).toString(); } @Nullable - private IConstant findConstant(@Nonnull Double value) { + private IConstant findConstant(double value) { final IConstant constant = findConstant(constantsRegistry.getSystemEntities(), value); if (constant != null) { return constant; } final IConstant piInv = constantsRegistry.get(Constants.PI_INV.getName()); - if (piInv != null && value.equals(piInv.getDoubleValue())) { - return piInv; + if (piInv != null) { + final Double piInvValue = piInv.getDoubleValue(); + if (piInvValue != null && piInvValue == value) { + return piInv; + } } return null; } @@ -251,7 +273,7 @@ public class JsclMathEngine implements MathEngine { @Nonnull public String addGroupingSeparators(@Nonnull NumeralBase nb, @Nonnull String ungroupedDoubleValue) { if (useGroupingSeparator) { - final String groupingSeparator = nb == NumeralBase.dec ? String.valueOf(this.groupingSeparator) : " "; + final String groupingSeparator = getGroupingSeparator(nb); final int dotIndex = ungroupedDoubleValue.indexOf("."); @@ -277,6 +299,15 @@ public class JsclMathEngine implements MathEngine { } } + @Nonnull + private String getGroupingSeparator(@Nonnull NumeralBase nb) { + return nb == NumeralBase.dec ? String.valueOf(groupingSeparator) : " "; + } + + private char getGroupingSeparatorChar(@Nonnull NumeralBase nb) { + return nb == NumeralBase.dec ? groupingSeparator : ' '; + } + @Nonnull private StringBuilder insertSeparators(@Nonnull NumeralBase nb, @Nonnull String groupingSeparator, diff --git a/jscl/src/main/java/jscl/MathContext.java b/jscl/src/main/java/jscl/MathContext.java index 30400a29..c4ef9cc7 100644 --- a/jscl/src/main/java/jscl/MathContext.java +++ b/jscl/src/main/java/jscl/MathContext.java @@ -6,6 +6,7 @@ import jscl.math.operator.Operator; import org.solovyev.common.math.MathRegistry; import javax.annotation.Nonnull; +import java.math.BigInteger; public interface MathContext { @@ -44,10 +45,12 @@ public interface MathContext { void setGroupingSeparator(char groupingSeparator); @Nonnull - String format(@Nonnull Double value) throws NumeralBaseException; + String format(double value) throws NumeralBaseException; + + String format(@Nonnull BigInteger value) throws NumeralBaseException; @Nonnull - String format(@Nonnull Double value, @Nonnull NumeralBase nb) throws NumeralBaseException; + String format(double value, @Nonnull NumeralBase nb) throws NumeralBaseException; @Nonnull String addGroupingSeparators(@Nonnull NumeralBase nb, @Nonnull String ungroupedIntValue); diff --git a/jscl/src/main/java/jscl/math/JsclInteger.java b/jscl/src/main/java/jscl/math/JsclInteger.java index a8fc3efa..c365d053 100644 --- a/jscl/src/main/java/jscl/math/JsclInteger.java +++ b/jscl/src/main/java/jscl/math/JsclInteger.java @@ -352,8 +352,7 @@ public final class JsclInteger extends Generic { } public String toString() { - // todo serso: actually better way is to provide custom format() method for integers and not to convert integer to double - return JsclMathEngine.getInstance().format(this.content.doubleValue()); + return JsclMathEngine.getInstance().format(content); } public String toJava() { diff --git a/jscl/src/main/java/jscl/math/numeric/Numeric.java b/jscl/src/main/java/jscl/math/numeric/Numeric.java index e30d75af..3ef0a3c1 100644 --- a/jscl/src/main/java/jscl/math/numeric/Numeric.java +++ b/jscl/src/main/java/jscl/math/numeric/Numeric.java @@ -324,7 +324,7 @@ public abstract class Numeric implements Arithmetic, INumeric, @Nonnull protected String toString(final double value) { - return JsclMathEngine.getInstance().format(value, JsclMathEngine.getInstance().getNumeralBase()); + return JsclMathEngine.getInstance().format(value); } public BigInteger toBigInteger() { diff --git a/jscl/src/main/java/org/solovyev/common/NumberFormatter.java b/jscl/src/main/java/org/solovyev/common/NumberFormatter.java index 5eb44bfa..b875774d 100644 --- a/jscl/src/main/java/org/solovyev/common/NumberFormatter.java +++ b/jscl/src/main/java/org/solovyev/common/NumberFormatter.java @@ -1,16 +1,13 @@ package org.solovyev.common; -import java.math.BigDecimal; - -import javax.annotation.Nonnull; - import midpcalc.Real; +import javax.annotation.Nonnull; +import java.math.BigDecimal; +import java.math.BigInteger; + import static java.lang.Math.pow; -import static midpcalc.Real.NumberFormat.FSE_ENG; -import static midpcalc.Real.NumberFormat.FSE_FIX; -import static midpcalc.Real.NumberFormat.FSE_NONE; -import static midpcalc.Real.NumberFormat.FSE_SCI; +import static midpcalc.Real.NumberFormat.*; public class NumberFormatter { @@ -54,11 +51,14 @@ public class NumberFormatter { return format(value, 10); } + @Nonnull + public CharSequence format(@Nonnull BigInteger value) { + return format(value, 10); + } + @Nonnull public CharSequence format(double value, int radix) { - if (radix != 2 && radix != 8 && radix != 10 && radix != 16) { - throw new IllegalArgumentException("Unsupported radix: " + radix); - } + checkRadix(radix); double absValue = Math.abs(value); final boolean simpleFormat = useSimpleFormat(radix, absValue); @@ -88,6 +88,39 @@ public class NumberFormatter { return prepare(value); } + @Nonnull + public CharSequence format(@Nonnull BigInteger value, int radix) { + checkRadix(radix); + final BigInteger absValue = value.abs(); + final boolean simpleFormat = useSimpleFormat(radix, absValue); + + final int effectivePrecision = precision == NO_ROUNDING ? MAX_PRECISION : precision; + if (simpleFormat) { + numberFormat.fse = FSE_FIX; + } else if (format == FSE_NONE) { + // originally, a simple format was requested but we have to use something more appropriate, f.e. scientific + // format + numberFormat.fse = FSE_SCI; + } else { + numberFormat.fse = format; + } + numberFormat.thousand = groupingSeparator; + numberFormat.precision = effectivePrecision; + numberFormat.base = radix; + numberFormat.maxwidth = simpleFormat ? 100 : 30; + + if (radix == 2 && value.compareTo(BigInteger.ZERO) < 0) { + return "-" + prepare(absValue); + } + return prepare(value); + } + + private void checkRadix(int radix) { + if (radix != 2 && radix != 8 && radix != 10 && radix != 16) { + throw new IllegalArgumentException("Unsupported radix: " + radix); + } + } + private boolean useSimpleFormat(int radix, double absValue) { if (radix != 10) { return true; @@ -103,17 +136,41 @@ public class NumberFormatter { return false; } + private boolean useSimpleFormat(int radix, @Nonnull BigInteger absValue) { + if (radix != 10) { + return true; + } + if (format == FSE_NONE) { + return true; + } + if (absValue.compareTo(BigInteger.valueOf((long) pow(10, simpleFormatMagnitude))) < 0) { + return true; + } + return false; + } + @Nonnull private CharSequence prepare(double value) { return stripZeros(realFormat(value)).replace('e', 'E'); } + @Nonnull + private CharSequence prepare(@Nonnull BigInteger value) { + return stripZeros(realFormat(value)).replace('e', 'E'); + } + @Nonnull private String realFormat(double value) { real.assign(Double.toString(value)); return real.toString(numberFormat); } + @Nonnull + private String realFormat(@Nonnull BigInteger value) { + real.assign(value.toString()); + return real.toString(numberFormat); + } + @Nonnull private String stripZeros(@Nonnull String s) { int dot = -1; diff --git a/jscl/src/test/java/jscl/JsclMathEngineTest.java b/jscl/src/test/java/jscl/JsclMathEngineTest.java index 96043c89..92b271fa 100644 --- a/jscl/src/test/java/jscl/JsclMathEngineTest.java +++ b/jscl/src/test/java/jscl/JsclMathEngineTest.java @@ -77,12 +77,31 @@ public class JsclMathEngineTest { assertEquals("13D", me.format(317d, NumeralBase.hex)); } + @Test public void testPiComputation() throws Exception { final JsclMathEngine me = JsclMathEngine.getInstance(); assertEquals("-1+0.0000000000000001*i", me.evaluate("exp(√(-1)*Π)")); } + @Test + public void testBinShouldAlwaysUseSpaceAsGroupingSeparator() throws Exception { + final JsclMathEngine me = new JsclMathEngine(); + me.setGroupingSeparator('\''); + me.setUseGroupingSeparator(true); + + assertEquals("100 0000 0000", me.format(1024d, NumeralBase.bin)); + } + + @Test + public void testHexShouldAlwaysUseSpaceAsGroupingSeparator() throws Exception { + final JsclMathEngine me = new JsclMathEngine(); + me.setGroupingSeparator('\''); + me.setUseGroupingSeparator(true); + + assertEquals("4 00", me.format(1024d, NumeralBase.hex)); + } + @Test public void testEngineeringNotationWithRounding() throws Exception { final JsclMathEngine me = JsclMathEngine.getInstance(); @@ -114,6 +133,7 @@ public class JsclMathEngineTest { assertEquals("-999", me.format(-999d)); assertEquals("-999.99", me.format(-999.99d)); assertEquals("-0.1", me.format(-0.1d)); + assertEquals("-0.12", me.format(-0.12d)); assertEquals("-0.123", me.format(-0.123d)); assertEquals("-0.1234", me.format(-0.1234d)); diff --git a/jscl/src/test/java/org/solovyev/common/NumberFormatterTest.java b/jscl/src/test/java/org/solovyev/common/NumberFormatterTest.java index 45ce11f1..e7847319 100644 --- a/jscl/src/test/java/org/solovyev/common/NumberFormatterTest.java +++ b/jscl/src/test/java/org/solovyev/common/NumberFormatterTest.java @@ -3,7 +3,10 @@ package org.solovyev.common; import org.junit.Before; import org.junit.Test; +import java.math.BigInteger; + import static java.lang.Math.pow; +import static java.math.BigInteger.TEN; import static org.junit.Assert.assertEquals; import static org.solovyev.common.NumberFormatter.DEFAULT_MAGNITUDE; import static org.solovyev.common.NumberFormatter.NO_ROUNDING; @@ -99,32 +102,66 @@ public class NumberFormatterTest { // testing simple format with and without rounding private void testSimpleFormat() { assertEquals("0.00001", numberFormatter.format(pow(10, -5))); + assertEquals("100", numberFormatter.format(pow(10, 2))); + assertEquals("1", numberFormatter.format(BigInteger.ONE)); + assertEquals("1000", numberFormatter.format(BigInteger.valueOf(1000))); + assertEquals("1000000000000000000", numberFormatter.format(pow(10, 18))); + assertEquals("1000000000000000000", numberFormatter.format(BigInteger.valueOf(10).pow(18))); + assertEquals("1E19", numberFormatter.format(pow(10, 19))); + assertEquals("1E19", numberFormatter.format(BigInteger.valueOf(10).pow(19))); + assertEquals("1E20", numberFormatter.format(pow(10, 20))); + assertEquals("1E20", numberFormatter.format(BigInteger.valueOf(10).pow(20))); + assertEquals("1E100", numberFormatter.format(pow(10, 100))); + assertEquals("1E100", numberFormatter.format(BigInteger.valueOf(10).pow(100))); assertEquals("0.01", numberFormatter.format(pow(10, -2))); assertEquals("5000000000000000000", numberFormatter.format(5000000000000000000d)); + assertEquals("5000000000000000000", numberFormatter.format(BigInteger.valueOf(5000000000000000000L))); + assertEquals("5000000000000000000", numberFormatter.format(5000000000000000001d)); + assertEquals("5000000000000000001", numberFormatter.format(BigInteger.valueOf(5000000000000000001L))); + assertEquals("5999999999999994900", numberFormatter.format(5999999999999994999d)); + assertEquals("5999999999999994999", numberFormatter.format(BigInteger.valueOf(5999999999999994999L))); + assertEquals("5E19", numberFormatter.format(50000000000000000000d)); + assertEquals("5E19", numberFormatter.format(BigInteger.valueOf(5L).multiply(TEN.pow(19)))); + assertEquals("5E40", numberFormatter.format(50000000000000000000000000000000000000000d)); + assertEquals("5E40", numberFormatter.format(BigInteger.valueOf(5L).multiply(TEN.pow(40)))); } // testing scientific format with and without rounding private void testScientificFormat() { assertEquals("0.00001", numberFormatter.format(pow(10, -5))); assertEquals("1E-6", numberFormatter.format(pow(10, -6))); + assertEquals("100", numberFormatter.format(pow(10, 2))); + assertEquals("100", numberFormatter.format(TEN.pow(2))); + assertEquals("10000", numberFormatter.format(pow(10, 4))); + assertEquals("10000", numberFormatter.format(TEN.pow(4))); + assertEquals("1E5", numberFormatter.format(pow(10, 5))); + assertEquals("1E5", numberFormatter.format(TEN.pow(5))); + assertEquals("1E18", numberFormatter.format(pow(10, 18))); + assertEquals("1E18", numberFormatter.format(TEN.pow(18))); + assertEquals("1E19", numberFormatter.format(pow(10, 19))); + assertEquals("1E19", numberFormatter.format(TEN.pow( 19))); + assertEquals("1E20", numberFormatter.format(pow(10, 20))); + assertEquals("1E20", numberFormatter.format(TEN.pow(20))); + assertEquals("1E100", numberFormatter.format(pow(10, 100))); + assertEquals("1E100", numberFormatter.format(TEN.pow(100))); assertEquals("0.01", numberFormatter.format(pow(10, -2))); assertEquals("1E-17", numberFormatter.format(pow(10, -17)));