Package org.drools.guvnor.client.widgets.drools.decoratedgrid

Source Code of org.drools.guvnor.client.widgets.drools.decoratedgrid.AbstractMergableGridWidget$CellSelectionDetail

/*
* Copyright 2011 JBoss Inc
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.drools.guvnor.client.widgets.drools.decoratedgrid;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import org.drools.guvnor.client.widgets.drools.decoratedgrid.CellValue.CellState;
import org.drools.guvnor.client.widgets.drools.decoratedgrid.CellValue.GroupedCellValue;
import org.drools.guvnor.client.widgets.drools.decoratedgrid.data.Coordinate;
import org.drools.guvnor.client.widgets.drools.decoratedgrid.data.DynamicData;
import org.drools.guvnor.client.widgets.drools.decoratedgrid.data.DynamicDataRow;
import org.drools.guvnor.client.widgets.drools.decoratedgrid.data.GroupedDynamicDataRow;
import org.drools.guvnor.client.widgets.drools.decoratedgrid.data.RowMapper;
import org.drools.guvnor.client.widgets.drools.decoratedgrid.events.AppendRowEvent;
import org.drools.guvnor.client.widgets.drools.decoratedgrid.events.CellStateChangedEvent;
import org.drools.guvnor.client.widgets.drools.decoratedgrid.events.CellStateChangedEvent.CellStateOperation;
import org.drools.guvnor.client.widgets.drools.decoratedgrid.events.ColumnResizeEvent;
import org.drools.guvnor.client.widgets.drools.decoratedgrid.events.CopyRowsEvent;
import org.drools.guvnor.client.widgets.drools.decoratedgrid.events.DeleteColumnEvent;
import org.drools.guvnor.client.widgets.drools.decoratedgrid.events.DeleteRowEvent;
import org.drools.guvnor.client.widgets.drools.decoratedgrid.events.InsertInternalColumnEvent;
import org.drools.guvnor.client.widgets.drools.decoratedgrid.events.InsertRowEvent;
import org.drools.guvnor.client.widgets.drools.decoratedgrid.events.MoveColumnsEvent;
import org.drools.guvnor.client.widgets.drools.decoratedgrid.events.PasteRowsEvent;
import org.drools.guvnor.client.widgets.drools.decoratedgrid.events.RowGroupingChangeEvent;
import org.drools.guvnor.client.widgets.drools.decoratedgrid.events.SelectedCellChangeEvent;
import org.drools.guvnor.client.widgets.drools.decoratedgrid.events.SetColumnVisibilityEvent;
import org.drools.guvnor.client.widgets.drools.decoratedgrid.events.SetInternalModelEvent;
import org.drools.guvnor.client.widgets.drools.decoratedgrid.events.SortDataEvent;
import org.drools.guvnor.client.widgets.drools.decoratedgrid.events.ToggleMergingEvent;
import org.drools.guvnor.client.widgets.drools.decoratedgrid.events.UpdateColumnDataEvent;
import org.drools.guvnor.client.widgets.drools.decoratedgrid.events.UpdateColumnDefinitionEvent;
import org.drools.guvnor.client.widgets.drools.decoratedgrid.events.UpdateModelEvent;
import org.drools.guvnor.client.widgets.drools.decoratedgrid.events.UpdateSelectedCellsEvent;

import com.google.gwt.cell.client.Cell.Context;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.TableCellElement;
import com.google.gwt.dom.client.TableElement;
import com.google.gwt.dom.client.TableRowElement;
import com.google.gwt.dom.client.TableSectionElement;
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.AbstractImagePrototype;
import com.google.gwt.user.client.ui.Widget;

/**
* An abstract grid of data. Implementations can choose the orientation to
* render "rows" and "columns" (e.g. some may transpose the normal meaning to
* provide a horizontal implementation of normally vertical tabular data)
*/
public abstract class AbstractMergableGridWidget<M, T> extends Widget
    implements
    ToggleMergingEvent.Handler,
    DeleteRowEvent.Handler,
    InsertRowEvent.Handler,
    AppendRowEvent.Handler,
    CopyRowsEvent.Handler,
    PasteRowsEvent.Handler,
    DeleteColumnEvent.Handler,
    SetInternalModelEvent.Handler<M, T>,
    InsertInternalColumnEvent.Handler<T>,
    SetColumnVisibilityEvent.Handler,
    UpdateColumnDataEvent.Handler,
    UpdateColumnDefinitionEvent.Handler,
    ColumnResizeEvent.Handler,
    MoveColumnsEvent.Handler,
    SortDataEvent.Handler,
    UpdateSelectedCellsEvent.Handler,
    CellStateChangedEvent.Handler {

    /**
     * Container for a details of a selected cell
     */
    public static class CellSelectionDetail {

        private Coordinate c;
        private int        offsetX;
        private int        offsetY;
        private int        height;
        private int        width;

        CellSelectionDetail(Coordinate c,
                            int offsetX,
                            int offsetY,
                            int height,
                            int width) {
            this.c = c;
            this.offsetX = offsetX;
            this.offsetY = offsetY;
            this.height = height;
            this.width = width;
        }

        public int getHeight() {
            return height;
        }

        public int getOffsetX() {
            return offsetX;
        }

        public int getOffsetY() {
            return offsetY;
        }

        public int getWidth() {
            return width;
        }

        public Coordinate getCoordinate() {
            return c;
        }

    }

    // Enum to support keyboard navigation
    public enum MOVE_DIRECTION {
        LEFT, RIGHT, UP, DOWN, NONE
    }

    //GWT disable text selection in an HTMLTable.
    //event.stopPropogation() doesn't prevent text selection
    private native static void disableTextSelectInternal(Element e,
                                                         boolean disable)/*-{
    if (disable) {
      e.ondrag = function() {
        return false;
      };
      e.onselectstart = function() {
        return false;
      };
      e.style.MozUserSelect = "none"
    } else {
      e.ondrag = null;
      e.onselectstart = null;
      e.style.MozUserSelect = "text"
    }
    }-*/;

    // Selections store the actual grid data selected (irrespective of
    // merged cells). So a merged cell spanning 2 rows is stored as 2
    // selections. Selections are ordered by row number so we can
    // iterate top to bottom.
    protected TreeSet<CellValue< ? extends Comparable< ? >>> selections           = new TreeSet<CellValue< ? extends Comparable< ? >>>(
                                                                                                                                              new Comparator<CellValue< ? extends Comparable< ? >>>() {

                                                                                                                                                  public int compare(CellValue< ? extends Comparable< ? >> o1,
                                                                                                                                                                     CellValue< ? extends Comparable< ? >> o2) {
                                                                                                                                                      return o1.getPhysicalCoordinate().getRow()
                                                                                                                                                             - o2.getPhysicalCoordinate().getRow();
                                                                                                                                                  }

                                                                                                                                              } );

    // TABLE elements
    protected TableElement                                   table;

    protected TableSectionElement                            tbody;

    // Resources
    protected ResourcesProvider<T>                           resources;
    protected EventBus                                       eventBus;

    protected String                                         selectorGroupedCellsHtml;
    protected String                                         selectorUngroupedCellsHtml;

    // Data and columns to render
    protected List<DynamicColumn<T>>                         columns;
    protected DynamicData                                    data;
    protected RowMapper                                      rowMapper;
    protected AbstractCellFactory<T>                         cellFactory;
    protected AbstractCellValueFactory<T, ? >                cellValueFactory;
    protected CellTableDropDownDataValueMapProvider          dropDownManager;

    //Properties for multi-cell selection
    protected CellValue< ? >                                 rangeOriginCell;
    protected CellValue< ? >                                 rangeExtentCell;

    protected MOVE_DIRECTION                                 rangeDirection       = MOVE_DIRECTION.NONE;
    protected boolean                                        bDragOperationPrimed = false;

    //Rows that have been copied in a copy-paste operation
    private List<DynamicDataRow>                             copiedRows           = new ArrayList<DynamicDataRow>();

    protected static final RowGroupingChangeEvent            ROW_GROUPING_EVENT   = new RowGroupingChangeEvent();

    protected final boolean                                  isReadOnly;

    /**
     * A grid of cells.
     */
    public AbstractMergableGridWidget(ResourcesProvider<T> resources,
                                      AbstractCellFactory<T> cellFactory,
                                      AbstractCellValueFactory<T, ? > cellValueFactory,
                                      CellTableDropDownDataValueMapProvider dropDownManager,
                                      boolean isReadOnly,
                                      EventBus eventBus) {
        this.resources = resources;
        this.cellFactory = cellFactory;
        this.cellValueFactory = cellValueFactory;
        this.dropDownManager = dropDownManager;
        this.isReadOnly = isReadOnly;
        this.eventBus = eventBus;

        ImageResource selectorGroupedCells = resources.collapseCellsIcon();
        ImageResource selectorUngroupedCells = resources.expandCellsIcon();
        this.selectorGroupedCellsHtml = makeImageHtml( selectorGroupedCells );
        this.selectorUngroupedCellsHtml = makeImageHtml( selectorUngroupedCells );

        // Create some elements to contain the grid
        table = Document.get().createTableElement();
        tbody = Document.get().createTBodyElement();
        table.setClassName( resources.cellTable() );
        table.setCellPadding( 0 );
        table.setCellSpacing( 0 );
        setElement( table );

        table.appendChild( tbody );

        // Events in which we're interested (note, if a Cell<?> appears not to
        // work I've probably forgotten some events. Might be a better way of
        // doing this, but I copied CellTable<?, ?>'s lead
        sinkEvents( Event.getTypeInt( "click" )
                    | Event.getTypeInt( "dblclick" )
                    | Event.getTypeInt( "mousedown" )
                    | Event.getTypeInt( "mouseup" )
                    | Event.getTypeInt( "mousemove" )
                    | Event.getTypeInt( "mouseout" )
                    | Event.getTypeInt( "change" )
                    | Event.getTypeInt( "keypress" )
                    | Event.getTypeInt( "keydown" ) );

        //Prevent text selection
        disableTextSelectInternal( table,
                                   true );

        //Wire-up events
        eventBus.addHandler( ToggleMergingEvent.TYPE,
                             this );
        eventBus.addHandler( DeleteRowEvent.TYPE,
                             this );
        eventBus.addHandler( InsertRowEvent.TYPE,
                             this );
        eventBus.addHandler( AppendRowEvent.TYPE,
                             this );
        eventBus.addHandler( CopyRowsEvent.TYPE,
                             this );
        eventBus.addHandler( PasteRowsEvent.TYPE,
                             this );
        eventBus.addHandler( DeleteColumnEvent.TYPE,
                             this );
        eventBus.addHandler( SetColumnVisibilityEvent.TYPE,
                             this );
        eventBus.addHandler( UpdateColumnDataEvent.TYPE,
                             this );
        eventBus.addHandler( UpdateColumnDefinitionEvent.TYPE,
                             this );
        eventBus.addHandler( ColumnResizeEvent.TYPE,
                             this );
        eventBus.addHandler( MoveColumnsEvent.TYPE,
                             this );
        eventBus.addHandler( SortDataEvent.TYPE,
                             this );
        eventBus.addHandler( UpdateSelectedCellsEvent.TYPE,
                             this );
        eventBus.addHandler( CellStateChangedEvent.TYPE,
                             this );
    }

    private static String makeImageHtml(ImageResource image) {
        return AbstractImagePrototype.create( image ).getHTML();
    }

    /**
     * Redraw the whole table
     */
    abstract void redraw();

    /**
     * Redraw table columns. Partial redraw
     *
     * @param startRedrawIndex
     *            Start column index (inclusive)
     * @param endRedrawIndex
     *            End column index (inclusive)
     */
    abstract void redrawColumns(int startRedrawIndex,
                                int endRedrawIndex);

    //Apply grouping by collapsing applicable rows
    private void applyModelGrouping(CellValue< ? > startCell,
                                    boolean bRedraw) {

        data.applyModelGrouping( startCell );

        //Partial redraw
        if ( bRedraw ) {
            int startRowIndex = startCell.getCoordinate().getRow();
            GroupedDynamicDataRow groupedRow = (GroupedDynamicDataRow) data.get( startRowIndex );
            int minRedrawRow = findMinRedrawRow( startRowIndex - (startRowIndex > 0 ? 1 : 0) );
            int maxRedrawRow = findMaxRedrawRow( startRowIndex + (startRowIndex < data.size() - 1 ? 1 : 0) );
            for ( int iRow = 0; iRow < groupedRow.getChildRows().size() - 1; iRow++ ) {
                deleteRowElement( startRowIndex );
            }
            redrawRows( minRedrawRow,
                        maxRedrawRow );
            eventBus.fireEvent( ROW_GROUPING_EVENT );
        }

    }

    //Check whether two values are equal or both null
    private boolean equalOrNull(Object o1,
                                Object o2) {
        if ( o1 == null && o2 == null ) {
            return true;
        }
        if ( o1 != null && o2 == null ) {
            return false;
        }
        if ( o1 == null && o2 != null ) {
            return false;
        }
        return o1.equals( o2 );
    }

    // Given a base row find the maximum row that needs to be re-rendered based
    // upon each columns merged cells; where each merged cell passes through the
    // base row
    private int findMaxRedrawRow(int baseRowIndex) {

        if ( data.size() == 0 ) {
            return 0;
        }

        // These should never happen, but it's a safe-guard
        if ( baseRowIndex < 0 ) {
            baseRowIndex = 0;
        }
        if ( baseRowIndex > data.size() - 1 ) {
            baseRowIndex = data.size() - 1;
        }

        int maxRedrawRow = baseRowIndex;
        DynamicDataRow baseRow = data.get( baseRowIndex );
        for ( int iCol = 0; iCol < baseRow.size(); iCol++ ) {
            int iRow = baseRowIndex;
            CellValue< ? extends Comparable< ? >> cell = baseRow.get( iCol );
            while ( cell.getRowSpan() != 1 && iRow < data.size() - 1 ) {
                iRow++;
                DynamicDataRow row = data.get( iRow );
                cell = row.get( iCol );
            }
            maxRedrawRow = (iRow > maxRedrawRow ? iRow : maxRedrawRow);
        }
        return maxRedrawRow;
    }

    //Find the bottom coordinate of a merged cell
    private Coordinate findMergedCellExtent(Coordinate c) {
        if ( c.getRow() == data.size() - 1 ) {
            return c;
        }
        Coordinate nc = new Coordinate( c.getRow() + 1,
                                        c.getCol() );
        CellValue< ? > newCell = data.get( nc );
        while ( newCell.getRowSpan() == 0 && nc.getRow() < data.size() - 1 ) {
            nc = new Coordinate( nc.getRow() + 1,
                                     nc.getCol() );
            newCell = data.get( nc );
        }
        if ( newCell.getRowSpan() != 0 ) {
            nc = new Coordinate( nc.getRow() - 1,
                                     nc.getCol() );
        }
        return nc;
    }

    // Given a base row find the minimum row that needs to be re-rendered based
    // upon each columns merged cells; where each merged cell passes through the
    // base row
    private int findMinRedrawRow(int baseRowIndex) {

        if ( data.size() == 0 ) {
            return 0;
        }

        // These should never happen, but it's a safe-guard
        if ( baseRowIndex < 0 ) {
            baseRowIndex = 0;
        }
        if ( baseRowIndex > data.size() - 1 ) {
            baseRowIndex = data.size() - 1;
        }

        int minRedrawRow = baseRowIndex;
        DynamicDataRow baseRow = data.get( baseRowIndex );
        for ( int iCol = 0; iCol < baseRow.size(); iCol++ ) {
            int iRow = baseRowIndex;
            CellValue< ? extends Comparable< ? >> cell = baseRow.get( iCol );
            while ( cell.getRowSpan() != 1 && iRow > 0 ) {
                iRow--;
                DynamicDataRow row = data.get( iRow );
                cell = row.get( iCol );
            }
            minRedrawRow = (iRow < minRedrawRow ? iRow : minRedrawRow);
        }
        return minRedrawRow;
    }

    //Get the next cell when selection moves in the specified direction
    private Coordinate getNextCell(Coordinate c,
                                   MOVE_DIRECTION dir) {

        int step = 0;
        Coordinate nc = c;

        switch ( dir ) {
            case LEFT :

                // Move left
                step = c.getCol() > 0 ? 1 : 0;
                if ( step > 0 ) {
                    nc = new Coordinate( c.getRow(),
                                         c.getCol() - step );

                    // Skip hidden columns
                    while ( nc.getCol() > 0
                            && !columns.get( nc.getCol() ).isVisible() ) {
                        nc = new Coordinate( c.getRow(),
                                             nc.getCol() - step );
                    }

                    //Move to top of a merged cells
                    CellValue< ? > newCell = data.get( nc );
                    while ( newCell.getRowSpan() == 0 ) {
                        nc = new Coordinate( nc.getRow() - 1,
                                             nc.getCol() );
                        newCell = data.get( nc );
                    }

                }
                break;
            case RIGHT :

                // Move right
                step = c.getCol() < columns.size() - 1 ? 1 : 0;
                if ( step > 0 ) {
                    nc = new Coordinate( c.getRow(),
                                         c.getCol() + step );

                    // Skip hidden columns
                    while ( nc.getCol() < columns.size() - 2
                            && !columns.get( nc.getCol() ).isVisible() ) {
                        nc = new Coordinate( c.getRow(),
                                             nc.getCol() + step );
                    }
                    //If the next column is not visible don't move
                    if ( !columns.get( nc.getCol() ).isVisible() ) {
                        nc = c;
                        break;
                    }

                    //Move to top of a merged cells
                    CellValue< ? > newCell = data.get( nc );
                    while ( newCell.getRowSpan() == 0 ) {
                        nc = new Coordinate( nc.getRow() - 1,
                                             nc.getCol() );
                        newCell = data.get( nc );
                    }

                }
                break;
            case UP :

                // Move up
                step = c.getRow() > 0 ? 1 : 0;
                if ( step > 0 ) {
                    nc = new Coordinate( c.getRow() - step,
                                         c.getCol() );

                    //Move to top of a merged cells
                    CellValue< ? > newCell = data.get( nc );
                    while ( newCell.getRowSpan() == 0 ) {
                        nc = new Coordinate( nc.getRow() - step,
                                             nc.getCol() );
                        newCell = data.get( nc );
                    }

                }
                break;
            case DOWN :

                // Move down
                step = c.getRow() < data.size() - 1 ? 1 : 0;
                if ( step > 0 ) {
                    nc = new Coordinate( c.getRow() + step,
                                         c.getCol() );

                    //Move to top of a merged cells
                    CellValue< ? > newCell = data.get( nc );
                    while ( newCell.getRowSpan() == 0 && nc.getRow() < data.size() - 1 ) {
                        nc = new Coordinate( nc.getRow() + step,
                                             nc.getCol() );
                        newCell = data.get( nc );
                    }
                    if ( newCell.getRowSpan() == 0 && nc.getRow() == data.size() - 1 ) {
                        nc = c;
                    }

                }
        }
        return nc;
    }

    // Re-index columns
    private void reindexColumns() {
        for ( int iCol = 0; iCol < columns.size(); iCol++ ) {
            DynamicColumn<T> col = columns.get( iCol );
            col.setColumnIndex( iCol );
        }
    }

    //Remove grouping by expanding applicable rows
    private void removeModelGrouping(CellValue< ? > startCell,
                                     boolean bRedraw) {

        List<DynamicDataRow> expandedRow = data.removeModelGrouping( startCell );

        //Partial redraw
        if ( bRedraw ) {
            int startRowIndex = startCell.getCoordinate().getRow();
            int minRedrawRow = findMinRedrawRow( startRowIndex - (startRowIndex > 0 ? 1 : 0) );
            int maxRedrawRow = findMaxRedrawRow( startRowIndex + (startRowIndex < data.size() - 2 ? 1 : 0) );
            for ( int iRow = 0; iRow < expandedRow.size() - 1; iRow++ ) {
                createEmptyRowElement( startRowIndex );

            }
            redrawRows( minRedrawRow,
                        maxRedrawRow );
            eventBus.fireEvent( ROW_GROUPING_EVENT );
        }

    }

    //Clear all selections
    protected void clearSelection() {
        // De-select any previously selected cells
        for ( CellValue< ? extends Comparable< ? >> cell : this.selections ) {
            cell.removeState( CellState.SELECTED );
            deselectCell( cell );
        }

        // Clear collection
        selections.clear();
        rangeDirection = MOVE_DIRECTION.NONE;
        SelectedCellChangeEvent scce = new SelectedCellChangeEvent();
        eventBus.fireEvent( scce );
    }

    abstract void createEmptyRowElement(int index);

    abstract void createRowElement(int index,
                                             DynamicDataRow rowData);

    abstract void deleteRowElement(int index);

    //Check whether "Grouping" widget has been clicked
    protected boolean isGroupWidgetClicked(Event event,
                                           Element target) {
        String eventType = event.getType();
        if ( eventType.equals( "mousedown" ) ) {
            String tagName = target.getTagName();
            if ( "img".equalsIgnoreCase( tagName ) ) {
                return true;
            }
        }
        return false;
    }

    /**
     * Redraw table rows. Partial redraw
     *
     * @param startRedrawIndex
     *            Start row index (inclusive)
     * @param endRedrawIndex
     *            End row index (inclusive)
     */
    abstract void redrawRows(int startRedrawIndex,
                                       int endRedrawIndex);

    abstract void removeRowElement(int index);

    /**
     * Remove styling indicating a selected state
     *
     * @param cell
     */
    abstract void deselectCell(CellValue< ? extends Comparable< ? >> cell);

    /**
     * Extend selection from the first cell selected to the cell specified
     *
     * @param end
     *            Extent of selection
     */
    void extendSelection(Coordinate end) {
        if ( rangeOriginCell == null ) {
            throw new IllegalArgumentException( "origin has not been set. Unable to extend selection" );
        }
        if ( end == null ) {
            throw new IllegalArgumentException( "end cannot be null" );
        }
        clearSelection();
        CellValue< ? > endCell = data.get( end );
        selectRange( rangeOriginCell,
                     endCell );
        if ( rangeOriginCell.getCoordinate().getRow() > endCell.getCoordinate().getRow() ) {
            rangeExtentCell = selections.first();
            rangeDirection = MOVE_DIRECTION.UP;
        } else {
            rangeExtentCell = selections.last();
            rangeDirection = MOVE_DIRECTION.DOWN;
        }
    }

    /**
     * Extend selection in the specified direction
     *
     * @param dir
     *            Direction to extend the selection
     */
    void extendSelection(MOVE_DIRECTION dir) {
        if ( selections.size() > 0 ) {
            CellValue< ? > activeCell = (rangeExtentCell == null ? rangeOriginCell : rangeExtentCell);
            Coordinate nc = getNextCell( activeCell.getCoordinate(),
                                         dir );
            clearSelection();
            rangeDirection = dir;
            rangeExtentCell = data.get( nc );
            selectRange( rangeOriginCell,
                         rangeExtentCell );

        }
    }

    /**
     * Retrieve the extents of a cell
     *
     * @param cv
     *            The cell for which to retrieve the extents
     * @return
     */
    CellSelectionDetail getSelectedCellExtents(CellValue< ? extends Comparable< ? >> cv) {

        if ( cv == null ) {
            throw new IllegalArgumentException( "cv cannot be null" );
        }

        // Cells in hidden columns do not have extents
        if ( !columns.get( cv.getCoordinate().getCol() ).isVisible() ) {
            return null;
        }

        Coordinate hc = cv.getHtmlCoordinate();
        TableRowElement tre = tbody.getRows().getItem( hc.getRow() ).<TableRowElement> cast();
        TableCellElement tce = tre.getCells().getItem( hc.getCol() ).<TableCellElement> cast();
        int offsetX = tce.getOffsetLeft();
        int offsetY = tce.getOffsetTop();
        int w = tce.getOffsetWidth();
        int h = tce.getOffsetHeight();
        CellSelectionDetail e = new CellSelectionDetail( cv.getCoordinate(),
                                                         offsetX,
                                                         offsetY,
                                                         h,
                                                         w );
        return e;
    }

    /**
     * Group a merged cell. If the cell is not merged across at least two rows
     * or the cell is not the top of the merged range no action is taken.
     *
     * @param start
     *            Coordinate of top of merged group.
     */
    void groupCells(Coordinate start) {
        if ( start == null ) {
            throw new IllegalArgumentException( "start cannot be null" );
        }
        CellValue< ? > startCell = data.get( start );

        //Start cell needs to be top of a merged range
        if ( startCell.getRowSpan() <= 1 && !startCell.isGrouped() ) {
            return;
        }

        clearSelection();
        if ( startCell.isGrouped() ) {
            removeModelGrouping( startCell,
                                 true );
        } else {
            applyModelGrouping( startCell,
                                true );
        }

    }

    /**
     * Hide a column
     */
    abstract void hideColumn(int index);

    /**
     * Move the selected cell
     *
     * @param dir
     *            Direction to move the selection
     */
    void moveSelection(MOVE_DIRECTION dir) {
        if ( selections.size() > 0 ) {
            CellValue< ? > activeCell = (rangeExtentCell == null ? rangeOriginCell : rangeExtentCell);
            Coordinate nc = getNextCell( activeCell.getCoordinate(),
                                         dir );
            startSelecting( nc );
            rangeDirection = dir;
        }
    }

    /**
     * Resize a column
     *
     * @param col
     * @param width
     */
    abstract void resizeColumn(DynamicColumn< ? > col,
                               int width);

    /**
     * Add styling to cell to indicate a selected state
     *
     * @param cell
     */
    abstract void selectCell(CellValue< ? extends Comparable< ? >> cell);

    /**
     * Select a range of cells
     *
     * @param startCell
     *            The first cell to select
     * @param endCell
     *            The last cell to select
     */
    void selectRange(CellValue< ? > startCell,
                     CellValue< ? > endCell) {
        int col = startCell.getCoordinate().getCol();

        //Ensure startCell precedes endCell
        if ( startCell.getCoordinate().getRow() > endCell.getCoordinate().getRow() ) {
            CellValue< ? > swap = startCell;
            startCell = endCell;
            endCell = swap;
        }

        //Ensure startCell is at the top of a merged cell
        while ( startCell.getRowSpan() == 0 ) {
            startCell = data.get( startCell.getCoordinate().getRow() - 1 ).get( col );
        }

        //Ensure endCell is at the bottom of a merged cell
        Coordinate nc = findMergedCellExtent( endCell.getCoordinate() );
        endCell = data.get( nc );

        //Select range
        for ( int iRow = startCell.getCoordinate().getRow(); iRow <= endCell.getCoordinate().getRow(); iRow++ ) {
            CellValue< ? > cell = data.get( iRow ).get( col );
            selections.add( cell );

            // Redraw selected cell
            cell.addState( CellState.SELECTED );
            selectCell( cell );
        }

        //Set extent of selected range according to the direction of selection
        switch ( rangeDirection ) {
            case DOWN :
                this.rangeExtentCell = this.selections.last();
                break;
            case UP :
                this.rangeExtentCell = this.selections.first();
                break;
        }
    }

    /**
     * Show a column
     */
    abstract void showColumn(int index);

    /**
     * Select a single cell. If the cell is merged the selection is extended to
     * include all merged cells.
     *
     * @param start
     *            The physical coordinate of the cell
     */
    void startSelecting(Coordinate start) {
        if ( start == null ) {
            throw new IllegalArgumentException( "start cannot be null" );
        }

        clearSelection();

        //Raise event signalling change in selection
        CellSelectionDetail ce = getSelectedCellExtents( data.get( start ) );
        SelectedCellChangeEvent scce = new SelectedCellChangeEvent( ce );
        eventBus.fireEvent( scce );

        CellValue< ? > startCell = data.get( start );
        selectRange( startCell,
                     startCell );
        rangeOriginCell = startCell;
        rangeExtentCell = null;
    }

    public void setData(DynamicData data) {
        this.data = data;
        this.rowMapper = new RowMapper( data );
    }

    public void setColumns(List<DynamicColumn<T>> columns) {
        this.columns = columns;
        reindexColumns();
    }

    public void onToggleMerging(ToggleMergingEvent event) {
        clearSelection();
        if ( event.isMerged() ) {
            if ( !data.isMerged() ) {
                data.setMerged( true );
                redraw();
            }
        } else {
            if ( data.isMerged() ) {
                data.setMerged( false );
                redraw();
                eventBus.fireEvent( ROW_GROUPING_EVENT );
            }
        }
    }

    public void onDeleteRow(DeleteRowEvent event) {
        int index = rowMapper.mapToMergedRow( event.getIndex() );

        // Clear any selections
        clearSelection();

        //Delete row data
        data.deleteRow( index );

        removeRowElement( index );

        // Partial redraw
        if ( data.isMerged() ) {
            if ( data.size() > 0 ) {
                int minRedrawRow = findMinRedrawRow( index - 1 );
                int maxRedrawRow = findMaxRedrawRow( index - 1 ) + 1;
                if ( maxRedrawRow > data.size() - 1 ) {
                    maxRedrawRow = data.size() - 1;
                }
                redrawRows( minRedrawRow,
                            maxRedrawRow );
            }
        }
    }

    public void onInsertRow(InsertRowEvent event) {
        int index = rowMapper.mapToMergedRow( event.getIndex() );
        DynamicDataRow rowData = cellValueFactory.makeUIRowData();
        insertRow( index,
                   rowData );
    }

    public void onAppendRow(AppendRowEvent event) {
        int index = data.size();
        DynamicDataRow rowData = cellValueFactory.makeUIRowData();
        insertRow( index,
                   rowData );
    }

    public void onCopyRows(CopyRowsEvent event) {
        copiedRows.clear();
        //Determine set of *unique* logical (grouped) rows from absolute indexes
        SortedSet<Integer> uniqueLogicalRowIndexes = new TreeSet<Integer>();
        for ( Integer iRow : event.getRowIndexes() ) {
            uniqueLogicalRowIndexes.add( rowMapper.mapToMergedRow( iRow ) );
        }
        for ( Integer iRow : uniqueLogicalRowIndexes ) {
            copiedRows.add( data.get( iRow ) );
        }
    }

    public void onPasteRows(PasteRowsEvent event) {
        if ( copiedRows == null || copiedRows.size() == 0 ) {
            return;
        }
        int iRow = rowMapper.mapToMergedRow( event.getTargetRowIndex() );
        for ( DynamicDataRow sourceRowData : copiedRows ) {
            //Clone the row, other than RowNumber column
            insertRow( iRow,
                       cloneRow( sourceRowData ) );
            iRow++;
        }

    }

    private DynamicDataRow cloneRow(DynamicDataRow sourceRowData) {
        if ( sourceRowData instanceof GroupedDynamicDataRow ) {
            return cloneDynamicDataRow( (GroupedDynamicDataRow) sourceRowData );
        }
        return cloneDynamicDataRow( sourceRowData );
    }

    private DynamicDataRow cloneDynamicDataRow(DynamicDataRow sourceRowData) {
        DynamicDataRow rowData = cellValueFactory.makeUIRowData();
        for ( int iCol = 0; iCol < sourceRowData.size(); iCol++ ) {
            CellValue< ? extends Comparable< ? >> cell = sourceRowData.get( iCol );
            rowData.get( iCol ).setValue( cell.getValue() );
        }
        return rowData;
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    private DynamicDataRow cloneDynamicDataRow(GroupedDynamicDataRow sourceRowData) {
        GroupedDynamicDataRow rowData = new GroupedDynamicDataRow();
        for ( int iCol = 0; iCol < sourceRowData.size(); iCol++ ) {
            CellValue< ? extends Comparable< ? >> sourceCell = sourceRowData.get( iCol );
            GroupedCellValue cell = sourceCell.convertToGroupedCell();
            if ( sourceCell instanceof GroupedCellValue ) {
                GroupedCellValue groupedSourceCell = (GroupedCellValue) sourceCell;
                if ( groupedSourceCell.isGrouped() ) {
                    cell.addState( CellState.GROUPED );
                }
                if ( groupedSourceCell.isOtherwise() ) {
                    cell.addState( CellState.OTHERWISE );
                }
            }
            rowData.add( cell );
        }
        for ( DynamicDataRow childRow : sourceRowData.getChildRows() ) {
            rowData.addChildRow( cloneRow( childRow ) );
        }
        return rowData;
    }

    private void insertRow(int index,
                           DynamicDataRow rowData) {

        // Clear any selections
        clearSelection();

        int minRedrawRow = index;
        int maxRedrawRow = index;

        // Find rows that need to be (re)drawn before the new row is inserted
        if ( data.isMerged() ) {
            if ( index < data.size() ) {
                minRedrawRow = findMinRedrawRow( index );
                maxRedrawRow = findMaxRedrawRow( index );
            }
        }

        data.addRow( index,
                     rowData );

        // Check extents as these could have changed after the row is inserted
        if ( data.isMerged() ) {
            if ( index < data.size() ) {
                minRedrawRow = Math.min( minRedrawRow,
                                         findMinRedrawRow( index ) );
                maxRedrawRow = Math.max( maxRedrawRow,
                                         findMaxRedrawRow( index ) );
            } else {
                minRedrawRow = Math.min( minRedrawRow,
                                         findMinRedrawRow( index ) );
                maxRedrawRow = index;
            }
        }

        // Partial redraw
        if ( !data.isMerged() ) {
            // Only new row when not merged
            createRowElement( index,
                              rowData );
        } else {
            // Affected rows when merged
            createEmptyRowElement( index );
            redrawRows( minRedrawRow,
                        maxRedrawRow );
        }

    }

    public void onDeleteColumn(DeleteColumnEvent event) {

        int firstColumnIndex = event.getFirstColumnIndex();
        boolean bRedraw = event.redraw();
        boolean bRedrawSidebar = false;

        //Expand any merged cells in column
        for ( int iCol = 0; iCol < event.getNumberOfColumns(); iCol++ ) {
            for ( int iRow = 0; iRow < data.size(); iRow++ ) {
                CellValue< ? > cv = data.get( iRow ).get( firstColumnIndex + iCol );
                if ( cv.isGrouped() ) {
                    removeModelGrouping( cv,
                                         false );
                    bRedrawSidebar = true;
                }
            }
        }

        // Clear any selections
        clearSelection();

        // Delete columns and data from grid
        for ( int iCol = 0; iCol < event.getNumberOfColumns(); iCol++ ) {
            columns.remove( firstColumnIndex );
            data.deleteColumn( firstColumnIndex );
        }
        reindexColumns();

        // Redraw
        if ( bRedraw ) {
            redraw();
            if ( bRedrawSidebar ) {
                eventBus.fireEvent( ROW_GROUPING_EVENT );
            }
        }
    }

    public void onInsertInternalColumn(InsertInternalColumnEvent<T> event) {

        int index = event.getIndex();
        boolean bRedraw = event.redraw();

        // Clear any selections
        clearSelection();

        // Add column definitions and data
        for ( int iCol = 0; iCol < event.getColumns().size(); iCol++ ) {
            DynamicColumn<T> column = event.getColumns().get( iCol );
            List<CellValue< ? extends Comparable< ? >>> columnData = event.getColumnsData().get( iCol );
            columns.add( index + iCol,
                         column );
            data.addColumn( index + iCol,
                            columnData,
                            column.isVisible() );
        }
        reindexColumns();

        // Redraw
        if ( bRedraw ) {
            redrawColumns( index,
                           columns.size() - 1 );
        }

    }

    public void onSetInternalModel(SetInternalModelEvent<M, T> event) {
        this.dropDownManager.setData( event.getData() );
        this.setColumns( event.getColumns() );
        this.setData( event.getData() );
        this.redraw();
    }

    public void onSetColumnVisibility(SetColumnVisibilityEvent event) {

        int index = event.getIndex();
        boolean isVisible = event.isVisible();

        if ( isVisible && !columns.get( index ).isVisible() ) {
            columns.get( index ).setVisible( isVisible );
            data.setColumnVisibility( index,
                                      isVisible );
            showColumn( index );
        } else if ( !isVisible && columns.get( index ).isVisible() ) {
            columns.get( index ).setVisible( isVisible );
            data.setColumnVisibility( index,
                                      isVisible );
            hideColumn( index );
        }
    }

    public void onUpdateColumnData(UpdateColumnDataEvent event) {
        int iRowIndex = 0;
        int iColIndex = event.getIndex();
        List<CellValue< ? extends Comparable< ? >>> columnData = event.getColumnData();

        for ( int iRow = 0; iRow < data.size(); iRow++ ) {
            DynamicDataRow row = data.get( iRow );
            CellValue< ? extends Comparable< ? >> cell = columnData.get( iRowIndex );
            if ( row instanceof GroupedDynamicDataRow ) {
                GroupedDynamicDataRow groupedRow = (GroupedDynamicDataRow) row;

                //Setting value on a GroupedCellValue causes all children to assume the same value
                groupedRow.get( iColIndex ).setValue( cell.getValue() );

                //So set the children's values accordingly
                for ( int iGroupedRow = 0; iGroupedRow < groupedRow.getChildRows().size(); iGroupedRow++ ) {
                    cell = columnData.get( iRowIndex );
                    groupedRow.getChildRows().get( iGroupedRow ).get( iColIndex ).setValue( cell.getValue() );
                    iRowIndex++;
                }
            } else {
                row.get( iColIndex ).setValue( cell.getValue() );
                iRowIndex++;
            }
        }

        data.assertModelMerging();

        redrawColumns( iColIndex,
                       columns.size() - 1 );
    }

    public void onUpdateColumnDefinition(UpdateColumnDefinitionEvent event) {
        int index = event.getColumnIndex();
        DynamicColumn<T> column = columns.get( index );
        column.setCell( event.getCell() );
        column.setSystemControlled( event.isSystemControlled() );
        column.setSortable( event.isSortable() );
    }

    public void onColumnResize(final ColumnResizeEvent event) {
        resizeColumn( event.getColumn(),
                      event.getWidth() );
    }

    public void onMoveColumns(MoveColumnsEvent event) {
        int sourceColumnIndex = event.getSourceColumnIndex();
        int targetColumnIndex = event.getTargetColumnIndex();
        int numberOfColumns = event.getNumberOfColumns();

        //Work-out what columns need redrawing
        int startRedrawIndex = sourceColumnIndex;
        int endRedrawIndex = targetColumnIndex;
        if ( targetColumnIndex < sourceColumnIndex ) {
            startRedrawIndex = targetColumnIndex;
            endRedrawIndex = sourceColumnIndex + numberOfColumns - 1;
        }

        //Move source columns and data to destination
        if ( targetColumnIndex > sourceColumnIndex ) {
            for ( int iCol = 0; iCol < numberOfColumns; iCol++ ) {
                this.columns.add( targetColumnIndex,
                                  this.columns.remove( sourceColumnIndex ) );
                for ( int iRow = 0; iRow < data.size(); iRow++ ) {
                    DynamicDataRow row = data.get( iRow );
                    row.move( targetColumnIndex,
                              sourceColumnIndex );
                }
            }
        } else if ( targetColumnIndex < sourceColumnIndex ) {
            for ( int iCol = 0; iCol < numberOfColumns; iCol++ ) {
                this.columns.add( targetColumnIndex,
                                  this.columns.remove( sourceColumnIndex ) );
                for ( int iRow = 0; iRow < data.size(); iRow++ ) {
                    DynamicDataRow row = data.get( iRow );
                    row.move( targetColumnIndex,
                              sourceColumnIndex );
                }
                sourceColumnIndex++;
                targetColumnIndex++;
            }
        }

        //Redraw the affected columns
        reindexColumns();
        data.assertModelMerging();
        redrawColumns( startRedrawIndex,
                       endRedrawIndex );

    }

    public void onSortData(SortDataEvent event) {

        //Remove grouping, if applicable
        if ( data.isGrouped() ) {
            ToggleMergingEvent tme = new ToggleMergingEvent( false );
            eventBus.fireEvent( tme );
        }

        //Sort data
        List<SortConfiguration> sortConfiguration = event.getSortConfiguration();
        data.sort( sortConfiguration );
        redraw();

        //Copy data and raise event for underlying model to update itself
        List<List<CellValue< ? extends Comparable< ? >>>> changedData = new ArrayList<List<CellValue< ? extends Comparable< ? >>>>();
        for ( DynamicDataRow row : data ) {
            List<CellValue< ? extends Comparable< ? >>> changedRow = new ArrayList<CellValue< ? extends Comparable< ? >>>();
            changedData.add( changedRow );

            //[size() - 1] to exclude the Analysis column data
            for ( int iCol = 0; iCol < row.size() - 1; iCol++ ) {
                CellValue< ? extends Comparable< ? >> changedCell = row.get( iCol );
                changedRow.add( changedCell );
            }
        }
        UpdateModelEvent dce = new UpdateModelEvent( new Coordinate( 0,
                                                                     0 ),
                                                     changedData );
        eventBus.fireEvent( dce );
    }

    @SuppressWarnings("rawtypes")
    public void onUpdateSelectedCells(UpdateSelectedCellsEvent event) {

        Comparable< ? > value = event.getValue();
        Map<Coordinate, List<List<CellValue< ? extends Comparable< ? >>>>> changedData = new HashMap<Coordinate, List<List<CellValue< ? extends Comparable< ? >>>>>();
        List<List<CellValue< ? extends Comparable< ? >>>> changedBlock;
        List<CellValue< ? extends Comparable< ? >>> changedRow;
        Coordinate firstSelection = selections.first().getCoordinate();

        //If selections span multiple cells, any of which are grouped we should ungroup them
        boolean bUngroupCells = false;
        if ( selections.size() > 1 ) {
            for ( CellValue< ? extends Comparable< ? >> cell : selections ) {
                if ( cell instanceof GroupedCellValue ) {
                    bUngroupCells = true;
                    break;
                }
            }
        }

        //---Update selected cells (before ungrouping otherwise selections would need to be expanded too)---
        changedBlock = new ArrayList<List<CellValue< ? extends Comparable< ? >>>>();
        for ( CellValue< ? extends Comparable< ? >> cell : selections ) {
            changedRow = new ArrayList<CellValue< ? extends Comparable< ? >>>();
            Coordinate c = cell.getCoordinate();
            if ( !columns.get( c.getCol() ).isSystemControlled() ) {
                data.set( c,
                          value );
                if ( value != null ) {
                    cell.removeState( CellState.OTHERWISE );
                }

                //Copy data that is changing for an event to update the underlying model
                if ( cell instanceof GroupedCellValue ) {
                    GroupedCellValue gcv = (GroupedCellValue) cell;
                    for ( int iChildValueIndex = 0; iChildValueIndex < gcv.getGroupedCells().size(); iChildValueIndex++ ) {
                        changedRow.add( data.get( c ) );
                        changedBlock.add( changedRow );
                    }
                } else {
                    changedRow.add( data.get( c ) );
                    changedBlock.add( changedRow );
                }
            }
        }
        Coordinate originSelected = new Coordinate( rowMapper.mapToAbsoluteRow( firstSelection.getRow() ),
                                                    firstSelection.getCol() );
        changedData.put( originSelected,
                         changedBlock );

        //---Clear dependent cells' values---
        final Context context = new Context( 0,
                                             firstSelection.getCol(),
                                             null );
        final Set<Integer> dependentColumnIndexes = this.dropDownManager.getDependentColumnIndexes( context );
        for ( Integer dependentColumnIndex : dependentColumnIndexes ) {
            changedBlock = new ArrayList<List<CellValue< ? extends Comparable< ? >>>>();
            for ( CellValue< ? extends Comparable< ? >> cell : selections ) {
                changedRow = new ArrayList<CellValue< ? extends Comparable< ? >>>();
                Coordinate dc = new Coordinate( cell.getCoordinate().getRow(),
                                                dependentColumnIndex );
                if ( !columns.get( dc.getCol() ).isSystemControlled() ) {
                    data.set( dc,
                              null );
                    if ( value != null ) {
                        cell.removeState( CellState.OTHERWISE );
                    }

                    //Copy data that is changing for an event to update the underlying model
                    if ( cell instanceof GroupedCellValue ) {
                        GroupedCellValue gcv = (GroupedCellValue) cell;
                        for ( int iChildValueIndex = 0; iChildValueIndex < gcv.getGroupedCells().size(); iChildValueIndex++ ) {
                            changedRow.add( data.get( dc ) );
                            changedBlock.add( changedRow );
                        }
                    } else {
                        changedRow.add( data.get( dc ) );
                        changedBlock.add( changedRow );
                    }
                }

            }
            Coordinate originDependent = new Coordinate( rowMapper.mapToAbsoluteRow( firstSelection.getRow() ),
                                                         dependentColumnIndex );
            changedData.put( originDependent,
                             changedBlock );
        }

        //Ungroup if applicable
        if ( bUngroupCells ) {
            for ( CellValue< ? extends Comparable< ? >> cell : selections ) {
                if ( cell instanceof GroupedCellValue ) {
                    //Removing merging partially redraws the grid
                    removeModelGrouping( cell,
                                         true );
                }
            }

        } else if ( data.isMerged() || selections.size() > 1 ) {

            //If the data is merged changes to the cells' value can cause the need for a greater range of
            //rows to be redrawn as a cell's new value could cause the merged span to increase. This is also
            //the only mechanism available to update multiple individual cells' values when multiple
            //cells are selected.
            data.assertModelMerging();

            // Partial redraw
            int baseRowIndex = selections.first().getCoordinate().getRow();
            int minRedrawRow = findMinRedrawRow( baseRowIndex );
            int maxRedrawRow = findMaxRedrawRow( baseRowIndex );

            // When merged cells become unmerged (if their value is
            // cleared need to ensure the re-draw range is at least
            // as large as the selection range
            if ( maxRedrawRow < selections.last().getCoordinate().getRow() ) {
                maxRedrawRow = selections.last().getCoordinate().getRow();
            }
            redrawRows( minRedrawRow,
                        maxRedrawRow );
        } else {
           
            //Redraw a single row
            int baseRowIndex = selections.first().getCoordinate().getRow();
            redrawRows( baseRowIndex,
                        baseRowIndex );
        }

        //Re-select applicable cells, following change to merge
        startSelecting( firstSelection );

        //Raise event for underlying model to update itself, converting logical row to physical
        UpdateModelEvent dce = new UpdateModelEvent( changedData );
        eventBus.fireEvent( dce );
    }

    @SuppressWarnings("rawtypes")
    public void onCellStateChanged(CellStateChangedEvent event) {
        Set<CellStateOperation> states = event.getStates();

        boolean bUngroupCells = false;
        Coordinate selection = selections.first().getCoordinate();
        List<List<CellValue< ? extends Comparable< ? >>>> changedData = new ArrayList<List<CellValue< ? extends Comparable< ? >>>>();

        //If selections span multiple cells, any of which are grouped we should ungroup them
        if ( selections.size() > 1 ) {
            for ( CellValue< ? extends Comparable< ? >> cell : selections ) {
                if ( cell instanceof GroupedCellValue ) {
                    bUngroupCells = true;
                    break;
                }
            }
        }

        // Update underlying data (update before ungrouping as selections would need to be expanded too)
        for ( CellValue< ? extends Comparable< ? >> cell : selections ) {
            Coordinate c = cell.getCoordinate();
            if ( !columns.get( c.getCol() ).isSystemControlled() ) {
                CellValue< ? extends Comparable< ? >> cv = data.get( c );
                for ( CellStateOperation state : states ) {
                    switch ( state.getOperation() ) {
                        case ADD :
                            cv.addState( state.getState() );
                            if ( state.getState() == CellState.OTHERWISE ) {
                                cv.setValue( null );
                            }
                            break;
                        case REMOVE :
                            cv.removeState( state.getState() );
                            break;
                    }
                }

                //Copy data that is changing for an event to update the underlying model
                if ( cell instanceof GroupedCellValue ) {
                    GroupedCellValue gcv = (GroupedCellValue) cell;
                    for ( int iChildValueIndex = 0; iChildValueIndex < gcv.getGroupedCells().size(); iChildValueIndex++ ) {
                        List<CellValue< ? extends Comparable< ? >>> changedRow = new ArrayList<CellValue< ? extends Comparable< ? >>>();
                        changedRow.add( data.get( c ) );
                        changedData.add( changedRow );
                    }
                } else {
                    List<CellValue< ? extends Comparable< ? >>> changedRow = new ArrayList<CellValue< ? extends Comparable< ? >>>();
                    changedRow.add( data.get( c ) );
                    changedData.add( changedRow );
                }

            }
        }

        //Ungroup if applicable
        if ( bUngroupCells ) {
            for ( CellValue< ? extends Comparable< ? >> cell : selections ) {
                if ( cell instanceof GroupedCellValue ) {
                    //Removing merging partially redraws the grid
                    removeModelGrouping( cell,
                                         true );
                }
            }
        } else {
            data.assertModelMerging();

            // Partial redraw
            int baseRowIndex = selections.first().getCoordinate().getRow();
            int minRedrawRow = findMinRedrawRow( baseRowIndex );
            int maxRedrawRow = findMaxRedrawRow( baseRowIndex );

            // When merged cells become unmerged (if their value is
            // cleared need to ensure the re-draw range is at least
            // as large as the selection range
            if ( maxRedrawRow < selections.last().getCoordinate().getRow() ) {
                maxRedrawRow = selections.last().getCoordinate().getRow();
            }
            redrawRows( minRedrawRow,
                        maxRedrawRow );
        }

        //Re-select applicable cells, following change to merge
        startSelecting( selection );

        //Raise event for underlying model to update itself
        UpdateModelEvent dce = new UpdateModelEvent( selections.first().getCoordinate(),
                                                     changedData );
        eventBus.fireEvent( dce );
    }

}
TOP

Related Classes of org.drools.guvnor.client.widgets.drools.decoratedgrid.AbstractMergableGridWidget$CellSelectionDetail

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.