package gueei.binding.widgets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import gueei.binding.AttributeBinder;
import gueei.binding.Binder;
import gueei.binding.BindingLog;
import gueei.binding.CollectionChangedEventArg;
import gueei.binding.CollectionObserver;
import gueei.binding.ConstantObservable;
import gueei.binding.IBindableView;
import gueei.binding.IObservable;
import gueei.binding.IObservableCollection;
import gueei.binding.ISyntaxResolver.SyntaxResolveException;
import gueei.binding.InnerFieldObservable;
import gueei.binding.Observer;
import gueei.binding.ViewAttribute;
import gueei.binding.collections.ArrayListObservable;
import gueei.binding.collections.ObservableCollection;
import gueei.binding.utility.ObservableCollectionMultiplexer;
import gueei.binding.utility.ObservableMultiplexer;
import gueei.binding.utility.WeakList;
import gueei.binding.viewAttributes.templates.LayoutRowChild;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;
public class BindableTableLayout extends TableLayout implements IBindableView<BindableTableLayout> {
private WeakList<Object> currentRowList = null;
private CollectionObserver collectionObserver = null;
private ObservableCollection<Object> rowList = null;
private LayoutRowChild rowChild = null;
private boolean updateEnabled = true;
private Observer observer = new Observer() {
@Override
public void onPropertyChanged(IObservable<?> prop, Collection<Object> initiators) {
if( initiators == null || currentRowList == null)
return;
int pos = -1;
for(Object i : initiators) {
pos = currentRowList.indexOf(i);
if( pos > 0)
break;
}
if( pos < 0)
return;
Object parent = currentRowList.get(pos);
ArrayList<Object> list = new ArrayList<Object>();
list.add(parent);
removeRows(list);
insertRow(pos, parent);
}
};
private ObservableMultiplexer<Object> observableChildLayoutID = new ObservableMultiplexer<Object>(observer);
private ObservableMultiplexer<Object> observableChildSpan = new ObservableMultiplexer<Object>(observer);
private ObservableCollectionMultiplexer<Object> observableCollectionRowChildren = new ObservableCollectionMultiplexer<Object>(observer);
public BindableTableLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public BindableTableLayout(Context context) {
super(context);
init();
}
private void init() {
}
@Override
protected void onDetachedFromWindow() {
if(rowList != null) {
rowList.unsubscribe(collectionObserver);
collectionObserver = null;
}
rowList = null;
observableChildLayoutID.clear();
observableChildSpan.clear();
observableCollectionRowChildren.clear();
currentRowList= null;
super.onDetachedFromWindow();
}
private void createItemSourceList(ObservableCollection<Object> newRowList) {
if( rowList != null && collectionObserver != null)
rowList.unsubscribe(collectionObserver);
collectionObserver = null;
rowList = newRowList;
if(newRowList==null)
return;
currentRowList = null;
collectionObserver = new CollectionObserver() {
@SuppressWarnings("unchecked")
@Override
public void onCollectionChanged(
IObservableCollection<?> collection,
CollectionChangedEventArg args,
Collection<Object> initiators) {
rowListChanged(args, (ObservableCollection<Object>)collection);
}
};
rowList.subscribe(collectionObserver);
newRowList(rowList);
}
private void newRowList(ObservableCollection<Object> rows) {
this.removeAllViews();
observableChildLayoutID.clear();
observableChildSpan.clear();
observableCollectionRowChildren.clear();
if( rows == null) {
currentRowList= null;
return;
}
currentRowList = new WeakList<Object>();
for( int pos=0; pos < rows.size(); pos ++ ) {
Object item = rows.getItem(pos);
insertRow(pos, item);
}
for( int pos=0; pos < rows.size(); pos ++ ) {
Object item = rows.getItem(pos);
currentRowList.add(item);
}
}
private void rowListChanged(CollectionChangedEventArg e, ObservableCollection<Object> rows) {
if( e == null)
return;
int pos=-1;
switch( e.getAction()) {
case Add:
pos = e.getNewStartingIndex();
for(Object row : e.getNewItems()) {
insertRow(pos, row);
pos++;
}
break;
case Remove:
removeRows(e.getOldItems());
break;
case Replace:
removeRows(e.getOldItems());
pos = e.getNewStartingIndex();
if( pos < 0)
pos=0;
for(Object item : e.getNewItems()) {
insertRow(pos, item);
pos++;
}
break;
case Reset:
newRowList(rows);
break;
case Move:
// currently the observable array list doesn't create this action
throw new IllegalArgumentException("move not implemented");
default:
throw new IllegalArgumentException("unknown action " + e.getAction().toString());
}
if( rows == null)
return;
for( pos=0; pos < rows.size(); pos ++ ) {
Object item = rows.getItem(pos);
currentRowList.add(item);
}
}
private ViewAttribute<BindableTableLayout, Object> ItemSourceAttribute =
new ViewAttribute<BindableTableLayout, Object>(Object.class, BindableTableLayout.this, "ItemSource") {
@SuppressWarnings("unchecked")
@Override
protected void doSetAttributeValue(Object newValue) {
if( !(newValue instanceof ArrayListObservable<?> ))
return;
rowList = (ArrayListObservable<Object>)newValue;
if( rowChild != null )
createItemSourceList(rowList);
}
@Override
public Object get() {
return rowList;
}
};
private ViewAttribute<BindableTableLayout, Object> RowChildAttribute =
new ViewAttribute<BindableTableLayout, Object>(Object.class, BindableTableLayout.this, "RowChild"){
@Override
protected void doSetAttributeValue(Object newValue) {
rowChild = null;
if( newValue instanceof LayoutRowChild ) {
rowChild = (LayoutRowChild) newValue;
if( rowList != null )
createItemSourceList(rowList);
}
}
@Override
public Object get() {
return rowChild;
}
};
private ViewAttribute<BindableTableLayout, Boolean> ItemUpdateEnabledAttribute =
new ViewAttribute<BindableTableLayout, Boolean>(Boolean.class, BindableTableLayout.this, "UpdateEnabled"){
@Override
protected void doSetAttributeValue(Object newValue) {
if( newValue == null ) {
updateEnabled = true;
}
else if( newValue instanceof Boolean ) {
Boolean value = (Boolean) newValue;
updateEnabled = value;
if(updateEnabled) {
BindableTableLayout.this.invalidate();
}
}
}
@Override
public Boolean get() {
return updateEnabled;
}
};
@Override
protected void onDraw(Canvas canvas) {
if( !updateEnabled )
return;
super.onDraw(canvas);
}
@Override
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {
if( !updateEnabled )
return;
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
public ViewAttribute<BindableTableLayout, ?> createViewAttribute(String attributeId) {
if (attributeId.equals("itemSource")) return ItemSourceAttribute;
if (attributeId.equals("rowChild")) return RowChildAttribute;
if (attributeId.equals("updateEnabled")) return ItemUpdateEnabledAttribute;
return null;
}
private void insertRow(int pos, Object row) {
if( rowChild == null )
return;
IObservable<?> childDataSource = null;
InnerFieldObservable<?> ifo = new InnerFieldObservable<Object>(rowChild.getChildDataSource());
if (ifo.createNodes(row)) {
childDataSource = ifo;
} else {
Object rawField = null;
try {
rawField = Binder.getSyntaxResolver().getFieldForModel(rowChild.getChildDataSource(), row);
} catch (SyntaxResolveException e) {
BindingLog.exception("BindableTableLayout.insertRow", e);
}
if (rawField instanceof IObservable<?>)
childDataSource = (IObservable<?>)rawField;
}
TableRow trow = createRow(childDataSource, pos, row);
currentRowList.add(pos, row);
this.addView(trow,pos);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private TableRow createRow(IObservable<?> childDataSource, int pos, Object row) {
TableRow tableRow = new TableRow(getContext());
InnerFieldObservable<?> ifo = null;
if( childDataSource == null) {
TextView textView = new TextView(getContext());
textView.setText("binding error - row: " + pos + " has no child datasource - please check binding:itemPath or the layout id in viewmodel");
textView.setTextColor(Color.RED);
tableRow.addView(textView);
} else {
Object dataSource = childDataSource.get();
if( dataSource instanceof ArrayListObservable<?>) {
ArrayListObservable<?> childItems = (ArrayListObservable<?>)dataSource;
// we might have to change the current row if the child datasource changes
observableCollectionRowChildren.add(childItems,row);
int col = 0;
for( Object childItem : childItems) {
int colSpan = 1;
int layoutId = rowChild.getLayoutId();
if( layoutId < 1 && rowChild.getLayoutName() != null ) {
IObservable<?> observable = null;
ifo = new InnerFieldObservable<Object>(rowChild.getLayoutName() );
if (ifo.createNodes(childItem)) {
observable = ifo;
} else {
Object rawField = null;
try {
rawField = Binder.getSyntaxResolver().getFieldForModel(rowChild.getLayoutName(), childItem);
} catch (SyntaxResolveException e) {
BindingLog.exception("BindableTableLayout.createRow", e);
}
if (rawField instanceof IObservable<?>)
observable = (IObservable<?>)rawField;
else if (rawField!=null)
observable= new ConstantObservable(rawField.getClass(), rawField);
}
if( observable != null) {
Object obj = observable.get();
if(obj instanceof Integer) {
observableChildLayoutID.add(observable, row);
layoutId = (Integer)obj;
}
}
}
View child = null;
if( childItem != null ) {
if( layoutId < 1 ) {
TextView textView = new TextView(getContext());
textView.setText("binding error - pos: " + pos + " has no layout - please check binding:itemPath or the layout id in viewmodel");
textView.setTextColor(Color.RED);
child = textView;
} else {
Binder.InflateResult result = Binder.inflateView(getContext(), layoutId, tableRow, false);
for(View view: result.processedViews){
AttributeBinder.getInstance().bindView(getContext(), view, childItem);
}
child = result.rootView;
}
}
TableRow.LayoutParams params = null;
// check if there is a colspan
if( rowChild.getColspanName() != null ) {
IObservable<?> observable = null;
ifo = new InnerFieldObservable(rowChild.getColspanName());
if (ifo.createNodes(childItem)) {
observable = ifo;
} else {
Object rawField = null;
try {
rawField = Binder.getSyntaxResolver().getFieldForModel(rowChild.getColspanName(), childItem);
} catch (SyntaxResolveException e) {
BindingLog.exception("BindableTableLayout.createRow", e);
}
if (rawField instanceof IObservable<?>)
observable = (IObservable<?>)rawField;
else if (rawField!=null)
observable= new ConstantObservable(rawField.getClass(), rawField);
}
if( observable != null) {
Object obj = observable.get();
if(obj instanceof Integer) {
observableChildSpan.add(observable, row);
colSpan = (Integer)obj;
}
}
}
if( tableRow.getLayoutParams() != null ) {
params = new TableRow.LayoutParams(tableRow.getLayoutParams());
}
if( child != null ) {
// ViewGroup.MarginLayoutParams ctor doesn't honor the margins
// so this is a workaround - we have to use the child - not the row
TableRow.LayoutParams rowParams = (TableRow.LayoutParams)child.getLayoutParams();
if( rowParams != null ) {
ViewGroup.MarginLayoutParams margins = new ViewGroup.MarginLayoutParams(rowParams);
margins.setMargins(rowParams.leftMargin, rowParams.topMargin,
rowParams.rightMargin, rowParams.bottomMargin);
params = new TableRow.LayoutParams(margins);
}
}
if( child != null ) {
if( params == null ) {
tableRow.addView(child);
} else {
params.span = colSpan;
params.column = col;
tableRow.setLayoutParams(params);
tableRow.addView(child, params);
}
}
col = col + colSpan;
}
}
}
return tableRow;
}
private void removeRows(List<?> deleteList) {
if( deleteList == null || deleteList.size() == 0 || currentRowList == null)
return;
ArrayList<Object> currentPositionList = new ArrayList<Object>(Arrays.asList(currentRowList.toArray()));
for(Object row : deleteList){
int pos = currentPositionList.indexOf(row);
currentRowList.remove(row);
observableChildLayoutID.removeParent(row);
observableChildSpan.removeParent(row);
observableCollectionRowChildren.removeParent(row);
currentPositionList.remove(row);
if( pos > -1 && pos < this.getChildCount())
this.removeViewAt(pos);
}
}
}