/**
   * The constants used in this Content Widget.
   */
  public static interface CwConstants extends Constants {
    String cwCustomDataGridColumnAddress();

    String cwCustomDataGridColumnAge();

    String cwCustomDataGridColumnCategory();

    String cwCustomDataGridColumnFirstName();

    String cwCustomDataGridColumnLastName();

    String cwCustomDataGridDescription();

    String cwCustomDataGridEmpty();

    String cwCustomDataGridName();
  }

  /**
   * The UiBinder interface used by this example.
   */
  interface Binder extends UiBinder<Widget, CwCustomDataGrid> {
  }

  /**
   * The resources used by this example.
   */
  interface Resources extends ClientBundle {

    /**
     * Get the styles used but this example.
     */
    @Source("CwCustomDataGrid.css")
    Styles styles();
  }

  /**
   * The CSS Resources used by this example.
   */
  interface Styles extends CssResource {
    /**
     * Indents cells in child rows.
     */
    String childCell();

    /**
     * Applies to group headers.
     */
    String groupHeaderCell();
  }

  /**
   * Renders custom table headers. The top header row includes the groups "Name"
   * and "Information", each of which spans multiple columns. The second row of
   * the headers includes the contacts' first and last names grouped under the
   * "Name" category. The second row also includes the age, category, and
   * address of the contacts grouped under the "Information" category.
   */
  private class CustomHeaderBuilder extends AbstractHeaderOrFooterBuilder<ContactInfo> {

    private Header<String> firstNameHeader = new TextHeader(constants
        .cwCustomDataGridColumnFirstName());
    private Header<String> lastNameHeader = new TextHeader(constants
        .cwCustomDataGridColumnLastName());
    private Header<String> ageHeader = new TextHeader(constants.cwCustomDataGridColumnAge());
    private Header<String> categoryHeader = new TextHeader(constants
        .cwCustomDataGridColumnCategory());
    private Header<String> addressHeader =
        new TextHeader(constants.cwCustomDataGridColumnAddress());

    public CustomHeaderBuilder() {
      super(dataGrid, false);
      setSortIconStartOfLine(false);
    }

    @Override
    protected boolean buildHeaderOrFooterImpl() {
      Style style = dataGrid.getResources().style();
      String groupHeaderCell = resources.styles().groupHeaderCell();

      // Add a 2x2 header above the checkbox and show friends columns.
      TableRowBuilder tr = startRow();
      tr.startTH().colSpan(2).rowSpan(2)
          .className(style.header() + " " + style.firstColumnHeader());
      tr.endTH();

      /*
       * Name group header. Associated with the last name column, so clicking on
       * the group header sorts by last name.
       */
      TableCellBuilder th = tr.startTH().colSpan(2).className(groupHeaderCell);
      enableColumnHandlers(th, lastNameColumn);
      th.style().trustedProperty("border-right", "10px solid white").cursor(Cursor.POINTER)
          .endStyle();
      th.text("Name").endTH();

      // Information group header.
      th = tr.startTH().colSpan(3).className(groupHeaderCell);
      th.text("Information").endTH();

      // Get information about the sorted column.
      ColumnSortList sortList = dataGrid.getColumnSortList();
      ColumnSortInfo sortedInfo = (sortList.size() == 0) ? null : sortList.get(0);
      Column<?, ?> sortedColumn = (sortedInfo == null) ? null : sortedInfo.getColumn();
      boolean isSortAscending = (sortedInfo == null) ? false : sortedInfo.isAscending();

      // Add column headers.
      tr = startRow();
      buildHeader(tr, firstNameHeader, firstNameColumn, sortedColumn, isSortAscending, false, false);
      buildHeader(tr, lastNameHeader, lastNameColumn, sortedColumn, isSortAscending, false, false);
      buildHeader(tr, ageHeader, ageColumn, sortedColumn, isSortAscending, false, false);
      buildHeader(tr, categoryHeader, categoryColumn, sortedColumn, isSortAscending, false, false);
      buildHeader(tr, addressHeader, addressColumn, sortedColumn, isSortAscending, false, true);
      tr.endTR();

      return true;
    }

    /**
     * Renders the header of one column, with the given options.
     * 
     * @param out the table row to build into
     * @param header the {@link Header} to render
     * @param column the column to associate with the header
     * @param sortedColumn the column that is currently sorted
     * @param isSortAscending true if the sorted column is in ascending order
     * @param isFirst true if this the first column
     * @param isLast true if this the last column
     */
    private void buildHeader(TableRowBuilder out, Header<?> header, Column<ContactInfo, ?> column,
        Column<?, ?> sortedColumn, boolean isSortAscending, boolean isFirst, boolean isLast) {
      // Choose the classes to include with the element.
      Style style = dataGrid.getResources().style();
      boolean isSorted = (sortedColumn == column);
      StringBuilder classesBuilder = new StringBuilder(style.header());
      if (isFirst) {
        classesBuilder.append(" " + style.firstColumnHeader());
      }
      if (isLast) {
        classesBuilder.append(" " + style.lastColumnHeader());
      }
      if (column.isSortable()) {
        classesBuilder.append(" " + style.sortableHeader());
      }
      if (isSorted) {
        classesBuilder.append(" "
            + (isSortAscending ? style.sortedHeaderAscending() : style.sortedHeaderDescending()));
      }

      // Create the table cell.
      TableCellBuilder th = out.startTH().className(classesBuilder.toString());

      // Associate the cell with the column to enable sorting of the column.
      enableColumnHandlers(th, column);

      // Render the header.
      Context context = new Context(0, 2, header.getKey());
      renderSortableHeader(th, context, header, isSorted, isSortAscending);

      // End the table cell.
      th.endTH();
    }
  }

  /**
   * Renders custom table footers that appear beneath the columns in the table.
   * This footer consists of a single cell containing the average age of all
   * contacts on the current page. This is an example of a dynamic footer that
   * changes with the row data in the table.
   */
  private class CustomFooterBuilder extends AbstractHeaderOrFooterBuilder<ContactInfo> {

    public CustomFooterBuilder() {
      super(dataGrid, true);
    }

    @Override
    protected boolean buildHeaderOrFooterImpl() {
      String footerStyle = dataGrid.getResources().style().footer();

      // Calculate the age of all visible contacts.
      String ageStr = "";
      List<ContactInfo> items = dataGrid.getVisibleItems();
      if (items.size() > 0) {
        int totalAge = 0;
        for (ContactInfo item : items) {
          totalAge += item.getAge();
        }
        ageStr = "Avg: " + totalAge / items.size();
      }

      // Cells before age column.
      TableRowBuilder tr = startRow();
      tr.startTH().colSpan(4).className(footerStyle).endTH();

      // Show the average age of all contacts.
      TableCellBuilder th =
          tr.startTH().className(footerStyle).align(
              HasHorizontalAlignment.ALIGN_CENTER.getTextAlignString());
      th.text(ageStr);
      th.endTH();

      // Cells after age column.
      tr.startTH().colSpan(2).className(footerStyle).endTH();
      tr.endTR();

      return true;
    }
  }

  /**
   * Renders the data rows that display each contact in the table.
   */
  private class CustomTableBuilder extends AbstractCellTableBuilder<ContactInfo> {

    private final int todayMonth;

    private final String childCell = " " + resources.styles().childCell();
    private final String rowStyle;
    private final String selectedRowStyle;
    private final String cellStyle;
    private final String selectedCellStyle;

    @SuppressWarnings("deprecation")
    public CustomTableBuilder() {
      super(dataGrid);

      // Cache styles for faster access.
      Style style = dataGrid.getResources().style();
      rowStyle = style.evenRow();
      selectedRowStyle = " " + style.selectedRow();
      cellStyle = style.cell() + " " + style.evenRowCell();
      selectedCellStyle = " " + style.selectedRowCell();

      // Record today's date.
      Date today = new Date();
      todayMonth = today.getMonth();
    }

    @SuppressWarnings("deprecation")
    @Override
    public void buildRowImpl(ContactInfo rowValue, int absRowIndex) {
      buildContactRow(rowValue, absRowIndex, false);

      // Display information about the user in another row that spans the entire
      // table.
      Date dob = rowValue.getBirthday();
      if (dob.getMonth() == todayMonth) {
        TableRowBuilder row = startRow();
        TableCellBuilder td = row.startTD().colSpan(7).className(cellStyle);
        td.style().trustedBackgroundColor("#ccf").endStyle();
        td.text(rowValue.getFirstName() + "'s birthday is this month!").endTD();
        row.endTR();
      }

      // Display list of friends.
      if (showingFriends.contains(rowValue.getId())) {
        Set<ContactInfo> friends = ContactDatabase.get().queryFriends(rowValue);
        for (ContactInfo friend : friends) {
          buildContactRow(friend, absRowIndex, true);
        }
      }
    }

    /**
     * Build a row.
     * 
     * @param rowValue the contact info
     * @param absRowIndex the absolute row index
     * @param isFriend true if this is a subrow, false if a top level row
     */
    @SuppressWarnings("deprecation")
    private void buildContactRow(ContactInfo rowValue, int absRowIndex, boolean isFriend) {
      // Calculate the row styles.
      SelectionModel<? super ContactInfo> selectionModel = dataGrid.getSelectionModel();
      boolean isSelected =
          (selectionModel == null || rowValue == null) ? false : selectionModel
              .isSelected(rowValue);
      boolean isEven = absRowIndex % 2 == 0;
      StringBuilder trClasses = new StringBuilder(rowStyle);
      if (isSelected) {
        trClasses.append(selectedRowStyle);
      }

      // Calculate the cell styles.
      String cellStyles = cellStyle;
      if (isSelected) {
        cellStyles += selectedCellStyle;
      }
      if (isFriend) {
        cellStyles += childCell;
      }

      TableRowBuilder row = startRow();
      row.className(trClasses.toString());

      /*
       * Checkbox column.
       * 
       * This table will uses a checkbox column for selection. Alternatively,
       * you can call dataGrid.setSelectionEnabled(true) to enable mouse
       * selection.
       */
      TableCellBuilder td = row.startTD();
      td.className(cellStyles);
      td.style().outlineStyle(OutlineStyle.NONE).endStyle();
      if (!isFriend) {
        renderCell(td, createContext(0), checkboxColumn, rowValue);
      }
      td.endTD();

      /*
       * View friends column.
       * 
       * Displays a link to "show friends". When clicked, the list of friends is
       * displayed below the contact.
       */
      td = row.startTD();
      td.className(cellStyles);
      if (!isFriend) {
        td.style().outlineStyle(OutlineStyle.NONE).endStyle();
        renderCell(td, createContext(1), viewFriendsColumn, rowValue);
      }
      td.endTD();

      // First name column.
      td = row.startTD();
      td.className(cellStyles);
      td.style().outlineStyle(OutlineStyle.NONE).endStyle();
      if (isFriend) {
        td.text(rowValue.getFirstName());
      } else {
        renderCell(td, createContext(2), firstNameColumn, rowValue);
      }
      td.endTD();

      // Last name column.
      td = row.startTD();
      td.className(cellStyles);
      td.style().outlineStyle(OutlineStyle.NONE).endStyle();
      if (isFriend) {
        td.text(rowValue.getLastName());
      } else {
        renderCell(td, createContext(3), lastNameColumn, rowValue);
      }
      td.endTD();

      // Age column.
      td = row.startTD();
      td.className(cellStyles);
      td.style().outlineStyle(OutlineStyle.NONE).endStyle();
      td.text(NumberFormat.getDecimalFormat().format(rowValue.getAge())).endTD();

      // Category column.
      td = row.startTD();
      td.className(cellStyles);
      td.style().outlineStyle(OutlineStyle.NONE).endStyle();
      if (isFriend) {
        td.text(rowValue.getCategory().getDisplayName());
      } else {
        renderCell(td, createContext(5), categoryColumn, rowValue);
      }
      td.endTD();

      // Address column.
      td = row.startTD();
      td.className(cellStyles);
      DivBuilder div = td.startDiv();
      div.style().outlineStyle(OutlineStyle.NONE).endStyle();
      div.text(rowValue.getAddress()).endDiv();
      td.endTD();

      row.endTR();
    }
  }

  /**
   * The main DataGrid.
   */
  @UiField(provided = true)
  DataGrid<ContactInfo> dataGrid;

  /**
   * The pager used to change the range of data.
   */
  @UiField(provided = true)
  SimplePager pager;

  /**
   * An instance of the constants.
   */
  private final CwConstants constants;

  /**
   * The resources used by this example.
   */
  private Resources resources;

  /**
   * Contains the contact id for each row in the table where the friends list is
   * currently expanded.
   */
  private final Set<Integer> showingFriends = new HashSet<Integer>();

  /**
   * Column to control selection.
   */
  private Column<ContactInfo, Boolean> checkboxColumn;

  /**
   * Column to expand friends list.
   */
  private Column<ContactInfo, String> viewFriendsColumn;

  /**
   * Column displays first name.
   */
  private Column<ContactInfo, String> firstNameColumn;

  /**
   * Column displays last name.
   */
  private Column<ContactInfo, String> lastNameColumn;

  /**
   * Column displays age.
   */
  private Column<ContactInfo, Number> ageColumn;

  /**
   * Column displays category.
   */
  private Column<ContactInfo, String> categoryColumn;

  /**
   * Column displays address.
   */
  private Column<ContactInfo, String> addressColumn;

  /**
   * Initialize this example.
   */
  @Override
  public Widget onInitialize() {
    resources = GWT.create(Resources.class);
    resources.styles().ensureInjected();

    // Create a DataGrid.

    /*
     * Set a key provider that provides a unique key for each contact. If key is
     * used to identify contacts when fields (such as the name and address)
     * change.
     */
    dataGrid = new DataGrid<ContactInfo>(ContactDatabase.ContactInfo.KEY_PROVIDER);
    dataGrid.setWidth("100%");

    /*
     * Do not refresh the headers every time the data is updated. The footer
     * depends on the current data, so we do not disable auto refresh on the
     * footer.
     */
    dataGrid.setAutoHeaderRefreshDisabled(true);

    // Set the message to display when the table is empty.
    dataGrid.setEmptyTableWidget(new Label(constants.cwCustomDataGridEmpty()));

    // Attach a column sort handler to the ListDataProvider to sort the list.
    ListHandler<ContactInfo> sortHandler =
        new ListHandler<ContactInfo>(ContactDatabase.get().getDataProvider().getList());
    dataGrid.addColumnSortHandler(sortHandler);

    // Create a Pager to control the table.
    SimplePager.Resources pagerResources = GWT.create(SimplePager.Resources.class);
    pager = new SimplePager(TextLocation.CENTER, pagerResources, false, 0, true);
    pager.setDisplay(dataGrid);

    // Add a selection model so we can select cells.
    final SelectionModel<ContactInfo> selectionModel =
        new MultiSelectionModel<ContactInfo>(ContactDatabase.ContactInfo.KEY_PROVIDER);
    dataGrid.setSelectionModel(selectionModel, DefaultSelectionEventManager
        .<ContactInfo> createCheckboxManager());

    // Initialize the columns.
    initializeColumns(sortHandler);

    // Specify a custom table.
    dataGrid.setTableBuilder(new CustomTableBuilder());
    dataGrid.setHeaderBuilder(new CustomHeaderBuilder());
    dataGrid.setFooterBuilder(new CustomFooterBuilder());

    // Add the CellList to the adapter in the database.
    ContactDatabase.get().addDataDisplay(dataGrid);

    // Create the UiBinder.
    Binder uiBinder = GWT.create(Binder.class);
    return uiBinder.createAndBindUi(this);
  }

  /**
   * Defines the columns in the custom table. Maps the data in the ContactInfo
   * for each row into the appropriate column in the table, and defines handlers
   * for each column.
   */
  private void initializeColumns(ListHandler<ContactInfo> sortHandler) {
    /*
     * Checkbox column.
     * 
     * This table will uses a checkbox column for selection. Alternatively, you
     * can call dataGrid.setSelectionEnabled(true) to enable mouse selection.
     */
    checkboxColumn = new Column<ContactInfo, Boolean>(new CheckboxCell(true, false)) {
      @Override
      public Boolean getValue(ContactInfo object) {
        // Get the value from the selection model.
        return dataGrid.getSelectionModel().isSelected(object);
      }
    };
    dataGrid.setColumnWidth(0, 40, Unit.PX);

    // View friends.
    SafeHtmlRenderer<String> anchorRenderer = new AbstractSafeHtmlRenderer<String>() {
      @Override
      public SafeHtml render(String object) {
        SafeHtmlBuilder sb = new SafeHtmlBuilder();
        sb.appendHtmlConstant("(<a href=\"javascript:;\">").appendEscaped(object)
            .appendHtmlConstant("</a>)");
        return sb.toSafeHtml();
      }
    };
    viewFriendsColumn = new Column<ContactInfo, String>(new ClickableTextCell(anchorRenderer)) {
      @Override
      public String getValue(ContactInfo object) {
        if (showingFriends.contains(object.getId())) {
          return "hide friends";
        } else {
          return "show friends";
        }
      }
    };
    viewFriendsColumn.setFieldUpdater(new FieldUpdater<ContactInfo, String>() {
      @Override
      public void update(int index, ContactInfo object, String value) {
        if (showingFriends.contains(object.getId())) {
          showingFriends.remove(object.getId());
        } else {
          showingFriends.add(object.getId());
        }

        // Redraw the modified row.
        dataGrid.redrawRow(index);
      }
    });
    dataGrid.setColumnWidth(1, 10, Unit.EM);

    // First name.
    firstNameColumn = new Column<ContactInfo, String>(new EditTextCell()) {
      @Override
      public String getValue(ContactInfo object) {
        return object.getFirstName();
      }
    };
    firstNameColumn.setSortable(true);
    sortHandler.setComparator(firstNameColumn, new Comparator<ContactInfo>() {
      @Override
      public int compare(ContactInfo o1, ContactInfo o2) {
        return o1.getFirstName().compareTo(o2.getFirstName());
      }
    });
    firstNameColumn.setFieldUpdater(new FieldUpdater<ContactInfo, String>() {
      @Override
      public void update(int index, ContactInfo object, String value) {
        // Called when the user changes the value.
        object.setFirstName(value);
        ContactDatabase.get().refreshDisplays();
      }
    });
    dataGrid.setColumnWidth(2, 20, Unit.PCT);

    // Last name.
    lastNameColumn = new Column<ContactInfo, String>(new EditTextCell()) {
      @Override
      public String getValue(ContactInfo object) {
        return object.getLastName();
      }
    };
    lastNameColumn.setSortable(true);
    sortHandler.setComparator(lastNameColumn, new Comparator<ContactInfo>() {
      @Override
      public int compare(ContactInfo o1, ContactInfo o2) {
        return o1.getLastName().compareTo(o2.getLastName());
      }
    });
    lastNameColumn.setFieldUpdater(new FieldUpdater<ContactInfo, String>() {
      @Override
      public void update(int index, ContactInfo object, String value) {
        // Called when the user changes the value.
        object.setLastName(value);
        ContactDatabase.get().refreshDisplays();
      }
    });
    dataGrid.setColumnWidth(3, 20, Unit.PCT);

    // Age.
    ageColumn = new Column<ContactInfo, Number>(new NumberCell()) {
      @Override
      public Number getValue(ContactInfo object) {
        return object.getAge();
      }
    };
    ageColumn.setSortable(true);
    sortHandler.setComparator(ageColumn, new Comparator<ContactInfo>() {
      @Override
      public int compare(ContactInfo o1, ContactInfo o2) {
        return o1.getAge() - o2.getAge();
      }
    });
    dataGrid.setColumnWidth(4, 7, Unit.EM);

    // Category.
    final Category[] categories = ContactDatabase.get().queryCategories();
    List<String> categoryNames = new ArrayList<String>();
    for (Category category : categories) {
      categoryNames.add(category.getDisplayName());
    }
    SelectionCell categoryCell = new SelectionCell(categoryNames);
    categoryColumn = new Column<ContactInfo, String>(categoryCell) {
      @Override
      public String getValue(ContactInfo object) {
        return object.getCategory().getDisplayName();
      }
    };
    categoryColumn.setFieldUpdater(new FieldUpdater<ContactInfo, String>() {
      @Override
      public void update(int index, ContactInfo object, String value) {
        for (Category category : categories) {
          if (category.getDisplayName().equals(value)) {
            object.setCategory(category);
          }
        }
        ContactDatabase.get().refreshDisplays();
      }
    });
    dataGrid.setColumnWidth(5, 130, Unit.PX);

    // Address.
    addressColumn = new Column<ContactInfo, String>(new TextCell()) {
      @Override
      public String getValue(ContactInfo object) {
        return object.getAddress();
      }
    };
    addressColumn.setSortable(true);
    sortHandler.setComparator(addressColumn, new Comparator<ContactInfo>() {
      @Override
      public int compare(ContactInfo o1, ContactInfo o2) {
        return o1.getAddress().compareTo(o2.getAddress());
      }
    });
    dataGrid.setColumnWidth(6, 60, Unit.PCT);
  }