blob: aa1e9e3fd120d0b2d9d442004f00f633e32f7163 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 2013 Innoopract Informationssysteme GmbH and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Innoopract Informationssysteme GmbH - initial API and implementation
* EclipseSource - ongoing development
******************************************************************************/
rwt.qx.Class.define( "rwt.widgets.Grid", {
extend : rwt.widgets.base.Parent,
construct : function( argsMap ) {
this.base( arguments );
this._rootItem = new rwt.widgets.GridItem();
// Style-Flags:
this._hasMultiSelection = false;
// Internal State:
this._hasSelectionListener = false;
this._hasDefaultSelectionListener = false;
this._leadItem = null;
this._topItemIndex = 0;
this._topItem = null;
this._hasSetDataListener = false;
this._selection = [];
this._focusItem = null;
this._renderQueue = {};
this._resizeLine = null;
this._selectionTimestamp = null;
this._selectionOffsetX = null;
this._delayedSelection = false;
this._sortDirection = null;
this._sortColumn = null;
this._hasFixedColumns = false;
this._hasExpandListener = false;
this._hasCollapseListener = false;
// Layout:
this._headerHeight = 0;
this._footerHeight = 0;
this._itemHeight = 16;
// Timer & Border
this._mergeEventsTimer = new rwt.client.Timer( 50 );
// Subwidgets
this._rowContainer = rwt.widgets.util.GridUtil.createTreeRowContainer( argsMap );
this._columns = {};
this._horzScrollBar = new rwt.widgets.base.ScrollBar( true );
this._vertScrollBar = new rwt.widgets.base.ScrollBar( false );
this._header = null;
this._footer = null;
this.add( this._rowContainer );
this.add( this._horzScrollBar );
this.add( this._vertScrollBar );
this._cellToolTip = null;
// Configure:
this._config = this._rowContainer.getRenderConfig();
this.setCursor( "default" );
this.setOverflow( "hidden" );
// Disable scrolling (see bugs 279460 and 364739)
rwt.widgets.base.Widget.disableScrolling( this );
this._configureScrollBars();
this._registerListeners();
this._parseArgsMap( argsMap );
},
destruct : function() {
this._rootItem.removeEventListener( "update", this._onItemUpdate, this );
this._rootItem.dispose();
this._rootItem = null;
this._mergeEventsTimer.dispose();
this._mergeEventsTimer = null;
this._rowContainer = null;
this._header = null;
this._footer = null;
this._horzScrollBar = null;
this._vertScrollBar = null;
this._leadItem = null;
this._focusItem = null;
this._sortColumn = null;
this._resizeLine = null;
if( this._cellToolTip ) {
this._cellToolTip.destroy();
this._cellToolTip = null;
}
},
members : {
/////////////////////////////////
// Contructor & Subwidget helpers
_createHeader : function() {
this._header = new rwt.widgets.base.GridHeader( {
"appearance" : this.getAppearance(),
"splitContainer" : this._hasFixedColumns
} );
this.add( this._header );
this._header.addEventListener( "showResizeLine", this._onShowResizeLine, this );
this._header.addEventListener( "hideResizeLine", this._onHideResizeLine, this );
this._header.setTop( 0 );
this._header.setLeft( 0 );
this._header.setScrollLeft( this._horzScrollBar.getValue() );
this._scheduleColumnUpdate();
},
_createFooter : function() {
this._footer = new rwt.widgets.base.GridHeader( {
"appearance" : this.getAppearance(),
"splitContainer" : this._hasFixedColumns,
"footer" : true
} );
this.add( this._footer );
this._footer.setLeft( 0 );
this._footer.setScrollLeft( this._horzScrollBar.getValue() );
this._scheduleColumnUpdate();
},
_configureScrollBars : function() {
var dragBlocker = function( event ) { event.stopPropagation(); };
this._horzScrollBar.setZIndex( 1e8 );
this._horzScrollBar.setVisibility( false );
this._horzScrollBar.setLeft( 0 );
this._horzScrollBar.setMergeEvents( false );
this._horzScrollBar.addEventListener( "dragstart", dragBlocker );
this._vertScrollBar.setZIndex( 1e8 );
this._vertScrollBar.setVisibility( false );
this._vertScrollBar.setIncrement( 16 );
this._vertScrollBar.setMergeEvents( false );
this._vertScrollBar.addEventListener( "dragstart", dragBlocker );
},
_registerListeners : function() {
this._rootItem.addEventListener( "update", this._onItemUpdate, this );
this.addEventListener( "mousedown", this._handleHyperlinkActivation, this );
this.addEventListener( "mouseup", this._handleHyperlinkActivation, this );
this.addEventListener( "click", this._handleHyperlinkActivation, this );
this.addEventListener( "mousedown", this._onMouseDown, this );
this.addEventListener( "mouseup", this._onMouseUp, this );
this.addEventListener( "mouseout", this._onMouseOut, this );
this.addEventListener( "keypress", this._onKeyPress, this );
this._rowContainer.addEventListener( "mousewheel", this._onClientAreaMouseWheel, this );
this._mergeEventsTimer.addEventListener( "interval", this._updateTopItemIndex, this );
this._horzScrollBar.addEventListener( "changeValue", this._onHorzScrollBarChangeValue, this );
this._vertScrollBar.addEventListener( "changeValue", this._onVertScrollBarChangeValue, this );
this._rowContainer.setSelectionProvider( this.isItemSelected, this );
this._rowContainer.setPostRenderFunction( this._vertScrollBar.autoEnableMerge,
this._vertScrollBar );
},
_parseArgsMap : function( map ) {
if( map.noScroll ) {
this._rowContainer.removeEventListener( "mousewheel", this._onClientAreaMouseWheel, this );
}
if( map.hideSelection ) {
this._config.hideSelection = true;
}
if( map.multiSelection ) {
this._hasMultiSelection = true;
}
if( map.fullSelection ) {
this._config.fullSelection = true;
} else {
this._config.selectionPadding = map.selectionPadding;
}
if( map.check ) {
this._config.hasCheckBoxes = true;
this._config.checkBoxLeft = map.checkBoxMetrics[ 0 ];
this._config.checkBoxWidth = map.checkBoxMetrics[ 1 ];
}
if( typeof map.indentionWidth === "number" ) {
this._config.indentionWidth = map.indentionWidth;
}
if( map.markupEnabled ) {
this._config.markupEnabled = true;
}
this._hasFixedColumns = map.splitContainer;
this._config.baseAppearance = map.appearance;
this._rowContainer.setBaseAppearance( map.appearance );
this.setAppearance( map.appearance );
},
///////////////////////////
// API for server - general
setItemCount : function( value ) {
this._rootItem.setItemCount( value );
},
setHeaderVisible : function( value ) {
if( value && this._header == null ) {
this._createHeader();
} else if( !value ) {
this._header.destroy();
this._header = null;
}
this._layoutX();
this._layoutY();
},
setFooterVisible : function( value ) {
if( value && this._footer == null ) {
this._createFooter();
} else if( !value ) {
this._footer.destroy();
this._footer = null;
}
this._scheduleUpdate( "scrollHeight" );
this._layoutX();
this._layoutY();
},
setHeaderHeight : function( value ) {
this._headerHeight = value;
this._layoutX();
this._layoutY();
},
setFooterHeight : function( value ) {
this._footerHeight = value;
this._scheduleUpdate( "scrollHeight" );
this._layoutX();
this._layoutY();
},
setItemHeight : function( height ) {
this._itemHeight = height;
this._vertScrollBar.setIncrement( height );
this._rowContainer.setRowHeight( height );
this._rootItem.setDefaultHeight( height );
this._scheduleUpdate( "scrollHeight" );
},
setColumnCount : function( count ) {
this._config.columnCount = count;
this._scheduleUpdate();
this._updateScrollWidth();
},
setItemMetrics : function( columnIndex,
left,
width,
imageLeft,
imageWidth,
textLeft,
textWidth,
checkLeft,
checkWidth )
{
this._config.itemLeft[ columnIndex ] = left;
this._config.itemWidth[ columnIndex ] = width;
this._config.itemImageLeft[ columnIndex ] = imageLeft;
this._config.itemImageWidth[ columnIndex ] = imageWidth;
this._config.itemTextLeft[ columnIndex ] = textLeft;
this._config.itemTextWidth[ columnIndex ] = textWidth;
if( !isNaN( checkLeft ) ) {
this._config.itemCellCheckLeft[ columnIndex ] = checkLeft;
this._config.itemCellCheckWidth[ columnIndex ] = checkWidth;
}
this._scheduleUpdate();
this._updateScrollWidth();
},
setTreeColumn : function( columnIndex ) {
this._config.treeColumn = columnIndex;
},
setTopItemIndex : function( index ) {
this._updateScrollHeight();
var offset = 0;
var item = this._rootItem.findItemByFlatIndex( index );
if( item != null ) {
offset = item.getOffset();
}
this._vertScrollBar.setValue( offset );
if( !this._inServerResponse() ) {
rwt.widgets.base.Widget.flushGlobalQueues();
}
},
getTopItemIndex : function() {
return this._topItemIndex;
},
setScrollLeft: function( value ) {
this._horzScrollBar.setValue( value );
},
selectItem : function( item ) {
this._selectItem( item, false );
this._scheduleItemUpdate( item );
},
deselectItem : function( item ) {
this._deselectItem( item, false );
this._scheduleItemUpdate( item );
},
setFocusItem : function( item ) {
this._focusItem = item;
this._sendItemFocusChange();
},
setSortDirection : function( direction ) {
this._sortDirection = direction;
if( this._sortColumn !== null ) {
this._sortColumn.setSortDirection( this._sortDirection );
}
},
setSortColumn : function( column ) {
if( this._sortColumn !== null ) {
this._sortColumn.setSortDirection( "none" );
}
this._sortColumn = column;
if( this._sortColumn !== null ) {
this._sortColumn.setSortDirection( this._sortDirection );
}
},
setScrollBarsVisible : function( horzVisible, vertVisible ) {
if( !horzVisible ) {
this._horzScrollBar.setValue( 0 );
}
this._horzScrollBar.setVisibility( horzVisible );
if( !vertVisible ) {
this._vertScrollBar.setValue( 0 );
}
this._vertScrollBar.setVisibility( vertVisible );
this._layoutX();
this._layoutY();
},
getVerticalBar : function() {
return this._vertScrollBar;
},
getHorizontalBar : function() {
return this._horzScrollBar;
},
isVerticalBarVisible : function() {
return this._vertScrollBar.getVisibility();
},
isHorizontalBarVisible : function() {
return this._horzScrollBar.getVisibility();
},
setHasSelectionListener : function( value ) {
this._hasSelectionListener = value;
},
setHasDefaultSelectionListener : function( value ) {
this._hasDefaultSelectionListener = value;
},
setHasExpandListener : function( value ) {
this._hasExpandListener = value;
},
setHasCollapseListener : function( value ) {
this._hasCollapseListener = value;
},
setHasSetDataListener : function( value ) {
this._hasSetDataListener = value;
},
setAlignment : function( column, value ) {
this._config.alignment[ column ] = value;
this._scheduleUpdate();
},
setCellCheck : function( column, value ) {
this._config.itemCellCheck[ column ] = value;
this._scheduleUpdate();
},
setLinesVisible : function( value ) {
this._config.linesVisible = value;
if( value ) {
this.addState( "linesvisible" );
} else {
this.removeState( "linesvisible" );
}
this._rowContainer.updateRowLines();
this._scheduleUpdate();
},
setAlwaysHideSelection : function( value ) {
this._config.alwaysHideSelection = value;
this._scheduleUpdate();
},
//////////////
// Overwritten
addState : function( state ) {
this.base( arguments, state );
if( state.slice( 0, 8 ) === "variant_" ) {
this._config.variant = state;
}
},
removeState : function( state ) {
if( this._config.variant === state ) {
this._config.variant = null;
}
this.base( arguments, state );
},
///////////////////////////////////////////////
// API for Tests, DND, TreeUtil and TableColumn
getRenderConfig : function() {
return this._config;
},
getRootItem : function() {
return this._rootItem;
},
isFocusItem : function( item ) {
return this._focusItem === item;
},
isItemSelected : function( item ) {
return this._selection.indexOf( item ) != -1;
},
getRowContainer : function() {
return this._rowContainer;
},
getTableHeader : function() {
return this._header;
},
getFooter : function() {
return this._footer;
},
update : function() {
this._scheduleUpdate();
},
addColumn : function( column ) {
//this.getTableHeader().addColumn( column );
this._columns[ column.toHashCode() ] = column;
column.addEventListener( "update", this._scheduleColumnUpdate, this );
this._scheduleColumnUpdate();
},
removeColumn : function( column ) {
//this.getTableHeader().removeColumn( column );
delete this._columns[ column.toHashCode() ];
column.removeEventListener( "update", this._scheduleColumnUpdate, this );
this._scheduleColumnUpdate();
},
////////////////
// event handler
_onItemUpdate : function( event ) {
var item = event.target;
if( event.msg === "collapsed" ) {
if( this._focusItem
&& ( this._focusItem.isDisposed() || this._focusItem.isChildOf( item ) )
) {
this.setFocusItem( item );
}
}
if( event.msg === "remove" ) {
this._scheduleUpdate( "checkDisposedItems" );
}
this._sendItemUpdate( item, event );
this._renderItemUpdate( item, event );
return false;
},
_beforeAppear : function() {
this.base( arguments );
this._scheduleColumnUpdate();
},
_scheduleColumnUpdate : function() {
rwt.widgets.base.Widget.addToGlobalWidgetQueue( this );
this._scheduleUpdate();
},
flushWidgetQueue : function() {
this._updateColumns();
},
_onVertScrollBarChangeValue : function() {
if( this._vertScrollBar._internalValueChange ) {
// NOTE : IE can create several scroll events with one click. Using
// this timer to merge theses events improves performance a bit.
this._mergeEventsTimer.start();
} else {
this._updateTopItemIndex();
}
},
_updateTopItemIndex : function() {
this._mergeEventsTimer.stop();
var scrollTop = this._vertScrollBar.getValue();
var beforeTopitem = this._rootItem.findItemByOffset( scrollTop - 1 );
if( beforeTopitem ) {
this._topItemIndex = beforeTopitem.getFlatIndex() + 1;
this._topItem = beforeTopitem.getNextItem();
} else {
this._topItemIndex = 0;
this._topItem = null;
}
if( this._inServerResponse() ) {
this._scheduleUpdate( "topItem" );
} else {
this._sendTopItemIndexChange();
this._updateTopItem( true );
}
},
_onHorzScrollBarChangeValue : function() {
this._rowContainer.setScrollLeft( this._horzScrollBar.getValue() );
if( this._header ) {
this._header.setScrollLeft( this._horzScrollBar.getValue() );
}
if( this._footer ) {
this._footer.setScrollLeft( this._horzScrollBar.getValue() );
}
this._sendScrollLeftChange();
},
_onMouseDown : function( event ) {
this._delayedSelection = false;
var target = event.getOriginalTarget();
if( target instanceof rwt.widgets.base.GridRow && !this._isHyperlinkTarget( event ) ) {
this._onRowMouseDown( target, event );
}
},
_onMouseUp : function( event ) {
if( this._delayedSelection ) {
this._onMouseDown( event );
}
},
_onRowMouseDown : function( row, event ) {
var item = this._rowContainer.findItemByRow( row );
if( item != null ) {
var identifier = row.getTargetIdentifier( event );
if( identifier[ 0 ] === "expandIcon" && item.hasChildren() ) {
var expanded = !item.isExpanded();
if( !expanded ) {
this._deselectVisibleChildren( item );
}
item.setExpanded( expanded );
} else if( identifier[ 0 ] === "checkBox" || identifier[ 0 ] === "cellCheckBox" ) {
this._toggleCheckSelection( item, identifier[ 1 ] );
} else if( this._isSelectionClick( identifier ) ) {
this._onSelectionClick( event, item );
}
}
},
_handleHyperlinkActivation : function( event ) {
if( this._isRWTHyperlinkTarget( event ) ) {
event.setDefaultPrevented( true );
if( event.getType() === "click" ) {
var domTarget = event.getDomTarget();
var row = event.getOriginalTarget();
var item = this._rowContainer.findItemByRow( row );
var text = domTarget.getAttribute( "href" );
if( !text ) {
text = domTarget.innerHTML;
}
this._sendSelectionEvent( item, false, "hyperlink", undefined, text );
}
}
},
_isHyperlinkTarget : function( event ) {
return event.getDomTarget().tagName.toLowerCase() === "a";
},
_isRWTHyperlinkTarget : function( event ) {
var domTarget = event.getDomTarget();
return this._isHyperlinkTarget( event ) && domTarget.getAttribute( "target" ) === "_rwt";
},
_isSelectionClick : function( identifier ) {
var result;
if( this._config.fullSelection ) {
result = identifier[ 0 ] !== "checkBox";
} else {
result = identifier[ 0 ] === "treeColumn";
}
return result;
},
_onSelectionClick : function( event, item ) {
// NOTE: Using a listener for "dblclick" does not work because the
// item is re-rendered on mousedown which prevents the dom-event.
var doubleClick = this._isDoubleClicked( event, item );
if( doubleClick ) {
this._sendSelectionEvent( item, true, null );
} else {
if( this._hasMultiSelection ) {
if( !this._delayMultiSelect( event, item ) ) {
this._multiSelectItem( event, item );
}
} else {
this._singleSelectItem( event, item );
}
}
},
_delayMultiSelect : function( event, item ) {
if( this._isDragSource() && this.isItemSelected( item ) && event.getType() === "mousedown" ) {
this._delayedSelection = true;
}
return this._delayedSelection;
},
_onMouseOut : function( event ) {
this._delayedSelection = false;
},
_onClientAreaMouseWheel : function( event ) {
event.preventDefault();
event.stopPropagation();
var change = event.getWheelDelta() * this._itemHeight * 2;
this._vertScrollBar.setValue( this._vertScrollBar.getValue() - change );
this._vertScrollBar.setValue( this._vertScrollBar.getValue() ); // See Bug 396309
},
_onKeyPress : function( event ) {
if( this._focusItem != null ) {
switch( event.getKeyIdentifier() ) {
case "Enter":
this._handleKeyEnter( event );
break;
case "Space":
this._handleKeySpace( event );
break;
case "Up":
this._handleKeyUp( event );
break;
case "Down":
this._handleKeyDown( event );
break;
case "PageUp":
this._handleKeyPageUp( event );
break;
case "PageDown":
this._handleKeyPageDown( event );
break;
case "Home":
this._handleKeyHome( event );
break;
case "End":
this._handleKeyEnd( event );
break;
case "Left":
this._handleKeyLeft( event );
break;
case "Right":
this._handleKeyRight( event );
break;
}
}
this._stopKeyEvent( event );
},
_stopKeyEvent : function( event ) {
switch( event.getKeyIdentifier() ) {
case "Up":
case "Down":
case "Left":
case "Right":
case "Home":
case "End":
case "PageUp":
case "PageDown":
event.preventDefault();
event.stopPropagation();
break;
}
},
// TODO [tb] : handle by event via TableHeader instead of direct call
_onShowResizeLine : function( event ) {
var x = event.position;
if( this._resizeLine === null ) {
this._resizeLine = new rwt.widgets.base.Terminator();
this._resizeLine.setAppearance( "table-column-resizer" );
this.add( this._resizeLine );
rwt.widgets.base.Widget.flushGlobalQueues();
}
var top = this._rowContainer.getTop();
this._resizeLine._renderRuntimeTop( top );
var left = x - 2 - this._horzScrollBar.getValue();
this._resizeLine._renderRuntimeLeft( left );
var height = this._rowContainer.getHeight();
this._resizeLine._renderRuntimeHeight( height );
this._resizeLine.removeStyleProperty( "visibility" );
},
_onHideResizeLine : function() {
this._resizeLine.setStyleProperty( "visibility", "hidden" );
},
_handleKeyEnter : function( event ) {
this._sendSelectionEvent( this._focusItem, true, null );
},
_handleKeySpace : function( event ) {
if( event.isCtrlPressed() || !this.isItemSelected( this._focusItem ) ) {
// NOTE: When space does not change the selection, the SWT Tree still fires an selection
// event, while the Table doesnt. Table behavior is used since it makes more sense.
var itemIndex = this._focusItem.getFlatIndex();
this._handleKeyboardSelect( event, this._focusItem, itemIndex );
}
if( this._config.hasCheckBoxes ) {
this._toggleCheckSelection( this._focusItem );
}
},
_handleKeyUp : function( event ) {
var item = this._focusItem.getPreviousItem();
if( item != null ) {
var itemIndex = item.getFlatIndex();
this._handleKeyboardSelect( event, item, itemIndex );
}
},
_handleKeyDown : function( event ) {
var item = this._focusItem.getNextItem();
if( item != null ) {
var itemIndex = item.getFlatIndex();
this._handleKeyboardSelect( event, item, itemIndex );
}
},
_handleKeyPageUp : function( event ) {
var oldOffset = this._focusItem.getOffset();
var diff = this._rowContainer.getHeight();
var newOffset = Math.max( 0, oldOffset - diff );
var item = this._rootItem.findItemByOffset( newOffset );
if( newOffset !== 0 ) {
item = item.getNextItem();
}
var itemIndex = item.getFlatIndex();
this._handleKeyboardSelect( event, item, itemIndex );
},
_handleKeyPageDown : function( event ) {
var oldOffset = this._focusItem.getOffset();
var diff = this._rowContainer.getHeight();
var max = this.getRootItem().getOffsetHeight() - 1;
var newOffset = Math.min( max, oldOffset + diff );
var item = this._rootItem.findItemByOffset( newOffset );
if( newOffset !== max ) {
item = item.getPreviousItem();
}
var itemIndex = item.getFlatIndex();
this._handleKeyboardSelect( event, item, itemIndex );
},
_handleKeyHome : function( event ) {
var item = this.getRootItem().getChild( 0 );
this._handleKeyboardSelect( event, item, 0 );
},
_handleKeyEnd : function( event ) {
var item = this.getRootItem().getLastChild();
var itemIndex = this.getRootItem().getVisibleChildrenCount() - 1;
this._handleKeyboardSelect( event, item, itemIndex );
},
_handleKeyLeft : function( event ) {
if( this._focusItem.isExpanded() ) {
this._focusItem.setExpanded( false );
} else if( !this._focusItem.getParent().isRootItem() ) {
var item = this._focusItem.getParent();
var itemIndex = item.getFlatIndex();
this._handleKeyboardSelect( event, item, itemIndex, true );
}
},
_handleKeyRight : function( event ) {
if( this._focusItem.hasChildren() ) {
if( !this._focusItem.isExpanded() ) {
this._focusItem.setExpanded( true );
} else {
var item = this._focusItem.getChild( 0 );
var itemIndex = item.getFlatIndex();
this._handleKeyboardSelect( event, item, itemIndex, true );
}
}
},
_handleKeyboardSelect : function( event, item, itemIndex, suppressMulti ) {
if( this._hasMultiSelection && !suppressMulti ) {
this._multiSelectItem( event, item );
} else {
this._singleSelectItem( event, item );
}
this._scrollIntoView( itemIndex, item );
},
/////////////////
// render content
_updateColumns : function() {
this._updateScrollWidth();
if( this._header != null ) {
this._header.renderColumns( this._columns );
}
if( this._footer != null ) {
this._footer.renderColumns( this._columns );
}
},
_renderItemUpdate : function( item, event ) {
if( item.isDisplayable() ) {
switch( event.msg ) {
case "expanded":
case "collapsed":
case "height":
this._topItem = null;
this._scheduleUpdate( "scrollHeight" );
break;
case "add":
case "remove":
// NOTE: the added/removed item is a child of this item
if( item.isExpanded() ) {
this._scheduleUpdate( "scrollHeight" );
} else {
this._scheduleItemUpdate( item );
}
this._topItem = null;
break;
default:
if( this._inServerResponse() ) {
this._scheduleItemUpdate( item );
} else {
this._rowContainer.renderItem( item );
}
break;
}
}
},
/**
* This will schedule the entire content of the tree (visible rows and gridlines)
* to be re-rendered. Additional tasks my be executed depending on "task" parameter.
* Is only used within a server-response or when expanding/collapsing. Not used
* when user is scrolling.
*/
_scheduleUpdate : function( task ) {
if( task !== undefined ) {
this.addToQueue( task );
}
this._renderQueue[ "allItems" ] = true;
this.addToQueue( "updateRows" );
},
/**
* Optimized version of _scheduleUpdate. Used when server only changes specific items.
*/
_scheduleItemUpdate : function( item ) {
this._renderQueue[ item.toHashCode() ] = item;
this.addToQueue( "updateRows" );
},
_layoutPost : function( changes ) {
this.base( arguments, changes );
if( changes[ "checkDisposedItems" ] ) {
this._checkDisposedItems();
}
if( changes[ "scrollHeight" ] ) {
this._updateScrollHeight();
}
if( changes[ "scrollHeight" ] || changes[ "topItem" ] ) {
this._updateTopItem( false );
}
if( changes[ "updateRows" ] ) {
if( this._renderQueue[ "allItems" ] ) {
this._rowContainer.renderAll();
} else {
this._rowContainer.renderItemQueue( this._renderQueue );
}
this._renderQueue = {};
}
},
////////////
// scrolling
_updateScrollHeight : function() {
var itemsOffsetHeight = this.getRootItem().getOffsetHeight();
var height = itemsOffsetHeight + ( this._footer ? this._footerHeight : 0 );
// recalculating topItem can be expensive, therefore this simple check:
if( this._vertScrollBar.getMaximum() != height ) {
// Without the check, it may cause an error in FF when unloading doc
if( !this._vertScrollBar.getDisposed() ) {
this._vertScrollBar.setMaximum( height );
}
}
},
/**
* NOTE: If render is true, the content will be updated immediately. The rendering
* assumes that no other parameter than topItem have changed and may optimize accordingly.
*/
_updateTopItem : function( render ) {
this._rowContainer.setTopItem( this._getTopItem(), this._topItemIndex, render );
},
_updateScrollWidth : function() {
var width = this._getItemWidth();
this._rowContainer.setRowWidth( this._getRowWidth() );
if( !this._horzScrollBar.getDisposed() ) {
this._horzScrollBar.setMaximum( width );
}
var headerOverlap = this._vertScrollBar.getVisibility() ? this._vertScrollBar.getWidth() : 0;
if( this._header ) {
this._header.setScrollWidth( width + headerOverlap );
}
if( this._footer ) {
this._footer.setScrollWidth( width );
}
},
_scrollIntoView : function( index, item ) {
if( index < this._topItemIndex ) {
this.setTopItemIndex( index );
} else if( index > this._topItemIndex ) {
var topItem = this._getTopItem();
var topItemOffset = topItem.getOffset();
var itemOffset = item.getOffset();
var pageSize = this._rowContainer.getHeight() - item.getOwnHeight();
if( itemOffset > topItemOffset + pageSize ) {
var newTopOffset = itemOffset - pageSize - 1;
var newTopItem = this.getRootItem().findItemByOffset( newTopOffset );
var newTopIndex = newTopItem.getFlatIndex() + 1;
this.setTopItemIndex( newTopIndex );
}
}
},
setHasScrollBarsSelectionListener : function( value ) {
this._vertScrollBar.setHasSelectionListener( true );
this._horzScrollBar.setHasSelectionListener( true );
},
//////////////
// Send events
_sendSelectionChange : function( item ) {
if( !this._inServerResponse() ) {
var selection = this._getSelectionList();
rwt.remote.Server.getInstance().getRemoteObject( this ).set( "selection", selection );
this._sendSelectionEvent( item, false, null );
}
},
_sendItemCheckedChange : function( item ) { // TODO [tb] : item events should be send by item
if( !this._inServerResponse() ) {
var req = rwt.remote.Server.getInstance();
var wm = rwt.remote.WidgetManager.getInstance();
var itemId = wm.findIdByWidget( item );
req.addParameter( itemId + ".checked", item.isChecked() );
this._sendSelectionEvent( item, false, "check" );
}
},
_sendCellCheckedChange : function( item, cell ) { // TODO [tb] : item events should be send by item
if( !this._inServerResponse() ) {
var server = rwt.remote.Server.getInstance();
var arr = item.getCellChecked();
var sendArr = [];
for( var i = 0; i < this._config.columnCount; i++ ) {
sendArr[ i ] = arr[ i ] === true;
}
server.getRemoteObject( item ).set( "cellChecked", sendArr );
this._sendSelectionEvent( item, false, "check", cell );
}
},
_sendItemFocusChange : function() {
if( !this._inServerResponse() ) {
var req = rwt.remote.Server.getInstance();
var id = rwt.remote.WidgetManager.getInstance().findIdByWidget( this );
req.addParameter( id + ".focusItem", this._getItemId( this._focusItem ) );
}
},
_sendTopItemIndexChange : function() {
var server = rwt.remote.Server.getInstance();
var remoteObject = server.getRemoteObject( this );
remoteObject.set( "topItemIndex", this._topItemIndex );
if( this._hasSetDataListener || this._vertScrollBar.getHasSelectionListener() ) {
this._startScrollBarChangesTimer( false );
}
},
_sendScrollLeftChange : function() {
// TODO [tb] : There should be a check for _inServerResponse,
// but currently this is needed to sync the value with the
// server when the scrollbars are hidden by the server.
var server = rwt.remote.Server.getInstance();
var remoteObject = server.getRemoteObject( this );
remoteObject.set( "scrollLeft", this._horzScrollBar.getValue() );
if( this._hasSetDataListener || this._horzScrollBar.getHasSelectionListener() ) {
this._startScrollBarChangesTimer( true );
}
},
_startScrollBarChangesTimer : function( horizontal ) {
var server = rwt.remote.Server.getInstance();
if( horizontal && this._horzScrollBar.getHasSelectionListener() ) {
server.onNextSend( this._sendHorizontalScrolled, this );
} else {
if( this._vertScrollBar.getHasSelectionListener() ) {
server.onNextSend( this._sendVerticalScrolled, this );
}
if( this._hasSetDataListener ) {
server.onNextSend( this._sendSetData, this );
}
}
server.sendDelayed( 400 );
},
_sendVerticalScrolled : function() {
var server = rwt.remote.Server.getInstance();
server.getRemoteObject( this._vertScrollBar ).notify( "Selection" );
},
_sendHorizontalScrolled : function() {
var server = rwt.remote.Server.getInstance();
server.getRemoteObject( this._horzScrollBar ).notify( "Selection" );
},
_sendSetData : function() {
var server = rwt.remote.Server.getInstance();
server.getRemoteObject( this ).notify( "SetData" );
},
_sendItemUpdate : function( item, event ) {
if( !this._inServerResponse() ) {
if( event.msg === "expanded" || event.msg === "collapsed" ) {
var expanded = event.msg === "expanded";
rwt.remote.Server.getInstance().getRemoteObject( item ).set( "expanded", expanded );
if( expanded && this._hasExpandListener ) {
rwt.remote.Server.getInstance().getRemoteObject( this ).notify( "Expand", {
"item" : rwt.remote.ObjectRegistry.getId( item )
} );
} else if( !expanded && this._hasCollapseListener ) {
rwt.remote.Server.getInstance().getRemoteObject( this ).notify( "Collapse", {
"item" : rwt.remote.ObjectRegistry.getId( item )
} );
}
}
}
},
_sendSelectionEvent : function( item, defaultSelected, detail, index, text ) {
if( ( this._hasSelectionListener && !defaultSelected )
|| ( this._hasDefaultSelectionListener && defaultSelected ) )
{
var properties = {
"item" : this._getItemId( item ),
"detail" : detail,
"index" : !isNaN( index ) ? index : undefined,
"text" : text
};
if( defaultSelected ) {
rwt.remote.EventUtil.notifyDefaultSelected( this, properties );
} else {
rwt.remote.EventUtil.notifySelected( this, properties );
}
}
},
_isDoubleClicked : function( event, item ) {
var result = false;
var mousedown = event.getType() === "mousedown";
var leftClick = event.getButton() === "left";
if( leftClick && mousedown && this.isFocusItem( item ) && this._selectionTimestamp != null ) {
var stamp = new Date();
var offset = event.getPageX();
var timeDiff = rwt.remote.EventUtil.DOUBLE_CLICK_TIME;
var offsetDiff = 8;
if ( stamp.getTime() - this._selectionTimestamp.getTime() < timeDiff
&& Math.abs( this._selectionOffsetX - offset ) < offsetDiff )
{
result = true;
}
}
if( mousedown && leftClick && !result ) {
this._selectionTimestamp = new Date();
this._selectionOffsetX = event.getPageX();
} else if( mousedown ) {
this._selectionTimestamp = null;
}
return result;
},
_getSelectionList : function() {
var result = [];
for( var i = 0; i < this._selection.length; i++ ) {
result.push( this._getItemId( this._selection[ i ] ) );
}
return result;
},
_getItemId : function( item ) {
var wm = rwt.remote.WidgetManager.getInstance();
var result;
if( item.isCached() ) {
result = wm.findIdByWidget( item );
} else {
var parent = item.getParent();
if( parent.isRootItem() ) {
result = wm.findIdByWidget( this );
} else {
result = wm.findIdByWidget( parent );
}
result += "#" + parent.indexOf( item );
}
return result;
},
////////////////////
// focus & selection
_singleSelectItem : function( event, item ) {
if( event.isCtrlPressed() && this.isItemSelected( item ) ) {
// NOTE: Apparently in SWT this is only supported by Table, not Tree.
// No reason not to support it in RAP though.
this._ctrlSelectItem( item );
} else {
this._exclusiveSelectItem( item );
}
},
_multiSelectItem : function( event, item ) {
if( event instanceof rwt.event.MouseEvent && event.isRightButtonPressed() ) {
if( !this.isItemSelected( item ) ) {
this._exclusiveSelectItem( item );
}
} else if( event.isCtrlPressed() ) {
if( event instanceof rwt.event.KeyEvent && item != this._focusItem ) {
this.setFocusItem( item );
} else {
this._ctrlSelectItem( item );
}
} else if( event.isShiftPressed() ) {
if( this._focusItem != null ) {
this._shiftSelectItem( item );
} else {
this._exclusiveSelectItem( item );
}
} else {
this._exclusiveSelectItem( item );
}
},
_exclusiveSelectItem : function( item ) {
this.deselectAll();
this._leadItem = null;
this._selectItem( item, true );
this._sendSelectionChange( item );
this.setFocusItem( item );
},
_ctrlSelectItem : function( item ) {
if( !this.isItemSelected( item ) ) {
this._selectItem( item, true );
} else {
this._deselectItem( item, true );
}
this._sendSelectionChange( item );
this.setFocusItem( item );
},
_shiftSelectItem : function( item ) {
this.deselectAll();
var currentItem = this._leadItem != null ? this._leadItem : this._focusItem;
this._leadItem = currentItem;
var targetItem = item;
var startIndex = currentItem.getFlatIndex();
var endIndex = targetItem.getFlatIndex();
if( startIndex > endIndex ) {
var temp = currentItem;
currentItem = targetItem;
targetItem = temp;
}
this._selectItem( currentItem, true );
while( currentItem !== targetItem ) {
currentItem = currentItem.getNextItem();
this._selectItem( currentItem, true );
}
this._sendSelectionChange( item );
this.setFocusItem( item );
},
_selectItem : function( item, render ) {
if( !this.isItemSelected( item ) ) {
this._selection.push( item );
}
if( render ) {
this._rowContainer.renderItem( item );
}
},
_deselectItem : function( item, render ) {
if( this.isItemSelected( item ) ) {
this._selection.splice( this._selection.indexOf( item ), 1 );
}
if( render ) {
this._rowContainer.renderItem( item );
}
},
deselectAll : function() {
this._checkDisposedItems();
var oldSelection = this._selection;
this._selection = [];
for( var i = 0; i < oldSelection.length; i++ ) {
this._rowContainer.renderItem( oldSelection[ i ] );
}
},
_toggleCheckSelection : function( item, cell ) {
if( item.isCached() ) {
if( isNaN( cell ) ) {
item.setChecked( !item.isChecked() );
this._sendItemCheckedChange( item );
} else if( item.isCellCheckable( cell ) ) {
item.toggleCellChecked( cell );
this._sendCellCheckedChange( item, cell );
}
}
},
_deselectVisibleChildren : function( item ) {
var currentItem = item.getNextItem();
var finalItem = item.getNextItem( true );
while( currentItem !== finalItem ) {
this._deselectItem( currentItem, false );
currentItem = currentItem.getNextItem();
}
},
_applyFocused : function( newValue, oldValue ) {
this.base( arguments, newValue, oldValue );
this._config.focused = newValue;
this._scheduleUpdate();
},
_applyEnabled : function( newValue, oldValue ) {
this.base( arguments, newValue, oldValue );
this._config.enabled = newValue;
this._scheduleUpdate();
},
_checkDisposedItems : function() {
// NOTE : FocusItem might already been fixed by the server. But since this is not
// always the case (depending on the server-side widget), we also do it here.
if( this._focusItem && this._focusItem.isDisposed() ) {
this._focusItem = null;
}
if( this._leadItem && this._leadItem.isDisposed() ) {
this._leadItem = null;
}
var i = 0;
while( i < this._selection.length ) {
if( this._selection[ i ].isDisposed() ) {
this._deselectItem( this._selection[ i ], false );
} else {
i++;
}
}
},
////////////////////////////
// internal layout & theming
_applyTextColor : function( newValue, oldValue ) {
this.base( arguments, newValue, oldValue );
this._config.textColor = newValue;
this._scheduleUpdate();
},
_applyFont : function( newValue, oldValue ) {
this.base( arguments, newValue, oldValue );
this._config.font = newValue;
this._scheduleUpdate();
},
_applyBackgroundColor : function( newValue ) {
this._rowContainer.setBackgroundColor( newValue );
},
_applyBackgroundImage : function( newValue ) {
this._rowContainer.setBackgroundImage( newValue );
},
_applyWidth : function( newValue, oldValue ) {
this.base( arguments, newValue, oldValue );
this._layoutX();
},
_applyHeight : function( newValue, oldValue ) {
this.base( arguments, newValue, oldValue );
this._layoutY();
},
_applyBorder : function( newValue, oldValue ) {
this.base( arguments, newValue, oldValue );
this._layoutX();
this._layoutY();
},
_layoutX : function() {
var width = this.getWidth() - this.getFrameWidth();
if( this._header && this._header.getDisplay() ) {
this._header.setWidth( width );
}
if( this._vertScrollBar.getVisibility() ) {
width -= this._vertScrollBar.getWidth();
this._vertScrollBar.setLeft( width );
}
if( this._footer ) {
this._footer.setWidth( width );
}
this._horzScrollBar.setWidth( width );
this._rowContainer.setWidth( width );
this._updateScrollWidth();
},
_layoutY : function() {
var top = 0;
top += this._header ? this._headerHeight : 0;
var height = this.getHeight() - this.getFrameHeight();
height -= this._header ? this._headerHeight : 0;
height -= this._footer ? this._footerHeight : 0;
height -= this._horzScrollBar.getVisibility() ? this._horzScrollBar.getHeight() : 0;
height = Math.max( 0, height );
if( this._header ) {
this._header.setHeight( this._headerHeight );
}
if( this._footer ) {
this._footer.setHeight( this._footerHeight );
this._footer.setTop( top + height );
}
if( this._horzScrollBar.getVisibility() ) {
this._horzScrollBar.setTop( top + height + ( this._footer ? this._footerHeight : 0 ) );
}
this._vertScrollBar.setHeight( height + ( this._footer ? this._footerHeight : 0 ) );
this._vertScrollBar.setTop( top );
this._rowContainer.setTop( top );
this._rowContainer.setHeight( height );
this._scheduleUpdate();
},
_getItemWidth : function() {
var result = 0;
if( this._config.itemLeft.length > 0 ) {
var columnCount = Math.max( 1, this._config.columnCount );
for( var i = 0; i < columnCount; i++ ) {
result = Math.max( result, this._config.itemLeft[ i ] + this._config.itemWidth[ i ] );
}
}
return result;
},
_getRowWidth : function() {
var width = this._rowContainer.getWidth();
var result = Math.max( this._getItemWidth(), width );
return result;
},
/////////
// helper
_inServerResponse : function() {
return rwt.remote.EventUtil.getSuspended();
},
_isDragSource : function() {
return this.hasEventListeners( "dragstart" );
},
_getTopItem : function() {
if( this._topItem === null ) {
this._topItem = this._rootItem.findItemByFlatIndex( this._topItemIndex );
}
return this._topItem;
},
////////////////////////
// Cell tooltip handling
setEnableCellToolTip : function( value ) {
if( value ) {
this._cellToolTip = new rwt.widgets.base.GridCellToolTip( this );
this._rowContainer.addEventListener( "mousemove", this._onClientAreaMouseMove, this );
this._rowContainer.setToolTip( this._cellToolTip );
} else {
this._rowContainer.removeEventListener( "mousemove", this._onClientAreaMouseMove, this );
this._rowContainer.setToolTip( null );
this._cellToolTip.destroy();
this._cellToolTip = null;
}
},
_onClientAreaMouseMove : function( evt ) {
if( this._cellToolTip != null ) {
var itemId = null;
var columnIndex = -1;
if( this._rowContainer.getHoverItem() ) {
var widgetManager = rwt.remote.WidgetManager.getInstance();
itemId = widgetManager.findIdByWidget( this._rowContainer.getHoverItem() );
columnIndex = rwt.widgets.util.GridUtil.getColumnByPageX( this, evt.getPageX() );
}
this._cellToolTip.setCell( itemId, columnIndex );
}
},
/** Only called by server-side */
setCellToolTipText : function( text ) {
if( this._cellToolTip != null ) {
this._cellToolTip.setText( text );
}
}
}
} );