package fr.lteconsulting.hexa.client.ui.miracle; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.JsArray; import com.google.gwt.dom.client.Element; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; 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.HTMLTable.Cell; import com.google.gwt.user.client.ui.Widget; import fr.lteconsulting.hexa.client.tools.JQuery; import fr.lteconsulting.hexa.client.ui.miracle.Edits.Editor; public class DynArrayInFlexTable<T> implements Prints<Iterable<T>>, DynArrayManager<T>, HasColumns<T> { MiracleTable table; RefMng<T> refMng; ArrayList<ColumnMng<T>> columns = new ArrayList<ColumnMng<T>>(); Comparator<T> userComparator = null; // edition state private class EditionState { // int editedRow = -1; 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 DynArrayInFlexTable( MiracleTable table, RefMng<T> refMng ) { this.table = table; this.refMng = refMng; table.addClickHandler( onTableClick ); table.addDomHandler( onTableKeyUp, KeyDownEvent.getType() ); table.addMouseDownHandler( onTableMouseDown ); } @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, table.getHdrPrinter( i ) ); } @Override public void print( Iterable<T> data ) { killCurrentEdit(); table.clear( true ); if( data == null ) return; if( userComparator != null ) { ArrayList<T> tmp = new ArrayList<T>(); for( T t : data ) { int ins = Collections.binarySearch( tmp, t, userComparator ); tmp.add( -ins - 1, t ); } data = tmp; } // each row int j = 0; for( T d : data ) { printRow( d, j ); j++; } } @Override public void updateRow( T object ) { // find the row associated with this object int objectRef = refMng.getRef( object ); int row = getRow( objectRef ); if( edition != null && refMng.getRef( edition.editedObject ) == objectRef ) killCurrentEdit(); int insPos = userComparator != null ? getInsertPoint( object ) : row; // insert or move the row to the right place and create a cell printer if( row < 0 ) { // new if( insPos >= 0 ) table.insertRow( insPos ); else insPos = table.getRowCount(); } else { if( insPos != row ) { GWT.log( "is at row " + row + " but should be at " + insPos ); insPos = moveRowFor( row, insPos, objectRef ); } } // print the row printRow( object, insPos ); } // return the row it was moved to private int moveRowFor( int actual, int target, int objectRef ) { Element tr = table.getBodyElement().getChild( actual ).cast(); assert tr.getPropertyInt( "ref" ) == objectRef; // just to be sure we do what we want Element parent = tr.getParentElement().cast(); parent.removeChild( tr ); if( target < 0 ) { DOM.appendChild( parent, tr ); return parent.getChildCount() - 1; } if( target >= actual ) target--; DOM.insertChild( parent, tr, target ); return target; } @Override public void deleteRow( int ref ) { int row = getRow( ref ); if( row < 0 ) 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(); table.removeRow( row ); } @Override public void setComparator( Comparator<T> comparator ) { userComparator = comparator; sortAndPrint( comparator ); } public void sortAndPrint( final Comparator<T> userComparator ) { class It { Element tr; T object; It( int row, Element tr, T object ) { this.object = object; this.tr = tr; } } ; ArrayList<It> its = new ArrayList<It>(); // get all objects from the table int nbRows = table.getRowCount(); for( int r = 0; r < nbRows; r++ ) { Element tr = table.getRowFormatter().getElement( r ); int ref = tr.getPropertyInt( "ref" ); its.add( new It( r, tr, 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++ ) { int refAtRow = getRefAtRow( j ); int needRef = refMng.getRef( its.get( j ).object ); if( needRef != refAtRow ) { // we have to take the row and put it at its right place Element tr = its.get( j ).tr; assert tr.getPropertyInt( "ref" ) == needRef; // just to be sure // we do what we // want DOM.insertChild( tr.getParentElement(), tr, j ); } } } private int getRefAtRow( int row ) { return table.getRowFormatter().getElement( row ).getPropertyInt( "ref" ); } // return the row index of the row associated to the object referenced as // objectRef // returns -1 if the row is not found private int getRow( int objectRef ) { JsArray<Element> rows = JQuery.get().jqSelect( "tr[ref=\"" + objectRef + "\"]", table.getBodyElement() ); if( rows.length() > 1 ) return -1; // an error actually if( rows.length() == 0 ) return -1; Element tr = rows.get( 0 ); int row = DOM.getChildIndex( tr.getParentElement(), tr ); return row; } private void printRow( T object, int row ) { // to reset the edition state, just in case... if( edition != null && refMng.getRef( edition.editedObject ) == refMng.getRef( object ) ) killCurrentEdit(); // each column for( int i = 0; i < columns.size(); i++ ) columns.get( i ).prints.print( object, new CellInFlexTablePrinter( table, row, i ) ); // 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 // to avoid cases when the colummn has printed nothing and the row // doesnt exist if( table.getRowCount() <= row ) table.setText( row, 0, "" ); table.getRowFormatter().getElement( row ).setPropertyInt( "ref", refMng.getRef( object ) ); } private boolean beginEdit( int row, int col ) { if( edition != null && getRefAtRow( row ) == 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 int ref = table.getRowFormatter().getElement( row ).getPropertyInt( "ref" ); T object = refMng.getObject( ref ); assert object != null; if( object == null ) return false; Element td = table.getCellFormatter().getElement( row, col ); edition = new EditionState(); // initialize and set the current edit // edition.editedRow = row; edition.editedCol = col; edition.editedObject = object; // create a Printer corresponding to this cell edition.editedPrinter = new CellInFlexTablePrinter( table, row, col ); edition.editedPrinter = new DynamicTablePrinter( object, 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 class DynamicTablePrinter implements Printer { final CellInFlexTablePrinter printer; DynamicTablePrinter( T object, int col ) { printer = new CellInFlexTablePrinter( table, getRow( refMng.getRef( object ) ), col ); } @Override public void setWidget( Widget widget ) { printer.setWidget( widget ); } @Override public void setText( String text ) { printer.setText( text ); } @Override public void setHTML( String html ) { printer.setHTML( html ); } } private static class CellPos { int row; int col; CellPos( int row, int col ) { this.row = row; this.col = col; } } private final ClickHandler onTableClick = new ClickHandler() { @Override public void onClick( ClickEvent event ) { // event.preventDefault(); // event.stopPropagation(); Cell cell = DynArrayInFlexTable.this.table.getCellForEvent( event ); // if clicking on a cell that is in editing mode, return if( cell != null && edition != null && (refMng.getRef( edition.editedObject ) == getRefAtRow( cell.getRowIndex() ) && edition.editedCol == cell.getCellIndex()) ) return; killCurrentEdit(); if( cell == null ) { // try to see if it's not on a th element int hdr = table.getHeaderForEvent( event.getNativeEvent() ); if( hdr < 0 ) return; CellClickMng<Void> clickMng = columns.get( hdr ).hdrClickMng; if( clickMng == null ) return; // get the printer, and go Printer printer = table.getHdrPrinter( hdr ); clickMng.onTableClick( null, DOM.eventGetTarget( Event.as( event.getNativeEvent() ) ), printer ); return; } boolean fHandled = false; // Tries edition fHandled = beginEdit( cell.getRowIndex(), cell.getCellIndex() ); // If not handled, tries click manager if( !fHandled ) { // if there is a click manager CellClickMng<T> clickMng = columns.get( cell.getCellIndex() ).clicks; if( clickMng != null ) { // find the T object that has been clicked int ref = table.getRowFormatter().getElement( cell.getRowIndex() ).getPropertyInt( "ref" ); T object = refMng.getObject( ref ); if( object == null ) return; CellInFlexTablePrinter pr = new CellInFlexTablePrinter( table, cell.getRowIndex(), cell.getCellIndex() ); fHandled = clickMng.onTableClick( object, DOM.eventGetTarget( Event.as( event.getNativeEvent() ) ), pr ); } } } }; private final MouseDownHandler onTableMouseDown = new MouseDownHandler() { @Override public void onMouseDown( MouseDownEvent event ) { // try to see if it's not on a th element int hdr = table.getHeaderForEvent( event.getNativeEvent() ); if( hdr < 0 ) return; Element th = table.getEventTargetHeader( Event.as( event.getNativeEvent() ) ); DragDrop.initiate( th, onDragDrop, hdr, Event.as( event.getNativeEvent() ) ); } }; DragDrop.Callback<Integer> onDragDrop = new DragDrop.Callback<Integer>() { @Override public String getGhostInnerHTML( Integer cookie, Element source ) { // TODO Auto-generated method stub return null; } @Override public void onDragDropFinished( Integer cookie, Element source, Element destination ) { Element th = table.getElementTargetHeader( destination ); if( th == null ) return; int newPos = DOM.getChildIndex( DOM.getParent( th ), th ); ColumnMng<T> dum = columns.get( cookie ); columns.set( cookie, columns.get( newPos ) ); columns.set( newPos, dum ); printHeaders(); // Redraw all objects in the table int nbRows = table.getRowCount(); for( int r = 0; r < nbRows; r++ ) { Element tr = table.getRowFormatter().getElement( r ); int ref = tr.getPropertyInt( "ref" ); printRow( refMng.getObject( ref ), r ); } } }; 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.row, 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 // cols.get( edition.editedCol ).print( edition.editedObject, // edition.editedPrinter ); // reprints the cell, with uptodate data columns.get( edition.editedCol ).prints.print( refMng.getObject( refMng.getRef( edition.editedObject ) ), edition.editedPrinter ); edition.close(); edition = null; } private int getInsertPoint( T object ) { int nbRows = table.getRowCount(); int objectRef = refMng.getRef( object ); if( userComparator != null ) { // search the insert position for( int i = 0; i < nbRows; i++ ) { int refAtRow = getRefAtRow( i ); if( userComparator.compare( object, refMng.getObject( refAtRow ) ) <= 0 ) { if( refAtRow == objectRef ) { // keep the same place only if lesser than th nxt item // or if there is no next if( i + 1 == nbRows ) { return i; } else { int refAtNextRow = getRefAtRow( i + 1 ); if( userComparator.compare( object, refMng.getObject( refAtNextRow ) ) <= 0 ) return i; } } else { return i; } } } } return -1; } CellPos getNextPos( int row, 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( row, c ); } // or on the next row = (row + 1) % table.getRowCount(); for( int c = 0; c < columns.size(); c++ ) { if( columns.get( c ).edits != null ) return new CellPos( row, c ); } return null; } CellPos getPrevPos( int row, int col ) { // either on the same row for( int c = col - 1; c >= 0; c-- ) { if( columns.get( c ).edits != null ) return new CellPos( col, c ); } // or on the previous row = (row + table.getRowCount() - 1) % table.getRowCount(); for( int c = columns.size() - 1; c >= 0; c-- ) { if( columns.get( c ).edits != null ) return new CellPos( row, 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 int editionRow = getRow( refMng.getRef( edition.editedObject ) ); CellPos pos = null; if( event.isShiftKeyDown() ) pos = getPrevPos( editionRow, edition.editedCol ); else pos = getNextPos( editionRow, edition.editedCol ); if( pos != null ) { beginEdit( pos.row, pos.col ); return; } } } }; @Override public void clearAllRows() { // to reset the edition state, just in case... killCurrentEdit(); table.clear( true ); } }