diff --git a/calculatorpp-core/src/main/java/org/solovyev/android/calculator/Calculator.java b/calculatorpp-core/src/main/java/org/solovyev/android/calculator/Calculator.java index 433c2163..28b2ae45 100644 --- a/calculatorpp-core/src/main/java/org/solovyev/android/calculator/Calculator.java +++ b/calculatorpp-core/src/main/java/org/solovyev/android/calculator/Calculator.java @@ -1,30 +1,25 @@ -package org.solovyev.android.calculator; - -import jscl.NumeralBase; -import jscl.math.Generic; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.solovyev.android.calculator.jscl.JsclOperation; -import org.solovyev.common.msg.MessageRegistry; - -/** - * User: Solovyev_S - * Date: 20.09.12 - * Time: 16:38 - */ -public interface Calculator extends CalculatorEventContainer { - - @NotNull - CalculatorEventDataId createFirstEventDataId(); - - void evaluate(@NotNull JsclOperation operation, - @NotNull String expression); - - @NotNull - CalculatorEventDataId evaluate(@NotNull JsclOperation operation, - @NotNull String expression, - @Nullable MessageRegistry mr); - - @NotNull - CalculatorEventDataId convert(@NotNull Generic generic, @NotNull NumeralBase to); -} +package org.solovyev.android.calculator; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.solovyev.android.calculator.jscl.JsclOperation; +import org.solovyev.common.msg.MessageRegistry; + +/** + * User: Solovyev_S + * Date: 20.09.12 + * Time: 16:38 + */ +public interface Calculator extends CalculatorEventContainer { + + @NotNull + CalculatorEventDataId createFirstEventDataId(); + + void evaluate(@NotNull JsclOperation operation, + @NotNull String expression); + + @NotNull + CalculatorEventDataId evaluate(@NotNull JsclOperation operation, + @NotNull String expression, + @Nullable MessageRegistry mr); +} diff --git a/calculatorpp-core/src/main/java/org/solovyev/android/calculator/CalculatorEvaluationEventDataImpl.java b/calculatorpp-core/src/main/java/org/solovyev/android/calculator/CalculatorEvaluationEventDataImpl.java index 9df965a0..f40cd6cf 100644 --- a/calculatorpp-core/src/main/java/org/solovyev/android/calculator/CalculatorEvaluationEventDataImpl.java +++ b/calculatorpp-core/src/main/java/org/solovyev/android/calculator/CalculatorEvaluationEventDataImpl.java @@ -1,58 +1,58 @@ -package org.solovyev.android.calculator; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.solovyev.android.calculator.jscl.JsclOperation; - -/** - * User: serso - * Date: 9/20/12 - * Time: 10:01 PM - */ -public class CalculatorEvaluationEventDataImpl implements CalculatorEvaluationEventData { - - @NotNull - private final CalculatorEventData calculatorEventData; - - @NotNull - private final JsclOperation operation; - - @NotNull - private final String expression; - - public CalculatorEvaluationEventDataImpl(@NotNull CalculatorEventData calculatorEventData, - @NotNull JsclOperation operation, - @NotNull String expression) { - this.calculatorEventData = calculatorEventData; - this.operation = operation; - this.expression = expression; - } - - @NotNull - @Override - public JsclOperation getOperation() { - return this.operation; - } - - @NotNull - @Override - public String getExpression() { - return this.expression; - } - - @Override - public long getEventId() { - return calculatorEventData.getEventId(); - } - - @Override - @Nullable - public Long getSequenceId() { - return calculatorEventData.getSequenceId(); - } - - @Override - public boolean isAfter(@NotNull CalculatorEventDataId calculatorEventDataId) { - return calculatorEventData.isAfter(calculatorEventDataId); - } -} +package org.solovyev.android.calculator; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.solovyev.android.calculator.jscl.JsclOperation; + +/** + * User: serso + * Date: 9/20/12 + * Time: 10:01 PM + */ +public class CalculatorEvaluationEventDataImpl implements CalculatorEvaluationEventData { + + @NotNull + private final CalculatorEventData calculatorEventData; + + @NotNull + private final JsclOperation operation; + + @NotNull + private final String expression; + + public CalculatorEvaluationEventDataImpl(@NotNull CalculatorEventData calculatorEventData, + @NotNull JsclOperation operation, + @NotNull String expression) { + this.calculatorEventData = calculatorEventData; + this.operation = operation; + this.expression = expression; + } + + @NotNull + @Override + public JsclOperation getOperation() { + return this.operation; + } + + @NotNull + @Override + public String getExpression() { + return this.expression; + } + + @Override + public long getEventId() { + return calculatorEventData.getEventId(); + } + + @Override + @Nullable + public Long getCalculationId() { + return calculatorEventData.getCalculationId(); + } + + @Override + public boolean isAfter(@NotNull CalculatorEventDataId calculatorEventDataId) { + return calculatorEventData.isAfter(calculatorEventDataId); + } +} diff --git a/calculatorpp-core/src/main/java/org/solovyev/android/calculator/CalculatorEventDataId.java b/calculatorpp-core/src/main/java/org/solovyev/android/calculator/CalculatorEventDataId.java index 783d82d6..f77558cd 100644 --- a/calculatorpp-core/src/main/java/org/solovyev/android/calculator/CalculatorEventDataId.java +++ b/calculatorpp-core/src/main/java/org/solovyev/android/calculator/CalculatorEventDataId.java @@ -1,21 +1,20 @@ -package org.solovyev.android.calculator; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * User: Solovyev_S - * Date: 20.09.12 - * Time: 18:18 - */ -public interface CalculatorEventDataId { - - // the higher id => the later event - long getEventId(); - - // the higher id => the later event - @Nullable - Long getSequenceId(); - - boolean isAfter(@NotNull CalculatorEventDataId calculatorEventDataId); -} +package org.solovyev.android.calculator; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * User: Solovyev_S + * Date: 20.09.12 + * Time: 18:18 + */ +public interface CalculatorEventDataId { + + // the higher id => the later event + long getEventId(); + + @Nullable + Long getCalculationId(); + + boolean isAfter(@NotNull CalculatorEventDataId calculatorEventDataId); +} diff --git a/calculatorpp-core/src/main/java/org/solovyev/android/calculator/CalculatorEventDataIdImpl.java b/calculatorpp-core/src/main/java/org/solovyev/android/calculator/CalculatorEventDataIdImpl.java index ad2e2ca9..0eb8d4a0 100644 --- a/calculatorpp-core/src/main/java/org/solovyev/android/calculator/CalculatorEventDataIdImpl.java +++ b/calculatorpp-core/src/main/java/org/solovyev/android/calculator/CalculatorEventDataIdImpl.java @@ -1,64 +1,66 @@ -package org.solovyev.android.calculator; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * User: Solovyev_S - * Date: 20.09.12 - * Time: 18:18 - */ -class CalculatorEventDataIdImpl implements CalculatorEventDataId { - - private final long eventId; - - @Nullable - private final Long sequenceId; - - private CalculatorEventDataIdImpl(long id, @Nullable Long sequenceId) { - this.eventId = id; - this.sequenceId = sequenceId; - } - - @NotNull - static CalculatorEventDataId newInstance(long id, @Nullable Long sequenceId) { - return new CalculatorEventDataIdImpl(id, sequenceId); - } - - @Override - public long getEventId() { - return this.eventId; - } - - @Nullable - @Override - public Long getSequenceId() { - return this.sequenceId; - } - - @Override - public boolean isAfter(@NotNull CalculatorEventDataId calculatorEventDataId) { - return this.eventId > calculatorEventDataId.getEventId(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof CalculatorEventDataIdImpl)) return false; - - CalculatorEventDataIdImpl that = (CalculatorEventDataIdImpl) o; - - if (eventId != that.eventId) return false; - if (sequenceId != null ? !sequenceId.equals(that.sequenceId) : that.sequenceId != null) - return false; - - return true; - } - - @Override - public int hashCode() { - int result = (int) (eventId ^ (eventId >>> 32)); - result = 31 * result + (sequenceId != null ? sequenceId.hashCode() : 0); - return result; - } -} +package org.solovyev.android.calculator; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * User: Solovyev_S + * Date: 20.09.12 + * Time: 18:18 + */ +class CalculatorEventDataIdImpl implements CalculatorEventDataId { + + private final long eventId; + + @Nullable + private final Long calculationId; + + private CalculatorEventDataIdImpl(long id, + @Nullable Long calculationId) { + this.eventId = id; + this.calculationId = calculationId; + } + + @NotNull + static CalculatorEventDataId newInstance(long id, + @Nullable Long calculationId) { + return new CalculatorEventDataIdImpl(id, calculationId); + } + + @Override + public long getEventId() { + return this.eventId; + } + + @Nullable + @Override + public Long getCalculationId() { + return this.calculationId; + } + + @Override + public boolean isAfter(@NotNull CalculatorEventDataId calculatorEventDataId) { + return this.eventId > calculatorEventDataId.getEventId(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof CalculatorEventDataIdImpl)) return false; + + CalculatorEventDataIdImpl that = (CalculatorEventDataIdImpl) o; + + if (eventId != that.eventId) return false; + if (calculationId != null ? !calculationId.equals(that.calculationId) : that.calculationId != null) + return false; + + return true; + } + + @Override + public int hashCode() { + int result = (int) (eventId ^ (eventId >>> 32)); + result = 31 * result + (calculationId != null ? calculationId.hashCode() : 0); + return result; + } +} diff --git a/calculatorpp-core/src/main/java/org/solovyev/android/calculator/CalculatorEventDataImpl.java b/calculatorpp-core/src/main/java/org/solovyev/android/calculator/CalculatorEventDataImpl.java index 34b37d2b..4f7151ce 100644 --- a/calculatorpp-core/src/main/java/org/solovyev/android/calculator/CalculatorEventDataImpl.java +++ b/calculatorpp-core/src/main/java/org/solovyev/android/calculator/CalculatorEventDataImpl.java @@ -1,57 +1,57 @@ -package org.solovyev.android.calculator; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * User: Solovyev_S - * Date: 20.09.12 - * Time: 16:54 - */ -class CalculatorEventDataImpl implements CalculatorEventData { - - @NotNull - private CalculatorEventDataId calculatorEventDataId; - - private CalculatorEventDataImpl(@NotNull CalculatorEventDataId calculatorEventDataId) { - this.calculatorEventDataId = calculatorEventDataId; - } - - @NotNull - public static CalculatorEventData newInstance(@NotNull CalculatorEventDataId calculatorEventDataId) { - return new CalculatorEventDataImpl(calculatorEventDataId); - } - - @Override - public long getEventId() { - return calculatorEventDataId.getEventId(); - } - - @Override - @Nullable - public Long getSequenceId() { - return calculatorEventDataId.getSequenceId(); - } - - @Override - public boolean isAfter(@NotNull CalculatorEventDataId calculatorEventDataId) { - return this.calculatorEventDataId.isAfter(calculatorEventDataId); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof CalculatorEventDataImpl)) return false; - - CalculatorEventDataImpl that = (CalculatorEventDataImpl) o; - - if (!calculatorEventDataId.equals(that.calculatorEventDataId)) return false; - - return true; - } - - @Override - public int hashCode() { - return calculatorEventDataId.hashCode(); - } -} +package org.solovyev.android.calculator; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * User: Solovyev_S + * Date: 20.09.12 + * Time: 16:54 + */ +class CalculatorEventDataImpl implements CalculatorEventData { + + @NotNull + private CalculatorEventDataId calculatorEventDataId; + + private CalculatorEventDataImpl(@NotNull CalculatorEventDataId calculatorEventDataId) { + this.calculatorEventDataId = calculatorEventDataId; + } + + @NotNull + public static CalculatorEventData newInstance(@NotNull CalculatorEventDataId calculatorEventDataId) { + return new CalculatorEventDataImpl(calculatorEventDataId); + } + + @Override + public long getEventId() { + return calculatorEventDataId.getEventId(); + } + + @Override + @Nullable + public Long getCalculationId() { + return calculatorEventDataId.getCalculationId(); + } + + @Override + public boolean isAfter(@NotNull CalculatorEventDataId calculatorEventDataId) { + return this.calculatorEventDataId.isAfter(calculatorEventDataId); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof CalculatorEventDataImpl)) return false; + + CalculatorEventDataImpl that = (CalculatorEventDataImpl) o; + + if (!calculatorEventDataId.equals(that.calculatorEventDataId)) return false; + + return true; + } + + @Override + public int hashCode() { + return calculatorEventDataId.hashCode(); + } +} diff --git a/calculatorpp-core/src/main/java/org/solovyev/android/calculator/CalculatorEventType.java b/calculatorpp-core/src/main/java/org/solovyev/android/calculator/CalculatorEventType.java index 1fd179b6..95568893 100644 --- a/calculatorpp-core/src/main/java/org/solovyev/android/calculator/CalculatorEventType.java +++ b/calculatorpp-core/src/main/java/org/solovyev/android/calculator/CalculatorEventType.java @@ -1,55 +1,43 @@ -package org.solovyev.android.calculator; - -import org.jetbrains.annotations.NotNull; - -/** - * User: Solovyev_S - * Date: 20.09.12 - * Time: 16:40 - */ -public enum CalculatorEventType { - - /* - ********************************************************************** - * - * org.solovyev.android.calculator.CalculatorEvaluationEventData - * - ********************************************************************** - */ - - // @NotNull org.solovyev.android.calculator.CalculatorInput - calculation_started, - - // @NotNull org.solovyev.android.calculator.CalculatorOutput - calculation_result, - - calculation_cancelled, - - calculation_finished, - - // @NotNull org.solovyev.android.calculator.CalculatorFailure - calculation_failed, - - /* - ********************************************************************** - * - * CONVERSION - * - ********************************************************************** - */ - conversion_started, - - // @NotNull String conversion result - conversion_finished; - - public boolean isOfType(@NotNull CalculatorEventType... types) { - for (CalculatorEventType type : types) { - if ( this == type ) { - return true; - } - } - - return false; - } - -} +package org.solovyev.android.calculator; + +import org.jetbrains.annotations.NotNull; + +/** + * User: Solovyev_S + * Date: 20.09.12 + * Time: 16:40 + */ +public enum CalculatorEventType { + + /* + ********************************************************************** + * + * org.solovyev.android.calculator.CalculatorEvaluationEventData + * + ********************************************************************** + */ + + // @NotNull org.solovyev.android.calculator.CalculatorInput + calculation_started, + + // @NotNull org.solovyev.android.calculator.CalculatorOutput + calculation_result, + + calculation_cancelled, + + calculation_finished, + + // @NotNull org.solovyev.android.calculator.CalculatorFailure + calculation_failed; + + public boolean isOfType(@NotNull CalculatorEventType... types) { + for (CalculatorEventType type : types) { + if ( this == type ) { + return true; + } + } + + return false; + } + +} diff --git a/calculatorpp-core/src/main/java/org/solovyev/android/calculator/CalculatorImpl.java b/calculatorpp-core/src/main/java/org/solovyev/android/calculator/CalculatorImpl.java index 3db62bfb..1e299d59 100644 --- a/calculatorpp-core/src/main/java/org/solovyev/android/calculator/CalculatorImpl.java +++ b/calculatorpp-core/src/main/java/org/solovyev/android/calculator/CalculatorImpl.java @@ -1,291 +1,209 @@ -package org.solovyev.android.calculator; - -import jscl.AbstractJsclArithmeticException; -import jscl.NumeralBase; -import jscl.NumeralBaseException; -import jscl.math.Generic; -import jscl.text.ParseInterruptedException; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.solovyev.android.calculator.jscl.JsclOperation; -import org.solovyev.android.calculator.text.TextProcessor; -import org.solovyev.common.msg.MessageRegistry; -import org.solovyev.common.msg.MessageType; -import org.solovyev.common.text.StringUtils; -import org.solovyev.math.units.UnitConverter; -import org.solovyev.math.units.UnitImpl; -import org.solovyev.math.units.UnitType; - -import java.util.List; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; -import java.util.concurrent.atomic.AtomicLong; - -/** - * User: Solovyev_S - * Date: 20.09.12 - * Time: 16:42 - */ -public class CalculatorImpl implements Calculator { - - private static final long FIRST_ID = 0; - - @NotNull - private final CalculatorEventContainer calculatorEventContainer = new ListCalculatorEventContainer(); - - @NotNull - private final AtomicLong counter = new AtomicLong(FIRST_ID); - - @NotNull - private final Object lock = new Object(); - - @NotNull - private final TextProcessor preprocessor = ToJsclTextProcessor.getInstance(); - - @NotNull - private final Executor threadPoolExecutor = Executors.newFixedThreadPool(10); - - public CalculatorImpl() { - } - - @NotNull - public static String doConversion(@NotNull UnitConverter converter, - @Nullable String from, - @NotNull UnitType fromUnitType, - @NotNull UnitType toUnitType) throws ConversionException{ - final String result; - - if (StringUtils.isEmpty(from)) { - result = ""; - } else { - - String to = null; - try { - if (converter.isSupported(fromUnitType, toUnitType)) { - to = converter.convert(UnitImpl.newInstance(from, fromUnitType), toUnitType).getValue(); - } - } catch (RuntimeException e) { - throw new ConversionException(e); - } - - result = to; - } - - return result; - } - - @NotNull - private CalculatorEventDataId nextCalculatorEventDataId() { - long eventId = counter.incrementAndGet(); - return CalculatorEventDataIdImpl.newInstance(eventId, eventId); - } - - @NotNull - private CalculatorEventDataId nextEventDataId(@NotNull Long sequenceId) { - long eventId = counter.incrementAndGet(); - return CalculatorEventDataIdImpl.newInstance(eventId, sequenceId); - } - - /* - ********************************************************************** - * - * CALCULATION - * - ********************************************************************** - */ - - @NotNull - @Override - public CalculatorEventDataId createFirstEventDataId() { - return CalculatorEventDataIdImpl.newInstance(FIRST_ID, FIRST_ID); - } - - @Override - public void evaluate(@NotNull JsclOperation operation, - @NotNull String expression) { - evaluate(operation, expression, null); - } - - @Override - @NotNull - public CalculatorEventDataId evaluate(@NotNull final JsclOperation operation, - @NotNull final String expression, - @Nullable final MessageRegistry mr) { - - final CalculatorEventDataId eventDataId = nextCalculatorEventDataId(); - - threadPoolExecutor.execute(new Runnable() { - @Override - public void run() { - CalculatorImpl.this.evaluate(eventDataId.getSequenceId(), operation, expression, mr); - } - }); - - return eventDataId; - } - - @NotNull - @Override - public CalculatorEventDataId convert(@NotNull final Generic generic, - @NotNull final NumeralBase to) { - final CalculatorEventDataId eventDataId = nextCalculatorEventDataId(); - - threadPoolExecutor.execute(new Runnable() { - @Override - public void run() { - final Long sequenceId = eventDataId.getSequenceId(); - assert sequenceId != null; - - fireCalculatorEvent(newConversionEventData(sequenceId), CalculatorEventType.conversion_started, null); - - final NumeralBase from = CalculatorLocatorImpl.getInstance().getCalculatorEngine().getEngine().getNumeralBase(); - - if (from != to) { - String fromString = generic.toString(); - if (!StringUtils.isEmpty(fromString)) { - try { - fromString = ToJsclTextProcessor.getInstance().process(fromString).getExpression(); - } catch (CalculatorParseException e) { - // ok, problems while processing occurred - } - } - - // todo serso: continue - //doConversion(AndroidNumeralBase.getConverter(), fromString, AndroidNumeralBase.valueOf(fromString), AndroidNumeralBase.valueOf(to)); - } else { - fireCalculatorEvent(newConversionEventData(sequenceId), CalculatorEventType.conversion_finished, generic.toString()); - } - } - }); - - return eventDataId; - } - - @NotNull - private CalculatorEventData newConversionEventData(@NotNull Long sequenceId) { - return CalculatorEventDataImpl.newInstance(nextEventDataId(sequenceId)); - } - - private void evaluate(@NotNull Long sequenceId, - @NotNull JsclOperation operation, - @NotNull String expression, - @Nullable MessageRegistry mr) { - synchronized (lock) { - - PreparedExpression preparedExpression = null; - - fireCalculatorEvent(newCalculationEventData(operation, expression, sequenceId), CalculatorEventType.calculation_started, new CalculatorInputImpl(expression, operation)); - - try { - preparedExpression = preprocessor.process(expression); - - final String jsclExpression = preparedExpression.toString(); - - try { - - final Generic result = operation.evaluateGeneric(jsclExpression); - - // NOTE: toString() method must be called here as ArithmeticOperationException may occur in it (just to avoid later check!) - result.toString(); - - final CalculatorOutputImpl data = new CalculatorOutputImpl(operation.getFromProcessor().process(result), operation, result); - 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 (jscl.text.ParseException e) { - handleException(sequenceId, operation, expression, mr, preparedExpression, new CalculatorParseException(e)); - } catch (ParseInterruptedException e) { - - // do nothing - we ourselves interrupt the calculations - fireCalculatorEvent(newCalculationEventData(operation, expression, sequenceId), CalculatorEventType.calculation_cancelled, 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) { - return new CalculatorEvaluationEventDataImpl(CalculatorEventDataImpl.newInstance(nextEventDataId(calculationId)), operation, expression); - } - - private void handleException(@NotNull Long calculationId, - @NotNull JsclOperation operation, - @NotNull String expression, - @Nullable MessageRegistry mr, - @Nullable PreparedExpression preparedExpression, - @NotNull CalculatorParseException parseException) { - - if (operation == JsclOperation.numeric - && preparedExpression != null - && preparedExpression.isExistsUndefinedVar()) { - - evaluate(calculationId, JsclOperation.simplify, expression, mr); - - } - - fireCalculatorEvent(newCalculationEventData(operation, expression, calculationId), CalculatorEventType.calculation_failed, new CalculatorFailureImpl(parseException)); - } - - private void handleException(@NotNull Long calculationId, - @NotNull JsclOperation operation, - @NotNull String expression, - @Nullable MessageRegistry mr, - @NotNull CalculatorEvalException evalException) { - - if (operation == JsclOperation.numeric && evalException.getCause() instanceof NumeralBaseException) { - evaluate(calculationId, JsclOperation.simplify, expression, mr); - } - - fireCalculatorEvent(newCalculationEventData(operation, expression, calculationId), CalculatorEventType.calculation_failed, new CalculatorFailureImpl(evalException)); - } - - /* - ********************************************************************** - * - * EVENTS - * - ********************************************************************** - */ - - @Override - public void addCalculatorEventListener(@NotNull CalculatorEventListener calculatorEventListener) { - calculatorEventContainer.addCalculatorEventListener(calculatorEventListener); - } - - @Override - public void removeCalculatorEventListener(@NotNull CalculatorEventListener calculatorEventListener) { - calculatorEventContainer.removeCalculatorEventListener(calculatorEventListener); - } - - @Override - public void fireCalculatorEvent(@NotNull CalculatorEventData calculatorEventData, @NotNull CalculatorEventType calculatorEventType, @Nullable Object data) { - calculatorEventContainer.fireCalculatorEvent(calculatorEventData, calculatorEventType, data); - } - - @Override - public void fireCalculatorEvents(@NotNull List calculatorEvents) { - calculatorEventContainer.fireCalculatorEvents(calculatorEvents); - } - - public static final class ConversionException extends Exception { - private ConversionException() { - } - - private ConversionException(Throwable throwable) { - super(throwable); - } - } -} +package org.solovyev.android.calculator; + +import jscl.AbstractJsclArithmeticException; +import jscl.NumeralBaseException; +import jscl.math.Generic; +import jscl.text.ParseInterruptedException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.solovyev.android.calculator.jscl.JsclOperation; +import org.solovyev.android.calculator.text.TextProcessor; +import org.solovyev.common.msg.MessageRegistry; +import org.solovyev.common.msg.MessageType; + +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicLong; + +/** + * User: Solovyev_S + * Date: 20.09.12 + * Time: 16:42 + */ +public class CalculatorImpl implements Calculator { + + private static final long FIRST_ID = 0; + + @NotNull + private final CalculatorEventContainer calculatorEventContainer = new ListCalculatorEventContainer(); + + @NotNull + private final AtomicLong counter = new AtomicLong(FIRST_ID); + + @NotNull + private final Object lock = new Object(); + + @NotNull + private final TextProcessor preprocessor = ToJsclTextProcessor.getInstance(); + + @NotNull + private final Executor threadPoolExecutor = Executors.newFixedThreadPool(10); + + public CalculatorImpl() { + } + + @NotNull + private CalculatorEventDataId nextCalculatorEventDataId() { + long eventId = counter.incrementAndGet(); + return CalculatorEventDataIdImpl.newInstance(eventId, eventId); + } + + @NotNull + private CalculatorEventDataId nextEventDataId(@NotNull Long calculationId) { + long eventId = counter.incrementAndGet(); + return CalculatorEventDataIdImpl.newInstance(eventId, calculationId); + } + + /* + ********************************************************************** + * + * CALCULATION + * + ********************************************************************** + */ + + @NotNull + @Override + public CalculatorEventDataId createFirstEventDataId() { + return CalculatorEventDataIdImpl.newInstance(FIRST_ID, FIRST_ID); + } + + @Override + public void evaluate(@NotNull JsclOperation operation, + @NotNull String expression) { + evaluate(operation, expression, null); + } + + @Override + @NotNull + public CalculatorEventDataId evaluate(@NotNull final JsclOperation operation, + @NotNull final String expression, + @Nullable final MessageRegistry mr) { + + final CalculatorEventDataId eventDataId = nextCalculatorEventDataId(); + + threadPoolExecutor.execute(new Runnable() { + @Override + public void run() { + CalculatorImpl.this.evaluate(eventDataId.getCalculationId(), operation, expression, mr); + } + }); + + return eventDataId; + } + + private void evaluate(@NotNull Long calculationId, + @NotNull JsclOperation operation, + @NotNull String expression, + @Nullable MessageRegistry mr) { + synchronized (lock) { + + PreparedExpression preparedExpression = null; + + fireCalculatorEvent(newCalculationEventData(operation, expression, calculationId), CalculatorEventType.calculation_started, new CalculatorInputImpl(expression, operation)); + + try { + preparedExpression = preprocessor.process(expression); + + final String jsclExpression = preparedExpression.toString(); + + try { + + final Generic result = operation.evaluateGeneric(jsclExpression); + + // NOTE: toString() method must be called here as ArithmeticOperationException may occur in it (just to avoid later check!) + result.toString(); + + final CalculatorOutputImpl data = new CalculatorOutputImpl(operation.getFromProcessor().process(result), operation, result); + fireCalculatorEvent(newCalculationEventData(operation, expression, calculationId), CalculatorEventType.calculation_result, data); + + } catch (AbstractJsclArithmeticException e) { + handleException(calculationId, operation, expression, mr, new CalculatorEvalException(e, e, jsclExpression)); + } + + } catch (ArithmeticException e) { + handleException(calculationId, operation, expression, mr, preparedExpression, new CalculatorParseException(expression, new CalculatorMessage(CalculatorMessages.msg_001, MessageType.error, e.getMessage()))); + } catch (StackOverflowError e) { + handleException(calculationId, operation, expression, mr, preparedExpression, new CalculatorParseException(expression, new CalculatorMessage(CalculatorMessages.msg_002, MessageType.error))); + } catch (jscl.text.ParseException e) { + handleException(calculationId, operation, expression, mr, preparedExpression, new CalculatorParseException(e)); + } catch (ParseInterruptedException e) { + + // do nothing - we ourselves interrupt the calculations + fireCalculatorEvent(newCalculationEventData(operation, expression, calculationId), CalculatorEventType.calculation_cancelled, null); + + } catch (CalculatorParseException e) { + handleException(calculationId, operation, expression, mr, preparedExpression, e); + } finally { + fireCalculatorEvent(newCalculationEventData(operation, expression, calculationId), CalculatorEventType.calculation_finished, null); + } + } + } + + @NotNull + private CalculatorEventData newCalculationEventData(@NotNull JsclOperation operation, + @NotNull String expression, + @NotNull Long calculationId) { + return new CalculatorEvaluationEventDataImpl(CalculatorEventDataImpl.newInstance(nextEventDataId(calculationId)), operation, expression); + } + + private void handleException(@NotNull Long calculationId, + @NotNull JsclOperation operation, + @NotNull String expression, + @Nullable MessageRegistry mr, + @Nullable PreparedExpression preparedExpression, + @NotNull CalculatorParseException parseException) { + + if (operation == JsclOperation.numeric + && preparedExpression != null + && preparedExpression.isExistsUndefinedVar()) { + + evaluate(calculationId, JsclOperation.simplify, expression, mr); + + } + + fireCalculatorEvent(newCalculationEventData(operation, expression, calculationId), CalculatorEventType.calculation_failed, new CalculatorFailureImpl(parseException)); + } + + private void handleException(@NotNull Long calculationId, + @NotNull JsclOperation operation, + @NotNull String expression, + @Nullable MessageRegistry mr, + @NotNull CalculatorEvalException evalException) { + + if (operation == JsclOperation.numeric && evalException.getCause() instanceof NumeralBaseException) { + evaluate(calculationId, JsclOperation.simplify, expression, mr); + } + + fireCalculatorEvent(newCalculationEventData(operation, expression, calculationId), CalculatorEventType.calculation_failed, new CalculatorFailureImpl(evalException)); + } + + /* + ********************************************************************** + * + * EVENTS + * + ********************************************************************** + */ + + @Override + public void addCalculatorEventListener(@NotNull CalculatorEventListener calculatorEventListener) { + calculatorEventContainer.addCalculatorEventListener(calculatorEventListener); + } + + @Override + public void removeCalculatorEventListener(@NotNull CalculatorEventListener calculatorEventListener) { + calculatorEventContainer.removeCalculatorEventListener(calculatorEventListener); + } + + @Override + public void fireCalculatorEvent(@NotNull CalculatorEventData calculatorEventData, @NotNull CalculatorEventType calculatorEventType, @Nullable Object data) { + calculatorEventContainer.fireCalculatorEvent(calculatorEventData, calculatorEventType, data); + } + + @Override + public void fireCalculatorEvents(@NotNull List calculatorEvents) { + calculatorEventContainer.fireCalculatorEvents(calculatorEvents); + } +} diff --git a/calculatorpp/project.properties b/calculatorpp/project.properties index 1b733b09..0b0ebc4d 100644 --- a/calculatorpp/project.properties +++ b/calculatorpp/project.properties @@ -1,19 +1,20 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system use, -# "ant.properties", and override values to adapt the script to your -# project structure. - -# Project target. -target=android-15 -android.library.reference.1=gen-external-apklibs/org.solovyev.android_android-common-core_1.0.0 -android.library.reference.2=gen-external-apklibs/org.solovyev.android_android-common-ads_1.0.0 -android.library.reference.3=gen-external-apklibs/org.solovyev.android_android-common-view_1.0.0 -android.library.reference.4=gen-external-apklibs/org.solovyev.android_android-common-preferences_1.0.0 -android.library.reference.5=gen-external-apklibs/org.solovyev.android_android-common-other_1.0.0 -android.library.reference.6=gen-external-apklibs/org.solovyev.android_android-common-menu_1.0.0 - - +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system use, +# "ant.properties", and override values to adapt the script to your +# project structure. + +# Project target. +target=android-15 +android.library.reference.1=../calculatorpp-service +android.library.reference.2=gen-external-apklibs/org.solovyev.android_android-common-core_1.0.0 +android.library.reference.3=gen-external-apklibs/org.solovyev.android_android-common-ads_1.0.0 +android.library.reference.4=gen-external-apklibs/org.solovyev.android_android-common-view_1.0.0 +android.library.reference.5=gen-external-apklibs/org.solovyev.android_android-common-preferences_1.0.0 +android.library.reference.6=gen-external-apklibs/org.solovyev.android_android-common-other_1.0.0 +android.library.reference.7=gen-external-apklibs/org.solovyev.android_android-common-menu_1.0.0 + + diff --git a/calculatorpp/src/main/java/org/solovyev/android/calculator/AndroidCalculatorDisplayView.java b/calculatorpp/src/main/java/org/solovyev/android/calculator/AndroidCalculatorDisplayView.java index d8db12c7..59b68e21 100644 --- a/calculatorpp/src/main/java/org/solovyev/android/calculator/AndroidCalculatorDisplayView.java +++ b/calculatorpp/src/main/java/org/solovyev/android/calculator/AndroidCalculatorDisplayView.java @@ -1,235 +1,316 @@ -/* - * 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.math.Generic; -import jscl.math.function.Constant; -import jscl.math.function.IConstant; -import org.jetbrains.annotations.NotNull; -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.view.NumeralBaseConverterDialog; -import org.solovyev.android.calculator.view.TextHighlighter; -import org.solovyev.android.menu.LabeledMenuItem; -import org.solovyev.android.view.AutoResizeTextView; -import org.solovyev.common.collections.CollectionsUtils; - -import java.util.HashSet; -import java.util.Set; - -/** - * User: serso - * Date: 9/17/11 - * Time: 10:58 PM - */ -public class AndroidCalculatorDisplayView extends AutoResizeTextView implements CalculatorDisplayView { - - public static enum MenuItem implements LabeledMenuItem { - - copy(R.string.c_copy) { - @Override - public void onClick(@NotNull CalculatorDisplayViewState data, @NotNull Context context) { - CalculatorModel.copyResult(context, data); - } - }, - - convert_to_bin(R.string.convert_to_bin) { - @Override - public void onClick(@NotNull CalculatorDisplayViewState 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 CalculatorDisplayViewState 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 CalculatorDisplayViewState 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 CalculatorDisplayViewState data, @NotNull Context context) { - final Generic result = data.getResult(); - if (result != null) { - new NumeralBaseConverterDialog(result.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 CalculatorDisplayViewState data, @NotNull Context context) { - final Generic generic = data.getResult(); - 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 getNotSystemConstants(@NotNull Generic generic) { - final Set notSystemConstants = new HashSet(); - - 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 CalculatorDisplayViewState displayViewState) { - //noinspection ConstantConditions - return displayViewState.isValid() && displayViewState.getResult() != null && isItemVisibleFor(displayViewState.getResult(), displayViewState.getOperation()); - } - - protected boolean isItemVisibleFor(@NotNull Generic generic, @NotNull JsclOperation operation) { - return true; - } - - @NotNull - @Override - public String getCaption(@NotNull Context context) { - return context.getString(captionId); - } - } - - @NotNull - private CalculatorDisplayViewState state = CalculatorDisplayViewStateImpl.newDefaultInstance(); - - @NotNull - private final static TextProcessor textHighlighter = new TextHighlighter(Color.WHITE, false, CalculatorEngine.instance.getEngine()); - - public AndroidCalculatorDisplayView(Context context) { - super(context); - } - - public AndroidCalculatorDisplayView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public AndroidCalculatorDisplayView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - public boolean isValid() { - return this.state.isValid(); - } - - - @Override - public void setState(@NotNull CalculatorDisplayViewState state) { - this.state = state; - if ( state.isValid() ) { - setTextColor(getResources().getColor(R.color.default_text_color)); - setText(state.getStringResult()); - } else { - setTextColor(getResources().getColor(R.color.display_error_text_color)); - setText(state.getErrorMessage()); - } - } - - @NotNull - @Override - public CalculatorDisplayViewState getState() { - return this.state; - } - - @Override - public void setText(CharSequence text, BufferType type) { - super.setText(text, type); - } - - 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 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.solovyev.android.calculator.jscl.JsclOperation; +import org.solovyev.android.calculator.model.CalculatorEngine; +import org.solovyev.android.calculator.text.TextProcessor; +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 AndroidCalculatorDisplayView extends AutoResizeTextView implements CalculatorDisplayView { + + private static enum ConversionMenuItem implements AMenuItem { + 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 CalculatorDisplayView data, @NotNull Context context) { + final NumeralBase fromNumeralBase = CalculatorEngine.instance.getEngine().getNumeralBase(); + + final Generic lastResult = CalculatorLocatorImpl.getInstance().getCalculatorDisplay().getViewState().getResult(); + + if (lastResult != null) { + String to; + try { + to = convert(lastResult); + + // 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 { + + copy(R.string.c_copy) { + @Override + public void onClick(@NotNull CalculatorDisplayView data, @NotNull Context context) { + CalculatorModel.copyResult(context, data); + } + }, + + convert_to_bin(R.string.convert_to_bin) { + @Override + public void onClick(@NotNull CalculatorDisplayView 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 CalculatorDisplayView 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 CalculatorDisplayView 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 CalculatorDisplayView data, @NotNull Context context) { + final Generic result = data.getState().getResult(); + if (result != null) { + new NumeralBaseConverterDialog(result.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 CalculatorDisplayView data, @NotNull Context context) { + final Generic generic = data.getState().getResult(); + 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 getNotSystemConstants(@NotNull Generic generic) { + final Set notSystemConstants = new HashSet(); + + 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 CalculatorDisplayViewState displayViewState) { + //noinspection ConstantConditions + return displayViewState.isValid() && displayViewState.getResult() != null && isItemVisibleFor(displayViewState.getResult(), displayViewState.getOperation()); + } + + protected boolean isItemVisibleFor(@NotNull Generic generic, @NotNull JsclOperation operation) { + return true; + } + + @NotNull + @Override + public String getCaption(@NotNull Context context) { + return context.getString(captionId); + } + } + + @NotNull + private CalculatorDisplayViewState state = CalculatorDisplayViewStateImpl.newDefaultInstance(); + + @NotNull + private final static TextProcessor textHighlighter = new TextHighlighter(Color.WHITE, false, CalculatorEngine.instance.getEngine()); + + public AndroidCalculatorDisplayView(Context context) { + super(context); + } + + public AndroidCalculatorDisplayView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public AndroidCalculatorDisplayView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public boolean isValid() { + return this.state.isValid(); + } + + + @Override + public void setState(@NotNull CalculatorDisplayViewState state) { + this.state = state; + if ( state.isValid() ) { + setTextColor(getResources().getColor(R.color.default_text_color)); + setText(state.getStringResult()); + } else { + setTextColor(getResources().getColor(R.color.display_error_text_color)); + setText(state.getErrorMessage()); + } + } + + @NotNull + @Override + public CalculatorDisplayViewState getState() { + return this.state; + } + + @Override + public void setText(CharSequence text, BufferType type) { + super.setText(text, type); + } + + 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 int getSelection() { + return this.getSelectionStart(); + } + + @Override + public void setSelection(int selection) { + // not supported by TextView + } +} diff --git a/calculatorpp/src/main/java/org/solovyev/android/calculator/CalculatorModel.java b/calculatorpp/src/main/java/org/solovyev/android/calculator/CalculatorModel.java index e43e9131..d459f15a 100644 --- a/calculatorpp/src/main/java/org/solovyev/android/calculator/CalculatorModel.java +++ b/calculatorpp/src/main/java/org/solovyev/android/calculator/CalculatorModel.java @@ -1,411 +1,412 @@ -/* - * 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.AndroidCalculatorHistoryImpl; -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.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, 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 AndroidCalculatorDisplayView 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 = (AndroidCalculatorDisplayView) activity.findViewById(R.id.calculatorDisplay); - this.display.setOnClickListener(new CalculatorDisplayOnClickListener(activity)); - - final CalculatorHistoryState lastState = AndroidCalculatorHistoryImpl.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 CalculatorDisplayViewState viewState) { - if (viewState.isValid()) { - final CharSequence text = viewState.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() { - AndroidCalculatorHistoryImpl.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 pendingOperation = new MutableObject(); - - 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) { - AndroidCalculatorHistoryImpl.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) { - AndroidCalculatorHistoryImpl.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 CalculatorOutput 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.getStringResult()); - } else { - display.setText(""); - } - display.setJsclOperation(result.getOperation()); - display.setGenericResult(result.getResult()); - } 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 AndroidCalculatorDisplayView 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 (AndroidCalculatorHistoryImpl.instance) { - if (AndroidCalculatorHistoryImpl.instance.isActionAvailable(historyAction)) { - final CalculatorHistoryState newState = AndroidCalculatorHistoryImpl.instance.doAction(historyAction, getCurrentHistoryState()); - if (newState != null) { - setCurrentHistoryState(newState); - } - } - } - } - - @Override - public void setCurrentHistoryState(@NotNull CalculatorHistoryState editorHistoryState) { - synchronized (AndroidCalculatorHistoryImpl.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 (AndroidCalculatorHistoryImpl.instance) { - return CalculatorHistoryState.newInstance(new TextViewEditorAdapter(this.editor), this.display); - } - } - - @NotNull - public AndroidCalculatorDisplayView 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 CalculatorDisplayView) { - final CalculatorDisplay cd = CalculatorLocatorImpl.getInstance().getCalculatorDisplay(); - - final CalculatorDisplayViewState displayViewState = cd.getViewState(); - - if (displayViewState.isValid()) { - final List filteredMenuItems = new ArrayList(AndroidCalculatorDisplayView.MenuItem.values().length); - for (AndroidCalculatorDisplayView.MenuItem menuItem : AndroidCalculatorDisplayView.MenuItem.values()) { - if (menuItem.isItemVisible(displayViewState)) { - filteredMenuItems.add(menuItem); - } - } - - if (!filteredMenuItems.isEmpty()) { - AMenuBuilder.newInstance(activity, MenuImpl.newInstance(filteredMenuItems)).create(cd).show(); - } - - } else { - final String errorMessage = displayViewState.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.AndroidCalculatorHistoryImpl; +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.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, 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 AndroidCalculatorDisplayView 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 = (AndroidCalculatorDisplayView) activity.findViewById(R.id.calculatorDisplay); + this.display.setOnClickListener(new CalculatorDisplayOnClickListener(activity)); + + final CalculatorHistoryState lastState = AndroidCalculatorHistoryImpl.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 CalculatorDisplayView display) { + final CalculatorDisplayViewState displayViewState = display.getState(); + if (displayViewState.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() { + AndroidCalculatorHistoryImpl.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 pendingOperation = new MutableObject(); + + 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) { + AndroidCalculatorHistoryImpl.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) { + AndroidCalculatorHistoryImpl.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 CalculatorOutput 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.getStringResult()); + } else { + display.setText(""); + } + display.setJsclOperation(result.getOperation()); + display.setGenericResult(result.getResult()); + } 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 AndroidCalculatorDisplayView 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 (AndroidCalculatorHistoryImpl.instance) { + if (AndroidCalculatorHistoryImpl.instance.isActionAvailable(historyAction)) { + final CalculatorHistoryState newState = AndroidCalculatorHistoryImpl.instance.doAction(historyAction, getCurrentHistoryState()); + if (newState != null) { + setCurrentHistoryState(newState); + } + } + } + } + + @Override + public void setCurrentHistoryState(@NotNull CalculatorHistoryState editorHistoryState) { + synchronized (AndroidCalculatorHistoryImpl.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 (AndroidCalculatorHistoryImpl.instance) { + return CalculatorHistoryState.newInstance(new TextViewEditorAdapter(this.editor), this.display); + } + } + + @NotNull + public AndroidCalculatorDisplayView 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 CalculatorDisplayView) { + final CalculatorDisplay cd = CalculatorLocatorImpl.getInstance().getCalculatorDisplay(); + + final CalculatorDisplayViewState displayViewState = cd.getViewState(); + + if (displayViewState.isValid()) { + final List filteredMenuItems = new ArrayList(AndroidCalculatorDisplayView.MenuItem.values().length); + for (AndroidCalculatorDisplayView.MenuItem menuItem : AndroidCalculatorDisplayView.MenuItem.values()) { + if (menuItem.isItemVisible(displayViewState)) { + filteredMenuItems.add(menuItem); + } + } + + if (!filteredMenuItems.isEmpty()) { + AMenuBuilder.newInstance(activity, MenuImpl.newInstance(filteredMenuItems)).create(cd).show(); + } + + } else { + final String errorMessage = displayViewState.getErrorMessage(); + if (errorMessage != null) { + showEvaluationError(activity, errorMessage); + } + } + } + } + } +} diff --git a/calculatorpp/src/main/java/org/solovyev/android/calculator/ConversionMenuItem.java b/calculatorpp/src/main/java/org/solovyev/android/calculator/ConversionMenuItem.java deleted file mode 100644 index c33039d8..00000000 --- a/calculatorpp/src/main/java/org/solovyev/android/calculator/ConversionMenuItem.java +++ /dev/null @@ -1,63 +0,0 @@ -package org.solovyev.android.calculator; - -import android.content.Context; -import jscl.NumeralBase; -import jscl.math.Generic; -import org.jetbrains.annotations.NotNull; -import org.solovyev.android.calculator.jscl.JsclOperation; -import org.solovyev.android.calculator.model.CalculatorEngine; -import org.solovyev.android.menu.AMenuItem; - -/** - * User: serso - * Date: 9/21/12 - * Time: 12:11 AM - */ -enum ConversionMenuItem implements AMenuItem { - - convert_to_bin(NumeralBase.bin), - convert_to_dec(NumeralBase.dec), - convert_to_hex(NumeralBase.hex); - - @NotNull - private final NumeralBase toNumeralBase; - - 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 (CalculatorImpl.ConversionException e) { - // conversion is not possible => return false - } - } - } - - return result; - } - - @Override - public void onClick(@NotNull CalculatorDisplayViewState data, @NotNull Context context) { - final NumeralBase fromNumeralBase = CalculatorEngine.instance.getEngine().getNumeralBase(); - - final Generic lastResult = data.getResult(); - - if (lastResult != null) { - convert(lastResult); - } - } - - private void convert(@NotNull Generic generic) { - CalculatorLocatorImpl.getInstance().getCalculator().convert(generic, this.toNumeralBase); - } -} diff --git a/calculatorpp/src/main/java/org/solovyev/android/calculator/view/UnitConverterViewBuilder.java b/calculatorpp/src/main/java/org/solovyev/android/calculator/view/UnitConverterViewBuilder.java index 41603705..d75c7240 100644 --- a/calculatorpp/src/main/java/org/solovyev/android/calculator/view/UnitConverterViewBuilder.java +++ b/calculatorpp/src/main/java/org/solovyev/android/calculator/view/UnitConverterViewBuilder.java @@ -1,224 +1,259 @@ -package org.solovyev.android.calculator.view; - -import android.app.Activity; -import android.content.Context; -import android.text.ClipboardManager; -import android.text.Editable; -import android.text.TextWatcher; -import android.view.View; -import android.view.ViewGroup; -import android.widget.*; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.solovyev.android.calculator.CalculatorImpl; -import org.solovyev.math.units.Unit; -import org.solovyev.math.units.UnitConverter; -import org.solovyev.math.units.UnitImpl; -import org.solovyev.math.units.UnitType; -import org.solovyev.android.calculator.R; -import org.solovyev.android.view.ViewBuilder; -import org.solovyev.android.view.ViewFromLayoutBuilder; - -import java.util.Collections; -import java.util.List; - -/** - * User: serso - * Date: 4/20/12 - * Time: 4:50 PM - */ -public class UnitConverterViewBuilder implements ViewBuilder { - - @NotNull - private List> fromUnitTypes = Collections.emptyList(); - - @NotNull - private List> toUnitTypes = Collections.emptyList(); - - @Nullable - private Unit fromValue; - - @NotNull - private UnitConverter converter = UnitConverter.Dummy.getInstance(); - - @Nullable - private View.OnClickListener okButtonOnClickListener; - - @Nullable - private CustomButtonData customButtonData; - - public void setFromUnitTypes(@NotNull List> fromUnitTypes) { - this.fromUnitTypes = fromUnitTypes; - } - - public void setToUnitTypes(@NotNull List> toUnitTypes) { - this.toUnitTypes = toUnitTypes; - } - - public void setFromValue(@Nullable Unit fromValue) { - this.fromValue = fromValue; - } - - public void setConverter(@NotNull UnitConverter converter) { - this.converter = converter; - } - - public void setOkButtonOnClickListener(@Nullable View.OnClickListener okButtonOnClickListener) { - this.okButtonOnClickListener = okButtonOnClickListener; - } - - public void setCustomButtonData(@Nullable CustomButtonData customButtonData) { - this.customButtonData = customButtonData; - } - - @NotNull - @Override - public View build(@NotNull final Context context) { - final View main = ViewFromLayoutBuilder.newInstance(R.layout.unit_converter).build(context); - - final Spinner fromSpinner = (Spinner) main.findViewById(R.id.unit_types_from); - final EditText fromEditText = (EditText) main.findViewById(R.id.units_from); - fromEditText.addTextChangedListener(new 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) { - doConversion(main, context, UnitConverterViewBuilder.this.converter); - } - }); - - fillSpinner(main, context, R.id.unit_types_from, fromUnitTypes); - fillSpinner(main, context, R.id.unit_types_to, toUnitTypes); - - if (fromValue != null) { - fromEditText.setText(fromValue.getValue()); - - int i = fromUnitTypes.indexOf(fromValue.getUnitType()); - if ( i >= 0 ) { - fromSpinner.setSelection(i); - } - } - - final Button copyButton = (Button) main.findViewById(R.id.unit_converter_copy_button); - copyButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - final EditText toEditText = (EditText) main.findViewById(R.id.units_to); - - final ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Activity.CLIPBOARD_SERVICE); - clipboard.setText(toEditText.getText().toString()); - Toast.makeText(context, context.getText(R.string.c_result_copied), Toast.LENGTH_SHORT).show(); - } - }); - - final Button okButton = (Button) main.findViewById(R.id.unit_converter_ok_button); - if ( okButtonOnClickListener == null ) { - ((ViewGroup) okButton.getParent()).removeView(okButton); - } else { - okButton.setOnClickListener(this.okButtonOnClickListener); - } - - final Button customButton = (Button) main.findViewById(R.id.unit_converter_custom_button); - if ( customButtonData == null ) { - ((ViewGroup) customButton.getParent()).removeView(customButton); - } else { - customButton.setText(customButtonData.text); - customButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - customButtonData.clickListener.onClick(getFromUnit(main), getToUnit(main)); - } - }); - } - - - - return main; - } - - private void fillSpinner(@NotNull final View main, - @NotNull final Context context, - final int spinnerId, - @NotNull List> unitTypes) { - final Spinner spinner = (Spinner) main.findViewById(spinnerId); - - final ArrayAdapter> adapter = new ArrayAdapter>(context, android.R.layout.simple_spinner_item); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - for (UnitType fromUnitType : unitTypes) { - adapter.add(fromUnitType); - } - spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - doConversion(main, context, UnitConverterViewBuilder.this.converter); - } - - @Override - public void onNothingSelected(AdapterView parent) { - } - }); - spinner.setAdapter(adapter); - } - - private static void doConversion(@NotNull View main, @NotNull Context context, @NotNull UnitConverter converter) { - final EditText fromEditText = (EditText) main.findViewById(R.id.units_from); - - final EditText toEditText = (EditText) main.findViewById(R.id.units_to); - - final String from = fromEditText.getText().toString(); - try { - toEditText.setText(CalculatorImpl.doConversion(converter, from, getFromUnitType(main), getToUnitType(main))); - } catch (CalculatorImpl.ConversionException e) { - toEditText.setText(context.getString(R.string.c_error)); - } - } - - @NotNull - private static Unit getToUnit(@NotNull View main) { - final EditText toUnits = (EditText) main.findViewById(R.id.units_to); - return UnitImpl.newInstance(toUnits.getText().toString(), getToUnitType(main)); - } - - @NotNull - private static UnitType getToUnitType(@NotNull View main) { - final Spinner toSpinner = (Spinner) main.findViewById(R.id.unit_types_to); - return (UnitType) toSpinner.getSelectedItem(); - } - - @NotNull - private static Unit getFromUnit(@NotNull View main) { - final EditText fromUnits = (EditText) main.findViewById(R.id.units_from); - return UnitImpl.newInstance(fromUnits.getText().toString(), getFromUnitType(main)); - } - - @NotNull - private static UnitType getFromUnitType(@NotNull View main) { - final Spinner fromSpinner = (Spinner) main.findViewById(R.id.unit_types_from); - return (UnitType) fromSpinner.getSelectedItem(); - } - - public static class CustomButtonData { - - @NotNull - private String text; - - @NotNull - private CustomButtonOnClickListener clickListener; - - - public CustomButtonData(@NotNull String text, @NotNull CustomButtonOnClickListener clickListener) { - this.text = text; - this.clickListener = clickListener; - } - } - - public static interface CustomButtonOnClickListener { - void onClick(@NotNull Unit fromUnits, @NotNull Unit toUnits); - } -} +package org.solovyev.android.calculator.view; + +import android.app.Activity; +import android.content.Context; +import android.text.ClipboardManager; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.View; +import android.view.ViewGroup; +import android.widget.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.solovyev.math.units.Unit; +import org.solovyev.math.units.UnitConverter; +import org.solovyev.math.units.UnitImpl; +import org.solovyev.math.units.UnitType; +import org.solovyev.android.calculator.R; +import org.solovyev.android.view.ViewBuilder; +import org.solovyev.android.view.ViewFromLayoutBuilder; +import org.solovyev.common.text.StringUtils; + +import java.util.Collections; +import java.util.List; + +/** + * User: serso + * Date: 4/20/12 + * Time: 4:50 PM + */ +public class UnitConverterViewBuilder implements ViewBuilder { + + @NotNull + private List> fromUnitTypes = Collections.emptyList(); + + @NotNull + private List> toUnitTypes = Collections.emptyList(); + + @Nullable + private Unit fromValue; + + @NotNull + private UnitConverter converter = UnitConverter.Dummy.getInstance(); + + @Nullable + private View.OnClickListener okButtonOnClickListener; + + @Nullable + private CustomButtonData customButtonData; + + public void setFromUnitTypes(@NotNull List> fromUnitTypes) { + this.fromUnitTypes = fromUnitTypes; + } + + public void setToUnitTypes(@NotNull List> toUnitTypes) { + this.toUnitTypes = toUnitTypes; + } + + public void setFromValue(@Nullable Unit fromValue) { + this.fromValue = fromValue; + } + + public void setConverter(@NotNull UnitConverter converter) { + this.converter = converter; + } + + public void setOkButtonOnClickListener(@Nullable View.OnClickListener okButtonOnClickListener) { + this.okButtonOnClickListener = okButtonOnClickListener; + } + + public void setCustomButtonData(@Nullable CustomButtonData customButtonData) { + this.customButtonData = customButtonData; + } + + @NotNull + @Override + public View build(@NotNull final Context context) { + final View main = ViewFromLayoutBuilder.newInstance(R.layout.unit_converter).build(context); + + final Spinner fromSpinner = (Spinner) main.findViewById(R.id.unit_types_from); + final EditText fromEditText = (EditText) main.findViewById(R.id.units_from); + fromEditText.addTextChangedListener(new 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) { + doConversion(main, context, UnitConverterViewBuilder.this.converter); + } + }); + + fillSpinner(main, context, R.id.unit_types_from, fromUnitTypes); + fillSpinner(main, context, R.id.unit_types_to, toUnitTypes); + + if (fromValue != null) { + fromEditText.setText(fromValue.getValue()); + + int i = fromUnitTypes.indexOf(fromValue.getUnitType()); + if ( i >= 0 ) { + fromSpinner.setSelection(i); + } + } + + final Button copyButton = (Button) main.findViewById(R.id.unit_converter_copy_button); + copyButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + final EditText toEditText = (EditText) main.findViewById(R.id.units_to); + + final ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Activity.CLIPBOARD_SERVICE); + clipboard.setText(toEditText.getText().toString()); + Toast.makeText(context, context.getText(R.string.c_result_copied), Toast.LENGTH_SHORT).show(); + } + }); + + final Button okButton = (Button) main.findViewById(R.id.unit_converter_ok_button); + if ( okButtonOnClickListener == null ) { + ((ViewGroup) okButton.getParent()).removeView(okButton); + } else { + okButton.setOnClickListener(this.okButtonOnClickListener); + } + + final Button customButton = (Button) main.findViewById(R.id.unit_converter_custom_button); + if ( customButtonData == null ) { + ((ViewGroup) customButton.getParent()).removeView(customButton); + } else { + customButton.setText(customButtonData.text); + customButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + customButtonData.clickListener.onClick(getFromUnit(main), getToUnit(main)); + } + }); + } + + + + return main; + } + + private void fillSpinner(@NotNull final View main, + @NotNull final Context context, + final int spinnerId, + @NotNull List> unitTypes) { + final Spinner spinner = (Spinner) main.findViewById(spinnerId); + + final ArrayAdapter> adapter = new ArrayAdapter>(context, android.R.layout.simple_spinner_item); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + for (UnitType fromUnitType : unitTypes) { + adapter.add(fromUnitType); + } + spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + doConversion(main, context, UnitConverterViewBuilder.this.converter); + } + + @Override + public void onNothingSelected(AdapterView parent) { + } + }); + spinner.setAdapter(adapter); + } + + private static void doConversion(@NotNull View main, @NotNull Context context, @NotNull UnitConverter converter) { + final EditText fromEditText = (EditText) main.findViewById(R.id.units_from); + + final EditText toEditText = (EditText) main.findViewById(R.id.units_to); + + final String from = fromEditText.getText().toString(); + try { + toEditText.setText(doConversion(converter, from, getFromUnitType(main), getToUnitType(main))); + } catch (ConversionException e) { + toEditText.setText(context.getString(R.string.c_error)); + } + } + + public static final class ConversionException extends Exception { + private ConversionException() { + } + + private ConversionException(Throwable throwable) { + super(throwable); + } + } + + @NotNull + public static String doConversion(@NotNull UnitConverter converter, + @Nullable String from, + @NotNull UnitType fromUnitType, + @NotNull UnitType toUnitType) throws ConversionException{ + final String result; + + if (StringUtils.isEmpty(from)) { + result = ""; + } else { + + String to = null; + try { + if (converter.isSupported(fromUnitType, toUnitType)) { + to = converter.convert(UnitImpl.newInstance(from, fromUnitType), toUnitType).getValue(); + } + } catch (RuntimeException e) { + throw new ConversionException(e); + } + + result = to; + } + + return result; + } + + @NotNull + private static Unit getToUnit(@NotNull View main) { + final EditText toUnits = (EditText) main.findViewById(R.id.units_to); + return UnitImpl.newInstance(toUnits.getText().toString(), getToUnitType(main)); + } + + @NotNull + private static UnitType getToUnitType(@NotNull View main) { + final Spinner toSpinner = (Spinner) main.findViewById(R.id.unit_types_to); + return (UnitType) toSpinner.getSelectedItem(); + } + + @NotNull + private static Unit getFromUnit(@NotNull View main) { + final EditText fromUnits = (EditText) main.findViewById(R.id.units_from); + return UnitImpl.newInstance(fromUnits.getText().toString(), getFromUnitType(main)); + } + + @NotNull + private static UnitType getFromUnitType(@NotNull View main) { + final Spinner fromSpinner = (Spinner) main.findViewById(R.id.unit_types_from); + return (UnitType) fromSpinner.getSelectedItem(); + } + + public static class CustomButtonData { + + @NotNull + private String text; + + @NotNull + private CustomButtonOnClickListener clickListener; + + + public CustomButtonData(@NotNull String text, @NotNull CustomButtonOnClickListener clickListener) { + this.text = text; + this.clickListener = clickListener; + } + } + + public static interface CustomButtonOnClickListener { + void onClick(@NotNull Unit fromUnits, @NotNull Unit toUnits); + } +}