/*
* Copyright 2013 serso aka se.solovyev
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Contact details
*
* Email: se.solovyev@gmail.com
* Site: http://se.solovyev.org
*/
package org.solovyev.android.calculator.functions;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.design.widget.TextInputLayout;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.widget.*;
import org.solovyev.android.Check;
import org.solovyev.android.calculator.App;
import org.solovyev.android.calculator.Preferences;
import org.solovyev.android.calculator.R;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
public class FunctionParamsView extends LinearLayout {
@Nonnull
public static final String PARAM_VIEW_TAG = "param-view";
private static final List<String> PARAM_NAMES = Arrays.asList("x", "y", "z", "t", "a", "b", "c");
private static final int FOOTERS = 1;
private static final int PARAM_VIEW_INDEX = 3;
private static final int START_ROW_ID = App.generateViewId();
private final int clickableAreaSize;
private final int imageButtonSize;
private final int imageButtonPadding;
@Nonnull
private final Preferences.Gui.Theme theme = App.getTheme();
private int maxRowId = START_ROW_ID;
private int maxParams = Integer.MAX_VALUE;
@Nonnull
private LinearLayout headerView;
{
final Resources resources = getResources();
clickableAreaSize = resources.getDimensionPixelSize(R.dimen.cpp_clickable_area_size);
imageButtonSize = resources.getDimensionPixelSize(R.dimen.cpp_image_button_size);
imageButtonPadding = resources.getDimensionPixelSize(R.dimen.cpp_image_button_padding);
}
public FunctionParamsView(Context context) {
super(context);
init();
}
public FunctionParamsView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public FunctionParamsView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
setOrientation(VERTICAL);
final Context context = getContext();
headerView = makeRowView(context);
final ImageButton addButton = makeButton(theme.light ? R.drawable.ic_add_black_24dp : R.drawable.ic_add_white_24dp);
addButton.setId(R.id.function_params_add);
addButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final LinearLayout rowView = addParam(generateParamName());
final EditText paramView = getParamView(rowView);
paramView.requestFocus();
}
});
headerView.addView(addButton, makeButtonParams());
headerView.addView(new View(context), new LayoutParams(3 * clickableAreaSize, WRAP_CONTENT));
addView(headerView, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
}
@Nullable
private String generateParamName() {
final List<String> available = new ArrayList<>(PARAM_NAMES);
available.removeAll(getParams());
return available.size() > 0 ? available.get(0) : null;
}
@NonNull
private ImageButton makeButton(int icon) {
final ImageButton addButton = new ImageButton(getContext());
addButton.setImageResource(icon);
addButton.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
addButton.setPadding(imageButtonPadding, imageButtonPadding, imageButtonPadding, imageButtonPadding);
final TypedValue value = new TypedValue();
if (getContext().getTheme().resolveAttribute(R.attr.selectableItemBackgroundBorderless, value, true)) {
addButton.setBackgroundResource(value.resourceId);
}
return addButton;
}
@Nonnull
private LinearLayout makeRowView(@Nonnull Context context) {
final LinearLayout rowView = new LinearLayout(context);
rowView.setOrientation(HORIZONTAL);
rowView.setMinimumHeight(clickableAreaSize);
rowView.setGravity(Gravity.CENTER_VERTICAL);
return rowView;
}
public void addParams(@Nonnull List<String> params) {
for (String param : params) {
addParam(param);
}
}
@NonNull
public LinearLayout addParam(@Nullable String param) {
return addParam(param, maxRowId++);
}
@NonNull
private LinearLayout addParam(@Nullable String param, final int id) {
final Context context = getContext();
final LinearLayout rowView = makeRowView(context);
final ImageButton removeButton = makeButton(theme.light ? R.drawable.ic_remove_black_24dp : R.drawable.ic_remove_white_24dp);
removeButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
removeRow(rowView);
}
});
rowView.addView(removeButton, makeButtonParams());
final ImageButton upButton = makeButton(theme.light ? R.drawable.ic_arrow_upward_black_24dp : R.drawable.ic_arrow_upward_white_24dp);
upButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
upRow(rowView);
}
});
rowView.addView(upButton, makeButtonParams());
final ImageButton downButton = makeButton(theme.light ? R.drawable.ic_arrow_downward_black_24dp : R.drawable.ic_arrow_downward_white_24dp);
downButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
downRow(rowView);
}
});
rowView.addView(downButton, makeButtonParams());
final TextInputLayout paramLabel = new TextInputLayout(context);
final EditText paramView = new EditText(context);
if (param != null) {
paramView.setText(param);
}
paramView.setOnFocusChangeListener(getOnFocusChangeListener());
paramView.setSelectAllOnFocus(true);
paramView.setInputType(EditorInfo.TYPE_CLASS_TEXT);
paramView.setId(id);
paramView.setTag(PARAM_VIEW_TAG);
paramView.setHint(R.string.cpp_parameter);
paramLabel.addView(paramView, new TextInputLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
rowView.addView(paramLabel, new LayoutParams(0, WRAP_CONTENT, 1));
// for row is added at 0 position, the consequent rows
addView(rowView, Math.max(0, getChildCount() - 1), new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
onParamsChanged();
return rowView;
}
private void onParamsChanged() {
final boolean enabled = getParamsCount() < maxParams;
headerView.setVisibility(enabled ? VISIBLE : GONE);
}
@NonNull
private LayoutParams makeButtonParams() {
return new LayoutParams(imageButtonSize, imageButtonSize);
}
private void downRow(@Nonnull ViewGroup row) {
final int index = indexOfChild(row);
if (index < getChildCount() - 1 - FOOTERS) {
swap(row, getRow(index + 1));
}
}
private void upRow(@Nonnull ViewGroup row) {
final int index = indexOfChild(row);
if (index > 0) {
swap(row, getRow(index - 1));
}
}
private void swap(@Nonnull ViewGroup l, @Nonnull ViewGroup r) {
final EditText lParam = getParamView(l);
final EditText rParam = getParamView(r);
swap(lParam, rParam);
}
private void swap(@Nonnull TextView l,
@Nonnull TextView r) {
final CharSequence tmp = l.getText();
l.setText(r.getText());
r.setText(tmp);
}
@Nonnull
private ViewGroup getRow(int index) {
Check.isTrue(index >= 0 && index < getParamsCount());
return (ViewGroup) getChildAt(index);
}
public void removeRow(@Nonnull ViewGroup row) {
removeView(row);
onParamsChanged();
}
@Nonnull
public List<String> getParams() {
final List<String> params = new ArrayList<>(getParamsCount());
for (int i = 0; i < getParamsCount(); i++) {
final ViewGroup row = getRow(i);
final EditText paramView = getParamView(row);
params.add(paramView.getText().toString());
}
return params;
}
private int getParamsCount() {
return getChildCount() - FOOTERS;
}
public void setMaxParams(int maxParams) {
this.maxParams = maxParams;
onParamsChanged();
}
@Nonnull
private EditText getParamView(@Nonnull ViewGroup row) {
final TextInputLayout paramLabel = getParamLabel(row);
for (int i = 0; i < paramLabel.getChildCount(); i++) {
final View child = paramLabel.getChildAt(i);
if (child instanceof EditText) {
return (EditText) child;
}
}
Check.shouldNotHappen();
return null;
}
@Nonnull
private TextInputLayout getParamLabel(@Nonnull ViewGroup row) {
return (TextInputLayout) row.getChildAt(PARAM_VIEW_INDEX);
}
@Override
protected Parcelable onSaveInstanceState() {
final Parcelable superState = super.onSaveInstanceState();
return new SavedState(superState, getRowIds());
}
@Nonnull
private int[] getRowIds() {
final int childCount = getChildCount();
final int[] rowIds = new int[childCount - FOOTERS];
for (int i = 0; i < childCount - FOOTERS; i++) {
final ViewGroup row = getRow(i);
final EditText paramView = getParamView(row);
rowIds[i] = paramView.getId();
}
return rowIds;
}
@Override
protected void onRestoreInstanceState(Parcelable in) {
if (!(in instanceof SavedState)) {
super.onRestoreInstanceState(in);
return;
}
final SavedState state = (SavedState) in;
for (int i = 0; i < state.rowIds.length; i++) {
final int rowId = state.rowIds[i];
addParam(null, rowId);
maxRowId = Math.max(maxRowId, rowId + 1);
}
super.onRestoreInstanceState(state.getSuperState());
}
@Nonnull
public TextInputLayout getParamLabel(int param) {
return getParamLabel(getRow(param));
}
public static final class SavedState extends BaseSavedState {
public static final Parcelable.Creator<SavedState> CREATOR =
new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(@Nonnull Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
private int[] rowIds;
public SavedState(@Nonnull Parcelable superState, int[] rowIds) {
super(superState);
this.rowIds = rowIds;
}
public SavedState(@Nonnull Parcel in) {
super(in);
rowIds = in.createIntArray();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeIntArray(rowIds);
}
}
}