package fr.lteconsulting.hexa.client.ui.miracle;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import com.google.gwt.dom.client.Element;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.Widget;
import fr.lteconsulting.hexa.client.ui.miracle.Edits.Editor;
import fr.lteconsulting.hexa.client.ui.treetable.Row;
import fr.lteconsulting.hexa.client.ui.treetable.TreeTable;
import fr.lteconsulting.hexa.client.ui.treetable.event.TableCellClickEvent.TableCellClickHandler;
import fr.lteconsulting.hexa.client.ui.treetable.event.TableHeaderClickEvent.TableHeaderClickHandler;
public class DynArrayInTreeTable<T> implements Prints<Iterable<T>>, DynArrayManager<T>, HasColumns<T>
{
TreeTable table;
RefMng<T> refMng;
ArrayList<ColumnMng<T>> columns = new ArrayList<ColumnMng<T>>();
Comparator<T> userComparator = null;
// edition state
private class EditionState
{
int editedCol = -1;
T editedObject = null;
Printer editedPrinter = null;
Editor editedEditor = null;
void close()
{
if( editedEditor != null )
editedEditor.close();
editedEditor = null;
}
}
EditionState edition = null;
public DynArrayInTreeTable( TreeTable table, RefMng<T> refMng )
{
this.table = table;
this.refMng = refMng;
table.addTableHeaderClickHandler( tableHeaderClickHandler );
table.addTableCellClickHandler( tableCellClickHandler );
table.addDomHandler( onTableKeyUp, KeyDownEvent.getType() );
table.addDomHandler( onTableMouseDown, MouseDownEvent.getType() );
}
@Override
public void addColumn( PrintsOn<T> column, Edits<T> editMng, CellClickMng<T> clickMng, PrintsOn<Void> hdrPrintsOn, CellClickMng<Void> hdrClickMng )
{
columns.add( new ColumnMng<T>( column, editMng, clickMng, hdrPrintsOn, hdrClickMng ) );
}
@Override
public void printHeaders()
{
int nbCols = columns.size();
for( int i = 0; i < nbCols; i++ )
columns.get( i ).hdrPrintsOn.print( null, new HdrInTreeTablePrinter( table, i ) );
}
@Override
public void print( Iterable<T> data )
{
killCurrentEdit();
table.emptyTable();
if( data == null )
return;
// sort if needed
if( userComparator != null )
{
ArrayList<T> tmp = new ArrayList<T>();
for( T t : data )
{
int ins = Collections.binarySearch( tmp, t, userComparator );
if( ins >= 0 )
tmp.add( ins, t );
else
tmp.add( -ins - 1, t );
}
data = tmp;
}
// each row
for( T d : data )
{
Row row = table.addRow( null );
row.setRef( refMng.getRef( d ) );
printRow( d, row );
}
}
@Override
public void updateRow( T object )
{
// find the row associated with this object
int objectRef = refMng.getRef( object );
Row item = getRow( objectRef );
if( edition != null && refMng.getRef( edition.editedObject ) == objectRef )
killCurrentEdit();
Row insPos = userComparator != null ? getInsertPoint( object ) : item;
// insert or move the row to the right place and create a cell printer
if( item == null )
{
// new
if( insPos != null )
insPos = insPos.addBefore();
else
insPos = table.addRow( null );
insPos.setRef( refMng.getRef( object ) );
}
else
{
if( insPos != item )
{
// GWT.log( "is at row " + row + " but should be at " + insPos
// );
item.moveBefore( insPos );
insPos = item;
}
}
// print the row
printRow( object, insPos );
}
private Row getInsertPoint( T object )
{
ArrayList<Row> items = table.getItemChilds( null );
int nbRows = items.size();
int objectRef = refMng.getRef( object );
if( userComparator != null )
{
for( Row item : items )
{
int refAtRow = item.getRef();
if( userComparator.compare( object, refMng.getObject( refAtRow ) ) <= 0 )
{
if( refAtRow == objectRef )
{
// keep the same place only if lesser than the nxt item
// or if there is no next
if( items.indexOf( item ) == nbRows - 1 )
{
return item;
}
else
{
int refAtNextRow = getRefAtRow( item.getNextSiblingItem() );
if( userComparator.compare( object, refMng.getObject( refAtNextRow ) ) <= 0 )
return item;
}
}
else
{
return item;
}
}
}
}
return null;
}
@Override
public void deleteRow( int ref )
{
Row item = getRow( ref );
if( item == null )
return;
// to reset the edition state, just in case... (an only if we are
// editing on the row we delete)
if( edition != null && refMng.getRef( edition.editedObject ) == ref )
killCurrentEdit();
item.remove();
}
@Override
public void setComparator( Comparator<T> comparator )
{
userComparator = comparator;
sortAndPrint( comparator );
}
public void sortAndPrint( final Comparator<T> userComparator )
{
class It
{
Row item;
T object;
It( Row item, T object )
{
this.object = object;
this.item = item;
}
}
;
ArrayList<It> its = new ArrayList<It>();
// get all objects from the table
ArrayList<Row> childs = table.getItemChilds( null );
for( Row item : childs )
{
int ref = getRefAtRow( item );
its.add( new It( item, refMng.getObject( ref ) ) );
}
// sort them
Collections.sort( its, new Comparator<It>()
{
@Override
public int compare( It o1, It o2 )
{
return userComparator.compare( o1.object, o2.object );
}
} );
// now reorder the lines
for( int j = 0; j < its.size(); j++ )
{
// item at position j ?
Row atJ = table.getItemChilds( null ).get( j );
// needed item is its[i].item
Row neededItem = its.get( j ).item;
if( atJ != neededItem )
{
// move its[i].item before item at position i
neededItem.moveBefore( atJ );
}
}
}
private int getRefAtRow( Row item )
{
return item.getRef();
}
// return the row index of the row associated to the object referenced as
// objectRef
// returns null if the row is not found
private Row getRow( int objectRef )
{
return table.getItemForRef( objectRef );
}
// returns a printer that can be used for the next call. a new one can be
// created
// WARNING : assumes the printer item is correctly initialized
private void printRow( T object, Row row )
{
// to reset the edition state, just in case...
if( edition != null && refMng.getRef( edition.editedObject ) == refMng.getRef( object ) )
killCurrentEdit();
// writes the element reference on the corresponding row element
// note that this does not create a hard link to the referenced object
// so
// no garbage is created here
row.setRef( refMng.getRef( object ) );
// each column
for( int i = 0; i < columns.size(); i++ )
columns.get( i ).prints.print( object, new CellInTreeTablePrinter( row, i ) );
}
private boolean beginEdit( Row item, int col )
{
int ref = getRefAtRow( item );
if( edition != null && ref == refMng.getRef( edition.editedObject ) )
return true; // already began !!!
killCurrentEdit();
Edits<T> editMng = columns.get( col ).edits;
if( editMng == null )
return false;
// find the T object that has been clicked
T object = refMng.getObject( ref );
assert object != null;
if( object == null )
return false;
Element td = item.getTdElement( col );
edition = new EditionState();
// initialize and set the current edit
edition.editedCol = col;
edition.editedObject = object;
// create a Printer corresponding to this cell
edition.editedPrinter = new CellInTreeTablePrinter( item, col );
// create an editor and init it
// -2 to remove padding : HACK HACK HACK
edition.editedEditor = editMng.createEditor( edition.editedObject, edition.editedPrinter, onEdit, td.getOffsetWidth() - 2, td.getClientHeight() - 2 );
return true;
}
private static class CellPos
{
Row item;
int col;
CellPos( Row item, int col )
{
this.item = item;
this.col = col;
}
}
private final MouseDownHandler onTableMouseDown = new MouseDownHandler()
{
@Override
public void onMouseDown( MouseDownEvent event )
{
Element source = DOM.eventGetTarget( Event.as( event.getNativeEvent() ) );
// try to see if it's not on a th element
int hdr = table.getEventTargetHeaderIdx( source );
if( hdr < 0 )
return;
DragDrop.initiate( source, onDragDrop, hdr, Event.as( event.getNativeEvent() ) );
}
};
DragDrop.Callback<Integer> onDragDrop = new DragDrop.Callback<Integer>()
{
@Override
public String getGhostInnerHTML( Integer cookie, Element source )
{
class TempPrinter implements Printer
{
String html = null;
@Override
public void setWidget( Widget widget )
{
assert false;
}
@Override
public void setText( String text )
{
html = text;
}
@Override
public void setHTML( String html )
{
this.html = html;
}
}
TempPrinter printer = new TempPrinter();
columns.get( cookie ).hdrPrintsOn.print( null, printer );
// Due to asynchronism, it may be possible that the printer did not
// yet print anything, well... Too late, we use the source element
// instead !!
if( printer.html == null )
return "<div style='background-color:rgba(120,120,120,0.4);'>" + source.getInnerHTML() + "</div>";
return "<div style='background-color:rgba(120,120,120,0.4);width:20px;height:20px;'>" + printer.html + "</div>";
}
@Override
public void onDragDropFinished( Integer cookie, Element source, Element destination )
{
int newPos = table.getEventTargetHeaderIdx( destination );
if( newPos < 0 )
return;
ColumnMng<T> dum = columns.get( cookie );
columns.set( cookie, columns.get( newPos ) );
columns.set( newPos, dum );
// Redraw headers
printHeaders();
// Redraw all objects in the table
ArrayList<Row> items = table.getItemChilds( null );
for( Row item : items )
{
int ref = item.getRef();
printRow( refMng.getObject( ref ), item );
}
}
};
private final Edits.Callback onEdit = new Edits.Callback()
{
@Override
public void cancelEdition()
{
killCurrentEdit();
}
@Override
public void validateEdition( boolean fJumpNext )
{
// just forget the edition state, but don't redraw
// that means we let the client continue drawing on its cell
if( edition != null && fJumpNext )
{
CellPos next = getNextPos( getRow( refMng.getRef( edition.editedObject ) ), edition.editedCol );
edition.close();
edition = null;
beginEdit( next.item, next.col );
}
}
};
// wait a bit to avoid ui jitter...
// use refMng in case the data change during the time waited
private void killCurrentEdit()
{
if( edition == null )
return;
// reprints the cell, with uptodate data
columns.get( edition.editedCol ).prints.print( refMng.getObject( refMng.getRef( edition.editedObject ) ), edition.editedPrinter );
edition.close();
edition = null;
}
CellPos getNextPos( Row item, int col )
{
// either on the same row
for( int c = col + 1; c < columns.size(); c++ )
{
if( columns.get( c ).edits != null )
return new CellPos( item, c );
}
// or on the next
item = item.getNextSiblingItem();
for( int c = 0; c < columns.size(); c++ )
{
if( columns.get( c ).edits != null )
return new CellPos( item, c );
}
return null;
}
CellPos getPrevPos( Row item, int col )
{
// either on the same row
for( int c = col - 1; c >= 0; c-- )
{
if( columns.get( c ).edits != null )
return new CellPos( item, c );
}
// or on the previous
item = item.getPrevSiblingItem();
for( int c = columns.size() - 1; c >= 0; c-- )
{
if( columns.get( c ).edits != null )
return new CellPos( item, c );
}
return null;
}
private final KeyDownHandler onTableKeyUp = new KeyDownHandler()
{
@Override
public void onKeyDown( KeyDownEvent event )
{
if( event.getNativeKeyCode() == KeyCodes.KEY_ESCAPE )
{
if( edition == null )
return;
event.stopPropagation();
event.preventDefault();
killCurrentEdit();
}
else if( event.getNativeKeyCode() == KeyCodes.KEY_TAB )
{
if( edition == null )
return;
event.stopPropagation();
event.preventDefault();
// find next cell to be edited
Row editionItem = getRow( refMng.getRef( edition.editedObject ) );
CellPos pos = null;
if( event.isShiftKeyDown() )
pos = getPrevPos( editionItem, edition.editedCol );
else
pos = getNextPos( editionItem, edition.editedCol );
if( pos != null )
{
beginEdit( pos.item, pos.col );
return;
}
}
}
};
private final TableHeaderClickHandler tableHeaderClickHandler = new TableHeaderClickHandler()
{
@Override
public void onTableHeaderClick( int column, ClickEvent clickEvent )
{
CellClickMng<Void> clickMng = columns.get( column ).hdrClickMng;
if( clickMng == null )
return;
// get the printer, and go
Printer printer = table.getHeaderPrinter( column );
clickMng.onTableClick( null, DOM.eventGetTarget( Event.as( clickEvent.getNativeEvent() ) ), printer );
}
};
private final TableCellClickHandler tableCellClickHandler = new TableCellClickHandler()
{
@Override
public void onTableCellClick( Row item, int column, ClickEvent event )
{
int clickedRef = getRefAtRow( item );
// if clicking on a cell that is in editing mode, return
if( edition != null && (refMng.getRef( edition.editedObject ) == clickedRef && edition.editedCol == column) )
return;
killCurrentEdit();
boolean fHandled = false;
// Tries edition
fHandled = beginEdit( item, column );
// If not handled, tries click manager
if( !fHandled )
{
// if there is a click manager
CellClickMng<T> clickMng = columns.get( column ).clicks;
if( clickMng != null )
{
// find the T object that has been clicked
T object = refMng.getObject( clickedRef );
if( object == null )
return;
CellInTreeTablePrinter pr = new CellInTreeTablePrinter( item, column );
fHandled = clickMng.onTableClick( object, DOM.eventGetTarget( Event.as( event.getNativeEvent() ) ), pr );
}
}
}
};
@Override
public void clearAllRows()
{
killCurrentEdit();
table.clear();
}
}