Add support for Nebula Grid row headers

Change-Id: Iaa9bdd766abc95e76963b2d967607f7be43a7235
diff --git a/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/Grid.java b/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/Grid.java
index b7126e5..fb0c878 100644
--- a/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/Grid.java
+++ b/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/Grid.java
@@ -80,6 +80,8 @@
   private GridItem focusItem;
   private GridColumn focusColumn;
   private GridColumn treeColumn;
+  private GridColumn rowHeadersColumn;
+  private boolean isInternalColumn;
   private boolean isTree;
   private boolean disposing;
   private boolean columnHeadersVisible;
@@ -99,6 +101,7 @@
   private IScrollBarProxy vScroll;
   private IScrollBarProxy hScroll;
   private boolean scrollValuesObsolete;
+  private boolean defaultRowHeadersTextObsolete;
   private int topIndex = -1;
   private int bottomIndex = -1;
   private boolean bottomIndexShownCompletely;
@@ -147,6 +150,7 @@
     gridAdapter = new GridAdapter();
     layoutCache = new LayoutCache();
     initListeners();
+    createRowHeadersColumn();
   }
 
   /**
@@ -1172,7 +1176,6 @@
     } else {
       selectedCells.clear();
     }
-    redraw();
     cellSelectionEnabled = cellSelection;
   }
 
@@ -1340,7 +1343,6 @@
         SWT.error( SWT.ERROR_NULL_ARGUMENT );
       }
       addToCellSelection( cell );
-      redraw();
     }
   }
 
@@ -1379,7 +1381,6 @@
       for( Point cell : cells ) {
         addToCellSelection( cell );
       }
-      redraw();
     }
   }
 
@@ -1582,7 +1583,6 @@
       SWT.error( SWT.ERROR_NULL_ARGUMENT );
     }
     selectedCells.remove( cell );
-    redraw();
   }
 
   /**
@@ -1619,7 +1619,6 @@
     for( Point cell : cells ) {
       selectedCells.remove( cell );
     }
-    redraw();
   }
 
   /**
@@ -1638,7 +1637,6 @@
   public void deselectAllCells() {
     checkWidget();
     selectedCells.clear();
-    redraw();
   }
 
   /**
@@ -1892,7 +1890,6 @@
       }
       selectedCells.clear();
       addToCellSelection( cell );
-      redraw();
     }
   }
 
@@ -1937,7 +1934,6 @@
       for( Point cell : cells ) {
         addToCellSelection( cell );
       }
-      redraw();
     }
   }
 
@@ -2868,6 +2864,144 @@
     }
   }
 
+  /**
+   * Marks the receiver's row header as visible if the argument is {@code true},
+   * and marks it invisible otherwise.
+   *
+   * @param show
+   *            the new visibility state
+   * @throws org.eclipse.swt.SWTException
+   *             <ul>
+   *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been
+   *             disposed</li>
+   *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread
+   *             that created the receiver</li>
+   *             </ul>
+   *
+   * @since 3.14
+   */
+  public void setRowHeaderVisible( boolean show ) {
+    setRowHeaderVisible( show, 10 );
+  }
+
+  /**
+   * Marks the receiver's row header as visible if the argument is {@code true},
+   * and marks it invisible otherwise.
+   *
+   * @param show
+   *            the new visibility state
+   * @param minWidth
+   *            the minimun width of the row column
+   * @throws org.eclipse.swt.SWTException
+   *             <ul>
+   *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been
+   *             disposed</li>
+   *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread
+   *             that created the receiver</li>
+   *             </ul>
+   *
+   * @since 3.14
+   */
+  public void setRowHeaderVisible( boolean show, int minWidth ) {
+    checkWidget();
+    if( rowHeadersColumn != null ) {
+      if( show ) {
+        rowHeadersColumn.setMinimumWidth( Math.max( 10, minWidth ) );
+      } else {
+        rowHeadersColumn.setMinimumWidth( 0 );
+        rowHeadersColumn.setWidth( 0 );
+      }
+    }
+  }
+
+  /**
+   * Sets the row header width to the specified value. This automatically disables
+   * the auto width feature of the grid.
+   *
+   * @param width
+   *            the width of the row header
+   * @see #getItemHeaderWidth()
+   *
+   * @since 3.14
+   */
+  public void setItemHeaderWidth( int width ) {
+    checkWidget();
+    if( rowHeadersColumn != null ) {
+      rowHeadersColumn.setWidth( width );
+    }
+  }
+
+  /**
+   * Returns the row header width or 0 if row headers are not visible.
+   *
+   * @return the width of the row headers
+   * @throws org.eclipse.swt.SWTException
+   *             <ul>
+   *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been
+   *             disposed</li>
+   *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread
+   *             that created the receiver</li>
+   *             </ul>
+   *
+   * @since 3.14
+   */
+  public int getItemHeaderWidth() {
+    checkWidget();
+    return rowHeadersColumn != null ? rowHeadersColumn.getWidth() : 0;
+  }
+
+  /**
+   * Returns {@code true} if the receiver's row header is visible, and
+   * {@code false} otherwise.
+   * <p>
+   *
+   * @return the receiver's row header's visibility state
+   * @throws org.eclipse.swt.SWTException
+   *             <ul>
+   *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been
+   *             disposed</li>
+   *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread
+   *             that created the receiver</li>
+   *             </ul>
+   *
+   * @since 3.14
+   */
+  public boolean isRowHeaderVisible() {
+    checkWidget();
+    return rowHeadersColumn != null && rowHeadersColumn.getWidth() > 0;
+  }
+
+  /**
+   * Sets the value of the word-wrap feature for row headers. When enabled, this
+   * feature will word-wrap the contents of row headers.
+   *
+   * @param enabled
+   *            Set to true to enable this feature, false (default) otherwise.
+   * @see #isWordWrapHeader()
+   *
+   * @since 3.14
+   */
+  public void setWordWrapHeader( boolean enabled ) {
+    checkWidget();
+    if( rowHeadersColumn != null ) {
+      rowHeadersColumn.setWordWrap( enabled );
+    }
+  }
+
+  /**
+   * Returns the value of the row header word-wrap feature, which word-wraps the
+   * content of row headers.
+   *
+   * @return Returns whether or not the row header word-wrap feature is enabled.
+   * @see #setWordWrapHeader(boolean)
+   *
+   * @since 3.14
+   */
+  public boolean isWordWrapHeader() {
+    checkWidget();
+    return rowHeadersColumn != null ? rowHeadersColumn.getWordWrap() : false;
+  }
+
   @Override
   @SuppressWarnings("unchecked")
   public <T> T getAdapter( Class<T> adapter ) {
@@ -2887,6 +3021,9 @@
   public void setData( String key, Object value ) {
     if( !RWT.MARKUP_ENABLED.equals( key ) || !isMarkupEnabledFor( this ) ) {
       checkMarkupPrecondition( key, TEXT, () -> items.size() == 0 );
+      if( RWT.ROW_TEMPLATE.equals( key ) && value instanceof Template ) {
+        rowHeadersColumn = null;
+      }
       super.setData( key, value );
     }
   }
@@ -2970,25 +3107,26 @@
     }
   }
 
-  int newColumn( GridColumn column, int index ) {
-    if( index == -1 ) {
-      columns.add( column );
-      displayOrderedColumns.add( column );
-    } else {
-      columns.add( index, column );
-      displayOrderedColumns.add( index, column );
+  void newColumn( GridColumn column, int index ) {
+    if( !isInternalColumn ) {
+      if( index == -1 ) {
+        columns.add( column );
+        displayOrderedColumns.add( column );
+      } else {
+        columns.add( index, column );
+        displayOrderedColumns.add( index, column );
+      }
+      updatePrimaryCheckColumn();
+      for( GridItem item : items ) {
+        item.columnAdded( index );
+      }
+      if( column.isCheck() ) {
+        layoutCache.invalidateItemHeight();
+      }
+      layoutCache.invalidateHeaderHeight();
+      layoutCache.invalidateFooterHeight();
+      scheduleRedraw();
     }
-    updatePrimaryCheckColumn();
-    for( GridItem item : items ) {
-      item.columnAdded( index );
-    }
-    if( column.isCheck() ) {
-      layoutCache.invalidateItemHeight();
-    }
-    layoutCache.invalidateHeaderHeight();
-    layoutCache.invalidateFooterHeight();
-    scheduleRedraw();
-    return columns.size() - 1;
   }
 
   void removeColumn( GridColumn column ) {
@@ -3055,8 +3193,7 @@
     return displayOrderedColumns.toArray( new GridColumn[ columns.size() ] );
   }
 
-  void imageSetOnItem( int column, GridItem item ) {
-    Image image = item.getImage( column );
+  void imageSetOnItem( Image image ) {
     if( image != null && itemImageSize == null ) {
       Rectangle imageBounds = image.getBounds();
       itemImageSize = new Point( imageBounds.width, imageBounds.height );
@@ -3145,6 +3282,7 @@
         }
       }
     }
+    updateDefaultRowHeadersText();
     updateScrollBars();
   }
 
@@ -3152,6 +3290,19 @@
     return ( getStyle() & SWT.VIRTUAL ) != 0;
   }
 
+  private void updateDefaultRowHeadersText() {
+    if( isRowHeaderVisible() && defaultRowHeadersTextObsolete ) {
+      int rowCounter = 1;
+      for( int index = 0; index < items.size(); index++ ) {
+        GridItem item = items.get( index );
+        if( item.isVisible() ) {
+          item.setDefaultHeaderText( Integer.toString( rowCounter++ ) );
+        }
+      }
+      defaultRowHeadersTextObsolete = false;
+    }
+  }
+
   void updateScrollBars() {
     if( scrollValuesObsolete ) {
       Point preferredSize = getTableSize();
@@ -3617,6 +3768,31 @@
     return Math.max( 0, getCellWidth( index ) - getTextOffset( index ) - getCellPadding().right );
   }
 
+  private int getRowHeaderImageOffset() {
+    return getCellPadding().left;
+  }
+
+  private int getRowHeaderImageWidth() {
+    if( hasColumnImages( Integer.MIN_VALUE ) ) {
+      int availableWidth = Math.max( 0, getItemHeaderWidth() - getCellPadding().left );
+      return Math.min( getItemImageSize().x, availableWidth );
+    }
+    return 0;
+  }
+
+  private int getRowHeaderTextOffset() {
+    int result = getRowHeaderImageOffset();
+    if( hasColumnImages( Integer.MIN_VALUE ) ) {
+      result += getItemImageSize().x;
+      result += getCellSpacing();
+    }
+    return result;
+  }
+
+  private int getRowHeaderTextWidth() {
+    return Math.max( 0, getItemHeaderWidth() - getRowHeaderTextOffset() - getCellPadding().right );
+  }
+
   Point getItemImageSize() {
     Point result = new Point( 0, 0 );
     if( itemImageSize != null ) {
@@ -3627,10 +3803,16 @@
   }
 
   boolean hasColumnImages( int index ) {
+    if( index == Integer.MIN_VALUE ) {
+      return rowHeadersColumn != null && rowHeadersColumn.imageCount > 0;
+    }
     return getColumn( index ).imageCount > 0;
   }
 
   boolean hasColumnTexts( int index ) {
+    if( index == Integer.MIN_VALUE ) {
+      return rowHeadersColumn != null && rowHeadersColumn.textCount > 0;
+    }
     return getColumn( index ).textCount > 0;
   }
 
@@ -3851,11 +4033,39 @@
     scrollValuesObsolete = true;
   }
 
+  void invalidateDefaultRowHeadersText() {
+    defaultRowHeadersTextObsolete = true;
+    redraw();
+  }
+
   void setHasSpanning( boolean hasSpanning ) {
     this.hasSpanning = hasSpanning;
   }
 
+  boolean isRowHeadersColumn( GridColumn column ) {
+    return column != null && column == rowHeadersColumn;
+  }
+
+  GridColumn getRowHeadersColumn() {
+    return rowHeadersColumn;
+  }
+
+  private void createRowHeadersColumn() {
+    isInternalColumn = true;
+    rowHeadersColumn = new GridColumn( this, SWT.NONE );
+    rowHeadersColumn.setMoveable( false );
+    rowHeadersColumn.setMinimumWidth( 0 );
+    rowHeadersColumn.setWidth( 0 );
+    isInternalColumn = false;
+  }
+
   private boolean isFixedColumn( GridColumn column ) {
+    int fixedColumns = getFixedColumns();
+    if( fixedColumns <= 0 ) {
+      return false;
+    } else if( isRowHeadersColumn( column ) ) {
+      return true;
+    }
     int[] columnOrder = getColumnOrder();
     int visualIndex = -1;
     for( int i = 0; i < columnOrder.length && visualIndex == -1; i++ ) {
@@ -3863,15 +4073,16 @@
         visualIndex = i;
       }
     }
-    return visualIndex < getFixedColumns();
+    return visualIndex < fixedColumns - 1;
   }
 
   private int getFixedColumns() {
-    Object fixedColumns = getData( RWT.FIXED_COLUMNS );
-    if( fixedColumns instanceof Integer ) {
-      if( !( getData( RWT.ROW_TEMPLATE ) instanceof Template ) ) {
-        return ( ( Integer )fixedColumns ).intValue();
+    if( !( getData( RWT.ROW_TEMPLATE ) instanceof Template ) ) {
+      Object fixedColumns = getData( RWT.FIXED_COLUMNS );
+      if( fixedColumns instanceof Integer ) {
+        return ( ( Integer )fixedColumns ).intValue() + 1;
       }
+      return 1;
     }
     return -1;
   }
@@ -3948,6 +4159,26 @@
     }
 
     @Override
+    public int getRowHeaderImageOffset() {
+      return Grid.this.getRowHeaderImageOffset();
+    }
+
+    @Override
+    public int getRowHeaderImageWidth() {
+      return Grid.this.getRowHeaderImageWidth();
+    }
+
+    @Override
+    public int getRowHeaderTextOffset() {
+      return Grid.this.getRowHeaderTextOffset();
+    }
+
+    @Override
+    public int getRowHeaderTextWidth() {
+      return Grid.this.getRowHeaderTextWidth();
+    }
+
+    @Override
     public int getItemIndex( GridItem item ) {
       return item.index;
     }
@@ -3982,6 +4213,9 @@
       for( GridColumnGroup columnGroup : columnGroups ) {
         visitor.visit( columnGroup );
       }
+      if( rowHeadersColumn != null ) {
+        visitor.visit( rowHeadersColumn );
+      }
       for( GridColumn column : columns ) {
         visitor.visit( column );
       }
@@ -4010,10 +4244,11 @@
 
     @Override
     public int getTreeColumn() {
+      int offset = rowHeadersColumn != null ? 1 : 0;
       if( treeColumn != null ) {
-        return indexOf( treeColumn );
+        return indexOf( treeColumn ) + offset;
       } else if( getColumnCount() > 0 ) {
-        return getColumnOrder()[ 0 ];
+        return getColumnOrder()[ 0 ] + offset;
       }
       return -1;
     }
@@ -4023,6 +4258,11 @@
       return selectionType;
     }
 
+    @Override
+    public GridColumn getRowHeadersColumn() {
+      return Grid.this.getRowHeadersColumn();
+    }
+
   }
 
   private final class CellToolTipProvider
diff --git a/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/GridColumn.java b/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/GridColumn.java
index a4cdb96..5bb1ffc 100644
--- a/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/GridColumn.java
+++ b/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/GridColumn.java
@@ -1157,15 +1157,18 @@
   }
 
   int getLeft() {
-    int result = 0;
-    boolean found = false;
-    int[] columnOrder = parent.getColumnOrder();
-    for( int i = 0; i < columnOrder.length && !found; i++ ) {
-      GridColumn currentColumn = parent.getColumn( columnOrder[ i ] );
-      if( currentColumn == this ) {
-        found = true;
-      } else if( currentColumn.isVisible() ) {
-        result += currentColumn.getWidth();
+    GridColumn rowHeaders = parent.getRowHeadersColumn();
+    int result = rowHeaders == null || rowHeaders == this ? 0 : rowHeaders.getWidth();
+    if( rowHeaders != this ) {
+      boolean found = false;
+      int[] columnOrder = parent.getColumnOrder();
+      for( int i = 0; i < columnOrder.length && !found; i++ ) {
+        GridColumn currentColumn = parent.getColumn( columnOrder[ i ] );
+        if( currentColumn == this ) {
+          found = true;
+        } else if( currentColumn.isVisible() ) {
+          result += currentColumn.getWidth();
+        }
       }
     }
     return result;
diff --git a/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/GridItem.java b/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/GridItem.java
index 050697e..51938db 100644
--- a/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/GridItem.java
+++ b/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/GridItem.java
@@ -172,6 +172,7 @@
       parentItem.newItem( this, index );
       setVisible( parentItem.isVisible() && parentItem.isExpanded() );
     }
+    parent.invalidateDefaultRowHeadersText();
   }
 
   /**
@@ -204,6 +205,7 @@
       } else {
         parent.removeRootItem( this.index );
       }
+      parent.invalidateDefaultRowHeadersText();
     }
     super.dispose();
   }
@@ -474,6 +476,7 @@
           }
         }
       }
+      parent.invalidateDefaultRowHeadersText();
       parent.scheduleRedraw();
       if( unselected ) {
         Event event = new Event();
@@ -978,7 +981,7 @@
     CellData cellData = getCellData( index );
     updateColumnImageCount( index, cellData.image, image );
     cellData.image = image;
-    parent.imageSetOnItem( index, this );
+    parent.imageSetOnItem( image );
     markCached();
   }
 
@@ -1299,6 +1302,235 @@
   }
 
   /**
+   * Sets the receiver's row header text. If the text is <code>null</code> the
+   * row header will display the row number.
+   *
+   * @param text
+   *            the new text
+   * @throws IllegalArgumentException
+   *             <ul>
+   *             <li>ERROR_NULL_ARGUMENT - if the text is null</li>
+   *             </ul>
+   * @throws org.eclipse.swt.SWTException
+   *             <ul>
+   *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed
+   *             </li>
+   *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
+   *             thread that created the receiver</li>
+   *             </ul>
+   *
+   * @since 3.14
+   */
+  public void setHeaderText( String text ) {
+    checkWidget();
+    if( parent.getRowHeadersColumn() != null ) {
+      updateColumnTextCount( Integer.MIN_VALUE, internalGetHeaderText(), text );
+      getItemData().headerText = text;
+    }
+  }
+
+  /**
+   * Returns the receiver's row header text. If the text is <code>null</code>
+   * the row header will display the row number.
+   *
+   * @return the text stored for the row header or code <code>null</code> if
+   *         the default has to be displayed
+   * @throws org.eclipse.swt.SWTException
+   *             <ul>
+   *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed
+   *             </li>
+   *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
+   *             thread that created the receiver</li>
+   *             </ul>
+   *
+   * @since 3.14
+   */
+  public String getHeaderText() {
+    checkWidget();
+    return getItemData().headerText;
+  }
+
+  /**
+   * Sets the receiver's row header image. If the image is <code>null</code>
+   * none is shown in the header
+   *
+   * @param image
+   *            the new image
+   * @throws org.eclipse.swt.SWTException
+   *             <ul>
+   *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed
+   *             </li>
+   *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
+   *             thread that created the receiver</li>
+   *             </ul>
+   *
+   * @since 3.14
+   */
+  public void setHeaderImage( Image image ) {
+    checkWidget();
+    if( image != null && image.isDisposed() ) {
+      SWT.error( SWT.ERROR_INVALID_ARGUMENT );
+    }
+    if( parent.getRowHeadersColumn() != null ) {
+      updateColumnImageCount( Integer.MIN_VALUE, getItemData().headerImage, image );
+      getItemData().headerImage = image;
+      parent.imageSetOnItem( image );
+    }
+  }
+
+  /**
+   * Returns the receiver's row header image.
+   *
+   * @return the image stored for the header or <code>null</code> if none has
+   *         to be displayed
+   * @throws org.eclipse.swt.SWTException
+   *             <ul>
+   *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed
+   *             </li>
+   *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
+   *             thread that created the receiver</li>
+   *             </ul>
+   *
+   * @since 3.14
+   */
+  public Image getHeaderImage() {
+    checkWidget();
+    return getItemData().headerImage;
+  }
+
+  /**
+   * Set the new header background
+   *
+   * @param headerBackground
+   *            the color or <code>null</code>
+   * @throws org.eclipse.swt.SWTException
+   *             <ul>
+   *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed
+   *             </li>
+   *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
+   *             thread that created the receiver</li>
+   *             </ul>
+   *
+   * @since 3.14
+   */
+  public void setHeaderBackground( Color headerBackground ) {
+    checkWidget();
+    if( headerBackground != null && headerBackground.isDisposed() ) {
+      SWT.error( SWT.ERROR_INVALID_ARGUMENT );
+    }
+    if( parent.getRowHeadersColumn() != null ) {
+      getItemData().headerBackground = headerBackground;
+    }
+  }
+
+  /**
+   * Returns the receiver's row header background color
+   *
+   * @return the color or <code>null</code> if none
+   * @throws org.eclipse.swt.SWTException
+   *             <ul>
+   *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed
+   *             </li>
+   *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
+   *             thread that created the receiver</li>
+   *             </ul>
+   *
+   * @since 3.14
+   */
+  public Color getHeaderBackground() {
+    checkWidget();
+    return getItemData().headerBackground;
+  }
+
+  /**
+   * Set the new header foreground
+   *
+   * @param headerForeground
+   *            the color or <code>null</code>
+   * @throws org.eclipse.swt.SWTException
+   *             <ul>
+   *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed
+   *             </li>
+   *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
+   *             thread that created the receiver</li>
+   *             </ul>
+   *
+   * @since 3.14
+   */
+  public void setHeaderForeground( Color headerForeground ) {
+    checkWidget();
+    if( headerForeground != null && headerForeground.isDisposed() ) {
+      SWT.error( SWT.ERROR_INVALID_ARGUMENT );
+    }
+    if( parent.getRowHeadersColumn() != null ) {
+      getItemData().headerForeground = headerForeground;
+    }
+  }
+
+  /**
+   * Returns the receiver's row header foreground color
+   *
+   * @return the color or <code>null</code> if none
+   * @throws org.eclipse.swt.SWTException
+   *             <ul>
+   *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed
+   *             </li>
+   *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
+   *             thread that created the receiver</li>
+   *             </ul>
+   *
+   * @since 3.14
+   */
+  public Color getHeaderForeground() {
+    checkWidget();
+    return getItemData().headerForeground;
+  }
+
+  /**
+   * Set the new header font
+   *
+   * @param headerFont
+   *            the font or <code>null</code>
+   * @throws org.eclipse.swt.SWTException
+   *             <ul>
+   *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed
+   *             </li>
+   *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
+   *             thread that created the receiver</li>
+   *             </ul>
+   *
+   * @since 3.14
+   */
+  public void setHeaderFont( Font headerFont ) {
+    checkWidget();
+    if( headerFont != null && headerFont.isDisposed() ) {
+      SWT.error( SWT.ERROR_INVALID_ARGUMENT );
+    }
+    if( parent.getRowHeadersColumn() != null ) {
+      getItemData().headerFont = headerFont;
+    }
+  }
+
+  /**
+   * Returns the receiver's row header font
+   *
+   * @return the font or <code>null</code> if none
+   * @throws org.eclipse.swt.SWTException
+   *             <ul>
+   *             <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed
+   *             </li>
+   *             <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
+   *             thread that created the receiver</li>
+   *             </ul>
+   *
+   * @since 3.14
+   */
+  public Font getHeaderFont() {
+    checkWidget();
+    return getItemData().headerFont;
+  }
+
+  /**
    * Sets this <code>GridItem</code> to its preferred height.
    *
    * @throws org.eclipse.swt.SWTException
@@ -1542,9 +1774,36 @@
     return data;
   }
 
+  private String internalGetHeaderText() {
+    if( !parent.isRowHeaderVisible() ) {
+      return "";
+    }
+    String text = getItemData().headerText;
+    if( text == null ) {
+      text = getItemData().defaultHeaderText;
+    }
+    return text == null ? "" : text;
+  }
+
+  void setDefaultHeaderText( String text ) {
+    getItemData().defaultHeaderText = text;
+  }
+
+  private Color internalGetHeaderBackground() {
+    if( !parent.isRowHeaderVisible() ) {
+      return null;
+    }
+    Color background = getItemData().headerBackground;
+    if( background == null ) {
+      background = getItemData().defaultHeaderBackground;
+    }
+    return background;
+  }
+
   void ensureItemData() {
     if( data == null ) {
       data = new GridItemData( parent.getColumnCount() );
+      data.defaultHeaderBackground = new Color( getDisplay(), 231, 231, 231 );
     }
   }
 
@@ -1579,7 +1838,9 @@
     } else if( oldImage != null && newImage == null ) {
       delta = -1;
     }
-    if( delta != 0 && index >= 0 && index < parent.getColumnCount() ) {
+    if( delta != 0 && index == Integer.MIN_VALUE ) {
+      parent.getRowHeadersColumn().imageCount += delta;
+    } else if( delta != 0 && index >= 0 && index < parent.getColumnCount() ) {
       parent.getColumn( index ).imageCount += delta;
     }
   }
@@ -1591,7 +1852,9 @@
     } else if( oldText.length() > 0 && newText.length() == 0 ) {
       delta = -1;
     }
-    if( delta != 0 && index >= 0 && index < parent.getColumnCount() ) {
+    if( delta != 0 && index == Integer.MIN_VALUE ) {
+      parent.getRowHeadersColumn().textCount += delta;
+    } else if( delta != 0 && index >= 0 && index < parent.getColumnCount() ) {
       parent.getColumn( index ).textCount += delta;
     }
   }
@@ -1645,10 +1908,16 @@
 
     @Override
     public String[] getTexts() {
-      int columnCount = Math.max( 1, getParent().getColumnCount() );
+      int offset = getColumnOffset();
+      int columnCount = Math.max( 1, getParent().getColumnCount() ) + offset;
       String[] result = null;
       for( int i = 0; i < columnCount; i++ ) {
-        String text = getCellData( i ).text;
+        String text = "";
+        if( i == 0 && offset == 1 ) {
+          text = internalGetHeaderText();
+        } else {
+          text = getCellData( i - offset ).text;
+        }
         if( !"".equals( text ) ) {
           if( result == null ) {
             result = new String[ columnCount ];
@@ -1662,10 +1931,16 @@
 
     @Override
     public Image[] getImages() {
-      int columnCount = Math.max( 1, getParent().getColumnCount() );
+      int offset = getColumnOffset();
+      int columnCount = Math.max( 1, getParent().getColumnCount() ) + offset;
       Image[] result = null;
       for( int i = 0; i < columnCount; i++ ) {
-        Image image = getCellData( i ).image;
+        Image image = null;
+        if( i == 0 && offset == 1 ) {
+          image = getItemData().headerImage;
+        } else {
+          image = getCellData( i - offset ).image;
+        }
         if( image != null ) {
           if( result == null ) {
             result = new Image[ columnCount ];
@@ -1678,10 +1953,16 @@
 
     @Override
     public Color[] getCellBackgrounds() {
-      int columnCount = Math.max( 1, getParent().getColumnCount() );
+      int offset = getColumnOffset();
+      int columnCount = Math.max( 1, getParent().getColumnCount() ) + offset;
       Color[] result = null;
       for( int i = 0; i < columnCount; i++ ) {
-        Color background = getCellData( i ).background;
+        Color background = null;
+        if( i == 0 && offset == 1 ) {
+          background = internalGetHeaderBackground();
+        } else {
+          background = getCellData( i - offset ).background;
+        }
         if( background != null ) {
           if( result == null ) {
             result = new Color[ columnCount ];
@@ -1694,10 +1975,16 @@
 
     @Override
     public Color[] getCellForegrounds() {
-      int columnCount = Math.max( 1, getParent().getColumnCount() );
+      int offset = getColumnOffset();
+      int columnCount = Math.max( 1, getParent().getColumnCount() ) + offset;
       Color[] result = null;
       for( int i = 0; i < columnCount; i++ ) {
-        Color foreground = getCellData( i ).foreground;
+        Color foreground = null;
+        if( i == 0 && offset == 1 ) {
+          foreground = getItemData().headerForeground;
+        } else {
+          foreground = getCellData( i - offset ).foreground;
+        }
         if( foreground != null ) {
           if( result == null ) {
             result = new Color[ columnCount ];
@@ -1710,10 +1997,16 @@
 
     @Override
     public Font[] getCellFonts() {
-      int columnCount = Math.max( 1, getParent().getColumnCount() );
+      int offset = getColumnOffset();
+      int columnCount = Math.max( 1, getParent().getColumnCount() ) + offset;
       Font[] result = null;
       for( int i = 0; i < columnCount; i++ ) {
-        Font font = getCellData( i ).font;
+        Font font = null;
+        if( i == 0 && offset == 1 ) {
+          font = getItemData().headerFont;
+        } else {
+          font = getCellData( i - offset ).font;
+        }
         if( font != null ) {
           if( result == null ) {
             result = new Font[ columnCount ];
@@ -1726,10 +2019,11 @@
 
     @Override
     public boolean[] getCellChecked() {
-      int columnCount = Math.max( 1, getParent().getColumnCount() );
+      int offset = getColumnOffset();
+      int columnCount = Math.max( 1, getParent().getColumnCount() ) + offset;
       boolean[] result = null;
-      for( int i = 0; i < columnCount; i++ ) {
-        boolean checked = getCellData( i ).checked;
+      for( int i = offset; i < columnCount; i++ ) {
+        boolean checked = getCellData( i - offset ).checked;
         if( checked ) {
           if( result == null ) {
             result = new boolean[ columnCount ];
@@ -1742,10 +2036,11 @@
 
     @Override
     public boolean[] getCellGrayed() {
-      int columnCount = Math.max( 1, getParent().getColumnCount() );
+      int offset = getColumnOffset();
+      int columnCount = Math.max( 1, getParent().getColumnCount() ) + offset;
       boolean[] result = null;
-      for( int i = 0; i < columnCount; i++ ) {
-        boolean grayed = getCellData( i ).grayed;
+      for( int i = offset; i < columnCount; i++ ) {
+        boolean grayed = getCellData( i - offset ).grayed;
         if( grayed ) {
           if( result == null ) {
             result = new boolean[ columnCount ];
@@ -1758,10 +2053,11 @@
 
     @Override
     public boolean[] getCellCheckable() {
-      int columnCount = Math.max( 1, getParent().getColumnCount() );
+      int offset = getColumnOffset();
+      int columnCount = Math.max( 1, getParent().getColumnCount() ) + offset;
       boolean[] result = null;
-      for( int i = 0; i < columnCount; i++ ) {
-        boolean checkable = getCellData( i ).checkable;
+      for( int i = offset; i < columnCount; i++ ) {
+        boolean checkable = getCellData( i - offset ).checkable;
         if( !checkable ) {
           if( result == null ) {
             result = new boolean[ columnCount ];
@@ -1775,10 +2071,11 @@
 
     @Override
     public int[] getColumnSpans() {
-      int columnCount = Math.max( 1, getParent().getColumnCount() );
+      int offset = getColumnOffset();
+      int columnCount = Math.max( 1, getParent().getColumnCount() ) + offset;
       int[] result = null;
-      for( int i = 0; i < columnCount; i++ ) {
-        int span = getCellData( i ).columnSpan;
+      for( int i = offset; i < columnCount; i++ ) {
+        int span = getCellData( i - offset ).columnSpan;
         if( span != 0 ) {
           if( result == null ) {
             result = new int[ columnCount ];
@@ -1789,6 +2086,10 @@
       return result;
     }
 
+    private int getColumnOffset() {
+      return parent.getRowHeadersColumn() != null ? 1 : 0;
+    }
+
   }
 
 }
diff --git a/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/internal/GridItemData.java b/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/internal/GridItemData.java
index 847a60f..02c2464 100644
--- a/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/internal/GridItemData.java
+++ b/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/internal/GridItemData.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2014, 2015 EclipseSource and others.
+ * Copyright (c) 2014, 2020 EclipseSource 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
@@ -26,6 +26,13 @@
   public Font defaultFont;
   public Color defaultBackground;
   public Color defaultForeground;
+  public String defaultHeaderText;
+  public String headerText;
+  public Image headerImage;
+  public Color defaultHeaderBackground;
+  public Color headerBackground;
+  public Color headerForeground;
+  public Font headerFont;
   public int customHeight = -1;
   public boolean expanded;
 
@@ -74,6 +81,12 @@
     defaultFont = null;
     defaultBackground = null;
     defaultForeground = null;
+    defaultHeaderText = null;
+    headerText = null;
+    headerImage = null;
+    headerFont = null;
+    headerBackground = null;
+    headerForeground = null;
   }
 
   public static final class CellData implements SerializableCompatibility {
diff --git a/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/internal/IGridAdapter.java b/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/internal/IGridAdapter.java
index cc5ffb4..8a0ccd9 100644
--- a/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/internal/IGridAdapter.java
+++ b/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/internal/IGridAdapter.java
@@ -28,6 +28,11 @@
   int getTextOffset( int index );
   int getTextWidth( int index );
 
+  int getRowHeaderImageOffset();
+  int getRowHeaderImageWidth();
+  int getRowHeaderTextOffset();
+  int getRowHeaderTextWidth();
+
   int getItemIndex( GridItem item );
 
   void doRedraw();
@@ -37,5 +42,6 @@
 
   int getTreeColumn();
   int getSelectionType();
+  GridColumn getRowHeadersColumn();
 
 }
diff --git a/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/internal/gridcolumngroupkit/GridColumnGroupLCA.java b/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/internal/gridcolumngroupkit/GridColumnGroupLCA.java
index c0b08ea..ba69cb2 100644
--- a/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/internal/gridcolumngroupkit/GridColumnGroupLCA.java
+++ b/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/internal/gridcolumngroupkit/GridColumnGroupLCA.java
@@ -106,8 +106,8 @@
   // Helping methods
 
   private static int getLeft( GridColumnGroup group ) {
-    int result = 0;
     Grid grid = group.getParent();
+    int result = grid.getItemHeaderWidth();
     int[] columnOrder = grid.getColumnOrder();
     boolean found = false;
     for( int i = 0; i < columnOrder.length && !found; i++ ) {
diff --git a/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/internal/gridcolumnkit/GridColumnLCA.java b/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/internal/gridcolumnkit/GridColumnLCA.java
index 1afe3c8..c1747f4 100644
--- a/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/internal/gridcolumnkit/GridColumnLCA.java
+++ b/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/internal/gridcolumnkit/GridColumnLCA.java
@@ -133,7 +133,12 @@
   // Helping methods
 
   private static int getLeft( GridColumn column ) {
-    return getGridAdapter( column ).getCellLeft( getIndex( column ) );
+    GridColumn rowHeadersColumn = getGridAdapter( column ).getRowHeadersColumn();
+    if( rowHeadersColumn == column ) {
+      return 0;
+    }
+    int columnIndex = column.getParent().indexOf( column );
+    return getGridAdapter( column ).getCellLeft( columnIndex );
   }
 
   private static String getAlignment( GridColumn column ) {
@@ -148,7 +153,12 @@
   }
 
   private static int getIndex( GridColumn column ) {
-    return column.getParent().indexOf( column );
+    GridColumn rowHeadersColumn = getGridAdapter( column ).getRowHeadersColumn();
+    if( rowHeadersColumn == column ) {
+      return 0;
+    }
+    int offset = rowHeadersColumn != null ? 1 : 0;
+    return column.getParent().indexOf( column ) + offset;
   }
 
   private static int getFooterSpan( GridColumn column ) {
diff --git a/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/internal/griditemkit/GridItemOperationHandler.java b/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/internal/griditemkit/GridItemOperationHandler.java
index 464ff6f..ad8ac33 100644
--- a/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/internal/griditemkit/GridItemOperationHandler.java
+++ b/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/internal/griditemkit/GridItemOperationHandler.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2013, 2014 EclipseSource and others.
+ * Copyright (c) 2013, 2020 EclipseSource 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
@@ -13,6 +13,7 @@
 import static org.eclipse.rap.rwt.internal.lifecycle.WidgetLCAUtil.preserveProperty;
 
 import org.eclipse.nebula.widgets.grid.GridItem;
+import org.eclipse.nebula.widgets.grid.internal.IGridAdapter;
 import org.eclipse.rap.json.JsonArray;
 import org.eclipse.rap.json.JsonObject;
 import org.eclipse.rap.json.JsonValue;
@@ -47,8 +48,9 @@
     JsonValue value = properties.get( PROP_CELL_CHECKED );
     if( value != null ) {
       JsonArray arrayValue = value.asArray();
-      for( int i = 0; i < arrayValue.size(); i++ ) {
-        item.setChecked( i, arrayValue.get( i ).asBoolean() );
+      int offset = getGridAdapter( item ).getRowHeadersColumn() != null ? 1 : 0;
+      for( int i = offset; i < arrayValue.size(); i++ ) {
+        item.setChecked( i - offset, arrayValue.get( i ).asBoolean() );
       }
     }
   }
@@ -82,4 +84,8 @@
     }
   }
 
+  private static IGridAdapter getGridAdapter( GridItem item ) {
+    return item.getParent().getAdapter( IGridAdapter.class );
+  }
+
 }
diff --git a/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/internal/gridkit/GridLCA.java b/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/internal/gridkit/GridLCA.java
index 70e3f9d..6c8500a 100644
--- a/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/internal/gridkit/GridLCA.java
+++ b/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/internal/gridkit/GridLCA.java
@@ -119,7 +119,7 @@
     preserveProperty( grid, PROP_ITEM_COUNT, grid.getRootItemCount() );
     preserveProperty( grid, PROP_ITEM_HEIGHT, grid.getItemHeight() );
     preserveProperty( grid, PROP_ITEM_METRICS, getItemMetrics( grid ) );
-    preserveProperty( grid, PROP_COLUMN_COUNT, grid.getColumnCount() );
+    preserveProperty( grid, PROP_COLUMN_COUNT, getColumnCount( grid ) );
     preserveProperty( grid, PROP_COLUMN_ORDER, getColumnOrder( grid ) );
     preserveProperty( grid, PROP_FIXED_COLUMNS, getFixedColumns( grid ) );
     preserveProperty( grid, PROP_TREE_COLUMN, getTreeColumn( grid ) );
@@ -151,7 +151,7 @@
     renderProperty( grid, PROP_ITEM_COUNT, grid.getRootItemCount(), ZERO );
     renderProperty( grid, PROP_ITEM_HEIGHT, grid.getItemHeight(), ZERO );
     renderItemMetrics( grid );
-    renderProperty( grid, PROP_COLUMN_COUNT, grid.getColumnCount(), ZERO );
+    renderProperty( grid, PROP_COLUMN_COUNT, getColumnCount( grid ), ZERO );
     renderProperty( grid, PROP_COLUMN_ORDER, getColumnOrder( grid ), DEFAULT_COLUMN_ORDER );
     renderProperty( grid, PROP_FIXED_COLUMNS, getFixedColumns( grid ), -1 );
     renderProperty( grid, PROP_TREE_COLUMN, getTreeColumn( grid ), ZERO );
@@ -234,9 +234,10 @@
 
   private static String[] getCellSelection( Grid grid ) {
     Point[] selection = grid.getCellSelection();
+    int offset = getColumnOffset( grid );
     String[] result = new String[ selection.length ];
     for( int i = 0; i < result.length; i++ ) {
-      result[ i ] = getId( grid.getItem( selection[ i ].y ) ) + "#" + selection[ i ].x;
+      result[ i ] = getId( grid.getItem( selection[ i ].y ) ) + "#" + ( selection[ i ].x + offset );
     }
     return result;
   }
@@ -269,11 +270,20 @@
     return result;
   }
 
+  private static int getColumnCount( Grid grid ) {
+    int columnCount = grid.getColumnCount();
+    return getRowHeadersColumn( grid ) == null ? columnCount : columnCount + 1;
+  }
+
   private static String[] getColumnOrder( Grid grid ) {
     int[] order = grid.getColumnOrder();
-    String[] result = new String[ order.length ];
-    for( int i = 0; i < result.length; i++ ) {
-      result[ i ] = getId( grid.getColumn( order[ i ] ) );
+    int offset = getColumnOffset( grid );
+    String[] result = new String[ order.length + offset ];
+    for( int i = offset; i < result.length; i++ ) {
+      result[ i ] = getId( grid.getColumn( order[ i - offset ] ) );
+    }
+    if( offset == 1 ) {
+      result[ 0 ] = getId( getRowHeadersColumn( grid ) );
     }
     return result;
   }
@@ -284,7 +294,7 @@
 
   private static int getFocusCell( Grid grid ) {
     GridColumn focusColumn = grid.getFocusColumn();
-    return focusColumn == null ? -1 : grid.indexOf( focusColumn );
+    return focusColumn == null ? -1 : grid.indexOf( focusColumn ) + getColumnOffset( grid );
   }
 
   ///////////////
@@ -311,22 +321,45 @@
 
   static ItemMetrics[] getItemMetrics( Grid grid ) {
     int columnCount = grid.getColumnCount();
-    ItemMetrics[] result = new ItemMetrics[ columnCount ];
+    int offset = getColumnOffset( grid );
+    ItemMetrics[] result = new ItemMetrics[ columnCount + offset ];
     IGridAdapter adapter = getGridAdapter( grid );
-    for( int i = 0; i < columnCount; i++ ) {
+    for( int i = offset; i < columnCount + offset; i++ ) {
       result[ i ] = new ItemMetrics();
-      result[ i ].left = adapter.getCellLeft( i );
-      result[ i ].width = adapter.getCellWidth( i );
-      result[ i ].checkLeft = result[ i ].left + adapter.getCheckBoxOffset( i );
-      result[ i ].checkWidth = adapter.getCheckBoxWidth( i );
-      result[ i ].imageLeft = result[ i ].left + adapter.getImageOffset( i );
-      result[ i ].imageWidth = adapter.getImageWidth( i );
-      result[ i ].textLeft = result[ i ].left + adapter.getTextOffset( i );
-      result[ i ].textWidth = adapter.getTextWidth( i );
+      result[ i ].left = adapter.getCellLeft( i - offset );
+      result[ i ].width = adapter.getCellWidth( i - offset );
+      result[ i ].checkLeft = result[ i ].left + adapter.getCheckBoxOffset( i - offset );
+      result[ i ].checkWidth = adapter.getCheckBoxWidth( i - offset );
+      result[ i ].imageLeft = result[ i ].left + adapter.getImageOffset( i - offset );
+      result[ i ].imageWidth = adapter.getImageWidth( i - offset );
+      result[ i ].textLeft = result[ i ].left + adapter.getTextOffset( i - offset );
+      result[ i ].textWidth = adapter.getTextWidth( i - offset );
+    }
+    if( offset == 1 ) {
+      result[ 0 ] = getRowHeaderItemMetrics( grid );
     }
     return result;
   }
 
+  private static ItemMetrics getRowHeaderItemMetrics( Grid grid ) {
+    IGridAdapter adapter = getGridAdapter( grid );
+    ItemMetrics result = new ItemMetrics();
+    result.width = grid.getItemHeaderWidth();
+    result.imageLeft = adapter.getRowHeaderImageOffset();
+    result.imageWidth = adapter.getRowHeaderImageWidth();
+    result.textLeft = adapter.getRowHeaderTextOffset();
+    result.textWidth = adapter.getRowHeaderTextWidth();
+    return result;
+  }
+
+  private static int getColumnOffset( Grid grid ) {
+    return getRowHeadersColumn( grid ) != null ? 1 : 0;
+  }
+
+  private static GridColumn getRowHeadersColumn( Grid grid ) {
+    return getGridAdapter( grid ).getRowHeadersColumn();
+  }
+
   private static IGridAdapter getGridAdapter( Grid grid ) {
     return grid.getAdapter( IGridAdapter.class );
   }
diff --git a/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/internal/gridkit/GridOperationHandler.java b/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/internal/gridkit/GridOperationHandler.java
index f5a4a55..ccdb916 100644
--- a/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/internal/gridkit/GridOperationHandler.java
+++ b/bundles/org.eclipse.rap.nebula.widgets.grid/src/org/eclipse/nebula/widgets/grid/internal/gridkit/GridOperationHandler.java
@@ -124,7 +124,8 @@
         JsonArray currentCell = cells.get( i ).asArray();
         GridItem item = getItem( grid, currentCell.get( 0 ).asString() );
         if( item != null ) {
-          selectedCells[ i ] = new Point( currentCell.get( 1 ).asInt(), grid.indexOf( item ) );
+          int x = currentCell.get( 1 ).asInt() - getColumnOffset( grid );
+          selectedCells[ i ] = new Point( x, grid.indexOf( item ) );
         }
       }
       grid.setCellSelection( selectedCells );
@@ -179,7 +180,7 @@
   public void handleSetFocusCell( Grid grid, JsonObject properties ) {
     JsonValue value = properties.get( PROP_FOCUS_CELL );
     if( value != null ) {
-      GridColumn column = grid.getColumn( value.asInt() );
+      GridColumn column = grid.getColumn( value.asInt() - getColumnOffset( grid ) );
       if( column != null ) {
         grid.setFocusColumn( column );
       }
@@ -197,8 +198,8 @@
     ICellToolTipProvider provider = adapter.getCellToolTipProvider();
     if( provider != null ) {
       GridItem item = getItem( grid, properties.get( "item" ).asString() );
-      int columnIndex = properties.get( "column" ).asInt();
-      if( item != null && ( columnIndex == 0 || columnIndex < grid.getColumnCount() ) ) {
+      int columnIndex = properties.get( "column" ).asInt() - getColumnOffset( grid );
+      if( item != null && ( columnIndex >= 0 && columnIndex < grid.getColumnCount() ) ) {
         provider.getToolTipText( item, columnIndex );
       }
     }
@@ -295,6 +296,10 @@
     return value == null ? 0 : value.asInt();
   }
 
+  private static int getColumnOffset( Grid grid ) {
+    return getGridAdapter( grid ).getRowHeadersColumn() != null ? 1 : 0;
+  }
+
   private static IGridAdapter getGridAdapter( Grid grid ) {
     return grid.getAdapter( IGridAdapter.class );
   }
diff --git a/bundles/org.eclipse.rap.rwt/js/rwt/widgets/Grid.js b/bundles/org.eclipse.rap.rwt/js/rwt/widgets/Grid.js
index e1176bb..57750e0 100644
--- a/bundles/org.eclipse.rap.rwt/js/rwt/widgets/Grid.js
+++ b/bundles/org.eclipse.rap.rwt/js/rwt/widgets/Grid.js
@@ -323,8 +323,10 @@
     },
 
     setFocusCell : function( cell ) {
-      this._focusCell = cell;
-      this.dispatchSimpleEvent( "focusCellChanged" );
+      if( cell > 0 ) {
+        this._focusCell = cell;
+        this.dispatchSimpleEvent( "focusCellChanged" );
+      }
     },
 
     getFocusCell : function() {
@@ -661,7 +663,7 @@
             && identifier[ 0 ] !== "cellCheckBox" )
         {
           var cell = this._config.cellOrder.indexOf( identifier[ 1 ] );
-          if( cell >= 0 ) {
+          if( cell > 0 ) {
             this._dragSelection = true;
             this._onSelectionClick( event, item, cell);
             this._dragSelection = false;
diff --git a/bundles/org.eclipse.rap.rwt/js/rwt/widgets/GridItem.js b/bundles/org.eclipse.rap.rwt/js/rwt/widgets/GridItem.js
index 6788b2c..8fdea47 100644
--- a/bundles/org.eclipse.rap.rwt/js/rwt/widgets/GridItem.js
+++ b/bundles/org.eclipse.rap.rwt/js/rwt/widgets/GridItem.js
@@ -371,14 +371,14 @@
     setCellSelection : function( selection ) {
       this._cellSelection = [];
       for( var i = 0; i < selection.length; i++ ) {
-        if( selection[ i ] >= 0 ) {
+        if( selection[ i ] > 0 ) {
           this._cellSelection.push( selection[ i ] );
         }
       }
     },
 
     selectCell : function( cell ) {
-      if( !this.isCellSelected( cell ) && cell >= 0 ) {
+      if( !this.isCellSelected( cell ) && cell > 0 ) {
         this._cellSelection.push( cell );
       }
     },
diff --git a/examples/org.eclipse.rap.demo.controls/src/org/eclipse/rap/demo/controls/NebulaGridTab.java b/examples/org.eclipse.rap.demo.controls/src/org/eclipse/rap/demo/controls/NebulaGridTab.java
index c4f9384..3c706ab 100644
--- a/examples/org.eclipse.rap.demo.controls/src/org/eclipse/rap/demo/controls/NebulaGridTab.java
+++ b/examples/org.eclipse.rap.demo.controls/src/org/eclipse/rap/demo/controls/NebulaGridTab.java
@@ -46,6 +46,7 @@
   private Image image;
   private boolean headerVisible = true;
   private boolean footerVisible = true;
+  private boolean rowHeadersVisible = true;
   private boolean cellSelectionEnabled;
   public NebulaGridTab() {
     super( "Nebula Grid" );
@@ -75,6 +76,7 @@
     createSetColumnSpanGroup( parent );
     createShowHeaderButton( parent );
     createShowFooterButton( parent );
+    createShowRowHeadersButton( parent );
     createAutoHeightButton( parent );
     createWordWrapButton( parent );
     createHeaderWordWrapButton( parent );
@@ -109,10 +111,12 @@
     grid.setLayoutData( new GridData( SWT.FILL, SWT.FILL, true, true, 1, 20 ) );
     grid.setHeaderVisible( headerVisible );
     grid.setFooterVisible( footerVisible );
+    grid.setRowHeaderVisible( rowHeadersVisible, 50 );
     grid.setCellSelectionEnabled( cellSelectionEnabled );
     addGridListeners();
     createGridColumns();
     createGridItems();
+    updateRowHeaders();
   }
 
   private void addGridListeners() {
@@ -120,10 +124,12 @@
       @Override
       public void treeExpanded( TreeEvent event ) {
         log( "grid treeExpanded: " + event );
+        updateRowHeaders();
       }
       @Override
       public void treeCollapsed( TreeEvent event ) {
         log( "grid treeExpanded: " + event );
+        updateRowHeaders();
       }
     } );
     grid.addSelectionListener( new SelectionListener() {
@@ -225,6 +231,17 @@
     grid.getItem( 0 ).setExpanded( true );
   }
 
+  private void updateRowHeaders() {
+    for( GridItem item : grid.getItems() ) {
+      if( item != null && item.isVisible() ) {
+        if( item.getParentItem() != null ) {
+          item.setHeaderImage( image );
+        }
+      }
+    }
+    grid.getItem( 0 ).setHeaderText( "X" );
+  }
+
   private void createAddRemoveItemButton( Composite parent ) {
     Composite composite = new Composite( parent, SWT.NONE );
     composite.setLayout( new GridLayout( 2, false ) );
@@ -461,6 +478,19 @@
     } );
   }
 
+  private void createShowRowHeadersButton( Composite parent ) {
+    final Button button = new Button( parent, SWT.CHECK );
+    button.setText( "Show row headers" );
+    button.setSelection( true );
+    button.addSelectionListener( new SelectionAdapter() {
+      @Override
+      public void widgetSelected( SelectionEvent event ) {
+        rowHeadersVisible = button.getSelection();
+        grid.setRowHeaderVisible( rowHeadersVisible, 50 );
+      }
+    } );
+  }
+
   private void createAutoHeightButton( Composite parent ) {
     final Button button = new Button( parent, SWT.CHECK );
     button.setText( "Item/Header/Footer auto height" );
diff --git a/tests/org.eclipse.rap.nebula.widgets.grid.test/src/org/eclipse/nebula/widgets/grid/GridItem_Test.java b/tests/org.eclipse.rap.nebula.widgets.grid.test/src/org/eclipse/nebula/widgets/grid/GridItem_Test.java
index cd3e4d0..444d0a0 100644
--- a/tests/org.eclipse.rap.nebula.widgets.grid.test/src/org/eclipse/nebula/widgets/grid/GridItem_Test.java
+++ b/tests/org.eclipse.rap.nebula.widgets.grid.test/src/org/eclipse/nebula/widgets/grid/GridItem_Test.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2012, 2019 EclipseSource and others.
+ * Copyright (c) 2012, 2020 EclipseSource 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
@@ -586,7 +586,7 @@
   }
 
   @Test( expected = IllegalArgumentException.class )
-  public void testSetBackground_DisposedFont() {
+  public void testSetBackground_DisposedColor() {
     GridItem item = new GridItem( grid, SWT.NONE );
     Color background = new Color( display, 0, 0, 255 );
     background.dispose();
@@ -626,7 +626,7 @@
   }
 
   @Test( expected = IllegalArgumentException.class )
-  public void testSetBackgroundByIndex_DisposedFont() {
+  public void testSetBackgroundByIndex_DisposedColor() {
     createGridColumns( grid, 3, SWT.NONE );
     GridItem item = new GridItem( grid, SWT.NONE );
     Color background = new Color( display, 0, 0, 255 );
@@ -663,7 +663,7 @@
   }
 
   @Test( expected = IllegalArgumentException.class )
-  public void testSetForeground_DisposedFont() {
+  public void testSetForeground_DisposedColor() {
     GridItem item = new GridItem( grid, SWT.NONE );
     Color foreground = new Color( display, 0, 0, 255 );
     foreground.dispose();
@@ -703,7 +703,7 @@
   }
 
   @Test( expected = IllegalArgumentException.class )
-  public void testSetForegroundByIndex_DisposedFont() {
+  public void testSetForegroundByIndex_DisposedColor() {
     createGridColumns( grid, 3, SWT.NONE );
     GridItem item = new GridItem( grid, SWT.NONE );
     Color foreground = new Color( display, 0, 0, 255 );
@@ -1148,6 +1148,17 @@
   }
 
   @Test
+  public void testGetBounds_WithRowHeaders() {
+    createGridColumns( grid, 3, SWT.NONE );
+    createGridItems( grid, 3, 3 );
+    grid.setRowHeaderVisible( true, 10 );
+
+    assertEquals( new Rectangle( 10, 27, 20, 27 ), grid.getItem( 4 ).getBounds( 0 ) );
+    assertEquals( new Rectangle( 30, 27, 40, 27 ), grid.getItem( 4 ).getBounds( 1 ) );
+    assertEquals( new Rectangle( 70, 27, 60, 27 ), grid.getItem( 4 ).getBounds( 2 ) );
+  }
+
+  @Test
   public void testGetBounds_WithOffset() {
     createGridColumns( grid, 5, SWT.NONE );
     createGridItems( grid, 20, 3 );
@@ -1535,6 +1546,126 @@
     assertSame( gridItem.getAdapter( WidgetLCA.class ), gridItem.getAdapter( WidgetLCA.class ) );
   }
 
+  @Test
+  public void testGetHeaderText_Inital() {
+    GridItem item = new GridItem( grid, SWT.NONE );
+
+    assertNull( item.getHeaderText());
+  }
+
+  @Test
+  public void testSetHeaderText() {
+    GridItem item = new GridItem( grid, SWT.NONE );
+
+    item.setHeaderText( "foo" );
+
+    assertEquals( "foo", item.getHeaderText());
+  }
+
+  @Test
+  public void testGetHeaderImage_Inital() {
+    GridItem item = new GridItem( grid, SWT.NONE );
+
+    assertNull( item.getImage() );
+  }
+
+  @Test
+  public void testGetHeaderImage() {
+    GridItem item = new GridItem( grid, SWT.NONE );
+    Image image = loadImage( display, Fixture.IMAGE1 );
+
+    item.setHeaderImage( image );
+
+    assertSame( image, item.getHeaderImage() );
+  }
+
+  @Test( expected = IllegalArgumentException.class )
+  public void testSetHeaderImage_DisposedImage() {
+    GridItem item = new GridItem( grid, SWT.NONE );
+    Image image = loadImage( display, Fixture.IMAGE1 );
+    image.dispose();
+
+    item.setHeaderImage( image );
+  }
+
+  @Test
+  public void testGetHeaderFont_Inital() {
+    GridItem item = new GridItem( grid, SWT.NONE );
+
+    assertNull( item.getHeaderFont() );
+  }
+
+  @Test
+  public void testGetHeaderFont() {
+    GridItem item = new GridItem( grid, SWT.NONE );
+    Font font = new Font( display, "Arial", 20, SWT.BOLD );
+
+    item.setHeaderFont( font );
+
+    assertSame( font, item.getHeaderFont() );
+  }
+
+  @Test( expected = IllegalArgumentException.class )
+  public void testSetHeaderFont_DisposedFont() {
+    GridItem item = new GridItem( grid, SWT.NONE );
+    Font font = new Font( display, "Arial", 20, SWT.BOLD );
+    font.dispose();
+
+    item.setHeaderFont( font );
+  }
+
+  @Test
+  public void testGetHeaderBackground_Initial() {
+    GridItem item = new GridItem( grid, SWT.NONE );
+
+    assertNull( item.getHeaderBackground() );
+  }
+
+  @Test
+  public void testGetHeaderBackground() {
+    GridItem item = new GridItem( grid, SWT.NONE );
+    Color background = new Color( display, 0, 0, 255 );
+
+    item.setHeaderBackground( background );
+
+    assertSame( background, item.getHeaderBackground() );
+  }
+
+  @Test( expected = IllegalArgumentException.class )
+  public void testSetHeaderBackground_DisposedColor() {
+    GridItem item = new GridItem( grid, SWT.NONE );
+    Color background = new Color( display, 0, 0, 255 );
+    background.dispose();
+
+    item.setHeaderBackground( background );
+  }
+
+  @Test
+  public void testGetHeaderForeground_Initial() {
+    GridItem item = new GridItem( grid, SWT.NONE );
+
+    assertNull( item.getHeaderForeground() );
+  }
+
+  @Test
+  public void testGetHeaderForeground() {
+    GridItem item = new GridItem( grid, SWT.NONE );
+    Color foreground = new Color( display, 0, 0, 255 );
+
+    item.setHeaderForeground( foreground );
+
+    assertSame( foreground, item.getHeaderForeground() );
+  }
+
+  @Test( expected = IllegalArgumentException.class )
+  public void testSetHeaderForeground_DisposedColor() {
+    GridItem item = new GridItem( grid, SWT.NONE );
+    Color foreground = new Color( display, 0, 0, 255 );
+    foreground.dispose();
+
+    item.setHeaderForeground( foreground );
+  }
+
   private void fakeSpacing( Grid grid, int spacing ) {
     grid.layoutCache.cellSpacing = spacing;
   }
diff --git a/tests/org.eclipse.rap.nebula.widgets.grid.test/src/org/eclipse/nebula/widgets/grid/Grid_Test.java b/tests/org.eclipse.rap.nebula.widgets.grid.test/src/org/eclipse/nebula/widgets/grid/Grid_Test.java
index 8ae6bd3..c112a5a 100644
--- a/tests/org.eclipse.rap.nebula.widgets.grid.test/src/org/eclipse/nebula/widgets/grid/Grid_Test.java
+++ b/tests/org.eclipse.rap.nebula.widgets.grid.test/src/org/eclipse/nebula/widgets/grid/Grid_Test.java
@@ -2860,6 +2860,18 @@
   }
 
   @Test
+  public void testGetOrigin_RowHeaderVisible() {
+    GridColumn[] columns = createGridColumns( grid, 10, SWT.NONE );
+    GridItem[] items = createGridItems( grid, 20, 3 );
+    horizontalBar.setSelection( 150 );
+    grid.setRowHeaderVisible( true, 10 );
+    grid.setTopIndex( 40 );
+
+    Point expected = new Point( -20, 2 * grid.getItemHeight() );
+    assertEquals( expected, grid.getOrigin( columns[ 3 ], items[ 48 ] ) );
+  }
+
+  @Test
   public void testIsShown() {
     GridItem[] items = createGridItems( grid, 20, 0 );
     grid.setTopIndex( 5 );
@@ -2906,11 +2918,12 @@
   public void testItemProvider_visitedItems() {
     GridColumnGroup group = new GridColumnGroup( grid, SWT.NONE );
     GridColumn column = new GridColumn( group, SWT.NONE );
+    GridColumn rowHeader = grid.getRowHeadersColumn();
     GridItem item = new GridItem( grid, SWT.NONE );
 
     List<Item> items = getVisitedItems();
 
-    assertEquals( Arrays.asList( group, column, item ), items );
+    assertEquals( Arrays.asList( group, rowHeader, column, item ), items );
   }
 
   @Test
diff --git a/tests/org.eclipse.rap.nebula.widgets.grid.test/src/org/eclipse/nebula/widgets/grid/internal/gridcolumngroupkit/GridColumnGroupLCA_Test.java b/tests/org.eclipse.rap.nebula.widgets.grid.test/src/org/eclipse/nebula/widgets/grid/internal/gridcolumngroupkit/GridColumnGroupLCA_Test.java
index 3766183..d7d6771 100644
--- a/tests/org.eclipse.rap.nebula.widgets.grid.test/src/org/eclipse/nebula/widgets/grid/internal/gridcolumngroupkit/GridColumnGroupLCA_Test.java
+++ b/tests/org.eclipse.rap.nebula.widgets.grid.test/src/org/eclipse/nebula/widgets/grid/internal/gridcolumngroupkit/GridColumnGroupLCA_Test.java
@@ -289,6 +289,20 @@
   }
 
   @Test
+  public void testRenderLeftWithRowHeader() throws IOException {
+    createGridColumns( grid, 3, SWT.NONE );
+    grid.setRowHeaderVisible( true, 10 );
+    grid.getColumn( 1 ).setVisible( false );
+    createGridColumns( group, 3, SWT.NONE );
+
+    grid.getColumn( 0 ).setWidth( 30 );
+    lca.renderChanges( group );
+
+    TestMessage message = Fixture.getProtocolMessage();
+    assertEquals( 100, message.findSetProperty( group, "left" ).asInt() );
+  }
+
+  @Test
   public void testRenderLeftUnchanged() throws IOException {
     createGridColumns( grid, 3, SWT.NONE );
     grid.getColumn( 1 ).setVisible( false );
diff --git a/tests/org.eclipse.rap.nebula.widgets.grid.test/src/org/eclipse/nebula/widgets/grid/internal/gridcolumnkit/GridColumnLCA_Test.java b/tests/org.eclipse.rap.nebula.widgets.grid.test/src/org/eclipse/nebula/widgets/grid/internal/gridcolumnkit/GridColumnLCA_Test.java
index 49f2dbd..1a51ebd 100644
--- a/tests/org.eclipse.rap.nebula.widgets.grid.test/src/org/eclipse/nebula/widgets/grid/internal/gridcolumnkit/GridColumnLCA_Test.java
+++ b/tests/org.eclipse.rap.nebula.widgets.grid.test/src/org/eclipse/nebula/widgets/grid/internal/gridcolumnkit/GridColumnLCA_Test.java
@@ -290,7 +290,7 @@
     lca.render( column );
 
     TestMessage message = Fixture.getProtocolMessage();
-    assertEquals( 0, message.findCreateProperty( column, "index" ).asInt() );
+    assertEquals( 1, message.findCreateProperty( column, "index" ).asInt() );
   }
 
   @Test
@@ -299,7 +299,7 @@
     lca.renderChanges( column );
 
     TestMessage message = Fixture.getProtocolMessage();
-    assertEquals( 1, message.findSetProperty( column, "index" ).asInt() );
+    assertEquals( 2, message.findSetProperty( column, "index" ).asInt() );
   }
 
   @Test
@@ -335,6 +335,17 @@
   }
 
   @Test
+  public void testRenderLeftWithRowHeader() throws IOException {
+    grid.setRowHeaderVisible( true, 10 );
+    GridColumn column2 = new GridColumn( grid, SWT.NONE, 0 );
+    column2.setWidth( 50 );
+    lca.renderChanges( column );
+
+    TestMessage message = Fixture.getProtocolMessage();
+    assertEquals( 60, message.findSetProperty( column, "left" ).asInt() );
+  }
+
+  @Test
   public void testRenderLeftUnchanged() throws IOException {
     Fixture.markInitialized( display );
     Fixture.markInitialized( column );
diff --git a/tests/org.eclipse.rap.nebula.widgets.grid.test/src/org/eclipse/nebula/widgets/grid/internal/griditemkit/GridItemLCA_Test.java b/tests/org.eclipse.rap.nebula.widgets.grid.test/src/org/eclipse/nebula/widgets/grid/internal/griditemkit/GridItemLCA_Test.java
index fea5c05..837d0db 100644
--- a/tests/org.eclipse.rap.nebula.widgets.grid.test/src/org/eclipse/nebula/widgets/grid/internal/griditemkit/GridItemLCA_Test.java
+++ b/tests/org.eclipse.rap.nebula.widgets.grid.test/src/org/eclipse/nebula/widgets/grid/internal/griditemkit/GridItemLCA_Test.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2012, 2015 EclipseSource and others.
+ * Copyright (c) 2012, 2020 EclipseSource 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
@@ -307,7 +307,22 @@
     lca.renderChanges( item );
 
     TestMessage message = Fixture.getProtocolMessage();
-    JsonArray expected = JsonArray.readFrom( "[\"item 0.0\", \"item 0.1\"]" );
+    JsonArray expected = JsonArray.readFrom( "[\"\", \"item 0.0\", \"item 0.1\"]" );
+    assertEquals( expected, message.findSetProperty( item, "texts" ) );
+  }
+
+  @Test
+  public void testRenderTextsWithRowHeader() throws IOException {
+    grid.setRowHeaderVisible( true );
+    createGridColumns( grid, 2, SWT.NONE );
+
+    item.setHeaderText( "header 0" );
+    item.setText( 0, "item 0.0" );
+    item.setText( 1, "item 0.1" );
+    lca.renderChanges( item );
+
+    TestMessage message = Fixture.getProtocolMessage();
+    JsonArray expected = JsonArray.readFrom( "[\"header 0\", \"item 0.0\", \"item 0.1\"]" );
     assertEquals( expected, message.findSetProperty( item, "texts" ) );
   }
 
@@ -362,7 +377,24 @@
 
     TestMessage message = Fixture.getProtocolMessage();
     JsonValue actual = message.findSetProperty( item, "images" );
-    String expected = "[null, [\"rwt-resources/generated/90fb0bfe.gif\",58,12]]";
+    String expected = "[null, null, [\"rwt-resources/generated/90fb0bfe.gif\",58,12]]";
+    assertEquals( JsonArray.readFrom( expected ), actual );
+  }
+
+  @Test
+  public void testRenderImagesWithRowHeader() throws IOException {
+    grid.setRowHeaderVisible( true );
+    createGridColumns( grid, 2, SWT.NONE );
+    Image image = loadImage( display, Fixture.IMAGE1 );
+
+    item.setHeaderImage( image );
+    item.setImage( 1, image );
+    lca.renderChanges( item );
+
+    TestMessage message = Fixture.getProtocolMessage();
+    JsonValue actual = message.findSetProperty( item, "images" );
+    String expected = "[[\"rwt-resources/generated/90fb0bfe.gif\",58,12], "
+                    + "null, [\"rwt-resources/generated/90fb0bfe.gif\",58,12]]";
     assertEquals( JsonArray.readFrom( expected ), actual );
   }
 
@@ -513,7 +545,21 @@
 
     TestMessage message = Fixture.getProtocolMessage();
     JsonValue actual = message.findSetProperty( item, "cellBackgrounds" );
-    assertEquals( JsonArray.readFrom( "[null, [0,255,0,255]]" ), actual );
+    assertEquals( JsonArray.readFrom( "[null, null, [0,255,0,255]]" ), actual );
+  }
+
+  @Test
+  public void testRenderCellBackgroundsWithRowHeader() throws IOException {
+    grid.setRowHeaderVisible( true );
+    createGridColumns( grid, 2, SWT.NONE );
+
+    item.setHeaderBackground( display.getSystemColor( SWT.COLOR_RED ) );
+    item.setBackground( 1, display.getSystemColor( SWT.COLOR_GREEN ) );
+    lca.renderChanges( item );
+
+    TestMessage message = Fixture.getProtocolMessage();
+    JsonValue actual = message.findSetProperty( item, "cellBackgrounds" );
+    assertEquals( JsonArray.readFrom( "[[255,0,0,255], null, [0,255,0,255]]" ), actual );
   }
 
   @Test
@@ -565,7 +611,20 @@
 
     TestMessage message = Fixture.getProtocolMessage();
     JsonValue actual = message.findSetProperty( item, "cellForegrounds" );
-    assertEquals( JsonArray.readFrom( "[null, [0,255,0,255]]" ), actual );
+    assertEquals( JsonArray.readFrom( "[null, null, [0,255,0,255]]" ), actual );
+  }
+
+  @Test
+  public void testRenderCellForegroundsWithRowHeader() throws IOException {
+    createGridColumns( grid, 2, SWT.NONE );
+
+    item.setHeaderForeground( display.getSystemColor( SWT.COLOR_RED ) );
+    item.setForeground( 1, display.getSystemColor( SWT.COLOR_GREEN ) );
+    lca.renderChanges( item );
+
+    TestMessage message = Fixture.getProtocolMessage();
+    JsonValue actual = message.findSetProperty( item, "cellForegrounds" );
+    assertEquals( JsonArray.readFrom( "[[255,0,0,255], null, [0,255,0,255]]" ), actual );
   }
 
   @Test
@@ -617,7 +676,24 @@
 
     TestMessage message = Fixture.getProtocolMessage();
     JsonValue actual = message.findSetProperty( item, "cellFonts" );
-    assertEquals( JsonArray.readFrom( "[null, [[\"Arial\"], 20, true, false]]" ), actual );
+    assertEquals( JsonArray.readFrom( "[null, null, [[\"Arial\"], 20, true, false]]" ), actual );
+  }
+
+  @Test
+  public void testRenderCellFontsWithRowHeader() throws IOException {
+    grid.setRowHeaderVisible( true );
+    createGridColumns( grid, 2, SWT.NONE );
+
+    Font font = new Font( display, "Arial", 20, SWT.BOLD );
+    item.setHeaderFont( font );
+    item.setFont( 1, font );
+    lca.renderChanges( item );
+
+    TestMessage message = Fixture.getProtocolMessage();
+    JsonValue actual = message.findSetProperty( item, "cellFonts" );
+    JsonArray expected = JsonArray.readFrom( "[[[\"Arial\"], 20, true, false], "
+                                           + "null, [[\"Arial\"], 20, true, false]]" );
+    assertEquals( expected, actual );
   }
 
   @Test
@@ -706,7 +782,7 @@
     lca.renderChanges( item );
 
     TestMessage message = Fixture.getProtocolMessage();
-    JsonArray expected = JsonArray.readFrom( "[false,true]" );
+    JsonArray expected = JsonArray.readFrom( "[false,false,true]" );
     assertEquals( expected, message.findSetProperty( item, "cellChecked" ) );
   }
 
@@ -766,7 +842,7 @@
     lca.renderChanges( item );
 
     TestMessage message = Fixture.getProtocolMessage();
-    JsonArray expected = JsonArray.readFrom( "[false, true]" );
+    JsonArray expected = JsonArray.readFrom( "[false, false, true]" );
     assertEquals( expected, message.findSetProperty( item, "cellGrayed" ) );
   }
 
@@ -826,7 +902,7 @@
     lca.renderChanges( item );
 
     TestMessage message = Fixture.getProtocolMessage();
-    JsonArray expected = JsonArray.readFrom( "[true, false]" );
+    JsonArray expected = JsonArray.readFrom( "[true, true, false]" );
     assertEquals( expected, message.findSetProperty( item, "cellCheckable" ) );
   }
 
@@ -882,7 +958,7 @@
     lca.renderChanges( item );
 
     TestMessage message = Fixture.getProtocolMessage();
-    JsonArray expected = JsonArray.readFrom( "[0, 1, 0]" );
+    JsonArray expected = JsonArray.readFrom( "[0, 0, 1, 0]" );
     assertEquals( expected, message.findSetProperty( item, "columnSpans" ) );
   }
 
diff --git a/tests/org.eclipse.rap.nebula.widgets.grid.test/src/org/eclipse/nebula/widgets/grid/internal/griditemkit/GridItemOperationHandler_Test.java b/tests/org.eclipse.rap.nebula.widgets.grid.test/src/org/eclipse/nebula/widgets/grid/internal/griditemkit/GridItemOperationHandler_Test.java
index be03cb8..8872829 100644
--- a/tests/org.eclipse.rap.nebula.widgets.grid.test/src/org/eclipse/nebula/widgets/grid/internal/griditemkit/GridItemOperationHandler_Test.java
+++ b/tests/org.eclipse.rap.nebula.widgets.grid.test/src/org/eclipse/nebula/widgets/grid/internal/griditemkit/GridItemOperationHandler_Test.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2013, 2014 EclipseSource and others.
+ * Copyright (c) 2013, 2020 EclipseSource 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
@@ -60,6 +60,7 @@
     createGridColumns( grid, 3, SWT.NONE );
 
     JsonArray cellChecked = new JsonArray()
+      .add( false )
       .add( true )
       .add( false )
       .add( true );
diff --git a/tests/org.eclipse.rap.nebula.widgets.grid.test/src/org/eclipse/nebula/widgets/grid/internal/gridkit/GridLCA_Test.java b/tests/org.eclipse.rap.nebula.widgets.grid.test/src/org/eclipse/nebula/widgets/grid/internal/gridkit/GridLCA_Test.java
index 3ce9e96..8577aa1 100644
--- a/tests/org.eclipse.rap.nebula.widgets.grid.test/src/org/eclipse/nebula/widgets/grid/internal/gridkit/GridLCA_Test.java
+++ b/tests/org.eclipse.rap.nebula.widgets.grid.test/src/org/eclipse/nebula/widgets/grid/internal/gridkit/GridLCA_Test.java
@@ -32,6 +32,7 @@
 import org.eclipse.nebula.widgets.grid.Grid;
 import org.eclipse.nebula.widgets.grid.GridColumn;
 import org.eclipse.nebula.widgets.grid.GridItem;
+import org.eclipse.nebula.widgets.grid.internal.IGridAdapter;
 import org.eclipse.nebula.widgets.grid.internal.gridkit.GridLCA.ItemMetrics;
 import org.eclipse.rap.json.JsonArray;
 import org.eclipse.rap.json.JsonObject;
@@ -119,9 +120,7 @@
   }
 
   @Test
-  public void testRenderCreateWithFixedColumns() throws IOException {
-    grid.setData( RWT.FIXED_COLUMNS, Integer.valueOf( 1 ) );
-
+  public void testRenderCreate_rendersSplitContainer() throws IOException {
     lca.renderInitialization( grid );
 
     TestMessage message = Fixture.getProtocolMessage();
@@ -130,6 +129,17 @@
   }
 
   @Test
+  public void testRenderCreate_doesNotRenderSplitContainerWithRowTemplate() throws IOException {
+    grid.setData( RWT.ROW_TEMPLATE, new Template() );
+
+    lca.renderInitialization( grid );
+
+    TestMessage message = Fixture.getProtocolMessage();
+    CreateOperation operation = message.findCreateOperation( grid );
+    assertTrue( operation.getProperties().names().indexOf( "splitContainer" ) == -1 );
+  }
+
+  @Test
   public void testRenderInitialization_setsOperationHandler() throws IOException {
     String id = getId( grid );
 
@@ -242,16 +252,19 @@
 
   @Test
   public void testRenderItemMetrics() throws IOException {
+    grid.setRowHeaderVisible( true, 10 );
     GridColumn column = new GridColumn( grid, SWT.NONE );
     column.setWidth( 50 );
     GridItem[] items = createGridItems( grid, 3, 1 );
+    items[ 0 ].setHeaderText( "bar" );
     items[ 0 ].setText( "foo" );
 
     lca.renderChanges( grid );
 
     TestMessage message = Fixture.getProtocolMessage();
     JsonArray actual = message.findSetProperty( grid, "itemMetrics" ).asArray();
-    assertEquals( JsonArray.readFrom( "[0, 0, 50, 0, 0, 0, 44, 0, 0]" ), actual.get( 0 ) );
+    assertEquals( JsonArray.readFrom( "[0, 0, 10, 6, 0, 6, 0, 0, 0]" ), actual.get( 0 ) );
+    assertEquals( JsonArray.readFrom( "[1, 10, 50, 10, 0, 10, 44, 10, 0]" ), actual.get( 1 ) );
   }
 
   @Test
@@ -264,8 +277,9 @@
 
     TestMessage message = Fixture.getProtocolMessage();
     JsonArray actual = message.findSetProperty( grid, "itemMetrics" ).asArray();
-    assertEquals( JsonArray.readFrom( "[0, 0, 20, 23, 0, 23, 0, 0, 21]" ), actual.get( 0 ) );
-    assertEquals( JsonArray.readFrom( "[1, 20, 40, 49, 0, 49, 5, 26, 21]" ), actual.get( 1 ) );
+    assertEquals( JsonArray.readFrom( "[0, 0, 0, 6, 0, 6, 0, 0, 0]" ), actual.get( 0 ) );
+    assertEquals( JsonArray.readFrom( "[1, 0, 20, 23, 0, 23, 0, 0, 21]" ), actual.get( 1 ) );
+    assertEquals( JsonArray.readFrom( "[2, 20, 40, 49, 0, 49, 5, 26, 21]" ), actual.get( 2 ) );
   }
 
   @Test
@@ -298,7 +312,7 @@
     lca.renderChanges( grid );
 
     TestMessage message = Fixture.getProtocolMessage();
-    assertEquals( 1, message.findSetProperty( grid, "columnCount" ).asInt() );
+    assertEquals( 2, message.findSetProperty( grid, "columnCount" ).asInt() );
   }
 
   @Test
@@ -331,7 +345,9 @@
     lca.renderChanges( grid );
 
     TestMessage message = Fixture.getProtocolMessage();
+    GridColumn rowHeadersColumn = grid.getAdapter( IGridAdapter.class ).getRowHeadersColumn();
     JsonArray expected = new JsonArray()
+      .add( getId( rowHeadersColumn ) )
       .add( getId( columns[ 2 ] ) )
       .add( getId( columns[ 0 ] ) )
       .add( getId( columns[ 1 ] ) );
@@ -371,7 +387,7 @@
     lca.renderChanges( grid );
 
     TestMessage message = Fixture.getProtocolMessage();
-    assertEquals( 1, message.findSetProperty( grid, "treeColumn" ).asInt() );
+    assertEquals( 2, message.findSetProperty( grid, "treeColumn" ).asInt() );
   }
 
   @Test
@@ -723,7 +739,7 @@
     lca.renderChanges( grid );
 
     TestMessage message = Fixture.getProtocolMessage();
-    int expected = grid.indexOf( columns[ 1 ] );
+    int expected = grid.indexOf( columns[ 1 ] ) + 1;
     assertEquals( expected, message.findSetProperty( grid, "focusCell" ).asInt() );
   }
 
@@ -869,12 +885,12 @@
     String item0Id = getId( grid.getItem( 0 ) );
     String item4Id = getId( grid.getItem( 4 ) );
     Object expected = new JsonArray()
-      .add( item0Id + "#0" )
       .add( item0Id + "#1" )
       .add( item0Id + "#2" )
-      .add( item4Id + "#0")
-      .add( item4Id + "#1" )
-      .add( item4Id + "#2" );
+      .add( item0Id + "#3" )
+      .add( item4Id + "#1")
+      .add( item4Id + "#2" )
+      .add( item4Id + "#3" );
     assertEquals( expected, message.findSetProperty( grid, "cellSelection" ) );
   }
 
@@ -1082,7 +1098,7 @@
     Fixture.markInitialized( grid );
 
     String itemId = getId( item );
-    processCellToolTipRequest( grid, itemId, 1 );
+    processCellToolTipRequest( grid, itemId, 2 );
 
     TestMessage message = Fixture.getProtocolMessage();
     assertEquals( "foo", message.findSetProperty( grid, "cellToolTipText" ).asString() );
@@ -1128,19 +1144,36 @@
     ItemMetrics[] metrics = GridLCA.getItemMetrics( grid );
 
     assertEquals( 0, metrics[ 0 ].left );
-    assertEquals( 100, metrics[ 1 ].left );
+    assertEquals( 0, metrics[ 1 ].left );
+    assertEquals( 100, metrics[ 2 ].left );
   }
 
   @Test
-  public void testGetItemMetrics_CellWidth() {
+  public void testGetItemMetrics_CellLeftWithRowHeader() {
+    grid.setRowHeaderVisible( true, 10 );
     GridColumn[] columns = createGridColumns( grid, 2, SWT.NONE );
     columns[ 0 ].setWidth( 100 );
     columns[ 1 ].setWidth( 150 );
 
     ItemMetrics[] metrics = GridLCA.getItemMetrics( grid );
 
-    assertEquals( 100, metrics[ 0 ].width );
-    assertEquals( 150, metrics[ 1 ].width );
+    assertEquals( 0, metrics[ 0 ].left );
+    assertEquals( 10, metrics[ 1 ].left );
+    assertEquals( 110, metrics[ 2 ].left );
+  }
+
+  @Test
+  public void testGetItemMetrics_CellWidth() {
+    grid.setRowHeaderVisible( true, 10 );
+    GridColumn[] columns = createGridColumns( grid, 2, SWT.NONE );
+    columns[ 0 ].setWidth( 100 );
+    columns[ 1 ].setWidth( 150 );
+
+    ItemMetrics[] metrics = GridLCA.getItemMetrics( grid );
+
+    assertEquals( 10, metrics[ 0 ].width );
+    assertEquals( 100, metrics[ 1 ].width );
+    assertEquals( 150, metrics[ 2 ].width );
   }
 
   @Test
@@ -1153,15 +1186,15 @@
     GridItem[] items = createGridItems( grid, 3, 1 );
 
     ItemMetrics[] metrics = GridLCA.getItemMetrics( grid );
-    assertEquals( 0, metrics[ 0 ].imageLeft );
-    assertEquals( 106, metrics[ 1 ].imageLeft );
+    assertEquals( 0, metrics[ 1 ].imageLeft );
+    assertEquals( 106, metrics[ 2 ].imageLeft );
 
     items[ 1 ].setImage( image2 );
     items[ 0 ].setImage( 1, image1 );
 
     metrics = GridLCA.getItemMetrics( grid );
-    assertEquals( 0, metrics[ 0 ].imageLeft );
-    assertEquals( 106, metrics[ 1 ].imageLeft );
+    assertEquals( 0, metrics[ 1 ].imageLeft );
+    assertEquals( 106, metrics[ 2 ].imageLeft );
   }
 
   @Test
@@ -1180,13 +1213,13 @@
     items[ 0 ].setImage( image1 );
 
     metrics = GridLCA.getItemMetrics( grid );
-    assertEquals( 50, metrics[ 0 ].imageWidth );
+    assertEquals( 50, metrics[ 1 ].imageWidth );
 
     items[ 1 ].setImage( null );
     items[ 0 ].setImage( null );
 
     metrics = GridLCA.getItemMetrics( grid );
-    assertEquals( 0, metrics[ 0 ].imageWidth );
+    assertEquals( 0, metrics[ 1 ].imageWidth );
   }
 
   @Test
@@ -1198,12 +1231,12 @@
     GridItem[] items = createGridItems( grid, 3, 1 );
 
     ItemMetrics[] metrics = GridLCA.getItemMetrics( grid );
-    assertEquals( 106, metrics[ 1 ].textLeft );
+    assertEquals( 106, metrics[ 2 ].textLeft );
 
     items[ 0 ].setImage( 1, image );
 
     metrics = GridLCA.getItemMetrics( grid );
-    assertEquals( 206, metrics[ 1 ].textLeft );
+    assertEquals( 206, metrics[ 2 ].textLeft );
   }
 
   @Test
@@ -1218,7 +1251,7 @@
 
     ItemMetrics[] metrics = GridLCA.getItemMetrics( grid );
 
-    assertEquals( 123, metrics[ 0 ].textLeft );
+    assertEquals( 123, metrics[ 1 ].textLeft );
   }
 
   @Test
@@ -1233,7 +1266,7 @@
 
     ItemMetrics[] metrics = GridLCA.getItemMetrics( grid );
 
-    assertEquals( 71, metrics[ 0 ].textWidth );
+    assertEquals( 71, metrics[ 1 ].textWidth );
   }
 
   @Test
diff --git a/tests/org.eclipse.rap.nebula.widgets.grid.test/src/org/eclipse/nebula/widgets/grid/internal/gridkit/GridOperationHandler_Test.java b/tests/org.eclipse.rap.nebula.widgets.grid.test/src/org/eclipse/nebula/widgets/grid/internal/gridkit/GridOperationHandler_Test.java
index 8bf971c..4ae867d 100644
--- a/tests/org.eclipse.rap.nebula.widgets.grid.test/src/org/eclipse/nebula/widgets/grid/internal/gridkit/GridOperationHandler_Test.java
+++ b/tests/org.eclipse.rap.nebula.widgets.grid.test/src/org/eclipse/nebula/widgets/grid/internal/gridkit/GridOperationHandler_Test.java
@@ -114,8 +114,8 @@
 
     Point[] selectedCells = grid.getCellSelection();
     assertEquals( 2, selectedCells.length );
-    assertEquals( new Point( 1, 0 ), selectedCells[ 0 ] );
-    assertEquals( new Point( 2, 2 ), selectedCells[ 1 ] );
+    assertEquals( new Point( 0, 0 ), selectedCells[ 0 ] );
+    assertEquals( new Point( 1, 2 ), selectedCells[ 1 ] );
   }
 
   @Test
@@ -169,13 +169,14 @@
     grid.setCellSelectionEnabled( true );
     GridColumn[] columns = createGridColumns( grid, 3, SWT.NONE );
 
-    handler.handleSet( new JsonObject().add( "focusCell", 2 ) );
+    handler.handleSet( new JsonObject().add( "focusCell", 3 ) );
 
     assertSame( columns[ 2 ], grid.getFocusColumn() );
   }
 
   @Test
   public void testHandleCallRenderToolTipText() {
+    new GridColumn( grid, SWT.NONE );
     GridItem item = new GridItem( grid, SWT.NONE );
     final ICellToolTipAdapter adapter = CellToolTipUtil.getAdapter( grid );
     adapter.setCellToolTipProvider( new ICellToolTipProvider() {
@@ -189,7 +190,7 @@
       }
     } );
 
-    JsonObject properties = new JsonObject().add( "item", getId( item ) ).add( "column", 0 );
+    JsonObject properties = new JsonObject().add( "item", getId( item ) ).add( "column", 1 );
     handler.handleCall( "renderToolTipText", properties );
 
     assertEquals( getId( item ) + ",0", CellToolTipUtil.getAdapter( grid ).getCellToolTipText() );