Editor/display changes

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

View File

@ -13,7 +13,10 @@ import static org.solovyev.android.calculator.CalculatorEventType.*;
public class CalculatorDisplayImpl implements CalculatorDisplay { public class CalculatorDisplayImpl implements CalculatorDisplay {
@NotNull @NotNull
private CalculatorEventData lastCalculatorEventData; private volatile CalculatorEventData lastCalculatorEventData;
@NotNull
private final Object lastCalculatorEventDataLock = new Object();
@Nullable @Nullable
private CalculatorDisplayView view; private CalculatorDisplayView view;
@ -88,8 +91,10 @@ public class CalculatorDisplayImpl implements CalculatorDisplay {
@Override @Override
@NotNull @NotNull
public CalculatorEventData getLastEventData() { public CalculatorEventData getLastEventData() {
synchronized (lastCalculatorEventDataLock) {
return lastCalculatorEventData; return lastCalculatorEventData;
} }
}
@Override @Override
public void onCalculatorEvent(@NotNull CalculatorEventData calculatorEventData, public void onCalculatorEvent(@NotNull CalculatorEventData calculatorEventData,
@ -97,10 +102,18 @@ public class CalculatorDisplayImpl implements CalculatorDisplay {
@Nullable Object data) { @Nullable Object data) {
if (calculatorEventType.isOfType(calculation_result, calculation_failed, calculation_cancelled, conversion_result, conversion_failed)) { if (calculatorEventType.isOfType(calculation_result, calculation_failed, calculation_cancelled, conversion_result, conversion_failed)) {
boolean processEvent = false;
boolean sameSequence = false;
synchronized (lastCalculatorEventDataLock) {
if (calculatorEventData.isAfter(lastCalculatorEventData)) { if (calculatorEventData.isAfter(lastCalculatorEventData)) {
sameSequence = calculatorEventData.isSameSequence(lastCalculatorEventData);
lastCalculatorEventData = calculatorEventData; lastCalculatorEventData = calculatorEventData;
processEvent = true;
}
} }
if (processEvent) {
switch (calculatorEventType) { switch (calculatorEventType) {
case conversion_failed: case conversion_failed:
processConversationFailed((CalculatorConversionEventData) calculatorEventData, (ConversionFailure) data); processConversationFailed((CalculatorConversionEventData) calculatorEventData, (ConversionFailure) data);
@ -118,7 +131,7 @@ public class CalculatorDisplayImpl implements CalculatorDisplay {
processCalculationFailed((CalculatorEvaluationEventData)calculatorEventData, (CalculatorFailure) data); processCalculationFailed((CalculatorEvaluationEventData)calculatorEventData, (CalculatorFailure) data);
break; break;
} }
}
} }
} }

View File

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

View File

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

View File

@ -40,7 +40,10 @@ public class CalculatorImpl implements Calculator, CalculatorEventListener {
private final TextProcessor<PreparedExpression, String> preprocessor = ToJsclTextProcessor.getInstance(); private final TextProcessor<PreparedExpression, String> preprocessor = ToJsclTextProcessor.getInstance();
@NotNull @NotNull
private final Executor threadPoolExecutor = Executors.newFixedThreadPool(10); private final Executor calculationsExecutor = Executors.newFixedThreadPool(10);
@NotNull
private final Executor eventExecutor = Executors.newFixedThreadPool(1);
public CalculatorImpl() { public CalculatorImpl() {
this.addCalculatorEventListener(this); this.addCalculatorEventListener(this);
@ -94,7 +97,7 @@ public class CalculatorImpl implements Calculator, CalculatorEventListener {
final CalculatorEventData eventDataId = nextEventData(); final CalculatorEventData eventDataId = nextEventData();
threadPoolExecutor.execute(new Runnable() { calculationsExecutor.execute(new Runnable() {
@Override @Override
public void run() { public void run() {
CalculatorImpl.this.evaluate(eventDataId.getSequenceId(), operation, expression, null); CalculatorImpl.this.evaluate(eventDataId.getSequenceId(), operation, expression, null);
@ -109,7 +112,7 @@ public class CalculatorImpl implements Calculator, CalculatorEventListener {
public CalculatorEventData evaluate(@NotNull final JsclOperation operation, @NotNull final String expression, @NotNull Long sequenceId) { public CalculatorEventData evaluate(@NotNull final JsclOperation operation, @NotNull final String expression, @NotNull Long sequenceId) {
final CalculatorEventData eventDataId = nextEventData(sequenceId); final CalculatorEventData eventDataId = nextEventData(sequenceId);
threadPoolExecutor.execute(new Runnable() { calculationsExecutor.execute(new Runnable() {
@Override @Override
public void run() { public void run() {
CalculatorImpl.this.evaluate(eventDataId.getSequenceId(), operation, expression, null); CalculatorImpl.this.evaluate(eventDataId.getSequenceId(), operation, expression, null);
@ -242,7 +245,7 @@ public class CalculatorImpl implements Calculator, CalculatorEventListener {
final CalculatorDisplayViewState displayViewState = CalculatorLocatorImpl.getInstance().getDisplay().getViewState(); final CalculatorDisplayViewState displayViewState = CalculatorLocatorImpl.getInstance().getDisplay().getViewState();
final NumeralBase from = CalculatorLocatorImpl.getInstance().getEngine().getNumeralBase(); final NumeralBase from = CalculatorLocatorImpl.getInstance().getEngine().getNumeralBase();
threadPoolExecutor.execute(new Runnable() { calculationsExecutor.execute(new Runnable() {
@Override @Override
public void run() { public void run() {
final Long sequenceId = eventDataId.getSequenceId(); final Long sequenceId = eventDataId.getSequenceId();
@ -316,26 +319,31 @@ public class CalculatorImpl implements Calculator, CalculatorEventListener {
} }
@Override @Override
public void fireCalculatorEvent(@NotNull CalculatorEventData calculatorEventData, @NotNull CalculatorEventType calculatorEventType, @Nullable Object data) { public void fireCalculatorEvent(@NotNull final CalculatorEventData calculatorEventData, @NotNull final CalculatorEventType calculatorEventType, @Nullable final Object data) {
eventExecutor.execute(new Runnable() {
@Override
public void run() {
calculatorEventContainer.fireCalculatorEvent(calculatorEventData, calculatorEventType, data); calculatorEventContainer.fireCalculatorEvent(calculatorEventData, calculatorEventType, data);
} }
});
}
@Override @Override
public void fireCalculatorEvents(@NotNull List<CalculatorEvent> calculatorEvents) { public void fireCalculatorEvents(@NotNull final List<CalculatorEvent> calculatorEvents) {
eventExecutor.execute(new Runnable() {
@Override
public void run() {
calculatorEventContainer.fireCalculatorEvents(calculatorEvents); calculatorEventContainer.fireCalculatorEvents(calculatorEvents);
} }
});
}
@NotNull @NotNull
@Override @Override
public CalculatorEventData fireCalculatorEvent(@NotNull final CalculatorEventType calculatorEventType, @Nullable final Object data) { public CalculatorEventData fireCalculatorEvent(@NotNull final CalculatorEventType calculatorEventType, @Nullable final Object data) {
final CalculatorEventData eventData = nextEventData(); final CalculatorEventData eventData = nextEventData();
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
fireCalculatorEvent(eventData, calculatorEventType, data); fireCalculatorEvent(eventData, calculatorEventType, data);
}
});
return eventData; return eventData;
} }
@ -345,12 +353,7 @@ public class CalculatorImpl implements Calculator, CalculatorEventListener {
public CalculatorEventData fireCalculatorEvent(@NotNull final CalculatorEventType calculatorEventType, @Nullable final Object data, @NotNull Long sequenceId) { public CalculatorEventData fireCalculatorEvent(@NotNull final CalculatorEventType calculatorEventType, @Nullable final Object data, @NotNull Long sequenceId) {
final CalculatorEventData eventData = nextEventData(sequenceId); final CalculatorEventData eventData = nextEventData(sequenceId);
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
fireCalculatorEvent(eventData, calculatorEventType, data); fireCalculatorEvent(eventData, calculatorEventType, data);
}
});
return eventData; return eventData;
} }

View File

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

View File

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