package odoo.controls;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import odoo.ODomain;
import org.json.JSONArray;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.os.AsyncTask;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import com.odoo.R;
import com.odoo.orm.OColumn;
import com.odoo.orm.OColumn.ColumnDomain;
import com.odoo.orm.ODataRow;
import com.odoo.orm.OFieldsHelper;
import com.odoo.orm.OModel;
import com.odoo.orm.OSyncHelper;
import com.odoo.support.listview.OListAdapter;
import com.odoo.util.OControls;
public class OSearchableMany2One extends LinearLayout implements
View.OnClickListener, TextWatcher, OnItemClickListener {
public static final String TAG = OSearchableMany2One.class.getSimpleName();
public static final String KEY_DATA_MODEL = "dataModel";
public static final String KEY_TITLE = "title";
public static final String KEY_DISPLAY_LAYOUT = "displayLayout";
public static final Integer ID_DEFAULT_VIEW = 0x123456;
public static final Integer ID_DIALOG_PARENT_VIEW = 0x1234567;
public static final Integer ID_DIALOG_SEARCH_BOX = 0x1234568;
public static final Integer ID_DIALOG_LISTVIEW = 0x1234569;
public static final Integer ID_DIALOG_LOADING = 0x1234570;
public static final Integer ID_DIALOG_EMPTY_LIST = 0x1234571;
private Context mContext;
private OModel mModel;
private TypedArray mTypedArray = null;
private DisplayMetrics mMetrics = null;
private OControlAttributes mAttr = new OControlAttributes();
private Float mScaleFactor = 0F;
private ODataRow mRecord = null;
private AlertDialog.Builder mDialogBuilder = null;
private AlertDialog mDialog = null;
private EditText mSearchBox = null;
private Boolean mVisibile = false;
private ListView mListView;
private List<Object> mObjects = new ArrayList<Object>();
private OListAdapter mAdapter;
private DialogListRowViewListener mDialogListRowViewListener = null;
private RecordsLoader mRecordsLoader = null;
private LinearLayout mParentView;
private ODomain mDefaultDomain = new ODomain();
private Boolean mEditMode = false;
private LinkedHashMap<String, ColumnDomain> columnDomains;
private OnChangeCallback mOnChangeCallback;
private OnDomainFilterCallbacks mOnDomainFilterCallbacks;
private ColumnDomain mColumnDomain;
public OSearchableMany2One(Context context) {
super(context);
init(context, null, 0);
}
public OSearchableMany2One(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs, 0);
}
public OSearchableMany2One(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs, defStyle);
}
private void init(Context context, AttributeSet attrs, int defStyle) {
mContext = context;
mMetrics = getResources().getDisplayMetrics();
mScaleFactor = mMetrics.density;
if (attrs != null) {
mTypedArray = mContext.obtainStyledAttributes(attrs,
R.styleable.OSearchableMany2One);
mAttr.put(KEY_DATA_MODEL, mTypedArray
.getString(R.styleable.OSearchableMany2One_dataModel));
mAttr.put(KEY_TITLE, mTypedArray
.getString(R.styleable.OSearchableMany2One_widget_title));
mAttr.put(KEY_DISPLAY_LAYOUT, mTypedArray.getResourceId(
R.styleable.OSearchableMany2One_displayLayout, -1));
mTypedArray.recycle();
}
initControl();
}
public void setRecord(ODataRow record) {
mRecord = record;
}
public void reInit(boolean editMode) {
mEditMode = editMode;
removeAllViews();
initControl();
}
public void setDefaultDomain(LinkedHashMap<String, ColumnDomain> domains) {
columnDomains = domains;
ODomain domain = new ODomain();
if (domain != null && domains.size() > 0) {
for (String key : domains.keySet()) {
ColumnDomain cDomain = domains.get(key);
domain.add(cDomain.getColumn(), cDomain.getOperator(),
cDomain.getValue());
}
mDefaultDomain.append(domain);
}
}
public void setModel(OModel model) {
mModel = model;
}
public OModel getModel() {
if (mModel == null && mAttr.getString(KEY_DATA_MODEL, null) != null) {
mModel = OModel
.get(mContext, mAttr.getString(KEY_DATA_MODEL, null));
}
return mModel;
}
private void initDialogBuilder() {
mDialogBuilder = new Builder(mContext);
mDialogBuilder.setCancelable(false);
mDialogBuilder.setView(getDialogView());
mDialogBuilder.setNegativeButton(
mContext.getString(R.string.label_cancel),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
mVisibile = false;
}
});
}
private void initControl() {
Log.v(TAG, "initControl()");
setOrientation(LinearLayout.VERTICAL);
if (hasDisplayLayout()) {
View layout = LayoutInflater.from(mContext).inflate(
mAttr.getResource(KEY_DISPLAY_LAYOUT, -1), this, false);
if (mEditMode) {
layout.setTag(ID_DEFAULT_VIEW);
layout.setOnClickListener(this);
layout.setBackgroundResource(R.drawable.drawer_item_selector);
}
if (mDialogListRowViewListener != null) {
mDialogListRowViewListener.bindDisplayLayoutLoad(mRecord,
layout);
}
addView(layout);
} else {
TextView layout = (TextView) getDefaltLayout();
layout.setText(getTitle());
if (mDialogListRowViewListener != null) {
mDialogListRowViewListener.bindDisplayLayoutLoad(mRecord,
layout);
}
addView(layout);
}
}
public void setTitle(String title) {
mAttr.put(KEY_TITLE, title);
}
public String getTitle() {
return mAttr.getString(KEY_TITLE, null);
}
private View getDefaltLayout() {
TextView txvDefault = new TextView(mContext);
setPadd(10, txvDefault);
txvDefault.setPadding((int) (5 * mScaleFactor),
txvDefault.getPaddingTop(), txvDefault.getPaddingRight(),
txvDefault.getPaddingBottom());
txvDefault.setTextAppearance(mContext,
android.R.attr.textAppearanceLarge);
txvDefault.setTypeface(OControlHelper.boldFont());
txvDefault.setTag(ID_DEFAULT_VIEW);
if (mEditMode) {
txvDefault.setOnClickListener(this);
txvDefault.setBackgroundResource(R.drawable.drawer_item_selector);
}
return txvDefault;
}
public void setDisplayLayout(int res) {
mAttr.put(KEY_DISPLAY_LAYOUT, res);
}
public boolean hasDisplayLayout() {
return (mAttr.getResource(KEY_DISPLAY_LAYOUT, -1) != -1);
}
private void setPadd(int padd, View view) {
int padding = (int) (mScaleFactor * padd);
view.setPadding(padding, padding, padding, padding);
}
@Override
public void onClick(View v) {
if (v.getTag() == ID_DEFAULT_VIEW) {
if (!mVisibile) {
initDialogBuilder();
mDialog = mDialogBuilder.create();
mDialog.show();
mVisibile = true;
}
}
}
private View getDialogView() {
// Parent View
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
mParentView = new LinearLayout(mContext);
mParentView.setTag(ID_DIALOG_PARENT_VIEW);
mParentView.setOrientation(LinearLayout.VERTICAL);
mParentView.setLayoutParams(params);
int padd = (int) (mScaleFactor * 10);
mParentView.setPadding(padd, padd, padd, padd);
// EditText Search box
initSearchBox();
mParentView.addView(mSearchBox);
// Separator
mParentView.addView(getSeparator());
// Preparing listview and data
prepareListControl();
mParentView.addView(mListView);
return mParentView;
}
private View getSeparator() {
LayoutParams param = new LayoutParams(LayoutParams.MATCH_PARENT, 2);
View separator = new View(mContext);
separator.setLayoutParams(param);
separator.setBackgroundColor(Color.LTGRAY);
return separator;
}
private void initSearchBox() {
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
mSearchBox = new EditText(mContext);
mSearchBox.setSingleLine();
mSearchBox.setLayoutParams(params);
mSearchBox.setId(ID_DIALOG_SEARCH_BOX);
mSearchBox.setTextAppearance(mContext,
android.R.attr.textAppearanceMedium);
mSearchBox.setTypeface(OControlHelper.lightFont());
mSearchBox.setBackgroundColor(Color.TRANSPARENT);
mSearchBox.setHint(getTitle());
mSearchBox.addTextChangedListener(this);
}
private void prepareListControl() {
AbsListView.LayoutParams params = new AbsListView.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
mListView = new ListView(mContext);
mListView.setLayoutParams(params);
mListView.setTag(ID_DIALOG_LISTVIEW);
mListView.setOnItemClickListener(this);
prepareAdapter();
}
private View dataLoadingView() {
View v = LayoutInflater.from(mContext).inflate(
R.layout.listview_data_loading_progress, this, false);
v.setTag(ID_DIALOG_LOADING);
return v;
}
private View emptyList() {
TextView txvEmpty = new TextView(mContext);
setPadd(30, txvEmpty);
txvEmpty.setTextAppearance(mContext, android.R.attr.textAppearanceLarge);
txvEmpty.setTypeface(OControlHelper.boldFont());
txvEmpty.setTag(ID_DIALOG_EMPTY_LIST);
txvEmpty.setText(mContext.getString(R.string.label_no_records_found));
txvEmpty.setGravity(Gravity.CENTER);
return txvEmpty;
}
private void prepareAdapter() {
mObjects.clear();
mAdapter = new OListAdapter(mContext,
android.R.layout.simple_list_item_1, mObjects) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (mDialogListRowViewListener != null) {
View v = mDialogListRowViewListener.onDialogListRowGetView(
(ODataRow) mObjects.get(position), position,
convertView, parent);
if (v != null)
return v;
}
if (convertView == null) {
convertView = LayoutInflater.from(mContext).inflate(
android.R.layout.simple_list_item_1, parent, false);
}
ODataRow row = (ODataRow) mObjects.get(position);
OControls.setText(
convertView,
android.R.id.text1,
(row.contains("name")) ? row.getString("name") : row
.toString());
return convertView;
}
};
mListView.setAdapter(mAdapter);
// Getting default records
if (mRecordsLoader != null)
mRecordsLoader.cancel(true);
mRecordsLoader = new RecordsLoader(mParentView, getModel());
mRecordsLoader.execute();
}
public void setDialogListRowViewListener(DialogListRowViewListener listener) {
mDialogListRowViewListener = listener;
}
private Object[] getWhere(ODomain domain) {
StringBuffer whr = new StringBuffer();
List<String> args = new ArrayList<String>();
for (String key : columnDomains.keySet()) {
ColumnDomain cDomain = columnDomains.get(key);
if (cDomain.getConditionalOperator() != null) {
whr.append(cDomain.getConditionalOperator());
} else {
whr.append(" ");
whr.append(cDomain.getColumn());
whr.append(" ");
whr.append(cDomain.getOperator());
whr.append(" ? ");
args.add(cDomain.getValue().toString());
}
}
JSONArray domains = domain.getArray();
for (int i = 0; i < domains.length(); i++) {
try {
JSONArray dmn = domains.getJSONArray(i);
if (whr.toString().trim().length() != 0) {
whr.append(" AND ");
}
whr.append(" ");
whr.append(dmn.getString(0));
whr.append(" ");
whr.append(validOperator(dmn.getString(1)));
whr.append(" ? ");
if (validOperator(dmn.getString(1)).contains("like"))
args.add("%" + dmn.getString(2) + "%");
else
args.add(dmn.getString(2));
} catch (Exception e) {
e.printStackTrace();
}
}
return new Object[] { whr.toString(), args };
}
private String validOperator(String ope) {
if (ope.equals("ilike") || ope.equals("=ilike")) {
return " like ";
}
return " = ";
}
private class RecordsLoader extends AsyncTask<String, Void, List<Object>> {
private OModel mModel;
private OSyncHelper mOdoo;
private LinearLayout mParentView;
public RecordsLoader(LinearLayout parentView, OModel model) {
mModel = model;
mOdoo = (model != null) ? model.getSyncHelper() : null;
mParentView = parentView;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
removeStatusView();
mParentView.addView(dataLoadingView());
}
@Override
protected List<Object> doInBackground(String... searchFor) {
List<Object> items = new ArrayList<Object>();
try {
if (mOdoo != null) {
ODomain domain = new ODomain();
domain.append(mDefaultDomain);
domain.add("id", "not in", mModel.ids());
ODomain localDomain = new ODomain();
if (searchFor.length > 0) {
if (mDialogListRowViewListener != null) {
ODomain filter = mDialogListRowViewListener
.onDialogSearchChange(searchFor[0]);
domain.append(filter);
localDomain.append(filter);
}
}
OFieldsHelper fields = new OFieldsHelper(mModel.fields());
items.addAll(mOdoo.dataHelper().searchRecords(fields,
domain, 20));
Object[] params = getWhere(localDomain);
@SuppressWarnings("unchecked")
List<String> args = (List<String>) params[1];
if (params[0].toString().length() > 0) {
items.addAll(mModel.select(params[0].toString(),
args.toArray(new String[args.size()])));
} else {
items.addAll(mModel.select());
}
}
} catch (Exception e) {
e.printStackTrace();
}
return items;
}
@Override
protected void onPostExecute(List<Object> result) {
super.onPostExecute(result);
removeStatusView();
mObjects.clear();
if (result.size() > 0) {
mObjects.addAll(result);
} else {
mParentView.addView(emptyList());
}
mAdapter.notifiyDataChange(mObjects);
}
private void removeStatusView() {
int loading_view = mParentView.indexOfChild(mParentView
.findViewWithTag(ID_DIALOG_LOADING));
if (loading_view > -1)
mParentView.removeViewAt(loading_view);
int empty_item = mParentView.indexOfChild(mParentView
.findViewWithTag(ID_DIALOG_EMPTY_LIST));
if (empty_item > -1)
mParentView.removeViewAt(empty_item);
}
}
public interface DialogListRowViewListener {
public View onDialogListRowGetView(ODataRow data, int position,
View view, ViewGroup parent);
public ODomain onDialogSearchChange(String filter);
public void bindDisplayLayoutLoad(ODataRow data, View layout);
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (s.length() > 3) {
if (mRecordsLoader != null) {
mRecordsLoader.cancel(true);
}
mRecordsLoader = new RecordsLoader(mParentView, mModel);
mRecordsLoader.execute(s.toString());
}
if (s.length() == 0) {
if (mRecordsLoader != null) {
mRecordsLoader.cancel(true);
}
mRecordsLoader = new RecordsLoader(mParentView, mModel);
mRecordsLoader.execute();
}
}
@Override
public void afterTextChanged(Editable s) {
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
mVisibile = false;
mRecord = (ODataRow) mObjects.get(position);
mDialog.dismiss();
reInit(mEditMode);
if (!mRecord.contains(OColumn.ROW_ID)) {
CreatingRecord creating = new CreatingRecord(mRecord);
creating.execute();
} else {
if (mOnChangeCallback != null) {
mOnChangeCallback.onValueChange(mRecord);
}
if (mOnDomainFilterCallbacks != null) {
mColumnDomain.setValue(mRecord.get(OColumn.ROW_ID));
mOnDomainFilterCallbacks.onFieldValueChanged(mColumnDomain);
}
}
}
private class CreatingRecord extends AsyncTask<Void, Void, Void> {
private ProgressDialog dialog;
private ODataRow record;
public CreatingRecord(ODataRow row) {
record = row;
dialog = new ProgressDialog(mContext);
dialog.setMessage(mContext.getString(R.string.title_working));
}
@Override
protected void onPreExecute() {
super.onPreExecute();
dialog.setCancelable(false);
dialog.show();
}
@Override
protected Void doInBackground(Void... params) {
try {
Thread.sleep(300);
} catch (Exception e) {
e.printStackTrace();
}
mModel.getSyncHelper().dataHelper().quickCreateLocalRecord(record);
return null;
}
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
dialog.dismiss();
mRecord.put(OColumn.ROW_ID,
mModel.selectRowId(mRecord.getInt("id")));
if (mOnChangeCallback != null) {
mOnChangeCallback.onValueChange(mRecord);
}
if (mOnDomainFilterCallbacks != null) {
mColumnDomain.setValue(mRecord.get(OColumn.ROW_ID));
mOnDomainFilterCallbacks.onFieldValueChanged(mColumnDomain);
}
}
}
public ODataRow getValue() {
return mRecord;
}
public void setOnChangeCallback(OnChangeCallback callback) {
mOnChangeCallback = callback;
}
public void setOnFilterDomainCallBack(ColumnDomain domain,
OnDomainFilterCallbacks callback) {
mColumnDomain = domain;
mOnDomainFilterCallbacks = callback;
}
}