KeyboardUi in EditFunctionDialog
@ -0,0 +1,408 @@
|
||||
package org.solovyev.android.calculator;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PointF;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.IdRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
import android.util.TypedValue;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import org.solovyev.android.views.dragbutton.DirectionDragButton;
|
||||
import org.solovyev.android.views.dragbutton.DragButton;
|
||||
import org.solovyev.android.views.dragbutton.DragDirection;
|
||||
import org.solovyev.android.views.dragbutton.SimpleDragListener;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.solovyev.android.views.dragbutton.DirectionDragButton.Direction.down;
|
||||
import static org.solovyev.android.views.dragbutton.DirectionDragButton.Direction.up;
|
||||
|
||||
|
||||
public class KeyboardUi {
|
||||
@NonNull
|
||||
private final ButtonHandler buttonHandler = new ButtonHandler();
|
||||
@NonNull
|
||||
private final User user;
|
||||
@NonNull
|
||||
private final List<String> parameterNames;
|
||||
@NonNull
|
||||
private final SimpleDragListener dragListener;
|
||||
private final int textColor;
|
||||
private final int textColorSecondary;
|
||||
private final int sidePadding;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public KeyboardUi(@NonNull User user, @NonNull List<String> parameterNames) {
|
||||
this.user = user;
|
||||
this.parameterNames = parameterNames;
|
||||
this.dragListener = new SimpleDragListener(buttonHandler, user.getContext());
|
||||
final Resources resources = user.getResources();
|
||||
textColor = resources.getColor(R.color.cpp_button_text);
|
||||
textColorSecondary = resources.getColor(R.color.cpp_button_text);
|
||||
sidePadding = resources.getDimensionPixelSize(R.dimen.cpp_button_padding);
|
||||
}
|
||||
|
||||
public void makeView() {
|
||||
LinearLayout row = makeRow();
|
||||
addButton(row, 0, "7");
|
||||
addButton(row, 0, "8");
|
||||
addButton(row, 0, "9").setText("π", up).setText("e", down);
|
||||
addOperationButton(row, R.id.cpp_kb_button_multiply, Locator.getInstance().getEngine().getMultiplicationSign()).setText("^n", up).setText("^2", down);
|
||||
addButton(row, R.id.cpp_kb_button_clear, "C");
|
||||
|
||||
row = makeRow();
|
||||
addButton(row, 0, "4");
|
||||
addButton(row, 0, "5");
|
||||
addButton(row, 0, "6");
|
||||
addOperationButton(row, R.id.cpp_kb_button_divide, "/").setText("%", up).setText("sqrt", down);
|
||||
final View backspace = addImageButton(row, R.id.cpp_kb_button_backspace, R.drawable.ic_backspace_white_24dp);
|
||||
LongClickEraser.createAndAttach(backspace, user.getEditor());
|
||||
|
||||
row = makeRow();
|
||||
addButton(row, 0, "1");
|
||||
addButton(row, 0, "2");
|
||||
addButton(row, 0, "3");
|
||||
addOperationButton(row, R.id.cpp_kb_button_plus, "+");
|
||||
addImageButton(row, R.id.cpp_kb_button_space, R.drawable.ic_space_bar_white_24dp);
|
||||
|
||||
row = makeRow();
|
||||
addButton(row, R.id.cpp_kb_button_brackets, "( )").setText("(", up).setText(")", down);
|
||||
addButton(row, 0, "0").setText("00", up).setText("000", down);
|
||||
addButton(row, 0, ".").setText(",", up);
|
||||
addOperationButton(row, R.id.cpp_kb_button_minus, "−");
|
||||
addImageButton(row, R.id.cpp_kb_button_keyboard, R.drawable.ic_keyboard_white_24dp);
|
||||
|
||||
row = makeRow();
|
||||
final int parametersCount = parameterNames.size();
|
||||
addButton(row, 0, parametersCount > 0 ? parameterNames.get(0) : "x");
|
||||
addButton(row, 0, parametersCount > 1 ? parameterNames.get(1) : "y");
|
||||
addButton(row, R.id.cpp_kb_button_functions, "f(x)");
|
||||
addButton(row, R.id.cpp_kb_button_constants, "π");
|
||||
addImageButton(row, R.id.cpp_kb_button_close, R.drawable.ic_done_white_24dp);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private View addImageButton(@NonNull LinearLayout row, @IdRes int id, @DrawableRes int icon) {
|
||||
final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT);
|
||||
lp.weight = 1f;
|
||||
final View view = makeImageButton(id, icon);
|
||||
row.addView(view, lp);
|
||||
return view;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private DirectionDragButton addOperationButton(@NonNull LinearLayout row, @IdRes int id, @NonNull String text) {
|
||||
final DirectionDragButton button = addButton(row, id, text);
|
||||
button.setBackgroundResource(R.drawable.material_button_light_primary);
|
||||
button.setTextColor(Color.WHITE);
|
||||
button.setDirectionTextColor(Color.WHITE);
|
||||
return button;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private DirectionDragButton addButton(@NonNull LinearLayout row, @IdRes int id, @NonNull String text) {
|
||||
final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT);
|
||||
lp.weight = 1f;
|
||||
final DirectionDragButton view = makeButton(id, text);
|
||||
row.addView(view, lp);
|
||||
return view;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private LinearLayout makeRow() {
|
||||
final LinearLayout row = new LinearLayout(user.getContext());
|
||||
row.setOrientation(LinearLayout.HORIZONTAL);
|
||||
final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0);
|
||||
lp.weight = 1f;
|
||||
user.getKeyboard().addView(row, lp);
|
||||
return row;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private DirectionDragButton makeButton(@IdRes int id, @NonNull String text) {
|
||||
final DirectionDragButton button = new DirectionDragButton(user.getContext());
|
||||
fillButton(button, id);
|
||||
button.setText(text);
|
||||
button.setTextColor(textColor);
|
||||
button.setDirectionTextColor(textColorSecondary);
|
||||
button.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 24);
|
||||
button.setOnDragListener(dragListener);
|
||||
return button;
|
||||
}
|
||||
|
||||
private void fillButton(@NonNull View button, @IdRes int id) {
|
||||
button.setOnClickListener(buttonHandler);
|
||||
button.setId(id);
|
||||
button.setBackgroundResource(R.drawable.material_button_light);
|
||||
button.setPadding(sidePadding, 1, sidePadding, 1);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
button.setStateListAnimator(null);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private View makeImageButton(@IdRes int id, @DrawableRes int icon) {
|
||||
final ImageButton button = new ImageButton(user.getContext());
|
||||
fillButton(button, id);
|
||||
button.setImageResource(icon);
|
||||
button.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
|
||||
return button;
|
||||
}
|
||||
|
||||
public interface User {
|
||||
@NonNull
|
||||
Context getContext();
|
||||
|
||||
@NonNull
|
||||
Resources getResources();
|
||||
|
||||
@NonNull
|
||||
EditText getEditor();
|
||||
|
||||
@NonNull
|
||||
ViewGroup getKeyboard();
|
||||
|
||||
void insertOperator(char operator);
|
||||
|
||||
void insertOperator(@NonNull String operator);
|
||||
|
||||
void showFunctions(@NonNull View v);
|
||||
|
||||
void showConstants(@NonNull View v);
|
||||
|
||||
void insertText(@NonNull CharSequence text, int offset);
|
||||
|
||||
void done();
|
||||
|
||||
void showIme();
|
||||
}
|
||||
|
||||
private class ButtonHandler implements View.OnClickListener, SimpleDragListener.DragProcessor {
|
||||
@Override
|
||||
public void onClick(@NonNull View v) {
|
||||
switch (v.getId()) {
|
||||
case R.id.cpp_kb_button_divide:
|
||||
user.insertOperator('/');
|
||||
break;
|
||||
case R.id.cpp_kb_button_plus:
|
||||
user.insertOperator('+');
|
||||
break;
|
||||
case R.id.cpp_kb_button_minus:
|
||||
user.insertOperator('-');
|
||||
break;
|
||||
case R.id.cpp_kb_button_multiply:
|
||||
user.insertOperator('*');
|
||||
break;
|
||||
case R.id.cpp_kb_button_functions:
|
||||
user.showFunctions(v);
|
||||
break;
|
||||
case R.id.cpp_kb_button_constants:
|
||||
user.showConstants(v);
|
||||
break;
|
||||
case R.id.cpp_kb_button_space:
|
||||
user.insertText(" ", 0);
|
||||
break;
|
||||
case R.id.cpp_kb_button_keyboard:
|
||||
user.showIme();
|
||||
break;
|
||||
case R.id.cpp_kb_button_clear:
|
||||
user.getEditor().setText("");
|
||||
user.getEditor().setSelection(0);
|
||||
break;
|
||||
case R.id.cpp_kb_button_brackets:
|
||||
user.insertText("()", -1);
|
||||
break;
|
||||
case R.id.cpp_kb_button_close:
|
||||
user.done();
|
||||
break;
|
||||
default:
|
||||
onDefaultClick(v);
|
||||
break;
|
||||
}
|
||||
user.getEditor().requestFocus();
|
||||
v.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
|
||||
}
|
||||
|
||||
private void onDefaultClick(@NonNull View v) {
|
||||
user.insertText(((Button) v).getText(), 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean processDragEvent(@NonNull DragDirection direction, @NonNull DragButton button, @NonNull PointF startPoint, @NonNull MotionEvent e) {
|
||||
switch (button.getId()) {
|
||||
default:
|
||||
return onDefaultDrag(button, direction);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean onDefaultDrag(@NonNull DragButton button, @NonNull DragDirection direction) {
|
||||
final String text = ((DirectionDragButton) button).getText(direction);
|
||||
if (TextUtils.isEmpty(text)) {
|
||||
return false;
|
||||
}
|
||||
switch (text) {
|
||||
case "sqrt":
|
||||
user.insertText("sqrt()", -1);
|
||||
break;
|
||||
case ",":
|
||||
user.insertText(", ", 0);
|
||||
break;
|
||||
case "^n":
|
||||
user.insertOperator('^');
|
||||
break;
|
||||
case "^2":
|
||||
user.insertOperator("^ 2");
|
||||
break;
|
||||
case "?":
|
||||
case ">":
|
||||
case "<":
|
||||
case ">=":
|
||||
case "<=":
|
||||
case ":":
|
||||
user.insertOperator(text);
|
||||
break;
|
||||
default:
|
||||
user.insertText(text, 0);
|
||||
break;
|
||||
}
|
||||
button.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class LongClickEraser implements View.OnTouchListener, View.OnClickListener {
|
||||
|
||||
@NonNull
|
||||
private final View view;
|
||||
|
||||
@NonNull
|
||||
private final EditText editText;
|
||||
|
||||
@NonNull
|
||||
private final GestureDetector gestureDetector;
|
||||
|
||||
@NonNull
|
||||
private final Eraser eraser = new Eraser();
|
||||
|
||||
private LongClickEraser(@NonNull final View view, @NonNull EditText editText) {
|
||||
this.view = view;
|
||||
this.editText = editText;
|
||||
this.gestureDetector = new GestureDetector(view.getContext(), new GestureDetector.SimpleOnGestureListener() {
|
||||
public void onLongPress(MotionEvent e) {
|
||||
if (eraser.isTracking()) {
|
||||
eraser.start();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void createAndAttach(@NonNull View view, @NonNull EditText editText) {
|
||||
final LongClickEraser l = new LongClickEraser(view, editText);
|
||||
view.setOnClickListener(l);
|
||||
view.setOnTouchListener(l);
|
||||
}
|
||||
|
||||
private static void erase(@NonNull EditText editText) {
|
||||
final int start = clampSelection(editText.getSelectionStart());
|
||||
final int end = clampSelection(editText.getSelectionEnd());
|
||||
if (start != end) {
|
||||
editText.getText().delete(Math.min(start, end), Math.max(start, end));
|
||||
} else if (start > 0) {
|
||||
editText.getText().delete(start - 1, start);
|
||||
}
|
||||
}
|
||||
|
||||
public static int clampSelection(int selection) {
|
||||
return selection < 0 ? 0 : selection;
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
case MotionEvent.ACTION_UP:
|
||||
eraser.stopTracking();
|
||||
break;
|
||||
default:
|
||||
eraser.startTracking();
|
||||
gestureDetector.onTouchEvent(event);
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
erase(editText);
|
||||
v.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
|
||||
}
|
||||
|
||||
private class Eraser implements Runnable {
|
||||
private static final int DELAY = 300;
|
||||
private long delay;
|
||||
private boolean erasing;
|
||||
private boolean tracking = true;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
erase(editText);
|
||||
if (editText.length() == 0 || clampSelection(editText.getSelectionStart()) == 0) {
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
delay = Math.max(50, 2 * delay / 3);
|
||||
view.postDelayed(this, delay);
|
||||
}
|
||||
|
||||
void start() {
|
||||
if (erasing) {
|
||||
stop();
|
||||
}
|
||||
erasing = true;
|
||||
delay = DELAY;
|
||||
view.removeCallbacks(this);
|
||||
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
|
||||
run();
|
||||
}
|
||||
|
||||
void stop() {
|
||||
view.removeCallbacks(this);
|
||||
if (!erasing) {
|
||||
return;
|
||||
}
|
||||
|
||||
erasing = false;
|
||||
}
|
||||
|
||||
public void stopTracking() {
|
||||
stop();
|
||||
tracking = false;
|
||||
}
|
||||
|
||||
public boolean isTracking() {
|
||||
return tracking;
|
||||
}
|
||||
|
||||
public void startTracking() {
|
||||
tracking = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
package org.solovyev.android.calculator;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.os.IBinder;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.PopupWindow;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class KeyboardWindow {
|
||||
|
||||
@Nullable
|
||||
private PopupWindow window;
|
||||
@Nullable
|
||||
private Dialog dialog;
|
||||
|
||||
private static void hideIme(@NonNull View view) {
|
||||
final IBinder token = view.getWindowToken();
|
||||
if (token != null) {
|
||||
InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService(
|
||||
Context.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(token, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public void hide() {
|
||||
if (!isShown()) {
|
||||
return;
|
||||
}
|
||||
moveDialog(Gravity.CENTER);
|
||||
window.dismiss();
|
||||
window = null;
|
||||
dialog = null;
|
||||
}
|
||||
|
||||
public void show(@NonNull KeyboardUi.User user, @Nullable Dialog dialog, @NonNull List<String> parameterNames) {
|
||||
if (isShown()) {
|
||||
return;
|
||||
}
|
||||
this.dialog = dialog;
|
||||
moveDialog(Gravity.TOP);
|
||||
final EditText editor = user.getEditor();
|
||||
hideIme(editor);
|
||||
final Context context = editor.getContext();
|
||||
final LinearLayout view = new LinearLayout(context);
|
||||
view.setOrientation(LinearLayout.VERTICAL);
|
||||
final int buttonSize = context.getResources().getDimensionPixelSize(R.dimen.cpp_kb_button_size);
|
||||
final int keyboardSize = 5 * buttonSize;
|
||||
window = new PopupWindow(view, keyboardSize, keyboardSize);
|
||||
window.setClippingEnabled(false);
|
||||
window.setOnDismissListener(new PopupWindow.OnDismissListener() {
|
||||
@Override
|
||||
public void onDismiss() {
|
||||
window = null;
|
||||
}
|
||||
});
|
||||
// see http://stackoverflow.com/a/4713487/720489
|
||||
editor.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (window == null) {
|
||||
return;
|
||||
}
|
||||
if (editor.getWindowToken() != null) {
|
||||
hideIme(editor);
|
||||
final int inputWidth = editor.getWidth();
|
||||
final int xOff = (inputWidth - keyboardSize) / 2;
|
||||
window.setWidth(keyboardSize);
|
||||
window.showAsDropDown(editor, xOff, 0);
|
||||
} else {
|
||||
editor.postDelayed(this, 50);
|
||||
}
|
||||
}
|
||||
});
|
||||
new KeyboardUi(user, parameterNames).makeView();
|
||||
}
|
||||
|
||||
public boolean isShown() {
|
||||
return window != null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <V extends View> V getContentView() {
|
||||
return (V) window.getContentView();
|
||||
}
|
||||
|
||||
public void moveDialog(int gravity) {
|
||||
if (dialog == null) {
|
||||
return;
|
||||
}
|
||||
final Window window = dialog.getWindow();
|
||||
final WindowManager.LayoutParams lp = window.getAttributes();
|
||||
lp.gravity = gravity;
|
||||
window.setAttributes(lp);
|
||||
}
|
||||
}
|
@ -22,9 +22,11 @@
|
||||
|
||||
package org.solovyev.android.calculator.function;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
@ -33,8 +35,15 @@ import android.support.design.widget.TextInputLayout;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.text.Editable;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
|
||||
@ -45,6 +54,8 @@ import org.solovyev.android.calculator.CalculatorEventListener;
|
||||
import org.solovyev.android.calculator.CalculatorEventType;
|
||||
import org.solovyev.android.calculator.CalculatorUtils;
|
||||
import org.solovyev.android.calculator.DisplayState;
|
||||
import org.solovyev.android.calculator.KeyboardUi;
|
||||
import org.solovyev.android.calculator.KeyboardWindow;
|
||||
import org.solovyev.android.calculator.Locator;
|
||||
import org.solovyev.android.calculator.R;
|
||||
import org.solovyev.android.calculator.math.edit.CalculatorFunctionsActivity;
|
||||
@ -52,8 +63,10 @@ import org.solovyev.android.calculator.math.edit.FunctionsFragment;
|
||||
import org.solovyev.android.calculator.math.edit.MathEntityRemover;
|
||||
import org.solovyev.android.calculator.math.edit.VarEditorSaver;
|
||||
import org.solovyev.android.calculator.model.AFunction;
|
||||
import org.solovyev.common.math.MathRegistry;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@ -66,11 +79,23 @@ import jscl.math.Generic;
|
||||
import jscl.math.function.Constant;
|
||||
import jscl.math.function.CustomFunction;
|
||||
import jscl.math.function.Function;
|
||||
import jscl.math.function.IConstant;
|
||||
import jscl.math.function.IFunction;
|
||||
|
||||
public class EditFunctionFragment extends BaseDialogFragment implements CalculatorEventListener {
|
||||
public class EditFunctionFragment extends BaseDialogFragment implements CalculatorEventListener, View.OnClickListener, View.OnFocusChangeListener, View.OnKeyListener {
|
||||
|
||||
private static final String ARG_INPUT = "input";
|
||||
private static final int MENU_FUNCTION = Menu.FIRST;
|
||||
private static final int MENU_CONSTANT = Menu.FIRST + 1;
|
||||
|
||||
@NonNull
|
||||
private final MathRegistry<Function> functionsRegistry = Locator.getInstance().getEngine().getFunctionsRegistry();
|
||||
@NonNull
|
||||
private final MathRegistry<IConstant> constantsRegistry = Locator.getInstance().getEngine().getVarsRegistry();
|
||||
@NonNull
|
||||
private final KeyboardWindow keyboardWindow = new KeyboardWindow();
|
||||
@NonNull
|
||||
private final KeyboardUser keyboardUser = new KeyboardUser();
|
||||
@Bind(R.id.function_params)
|
||||
FunctionParamsView paramsView;
|
||||
@Bind(R.id.function_name_label)
|
||||
@ -154,6 +179,34 @@ public class EditFunctionFragment extends BaseDialogFragment implements Calculat
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFocusChange(View v, boolean hasFocus) {
|
||||
if (v.getId() == R.id.function_body) {
|
||||
if (hasFocus) {
|
||||
keyboardWindow.show(keyboardUser, getDialog(), paramsView.getParams());
|
||||
} else {
|
||||
keyboardWindow.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (v.getId() == R.id.function_body) {
|
||||
keyboardWindow.show(keyboardUser, getDialog(), paramsView.getParams());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||
if (v.getId() == R.id.function_body) {
|
||||
if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK && keyboardWindow.isShown()) {
|
||||
keyboardWindow.hide();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void tryClose() {
|
||||
if (validate()) {
|
||||
applyData();
|
||||
@ -169,6 +222,9 @@ public class EditFunctionFragment extends BaseDialogFragment implements Calculat
|
||||
if (!validateName()) {
|
||||
return false;
|
||||
}
|
||||
if (!validateBody()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -182,6 +238,11 @@ public class EditFunctionFragment extends BaseDialogFragment implements Calculat
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean validateBody() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
@NonNull
|
||||
@Override
|
||||
protected View onCreateDialogView(@NonNull Context context, @NonNull LayoutInflater inflater, @Nullable Bundle savedInstanceState) {
|
||||
@ -198,6 +259,9 @@ public class EditFunctionFragment extends BaseDialogFragment implements Calculat
|
||||
descriptionView.setText(input.getDescription());
|
||||
bodyView.setText(input.getContent());
|
||||
}
|
||||
bodyView.setOnClickListener(this);
|
||||
bodyView.setOnFocusChangeListener(this);
|
||||
bodyView.setOnKeyListener(this);
|
||||
|
||||
return view;
|
||||
}
|
||||
@ -275,7 +339,7 @@ public class EditFunctionFragment extends BaseDialogFragment implements Calculat
|
||||
result.content = in.readString();
|
||||
result.description = in.readString();
|
||||
|
||||
final List<String> parameterNames = new ArrayList<String>();
|
||||
final List<String> parameterNames = new ArrayList<>();
|
||||
in.readTypedList(parameterNames, STRING_CREATOR);
|
||||
result.parameterNames = parameterNames;
|
||||
|
||||
@ -310,7 +374,7 @@ public class EditFunctionFragment extends BaseDialogFragment implements Calculat
|
||||
result.name = name;
|
||||
result.content = value;
|
||||
result.description = description;
|
||||
result.parameterNames = new ArrayList<String>(parameterNames);
|
||||
result.parameterNames = new ArrayList<>(parameterNames);
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -323,7 +387,7 @@ public class EditFunctionFragment extends BaseDialogFragment implements Calculat
|
||||
final Generic generic = viewState.getResult();
|
||||
if (generic != null) {
|
||||
final Set<Constant> constants = CalculatorUtils.getNotSystemConstants(generic);
|
||||
final List<String> parameterNames = new ArrayList<String>(constants.size());
|
||||
final List<String> parameterNames = new ArrayList<>(constants.size());
|
||||
for (Constant constant : constants) {
|
||||
parameterNames.add(constant.getName());
|
||||
}
|
||||
@ -372,4 +436,161 @@ public class EditFunctionFragment extends BaseDialogFragment implements Calculat
|
||||
out.writeSerializable(function);
|
||||
}
|
||||
}
|
||||
|
||||
private class KeyboardUser implements KeyboardUi.User, MenuItem.OnMenuItemClickListener {
|
||||
@NonNull
|
||||
@Override
|
||||
public Context getContext() {
|
||||
return getActivity();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Resources getResources() {
|
||||
return EditFunctionFragment.this.getResources();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public EditText getEditor() {
|
||||
return bodyView;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewGroup getKeyboard() {
|
||||
return keyboardWindow.getContentView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertOperator(char operator) {
|
||||
insertOperator(String.valueOf(operator));
|
||||
}
|
||||
|
||||
public int clampSelection(int selection) {
|
||||
return selection < 0 ? 0 : selection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertOperator(@NonNull String operator) {
|
||||
final int start = clampSelection(bodyView.getSelectionStart());
|
||||
final int end = clampSelection(bodyView.getSelectionEnd());
|
||||
final Editable e = bodyView.getText();
|
||||
e.replace(start, end, getOperator(start, end, e, operator));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String getOperator(int start, int end, @NonNull Editable e, @NonNull CharSequence operator) {
|
||||
boolean spaceBefore = true;
|
||||
boolean spaceAfter = true;
|
||||
if (start > 0 && Character.isSpaceChar(e.charAt(start - 1))) {
|
||||
spaceBefore = false;
|
||||
}
|
||||
if (end < e.length() && Character.isSpaceChar(e.charAt(end))) {
|
||||
spaceAfter = false;
|
||||
}
|
||||
|
||||
if (spaceBefore && spaceAfter) {
|
||||
return " " + operator + " ";
|
||||
}
|
||||
if (spaceBefore) {
|
||||
return " " + operator;
|
||||
}
|
||||
if (spaceAfter) {
|
||||
return operator + " ";
|
||||
}
|
||||
return String.valueOf(operator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showConstants(@NonNull View v) {
|
||||
bodyView.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() {
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
|
||||
final int id = v.getId();
|
||||
if (id == R.id.function_body) {
|
||||
menu.clear();
|
||||
for (String constant : getNamesSorted(constantsRegistry)) {
|
||||
menu.add(MENU_CONSTANT, Menu.NONE, Menu.NONE, constant).setOnMenuItemClickListener(KeyboardUser.this);
|
||||
}
|
||||
unregisterForContextMenu(bodyView);
|
||||
}
|
||||
}
|
||||
});
|
||||
bodyView.showContextMenu();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private List<String> getNamesSorted(@NonNull MathRegistry<?> registry) {
|
||||
final List<String> names = registry.getNames();
|
||||
Collections.sort(names);
|
||||
return names;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showFunctions(@NonNull View v) {
|
||||
bodyView.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() {
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
|
||||
final int id = v.getId();
|
||||
if (id == R.id.function_body) {
|
||||
menu.clear();
|
||||
for (String function : getNamesSorted(functionsRegistry)) {
|
||||
menu.add(MENU_FUNCTION, Menu.NONE, Menu.NONE, function).setOnMenuItemClickListener(KeyboardUser.this);
|
||||
}
|
||||
unregisterForContextMenu(bodyView);
|
||||
}
|
||||
}
|
||||
});
|
||||
bodyView.showContextMenu();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertText(@NonNull CharSequence text, int selectionOffset) {
|
||||
final int start = clampSelection(bodyView.getSelectionStart());
|
||||
final int end = clampSelection(bodyView.getSelectionEnd());
|
||||
final Editable e = bodyView.getText();
|
||||
e.replace(start, end, text);
|
||||
if (selectionOffset != 0) {
|
||||
final int selection = clampSelection(bodyView.getSelectionEnd());
|
||||
final int newSelection = selection + selectionOffset;
|
||||
if (newSelection >= 0 && newSelection < e.length()) {
|
||||
bodyView.setSelection(newSelection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void done() {
|
||||
keyboardWindow.hide();
|
||||
validateBody();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showIme() {
|
||||
final InputMethodManager keyboard = (InputMethodManager)
|
||||
getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
keyboard.showSoftInput(getEditor(), InputMethodManager.SHOW_FORCED);
|
||||
keyboardWindow.hide();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
final int groupId = item.getGroupId();
|
||||
final CharSequence title = item.getTitle();
|
||||
if (groupId == MENU_FUNCTION) {
|
||||
final int argsListIndex = title.toString().indexOf("(");
|
||||
if (argsListIndex < 0) {
|
||||
keyboardUser.insertText(title + "()", -1);
|
||||
} else {
|
||||
keyboardUser.insertText(title.subSequence(0, argsListIndex) + "()", -1);
|
||||
}
|
||||
} else if (groupId == MENU_CONSTANT) {
|
||||
keyboardUser.insertText(title.toString(), 0);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,8 @@ import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@ -38,6 +40,7 @@ import android.widget.TextView;
|
||||
|
||||
import org.solovyev.android.Check;
|
||||
import org.solovyev.android.calculator.App;
|
||||
import org.solovyev.android.calculator.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@ -146,6 +149,7 @@ public class FunctionParamsView extends LinearLayout {
|
||||
}
|
||||
paramView.setInputType(EditorInfo.TYPE_CLASS_TEXT);
|
||||
paramView.setId(id);
|
||||
paramView.setHint(R.string.c_function_parameter);
|
||||
rowView.addView(paramView, new LayoutParams(0, WRAP_CONTENT, 3));
|
||||
|
||||
addView(rowView, new ViewGroup.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
|
||||
@ -195,7 +199,10 @@ public class FunctionParamsView extends LinearLayout {
|
||||
for (int i = 1; i < getChildCount(); i++) {
|
||||
final ViewGroup row = getRowByIndex(i);
|
||||
final EditText paramView = (EditText) row.getChildAt(PARAM_VIEW_INDEX);
|
||||
params.add(paramView.getText().toString());
|
||||
final Editable param = paramView.getText();
|
||||
if (!TextUtils.isEmpty(param)) {
|
||||
params.add(param.toString());
|
||||
}
|
||||
}
|
||||
|
||||
return params;
|
||||
|
@ -1,5 +1,6 @@
|
||||
package org.solovyev.android.calculator.history;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
@ -11,14 +12,16 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import butterknife.Bind;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
import org.solovyev.android.calculator.AppComponent;
|
||||
import org.solovyev.android.calculator.BaseDialogFragment;
|
||||
import org.solovyev.android.calculator.R;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import butterknife.Bind;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class EditHistoryFragment extends BaseDialogFragment {
|
||||
|
||||
public static final String ARG_STATE = "state";
|
||||
@ -81,6 +84,7 @@ public class EditHistoryFragment extends BaseDialogFragment {
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
@NonNull
|
||||
@Override
|
||||
protected View onCreateDialogView(@NonNull Context context, @NonNull LayoutInflater inflater, @Nullable Bundle savedInstanceState) {
|
||||
|
BIN
app/src/main/res/drawable-hdpi/app_icon.png
Normal file
After Width: | Height: | Size: 5.7 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_backspace_white_24dp.png
Normal file
After Width: | Height: | Size: 525 B |
BIN
app/src/main/res/drawable-hdpi/ic_done_white_24dp.png
Normal file
After Width: | Height: | Size: 264 B |
BIN
app/src/main/res/drawable-hdpi/ic_keyboard_white_24dp.png
Normal file
After Width: | Height: | Size: 343 B |
BIN
app/src/main/res/drawable-hdpi/ic_space_bar_white_24dp.png
Normal file
After Width: | Height: | Size: 188 B |
BIN
app/src/main/res/drawable-mdpi/app_icon.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
app/src/main/res/drawable-mdpi/ic_backspace_white_24dp.png
Normal file
After Width: | Height: | Size: 371 B |
BIN
app/src/main/res/drawable-mdpi/ic_done_white_24dp.png
Normal file
After Width: | Height: | Size: 207 B |
BIN
app/src/main/res/drawable-mdpi/ic_keyboard_white_24dp.png
Normal file
After Width: | Height: | Size: 222 B |
BIN
app/src/main/res/drawable-mdpi/ic_space_bar_white_24dp.png
Normal file
After Width: | Height: | Size: 147 B |
BIN
app/src/main/res/drawable-xhdpi/app_icon.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
app/src/main/res/drawable-xhdpi/ic_backspace_white_24dp.png
Normal file
After Width: | Height: | Size: 640 B |
BIN
app/src/main/res/drawable-xhdpi/ic_done_white_24dp.png
Normal file
After Width: | Height: | Size: 323 B |
BIN
app/src/main/res/drawable-xhdpi/ic_keyboard_white_24dp.png
Normal file
After Width: | Height: | Size: 316 B |
BIN
app/src/main/res/drawable-xhdpi/ic_space_bar_white_24dp.png
Normal file
After Width: | Height: | Size: 176 B |
BIN
app/src/main/res/drawable-xxhdpi/app_icon.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
app/src/main/res/drawable-xxhdpi/ic_backspace_white_24dp.png
Normal file
After Width: | Height: | Size: 838 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_done_white_24dp.png
Normal file
After Width: | Height: | Size: 455 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_keyboard_white_24dp.png
Normal file
After Width: | Height: | Size: 389 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_space_bar_white_24dp.png
Normal file
After Width: | Height: | Size: 186 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_backspace_white_24dp.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
app/src/main/res/drawable-xxxhdpi/ic_done_white_24dp.png
Normal file
After Width: | Height: | Size: 565 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_keyboard_white_24dp.png
Normal file
After Width: | Height: | Size: 471 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_space_bar_white_24dp.png
Normal file
After Width: | Height: | Size: 198 B |
@ -29,4 +29,5 @@
|
||||
|
||||
<dimen name="cpp_display_text_size">20sp</dimen>
|
||||
<dimen name="cpp_display_text_size_mobile">20sp</dimen>
|
||||
<dimen name="cpp_kb_button_size">40dp</dimen>
|
||||
</resources>
|
@ -39,4 +39,5 @@
|
||||
<dimen name="cpp_onscreen_main_padding">1dp</dimen>
|
||||
|
||||
<dimen name="cpp_dialog_spacing">20dp</dimen>
|
||||
<dimen name="cpp_kb_button_size">50dp</dimen>
|
||||
</resources>
|
@ -60,5 +60,17 @@
|
||||
<item name="acl_wizard_prev_button" type="id" />
|
||||
<item name="acl_wizard_next_button" type="id" />
|
||||
<item name="acl_wizard_content" type="id" />
|
||||
<item name="cpp_kb_button_multiply" type="id" />
|
||||
<item name="cpp_kb_button_clear" type="id" />
|
||||
<item name="cpp_kb_button_divide" type="id" />
|
||||
<item name="cpp_kb_button_backspace" type="id" />
|
||||
<item name="cpp_kb_button_plus" type="id" />
|
||||
<item name="cpp_kb_button_space" type="id" />
|
||||
<item name="cpp_kb_button_brackets" type="id" />
|
||||
<item name="cpp_kb_button_minus" type="id" />
|
||||
<item name="cpp_kb_button_keyboard" type="id" />
|
||||
<item name="cpp_kb_button_functions" type="id" />
|
||||
<item name="cpp_kb_button_constants" type="id" />
|
||||
<item name="cpp_kb_button_close" type="id" />
|
||||
|
||||
</resources>
|
@ -149,6 +149,7 @@
|
||||
<string name="c_function_value">Value</string>
|
||||
<string name="c_function_description">Description</string>
|
||||
<string name="c_function_parameters">Parameters</string>
|
||||
<string name="c_function_parameter">Parameter</string>
|
||||
<string name="function_create_function">Create function</string>
|
||||
<string name="function_edit_function">Edit function</string>
|
||||
<string name="function_name_is_not_valid">Name of function is not valid: name must start with a letter, can contain letters, digits and underscore.</string>
|
||||
|