Module separation
This commit is contained in:
@@ -1,348 +1,347 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
||||
* For more information, please, contact se.solovyev@gmail.com
|
||||
*/
|
||||
|
||||
package org.solovyev.android.calculator;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.text.Html;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import jscl.NumeralBase;
|
||||
import jscl.math.Generic;
|
||||
import jscl.math.function.Constant;
|
||||
import jscl.math.function.IConstant;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.calculator.jscl.JsclOperation;
|
||||
import org.solovyev.android.calculator.model.CalculatorEngine;
|
||||
import org.solovyev.android.calculator.model.CalculatorParseException;
|
||||
import org.solovyev.android.calculator.model.TextProcessor;
|
||||
import org.solovyev.android.calculator.model.ToJsclTextProcessor;
|
||||
import org.solovyev.android.calculator.view.NumeralBaseConverterDialog;
|
||||
import org.solovyev.android.calculator.view.TextHighlighter;
|
||||
import org.solovyev.android.calculator.view.UnitConverterViewBuilder;
|
||||
import org.solovyev.android.menu.AMenuItem;
|
||||
import org.solovyev.android.menu.LabeledMenuItem;
|
||||
import org.solovyev.android.view.AutoResizeTextView;
|
||||
import org.solovyev.common.collections.CollectionsUtils;
|
||||
import org.solovyev.common.text.StringUtils;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 9/17/11
|
||||
* Time: 10:58 PM
|
||||
*/
|
||||
public class CalculatorDisplay extends AutoResizeTextView implements ICalculatorDisplay{
|
||||
|
||||
private static enum ConversionMenuItem implements AMenuItem<CalculatorDisplay> {
|
||||
convert_to_bin(NumeralBase.bin),
|
||||
convert_to_dec(NumeralBase.dec),
|
||||
convert_to_hex(NumeralBase.hex);
|
||||
|
||||
@NotNull
|
||||
private final NumeralBase toNumeralBase;
|
||||
|
||||
private ConversionMenuItem(@NotNull NumeralBase toNumeralBase) {
|
||||
this.toNumeralBase = toNumeralBase;
|
||||
}
|
||||
|
||||
protected boolean isItemVisibleFor(@NotNull Generic generic, @NotNull JsclOperation operation) {
|
||||
boolean result = false;
|
||||
|
||||
if (operation == JsclOperation.numeric) {
|
||||
if (generic.getConstants().isEmpty()) {
|
||||
try {
|
||||
convert(generic);
|
||||
|
||||
// conversion possible => return true
|
||||
result = true;
|
||||
|
||||
} catch (UnitConverterViewBuilder.ConversionException e) {
|
||||
// conversion is not possible => return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(@NotNull CalculatorDisplay data, @NotNull Context context) {
|
||||
final NumeralBase fromNumeralBase = CalculatorEngine.instance.getEngine().getNumeralBase();
|
||||
|
||||
String to;
|
||||
try {
|
||||
to = convert(data.getGenericResult());
|
||||
|
||||
// add prefix
|
||||
if (fromNumeralBase != toNumeralBase) {
|
||||
to = toNumeralBase.getJsclPrefix() + to;
|
||||
}
|
||||
} catch (UnitConverterViewBuilder.ConversionException e) {
|
||||
to = context.getString(R.string.c_error);
|
||||
}
|
||||
|
||||
data.setText(to);
|
||||
data.redraw();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String convert(@NotNull Generic generic) throws UnitConverterViewBuilder.ConversionException {
|
||||
final NumeralBase fromNumeralBase = CalculatorEngine.instance.getEngine().getNumeralBase();
|
||||
|
||||
if (fromNumeralBase != toNumeralBase) {
|
||||
String from = generic.toString();
|
||||
if (!StringUtils.isEmpty(from)) {
|
||||
try {
|
||||
from = ToJsclTextProcessor.getInstance().process(from).getExpression();
|
||||
} catch (CalculatorParseException e) {
|
||||
// ok, problems while processing occurred
|
||||
}
|
||||
}
|
||||
|
||||
return UnitConverterViewBuilder.doConversion(AndroidNumeralBase.getConverter(), from, AndroidNumeralBase.valueOf(fromNumeralBase), AndroidNumeralBase.valueOf(toNumeralBase));
|
||||
} else {
|
||||
return generic.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static enum MenuItem implements LabeledMenuItem<CalculatorDisplay> {
|
||||
|
||||
copy(R.string.c_copy) {
|
||||
@Override
|
||||
public void onClick(@NotNull CalculatorDisplay data, @NotNull Context context) {
|
||||
CalculatorModel.copyResult(context, data);
|
||||
}
|
||||
},
|
||||
|
||||
convert_to_bin(R.string.convert_to_bin) {
|
||||
@Override
|
||||
public void onClick(@NotNull CalculatorDisplay data, @NotNull Context context) {
|
||||
ConversionMenuItem.convert_to_bin.onClick(data, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isItemVisibleFor(@NotNull Generic generic, @NotNull JsclOperation operation) {
|
||||
return ConversionMenuItem.convert_to_bin.isItemVisibleFor(generic, operation);
|
||||
}
|
||||
},
|
||||
|
||||
convert_to_dec(R.string.convert_to_dec) {
|
||||
@Override
|
||||
public void onClick(@NotNull CalculatorDisplay data, @NotNull Context context) {
|
||||
ConversionMenuItem.convert_to_dec.onClick(data, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isItemVisibleFor(@NotNull Generic generic, @NotNull JsclOperation operation) {
|
||||
return ConversionMenuItem.convert_to_dec.isItemVisibleFor(generic, operation);
|
||||
}
|
||||
},
|
||||
|
||||
convert_to_hex(R.string.convert_to_hex) {
|
||||
@Override
|
||||
public void onClick(@NotNull CalculatorDisplay data, @NotNull Context context) {
|
||||
ConversionMenuItem.convert_to_hex.onClick(data, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isItemVisibleFor(@NotNull Generic generic, @NotNull JsclOperation operation) {
|
||||
return ConversionMenuItem.convert_to_hex.isItemVisibleFor(generic, operation);
|
||||
}
|
||||
},
|
||||
|
||||
convert(R.string.c_convert) {
|
||||
@Override
|
||||
public void onClick(@NotNull CalculatorDisplay data, @NotNull Context context) {
|
||||
new NumeralBaseConverterDialog(data.getGenericResult().toString()).show(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isItemVisibleFor(@NotNull Generic generic, @NotNull JsclOperation operation) {
|
||||
return operation == JsclOperation.numeric && generic.getConstants().isEmpty();
|
||||
}
|
||||
},
|
||||
|
||||
plot(R.string.c_plot) {
|
||||
@Override
|
||||
public void onClick(@NotNull CalculatorDisplay data, @NotNull Context context) {
|
||||
final Generic generic = data.getGenericResult();
|
||||
assert generic != null;
|
||||
|
||||
final Constant constant = CollectionsUtils.getFirstCollectionElement(getNotSystemConstants(generic));
|
||||
assert constant != null;
|
||||
CalculatorActivityLauncher.plotGraph(context, generic, constant);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isItemVisibleFor(@NotNull Generic generic, @NotNull JsclOperation operation) {
|
||||
boolean result = false;
|
||||
|
||||
if (operation == JsclOperation.simplify) {
|
||||
if (getNotSystemConstants(generic).size() == 1) {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Set<Constant> getNotSystemConstants(@NotNull Generic generic) {
|
||||
final Set<Constant> notSystemConstants = new HashSet<Constant>();
|
||||
|
||||
for (Constant constant : generic.getConstants()) {
|
||||
IConstant var = CalculatorEngine.instance.getVarsRegistry().get(constant.getName());
|
||||
if (var != null && !var.isSystem() && !var.isDefined()) {
|
||||
notSystemConstants.add(constant);
|
||||
}
|
||||
}
|
||||
|
||||
return notSystemConstants;
|
||||
}
|
||||
};
|
||||
|
||||
private final int captionId;
|
||||
|
||||
MenuItem(int captionId) {
|
||||
this.captionId = captionId;
|
||||
}
|
||||
|
||||
public final boolean isItemVisible(@NotNull CalculatorDisplay display) {
|
||||
//noinspection ConstantConditions
|
||||
return display.isValid() && display.getGenericResult() != null && isItemVisibleFor(display.getGenericResult(), display.getJsclOperation());
|
||||
}
|
||||
|
||||
protected boolean isItemVisibleFor(@NotNull Generic generic, @NotNull JsclOperation operation) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getCaption(@NotNull Context context) {
|
||||
return context.getString(captionId);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean valid = true;
|
||||
|
||||
@Nullable
|
||||
private String errorMessage;
|
||||
|
||||
@NotNull
|
||||
private JsclOperation jsclOperation = JsclOperation.numeric;
|
||||
|
||||
@NotNull
|
||||
private final static TextProcessor<TextHighlighter.Result, String> textHighlighter = new TextHighlighter(Color.WHITE, false, CalculatorEngine.instance.getEngine());
|
||||
|
||||
@Nullable
|
||||
private Generic genericResult;
|
||||
|
||||
public CalculatorDisplay(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public CalculatorDisplay(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public CalculatorDisplay(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return valid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValid(boolean valid) {
|
||||
this.valid = valid;
|
||||
if (valid) {
|
||||
errorMessage = null;
|
||||
setTextColor(getResources().getColor(R.color.default_text_color));
|
||||
} else {
|
||||
setTextColor(getResources().getColor(R.color.display_error_text_color));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setErrorMessage(@Nullable String errorMessage) {
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setJsclOperation(@NotNull JsclOperation jsclOperation) {
|
||||
this.jsclOperation = jsclOperation;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public JsclOperation getJsclOperation() {
|
||||
return jsclOperation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setText(CharSequence text, BufferType type) {
|
||||
super.setText(text, type);
|
||||
|
||||
setValid(true);
|
||||
}
|
||||
|
||||
public synchronized void redraw() {
|
||||
if (isValid()) {
|
||||
String text = getText().toString();
|
||||
|
||||
Log.d(this.getClass().getName(), text);
|
||||
|
||||
try {
|
||||
TextHighlighter.Result result = textHighlighter.process(text);
|
||||
text = result.toString();
|
||||
} catch (CalculatorParseException e) {
|
||||
Log.e(this.getClass().getName(), e.getMessage(), e);
|
||||
}
|
||||
|
||||
Log.d(this.getClass().getName(), text);
|
||||
super.setText(Html.fromHtml(text), BufferType.EDITABLE);
|
||||
}
|
||||
|
||||
// todo serso: think where to move it (keep in mind org.solovyev.android.view.AutoResizeTextView.resetTextSize())
|
||||
setAddEllipsis(false);
|
||||
setMinTextSize(10);
|
||||
resizeText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGenericResult(@Nullable Generic genericResult) {
|
||||
this.genericResult = genericResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Generic getGenericResult() {
|
||||
return genericResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSelection() {
|
||||
return this.getSelectionStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelection(int selection) {
|
||||
// not supported by TextView
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
||||
* For more information, please, contact se.solovyev@gmail.com
|
||||
*/
|
||||
|
||||
package org.solovyev.android.calculator;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.text.Html;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import jscl.NumeralBase;
|
||||
import jscl.math.Generic;
|
||||
import jscl.math.function.Constant;
|
||||
import jscl.math.function.IConstant;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.calculator.jscl.JsclOperation;
|
||||
import org.solovyev.android.calculator.model.CalculatorEngine;
|
||||
import org.solovyev.android.calculator.text.TextProcessor;
|
||||
import org.solovyev.android.calculator.model.ToJsclTextProcessor;
|
||||
import org.solovyev.android.calculator.view.NumeralBaseConverterDialog;
|
||||
import org.solovyev.android.calculator.view.TextHighlighter;
|
||||
import org.solovyev.android.calculator.view.UnitConverterViewBuilder;
|
||||
import org.solovyev.android.menu.AMenuItem;
|
||||
import org.solovyev.android.menu.LabeledMenuItem;
|
||||
import org.solovyev.android.view.AutoResizeTextView;
|
||||
import org.solovyev.common.collections.CollectionsUtils;
|
||||
import org.solovyev.common.text.StringUtils;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 9/17/11
|
||||
* Time: 10:58 PM
|
||||
*/
|
||||
public class CalculatorDisplay extends AutoResizeTextView implements ICalculatorDisplay{
|
||||
|
||||
private static enum ConversionMenuItem implements AMenuItem<CalculatorDisplay> {
|
||||
convert_to_bin(NumeralBase.bin),
|
||||
convert_to_dec(NumeralBase.dec),
|
||||
convert_to_hex(NumeralBase.hex);
|
||||
|
||||
@NotNull
|
||||
private final NumeralBase toNumeralBase;
|
||||
|
||||
private ConversionMenuItem(@NotNull NumeralBase toNumeralBase) {
|
||||
this.toNumeralBase = toNumeralBase;
|
||||
}
|
||||
|
||||
protected boolean isItemVisibleFor(@NotNull Generic generic, @NotNull JsclOperation operation) {
|
||||
boolean result = false;
|
||||
|
||||
if (operation == JsclOperation.numeric) {
|
||||
if (generic.getConstants().isEmpty()) {
|
||||
try {
|
||||
convert(generic);
|
||||
|
||||
// conversion possible => return true
|
||||
result = true;
|
||||
|
||||
} catch (UnitConverterViewBuilder.ConversionException e) {
|
||||
// conversion is not possible => return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(@NotNull CalculatorDisplay data, @NotNull Context context) {
|
||||
final NumeralBase fromNumeralBase = CalculatorEngine.instance.getEngine().getNumeralBase();
|
||||
|
||||
String to;
|
||||
try {
|
||||
to = convert(data.getGenericResult());
|
||||
|
||||
// add prefix
|
||||
if (fromNumeralBase != toNumeralBase) {
|
||||
to = toNumeralBase.getJsclPrefix() + to;
|
||||
}
|
||||
} catch (UnitConverterViewBuilder.ConversionException e) {
|
||||
to = context.getString(R.string.c_error);
|
||||
}
|
||||
|
||||
data.setText(to);
|
||||
data.redraw();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String convert(@NotNull Generic generic) throws UnitConverterViewBuilder.ConversionException {
|
||||
final NumeralBase fromNumeralBase = CalculatorEngine.instance.getEngine().getNumeralBase();
|
||||
|
||||
if (fromNumeralBase != toNumeralBase) {
|
||||
String from = generic.toString();
|
||||
if (!StringUtils.isEmpty(from)) {
|
||||
try {
|
||||
from = ToJsclTextProcessor.getInstance().process(from).getExpression();
|
||||
} catch (CalculatorParseException e) {
|
||||
// ok, problems while processing occurred
|
||||
}
|
||||
}
|
||||
|
||||
return UnitConverterViewBuilder.doConversion(AndroidNumeralBase.getConverter(), from, AndroidNumeralBase.valueOf(fromNumeralBase), AndroidNumeralBase.valueOf(toNumeralBase));
|
||||
} else {
|
||||
return generic.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static enum MenuItem implements LabeledMenuItem<CalculatorDisplay> {
|
||||
|
||||
copy(R.string.c_copy) {
|
||||
@Override
|
||||
public void onClick(@NotNull CalculatorDisplay data, @NotNull Context context) {
|
||||
CalculatorModel.copyResult(context, data);
|
||||
}
|
||||
},
|
||||
|
||||
convert_to_bin(R.string.convert_to_bin) {
|
||||
@Override
|
||||
public void onClick(@NotNull CalculatorDisplay data, @NotNull Context context) {
|
||||
ConversionMenuItem.convert_to_bin.onClick(data, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isItemVisibleFor(@NotNull Generic generic, @NotNull JsclOperation operation) {
|
||||
return ConversionMenuItem.convert_to_bin.isItemVisibleFor(generic, operation);
|
||||
}
|
||||
},
|
||||
|
||||
convert_to_dec(R.string.convert_to_dec) {
|
||||
@Override
|
||||
public void onClick(@NotNull CalculatorDisplay data, @NotNull Context context) {
|
||||
ConversionMenuItem.convert_to_dec.onClick(data, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isItemVisibleFor(@NotNull Generic generic, @NotNull JsclOperation operation) {
|
||||
return ConversionMenuItem.convert_to_dec.isItemVisibleFor(generic, operation);
|
||||
}
|
||||
},
|
||||
|
||||
convert_to_hex(R.string.convert_to_hex) {
|
||||
@Override
|
||||
public void onClick(@NotNull CalculatorDisplay data, @NotNull Context context) {
|
||||
ConversionMenuItem.convert_to_hex.onClick(data, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isItemVisibleFor(@NotNull Generic generic, @NotNull JsclOperation operation) {
|
||||
return ConversionMenuItem.convert_to_hex.isItemVisibleFor(generic, operation);
|
||||
}
|
||||
},
|
||||
|
||||
convert(R.string.c_convert) {
|
||||
@Override
|
||||
public void onClick(@NotNull CalculatorDisplay data, @NotNull Context context) {
|
||||
new NumeralBaseConverterDialog(data.getGenericResult().toString()).show(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isItemVisibleFor(@NotNull Generic generic, @NotNull JsclOperation operation) {
|
||||
return operation == JsclOperation.numeric && generic.getConstants().isEmpty();
|
||||
}
|
||||
},
|
||||
|
||||
plot(R.string.c_plot) {
|
||||
@Override
|
||||
public void onClick(@NotNull CalculatorDisplay data, @NotNull Context context) {
|
||||
final Generic generic = data.getGenericResult();
|
||||
assert generic != null;
|
||||
|
||||
final Constant constant = CollectionsUtils.getFirstCollectionElement(getNotSystemConstants(generic));
|
||||
assert constant != null;
|
||||
CalculatorActivityLauncher.plotGraph(context, generic, constant);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isItemVisibleFor(@NotNull Generic generic, @NotNull JsclOperation operation) {
|
||||
boolean result = false;
|
||||
|
||||
if (operation == JsclOperation.simplify) {
|
||||
if (getNotSystemConstants(generic).size() == 1) {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Set<Constant> getNotSystemConstants(@NotNull Generic generic) {
|
||||
final Set<Constant> notSystemConstants = new HashSet<Constant>();
|
||||
|
||||
for (Constant constant : generic.getConstants()) {
|
||||
IConstant var = CalculatorEngine.instance.getVarsRegistry().get(constant.getName());
|
||||
if (var != null && !var.isSystem() && !var.isDefined()) {
|
||||
notSystemConstants.add(constant);
|
||||
}
|
||||
}
|
||||
|
||||
return notSystemConstants;
|
||||
}
|
||||
};
|
||||
|
||||
private final int captionId;
|
||||
|
||||
MenuItem(int captionId) {
|
||||
this.captionId = captionId;
|
||||
}
|
||||
|
||||
public final boolean isItemVisible(@NotNull CalculatorDisplay display) {
|
||||
//noinspection ConstantConditions
|
||||
return display.isValid() && display.getGenericResult() != null && isItemVisibleFor(display.getGenericResult(), display.getJsclOperation());
|
||||
}
|
||||
|
||||
protected boolean isItemVisibleFor(@NotNull Generic generic, @NotNull JsclOperation operation) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getCaption(@NotNull Context context) {
|
||||
return context.getString(captionId);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean valid = true;
|
||||
|
||||
@Nullable
|
||||
private String errorMessage;
|
||||
|
||||
@NotNull
|
||||
private JsclOperation jsclOperation = JsclOperation.numeric;
|
||||
|
||||
@NotNull
|
||||
private final static TextProcessor<TextHighlighter.Result, String> textHighlighter = new TextHighlighter(Color.WHITE, false, CalculatorEngine.instance.getEngine());
|
||||
|
||||
@Nullable
|
||||
private Generic genericResult;
|
||||
|
||||
public CalculatorDisplay(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public CalculatorDisplay(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public CalculatorDisplay(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return valid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValid(boolean valid) {
|
||||
this.valid = valid;
|
||||
if (valid) {
|
||||
errorMessage = null;
|
||||
setTextColor(getResources().getColor(R.color.default_text_color));
|
||||
} else {
|
||||
setTextColor(getResources().getColor(R.color.display_error_text_color));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setErrorMessage(@Nullable String errorMessage) {
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setJsclOperation(@NotNull JsclOperation jsclOperation) {
|
||||
this.jsclOperation = jsclOperation;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public JsclOperation getJsclOperation() {
|
||||
return jsclOperation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setText(CharSequence text, BufferType type) {
|
||||
super.setText(text, type);
|
||||
|
||||
setValid(true);
|
||||
}
|
||||
|
||||
public synchronized void redraw() {
|
||||
if (isValid()) {
|
||||
String text = getText().toString();
|
||||
|
||||
Log.d(this.getClass().getName(), text);
|
||||
|
||||
try {
|
||||
TextHighlighter.Result result = textHighlighter.process(text);
|
||||
text = result.toString();
|
||||
} catch (CalculatorParseException e) {
|
||||
Log.e(this.getClass().getName(), e.getMessage(), e);
|
||||
}
|
||||
|
||||
Log.d(this.getClass().getName(), text);
|
||||
super.setText(Html.fromHtml(text), BufferType.EDITABLE);
|
||||
}
|
||||
|
||||
// todo serso: think where to move it (keep in mind org.solovyev.android.view.AutoResizeTextView.resetTextSize())
|
||||
setAddEllipsis(false);
|
||||
setMinTextSize(10);
|
||||
resizeText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGenericResult(@Nullable Generic genericResult) {
|
||||
this.genericResult = genericResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Generic getGenericResult() {
|
||||
return genericResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSelection() {
|
||||
return this.getSelectionStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelection(int selection) {
|
||||
// not supported by TextView
|
||||
}
|
||||
}
|
||||
|
@@ -1,166 +1,165 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
||||
* For more information, please, contact se.solovyev@gmail.com
|
||||
*/
|
||||
|
||||
package org.solovyev.android.calculator;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Color;
|
||||
import android.os.Build;
|
||||
import android.text.Html;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.ContextMenu;
|
||||
import android.widget.EditText;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.solovyev.android.calculator.model.CalculatorEngine;
|
||||
import org.solovyev.android.calculator.model.CalculatorParseException;
|
||||
import org.solovyev.android.calculator.model.TextProcessor;
|
||||
import org.solovyev.android.calculator.view.TextHighlighter;
|
||||
import org.solovyev.common.collections.CollectionsUtils;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 9/17/11
|
||||
* Time: 12:25 AM
|
||||
*/
|
||||
public class CalculatorEditor extends EditText implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
private static final String CALC_COLOR_DISPLAY_KEY = "org.solovyev.android.calculator.CalculatorModel_color_display";
|
||||
private static final boolean CALC_COLOR_DISPLAY_DEFAULT = true;
|
||||
|
||||
private boolean highlightText = true;
|
||||
|
||||
@NotNull
|
||||
private final static TextProcessor<TextHighlighter.Result, String> textHighlighter = new TextHighlighter(Color.WHITE, true, CalculatorEngine.instance.getEngine());
|
||||
|
||||
public CalculatorEditor(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public CalculatorEditor(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
// NOTE: in this solution cursor is missing
|
||||
|
||||
/*this.setOnTouchListener(new OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
final TextView textView = (TextView)v;
|
||||
// backup the input type
|
||||
int inputType = textView.getInputType();
|
||||
|
||||
// disable soft input
|
||||
textView.setInputType(InputType.TYPE_NULL);
|
||||
|
||||
// call native handler
|
||||
textView.onTouchEvent(event);
|
||||
|
||||
// restore input type
|
||||
textView.setInputType(inputType);
|
||||
|
||||
// consume touch even
|
||||
return true;
|
||||
}
|
||||
});*/
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onCheckIsTextEditor() {
|
||||
// NOTE: code below can be used carefully and should not be copied without special intention
|
||||
// The main purpose of code is to disable soft input (virtual keyboard) but leave all the TextEdit functionality, like cursor, scrolling, copy/paste menu etc
|
||||
|
||||
if ( Build.VERSION.SDK_INT >= 11 ) {
|
||||
// fix for missing cursor in android 3 and higher
|
||||
try {
|
||||
// IDEA: return false always except if method was called from TextView.isCursorVisible() method
|
||||
for (StackTraceElement stackTraceElement : CollectionsUtils.asList(Thread.currentThread().getStackTrace())) {
|
||||
if ( "isCursorVisible".equals(stackTraceElement.getMethodName()) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
// just in case...
|
||||
}
|
||||
|
||||
return false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public CalculatorEditor(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
init();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreateContextMenu(ContextMenu menu) {
|
||||
super.onCreateContextMenu(menu);
|
||||
|
||||
menu.removeItem(android.R.id.selectAll);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setText(CharSequence text, BufferType type) {
|
||||
super.setText(text, type);
|
||||
}
|
||||
|
||||
public synchronized void redraw() {
|
||||
String text = getText().toString();
|
||||
|
||||
int selectionStart = getSelectionStart();
|
||||
int selectionEnd = getSelectionEnd();
|
||||
|
||||
if (highlightText) {
|
||||
|
||||
Log.d(this.getClass().getName(), text);
|
||||
|
||||
try {
|
||||
final TextHighlighter.Result result = textHighlighter.process(text);
|
||||
selectionStart += result.getOffset();
|
||||
selectionEnd += result.getOffset();
|
||||
text = result.toString();
|
||||
} catch (CalculatorParseException e) {
|
||||
Log.e(this.getClass().getName(), e.getMessage(), e);
|
||||
}
|
||||
|
||||
Log.d(this.getClass().getName(), text);
|
||||
super.setText(Html.fromHtml(text), BufferType.EDITABLE);
|
||||
} else {
|
||||
super.setText(text, BufferType.EDITABLE);
|
||||
}
|
||||
|
||||
Log.d(this.getClass().getName(), getText().toString());
|
||||
|
||||
int length = getText().length();
|
||||
setSelection(Math.max(Math.min(length, selectionStart), 0), Math.max(Math.min(length, selectionEnd), 0));
|
||||
}
|
||||
|
||||
public boolean isHighlightText() {
|
||||
return highlightText;
|
||||
}
|
||||
|
||||
public void setHighlightText(boolean highlightText) {
|
||||
this.highlightText = highlightText;
|
||||
redraw();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||
if (CALC_COLOR_DISPLAY_KEY.equals(key)) {
|
||||
this.setHighlightText(preferences.getBoolean(CALC_COLOR_DISPLAY_KEY, CALC_COLOR_DISPLAY_DEFAULT));
|
||||
}
|
||||
}
|
||||
|
||||
public void init(@NotNull SharedPreferences preferences) {
|
||||
onSharedPreferenceChanged(preferences, CALC_COLOR_DISPLAY_KEY);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
||||
* For more information, please, contact se.solovyev@gmail.com
|
||||
*/
|
||||
|
||||
package org.solovyev.android.calculator;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Color;
|
||||
import android.os.Build;
|
||||
import android.text.Html;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.ContextMenu;
|
||||
import android.widget.EditText;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.solovyev.android.calculator.model.CalculatorEngine;
|
||||
import org.solovyev.android.calculator.text.TextProcessor;
|
||||
import org.solovyev.android.calculator.view.TextHighlighter;
|
||||
import org.solovyev.common.collections.CollectionsUtils;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 9/17/11
|
||||
* Time: 12:25 AM
|
||||
*/
|
||||
public class CalculatorEditor extends EditText implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
private static final String CALC_COLOR_DISPLAY_KEY = "org.solovyev.android.calculator.CalculatorModel_color_display";
|
||||
private static final boolean CALC_COLOR_DISPLAY_DEFAULT = true;
|
||||
|
||||
private boolean highlightText = true;
|
||||
|
||||
@NotNull
|
||||
private final static TextProcessor<TextHighlighter.Result, String> textHighlighter = new TextHighlighter(Color.WHITE, true, CalculatorEngine.instance.getEngine());
|
||||
|
||||
public CalculatorEditor(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public CalculatorEditor(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
// NOTE: in this solution cursor is missing
|
||||
|
||||
/*this.setOnTouchListener(new OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
final TextView textView = (TextView)v;
|
||||
// backup the input type
|
||||
int inputType = textView.getInputType();
|
||||
|
||||
// disable soft input
|
||||
textView.setInputType(InputType.TYPE_NULL);
|
||||
|
||||
// call native handler
|
||||
textView.onTouchEvent(event);
|
||||
|
||||
// restore input type
|
||||
textView.setInputType(inputType);
|
||||
|
||||
// consume touch even
|
||||
return true;
|
||||
}
|
||||
});*/
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onCheckIsTextEditor() {
|
||||
// NOTE: code below can be used carefully and should not be copied without special intention
|
||||
// The main purpose of code is to disable soft input (virtual keyboard) but leave all the TextEdit functionality, like cursor, scrolling, copy/paste menu etc
|
||||
|
||||
if ( Build.VERSION.SDK_INT >= 11 ) {
|
||||
// fix for missing cursor in android 3 and higher
|
||||
try {
|
||||
// IDEA: return false always except if method was called from TextView.isCursorVisible() method
|
||||
for (StackTraceElement stackTraceElement : CollectionsUtils.asList(Thread.currentThread().getStackTrace())) {
|
||||
if ( "isCursorVisible".equals(stackTraceElement.getMethodName()) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
// just in case...
|
||||
}
|
||||
|
||||
return false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public CalculatorEditor(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
init();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreateContextMenu(ContextMenu menu) {
|
||||
super.onCreateContextMenu(menu);
|
||||
|
||||
menu.removeItem(android.R.id.selectAll);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setText(CharSequence text, BufferType type) {
|
||||
super.setText(text, type);
|
||||
}
|
||||
|
||||
public synchronized void redraw() {
|
||||
String text = getText().toString();
|
||||
|
||||
int selectionStart = getSelectionStart();
|
||||
int selectionEnd = getSelectionEnd();
|
||||
|
||||
if (highlightText) {
|
||||
|
||||
Log.d(this.getClass().getName(), text);
|
||||
|
||||
try {
|
||||
final TextHighlighter.Result result = textHighlighter.process(text);
|
||||
selectionStart += result.getOffset();
|
||||
selectionEnd += result.getOffset();
|
||||
text = result.toString();
|
||||
} catch (CalculatorParseException e) {
|
||||
Log.e(this.getClass().getName(), e.getMessage(), e);
|
||||
}
|
||||
|
||||
Log.d(this.getClass().getName(), text);
|
||||
super.setText(Html.fromHtml(text), BufferType.EDITABLE);
|
||||
} else {
|
||||
super.setText(text, BufferType.EDITABLE);
|
||||
}
|
||||
|
||||
Log.d(this.getClass().getName(), getText().toString());
|
||||
|
||||
int length = getText().length();
|
||||
setSelection(Math.max(Math.min(length, selectionStart), 0), Math.max(Math.min(length, selectionEnd), 0));
|
||||
}
|
||||
|
||||
public boolean isHighlightText() {
|
||||
return highlightText;
|
||||
}
|
||||
|
||||
public void setHighlightText(boolean highlightText) {
|
||||
this.highlightText = highlightText;
|
||||
redraw();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
|
||||
if (CALC_COLOR_DISPLAY_KEY.equals(key)) {
|
||||
this.setHighlightText(preferences.getBoolean(CALC_COLOR_DISPLAY_KEY, CALC_COLOR_DISPLAY_DEFAULT));
|
||||
}
|
||||
}
|
||||
|
||||
public void init(@NotNull SharedPreferences preferences) {
|
||||
onSharedPreferenceChanged(preferences, CALC_COLOR_DISPLAY_KEY);
|
||||
}
|
||||
}
|
||||
|
@@ -1,411 +1,410 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
||||
* For more information, please, contact se.solovyev@gmail.com
|
||||
*/
|
||||
|
||||
package org.solovyev.android.calculator;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Handler;
|
||||
import android.text.ClipboardManager;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.CursorControl;
|
||||
import org.solovyev.android.calculator.history.CalculatorHistory;
|
||||
import org.solovyev.android.calculator.history.CalculatorHistoryState;
|
||||
import org.solovyev.android.calculator.history.TextViewEditorAdapter;
|
||||
import org.solovyev.android.calculator.jscl.JsclOperation;
|
||||
import org.solovyev.android.calculator.math.MathType;
|
||||
import org.solovyev.android.calculator.model.CalculatorEngine;
|
||||
import org.solovyev.android.calculator.model.CalculatorEvalException;
|
||||
import org.solovyev.android.calculator.model.CalculatorParseException;
|
||||
import org.solovyev.android.history.HistoryControl;
|
||||
import org.solovyev.android.menu.AMenuBuilder;
|
||||
import org.solovyev.android.menu.MenuImpl;
|
||||
import org.solovyev.common.MutableObject;
|
||||
import org.solovyev.common.history.HistoryAction;
|
||||
import org.solovyev.common.msg.Message;
|
||||
import org.solovyev.common.text.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 9/12/11
|
||||
* Time: 11:15 PM
|
||||
*/
|
||||
public enum CalculatorModel implements CursorControl, HistoryControl<CalculatorHistoryState>, CalculatorEngineControl {
|
||||
|
||||
instance;
|
||||
|
||||
// millis to wait before evaluation after user edit action
|
||||
public static final int EVAL_DELAY_MILLIS = 0;
|
||||
|
||||
@NotNull
|
||||
private CalculatorEditor editor;
|
||||
|
||||
@NotNull
|
||||
private CalculatorDisplay display;
|
||||
|
||||
@NotNull
|
||||
private CalculatorEngine calculatorEngine;
|
||||
|
||||
public CalculatorModel init(@NotNull final Activity activity, @NotNull SharedPreferences preferences, @NotNull CalculatorEngine calculator) {
|
||||
Log.d(this.getClass().getName(), "CalculatorModel initialization with activity: " + activity);
|
||||
this.calculatorEngine = calculator;
|
||||
|
||||
this.editor = (CalculatorEditor) activity.findViewById(R.id.calculatorEditor);
|
||||
this.editor.init(preferences);
|
||||
preferences.registerOnSharedPreferenceChangeListener(editor);
|
||||
|
||||
this.display = (CalculatorDisplay) activity.findViewById(R.id.calculatorDisplay);
|
||||
this.display.setOnClickListener(new CalculatorDisplayOnClickListener(activity));
|
||||
|
||||
final CalculatorHistoryState lastState = CalculatorHistory.instance.getLastHistoryState();
|
||||
if (lastState == null) {
|
||||
saveHistoryState();
|
||||
} else {
|
||||
setCurrentHistoryState(lastState);
|
||||
}
|
||||
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private static void showEvaluationError(@NotNull Activity activity, @NotNull final String errorMessage) {
|
||||
final LayoutInflater layoutInflater = (LayoutInflater) activity.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
|
||||
|
||||
final View errorMessageView = layoutInflater.inflate(R.layout.display_error_message, null);
|
||||
((TextView) errorMessageView.findViewById(R.id.error_message_text_view)).setText(errorMessage);
|
||||
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(activity)
|
||||
.setPositiveButton(R.string.c_cancel, null)
|
||||
.setView(errorMessageView);
|
||||
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
public void copyResult(@NotNull Context context) {
|
||||
copyResult(context, display);
|
||||
}
|
||||
|
||||
public static void copyResult(@NotNull Context context, @NotNull final CalculatorDisplay display) {
|
||||
if (display.isValid()) {
|
||||
final CharSequence text = display.getText();
|
||||
if (!StringUtils.isEmpty(text)) {
|
||||
final ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Activity.CLIPBOARD_SERVICE);
|
||||
clipboard.setText(text.toString());
|
||||
Toast.makeText(context, context.getText(R.string.c_result_copied), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void saveHistoryState() {
|
||||
CalculatorHistory.instance.addState(getCurrentHistoryState());
|
||||
}
|
||||
|
||||
public void setCursorOnStart() {
|
||||
editor.setSelection(0);
|
||||
}
|
||||
|
||||
public void setCursorOnEnd() {
|
||||
editor.setSelection(editor.getText().length());
|
||||
}
|
||||
|
||||
public void moveCursorLeft() {
|
||||
if (editor.getSelectionStart() > 0) {
|
||||
editor.setSelection(editor.getSelectionStart() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
public void moveCursorRight() {
|
||||
if (editor.getSelectionStart() < editor.getText().length()) {
|
||||
editor.setSelection(editor.getSelectionStart() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
public void doTextOperation(@NotNull TextOperation operation) {
|
||||
doTextOperation(operation, true);
|
||||
}
|
||||
|
||||
public void doTextOperation(@NotNull TextOperation operation, boolean delayEvaluate) {
|
||||
doTextOperation(operation, delayEvaluate, JsclOperation.numeric, false);
|
||||
}
|
||||
|
||||
public void doTextOperation(@NotNull TextOperation operation, boolean delayEvaluate, @NotNull JsclOperation jsclOperation, boolean forceEval) {
|
||||
final String editorStateBefore = this.editor.getText().toString();
|
||||
|
||||
Log.d(CalculatorModel.class.getName(), "Editor state changed before '" + editorStateBefore + "'");
|
||||
operation.doOperation(this.editor);
|
||||
//Log.d(CalculatorModel.class.getName(), "Doing text operation" + StringUtils.fromStackTrace(Thread.currentThread().getStackTrace()));
|
||||
|
||||
final String editorStateAfter = this.editor.getText().toString();
|
||||
if (forceEval ||!editorStateBefore.equals(editorStateAfter)) {
|
||||
|
||||
editor.redraw();
|
||||
|
||||
evaluate(delayEvaluate, editorStateAfter, jsclOperation, null);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private final static MutableObject<Runnable> pendingOperation = new MutableObject<Runnable>();
|
||||
|
||||
private void evaluate(boolean delayEvaluate,
|
||||
@NotNull final String expression,
|
||||
@NotNull final JsclOperation operation,
|
||||
@Nullable CalculatorHistoryState historyState) {
|
||||
|
||||
final CalculatorHistoryState localHistoryState;
|
||||
if (historyState == null) {
|
||||
//this.display.setText("");
|
||||
localHistoryState = getCurrentHistoryState();
|
||||
} else {
|
||||
this.display.setText(historyState.getDisplayState().getEditorState().getText());
|
||||
localHistoryState = historyState;
|
||||
}
|
||||
|
||||
pendingOperation.setObject(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// allow only one runner at one time
|
||||
synchronized (pendingOperation) {
|
||||
//lock all operations with history
|
||||
if (pendingOperation.getObject() == this) {
|
||||
// actually nothing shall be logged while text operations are done
|
||||
evaluate(expression, operation, this);
|
||||
|
||||
if (pendingOperation.getObject() == this) {
|
||||
// todo serso: of course there is small probability that someone will set pendingOperation after if statement but before .setObject(null)
|
||||
pendingOperation.setObject(null);
|
||||
localHistoryState.setDisplayState(getCurrentHistoryState().getDisplayState());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (delayEvaluate) {
|
||||
if (historyState == null) {
|
||||
CalculatorHistory.instance.addState(localHistoryState);
|
||||
}
|
||||
// todo serso: this is not correct - operation is processing still in the same thread
|
||||
new Handler().postDelayed(pendingOperation.getObject(), EVAL_DELAY_MILLIS);
|
||||
} else {
|
||||
pendingOperation.getObject().run();
|
||||
if (historyState == null) {
|
||||
CalculatorHistory.instance.addState(localHistoryState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evaluate() {
|
||||
evaluate(false, this.editor.getText().toString(), JsclOperation.numeric, null);
|
||||
}
|
||||
|
||||
public void evaluate(@NotNull JsclOperation operation) {
|
||||
evaluate(false, this.editor.getText().toString(), operation, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void simplify() {
|
||||
evaluate(false, this.editor.getText().toString(), JsclOperation.simplify, null);
|
||||
}
|
||||
|
||||
private void evaluate(@Nullable final String expression,
|
||||
@NotNull JsclOperation operation,
|
||||
@NotNull Runnable currentRunner) {
|
||||
|
||||
if (!StringUtils.isEmpty(expression)) {
|
||||
try {
|
||||
Log.d(CalculatorModel.class.getName(), "Trying to evaluate '" + operation + "': " + expression /*+ StringUtils.fromStackTrace(Thread.currentThread().getStackTrace())*/);
|
||||
final CalculatorEngine.Result result = calculatorEngine.evaluate(operation, expression);
|
||||
|
||||
// todo serso: second condition might replaced with expression.equals(this.editor.getText().toString()) ONLY if expression will be formatted with text highlighter
|
||||
if (currentRunner == pendingOperation.getObject() && this.editor.getText().length() > 0) {
|
||||
display.setText(result.getResult());
|
||||
} else {
|
||||
display.setText("");
|
||||
}
|
||||
display.setJsclOperation(result.getUserOperation());
|
||||
display.setGenericResult(result.getGenericResult());
|
||||
} catch (CalculatorParseException e) {
|
||||
handleEvaluationException(expression, display, operation, e);
|
||||
} catch (CalculatorEvalException e) {
|
||||
handleEvaluationException(expression, display, operation, e);
|
||||
}
|
||||
} else {
|
||||
this.display.setText("");
|
||||
this.display.setJsclOperation(operation);
|
||||
this.display.setGenericResult(null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
this.display.redraw();
|
||||
}
|
||||
|
||||
private void handleEvaluationException(@NotNull String expression,
|
||||
@NotNull CalculatorDisplay localDisplay,
|
||||
@NotNull JsclOperation operation,
|
||||
@NotNull Message e) {
|
||||
Log.d(CalculatorModel.class.getName(), "Evaluation failed for : " + expression + ". Error message: " + e);
|
||||
if ( StringUtils.isEmpty(localDisplay.getText()) ) {
|
||||
// if previous display state was empty -> show error
|
||||
localDisplay.setText(R.string.c_syntax_error);
|
||||
} else {
|
||||
// show previous result instead of error caption (actually previous result will be greyed)
|
||||
}
|
||||
localDisplay.setJsclOperation(operation);
|
||||
localDisplay.setGenericResult(null);
|
||||
localDisplay.setValid(false);
|
||||
localDisplay.setErrorMessage(e.getLocalizedMessage());
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
if (!StringUtils.isEmpty(editor.getText()) || !StringUtils.isEmpty(display.getText())) {
|
||||
editor.getText().clear();
|
||||
display.setText("");
|
||||
saveHistoryState();
|
||||
}
|
||||
}
|
||||
|
||||
public void processDigitButtonAction(@Nullable final String text) {
|
||||
processDigitButtonAction(text, true);
|
||||
}
|
||||
|
||||
public void processDigitButtonAction(@Nullable final String text, boolean delayEvaluate) {
|
||||
|
||||
if (!StringUtils.isEmpty(text)) {
|
||||
doTextOperation(new CalculatorModel.TextOperation() {
|
||||
|
||||
@Override
|
||||
public void doOperation(@NotNull EditText editor) {
|
||||
int cursorPositionOffset = 0;
|
||||
final StringBuilder textToBeInserted = new StringBuilder(text);
|
||||
|
||||
final MathType.Result mathType = MathType.getType(text, 0, false);
|
||||
switch (mathType.getMathType()) {
|
||||
case function:
|
||||
textToBeInserted.append("()");
|
||||
cursorPositionOffset = -1;
|
||||
break;
|
||||
case operator:
|
||||
textToBeInserted.append("()");
|
||||
cursorPositionOffset = -1;
|
||||
break;
|
||||
case comma:
|
||||
textToBeInserted.append(" ");
|
||||
break;
|
||||
}
|
||||
|
||||
if (cursorPositionOffset == 0) {
|
||||
if (MathType.openGroupSymbols.contains(text)) {
|
||||
cursorPositionOffset = -1;
|
||||
}
|
||||
}
|
||||
|
||||
editor.getText().insert(editor.getSelectionStart(), textToBeInserted.toString());
|
||||
editor.setSelection(editor.getSelectionStart() + cursorPositionOffset, editor.getSelectionEnd() + cursorPositionOffset);
|
||||
}
|
||||
}, delayEvaluate);
|
||||
}
|
||||
}
|
||||
|
||||
public static interface TextOperation {
|
||||
|
||||
void doOperation(@NotNull EditText editor);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doHistoryAction(@NotNull HistoryAction historyAction) {
|
||||
synchronized (CalculatorHistory.instance) {
|
||||
if (CalculatorHistory.instance.isActionAvailable(historyAction)) {
|
||||
final CalculatorHistoryState newState = CalculatorHistory.instance.doAction(historyAction, getCurrentHistoryState());
|
||||
if (newState != null) {
|
||||
setCurrentHistoryState(newState);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCurrentHistoryState(@NotNull CalculatorHistoryState editorHistoryState) {
|
||||
synchronized (CalculatorHistory.instance) {
|
||||
Log.d(this.getClass().getName(), "Saved history found: " + editorHistoryState);
|
||||
|
||||
editorHistoryState.setValuesFromHistory(new TextViewEditorAdapter(this.editor), this.display);
|
||||
|
||||
final String expression = this.editor.getText().toString();
|
||||
if ( !StringUtils.isEmpty(expression) ) {
|
||||
if ( StringUtils.isEmpty(this.display.getText().toString()) ) {
|
||||
evaluate(false, expression, this.display.getJsclOperation(), editorHistoryState);
|
||||
}
|
||||
}
|
||||
|
||||
editor.redraw();
|
||||
display.redraw();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public CalculatorHistoryState getCurrentHistoryState() {
|
||||
synchronized (CalculatorHistory.instance) {
|
||||
return CalculatorHistoryState.newInstance(new TextViewEditorAdapter(this.editor), this.display);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CalculatorDisplay getDisplay() {
|
||||
return display;
|
||||
}
|
||||
|
||||
private static class CalculatorDisplayOnClickListener implements View.OnClickListener {
|
||||
|
||||
@NotNull
|
||||
private final Activity activity;
|
||||
|
||||
public CalculatorDisplayOnClickListener(@NotNull Activity activity) {
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (v instanceof CalculatorDisplay) {
|
||||
final CalculatorDisplay cd = (CalculatorDisplay) v;
|
||||
|
||||
if (cd.isValid()) {
|
||||
final List<CalculatorDisplay.MenuItem> filteredMenuItems = new ArrayList<CalculatorDisplay.MenuItem>(CalculatorDisplay.MenuItem.values().length);
|
||||
for (CalculatorDisplay.MenuItem menuItem : CalculatorDisplay.MenuItem.values()) {
|
||||
if (menuItem.isItemVisible(cd)) {
|
||||
filteredMenuItems.add(menuItem);
|
||||
}
|
||||
}
|
||||
|
||||
if (!filteredMenuItems.isEmpty()) {
|
||||
AMenuBuilder.newInstance(activity, MenuImpl.newInstance(filteredMenuItems)).create(cd).show();
|
||||
}
|
||||
|
||||
} else {
|
||||
final String errorMessage = cd.getErrorMessage();
|
||||
if (errorMessage != null) {
|
||||
showEvaluationError(activity, errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
||||
* For more information, please, contact se.solovyev@gmail.com
|
||||
*/
|
||||
|
||||
package org.solovyev.android.calculator;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Handler;
|
||||
import android.text.ClipboardManager;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.CursorControl;
|
||||
import org.solovyev.android.calculator.history.CalculatorHistory;
|
||||
import org.solovyev.android.calculator.history.CalculatorHistoryState;
|
||||
import org.solovyev.android.calculator.history.TextViewEditorAdapter;
|
||||
import org.solovyev.android.calculator.jscl.JsclOperation;
|
||||
import org.solovyev.android.calculator.math.MathType;
|
||||
import org.solovyev.android.calculator.model.CalculatorEngine;
|
||||
import org.solovyev.android.calculator.model.CalculatorEvalException;
|
||||
import org.solovyev.android.history.HistoryControl;
|
||||
import org.solovyev.android.menu.AMenuBuilder;
|
||||
import org.solovyev.android.menu.MenuImpl;
|
||||
import org.solovyev.common.MutableObject;
|
||||
import org.solovyev.common.history.HistoryAction;
|
||||
import org.solovyev.common.msg.Message;
|
||||
import org.solovyev.common.text.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 9/12/11
|
||||
* Time: 11:15 PM
|
||||
*/
|
||||
public enum CalculatorModel implements CursorControl, HistoryControl<CalculatorHistoryState>, CalculatorEngineControl {
|
||||
|
||||
instance;
|
||||
|
||||
// millis to wait before evaluation after user edit action
|
||||
public static final int EVAL_DELAY_MILLIS = 0;
|
||||
|
||||
@NotNull
|
||||
private CalculatorEditor editor;
|
||||
|
||||
@NotNull
|
||||
private CalculatorDisplay display;
|
||||
|
||||
@NotNull
|
||||
private CalculatorEngine calculatorEngine;
|
||||
|
||||
public CalculatorModel init(@NotNull final Activity activity, @NotNull SharedPreferences preferences, @NotNull CalculatorEngine calculator) {
|
||||
Log.d(this.getClass().getName(), "CalculatorModel initialization with activity: " + activity);
|
||||
this.calculatorEngine = calculator;
|
||||
|
||||
this.editor = (CalculatorEditor) activity.findViewById(R.id.calculatorEditor);
|
||||
this.editor.init(preferences);
|
||||
preferences.registerOnSharedPreferenceChangeListener(editor);
|
||||
|
||||
this.display = (CalculatorDisplay) activity.findViewById(R.id.calculatorDisplay);
|
||||
this.display.setOnClickListener(new CalculatorDisplayOnClickListener(activity));
|
||||
|
||||
final CalculatorHistoryState lastState = CalculatorHistory.instance.getLastHistoryState();
|
||||
if (lastState == null) {
|
||||
saveHistoryState();
|
||||
} else {
|
||||
setCurrentHistoryState(lastState);
|
||||
}
|
||||
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private static void showEvaluationError(@NotNull Activity activity, @NotNull final String errorMessage) {
|
||||
final LayoutInflater layoutInflater = (LayoutInflater) activity.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
|
||||
|
||||
final View errorMessageView = layoutInflater.inflate(R.layout.display_error_message, null);
|
||||
((TextView) errorMessageView.findViewById(R.id.error_message_text_view)).setText(errorMessage);
|
||||
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(activity)
|
||||
.setPositiveButton(R.string.c_cancel, null)
|
||||
.setView(errorMessageView);
|
||||
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
public void copyResult(@NotNull Context context) {
|
||||
copyResult(context, display);
|
||||
}
|
||||
|
||||
public static void copyResult(@NotNull Context context, @NotNull final CalculatorDisplay display) {
|
||||
if (display.isValid()) {
|
||||
final CharSequence text = display.getText();
|
||||
if (!StringUtils.isEmpty(text)) {
|
||||
final ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Activity.CLIPBOARD_SERVICE);
|
||||
clipboard.setText(text.toString());
|
||||
Toast.makeText(context, context.getText(R.string.c_result_copied), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void saveHistoryState() {
|
||||
CalculatorHistory.instance.addState(getCurrentHistoryState());
|
||||
}
|
||||
|
||||
public void setCursorOnStart() {
|
||||
editor.setSelection(0);
|
||||
}
|
||||
|
||||
public void setCursorOnEnd() {
|
||||
editor.setSelection(editor.getText().length());
|
||||
}
|
||||
|
||||
public void moveCursorLeft() {
|
||||
if (editor.getSelectionStart() > 0) {
|
||||
editor.setSelection(editor.getSelectionStart() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
public void moveCursorRight() {
|
||||
if (editor.getSelectionStart() < editor.getText().length()) {
|
||||
editor.setSelection(editor.getSelectionStart() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
public void doTextOperation(@NotNull TextOperation operation) {
|
||||
doTextOperation(operation, true);
|
||||
}
|
||||
|
||||
public void doTextOperation(@NotNull TextOperation operation, boolean delayEvaluate) {
|
||||
doTextOperation(operation, delayEvaluate, JsclOperation.numeric, false);
|
||||
}
|
||||
|
||||
public void doTextOperation(@NotNull TextOperation operation, boolean delayEvaluate, @NotNull JsclOperation jsclOperation, boolean forceEval) {
|
||||
final String editorStateBefore = this.editor.getText().toString();
|
||||
|
||||
Log.d(CalculatorModel.class.getName(), "Editor state changed before '" + editorStateBefore + "'");
|
||||
operation.doOperation(this.editor);
|
||||
//Log.d(CalculatorModel.class.getName(), "Doing text operation" + StringUtils.fromStackTrace(Thread.currentThread().getStackTrace()));
|
||||
|
||||
final String editorStateAfter = this.editor.getText().toString();
|
||||
if (forceEval ||!editorStateBefore.equals(editorStateAfter)) {
|
||||
|
||||
editor.redraw();
|
||||
|
||||
evaluate(delayEvaluate, editorStateAfter, jsclOperation, null);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private final static MutableObject<Runnable> pendingOperation = new MutableObject<Runnable>();
|
||||
|
||||
private void evaluate(boolean delayEvaluate,
|
||||
@NotNull final String expression,
|
||||
@NotNull final JsclOperation operation,
|
||||
@Nullable CalculatorHistoryState historyState) {
|
||||
|
||||
final CalculatorHistoryState localHistoryState;
|
||||
if (historyState == null) {
|
||||
//this.display.setText("");
|
||||
localHistoryState = getCurrentHistoryState();
|
||||
} else {
|
||||
this.display.setText(historyState.getDisplayState().getEditorState().getText());
|
||||
localHistoryState = historyState;
|
||||
}
|
||||
|
||||
pendingOperation.setObject(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// allow only one runner at one time
|
||||
synchronized (pendingOperation) {
|
||||
//lock all operations with history
|
||||
if (pendingOperation.getObject() == this) {
|
||||
// actually nothing shall be logged while text operations are done
|
||||
evaluate(expression, operation, this);
|
||||
|
||||
if (pendingOperation.getObject() == this) {
|
||||
// todo serso: of course there is small probability that someone will set pendingOperation after if statement but before .setObject(null)
|
||||
pendingOperation.setObject(null);
|
||||
localHistoryState.setDisplayState(getCurrentHistoryState().getDisplayState());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (delayEvaluate) {
|
||||
if (historyState == null) {
|
||||
CalculatorHistory.instance.addState(localHistoryState);
|
||||
}
|
||||
// todo serso: this is not correct - operation is processing still in the same thread
|
||||
new Handler().postDelayed(pendingOperation.getObject(), EVAL_DELAY_MILLIS);
|
||||
} else {
|
||||
pendingOperation.getObject().run();
|
||||
if (historyState == null) {
|
||||
CalculatorHistory.instance.addState(localHistoryState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evaluate() {
|
||||
evaluate(false, this.editor.getText().toString(), JsclOperation.numeric, null);
|
||||
}
|
||||
|
||||
public void evaluate(@NotNull JsclOperation operation) {
|
||||
evaluate(false, this.editor.getText().toString(), operation, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void simplify() {
|
||||
evaluate(false, this.editor.getText().toString(), JsclOperation.simplify, null);
|
||||
}
|
||||
|
||||
private void evaluate(@Nullable final String expression,
|
||||
@NotNull JsclOperation operation,
|
||||
@NotNull Runnable currentRunner) {
|
||||
|
||||
if (!StringUtils.isEmpty(expression)) {
|
||||
try {
|
||||
Log.d(CalculatorModel.class.getName(), "Trying to evaluate '" + operation + "': " + expression /*+ StringUtils.fromStackTrace(Thread.currentThread().getStackTrace())*/);
|
||||
final CalculatorEngine.Result result = calculatorEngine.evaluate(operation, expression);
|
||||
|
||||
// todo serso: second condition might replaced with expression.equals(this.editor.getText().toString()) ONLY if expression will be formatted with text highlighter
|
||||
if (currentRunner == pendingOperation.getObject() && this.editor.getText().length() > 0) {
|
||||
display.setText(result.getResult());
|
||||
} else {
|
||||
display.setText("");
|
||||
}
|
||||
display.setJsclOperation(result.getUserOperation());
|
||||
display.setGenericResult(result.getGenericResult());
|
||||
} catch (CalculatorParseException e) {
|
||||
handleEvaluationException(expression, display, operation, e);
|
||||
} catch (CalculatorEvalException e) {
|
||||
handleEvaluationException(expression, display, operation, e);
|
||||
}
|
||||
} else {
|
||||
this.display.setText("");
|
||||
this.display.setJsclOperation(operation);
|
||||
this.display.setGenericResult(null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
this.display.redraw();
|
||||
}
|
||||
|
||||
private void handleEvaluationException(@NotNull String expression,
|
||||
@NotNull CalculatorDisplay localDisplay,
|
||||
@NotNull JsclOperation operation,
|
||||
@NotNull Message e) {
|
||||
Log.d(CalculatorModel.class.getName(), "Evaluation failed for : " + expression + ". Error message: " + e);
|
||||
if ( StringUtils.isEmpty(localDisplay.getText()) ) {
|
||||
// if previous display state was empty -> show error
|
||||
localDisplay.setText(R.string.c_syntax_error);
|
||||
} else {
|
||||
// show previous result instead of error caption (actually previous result will be greyed)
|
||||
}
|
||||
localDisplay.setJsclOperation(operation);
|
||||
localDisplay.setGenericResult(null);
|
||||
localDisplay.setValid(false);
|
||||
localDisplay.setErrorMessage(e.getLocalizedMessage());
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
if (!StringUtils.isEmpty(editor.getText()) || !StringUtils.isEmpty(display.getText())) {
|
||||
editor.getText().clear();
|
||||
display.setText("");
|
||||
saveHistoryState();
|
||||
}
|
||||
}
|
||||
|
||||
public void processDigitButtonAction(@Nullable final String text) {
|
||||
processDigitButtonAction(text, true);
|
||||
}
|
||||
|
||||
public void processDigitButtonAction(@Nullable final String text, boolean delayEvaluate) {
|
||||
|
||||
if (!StringUtils.isEmpty(text)) {
|
||||
doTextOperation(new CalculatorModel.TextOperation() {
|
||||
|
||||
@Override
|
||||
public void doOperation(@NotNull EditText editor) {
|
||||
int cursorPositionOffset = 0;
|
||||
final StringBuilder textToBeInserted = new StringBuilder(text);
|
||||
|
||||
final MathType.Result mathType = MathType.getType(text, 0, false);
|
||||
switch (mathType.getMathType()) {
|
||||
case function:
|
||||
textToBeInserted.append("()");
|
||||
cursorPositionOffset = -1;
|
||||
break;
|
||||
case operator:
|
||||
textToBeInserted.append("()");
|
||||
cursorPositionOffset = -1;
|
||||
break;
|
||||
case comma:
|
||||
textToBeInserted.append(" ");
|
||||
break;
|
||||
}
|
||||
|
||||
if (cursorPositionOffset == 0) {
|
||||
if (MathType.openGroupSymbols.contains(text)) {
|
||||
cursorPositionOffset = -1;
|
||||
}
|
||||
}
|
||||
|
||||
editor.getText().insert(editor.getSelectionStart(), textToBeInserted.toString());
|
||||
editor.setSelection(editor.getSelectionStart() + cursorPositionOffset, editor.getSelectionEnd() + cursorPositionOffset);
|
||||
}
|
||||
}, delayEvaluate);
|
||||
}
|
||||
}
|
||||
|
||||
public static interface TextOperation {
|
||||
|
||||
void doOperation(@NotNull EditText editor);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doHistoryAction(@NotNull HistoryAction historyAction) {
|
||||
synchronized (CalculatorHistory.instance) {
|
||||
if (CalculatorHistory.instance.isActionAvailable(historyAction)) {
|
||||
final CalculatorHistoryState newState = CalculatorHistory.instance.doAction(historyAction, getCurrentHistoryState());
|
||||
if (newState != null) {
|
||||
setCurrentHistoryState(newState);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCurrentHistoryState(@NotNull CalculatorHistoryState editorHistoryState) {
|
||||
synchronized (CalculatorHistory.instance) {
|
||||
Log.d(this.getClass().getName(), "Saved history found: " + editorHistoryState);
|
||||
|
||||
editorHistoryState.setValuesFromHistory(new TextViewEditorAdapter(this.editor), this.display);
|
||||
|
||||
final String expression = this.editor.getText().toString();
|
||||
if ( !StringUtils.isEmpty(expression) ) {
|
||||
if ( StringUtils.isEmpty(this.display.getText().toString()) ) {
|
||||
evaluate(false, expression, this.display.getJsclOperation(), editorHistoryState);
|
||||
}
|
||||
}
|
||||
|
||||
editor.redraw();
|
||||
display.redraw();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public CalculatorHistoryState getCurrentHistoryState() {
|
||||
synchronized (CalculatorHistory.instance) {
|
||||
return CalculatorHistoryState.newInstance(new TextViewEditorAdapter(this.editor), this.display);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CalculatorDisplay getDisplay() {
|
||||
return display;
|
||||
}
|
||||
|
||||
private static class CalculatorDisplayOnClickListener implements View.OnClickListener {
|
||||
|
||||
@NotNull
|
||||
private final Activity activity;
|
||||
|
||||
public CalculatorDisplayOnClickListener(@NotNull Activity activity) {
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (v instanceof CalculatorDisplay) {
|
||||
final CalculatorDisplay cd = (CalculatorDisplay) v;
|
||||
|
||||
if (cd.isValid()) {
|
||||
final List<CalculatorDisplay.MenuItem> filteredMenuItems = new ArrayList<CalculatorDisplay.MenuItem>(CalculatorDisplay.MenuItem.values().length);
|
||||
for (CalculatorDisplay.MenuItem menuItem : CalculatorDisplay.MenuItem.values()) {
|
||||
if (menuItem.isItemVisible(cd)) {
|
||||
filteredMenuItems.add(menuItem);
|
||||
}
|
||||
}
|
||||
|
||||
if (!filteredMenuItems.isEmpty()) {
|
||||
AMenuBuilder.newInstance(activity, MenuImpl.newInstance(filteredMenuItems)).create(cd).show();
|
||||
}
|
||||
|
||||
} else {
|
||||
final String errorMessage = cd.getErrorMessage();
|
||||
if (errorMessage != null) {
|
||||
showEvaluationError(activity, errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,81 +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.history;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.simpleframework.xml.Element;
|
||||
import org.simpleframework.xml.Transient;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 10/15/11
|
||||
* Time: 1:45 PM
|
||||
*/
|
||||
public class AbstractHistoryState implements Cloneable{
|
||||
|
||||
@Element
|
||||
private long time = new Date().getTime();
|
||||
|
||||
@Element(required = false)
|
||||
@Nullable
|
||||
private String comment;
|
||||
|
||||
@Transient
|
||||
private boolean saved;
|
||||
|
||||
@Transient
|
||||
private int id = 0;
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public long getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public void setTime(long time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getComment() {
|
||||
return comment;
|
||||
}
|
||||
|
||||
public void setComment(@Nullable String comment) {
|
||||
this.comment = comment;
|
||||
}
|
||||
|
||||
public boolean isSaved() {
|
||||
return saved;
|
||||
}
|
||||
|
||||
public void setSaved(boolean saved) {
|
||||
this.saved = saved;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractHistoryState clone() {
|
||||
AbstractHistoryState clone;
|
||||
|
||||
try {
|
||||
clone = (AbstractHistoryState)super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new UnsupportedOperationException(e);
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
}
|
@@ -1,138 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
||||
* For more information, please, contact se.solovyev@gmail.com
|
||||
*/
|
||||
|
||||
package org.solovyev.android.calculator.history;
|
||||
|
||||
import jscl.math.Generic;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.simpleframework.xml.Element;
|
||||
import org.simpleframework.xml.Root;
|
||||
import org.simpleframework.xml.Transient;
|
||||
import org.solovyev.android.calculator.ICalculatorDisplay;
|
||||
import org.solovyev.android.calculator.jscl.JsclOperation;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 9/17/11
|
||||
* Time: 11:05 PM
|
||||
*/
|
||||
|
||||
@Root
|
||||
public class CalculatorDisplayHistoryState implements Cloneable {
|
||||
|
||||
@Transient
|
||||
private boolean valid = true;
|
||||
|
||||
@Transient
|
||||
@Nullable
|
||||
private String errorMessage = null;
|
||||
|
||||
@Element
|
||||
@NotNull
|
||||
private EditorHistoryState editorState;
|
||||
|
||||
@Element
|
||||
@NotNull
|
||||
private JsclOperation jsclOperation;
|
||||
|
||||
@Transient
|
||||
@Nullable
|
||||
private Generic genericResult;
|
||||
|
||||
private CalculatorDisplayHistoryState() {
|
||||
// for xml
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static CalculatorDisplayHistoryState newInstance(@NotNull ICalculatorDisplay display) {
|
||||
final CalculatorDisplayHistoryState result = new CalculatorDisplayHistoryState();
|
||||
|
||||
result.editorState = EditorHistoryState.newInstance(display);
|
||||
result.valid = display.isValid();
|
||||
result.jsclOperation = display.getJsclOperation();
|
||||
result.genericResult = display.getGenericResult();
|
||||
result.errorMessage = display.getErrorMessage();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void setValuesFromHistory(@NotNull ICalculatorDisplay display) {
|
||||
this.getEditorState().setValuesFromHistory(display);
|
||||
display.setValid(this.isValid());
|
||||
display.setErrorMessage(this.getErrorMessage());
|
||||
display.setJsclOperation(this.getJsclOperation());
|
||||
display.setGenericResult(this.getGenericResult());
|
||||
}
|
||||
|
||||
|
||||
public boolean isValid() {
|
||||
return valid;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public EditorHistoryState getEditorState() {
|
||||
return editorState;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public JsclOperation getJsclOperation() {
|
||||
return jsclOperation;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Generic getGenericResult() {
|
||||
return genericResult;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
CalculatorDisplayHistoryState that = (CalculatorDisplayHistoryState) o;
|
||||
|
||||
if (!editorState.equals(that.editorState)) return false;
|
||||
if (jsclOperation != that.jsclOperation) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = editorState.hashCode();
|
||||
result = 31 * result + jsclOperation.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CalculatorDisplayHistoryState{" +
|
||||
"valid=" + valid +
|
||||
", errorMessage='" + errorMessage + '\'' +
|
||||
", editorHistoryState=" + editorState +
|
||||
", jsclOperation=" + jsclOperation +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CalculatorDisplayHistoryState clone() {
|
||||
try {
|
||||
final CalculatorDisplayHistoryState clone = (CalculatorDisplayHistoryState) super.clone();
|
||||
|
||||
clone.editorState = this.editorState.clone();
|
||||
|
||||
return clone;
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
||||
* For more information, please, contact se.solovyev@gmail.com
|
||||
*/
|
||||
|
||||
package org.solovyev.android.calculator.history;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.simpleframework.xml.Element;
|
||||
import org.simpleframework.xml.Root;
|
||||
import org.solovyev.android.calculator.Editor;
|
||||
import org.solovyev.android.calculator.ICalculatorDisplay;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 9/11/11
|
||||
* Time: 12:16 AM
|
||||
*/
|
||||
|
||||
@Root
|
||||
public class CalculatorHistoryState extends AbstractHistoryState {
|
||||
|
||||
@Element
|
||||
@NotNull
|
||||
private EditorHistoryState editorState;
|
||||
|
||||
@Element
|
||||
@NotNull
|
||||
private CalculatorDisplayHistoryState displayState;
|
||||
|
||||
private CalculatorHistoryState() {
|
||||
// for xml
|
||||
}
|
||||
|
||||
private CalculatorHistoryState(@NotNull EditorHistoryState editorState,
|
||||
@NotNull CalculatorDisplayHistoryState displayState) {
|
||||
this.editorState = editorState;
|
||||
this.displayState = displayState;
|
||||
}
|
||||
|
||||
public static CalculatorHistoryState newInstance(@NotNull Editor editor, @NotNull ICalculatorDisplay display) {
|
||||
final EditorHistoryState editorHistoryState = EditorHistoryState.newInstance(editor);
|
||||
final CalculatorDisplayHistoryState displayHistoryState = CalculatorDisplayHistoryState.newInstance(display);
|
||||
return new CalculatorHistoryState(editorHistoryState, displayHistoryState);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public EditorHistoryState getEditorState() {
|
||||
return editorState;
|
||||
}
|
||||
|
||||
public void setEditorState(@NotNull EditorHistoryState editorState) {
|
||||
this.editorState = editorState;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CalculatorDisplayHistoryState getDisplayState() {
|
||||
return displayState;
|
||||
}
|
||||
|
||||
public void setDisplayState(@NotNull CalculatorDisplayHistoryState displayState) {
|
||||
this.displayState = displayState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CalculatorHistoryState{" +
|
||||
"editorState=" + editorState +
|
||||
", displayState=" + displayState +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
CalculatorHistoryState that = (CalculatorHistoryState) o;
|
||||
|
||||
if (this.isSaved() != that.isSaved()) return false;
|
||||
if (this.getId() != that.getId()) return false;
|
||||
if (!displayState.equals(that.displayState)) return false;
|
||||
if (!editorState.equals(that.editorState)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = Boolean.valueOf(isSaved()).hashCode();
|
||||
result = 31 * result + getId();
|
||||
result = 31 * result + editorState.hashCode();
|
||||
result = 31 * result + displayState.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
public void setValuesFromHistory(@NotNull Editor editor, @NotNull ICalculatorDisplay display) {
|
||||
this.getEditorState().setValuesFromHistory(editor);
|
||||
this.getDisplayState().setValuesFromHistory(display);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CalculatorHistoryState clone() {
|
||||
final CalculatorHistoryState clone = (CalculatorHistoryState)super.clone();
|
||||
|
||||
clone.editorState = this.editorState.clone();
|
||||
clone.displayState = this.displayState.clone();
|
||||
|
||||
return clone;
|
||||
}
|
||||
}
|
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
||||
* For more information, please, contact se.solovyev@gmail.com
|
||||
*/
|
||||
|
||||
package org.solovyev.android.calculator.history;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.simpleframework.xml.Element;
|
||||
import org.simpleframework.xml.Root;
|
||||
import org.solovyev.android.calculator.Editor;
|
||||
|
||||
@Root
|
||||
public class EditorHistoryState implements Cloneable{
|
||||
|
||||
@Element
|
||||
private int cursorPosition;
|
||||
|
||||
@Element(required = false)
|
||||
@Nullable
|
||||
private String text;
|
||||
|
||||
private EditorHistoryState() {
|
||||
// for xml
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static EditorHistoryState newInstance(@NotNull Editor editor) {
|
||||
final EditorHistoryState result = new EditorHistoryState();
|
||||
|
||||
result.text = String.valueOf(editor.getText());
|
||||
result.cursorPosition = editor.getSelection();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void setValuesFromHistory(@NotNull Editor editor) {
|
||||
editor.setText(this.getText());
|
||||
editor.setSelection(this.getCursorPosition());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public int getCursorPosition() {
|
||||
return cursorPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof EditorHistoryState)) return false;
|
||||
|
||||
EditorHistoryState that = (EditorHistoryState) o;
|
||||
|
||||
if (cursorPosition != that.cursorPosition) return false;
|
||||
if (text != null ? !text.equals(that.text) : that.text != null) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = cursorPosition;
|
||||
result = 31 * result + (text != null ? text.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "EditorHistoryState{" +
|
||||
"cursorPosition=" + cursorPosition +
|
||||
", text='" + text + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EditorHistoryState clone() {
|
||||
try {
|
||||
return (EditorHistoryState)super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new UnsupportedOperationException(e);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,33 +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.history;
|
||||
|
||||
import org.simpleframework.xml.ElementList;
|
||||
import org.simpleframework.xml.Root;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/17/11
|
||||
* Time: 9:30 PM
|
||||
*/
|
||||
|
||||
@Root
|
||||
public class History {
|
||||
|
||||
@ElementList
|
||||
private List<CalculatorHistoryState> historyItems = new ArrayList<CalculatorHistoryState>();
|
||||
|
||||
public History() {
|
||||
}
|
||||
|
||||
public List<CalculatorHistoryState> getHistoryItems() {
|
||||
return historyItems;
|
||||
}
|
||||
}
|
@@ -1,61 +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.history;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.simpleframework.xml.Serializer;
|
||||
import org.simpleframework.xml.core.Persister;
|
||||
|
||||
import java.io.StringWriter;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/17/11
|
||||
* Time: 9:59 PM
|
||||
*/
|
||||
class HistoryUtils {
|
||||
|
||||
// not intended for instantiation
|
||||
private HistoryUtils() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
public static void fromXml(@Nullable String xml, @NotNull List<CalculatorHistoryState> historyItems) {
|
||||
if (xml != null) {
|
||||
final Serializer serializer = new Persister();
|
||||
try {
|
||||
final History history = serializer.read(History.class, xml);
|
||||
for (CalculatorHistoryState historyItem : history.getHistoryItems()) {
|
||||
historyItems.add(historyItem);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String toXml(@NotNull List<CalculatorHistoryState> historyItems) {
|
||||
final History history = new History();
|
||||
for (CalculatorHistoryState historyState : historyItems) {
|
||||
if (historyState.isSaved()) {
|
||||
history.getHistoryItems().add(historyState);
|
||||
}
|
||||
}
|
||||
|
||||
final StringWriter xml = new StringWriter();
|
||||
final Serializer serializer = new Persister();
|
||||
try {
|
||||
serializer.write(history, xml);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return xml.toString();
|
||||
}
|
||||
}
|
@@ -16,9 +16,11 @@ import jscl.text.ParseInterruptedException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.calculator.CalculatorApplication;
|
||||
import org.solovyev.android.calculator.CalculatorParseException;
|
||||
import org.solovyev.android.calculator.JCalculatorEngine;
|
||||
import org.solovyev.android.calculator.R;
|
||||
import org.solovyev.android.calculator.jscl.JsclOperation;
|
||||
import org.solovyev.android.calculator.text.TextProcessor;
|
||||
import org.solovyev.android.msg.AndroidMessage;
|
||||
import org.solovyev.android.prefs.BooleanPreference;
|
||||
import org.solovyev.android.prefs.Preference;
|
||||
|
@@ -9,7 +9,9 @@ package org.solovyev.android.calculator.model;
|
||||
import jscl.math.function.IConstant;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.solovyev.android.calculator.CalculatorApplication;
|
||||
import org.solovyev.android.calculator.CalculatorParseException;
|
||||
import org.solovyev.android.calculator.R;
|
||||
import org.solovyev.android.calculator.text.TextProcessor;
|
||||
import org.solovyev.android.msg.AndroidMessage;
|
||||
import org.solovyev.common.StartsWithFinder;
|
||||
import org.solovyev.android.calculator.math.MathType;
|
||||
|
@@ -1,360 +1,360 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
||||
* For more information, please, contact se.solovyev@gmail.com
|
||||
* or visit http://se.solovyev.org
|
||||
*/
|
||||
|
||||
package org.solovyev.android.calculator.plot;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.widget.Toast;
|
||||
import jscl.math.Expression;
|
||||
import jscl.math.Generic;
|
||||
import jscl.math.function.Constant;
|
||||
import jscl.text.ParseException;
|
||||
import org.achartengine.ChartFactory;
|
||||
import org.achartengine.GraphicalView;
|
||||
import org.achartengine.chart.CubicLineChart;
|
||||
import org.achartengine.chart.PointStyle;
|
||||
import org.achartengine.chart.XYChart;
|
||||
import org.achartengine.model.XYMultipleSeriesDataset;
|
||||
import org.achartengine.model.XYSeries;
|
||||
import org.achartengine.renderer.BasicStroke;
|
||||
import org.achartengine.renderer.XYMultipleSeriesRenderer;
|
||||
import org.achartengine.renderer.XYSeriesRenderer;
|
||||
import org.achartengine.tools.PanListener;
|
||||
import org.achartengine.tools.ZoomEvent;
|
||||
import org.achartengine.tools.ZoomListener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.calculator.R;
|
||||
import org.solovyev.android.calculator.model.CalculatorParseException;
|
||||
import org.solovyev.android.calculator.model.PreparedExpression;
|
||||
import org.solovyev.android.calculator.model.ToJsclTextProcessor;
|
||||
import org.solovyev.common.MutableObject;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/1/11
|
||||
* Time: 12:40 AM
|
||||
*/
|
||||
public class CalculatorPlotActivity extends Activity {
|
||||
|
||||
private static final String TAG = CalculatorPlotActivity.class.getSimpleName();
|
||||
|
||||
private static final int DEFAULT_NUMBER_OF_STEPS = 100;
|
||||
|
||||
private static final int DEFAULT_MIN_NUMBER = -10;
|
||||
|
||||
private static final int DEFAULT_MAX_NUMBER = 10;
|
||||
|
||||
public static final String INPUT = "org.solovyev.android.calculator.CalculatorPlotActivity_input";
|
||||
|
||||
public static final long EVAL_DELAY_MILLIS = 200;
|
||||
|
||||
private XYChart chart;
|
||||
|
||||
/**
|
||||
* The encapsulated graphical view.
|
||||
*/
|
||||
private GraphicalView graphicalView;
|
||||
|
||||
@NotNull
|
||||
private Generic expression;
|
||||
|
||||
@NotNull
|
||||
private Constant variable;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Bundle extras = getIntent().getExtras();
|
||||
|
||||
final Input input = (Input) extras.getSerializable(INPUT);
|
||||
|
||||
try {
|
||||
final PreparedExpression preparedExpression = ToJsclTextProcessor.getInstance().process(input.getExpression());
|
||||
this.expression = Expression.valueOf(preparedExpression.getExpression());
|
||||
this.variable = new Constant(input.getVariableName());
|
||||
|
||||
String title = extras.getString(ChartFactory.TITLE);
|
||||
if (title == null) {
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
} else if (title.length() > 0) {
|
||||
setTitle(title);
|
||||
}
|
||||
|
||||
setContentView(R.layout.calc_plot_view);
|
||||
|
||||
final Object lastNonConfigurationInstance = getLastNonConfigurationInstance();
|
||||
setGraphicalView(lastNonConfigurationInstance instanceof PlotBoundaries ? (PlotBoundaries)lastNonConfigurationInstance : null);
|
||||
|
||||
} catch (ParseException e) {
|
||||
Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
} catch (ArithmeticException e) {
|
||||
Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
} catch (CalculatorParseException e) {
|
||||
Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
private void setGraphicalView(@Nullable PlotBoundaries plotBoundaries) {
|
||||
double minValue = plotBoundaries == null ? DEFAULT_MIN_NUMBER : plotBoundaries.xMin;
|
||||
double maxValue = plotBoundaries == null ? DEFAULT_MAX_NUMBER : plotBoundaries.xMax;
|
||||
|
||||
final ViewGroup graphContainer = (ViewGroup) findViewById(R.id.plot_view_container);
|
||||
|
||||
if (graphicalView != null) {
|
||||
graphContainer.removeView(graphicalView);
|
||||
}
|
||||
|
||||
chart = prepareChart(minValue, maxValue, expression, variable);
|
||||
|
||||
// reverting boundaries (as in prepareChart() we add some cached values )
|
||||
double minX = Double.MAX_VALUE;
|
||||
double minY = Double.MAX_VALUE;
|
||||
|
||||
double maxX = Double.MIN_VALUE;
|
||||
double maxY = Double.MIN_VALUE;
|
||||
|
||||
for (XYSeries series : chart.getDataset().getSeries()) {
|
||||
minX = Math.min(minX, series.getMinX());
|
||||
minY = Math.min(minY, series.getMinY());
|
||||
maxX = Math.max(maxX, series.getMaxX());
|
||||
maxY = Math.max(maxY, series.getMaxY());
|
||||
}
|
||||
|
||||
Log.d(CalculatorPlotActivity.class.getName(), "min x: " + minX + ", min y: " + minY + ", max x: " + maxX + ", max y: " + maxY);
|
||||
Log.d(CalculatorPlotActivity.class.getName(), "Plot boundaries are " + plotBoundaries);
|
||||
|
||||
|
||||
if (plotBoundaries == null) {
|
||||
chart.getRenderer().setXAxisMin(Math.max(minX, minValue));
|
||||
chart.getRenderer().setYAxisMin(Math.max(minY, minValue));
|
||||
chart.getRenderer().setXAxisMax(Math.min(maxX, maxValue));
|
||||
chart.getRenderer().setYAxisMax(Math.min(maxY, maxValue));
|
||||
} else {
|
||||
chart.getRenderer().setXAxisMin(plotBoundaries.xMin);
|
||||
chart.getRenderer().setYAxisMin(plotBoundaries.yMin);
|
||||
chart.getRenderer().setXAxisMax(plotBoundaries.xMax);
|
||||
chart.getRenderer().setYAxisMax(plotBoundaries.yMax);
|
||||
}
|
||||
|
||||
graphicalView = new GraphicalView(this, chart);
|
||||
|
||||
graphicalView.addZoomListener(new ZoomListener() {
|
||||
@Override
|
||||
public void zoomApplied(ZoomEvent e) {
|
||||
updateDataSets(chart);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void zoomReset() {
|
||||
updateDataSets(chart);
|
||||
}
|
||||
}, true, true);
|
||||
|
||||
graphicalView.addPanListener(new PanListener() {
|
||||
@Override
|
||||
public void panApplied() {
|
||||
Log.d(TAG, "org.achartengine.tools.PanListener.panApplied");
|
||||
updateDataSets(chart);
|
||||
}
|
||||
|
||||
});
|
||||
graphContainer.addView(graphicalView);
|
||||
|
||||
updateDataSets(chart, 50);
|
||||
}
|
||||
|
||||
|
||||
private void updateDataSets(@NotNull final XYChart chart) {
|
||||
updateDataSets(chart, EVAL_DELAY_MILLIS);
|
||||
}
|
||||
|
||||
private void updateDataSets(@NotNull final XYChart chart, long millisToWait) {
|
||||
pendingOperation.setObject(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// allow only one runner at one time
|
||||
synchronized (pendingOperation) {
|
||||
//lock all operations with history
|
||||
if (pendingOperation.getObject() == this) {
|
||||
|
||||
Log.d(TAG, "org.solovyev.android.calculator.plot.CalculatorPlotActivity.updateDataSets");
|
||||
|
||||
final XYMultipleSeriesRenderer dr = chart.getRenderer();
|
||||
|
||||
//Log.d(CalculatorPlotActivity.class.getName(), "x = [" + dr.getXAxisMin() + ", " + dr.getXAxisMax() + "], y = [" + dr.getYAxisMin() + ", " + dr.getYAxisMax() + "]");
|
||||
|
||||
final MyXYSeries realSeries = (MyXYSeries)chart.getDataset().getSeriesAt(0);
|
||||
|
||||
final MyXYSeries imagSeries;
|
||||
if (chart.getDataset().getSeriesCount() > 1) {
|
||||
imagSeries = (MyXYSeries)chart.getDataset().getSeriesAt(1);
|
||||
} else {
|
||||
imagSeries = new MyXYSeries(getImagFunctionName(CalculatorPlotActivity.this.variable), DEFAULT_NUMBER_OF_STEPS * 2);
|
||||
}
|
||||
|
||||
try {
|
||||
if (PlotUtils.addXY(dr.getXAxisMin(), dr.getXAxisMax(), expression, variable, realSeries, imagSeries, true, DEFAULT_NUMBER_OF_STEPS)) {
|
||||
if (chart.getDataset().getSeriesCount() <= 1) {
|
||||
chart.getDataset().addSeries(imagSeries);
|
||||
chart.getRenderer().addSeriesRenderer(createImagRenderer());
|
||||
}
|
||||
}
|
||||
} catch (ArithmeticException e) {
|
||||
// todo serso: translate
|
||||
Toast.makeText(CalculatorPlotActivity.this, "Arithmetic error: " + e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
|
||||
CalculatorPlotActivity.this.finish();
|
||||
}
|
||||
|
||||
if (pendingOperation.getObject() == this) {
|
||||
graphicalView.repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
new Handler().postDelayed(pendingOperation.getObject(), millisToWait);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String getImagFunctionName(@NotNull Constant variable) {
|
||||
return "g(" + variable.getName() +")" + " = " + "Im(ƒ(" + variable.getName() +"))";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String getRealFunctionName(@NotNull Generic expression, @NotNull Constant variable) {
|
||||
return "ƒ(" + variable.getName() +")" + " = " + expression.toString();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private final static MutableObject<Runnable> pendingOperation = new MutableObject<Runnable>();
|
||||
|
||||
private static XYChart prepareChart(final double minValue, final double maxValue, @NotNull final Generic expression, @NotNull final Constant variable) {
|
||||
final MyXYSeries realSeries = new MyXYSeries(getRealFunctionName(expression, variable), DEFAULT_NUMBER_OF_STEPS * 2);
|
||||
final MyXYSeries imagSeries = new MyXYSeries(getImagFunctionName(variable), DEFAULT_NUMBER_OF_STEPS * 2);
|
||||
|
||||
boolean imagExists = PlotUtils.addXY(minValue, maxValue, expression, variable, realSeries, imagSeries, false, DEFAULT_NUMBER_OF_STEPS);
|
||||
|
||||
final XYMultipleSeriesDataset data = new XYMultipleSeriesDataset();
|
||||
data.addSeries(realSeries);
|
||||
if (imagExists) {
|
||||
data.addSeries(imagSeries);
|
||||
}
|
||||
|
||||
final XYMultipleSeriesRenderer renderer = new XYMultipleSeriesRenderer();
|
||||
renderer.setShowGrid(true);
|
||||
renderer.setXTitle(variable.getName());
|
||||
renderer.setYTitle("f(" + variable.getName() + ")");
|
||||
renderer.setChartTitleTextSize(20);
|
||||
|
||||
renderer.setZoomEnabled(true);
|
||||
renderer.setZoomButtonsVisible(true);
|
||||
|
||||
renderer.addSeriesRenderer(createCommonRenderer());
|
||||
if (imagExists) {
|
||||
renderer.addSeriesRenderer(createImagRenderer());
|
||||
}
|
||||
|
||||
return new CubicLineChart(data, renderer, 0.1f);
|
||||
}
|
||||
|
||||
private static XYSeriesRenderer createImagRenderer() {
|
||||
final XYSeriesRenderer imagRenderer = createCommonRenderer();
|
||||
imagRenderer.setStroke(BasicStroke.DASHED);
|
||||
imagRenderer.setColor(Color.LTGRAY);
|
||||
return imagRenderer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object onRetainNonConfigurationInstance() {
|
||||
return new PlotBoundaries(chart.getRenderer());
|
||||
}
|
||||
|
||||
private static final class PlotBoundaries implements Serializable {
|
||||
|
||||
private final double xMin;
|
||||
private final double xMax;
|
||||
private final double yMin;
|
||||
private final double yMax;
|
||||
|
||||
public PlotBoundaries(@NotNull XYMultipleSeriesRenderer renderer) {
|
||||
this.xMin = renderer.getXAxisMin();
|
||||
this.yMin = renderer.getYAxisMin();
|
||||
this.xMax = renderer.getXAxisMax();
|
||||
this.yMax = renderer.getYAxisMax();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PlotBoundaries{" +
|
||||
"yMax=" + yMax +
|
||||
", yMin=" + yMin +
|
||||
", xMax=" + xMax +
|
||||
", xMin=" + xMin +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
private static XYSeriesRenderer createCommonRenderer() {
|
||||
final XYSeriesRenderer renderer = new XYSeriesRenderer();
|
||||
renderer.setFillPoints(true);
|
||||
renderer.setPointStyle(PointStyle.POINT);
|
||||
renderer.setLineWidth(3);
|
||||
renderer.setColor(Color.WHITE);
|
||||
renderer.setStroke(BasicStroke.SOLID);
|
||||
return renderer;
|
||||
}
|
||||
|
||||
public void zoomInClickHandler(@NotNull View v) {
|
||||
this.graphicalView.zoomIn();
|
||||
}
|
||||
|
||||
public void zoomOutClickHandler(@NotNull View v) {
|
||||
this.graphicalView.zoomOut();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static class Input implements Serializable {
|
||||
|
||||
@NotNull
|
||||
private String expression;
|
||||
|
||||
@NotNull
|
||||
private String variableName;
|
||||
|
||||
public Input(@NotNull String expression, @NotNull String variableName) {
|
||||
this.expression = expression;
|
||||
this.variableName = variableName;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getExpression() {
|
||||
return expression;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getVariableName() {
|
||||
return variableName;
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
||||
* For more information, please, contact se.solovyev@gmail.com
|
||||
* or visit http://se.solovyev.org
|
||||
*/
|
||||
|
||||
package org.solovyev.android.calculator.plot;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.widget.Toast;
|
||||
import jscl.math.Expression;
|
||||
import jscl.math.Generic;
|
||||
import jscl.math.function.Constant;
|
||||
import jscl.text.ParseException;
|
||||
import org.achartengine.ChartFactory;
|
||||
import org.achartengine.GraphicalView;
|
||||
import org.achartengine.chart.CubicLineChart;
|
||||
import org.achartengine.chart.PointStyle;
|
||||
import org.achartengine.chart.XYChart;
|
||||
import org.achartengine.model.XYMultipleSeriesDataset;
|
||||
import org.achartengine.model.XYSeries;
|
||||
import org.achartengine.renderer.BasicStroke;
|
||||
import org.achartengine.renderer.XYMultipleSeriesRenderer;
|
||||
import org.achartengine.renderer.XYSeriesRenderer;
|
||||
import org.achartengine.tools.PanListener;
|
||||
import org.achartengine.tools.ZoomEvent;
|
||||
import org.achartengine.tools.ZoomListener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.calculator.R;
|
||||
import org.solovyev.android.calculator.CalculatorParseException;
|
||||
import org.solovyev.android.calculator.model.PreparedExpression;
|
||||
import org.solovyev.android.calculator.model.ToJsclTextProcessor;
|
||||
import org.solovyev.common.MutableObject;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 12/1/11
|
||||
* Time: 12:40 AM
|
||||
*/
|
||||
public class CalculatorPlotActivity extends Activity {
|
||||
|
||||
private static final String TAG = CalculatorPlotActivity.class.getSimpleName();
|
||||
|
||||
private static final int DEFAULT_NUMBER_OF_STEPS = 100;
|
||||
|
||||
private static final int DEFAULT_MIN_NUMBER = -10;
|
||||
|
||||
private static final int DEFAULT_MAX_NUMBER = 10;
|
||||
|
||||
public static final String INPUT = "org.solovyev.android.calculator.CalculatorPlotActivity_input";
|
||||
|
||||
public static final long EVAL_DELAY_MILLIS = 200;
|
||||
|
||||
private XYChart chart;
|
||||
|
||||
/**
|
||||
* The encapsulated graphical view.
|
||||
*/
|
||||
private GraphicalView graphicalView;
|
||||
|
||||
@NotNull
|
||||
private Generic expression;
|
||||
|
||||
@NotNull
|
||||
private Constant variable;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Bundle extras = getIntent().getExtras();
|
||||
|
||||
final Input input = (Input) extras.getSerializable(INPUT);
|
||||
|
||||
try {
|
||||
final PreparedExpression preparedExpression = ToJsclTextProcessor.getInstance().process(input.getExpression());
|
||||
this.expression = Expression.valueOf(preparedExpression.getExpression());
|
||||
this.variable = new Constant(input.getVariableName());
|
||||
|
||||
String title = extras.getString(ChartFactory.TITLE);
|
||||
if (title == null) {
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
} else if (title.length() > 0) {
|
||||
setTitle(title);
|
||||
}
|
||||
|
||||
setContentView(R.layout.calc_plot_view);
|
||||
|
||||
final Object lastNonConfigurationInstance = getLastNonConfigurationInstance();
|
||||
setGraphicalView(lastNonConfigurationInstance instanceof PlotBoundaries ? (PlotBoundaries)lastNonConfigurationInstance : null);
|
||||
|
||||
} catch (ParseException e) {
|
||||
Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
} catch (ArithmeticException e) {
|
||||
Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
} catch (CalculatorParseException e) {
|
||||
Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
private void setGraphicalView(@Nullable PlotBoundaries plotBoundaries) {
|
||||
double minValue = plotBoundaries == null ? DEFAULT_MIN_NUMBER : plotBoundaries.xMin;
|
||||
double maxValue = plotBoundaries == null ? DEFAULT_MAX_NUMBER : plotBoundaries.xMax;
|
||||
|
||||
final ViewGroup graphContainer = (ViewGroup) findViewById(R.id.plot_view_container);
|
||||
|
||||
if (graphicalView != null) {
|
||||
graphContainer.removeView(graphicalView);
|
||||
}
|
||||
|
||||
chart = prepareChart(minValue, maxValue, expression, variable);
|
||||
|
||||
// reverting boundaries (as in prepareChart() we add some cached values )
|
||||
double minX = Double.MAX_VALUE;
|
||||
double minY = Double.MAX_VALUE;
|
||||
|
||||
double maxX = Double.MIN_VALUE;
|
||||
double maxY = Double.MIN_VALUE;
|
||||
|
||||
for (XYSeries series : chart.getDataset().getSeries()) {
|
||||
minX = Math.min(minX, series.getMinX());
|
||||
minY = Math.min(minY, series.getMinY());
|
||||
maxX = Math.max(maxX, series.getMaxX());
|
||||
maxY = Math.max(maxY, series.getMaxY());
|
||||
}
|
||||
|
||||
Log.d(CalculatorPlotActivity.class.getName(), "min x: " + minX + ", min y: " + minY + ", max x: " + maxX + ", max y: " + maxY);
|
||||
Log.d(CalculatorPlotActivity.class.getName(), "Plot boundaries are " + plotBoundaries);
|
||||
|
||||
|
||||
if (plotBoundaries == null) {
|
||||
chart.getRenderer().setXAxisMin(Math.max(minX, minValue));
|
||||
chart.getRenderer().setYAxisMin(Math.max(minY, minValue));
|
||||
chart.getRenderer().setXAxisMax(Math.min(maxX, maxValue));
|
||||
chart.getRenderer().setYAxisMax(Math.min(maxY, maxValue));
|
||||
} else {
|
||||
chart.getRenderer().setXAxisMin(plotBoundaries.xMin);
|
||||
chart.getRenderer().setYAxisMin(plotBoundaries.yMin);
|
||||
chart.getRenderer().setXAxisMax(plotBoundaries.xMax);
|
||||
chart.getRenderer().setYAxisMax(plotBoundaries.yMax);
|
||||
}
|
||||
|
||||
graphicalView = new GraphicalView(this, chart);
|
||||
|
||||
graphicalView.addZoomListener(new ZoomListener() {
|
||||
@Override
|
||||
public void zoomApplied(ZoomEvent e) {
|
||||
updateDataSets(chart);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void zoomReset() {
|
||||
updateDataSets(chart);
|
||||
}
|
||||
}, true, true);
|
||||
|
||||
graphicalView.addPanListener(new PanListener() {
|
||||
@Override
|
||||
public void panApplied() {
|
||||
Log.d(TAG, "org.achartengine.tools.PanListener.panApplied");
|
||||
updateDataSets(chart);
|
||||
}
|
||||
|
||||
});
|
||||
graphContainer.addView(graphicalView);
|
||||
|
||||
updateDataSets(chart, 50);
|
||||
}
|
||||
|
||||
|
||||
private void updateDataSets(@NotNull final XYChart chart) {
|
||||
updateDataSets(chart, EVAL_DELAY_MILLIS);
|
||||
}
|
||||
|
||||
private void updateDataSets(@NotNull final XYChart chart, long millisToWait) {
|
||||
pendingOperation.setObject(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// allow only one runner at one time
|
||||
synchronized (pendingOperation) {
|
||||
//lock all operations with history
|
||||
if (pendingOperation.getObject() == this) {
|
||||
|
||||
Log.d(TAG, "org.solovyev.android.calculator.plot.CalculatorPlotActivity.updateDataSets");
|
||||
|
||||
final XYMultipleSeriesRenderer dr = chart.getRenderer();
|
||||
|
||||
//Log.d(CalculatorPlotActivity.class.getName(), "x = [" + dr.getXAxisMin() + ", " + dr.getXAxisMax() + "], y = [" + dr.getYAxisMin() + ", " + dr.getYAxisMax() + "]");
|
||||
|
||||
final MyXYSeries realSeries = (MyXYSeries)chart.getDataset().getSeriesAt(0);
|
||||
|
||||
final MyXYSeries imagSeries;
|
||||
if (chart.getDataset().getSeriesCount() > 1) {
|
||||
imagSeries = (MyXYSeries)chart.getDataset().getSeriesAt(1);
|
||||
} else {
|
||||
imagSeries = new MyXYSeries(getImagFunctionName(CalculatorPlotActivity.this.variable), DEFAULT_NUMBER_OF_STEPS * 2);
|
||||
}
|
||||
|
||||
try {
|
||||
if (PlotUtils.addXY(dr.getXAxisMin(), dr.getXAxisMax(), expression, variable, realSeries, imagSeries, true, DEFAULT_NUMBER_OF_STEPS)) {
|
||||
if (chart.getDataset().getSeriesCount() <= 1) {
|
||||
chart.getDataset().addSeries(imagSeries);
|
||||
chart.getRenderer().addSeriesRenderer(createImagRenderer());
|
||||
}
|
||||
}
|
||||
} catch (ArithmeticException e) {
|
||||
// todo serso: translate
|
||||
Toast.makeText(CalculatorPlotActivity.this, "Arithmetic error: " + e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
|
||||
CalculatorPlotActivity.this.finish();
|
||||
}
|
||||
|
||||
if (pendingOperation.getObject() == this) {
|
||||
graphicalView.repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
new Handler().postDelayed(pendingOperation.getObject(), millisToWait);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String getImagFunctionName(@NotNull Constant variable) {
|
||||
return "g(" + variable.getName() +")" + " = " + "Im(ƒ(" + variable.getName() +"))";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String getRealFunctionName(@NotNull Generic expression, @NotNull Constant variable) {
|
||||
return "ƒ(" + variable.getName() +")" + " = " + expression.toString();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private final static MutableObject<Runnable> pendingOperation = new MutableObject<Runnable>();
|
||||
|
||||
private static XYChart prepareChart(final double minValue, final double maxValue, @NotNull final Generic expression, @NotNull final Constant variable) {
|
||||
final MyXYSeries realSeries = new MyXYSeries(getRealFunctionName(expression, variable), DEFAULT_NUMBER_OF_STEPS * 2);
|
||||
final MyXYSeries imagSeries = new MyXYSeries(getImagFunctionName(variable), DEFAULT_NUMBER_OF_STEPS * 2);
|
||||
|
||||
boolean imagExists = PlotUtils.addXY(minValue, maxValue, expression, variable, realSeries, imagSeries, false, DEFAULT_NUMBER_OF_STEPS);
|
||||
|
||||
final XYMultipleSeriesDataset data = new XYMultipleSeriesDataset();
|
||||
data.addSeries(realSeries);
|
||||
if (imagExists) {
|
||||
data.addSeries(imagSeries);
|
||||
}
|
||||
|
||||
final XYMultipleSeriesRenderer renderer = new XYMultipleSeriesRenderer();
|
||||
renderer.setShowGrid(true);
|
||||
renderer.setXTitle(variable.getName());
|
||||
renderer.setYTitle("f(" + variable.getName() + ")");
|
||||
renderer.setChartTitleTextSize(20);
|
||||
|
||||
renderer.setZoomEnabled(true);
|
||||
renderer.setZoomButtonsVisible(true);
|
||||
|
||||
renderer.addSeriesRenderer(createCommonRenderer());
|
||||
if (imagExists) {
|
||||
renderer.addSeriesRenderer(createImagRenderer());
|
||||
}
|
||||
|
||||
return new CubicLineChart(data, renderer, 0.1f);
|
||||
}
|
||||
|
||||
private static XYSeriesRenderer createImagRenderer() {
|
||||
final XYSeriesRenderer imagRenderer = createCommonRenderer();
|
||||
imagRenderer.setStroke(BasicStroke.DASHED);
|
||||
imagRenderer.setColor(Color.LTGRAY);
|
||||
return imagRenderer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object onRetainNonConfigurationInstance() {
|
||||
return new PlotBoundaries(chart.getRenderer());
|
||||
}
|
||||
|
||||
private static final class PlotBoundaries implements Serializable {
|
||||
|
||||
private final double xMin;
|
||||
private final double xMax;
|
||||
private final double yMin;
|
||||
private final double yMax;
|
||||
|
||||
public PlotBoundaries(@NotNull XYMultipleSeriesRenderer renderer) {
|
||||
this.xMin = renderer.getXAxisMin();
|
||||
this.yMin = renderer.getYAxisMin();
|
||||
this.xMax = renderer.getXAxisMax();
|
||||
this.yMax = renderer.getYAxisMax();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PlotBoundaries{" +
|
||||
"yMax=" + yMax +
|
||||
", yMin=" + yMin +
|
||||
", xMax=" + xMax +
|
||||
", xMin=" + xMin +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
private static XYSeriesRenderer createCommonRenderer() {
|
||||
final XYSeriesRenderer renderer = new XYSeriesRenderer();
|
||||
renderer.setFillPoints(true);
|
||||
renderer.setPointStyle(PointStyle.POINT);
|
||||
renderer.setLineWidth(3);
|
||||
renderer.setColor(Color.WHITE);
|
||||
renderer.setStroke(BasicStroke.SOLID);
|
||||
return renderer;
|
||||
}
|
||||
|
||||
public void zoomInClickHandler(@NotNull View v) {
|
||||
this.graphicalView.zoomIn();
|
||||
}
|
||||
|
||||
public void zoomOutClickHandler(@NotNull View v) {
|
||||
this.graphicalView.zoomOut();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static class Input implements Serializable {
|
||||
|
||||
@NotNull
|
||||
private String expression;
|
||||
|
||||
@NotNull
|
||||
private String variableName;
|
||||
|
||||
public Input(@NotNull String expression, @NotNull String variableName) {
|
||||
this.expression = expression;
|
||||
this.variableName = variableName;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getExpression() {
|
||||
return expression;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getVariableName() {
|
||||
return variableName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -12,7 +12,7 @@ import org.solovyev.android.calculator.AndroidNumeralBase;
|
||||
import org.solovyev.android.calculator.CalculatorModel;
|
||||
import org.solovyev.android.calculator.R;
|
||||
import org.solovyev.android.calculator.model.CalculatorEngine;
|
||||
import org.solovyev.android.calculator.model.CalculatorParseException;
|
||||
import org.solovyev.android.calculator.CalculatorParseException;
|
||||
import org.solovyev.android.calculator.model.ToJsclTextProcessor;
|
||||
import org.solovyev.common.MutableObject;
|
||||
import org.solovyev.common.text.StringUtils;
|
||||
|
@@ -1,238 +1,240 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
||||
* For more information, please, contact se.solovyev@gmail.com
|
||||
* or visit http://se.solovyev.org
|
||||
*/
|
||||
|
||||
package org.solovyev.android.calculator.view;
|
||||
|
||||
import jscl.MathContext;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.calculator.math.MathType;
|
||||
import org.solovyev.android.calculator.model.*;
|
||||
import org.solovyev.common.MutableObject;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 10/12/11
|
||||
* Time: 9:47 PM
|
||||
*/
|
||||
public class TextHighlighter implements TextProcessor<TextHighlighter.Result, String> {
|
||||
|
||||
private static final Map<String, String> nbFontAttributes = new HashMap<String, String>();
|
||||
|
||||
static {
|
||||
nbFontAttributes.put("color", "#008000");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public final MathContext mathContext;
|
||||
|
||||
public static class Result implements CharSequence {
|
||||
|
||||
@NotNull
|
||||
private final String string;
|
||||
|
||||
private final int offset;
|
||||
|
||||
public Result(@NotNull String string, int offset) {
|
||||
this.string = string;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int length() {
|
||||
return string.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public char charAt(int i) {
|
||||
return string.charAt(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence subSequence(int i, int i1) {
|
||||
return string.subSequence(i, i1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return string;
|
||||
}
|
||||
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
}
|
||||
|
||||
private final int color;
|
||||
private final int colorRed;
|
||||
private final int colorGreen;
|
||||
private final int colorBlue;
|
||||
private final boolean formatNumber;
|
||||
|
||||
public TextHighlighter(int baseColor, boolean formatNumber, @NotNull MathContext mathContext) {
|
||||
this.color = baseColor;
|
||||
this.formatNumber = formatNumber;
|
||||
this.mathContext = mathContext;
|
||||
//this.colorRed = Color.red(baseColor);
|
||||
this.colorRed = (baseColor >> 16) & 0xFF;
|
||||
//this.colorGreen = Color.green(baseColor);
|
||||
this.colorGreen = (color >> 8) & 0xFF;
|
||||
//this.colorBlue = Color.blue(baseColor);
|
||||
this.colorBlue = color & 0xFF;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Result process(@NotNull String text) throws CalculatorParseException {
|
||||
final String result;
|
||||
|
||||
int maxNumberOfOpenGroupSymbols = 0;
|
||||
int numberOfOpenGroupSymbols = 0;
|
||||
|
||||
final StringBuilder text1 = new StringBuilder();
|
||||
|
||||
int resultOffset = 0;
|
||||
|
||||
final AbstractNumberBuilder numberBuilder;
|
||||
if (!formatNumber) {
|
||||
numberBuilder = new LiteNumberBuilder(CalculatorEngine.instance.getEngine());
|
||||
} else {
|
||||
numberBuilder = new NumberBuilder(CalculatorEngine.instance.getEngine());
|
||||
}
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
MathType.Result mathType = MathType.getType(text, i, numberBuilder.isHexMode());
|
||||
|
||||
if (numberBuilder instanceof NumberBuilder) {
|
||||
final MutableObject<Integer> numberOffset = new MutableObject<Integer>(0);
|
||||
((NumberBuilder) numberBuilder).process(text1, mathType, numberOffset);
|
||||
resultOffset += numberOffset.getObject();
|
||||
} else {
|
||||
((LiteNumberBuilder) numberBuilder).process(mathType);
|
||||
}
|
||||
|
||||
final String match = mathType.getMatch();
|
||||
switch (mathType.getMathType()) {
|
||||
case open_group_symbol:
|
||||
numberOfOpenGroupSymbols++;
|
||||
maxNumberOfOpenGroupSymbols = Math.max(maxNumberOfOpenGroupSymbols, numberOfOpenGroupSymbols);
|
||||
text1.append(text.charAt(i));
|
||||
break;
|
||||
case close_group_symbol:
|
||||
numberOfOpenGroupSymbols--;
|
||||
text1.append(text.charAt(i));
|
||||
break;
|
||||
case operator:
|
||||
text1.append(match);
|
||||
if (match.length() > 1) {
|
||||
i += match.length() - 1;
|
||||
}
|
||||
break;
|
||||
case function:
|
||||
i = processHighlightedText(text1, i, match, "i", null);
|
||||
break;
|
||||
case constant:
|
||||
i = processHighlightedText(text1, i, match, "b", null);
|
||||
break;
|
||||
case numeral_base:
|
||||
i = processHighlightedText(text1, i, match, "b", null);
|
||||
break;
|
||||
default:
|
||||
if (mathType.getMathType() == MathType.text || match.length() <= 1) {
|
||||
text1.append(text.charAt(i));
|
||||
} else {
|
||||
text1.append(match);
|
||||
i += match.length() - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (numberBuilder instanceof NumberBuilder) {
|
||||
final MutableObject<Integer> numberOffset = new MutableObject<Integer>(0);
|
||||
((NumberBuilder) numberBuilder).processNumber(text1, numberOffset);
|
||||
resultOffset += numberOffset.getObject();
|
||||
}
|
||||
|
||||
if (maxNumberOfOpenGroupSymbols > 0) {
|
||||
|
||||
final StringBuilder text2 = new StringBuilder();
|
||||
|
||||
String s = text1.toString();
|
||||
int i = processBracketGroup(text2, s, 0, 0, maxNumberOfOpenGroupSymbols);
|
||||
for (; i < s.length(); i++) {
|
||||
text2.append(s.charAt(i));
|
||||
}
|
||||
|
||||
//Log.d(CalculatorEditor.class.getName(), text2.toString());
|
||||
|
||||
result = text2.toString();
|
||||
} else {
|
||||
result = text1.toString();
|
||||
}
|
||||
|
||||
return new Result(result, resultOffset);
|
||||
}
|
||||
|
||||
private int processHighlightedText(@NotNull StringBuilder result, int i, @NotNull String match, @NotNull String tag, @Nullable Map<String, String> tagAttributes) {
|
||||
result.append("<").append(tag);
|
||||
|
||||
if (tagAttributes != null) {
|
||||
for (Map.Entry<String, String> entry : tagAttributes.entrySet()) {
|
||||
// attr1="attr1_value" attr2="attr2_value"
|
||||
result.append(" ").append(entry.getKey()).append("=\"").append(entry.getValue()).append("\"");
|
||||
}
|
||||
}
|
||||
|
||||
result.append(">").append(match).append("</").append(tag).append(">");
|
||||
if (match.length() > 1) {
|
||||
return i + match.length() - 1;
|
||||
} else {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
private int processBracketGroup(@NotNull StringBuilder result, @NotNull String s, int i, int numberOfOpenings, int maxNumberOfGroups) {
|
||||
|
||||
result.append("<font color=\"").append(getColor(maxNumberOfGroups, numberOfOpenings)).append("\">");
|
||||
|
||||
for (; i < s.length(); i++) {
|
||||
char ch = s.charAt(i);
|
||||
|
||||
if (MathType.open_group_symbol.getTokens().contains(String.valueOf(ch))) {
|
||||
result.append(ch);
|
||||
result.append("</font>");
|
||||
i = processBracketGroup(result, s, i + 1, numberOfOpenings + 1, maxNumberOfGroups);
|
||||
result.append("<font color=\"").append(getColor(maxNumberOfGroups, numberOfOpenings)).append("\">");
|
||||
if (i < s.length() && MathType.close_group_symbol.getTokens().contains(String.valueOf(s.charAt(i)))) {
|
||||
result.append(s.charAt(i));
|
||||
}
|
||||
} else if (MathType.close_group_symbol.getTokens().contains(String.valueOf(ch))) {
|
||||
break;
|
||||
} else {
|
||||
result.append(ch);
|
||||
}
|
||||
}
|
||||
|
||||
result.append("</font>");
|
||||
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
private String getColor(int totalNumberOfOpenings, int numberOfOpenings) {
|
||||
double c = 0.8;
|
||||
|
||||
int offset = ((int) (255 * c)) * numberOfOpenings / (totalNumberOfOpenings + 1);
|
||||
|
||||
// for tests:
|
||||
// innt result = Color.rgb(BASE_COLOUR_RED_COMPONENT - offset, BASE_COLOUR_GREEN_COMPONENT - offset, BASE_COLOUR_BLUE_COMPONENT - offset);
|
||||
int result = (0xFF << 24) | ((colorRed - offset) << 16) | ((colorGreen - offset) << 8) | (colorBlue - offset);
|
||||
|
||||
return "#" + Integer.toHexString(result).substring(2);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
||||
* For more information, please, contact se.solovyev@gmail.com
|
||||
* or visit http://se.solovyev.org
|
||||
*/
|
||||
|
||||
package org.solovyev.android.calculator.view;
|
||||
|
||||
import jscl.MathContext;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.calculator.CalculatorParseException;
|
||||
import org.solovyev.android.calculator.math.MathType;
|
||||
import org.solovyev.android.calculator.model.*;
|
||||
import org.solovyev.android.calculator.text.TextProcessor;
|
||||
import org.solovyev.common.MutableObject;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 10/12/11
|
||||
* Time: 9:47 PM
|
||||
*/
|
||||
public class TextHighlighter implements TextProcessor<TextHighlighter.Result, String> {
|
||||
|
||||
private static final Map<String, String> nbFontAttributes = new HashMap<String, String>();
|
||||
|
||||
static {
|
||||
nbFontAttributes.put("color", "#008000");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public final MathContext mathContext;
|
||||
|
||||
public static class Result implements CharSequence {
|
||||
|
||||
@NotNull
|
||||
private final String string;
|
||||
|
||||
private final int offset;
|
||||
|
||||
public Result(@NotNull String string, int offset) {
|
||||
this.string = string;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int length() {
|
||||
return string.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public char charAt(int i) {
|
||||
return string.charAt(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence subSequence(int i, int i1) {
|
||||
return string.subSequence(i, i1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return string;
|
||||
}
|
||||
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
}
|
||||
|
||||
private final int color;
|
||||
private final int colorRed;
|
||||
private final int colorGreen;
|
||||
private final int colorBlue;
|
||||
private final boolean formatNumber;
|
||||
|
||||
public TextHighlighter(int baseColor, boolean formatNumber, @NotNull MathContext mathContext) {
|
||||
this.color = baseColor;
|
||||
this.formatNumber = formatNumber;
|
||||
this.mathContext = mathContext;
|
||||
//this.colorRed = Color.red(baseColor);
|
||||
this.colorRed = (baseColor >> 16) & 0xFF;
|
||||
//this.colorGreen = Color.green(baseColor);
|
||||
this.colorGreen = (color >> 8) & 0xFF;
|
||||
//this.colorBlue = Color.blue(baseColor);
|
||||
this.colorBlue = color & 0xFF;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Result process(@NotNull String text) throws CalculatorParseException {
|
||||
final String result;
|
||||
|
||||
int maxNumberOfOpenGroupSymbols = 0;
|
||||
int numberOfOpenGroupSymbols = 0;
|
||||
|
||||
final StringBuilder text1 = new StringBuilder();
|
||||
|
||||
int resultOffset = 0;
|
||||
|
||||
final AbstractNumberBuilder numberBuilder;
|
||||
if (!formatNumber) {
|
||||
numberBuilder = new LiteNumberBuilder(CalculatorEngine.instance.getEngine());
|
||||
} else {
|
||||
numberBuilder = new NumberBuilder(CalculatorEngine.instance.getEngine());
|
||||
}
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
MathType.Result mathType = MathType.getType(text, i, numberBuilder.isHexMode());
|
||||
|
||||
if (numberBuilder instanceof NumberBuilder) {
|
||||
final MutableObject<Integer> numberOffset = new MutableObject<Integer>(0);
|
||||
((NumberBuilder) numberBuilder).process(text1, mathType, numberOffset);
|
||||
resultOffset += numberOffset.getObject();
|
||||
} else {
|
||||
((LiteNumberBuilder) numberBuilder).process(mathType);
|
||||
}
|
||||
|
||||
final String match = mathType.getMatch();
|
||||
switch (mathType.getMathType()) {
|
||||
case open_group_symbol:
|
||||
numberOfOpenGroupSymbols++;
|
||||
maxNumberOfOpenGroupSymbols = Math.max(maxNumberOfOpenGroupSymbols, numberOfOpenGroupSymbols);
|
||||
text1.append(text.charAt(i));
|
||||
break;
|
||||
case close_group_symbol:
|
||||
numberOfOpenGroupSymbols--;
|
||||
text1.append(text.charAt(i));
|
||||
break;
|
||||
case operator:
|
||||
text1.append(match);
|
||||
if (match.length() > 1) {
|
||||
i += match.length() - 1;
|
||||
}
|
||||
break;
|
||||
case function:
|
||||
i = processHighlightedText(text1, i, match, "i", null);
|
||||
break;
|
||||
case constant:
|
||||
i = processHighlightedText(text1, i, match, "b", null);
|
||||
break;
|
||||
case numeral_base:
|
||||
i = processHighlightedText(text1, i, match, "b", null);
|
||||
break;
|
||||
default:
|
||||
if (mathType.getMathType() == MathType.text || match.length() <= 1) {
|
||||
text1.append(text.charAt(i));
|
||||
} else {
|
||||
text1.append(match);
|
||||
i += match.length() - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (numberBuilder instanceof NumberBuilder) {
|
||||
final MutableObject<Integer> numberOffset = new MutableObject<Integer>(0);
|
||||
((NumberBuilder) numberBuilder).processNumber(text1, numberOffset);
|
||||
resultOffset += numberOffset.getObject();
|
||||
}
|
||||
|
||||
if (maxNumberOfOpenGroupSymbols > 0) {
|
||||
|
||||
final StringBuilder text2 = new StringBuilder();
|
||||
|
||||
String s = text1.toString();
|
||||
int i = processBracketGroup(text2, s, 0, 0, maxNumberOfOpenGroupSymbols);
|
||||
for (; i < s.length(); i++) {
|
||||
text2.append(s.charAt(i));
|
||||
}
|
||||
|
||||
//Log.d(CalculatorEditor.class.getName(), text2.toString());
|
||||
|
||||
result = text2.toString();
|
||||
} else {
|
||||
result = text1.toString();
|
||||
}
|
||||
|
||||
return new Result(result, resultOffset);
|
||||
}
|
||||
|
||||
private int processHighlightedText(@NotNull StringBuilder result, int i, @NotNull String match, @NotNull String tag, @Nullable Map<String, String> tagAttributes) {
|
||||
result.append("<").append(tag);
|
||||
|
||||
if (tagAttributes != null) {
|
||||
for (Map.Entry<String, String> entry : tagAttributes.entrySet()) {
|
||||
// attr1="attr1_value" attr2="attr2_value"
|
||||
result.append(" ").append(entry.getKey()).append("=\"").append(entry.getValue()).append("\"");
|
||||
}
|
||||
}
|
||||
|
||||
result.append(">").append(match).append("</").append(tag).append(">");
|
||||
if (match.length() > 1) {
|
||||
return i + match.length() - 1;
|
||||
} else {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
private int processBracketGroup(@NotNull StringBuilder result, @NotNull String s, int i, int numberOfOpenings, int maxNumberOfGroups) {
|
||||
|
||||
result.append("<font color=\"").append(getColor(maxNumberOfGroups, numberOfOpenings)).append("\">");
|
||||
|
||||
for (; i < s.length(); i++) {
|
||||
char ch = s.charAt(i);
|
||||
|
||||
if (MathType.open_group_symbol.getTokens().contains(String.valueOf(ch))) {
|
||||
result.append(ch);
|
||||
result.append("</font>");
|
||||
i = processBracketGroup(result, s, i + 1, numberOfOpenings + 1, maxNumberOfGroups);
|
||||
result.append("<font color=\"").append(getColor(maxNumberOfGroups, numberOfOpenings)).append("\">");
|
||||
if (i < s.length() && MathType.close_group_symbol.getTokens().contains(String.valueOf(s.charAt(i)))) {
|
||||
result.append(s.charAt(i));
|
||||
}
|
||||
} else if (MathType.close_group_symbol.getTokens().contains(String.valueOf(ch))) {
|
||||
break;
|
||||
} else {
|
||||
result.append(ch);
|
||||
}
|
||||
}
|
||||
|
||||
result.append("</font>");
|
||||
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
private String getColor(int totalNumberOfOpenings, int numberOfOpenings) {
|
||||
double c = 0.8;
|
||||
|
||||
int offset = ((int) (255 * c)) * numberOfOpenings / (totalNumberOfOpenings + 1);
|
||||
|
||||
// for tests:
|
||||
// innt result = Color.rgb(BASE_COLOUR_RED_COMPONENT - offset, BASE_COLOUR_GREEN_COMPONENT - offset, BASE_COLOUR_BLUE_COMPONENT - offset);
|
||||
int result = (0xFF << 24) | ((colorRed - offset) << 16) | ((colorGreen - offset) << 8) | (colorBlue - offset);
|
||||
|
||||
return "#" + Integer.toHexString(result).substring(2);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user