package fr.lteconsulting.hexa.client.ui.treetable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.core.shared.GWT;
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.DoubleClickEvent;
import com.google.gwt.event.dom.client.DoubleClickHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.uibinder.client.UiConstructor;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Focusable;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.impl.FocusImpl;
import fr.lteconsulting.hexa.client.tools.JQuery;
import fr.lteconsulting.hexa.client.ui.containers.CustomPanel;
import fr.lteconsulting.hexa.client.ui.miracle.Printer;
import fr.lteconsulting.hexa.client.ui.treetable.event.TableCellClickEvent;
import fr.lteconsulting.hexa.client.ui.treetable.event.TableCellClickEvent.TableCellClickHandler;
import fr.lteconsulting.hexa.client.ui.treetable.event.TableCellDoubleClickEvent;
import fr.lteconsulting.hexa.client.ui.treetable.event.TableCellDoubleClickEvent.TableCellDoubleClickHandler;
import fr.lteconsulting.hexa.client.ui.treetable.event.TableHeaderClickEvent;
import fr.lteconsulting.hexa.client.ui.treetable.event.TableHeaderClickEvent.TableHeaderClickHandler;
public class TreeTable extends Composite implements Focusable
{
interface BasicImageBundle extends ClientBundle
{
@Source( "16-arrow-down.png" )
ImageResource treeMinus();
@Source( "16-arrow-right.png" )
ImageResource treePlus();
@Source( "blank16.png" )
ImageResource blank();
}
private static BasicImageBundle BUNDLE;
private BasicImageBundle basicBundle()
{
if( BUNDLE == null )
BUNDLE = GWT.create( BasicImageBundle.class );
return BUNDLE;
}
final int treePadding = 25;
private CustomPanel customPanel;
final ImageResource treeMinus;
final ImageResource treePlus;
final ImageResource blankImage;
// can be : sub item added, sub item removed, expanded, shrinked
public interface IItemStateCallback
{
void onItemStateChange();
}
Element m_decorator;
Element m_table;
Element m_head;
Element m_body;
Element m_headerRow = null;
Row m_rootItem = new Row( this );
int m_nbColumns = 0;
String[] m_headers = null;
String m_rowTemplate = "";
HashMap<Element, Widget> m_widgets = new HashMap<Element, Widget>();
@UiConstructor
public TreeTable( ImageResource treeMinus, ImageResource treePlus )
{
this( treeMinus, treePlus, null );
}
public TreeTable( ImageResource treeMinus, ImageResource treePlus, ImageResource blankImage )
{
if( treeMinus == null )
treeMinus = basicBundle().treeMinus();
if( treePlus == null )
treePlus = basicBundle().treePlus();
if( blankImage == null )
blankImage = basicBundle().blank();
this.treeMinus = treeMinus;
this.treePlus = treePlus;
this.blankImage = blankImage;
m_decorator = DOM.createDiv();
m_decorator.setClassName( "tableDecorator" );
customPanel = new CustomPanel( m_decorator );
initWidget( customPanel );
setTabIndex( 0 );
m_table = DOM.createTable();
m_table.setClassName( "TreeTable" );
m_decorator.appendChild( m_table );
m_head = DOM.createTHead();
m_table.appendChild( m_head );
m_body = DOM.createTBody();
m_table.appendChild( m_body );
addDomHandler( new ClickHandler()
{
@Override
public void onClick( ClickEvent event )
{
Element td = getEventTargetCell( Event.as( event.getNativeEvent() ) );
if( td == null )
{
Element th = getEventTargetHeader( DOM.eventGetTarget( Event.as( event.getNativeEvent() ) ) );
if( th == null )
return;
int column = DOM.getChildIndex( m_headerRow, th );
fireEvent( new TableHeaderClickEvent( column, event ) );
return;
}
Element tr = DOM.getParent( td );
int column = DOM.getChildIndex( tr, td );
Row item = (Row) tr.getPropertyObject( "linkedItem" );
if( item != null )
{
// if hit the tree arrow (which is the <img> first child of
// <td>
if( column == 0 && event.getNativeEvent().getEventTarget().<Element> cast() == td.getFirstChildElement() )
item.setExpanded( !item.getExpanded() );
else
fireEvent( new TableCellClickEvent( item, column, event ) );
}
}
}, ClickEvent.getType() );
addDomHandler( new DoubleClickHandler()
{
@Override
public void onDoubleClick( DoubleClickEvent event )
{
Element td = getEventTargetCell( Event.as( event.getNativeEvent() ) );
if( td == null )
return;
Element tr = DOM.getParent( td );
int column = DOM.getChildIndex( tr, td );
Row item = (Row) tr.getPropertyObject( "linkedItem" );
if( item != null )
{
// if hit the tree arrow (which is the <img> first child of
// <td>
if( !(column == 0 && event.getNativeEvent().getEventTarget().<Element> cast() == td.getFirstChildElement()) )
fireEvent( new TableCellDoubleClickEvent( item, column, event ) );
}
}
}, DoubleClickEvent.getType() );
}
public TreeTable()
{
this( null, null, null );
}
public HandlerRegistration addTableHeaderClickHandler( TableHeaderClickHandler handler )
{
return addHandler( handler, TableHeaderClickEvent.getType() );
}
public HandlerRegistration addTableCellClickHandler( TableCellClickHandler handler )
{
return addHandler( handler, TableCellClickEvent.getType() );
}
public HandlerRegistration addTableCellDoubleClickHandler( TableCellDoubleClickHandler handler )
{
return addHandler( handler, TableCellDoubleClickEvent.getType() );
}
public void clear()
{
emptyTable();
}
public int getEventTargetHeaderIdx( Element th )
{
th = getEventTargetHeader( th );
if( th == null )
return -1;
return DOM.getChildIndex( m_headerRow, th );
}
protected Element getEventTargetHeader( Element th )
{
for( ; th != null; th = DOM.getParent( th ) )
{
if( th.getTagName().equalsIgnoreCase( "th" ) )
{
Element head = DOM.getParent( th );
if( head == m_headerRow )
return th;
}
// If we run into this table's head, we're out of options.
if( th == m_headerRow )
{
return null;
}
}
return null;
}
protected Element getEventTargetCell( Event event )
{
Element td = DOM.eventGetTarget( event );
for( ; td != null; td = DOM.getParent( td ) )
{
// If it's a TD, it might be the one we're looking for.
if( td.getTagName().equalsIgnoreCase( "td" ) )
{
// Make sure it's directly a part of this table before returning
// it
Element tr = DOM.getParent( td );
Element body = DOM.getParent( tr );
if( body == m_body )
return td;
}
// If we run into this table's body, we're out of options.
if( td == m_body )
return null;
}
return null;
}
public void emptyTable()
{
for( Entry<Element, Widget> entry : m_widgets.entrySet() )
customPanel.remove( entry.getValue() );
m_widgets.clear();
m_rootItem = new Row( this );
m_body.setInnerText( "" );
}
void clearCell( Element td )
{
clearCellWidget( td );
clearCellText( td );
}
void clearCellWidget( Element td )
{
Widget old = m_widgets.get( td );
if( old != null )
removeWidget( td, old );
}
private void clearCellText( Element td )
{
td.setInnerText( "" );
}
void addWidget( Element td, Widget w )
{
m_widgets.put( td, w );
customPanel.addIn( td, w );
}
private void removeWidget( Element td, Widget w )
{
customPanel.remove( w );
m_widgets.remove( td );
}
public Row getItemForRef( int ref )
{
JsArray<Element> rows = JQuery.get().jqSelect( "tr[ref=\"" + ref + "\"]", m_body );
if( rows.length() != 1 )
return null;
Element tr = rows.get( 0 );
if( tr == null )
return null;
Object item = tr.getPropertyObject( "linkedItem" );
return (Row) item;
}
public ArrayList<Row> getItemChilds( Row item )
{
if( item == null )
item = m_rootItem;
return item.getChilds();
}
private class HeaderPrinter implements Printer
{
final int column;
HeaderPrinter( int column )
{
this.column = column;
}
@Override
public void setWidget( Widget widget )
{
assert false;
setHeader( column, "WIDGET SHOULD BE HERE, NOT IMPLEMENTED" );
}
@Override
public void setText( String text )
{
setHeader( column, text );
}
@Override
public void setHTML( String html )
{
setHeader( column, html + " (html not implemented)" );
}
}
public Printer getHeaderPrinter( int column )
{
return new HeaderPrinter( column );
}
public void setColumnWidth( int column, int width )
{
DOM.getChild( m_headerRow, column ).setAttribute( "width", width + "px" );
}
public void setHeader( int col, String header )
{
if( col >= m_nbColumns )
{
String[] newHeaders = new String[Math.max( col + 1, m_nbColumns )];
for( int i = 0; i < m_nbColumns; i++ )
newHeaders[i] = m_headers[i];
m_headers = newHeaders;
}
m_headers[col] = header;
setHeaders( m_headers );
}
public void setHeaders( String... headers )
{
Element oldHeaderRow = m_headerRow;
m_headers = headers;
m_headerRow = DOM.createTR();
m_headerRow.setClassName( "thead" );
StringBuilder b = new StringBuilder();
StringBuilder bTemplate = new StringBuilder();
m_nbColumns = headers.length;
for( int i = 0; i < m_nbColumns; i++ )
{
b.append( "<th>" + headers[i] + "</th>" );
bTemplate.append( "<td/>" );
}
m_headerRow.setInnerHTML( b.toString() );
if( oldHeaderRow != null )
m_head.replaceChild( m_headerRow, oldHeaderRow );
else
DOM.insertChild( m_head, m_headerRow, 0 );
m_rowTemplate = bTemplate.toString();
}
public Row addRow()
{
return addRow( null );
}
// adds an item at the end of the children of the parent
public Row addRow( Object parent )
{
Row parentItem = (Row) parent;
if( parentItem == null )
parentItem = m_rootItem;
return parentItem.addLastChild();
}
public Iterator<Widget> iterator()
{
return m_widgets.values().iterator();
}
public boolean remove( Widget child )
{
return false;
}
static final FocusImpl focusImpl = FocusImpl.getFocusImplForPanel();
@Override
public int getTabIndex()
{
return focusImpl.getTabIndex( getElement() );
}
@Override
public void setAccessKey( char key )
{
focusImpl.setAccessKey( getElement(), key );
}
@Override
public void setFocus( boolean focused )
{
if( focused )
{
focusImpl.focus( getElement() );
}
else
{
focusImpl.blur( getElement() );
}
}
@Override
public void setTabIndex( int index )
{
focusImpl.setTabIndex( getElement(), index );
}
}