Editor/display changes

This commit is contained in:
Sergey Solovyev 2012-09-24 23:20:19 +04:00
parent 4b08fa133e
commit 4e42f6ad6b
6 changed files with 813 additions and 699 deletions

View File

@ -1,170 +1,183 @@
package org.solovyev.android.calculator; package org.solovyev.android.calculator;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import static org.solovyev.android.calculator.CalculatorEventType.*; import static org.solovyev.android.calculator.CalculatorEventType.*;
/** /**
* User: serso * User: serso
* Date: 9/20/12 * Date: 9/20/12
* Time: 8:24 PM * Time: 8:24 PM
*/ */
public class CalculatorDisplayImpl implements CalculatorDisplay { public class CalculatorDisplayImpl implements CalculatorDisplay {
@NotNull @NotNull
private CalculatorEventData lastCalculatorEventData; private volatile CalculatorEventData lastCalculatorEventData;
@Nullable @NotNull
private CalculatorDisplayView view; private final Object lastCalculatorEventDataLock = new Object();
@NotNull @Nullable
private final Object viewLock = new Object(); private CalculatorDisplayView view;
@NotNull @NotNull
private CalculatorDisplayViewState viewState = CalculatorDisplayViewStateImpl.newDefaultInstance(); private final Object viewLock = new Object();
@NotNull @NotNull
private final Calculator calculator; private CalculatorDisplayViewState viewState = CalculatorDisplayViewStateImpl.newDefaultInstance();
public CalculatorDisplayImpl(@NotNull Calculator calculator) { @NotNull
this.calculator = calculator; private final Calculator calculator;
this.lastCalculatorEventData = CalculatorUtils.createFirstEventDataId();
this.calculator.addCalculatorEventListener(this); public CalculatorDisplayImpl(@NotNull Calculator calculator) {
} this.calculator = calculator;
this.lastCalculatorEventData = CalculatorUtils.createFirstEventDataId();
@Override this.calculator.addCalculatorEventListener(this);
public void setView(@Nullable CalculatorDisplayView view) { }
synchronized (viewLock) {
this.view = view; @Override
public void setView(@Nullable CalculatorDisplayView view) {
if (view != null) { synchronized (viewLock) {
this.view.setState(viewState); this.view = view;
}
} if (view != null) {
} this.view.setState(viewState);
}
@Nullable }
@Override }
public CalculatorDisplayView getView() {
return this.view; @Nullable
} @Override
public CalculatorDisplayView getView() {
@NotNull return this.view;
@Override }
public CalculatorDisplayViewState getViewState() {
return this.viewState; @NotNull
} @Override
public CalculatorDisplayViewState getViewState() {
@Override return this.viewState;
public void setViewState(@NotNull CalculatorDisplayViewState newViewState) { }
synchronized (viewLock) {
final CalculatorDisplayViewState oldViewState = setViewState0(newViewState); @Override
public void setViewState(@NotNull CalculatorDisplayViewState newViewState) {
this.calculator.fireCalculatorEvent(display_state_changed, new CalculatorDisplayChangeEventDataImpl(oldViewState, newViewState)); synchronized (viewLock) {
} final CalculatorDisplayViewState oldViewState = setViewState0(newViewState);
}
this.calculator.fireCalculatorEvent(display_state_changed, new CalculatorDisplayChangeEventDataImpl(oldViewState, newViewState));
private void setViewStateForSequence(@NotNull CalculatorDisplayViewState newViewState, @NotNull Long sequenceId) { }
synchronized (viewLock) { }
final CalculatorDisplayViewState oldViewState = setViewState0(newViewState);
private void setViewStateForSequence(@NotNull CalculatorDisplayViewState newViewState, @NotNull Long sequenceId) {
this.calculator.fireCalculatorEvent(display_state_changed, new CalculatorDisplayChangeEventDataImpl(oldViewState, newViewState), sequenceId); synchronized (viewLock) {
} final CalculatorDisplayViewState oldViewState = setViewState0(newViewState);
}
this.calculator.fireCalculatorEvent(display_state_changed, new CalculatorDisplayChangeEventDataImpl(oldViewState, newViewState), sequenceId);
// must be synchronized with viewLock }
@NotNull }
private CalculatorDisplayViewState setViewState0(@NotNull CalculatorDisplayViewState newViewState) {
final CalculatorDisplayViewState oldViewState = this.viewState; // must be synchronized with viewLock
@NotNull
this.viewState = newViewState; private CalculatorDisplayViewState setViewState0(@NotNull CalculatorDisplayViewState newViewState) {
if (this.view != null) { final CalculatorDisplayViewState oldViewState = this.viewState;
this.view.setState(newViewState);
} this.viewState = newViewState;
return oldViewState; if (this.view != null) {
} this.view.setState(newViewState);
}
@Override return oldViewState;
@NotNull }
public CalculatorEventData getLastEventData() {
return lastCalculatorEventData; @Override
} @NotNull
public CalculatorEventData getLastEventData() {
@Override synchronized (lastCalculatorEventDataLock) {
public void onCalculatorEvent(@NotNull CalculatorEventData calculatorEventData, return lastCalculatorEventData;
@NotNull CalculatorEventType calculatorEventType, }
@Nullable Object data) { }
if (calculatorEventType.isOfType(calculation_result, calculation_failed, calculation_cancelled, conversion_result, conversion_failed)) {
@Override
if (calculatorEventData.isAfter(lastCalculatorEventData)) { public void onCalculatorEvent(@NotNull CalculatorEventData calculatorEventData,
lastCalculatorEventData = calculatorEventData; @NotNull CalculatorEventType calculatorEventType,
} @Nullable Object data) {
if (calculatorEventType.isOfType(calculation_result, calculation_failed, calculation_cancelled, conversion_result, conversion_failed)) {
switch (calculatorEventType) {
case conversion_failed: boolean processEvent = false;
processConversationFailed((CalculatorConversionEventData) calculatorEventData, (ConversionFailure) data); boolean sameSequence = false;
break;
case conversion_result: synchronized (lastCalculatorEventDataLock) {
processConversationResult((CalculatorConversionEventData)calculatorEventData, (String)data); if (calculatorEventData.isAfter(lastCalculatorEventData)) {
break; sameSequence = calculatorEventData.isSameSequence(lastCalculatorEventData);
case calculation_result: lastCalculatorEventData = calculatorEventData;
processCalculationResult((CalculatorEvaluationEventData) calculatorEventData, (CalculatorOutput) data); processEvent = true;
break; }
case calculation_cancelled: }
processCalculationCancelled((CalculatorEvaluationEventData)calculatorEventData);
break; if (processEvent) {
case calculation_failed: switch (calculatorEventType) {
processCalculationFailed((CalculatorEvaluationEventData)calculatorEventData, (CalculatorFailure) data); case conversion_failed:
break; processConversationFailed((CalculatorConversionEventData) calculatorEventData, (ConversionFailure) data);
} break;
case conversion_result:
} processConversationResult((CalculatorConversionEventData)calculatorEventData, (String)data);
} break;
case calculation_result:
private void processConversationFailed(@NotNull CalculatorConversionEventData calculatorEventData, processCalculationResult((CalculatorEvaluationEventData) calculatorEventData, (CalculatorOutput) data);
@NotNull ConversionFailure data) { break;
this.setViewStateForSequence(CalculatorDisplayViewStateImpl.newErrorState(calculatorEventData.getDisplayState().getOperation(), CalculatorMessages.getBundle().getString(CalculatorMessages.syntax_error)), calculatorEventData.getSequenceId()); case calculation_cancelled:
processCalculationCancelled((CalculatorEvaluationEventData)calculatorEventData);
} break;
case calculation_failed:
private void processCalculationFailed(@NotNull CalculatorEvaluationEventData calculatorEventData, @NotNull CalculatorFailure data) { processCalculationFailed((CalculatorEvaluationEventData)calculatorEventData, (CalculatorFailure) data);
break;
final CalculatorEvalException calculatorEvalException = data.getCalculationEvalException(); }
}
final String errorMessage; }
if (calculatorEvalException != null) { }
errorMessage = CalculatorMessages.getBundle().getString(CalculatorMessages.syntax_error);
} else { private void processConversationFailed(@NotNull CalculatorConversionEventData calculatorEventData,
final CalculatorParseException calculationParseException = data.getCalculationParseException(); @NotNull ConversionFailure data) {
if (calculationParseException != null) { this.setViewStateForSequence(CalculatorDisplayViewStateImpl.newErrorState(calculatorEventData.getDisplayState().getOperation(), CalculatorMessages.getBundle().getString(CalculatorMessages.syntax_error)), calculatorEventData.getSequenceId());
errorMessage = calculationParseException.getLocalizedMessage();
} else { }
errorMessage = CalculatorMessages.getBundle().getString(CalculatorMessages.syntax_error);
} private void processCalculationFailed(@NotNull CalculatorEvaluationEventData calculatorEventData, @NotNull CalculatorFailure data) {
}
final CalculatorEvalException calculatorEvalException = data.getCalculationEvalException();
this.setViewStateForSequence(CalculatorDisplayViewStateImpl.newErrorState(calculatorEventData.getOperation(), errorMessage), calculatorEventData.getSequenceId());
} final String errorMessage;
if (calculatorEvalException != null) {
private void processCalculationCancelled(@NotNull CalculatorEvaluationEventData calculatorEventData) { errorMessage = CalculatorMessages.getBundle().getString(CalculatorMessages.syntax_error);
final String errorMessage = CalculatorMessages.getBundle().getString(CalculatorMessages.syntax_error); } else {
final CalculatorParseException calculationParseException = data.getCalculationParseException();
this.setViewStateForSequence(CalculatorDisplayViewStateImpl.newErrorState(calculatorEventData.getOperation(), errorMessage), calculatorEventData.getSequenceId()); if (calculationParseException != null) {
} errorMessage = calculationParseException.getLocalizedMessage();
} else {
private void processCalculationResult(@NotNull CalculatorEvaluationEventData calculatorEventData, @NotNull CalculatorOutput data) { errorMessage = CalculatorMessages.getBundle().getString(CalculatorMessages.syntax_error);
final String stringResult = data.getStringResult(); }
this.setViewStateForSequence(CalculatorDisplayViewStateImpl.newValidState(calculatorEventData.getOperation(), data.getResult(), stringResult, 0), calculatorEventData.getSequenceId()); }
}
this.setViewStateForSequence(CalculatorDisplayViewStateImpl.newErrorState(calculatorEventData.getOperation(), errorMessage), calculatorEventData.getSequenceId());
private void processConversationResult(@NotNull CalculatorConversionEventData calculatorEventData, @NotNull String result) { }
// add prefix
if (calculatorEventData.getFromNumeralBase() != calculatorEventData.getToNumeralBase()) { private void processCalculationCancelled(@NotNull CalculatorEvaluationEventData calculatorEventData) {
result = calculatorEventData.getToNumeralBase().getJsclPrefix() + result; final String errorMessage = CalculatorMessages.getBundle().getString(CalculatorMessages.syntax_error);
}
this.setViewStateForSequence(CalculatorDisplayViewStateImpl.newErrorState(calculatorEventData.getOperation(), errorMessage), calculatorEventData.getSequenceId());
final CalculatorDisplayViewState displayState = calculatorEventData.getDisplayState(); }
this.setViewStateForSequence(CalculatorDisplayViewStateImpl.newValidState(displayState.getOperation(), displayState.getResult(), result, 0), calculatorEventData.getSequenceId());
} private void processCalculationResult(@NotNull CalculatorEvaluationEventData calculatorEventData, @NotNull CalculatorOutput data) {
} final String stringResult = data.getStringResult();
this.setViewStateForSequence(CalculatorDisplayViewStateImpl.newValidState(calculatorEventData.getOperation(), data.getResult(), stringResult, 0), calculatorEventData.getSequenceId());
}
private void processConversationResult(@NotNull CalculatorConversionEventData calculatorEventData, @NotNull String result) {
// add prefix
if (calculatorEventData.getFromNumeralBase() != calculatorEventData.getToNumeralBase()) {
result = calculatorEventData.getToNumeralBase().getJsclPrefix() + result;
}
final CalculatorDisplayViewState displayState = calculatorEventData.getDisplayState();
this.setViewStateForSequence(CalculatorDisplayViewStateImpl.newValidState(displayState.getOperation(), displayState.getResult(), result, 0), calculatorEventData.getSequenceId());
}
}

View File

@ -16,6 +16,9 @@ public interface CalculatorEditor extends CalculatorEventListener {
@NotNull @NotNull
CalculatorEditorViewState getViewState(); CalculatorEditorViewState getViewState();
// updates state of view (view.setState())
void updateViewState();
void setViewState(@NotNull CalculatorEditorViewState viewState); void setViewState(@NotNull CalculatorEditorViewState viewState);
/* /*

View File

@ -48,8 +48,17 @@ public class CalculatorEditorImpl implements CalculatorEditor {
return lastViewState; return lastViewState;
} }
@Override
public void updateViewState() {
setViewState(this.lastViewState, false);
}
@Override @Override
public void setViewState(@NotNull CalculatorEditorViewState newViewState) { public void setViewState(@NotNull CalculatorEditorViewState newViewState) {
setViewState(newViewState, true);
}
private void setViewState(CalculatorEditorViewState newViewState, boolean fireEvent) {
synchronized (viewLock) { synchronized (viewLock) {
final CalculatorEditorViewState oldViewState = this.lastViewState; final CalculatorEditorViewState oldViewState = this.lastViewState;
@ -58,7 +67,9 @@ public class CalculatorEditorImpl implements CalculatorEditor {
this.view.setState(newViewState); this.view.setState(newViewState);
} }
calculator.fireCalculatorEvent(CalculatorEventType.editor_state_changed, new CalculatorEditorChangeEventDataImpl(oldViewState, newViewState)); if (fireEvent) {
calculator.fireCalculatorEvent(CalculatorEventType.editor_state_changed, new CalculatorEditorChangeEventDataImpl(oldViewState, newViewState));
}
} }
} }

View File

@ -1,433 +1,436 @@
package org.solovyev.android.calculator; package org.solovyev.android.calculator;
import jscl.AbstractJsclArithmeticException; import jscl.AbstractJsclArithmeticException;
import jscl.NumeralBase; import jscl.NumeralBase;
import jscl.NumeralBaseException; import jscl.NumeralBaseException;
import jscl.math.Generic; import jscl.math.Generic;
import jscl.text.ParseInterruptedException; import jscl.text.ParseInterruptedException;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.solovyev.android.calculator.history.CalculatorHistory; import org.solovyev.android.calculator.history.CalculatorHistory;
import org.solovyev.android.calculator.history.CalculatorHistoryState; import org.solovyev.android.calculator.history.CalculatorHistoryState;
import org.solovyev.android.calculator.jscl.JsclOperation; import org.solovyev.android.calculator.jscl.JsclOperation;
import org.solovyev.android.calculator.text.TextProcessor; import org.solovyev.android.calculator.text.TextProcessor;
import org.solovyev.android.calculator.units.CalculatorNumeralBase; import org.solovyev.android.calculator.units.CalculatorNumeralBase;
import org.solovyev.common.history.HistoryAction; import org.solovyev.common.history.HistoryAction;
import org.solovyev.common.msg.MessageRegistry; import org.solovyev.common.msg.MessageRegistry;
import org.solovyev.common.msg.MessageType; import org.solovyev.common.msg.MessageType;
import org.solovyev.common.text.StringUtils; import org.solovyev.common.text.StringUtils;
import org.solovyev.math.units.*; import org.solovyev.math.units.*;
import java.util.List; import java.util.List;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
/** /**
* User: Solovyev_S * User: Solovyev_S
* Date: 20.09.12 * Date: 20.09.12
* Time: 16:42 * Time: 16:42
*/ */
public class CalculatorImpl implements Calculator, CalculatorEventListener { public class CalculatorImpl implements Calculator, CalculatorEventListener {
@NotNull @NotNull
private final CalculatorEventContainer calculatorEventContainer = new ListCalculatorEventContainer(); private final CalculatorEventContainer calculatorEventContainer = new ListCalculatorEventContainer();
@NotNull @NotNull
private final AtomicLong counter = new AtomicLong(CalculatorUtils.FIRST_ID); private final AtomicLong counter = new AtomicLong(CalculatorUtils.FIRST_ID);
@NotNull @NotNull
private final TextProcessor<PreparedExpression, String> preprocessor = ToJsclTextProcessor.getInstance(); private final TextProcessor<PreparedExpression, String> preprocessor = ToJsclTextProcessor.getInstance();
@NotNull @NotNull
private final Executor threadPoolExecutor = Executors.newFixedThreadPool(10); private final Executor calculationsExecutor = Executors.newFixedThreadPool(10);
public CalculatorImpl() { @NotNull
this.addCalculatorEventListener(this); private final Executor eventExecutor = Executors.newFixedThreadPool(1);
}
public CalculatorImpl() {
@NotNull this.addCalculatorEventListener(this);
private CalculatorEventData nextEventData() { }
long eventId = counter.incrementAndGet();
return CalculatorEventDataImpl.newInstance(eventId, eventId); @NotNull
} private CalculatorEventData nextEventData() {
long eventId = counter.incrementAndGet();
@NotNull return CalculatorEventDataImpl.newInstance(eventId, eventId);
private CalculatorEventData nextEventData(@NotNull Long sequenceId) { }
long eventId = counter.incrementAndGet();
return CalculatorEventDataImpl.newInstance(eventId, sequenceId); @NotNull
} private CalculatorEventData nextEventData(@NotNull Long sequenceId) {
long eventId = counter.incrementAndGet();
/* return CalculatorEventDataImpl.newInstance(eventId, sequenceId);
********************************************************************** }
*
* CALCULATION /*
* **********************************************************************
********************************************************************** *
*/ * CALCULATION
*
@Override **********************************************************************
public void evaluate() { */
final CalculatorEditorViewState viewState = getEditor().getViewState();
fireCalculatorEvent(CalculatorEventType.manual_calculation_requested, viewState); @Override
this.evaluate(JsclOperation.numeric, viewState.getText()); public void evaluate() {
} final CalculatorEditorViewState viewState = getEditor().getViewState();
fireCalculatorEvent(CalculatorEventType.manual_calculation_requested, viewState);
@Override this.evaluate(JsclOperation.numeric, viewState.getText());
public void evaluate(@NotNull Long sequenceId) { }
final CalculatorEditorViewState viewState = getEditor().getViewState();
fireCalculatorEvent(CalculatorEventType.manual_calculation_requested, viewState, sequenceId); @Override
this.evaluate(JsclOperation.numeric, viewState.getText(), sequenceId); public void evaluate(@NotNull Long sequenceId) {
} final CalculatorEditorViewState viewState = getEditor().getViewState();
fireCalculatorEvent(CalculatorEventType.manual_calculation_requested, viewState, sequenceId);
@Override this.evaluate(JsclOperation.numeric, viewState.getText(), sequenceId);
public void simplify() { }
final CalculatorEditorViewState viewState = getEditor().getViewState();
fireCalculatorEvent(CalculatorEventType.manual_calculation_requested, viewState); @Override
this.evaluate(JsclOperation.simplify, viewState.getText()); public void simplify() {
} final CalculatorEditorViewState viewState = getEditor().getViewState();
fireCalculatorEvent(CalculatorEventType.manual_calculation_requested, viewState);
@NotNull this.evaluate(JsclOperation.simplify, viewState.getText());
@Override }
public CalculatorEventData evaluate(@NotNull final JsclOperation operation,
@NotNull final String expression) { @NotNull
@Override
final CalculatorEventData eventDataId = nextEventData(); public CalculatorEventData evaluate(@NotNull final JsclOperation operation,
@NotNull final String expression) {
threadPoolExecutor.execute(new Runnable() {
@Override final CalculatorEventData eventDataId = nextEventData();
public void run() {
CalculatorImpl.this.evaluate(eventDataId.getSequenceId(), operation, expression, null); calculationsExecutor.execute(new Runnable() {
} @Override
}); public void run() {
CalculatorImpl.this.evaluate(eventDataId.getSequenceId(), operation, expression, null);
return eventDataId; }
} });
@NotNull return eventDataId;
@Override }
public CalculatorEventData evaluate(@NotNull final JsclOperation operation, @NotNull final String expression, @NotNull Long sequenceId) {
final CalculatorEventData eventDataId = nextEventData(sequenceId); @NotNull
@Override
threadPoolExecutor.execute(new Runnable() { public CalculatorEventData evaluate(@NotNull final JsclOperation operation, @NotNull final String expression, @NotNull Long sequenceId) {
@Override final CalculatorEventData eventDataId = nextEventData(sequenceId);
public void run() {
CalculatorImpl.this.evaluate(eventDataId.getSequenceId(), operation, expression, null); calculationsExecutor.execute(new Runnable() {
} @Override
}); public void run() {
CalculatorImpl.this.evaluate(eventDataId.getSequenceId(), operation, expression, null);
return eventDataId; }
} });
@Override return eventDataId;
public void init() { }
CalculatorLocatorImpl.getInstance().getEngine().init();
CalculatorLocatorImpl.getInstance().getHistory().load(); @Override
} public void init() {
CalculatorLocatorImpl.getInstance().getEngine().init();
@NotNull CalculatorLocatorImpl.getInstance().getHistory().load();
private CalculatorConversionEventData newConversionEventData(@NotNull Long sequenceId, }
@NotNull Generic value,
@NotNull NumeralBase from, @NotNull
@NotNull NumeralBase to, private CalculatorConversionEventData newConversionEventData(@NotNull Long sequenceId,
@NotNull CalculatorDisplayViewState displayViewState) { @NotNull Generic value,
return CalculatorConversionEventDataImpl.newInstance(nextEventData(sequenceId), value, from, to, displayViewState); @NotNull NumeralBase from,
} @NotNull NumeralBase to,
@NotNull CalculatorDisplayViewState displayViewState) {
private void evaluate(@NotNull Long sequenceId, return CalculatorConversionEventDataImpl.newInstance(nextEventData(sequenceId), value, from, to, displayViewState);
@NotNull JsclOperation operation, }
@NotNull String expression,
@Nullable MessageRegistry mr) { private void evaluate(@NotNull Long sequenceId,
@NotNull JsclOperation operation,
PreparedExpression preparedExpression = null; @NotNull String expression,
@Nullable MessageRegistry mr) {
fireCalculatorEvent(newCalculationEventData(operation, expression, sequenceId), CalculatorEventType.calculation_started, new CalculatorInputImpl(expression, operation));
PreparedExpression preparedExpression = null;
try {
fireCalculatorEvent(newCalculationEventData(operation, expression, sequenceId), CalculatorEventType.calculation_started, new CalculatorInputImpl(expression, operation));
expression = expression.trim();
try {
if (StringUtils.isEmpty(expression)) {
fireCalculatorEvent(newCalculationEventData(operation, expression, sequenceId), CalculatorEventType.calculation_result, CalculatorOutputImpl.newEmptyOutput(operation)); expression = expression.trim();
} else {
preparedExpression = preprocessor.process(expression); if (StringUtils.isEmpty(expression)) {
fireCalculatorEvent(newCalculationEventData(operation, expression, sequenceId), CalculatorEventType.calculation_result, CalculatorOutputImpl.newEmptyOutput(operation));
final String jsclExpression = preparedExpression.toString(); } else {
preparedExpression = preprocessor.process(expression);
try {
final String jsclExpression = preparedExpression.toString();
final Generic result = operation.evaluateGeneric(jsclExpression, CalculatorLocatorImpl.getInstance().getEngine().getMathEngine());
try {
// NOTE: toString() method must be called here as ArithmeticOperationException may occur in it (just to avoid later check!)
result.toString(); final Generic result = operation.evaluateGeneric(jsclExpression, CalculatorLocatorImpl.getInstance().getEngine().getMathEngine());
final CalculatorOutput data = CalculatorOutputImpl.newOutput(operation.getFromProcessor().process(result), operation, result); // NOTE: toString() method must be called here as ArithmeticOperationException may occur in it (just to avoid later check!)
fireCalculatorEvent(newCalculationEventData(operation, expression, sequenceId), CalculatorEventType.calculation_result, data); result.toString();
} catch (AbstractJsclArithmeticException e) { final CalculatorOutput data = CalculatorOutputImpl.newOutput(operation.getFromProcessor().process(result), operation, result);
handleException(sequenceId, operation, expression, mr, new CalculatorEvalException(e, e, jsclExpression)); fireCalculatorEvent(newCalculationEventData(operation, expression, sequenceId), CalculatorEventType.calculation_result, data);
}
} } catch (AbstractJsclArithmeticException e) {
handleException(sequenceId, operation, expression, mr, new CalculatorEvalException(e, e, jsclExpression));
} catch (ArithmeticException e) { }
handleException(sequenceId, operation, expression, mr, preparedExpression, new CalculatorParseException(expression, new CalculatorMessage(CalculatorMessages.msg_001, MessageType.error, e.getMessage()))); }
} catch (StackOverflowError e) {
handleException(sequenceId, operation, expression, mr, preparedExpression, new CalculatorParseException(expression, new CalculatorMessage(CalculatorMessages.msg_002, MessageType.error))); } catch (ArithmeticException e) {
} catch (jscl.text.ParseException e) { handleException(sequenceId, operation, expression, mr, preparedExpression, new CalculatorParseException(expression, new CalculatorMessage(CalculatorMessages.msg_001, MessageType.error, e.getMessage())));
handleException(sequenceId, operation, expression, mr, preparedExpression, new CalculatorParseException(e)); } catch (StackOverflowError e) {
} catch (ParseInterruptedException e) { handleException(sequenceId, operation, expression, mr, preparedExpression, new CalculatorParseException(expression, new CalculatorMessage(CalculatorMessages.msg_002, MessageType.error)));
} catch (jscl.text.ParseException e) {
// do nothing - we ourselves interrupt the calculations handleException(sequenceId, operation, expression, mr, preparedExpression, new CalculatorParseException(e));
fireCalculatorEvent(newCalculationEventData(operation, expression, sequenceId), CalculatorEventType.calculation_cancelled, null); } catch (ParseInterruptedException e) {
} catch (CalculatorParseException e) { // do nothing - we ourselves interrupt the calculations
handleException(sequenceId, operation, expression, mr, preparedExpression, e); fireCalculatorEvent(newCalculationEventData(operation, expression, sequenceId), CalculatorEventType.calculation_cancelled, null);
} finally {
fireCalculatorEvent(newCalculationEventData(operation, expression, sequenceId), CalculatorEventType.calculation_finished, null); } catch (CalculatorParseException e) {
} handleException(sequenceId, operation, expression, mr, preparedExpression, e);
} } finally {
fireCalculatorEvent(newCalculationEventData(operation, expression, sequenceId), CalculatorEventType.calculation_finished, null);
@NotNull }
private CalculatorEventData newCalculationEventData(@NotNull JsclOperation operation, }
@NotNull String expression,
@NotNull Long calculationId) { @NotNull
return new CalculatorEvaluationEventDataImpl(nextEventData(calculationId), operation, expression); private CalculatorEventData newCalculationEventData(@NotNull JsclOperation operation,
} @NotNull String expression,
@NotNull Long calculationId) {
private void handleException(@NotNull Long sequenceId, return new CalculatorEvaluationEventDataImpl(nextEventData(calculationId), operation, expression);
@NotNull JsclOperation operation, }
@NotNull String expression,
@Nullable MessageRegistry mr, private void handleException(@NotNull Long sequenceId,
@Nullable PreparedExpression preparedExpression, @NotNull JsclOperation operation,
@NotNull CalculatorParseException parseException) { @NotNull String expression,
@Nullable MessageRegistry mr,
if (operation == JsclOperation.numeric @Nullable PreparedExpression preparedExpression,
&& preparedExpression != null @NotNull CalculatorParseException parseException) {
&& preparedExpression.isExistsUndefinedVar()) {
if (operation == JsclOperation.numeric
evaluate(sequenceId, JsclOperation.simplify, expression, mr); && preparedExpression != null
} else { && preparedExpression.isExistsUndefinedVar()) {
fireCalculatorEvent(newCalculationEventData(operation, expression, sequenceId), CalculatorEventType.calculation_failed, new CalculatorFailureImpl(parseException)); evaluate(sequenceId, JsclOperation.simplify, expression, mr);
} } else {
}
fireCalculatorEvent(newCalculationEventData(operation, expression, sequenceId), CalculatorEventType.calculation_failed, new CalculatorFailureImpl(parseException));
private void handleException(@NotNull Long calculationId, }
@NotNull JsclOperation operation, }
@NotNull String expression,
@Nullable MessageRegistry mr, private void handleException(@NotNull Long calculationId,
@NotNull CalculatorEvalException evalException) { @NotNull JsclOperation operation,
@NotNull String expression,
if (operation == JsclOperation.numeric && evalException.getCause() instanceof NumeralBaseException) { @Nullable MessageRegistry mr,
evaluate(calculationId, JsclOperation.simplify, expression, mr); @NotNull CalculatorEvalException evalException) {
}
if (operation == JsclOperation.numeric && evalException.getCause() instanceof NumeralBaseException) {
fireCalculatorEvent(newCalculationEventData(operation, expression, calculationId), CalculatorEventType.calculation_failed, new CalculatorFailureImpl(evalException)); evaluate(calculationId, JsclOperation.simplify, expression, mr);
} }
/* fireCalculatorEvent(newCalculationEventData(operation, expression, calculationId), CalculatorEventType.calculation_failed, new CalculatorFailureImpl(evalException));
********************************************************************** }
*
* CONVERSION /*
* **********************************************************************
********************************************************************** *
*/ * CONVERSION
*
@NotNull **********************************************************************
@Override */
public CalculatorEventData convert(@NotNull final Generic value,
@NotNull final NumeralBase to) { @NotNull
final CalculatorEventData eventDataId = nextEventData(); @Override
public CalculatorEventData convert(@NotNull final Generic value,
final CalculatorDisplayViewState displayViewState = CalculatorLocatorImpl.getInstance().getDisplay().getViewState(); @NotNull final NumeralBase to) {
final NumeralBase from = CalculatorLocatorImpl.getInstance().getEngine().getNumeralBase(); final CalculatorEventData eventDataId = nextEventData();
threadPoolExecutor.execute(new Runnable() { final CalculatorDisplayViewState displayViewState = CalculatorLocatorImpl.getInstance().getDisplay().getViewState();
@Override final NumeralBase from = CalculatorLocatorImpl.getInstance().getEngine().getNumeralBase();
public void run() {
final Long sequenceId = eventDataId.getSequenceId(); calculationsExecutor.execute(new Runnable() {
@Override
fireCalculatorEvent(newConversionEventData(sequenceId, value, from, to, displayViewState), CalculatorEventType.conversion_started, null); public void run() {
try { final Long sequenceId = eventDataId.getSequenceId();
final String result = doConversion(value, from, to); fireCalculatorEvent(newConversionEventData(sequenceId, value, from, to, displayViewState), CalculatorEventType.conversion_started, null);
try {
fireCalculatorEvent(newConversionEventData(sequenceId, value, from, to, displayViewState), CalculatorEventType.conversion_result, result);
final String result = doConversion(value, from, to);
} catch (ConversionException e) {
fireCalculatorEvent(newConversionEventData(sequenceId, value, from, to, displayViewState), CalculatorEventType.conversion_failed, new ConversionFailureImpl(e)); fireCalculatorEvent(newConversionEventData(sequenceId, value, from, to, displayViewState), CalculatorEventType.conversion_result, result);
}
} } catch (ConversionException e) {
}); fireCalculatorEvent(newConversionEventData(sequenceId, value, from, to, displayViewState), CalculatorEventType.conversion_failed, new ConversionFailureImpl(e));
}
return eventDataId; }
} });
@NotNull return eventDataId;
private static String doConversion(@NotNull Generic generic, }
@NotNull NumeralBase from,
@NotNull NumeralBase to) throws ConversionException { @NotNull
final String result; private static String doConversion(@NotNull Generic generic,
@NotNull NumeralBase from,
if (from != to) { @NotNull NumeralBase to) throws ConversionException {
String fromString = generic.toString(); final String result;
if (!StringUtils.isEmpty(fromString)) {
try { if (from != to) {
fromString = ToJsclTextProcessor.getInstance().process(fromString).getExpression(); String fromString = generic.toString();
} catch (CalculatorParseException e) { if (!StringUtils.isEmpty(fromString)) {
// ok, problems while processing occurred try {
} fromString = ToJsclTextProcessor.getInstance().process(fromString).getExpression();
} } catch (CalculatorParseException e) {
// ok, problems while processing occurred
result = ConversionUtils.doConversion(CalculatorNumeralBase.getConverter(), fromString, CalculatorNumeralBase.valueOf(from), CalculatorNumeralBase.valueOf(to)); }
} else { }
result = generic.toString();
} result = ConversionUtils.doConversion(CalculatorNumeralBase.getConverter(), fromString, CalculatorNumeralBase.valueOf(from), CalculatorNumeralBase.valueOf(to));
} else {
return result; result = generic.toString();
} }
@Override return result;
public boolean isConversionPossible(@NotNull Generic generic, NumeralBase from, @NotNull NumeralBase to) { }
try {
doConversion(generic, from, to); @Override
return true; public boolean isConversionPossible(@NotNull Generic generic, NumeralBase from, @NotNull NumeralBase to) {
} catch (ConversionException e) { try {
return false; doConversion(generic, from, to);
} return true;
} } catch (ConversionException e) {
return false;
/* }
********************************************************************** }
*
* EVENTS /*
* **********************************************************************
********************************************************************** *
*/ * EVENTS
*
@Override **********************************************************************
public void addCalculatorEventListener(@NotNull CalculatorEventListener calculatorEventListener) { */
calculatorEventContainer.addCalculatorEventListener(calculatorEventListener);
} @Override
public void addCalculatorEventListener(@NotNull CalculatorEventListener calculatorEventListener) {
@Override calculatorEventContainer.addCalculatorEventListener(calculatorEventListener);
public void removeCalculatorEventListener(@NotNull CalculatorEventListener calculatorEventListener) { }
calculatorEventContainer.removeCalculatorEventListener(calculatorEventListener);
} @Override
public void removeCalculatorEventListener(@NotNull CalculatorEventListener calculatorEventListener) {
@Override calculatorEventContainer.removeCalculatorEventListener(calculatorEventListener);
public void fireCalculatorEvent(@NotNull CalculatorEventData calculatorEventData, @NotNull CalculatorEventType calculatorEventType, @Nullable Object data) { }
calculatorEventContainer.fireCalculatorEvent(calculatorEventData, calculatorEventType, data);
} @Override
public void fireCalculatorEvent(@NotNull final CalculatorEventData calculatorEventData, @NotNull final CalculatorEventType calculatorEventType, @Nullable final Object data) {
@Override eventExecutor.execute(new Runnable() {
public void fireCalculatorEvents(@NotNull List<CalculatorEvent> calculatorEvents) { @Override
calculatorEventContainer.fireCalculatorEvents(calculatorEvents); public void run() {
} calculatorEventContainer.fireCalculatorEvent(calculatorEventData, calculatorEventType, data);
}
@NotNull });
@Override }
public CalculatorEventData fireCalculatorEvent(@NotNull final CalculatorEventType calculatorEventType, @Nullable final Object data) {
final CalculatorEventData eventData = nextEventData(); @Override
public void fireCalculatorEvents(@NotNull final List<CalculatorEvent> calculatorEvents) {
threadPoolExecutor.execute(new Runnable() { eventExecutor.execute(new Runnable() {
@Override @Override
public void run() { public void run() {
fireCalculatorEvent(eventData, calculatorEventType, data); calculatorEventContainer.fireCalculatorEvents(calculatorEvents);
} }
}); });
}
return eventData;
} @NotNull
@Override
@NotNull public CalculatorEventData fireCalculatorEvent(@NotNull final CalculatorEventType calculatorEventType, @Nullable final Object data) {
@Override final CalculatorEventData eventData = nextEventData();
public CalculatorEventData fireCalculatorEvent(@NotNull final CalculatorEventType calculatorEventType, @Nullable final Object data, @NotNull Long sequenceId) {
final CalculatorEventData eventData = nextEventData(sequenceId); fireCalculatorEvent(eventData, calculatorEventType, data);
threadPoolExecutor.execute(new Runnable() { return eventData;
@Override }
public void run() {
fireCalculatorEvent(eventData, calculatorEventType, data); @NotNull
} @Override
}); public CalculatorEventData fireCalculatorEvent(@NotNull final CalculatorEventType calculatorEventType, @Nullable final Object data, @NotNull Long sequenceId) {
final CalculatorEventData eventData = nextEventData(sequenceId);
return eventData;
} fireCalculatorEvent(eventData, calculatorEventType, data);
/* return eventData;
********************************************************************** }
*
* EVENTS HANDLER /*
* **********************************************************************
********************************************************************** *
*/ * EVENTS HANDLER
*
@Override **********************************************************************
public void onCalculatorEvent(@NotNull CalculatorEventData calculatorEventData, @NotNull CalculatorEventType calculatorEventType, @Nullable Object data) { */
switch (calculatorEventType) { @Override
case editor_state_changed: public void onCalculatorEvent(@NotNull CalculatorEventData calculatorEventData, @NotNull CalculatorEventType calculatorEventType, @Nullable Object data) {
final CalculatorEditorChangeEventData changeEventData = (CalculatorEditorChangeEventData) data;
switch (calculatorEventType) {
final String newText = changeEventData.getNewState().getText(); case editor_state_changed:
final String oldText = changeEventData.getOldState().getText(); final CalculatorEditorChangeEventData changeEventData = (CalculatorEditorChangeEventData) data;
if (!newText.equals(oldText)) { final String newText = changeEventData.getNewState().getText();
evaluate(JsclOperation.numeric, changeEventData.getNewState().getText(), calculatorEventData.getSequenceId()); final String oldText = changeEventData.getOldState().getText();
}
break; if (!newText.equals(oldText)) {
case engine_preferences_changed: evaluate(JsclOperation.numeric, changeEventData.getNewState().getText(), calculatorEventData.getSequenceId());
evaluate(calculatorEventData.getSequenceId()); }
break; break;
} case engine_preferences_changed:
} evaluate(calculatorEventData.getSequenceId());
break;
/* }
********************************************************************** }
*
* HISTORY /*
* **********************************************************************
********************************************************************** *
*/ * HISTORY
*
@Override **********************************************************************
public void doHistoryAction(@NotNull HistoryAction historyAction) { */
final CalculatorHistory history = CalculatorLocatorImpl.getInstance().getHistory();
if (history.isActionAvailable(historyAction)) { @Override
final CalculatorHistoryState newState = history.doAction(historyAction, getCurrentHistoryState()); public void doHistoryAction(@NotNull HistoryAction historyAction) {
if (newState != null) { final CalculatorHistory history = CalculatorLocatorImpl.getInstance().getHistory();
setCurrentHistoryState(newState); if (history.isActionAvailable(historyAction)) {
} final CalculatorHistoryState newState = history.doAction(historyAction, getCurrentHistoryState());
} if (newState != null) {
} setCurrentHistoryState(newState);
}
@Override }
public void setCurrentHistoryState(@NotNull CalculatorHistoryState editorHistoryState) { }
editorHistoryState.setValuesFromHistory(getEditor(), getDisplay());
} @Override
public void setCurrentHistoryState(@NotNull CalculatorHistoryState editorHistoryState) {
@NotNull editorHistoryState.setValuesFromHistory(getEditor(), getDisplay());
@Override }
public CalculatorHistoryState getCurrentHistoryState() {
return CalculatorHistoryState.newInstance(getEditor(), getDisplay()); @NotNull
} @Override
public CalculatorHistoryState getCurrentHistoryState() {
/* return CalculatorHistoryState.newInstance(getEditor(), getDisplay());
********************************************************************** }
*
* OTHER /*
* **********************************************************************
********************************************************************** *
*/ * OTHER
*
@NotNull **********************************************************************
private CalculatorEditor getEditor() { */
return CalculatorLocatorImpl.getInstance().getEditor();
} @NotNull
private CalculatorEditor getEditor() {
@NotNull return CalculatorLocatorImpl.getInstance().getEditor();
private CalculatorDisplay getDisplay() { }
return CalculatorLocatorImpl.getInstance().getDisplay();
} @NotNull
} private CalculatorDisplay getDisplay() {
return CalculatorLocatorImpl.getInstance().getDisplay();
}
}

View File

@ -8,10 +8,12 @@ package org.solovyev.android.calculator;
import android.content.Context; import android.content.Context;
import android.graphics.Color; import android.graphics.Color;
import android.os.Handler; import android.os.Handler;
import android.text.Editable;
import android.text.Html; import android.text.Html;
import android.text.TextWatcher;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.android.calculator.text.TextProcessor; import org.solovyev.android.calculator.text.TextProcessor;
import org.solovyev.android.calculator.view.TextHighlighter; import org.solovyev.android.calculator.view.TextHighlighter;
import org.solovyev.android.view.AutoResizeTextView; import org.solovyev.android.view.AutoResizeTextView;
@ -43,7 +45,12 @@ public class AndroidCalculatorDisplayView extends AutoResizeTextView implements
*/ */
@NotNull @NotNull
private CalculatorDisplayViewState state = CalculatorDisplayViewStateImpl.newDefaultInstance(); private volatile CalculatorDisplayViewState state = CalculatorDisplayViewStateImpl.newDefaultInstance();
private volatile boolean viewStateChange = false;
@NotNull
private final Object lock = new Object();
@NotNull @NotNull
private final Handler handler = new Handler(); private final Handler handler = new Handler();
@ -58,14 +65,18 @@ public class AndroidCalculatorDisplayView extends AutoResizeTextView implements
public AndroidCalculatorDisplayView(Context context) { public AndroidCalculatorDisplayView(Context context) {
super(context); super(context);
this.addTextChangedListener(new TextWatcherImpl());
} }
public AndroidCalculatorDisplayView(Context context, AttributeSet attrs) { public AndroidCalculatorDisplayView(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
this.addTextChangedListener(new TextWatcherImpl());
} }
public AndroidCalculatorDisplayView(Context context, AttributeSet attrs, int defStyle) { public AndroidCalculatorDisplayView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle); super(context, attrs, defStyle);
this.addTextChangedListener(new TextWatcherImpl());
} }
/* /*
@ -76,63 +87,103 @@ public class AndroidCalculatorDisplayView extends AutoResizeTextView implements
********************************************************************** **********************************************************************
*/ */
public boolean isValid() {
synchronized (this) {
return this.state.isValid();
}
}
@Override @Override
public void setState(@NotNull final CalculatorDisplayViewState state) { public void setState(@NotNull final CalculatorDisplayViewState state) {
handler.postDelayed(new Runnable() { final CharSequence text = prepareText(state.getStringResult(), state.isValid());
handler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
synchronized (AndroidCalculatorDisplayView.this) { synchronized (lock) {
AndroidCalculatorDisplayView.this.state = state; try {
if ( state.isValid() ) { viewStateChange = true;
setTextColor(getResources().getColor(R.color.default_text_color));
setText(state.getStringResult());
redraw();
} else {
setTextColor(getResources().getColor(R.color.display_error_text_color));
// error messages are never shown -> just greyed out text (error message will be shown on click) AndroidCalculatorDisplayView.this.state = state;
//setText(state.getErrorMessage()); if (state.isValid()) {
//redraw(); setTextColor(getResources().getColor(R.color.default_text_color));
setText(text);
adjustTextSize();
} else {
// update text in order to get rid of HTML tags
setText(getText().toString());
setTextColor(getResources().getColor(R.color.display_error_text_color));
// error messages are never shown -> just greyed out text (error message will be shown on click)
//setText(state.getErrorMessage());
//redraw();
}
} finally {
viewStateChange = false;
} }
} }
} }
}, 1); });
} }
@NotNull @NotNull
@Override @Override
public CalculatorDisplayViewState getState() { public CalculatorDisplayViewState getState() {
synchronized (this) { synchronized (lock) {
return this.state; return this.state;
} }
} }
private synchronized void redraw() { @Nullable
if (isValid()) { private static CharSequence prepareText(@Nullable String text, boolean valid) {
String text = getText().toString(); CharSequence result;
Log.d(this.getClass().getName(), text); if (valid && text != null) {
//Log.d(this.getClass().getName(), text);
try { try {
TextHighlighter.Result result = textHighlighter.process(text); final TextHighlighter.Result processedText = textHighlighter.process(text);
text = result.toString(); text = processedText.toString();
result = Html.fromHtml(text);
} catch (CalculatorParseException e) { } catch (CalculatorParseException e) {
Log.e(this.getClass().getName(), e.getMessage(), e); result = text;
} }
} else {
Log.d(this.getClass().getName(), text); result = text;
super.setText(Html.fromHtml(text), BufferType.EDITABLE);
} }
return result;
}
private void adjustTextSize() {
// todo serso: think where to move it (keep in mind org.solovyev.android.view.AutoResizeTextView.resetTextSize()) // todo serso: think where to move it (keep in mind org.solovyev.android.view.AutoResizeTextView.resetTextSize())
setAddEllipsis(false); setAddEllipsis(false);
setMinTextSize(10); setMinTextSize(10);
resizeText(); resizeText();
} }
public void handleTextChange(Editable s) {
synchronized (lock) {
if (!viewStateChange) {
// external text change => need to notify display
// todo serso: implement
}
}
}
private final class TextWatcherImpl implements TextWatcher {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
handleTextChange(s);
}
}
} }

View File

@ -10,10 +10,15 @@ import android.content.SharedPreferences;
import android.graphics.Color; import android.graphics.Color;
import android.os.Build; import android.os.Build;
import android.os.Handler; import android.os.Handler;
import android.text.Editable;
import android.text.Html;
import android.text.TextWatcher;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log;
import android.view.ContextMenu; import android.view.ContextMenu;
import android.widget.EditText; import android.widget.EditText;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.solovyev.android.calculator.text.TextProcessor; import org.solovyev.android.calculator.text.TextProcessor;
import org.solovyev.android.calculator.view.TextHighlighter; import org.solovyev.android.calculator.view.TextHighlighter;
import org.solovyev.common.collections.CollectionsUtils; import org.solovyev.common.collections.CollectionsUtils;
@ -25,32 +30,39 @@ import org.solovyev.common.collections.CollectionsUtils;
*/ */
public class AndroidCalculatorEditorView extends EditText implements SharedPreferences.OnSharedPreferenceChangeListener, CalculatorEditorView { public class AndroidCalculatorEditorView extends EditText implements SharedPreferences.OnSharedPreferenceChangeListener, CalculatorEditorView {
private static final String CALC_COLOR_DISPLAY_KEY = "org.solovyev.android.calculator.CalculatorModel_color_display"; 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 static final boolean CALC_COLOR_DISPLAY_DEFAULT = true;
private boolean highlightText = true; private boolean highlightText = true;
@NotNull @NotNull
private final static TextProcessor<TextHighlighter.Result, String> textHighlighter = new TextHighlighter(Color.WHITE, true); private final static TextProcessor<TextHighlighter.Result, String> textHighlighter = new TextHighlighter(Color.WHITE, false);
@NotNull @NotNull
private volatile CalculatorEditorViewState viewState = CalculatorEditorViewStateImpl.newDefaultInstance(); private volatile CalculatorEditorViewState viewState = CalculatorEditorViewStateImpl.newDefaultInstance();
private volatile boolean viewStateChange = false; private volatile boolean viewStateChange = false;
// NOTE: static because super constructor calls some overridden methods (like onSelectionChanged and current lock is not yet created)
@NotNull
private static final Object lock = new Object();
@NotNull @NotNull
private final Handler handler = new Handler(); private final Handler handler = new Handler();
public AndroidCalculatorEditorView(Context context) { public AndroidCalculatorEditorView(Context context) {
super(context); super(context);
} this.addTextChangedListener(new TextWatcherImpl());
}
public AndroidCalculatorEditorView(Context context, AttributeSet attrs) { public AndroidCalculatorEditorView(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
this.addTextChangedListener(new TextWatcherImpl());
} }
public AndroidCalculatorEditorView(Context context, AttributeSet attrs, int defStyle) { public AndroidCalculatorEditorView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle); super(context, attrs, defStyle);
this.addTextChangedListener(new TextWatcherImpl());
} }
@ -59,12 +71,12 @@ public class AndroidCalculatorEditorView extends EditText implements SharedPrefe
// NOTE: code below can be used carefully and should not be copied without special intention // 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 // 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 ) { if (Build.VERSION.SDK_INT >= 11) {
// fix for missing cursor in android 3 and higher // fix for missing cursor in android 3 and higher
try { try {
// IDEA: return false always except if method was called from TextView.isCursorVisible() method // IDEA: return false always except if method was called from TextView.isCursorVisible() method
for (StackTraceElement stackTraceElement : CollectionsUtils.asList(Thread.currentThread().getStackTrace())) { for (StackTraceElement stackTraceElement : CollectionsUtils.asList(Thread.currentThread().getStackTrace())) {
if ( "isCursorVisible".equals(stackTraceElement.getMethodName()) ) { if ("isCursorVisible".equals(stackTraceElement.getMethodName())) {
return true; return true;
} }
} }
@ -78,94 +90,115 @@ public class AndroidCalculatorEditorView extends EditText implements SharedPrefe
} }
} }
@Override @Override
protected void onCreateContextMenu(ContextMenu menu) { protected void onCreateContextMenu(ContextMenu menu) {
super.onCreateContextMenu(menu); super.onCreateContextMenu(menu);
menu.removeItem(android.R.id.selectAll); menu.removeItem(android.R.id.selectAll);
} }
// todo serso: fix redraw @Nullable
// Now problem is that calculator editor cursor position might be different than position of cursor in view (as some extra spaces can be inserted fur to number formatting) private CharSequence prepareText(@NotNull String text, boolean highlightText) {
/*private synchronized void redraw() { CharSequence result;
String text = getText().toString();
int selectionStart = getSelectionStart(); if (highlightText) {
int selectionEnd = getSelectionEnd();
if (highlightText) { try {
final TextHighlighter.Result processesText = textHighlighter.process(text);
Log.d(this.getClass().getName(), text); assert processesText.getOffset() == 0;
try { result = Html.fromHtml(processesText.toString());
final TextHighlighter.Result result = textHighlighter.process(text); } catch (CalculatorParseException e) {
selectionStart += result.getOffset(); // set raw text
selectionEnd += result.getOffset(); result = text;
text = result.toString();
} catch (CalculatorParseException e) {
Log.e(this.getClass().getName(), e.getMessage(), e);
}
Log.d(this.getClass().getName(), text); Log.e(this.getClass().getName(), e.getMessage(), e);
super.setText(Html.fromHtml(text), BufferType.EDITABLE); }
} else { } else {
super.setText(text, BufferType.EDITABLE); result = text;
} }
Log.d(this.getClass().getName(), getText().toString()); return result;
}
int length = getText().length(); public boolean isHighlightText() {
setSelection(Math.max(Math.min(length, selectionStart), 0), Math.max(Math.min(length, selectionEnd), 0)); return highlightText;
}*/ }
public boolean isHighlightText() { public void setHighlightText(boolean highlightText) {
return highlightText; this.highlightText = highlightText;
} CalculatorLocatorImpl.getInstance().getEditor().updateViewState();
}
public void setHighlightText(boolean highlightText) { @Override
this.highlightText = highlightText; public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
//redraw(); if (CALC_COLOR_DISPLAY_KEY.equals(key)) {
} this.setHighlightText(preferences.getBoolean(CALC_COLOR_DISPLAY_KEY, CALC_COLOR_DISPLAY_DEFAULT));
}
}
@Override public void init(@NotNull SharedPreferences preferences) {
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) { onSharedPreferenceChanged(preferences, CALC_COLOR_DISPLAY_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);
}
@Override @Override
public void setState(@NotNull final CalculatorEditorViewState viewState) { public void setState(@NotNull final CalculatorEditorViewState viewState) {
handler.postDelayed(new Runnable() {
final CharSequence text = prepareText(viewState.getText(), highlightText);
handler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
final AndroidCalculatorEditorView editorView = AndroidCalculatorEditorView.this; final AndroidCalculatorEditorView editorView = AndroidCalculatorEditorView.this;
synchronized (editorView) { synchronized (lock) {
try { try {
editorView.viewStateChange = true; editorView.viewStateChange = true;
editorView.viewState = viewState; editorView.viewState = viewState;
editorView.setText(viewState.getText()); editorView.setText(text, BufferType.EDITABLE);
editorView.setSelection(viewState.getSelection()); editorView.setSelection(viewState.getSelection());
//redraw();
} finally { } finally {
editorView.viewStateChange = false; editorView.viewStateChange = false;
} }
} }
} }
}, 1); });
} }
@Override @Override
protected void onSelectionChanged(int selStart, int selEnd) { protected void onSelectionChanged(int selStart, int selEnd) {
synchronized (this) { synchronized (lock) {
if (!viewStateChange) { if (!viewStateChange) {
// external text change => need to notify editor
super.onSelectionChanged(selStart, selEnd); super.onSelectionChanged(selStart, selEnd);
CalculatorLocatorImpl.getInstance().getEditor().setSelection(selStart); CalculatorLocatorImpl.getInstance().getEditor().setSelection(selStart);
} }
} }
} }
public void handleTextChange(Editable s) {
synchronized (lock) {
if (!viewStateChange) {
// external text change => need to notify editor
CalculatorLocatorImpl.getInstance().getEditor().setText(String.valueOf(s));
}
}
}
private final class TextWatcherImpl implements TextWatcher {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
handleTextChange(s);
}
}
} }