Logo Search packages:      
Sourcecode: chromium-browser version File versions  Download package

table_view.cc

// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "views/controls/table/table_view.h"

#include <commctrl.h>
#include <windowsx.h>

#include <algorithm>

#include "app/l10n_util.h"
#include "app/l10n_util_win.h"
#include "app/resource_bundle.h"
#include "app/table_model.h"
#include "base/i18n/rtl.h"
#include "base/string_util.h"
#include "base/win_util.h"
#include "gfx/canvas.h"
#include "gfx/favicon_size.h"
#include "gfx/font.h"
#include "gfx/icon_util.h"
#include "skia/ext/skia_utils_win.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColorFilter.h"
#include "views/controls/native/native_view_host.h"
#include "views/controls/table/table_view_observer.h"

namespace views {

// Added to column width to prevent truncation.
const int kListViewTextPadding = 15;
// Additional column width necessary if column has icons.
const int kListViewIconWidthAndPadding = 18;

// TableView ------------------------------------------------------------------

// static
const int TableView::kImageSize = 18;

TableView::TableView(TableModel* model,
                     const std::vector<TableColumn>& columns,
                     TableTypes table_type,
                     bool single_selection,
                     bool resizable_columns,
                     bool autosize_columns)
    : model_(model),
      table_view_observer_(NULL),
      visible_columns_(),
      all_columns_(),
      column_count_(static_cast<int>(columns.size())),
      table_type_(table_type),
      single_selection_(single_selection),
      ignore_listview_change_(false),
      custom_colors_enabled_(false),
      sized_columns_(false),
      autosize_columns_(autosize_columns),
      resizable_columns_(resizable_columns),
      list_view_(NULL),
      header_original_handler_(NULL),
      original_handler_(NULL),
      ALLOW_THIS_IN_INITIALIZER_LIST(table_view_wrapper_(this)),
      custom_cell_font_(NULL),
      content_offset_(0) {
  for (std::vector<TableColumn>::const_iterator i = columns.begin();
      i != columns.end(); ++i) {
    AddColumn(*i);
    visible_columns_.push_back(i->id);
  }
}

TableView::~TableView() {
  if (list_view_) {
    if (model_)
      model_->SetObserver(NULL);
  }
  if (custom_cell_font_)
    DeleteObject(custom_cell_font_);
}

void TableView::SetModel(TableModel* model) {
  if (model == model_)
    return;

  if (list_view_ && model_)
    model_->SetObserver(NULL);
  model_ = model;
  if (list_view_ && model_)
    model_->SetObserver(this);
  if (list_view_)
    OnModelChanged();
}

void TableView::SetSortDescriptors(const SortDescriptors& sort_descriptors) {
  if (!sort_descriptors_.empty()) {
    ResetColumnSortImage(sort_descriptors_[0].column_id,
                         NO_SORT);
  }
  sort_descriptors_ = sort_descriptors;
  if (!sort_descriptors_.empty()) {
    ResetColumnSortImage(
        sort_descriptors_[0].column_id,
        sort_descriptors_[0].ascending ? ASCENDING_SORT : DESCENDING_SORT);
  }
  if (!list_view_)
    return;

  // For some reason we have to turn off/on redraw, otherwise the display
  // isn't updated when done.
  SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(FALSE), 0);

  UpdateItemsLParams(0, 0);

  SortItemsAndUpdateMapping();

  SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(TRUE), 0);
}

void TableView::DidChangeBounds(const gfx::Rect& previous,
                                const gfx::Rect& current) {
  if (!list_view_)
    return;
  SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(FALSE), 0);
  Layout();
  if ((!sized_columns_ || autosize_columns_) && width() > 0) {
    sized_columns_ = true;
    ResetColumnSizes();
  }
  UpdateContentOffset();
  SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(TRUE), 0);
}

int TableView::RowCount() {
  if (!list_view_)
    return 0;
  return ListView_GetItemCount(list_view_);
}

int TableView::SelectedRowCount() {
  if (!list_view_)
    return 0;
  return ListView_GetSelectedCount(list_view_);
}

void TableView::Select(int model_row) {
  if (!list_view_)
    return;

  DCHECK(model_row >= 0 && model_row < RowCount());
  SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(FALSE), 0);
  ignore_listview_change_ = true;

  // Unselect everything.
  ListView_SetItemState(list_view_, -1, 0, LVIS_SELECTED);

  // Select the specified item.
  int view_row = model_to_view(model_row);
  ListView_SetItemState(list_view_, view_row, LVIS_SELECTED | LVIS_FOCUSED,
                        LVIS_SELECTED | LVIS_FOCUSED);

  // Make it visible.
  ListView_EnsureVisible(list_view_, view_row, FALSE);
  ignore_listview_change_ = false;
  SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(TRUE), 0);
  if (table_view_observer_)
    table_view_observer_->OnSelectionChanged();
}

void TableView::SetSelectedState(int model_row, bool state) {
  if (!list_view_)
    return;

  DCHECK(model_row >= 0 && model_row < RowCount());

  ignore_listview_change_ = true;

  // Select the specified item.
  ListView_SetItemState(list_view_, model_to_view(model_row),
                        state ? LVIS_SELECTED : 0,  LVIS_SELECTED);

  ignore_listview_change_ = false;
}

void TableView::SetFocusOnItem(int model_row) {
  if (!list_view_)
    return;

  DCHECK(model_row >= 0 && model_row < RowCount());

  ignore_listview_change_ = true;

  // Set the focus to the given item.
  ListView_SetItemState(list_view_, model_to_view(model_row), LVIS_FOCUSED,
                        LVIS_FOCUSED);

  ignore_listview_change_ = false;
}

int TableView::FirstSelectedRow() {
  if (!list_view_)
    return -1;

  int view_row = ListView_GetNextItem(list_view_, -1, LVNI_ALL | LVIS_SELECTED);
  return view_row == -1 ? -1 : view_to_model(view_row);
}

bool TableView::IsItemSelected(int model_row) {
  if (!list_view_)
    return false;

  DCHECK(model_row >= 0 && model_row < RowCount());
  return (ListView_GetItemState(list_view_, model_to_view(model_row),
                                LVIS_SELECTED) == LVIS_SELECTED);
}

bool TableView::ItemHasTheFocus(int model_row) {
  if (!list_view_)
    return false;

  DCHECK(model_row >= 0 && model_row < RowCount());
  return (ListView_GetItemState(list_view_, model_to_view(model_row),
                                LVIS_FOCUSED) == LVIS_FOCUSED);
}

TableView::iterator TableView::SelectionBegin() {
  return TableView::iterator(this, LastSelectedViewIndex());
}

TableView::iterator TableView::SelectionEnd() {
  return TableView::iterator(this, -1);
}

void TableView::OnItemsChanged(int start, int length) {
  if (!list_view_)
    return;

  if (length == -1) {
    DCHECK_GE(start, 0);
    length = model_->RowCount() - start;
  }
  int row_count = RowCount();
  DCHECK(start >= 0 && length > 0 && start + length <= row_count);
  SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(FALSE), 0);
  if (table_type_ == ICON_AND_TEXT) {
    // The redraw event does not include the icon in the clip rect, preventing
    // our icon from being repainted. So far the only way I could find around
    // this is to change the image for the item. Even if the image does not
    // exist, it causes the clip rect to include the icon's bounds so we can
    // paint it in the post paint event.
    LVITEM lv_item;
    memset(&lv_item, 0, sizeof(LVITEM));
    lv_item.mask = LVIF_IMAGE;
    for (int i = start; i < start + length; ++i) {
      // Retrieve the current icon index.
      lv_item.iItem = model_to_view(i);
      BOOL r = ListView_GetItem(list_view_, &lv_item);
      DCHECK(r);
      // Set the current icon index to the other image.
      lv_item.iImage = (lv_item.iImage + 1) % 2;
      DCHECK((lv_item.iImage == 0) || (lv_item.iImage == 1));
      r = ListView_SetItem(list_view_, &lv_item);
      DCHECK(r);
    }
  }
  UpdateListViewCache(start, length, false);
  SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(TRUE), 0);
}

void TableView::OnModelChanged() {
  if (!list_view_)
    return;

  int current_row_count = ListView_GetItemCount(list_view_);
  if (current_row_count > 0)
    OnItemsRemoved(0, current_row_count);
  if (model_ && model_->RowCount())
    OnItemsAdded(0, model_->RowCount());
}

void TableView::OnItemsAdded(int start, int length) {
  if (!list_view_)
    return;

  DCHECK(start >= 0 && length > 0 && start <= RowCount());
  SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(FALSE), 0);
  UpdateListViewCache(start, length, true);
  SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(TRUE), 0);
}

void TableView::OnItemsRemoved(int start, int length) {
  if (!list_view_)
    return;

  if (start < 0 || length < 0 || start + length > RowCount()) {
    NOTREACHED();
    return;
  }

  SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(FALSE), 0);

  bool had_selection = (SelectedRowCount() > 0);
  int old_row_count = RowCount();
  if (start == 0 && length == RowCount()) {
    // Everything was removed.
    ListView_DeleteAllItems(list_view_);
    view_to_model_.reset(NULL);
    model_to_view_.reset(NULL);
  } else {
    // Only a portion of the data was removed.
    if (is_sorted()) {
      int new_row_count = model_->RowCount();
      std::vector<int> view_items_to_remove;
      view_items_to_remove.reserve(length);
      // Iterate through the elements, updating the view_to_model_ mapping
      // as well as collecting the rows that need to be deleted.
      for (int i = 0, removed_count = 0; i < old_row_count; ++i) {
        int model_index = view_to_model(i);
        if (model_index >= start) {
          if (model_index < start + length) {
            // This item was removed.
            view_items_to_remove.push_back(i);
            model_index = -1;
          } else {
            model_index -= length;
          }
        }
        if (model_index >= 0) {
          view_to_model_[i - static_cast<int>(view_items_to_remove.size())] =
              model_index;
        }
      }

      // Update the model_to_view mapping from the updated view_to_model
      // mapping.
      for (int i = 0; i < new_row_count; ++i)
        model_to_view_[view_to_model_[i]] = i;

      // And finally delete the items. We do this backwards as the items were
      // added ordered smallest to largest.
      for (int i = length - 1; i >= 0; --i)
        ListView_DeleteItem(list_view_, view_items_to_remove[i]);
    } else {
      for (int i = 0; i < length; ++i)
        ListView_DeleteItem(list_view_, start);
    }
  }

  SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(TRUE), 0);

  // If the row count goes to zero and we had a selection LVN_ITEMCHANGED isn't
  // invoked, so we handle it here.
  //
  // When the model is set to NULL all the rows are removed. We don't notify
  // the delegate in this case as setting the model to NULL is usually done as
  // the last step before being deleted and callers shouldn't have to deal with
  // getting a selection change when the model is being reset.
  if (model_ && table_view_observer_ && had_selection && RowCount() == 0)
    table_view_observer_->OnSelectionChanged();
}

void TableView::AddColumn(const TableColumn& col) {
  DCHECK_EQ(0, all_columns_.count(col.id));
  all_columns_[col.id] = col;
}

void TableView::SetColumns(const std::vector<TableColumn>& columns) {
  // Remove the currently visible columns.
  while (!visible_columns_.empty())
    SetColumnVisibility(visible_columns_.front(), false);

  all_columns_.clear();
  for (std::vector<TableColumn>::const_iterator i = columns.begin();
       i != columns.end(); ++i) {
    AddColumn(*i);
  }

  // Remove any sort descriptors that are no longer valid.
  SortDescriptors sort = sort_descriptors();
  for (SortDescriptors::iterator i = sort.begin(); i != sort.end();) {
    if (all_columns_.count(i->column_id) == 0)
      i = sort.erase(i);
    else
      ++i;
  }
  sort_descriptors_ = sort;
}

void TableView::OnColumnsChanged() {
  column_count_ = static_cast<int>(visible_columns_.size());
  ResetColumnSizes();
}

void TableView::SetColumnVisibility(int id, bool is_visible) {
  bool changed = false;
  for (std::vector<int>::iterator i = visible_columns_.begin();
       i != visible_columns_.end(); ++i) {
    if (*i == id) {
      if (is_visible) {
        // It's already visible, bail out early.
        return;
      } else {
        int index = static_cast<int>(i - visible_columns_.begin());
        // This could be called before the native list view has been created
        // (in CreateNativeControl, called when the view is added to a
        // Widget). In that case since the column is not in
        // visible_columns_ it will not be added later on when it is created.
        if (list_view_)
          SendMessage(list_view_, LVM_DELETECOLUMN, index, 0);
        visible_columns_.erase(i);
        changed = true;
        break;
      }
    }
  }
  if (is_visible) {
    visible_columns_.push_back(id);
    TableColumn& column = all_columns_[id];
    InsertColumn(column, column_count_);
    if (column.min_visible_width == 0) {
      // ListView_GetStringWidth must be padded or else truncation will occur.
      column.min_visible_width = ListView_GetStringWidth(list_view_,
                                                         column.title.c_str()) +
                                 kListViewTextPadding;
    }
    changed = true;
  }
  if (changed)
    OnColumnsChanged();
}

void TableView::SetVisibleColumns(const std::vector<int>& columns) {
  size_t old_count = visible_columns_.size();
  size_t new_count = columns.size();
  // remove the old columns
  if (list_view_) {
    for (std::vector<int>::reverse_iterator i = visible_columns_.rbegin();
         i != visible_columns_.rend(); ++i) {
      int index = static_cast<int>(i - visible_columns_.rend());
      SendMessage(list_view_, LVM_DELETECOLUMN, index, 0);
    }
  }
  visible_columns_ = columns;
  // Insert the new columns.
  if (list_view_) {
    for (std::vector<int>::iterator i = visible_columns_.begin();
         i != visible_columns_.end(); ++i) {
      int index = static_cast<int>(i - visible_columns_.end());
      InsertColumn(all_columns_[*i], index);
    }
  }
  OnColumnsChanged();
}

bool TableView::IsColumnVisible(int id) const {
  for (std::vector<int>::const_iterator i = visible_columns_.begin();
       i != visible_columns_.end(); ++i)
    if (*i == id) {
      return true;
    }
  return false;
}

const TableColumn& TableView::GetColumnAtPosition(int pos) {
  return all_columns_[visible_columns_[pos]];
}

bool TableView::HasColumn(int id) {
  return all_columns_.count(id) > 0;
}

gfx::Point TableView::GetKeyboardContextMenuLocation() {
  int first_selected = FirstSelectedRow();
  int y = height() / 2;
  if (first_selected != -1) {
    RECT cell_bounds;
    RECT client_rect;
    if (ListView_GetItemRect(GetNativeControlHWND(), first_selected,
                             &cell_bounds, LVIR_BOUNDS) &&
        GetClientRect(GetNativeControlHWND(), &client_rect) &&
        cell_bounds.bottom >= 0 && cell_bounds.bottom < client_rect.bottom) {
      y = cell_bounds.bottom;
    }
  }
  gfx::Point screen_loc(0, y);
  if (UILayoutIsRightToLeft())
    screen_loc.set_x(width());
  ConvertPointToScreen(this, &screen_loc);
  return screen_loc;
}

void TableView::SetCustomColorsEnabled(bool custom_colors_enabled) {
  custom_colors_enabled_ = custom_colors_enabled;
}

bool TableView::GetCellColors(int model_row,
                              int column,
                              ItemColor* foreground,
                              ItemColor* background,
                              LOGFONT* logfont) {
  return false;
}

static int GetViewIndexFromMouseEvent(HWND window, LPARAM l_param) {
  int x = GET_X_LPARAM(l_param);
  int y = GET_Y_LPARAM(l_param);
  LVHITTESTINFO hit_info = {0};
  hit_info.pt.x = x;
  hit_info.pt.y = y;
  return ListView_HitTest(window, &hit_info);
}

// static
LRESULT CALLBACK TableView::TableWndProc(HWND window,
                                         UINT message,
                                         WPARAM w_param,
                                         LPARAM l_param) {
  TableView* table_view = reinterpret_cast<TableViewWrapper*>(
      GetWindowLongPtr(window, GWLP_USERDATA))->table_view;

  // Is the mouse down on the table?
  static bool in_mouse_down = false;
  // Should we select on mouse up?
  static bool select_on_mouse_up = false;

  // If the mouse is down, this is the location of the mouse down message.
  static int mouse_down_x, mouse_down_y;

  switch (message) {
    case WM_CONTEXTMENU: {
      // This addresses two problems seen with context menus in right to left
      // locales:
      // 1. The mouse coordinates in the l_param were occasionally wrong in
      //    weird ways. This is most often seen when right clicking on the
      //    list-view twice in a row.
      // 2. Right clicking on the icon would show the scrollbar menu.
      //
      // As a work around this uses the position of the cursor and ignores
      // the position supplied in the l_param.
      if (table_view->UILayoutIsRightToLeft() &&
          (GET_X_LPARAM(l_param) != -1 || GET_Y_LPARAM(l_param) != -1)) {
        POINT screen_point;
        GetCursorPos(&screen_point);
        POINT table_point = screen_point;
        RECT client_rect;
        if (ScreenToClient(window, &table_point) &&
            GetClientRect(window, &client_rect) &&
            PtInRect(&client_rect, table_point)) {
          // The point is over the client area of the table, handle it ourself.
          // But first select the row if it isn't already selected.
          LVHITTESTINFO hit_info = {0};
          hit_info.pt.x = table_point.x;
          hit_info.pt.y = table_point.y;
          int view_index = ListView_HitTest(window, &hit_info);
          if (view_index != -1) {
            int model_index = table_view->view_to_model(view_index);
            if (!table_view->IsItemSelected(model_index))
              table_view->Select(model_index);
          }
          table_view->OnContextMenu(screen_point);
          return 0;  // So that default processing doesn't occur.
        }
      }
      // else case: default handling is fine, so break and let the default
      // handler service the request (which will likely calls us back with
      // OnContextMenu).
      break;
    }

    case WM_CANCELMODE: {
      if (in_mouse_down) {
        in_mouse_down = false;
        return 0;
      }
      break;
    }

    case WM_ERASEBKGND:
      // We make WM_ERASEBKGND do nothing (returning 1 indicates we handled
      // the request). We do this so that the table view doesn't flicker during
      // resizing.
      return 1;

    case WM_PAINT: {
      LRESULT result = CallWindowProc(table_view->original_handler_, window,
                                      message, w_param, l_param);
      table_view->PostPaint();
      return result;
    }

    case WM_KEYDOWN: {
      if (!table_view->single_selection_ && w_param == 'A' &&
          GetKeyState(VK_CONTROL) < 0 && table_view->RowCount() > 0) {
        // Select everything.
        ListView_SetItemState(window, -1, LVIS_SELECTED, LVIS_SELECTED);
        // And make the first row focused.
        ListView_SetItemState(window, 0, LVIS_FOCUSED, LVIS_FOCUSED);
        return 0;
      } else if (w_param == VK_DELETE && table_view->table_view_observer_) {
        table_view->table_view_observer_->OnTableViewDelete(table_view);
        return 0;
      }
      // else case: fall through to default processing.
      break;
    }

    case WM_LBUTTONDBLCLK: {
      if (w_param == MK_LBUTTON)
        table_view->OnDoubleClick();
      return 0;
    }

    case WM_MBUTTONDOWN: {
      if (w_param == MK_MBUTTON) {
        int view_index = GetViewIndexFromMouseEvent(window, l_param);
        if (view_index != -1) {
          int model_index = table_view->view_to_model(view_index);
          // Clear all and select the row that was middle clicked.
          table_view->Select(model_index);
          table_view->OnMiddleClick();
        }
      }
      return 0;
    }

    case WM_LBUTTONUP: {
      if (in_mouse_down) {
        in_mouse_down = false;
        ReleaseCapture();
        SetFocus(window);
        if (select_on_mouse_up) {
          int view_index = GetViewIndexFromMouseEvent(window, l_param);
          if (view_index != -1)
            table_view->Select(table_view->view_to_model(view_index));
        }
        return 0;
      }
      break;
    }

    case WM_LBUTTONDOWN: {
      // ListView treats clicking on an area outside the text of a column as
      // drag to select. This is confusing when the selection is shown across
      // the whole row. For this reason we override the default handling for
      // mouse down/move/up and treat the whole row as draggable. That is, no
      // matter where you click in the row we'll attempt to start dragging.
      //
      // Only do custom mouse handling if no other mouse buttons are down.
      if ((w_param | (MK_LBUTTON | MK_CONTROL | MK_SHIFT)) ==
          (MK_LBUTTON | MK_CONTROL | MK_SHIFT)) {
        if (in_mouse_down)
          return 0;

        int view_index = GetViewIndexFromMouseEvent(window, l_param);
        if (view_index != -1) {
          table_view->ignore_listview_change_ = true;
          in_mouse_down = true;
          select_on_mouse_up = false;
          mouse_down_x = GET_X_LPARAM(l_param);
          mouse_down_y = GET_Y_LPARAM(l_param);
          int model_index = table_view->view_to_model(view_index);
          bool select = true;
          if (w_param & MK_CONTROL) {
            select = false;
            if (!table_view->IsItemSelected(model_index)) {
              if (table_view->single_selection_) {
                // Single selection mode and the row isn't selected, select
                // only it.
                table_view->Select(model_index);
              } else {
                // Not single selection, add this row to the selection.
                table_view->SetSelectedState(model_index, true);
              }
            } else {
              // Remove this row from the selection.
              table_view->SetSelectedState(model_index, false);
            }
            ListView_SetSelectionMark(window, view_index);
          } else if (!table_view->single_selection_ && w_param & MK_SHIFT) {
            int mark_view_index = ListView_GetSelectionMark(window);
            if (mark_view_index != -1) {
              // Unselect everything.
              ListView_SetItemState(window, -1, 0, LVIS_SELECTED);
              select = false;

              // Select from mark to mouse down location.
              for (int i = std::min(view_index, mark_view_index),
                   max_i = std::max(view_index, mark_view_index); i <= max_i;
                   ++i) {
                table_view->SetSelectedState(table_view->view_to_model(i),
                                             true);
              }
            }
          }
          // Make the row the user clicked on the focused row.
          ListView_SetItemState(window, view_index, LVIS_FOCUSED,
                                LVIS_FOCUSED);
          if (select) {
            if (!table_view->IsItemSelected(model_index)) {
              // Clear all.
              ListView_SetItemState(window, -1, 0, LVIS_SELECTED);
              // And select the row the user clicked on.
              table_view->SetSelectedState(model_index, true);
            } else {
              // The item is already selected, don't clear the state right away
              // in case the user drags. Instead wait for mouse up, then only
              // select the row the user clicked on.
              select_on_mouse_up = true;
            }
            ListView_SetSelectionMark(window, view_index);
          }
          table_view->ignore_listview_change_ = false;
          table_view->OnSelectedStateChanged();
          SetCapture(window);
          return 0;
        }
        // else case, continue on to default handler
      }
      break;
    }

    case WM_MOUSEMOVE: {
      if (in_mouse_down) {
        int x = GET_X_LPARAM(l_param);
        int y = GET_Y_LPARAM(l_param);
        if (View::ExceededDragThreshold(x - mouse_down_x, y - mouse_down_y)) {
          // We're about to start drag and drop, which results in no mouse up.
          // Release capture and reset state.
          ReleaseCapture();
          in_mouse_down = false;

          NMLISTVIEW details;
          memset(&details, 0, sizeof(details));
          details.hdr.code = LVN_BEGINDRAG;
          SendMessage(::GetParent(window), WM_NOTIFY, 0,
                      reinterpret_cast<LPARAM>(&details));
        }
        return 0;
      }
      break;
    }

    default:
      break;
  }
  DCHECK(table_view->original_handler_);
  return CallWindowProc(table_view->original_handler_, window, message, w_param,
                        l_param);
}

LRESULT CALLBACK TableView::TableHeaderWndProc(HWND window, UINT message,
                                               WPARAM w_param, LPARAM l_param) {
  TableView* table_view = reinterpret_cast<TableViewWrapper*>(
      GetWindowLongPtr(window, GWLP_USERDATA))->table_view;

  switch (message) {
    case WM_SETCURSOR:
      if (!table_view->resizable_columns_)
        // Prevents the cursor from changing to the resize cursor.
        return TRUE;
      break;
    case WM_LBUTTONDBLCLK:
      if (!table_view->resizable_columns_)
        // Prevents the double-click on the column separator from auto-resizing
        // the column.
        return TRUE;
      break;
    default:
      break;
  }
  DCHECK(table_view->header_original_handler_);
  return CallWindowProc(table_view->header_original_handler_,
                        window, message, w_param, l_param);
}

HWND TableView::CreateNativeControl(HWND parent_container) {
  int style = WS_CHILD | LVS_REPORT | LVS_SHOWSELALWAYS;
  if (single_selection_)
    style |= LVS_SINGLESEL;
  // If there's only one column and the title string is empty, don't show a
  // header.
  if (all_columns_.size() == 1) {
    std::map<int, TableColumn>::const_iterator first =
        all_columns_.begin();
    if (first->second.title.empty())
      style |= LVS_NOCOLUMNHEADER;
  }
  list_view_ = ::CreateWindowEx(WS_EX_CLIENTEDGE | GetAdditionalRTLStyle(),
                                WC_LISTVIEW,
                                L"",
                                style,
                                0, 0, width(), height(),
                                parent_container, NULL, NULL, NULL);

  // Make the selection extend across the row.
  // Reduce overdraw/flicker artifacts by double buffering.
  DWORD list_view_style = LVS_EX_FULLROWSELECT;
  if (win_util::GetWinVersion() > win_util::WINVERSION_2000) {
    list_view_style |= LVS_EX_DOUBLEBUFFER;
  }
  if (table_type_ == CHECK_BOX_AND_TEXT)
    list_view_style |= LVS_EX_CHECKBOXES;
  ListView_SetExtendedListViewStyleEx(list_view_, 0, list_view_style);
  l10n_util::AdjustUIFontForWindow(list_view_);

  // Add the columns.
  for (std::vector<int>::iterator i = visible_columns_.begin();
       i != visible_columns_.end(); ++i) {
    InsertColumn(all_columns_[*i],
                 static_cast<int>(i - visible_columns_.begin()));
  }

  if (model_)
    model_->SetObserver(this);

  // Add the groups.
  if (model_ && model_->HasGroups() &&
      win_util::GetWinVersion() > win_util::WINVERSION_2000) {
    ListView_EnableGroupView(list_view_, true);

    TableModel::Groups groups = model_->GetGroups();
    LVGROUP group = { 0 };
    group.cbSize = sizeof(LVGROUP);
    group.mask = LVGF_HEADER | LVGF_ALIGN | LVGF_GROUPID;
    group.uAlign = LVGA_HEADER_LEFT;
    for (size_t i = 0; i < groups.size(); ++i) {
      group.pszHeader = const_cast<wchar_t*>(groups[i].title.c_str());
      group.iGroupId = groups[i].id;
      ListView_InsertGroup(list_view_, static_cast<int>(i), &group);
    }
  }

  // Set the # of rows.
  if (model_)
    UpdateListViewCache(0, model_->RowCount(), true);

  if (table_type_ == ICON_AND_TEXT) {
    HIMAGELIST image_list =
        ImageList_Create(kImageSize, kImageSize, ILC_COLOR32, 2, 2);
    // We create 2 phony images because we are going to switch images at every
    // refresh in order to force a refresh of the icon area (somehow the clip
    // rect does not include the icon).
    gfx::Canvas canvas(kImageSize, kImageSize, false);
    // Make the background completely transparent.
    canvas.drawColor(SK_ColorBLACK, SkXfermode::kClear_Mode);
    HICON empty_icon =
        IconUtil::CreateHICONFromSkBitmap(canvas.ExtractBitmap());
    ImageList_AddIcon(image_list, empty_icon);
    ImageList_AddIcon(image_list, empty_icon);
    DeleteObject(empty_icon);
    ListView_SetImageList(list_view_, image_list, LVSIL_SMALL);
  }

  if (!resizable_columns_) {
    // To disable the resizing of columns we'll filter the events happening on
    // the header. We also need to intercept the HDM_LAYOUT to size the header
    // for the Chrome headers.
    HWND header = ListView_GetHeader(list_view_);
    DCHECK(header);
    SetWindowLongPtr(header, GWLP_USERDATA,
        reinterpret_cast<LONG_PTR>(&table_view_wrapper_));
    header_original_handler_ = win_util::SetWindowProc(header,
        &TableView::TableHeaderWndProc);
  }

  SetWindowLongPtr(list_view_, GWLP_USERDATA,
      reinterpret_cast<LONG_PTR>(&table_view_wrapper_));
  original_handler_ =
      win_util::SetWindowProc(list_view_, &TableView::TableWndProc);

  // Bug 964884: detach the IME attached to this window.
  // We should attach IMEs only when we need to input CJK strings.
  ::ImmAssociateContextEx(list_view_, NULL, 0);

  UpdateContentOffset();

  return list_view_;
}

void TableView::ToggleSortOrder(int column_id) {
  SortDescriptors sort = sort_descriptors();
  if (!sort.empty() && sort[0].column_id == column_id) {
    sort[0].ascending = !sort[0].ascending;
  } else {
    SortDescriptor descriptor(column_id, true);
    sort.insert(sort.begin(), descriptor);
    if (sort.size() > 2) {
      // Only persist two sort descriptors.
      sort.resize(2);
    }
  }
  SetSortDescriptors(sort);
}

void TableView::UpdateItemsLParams(int start, int length) {
  LVITEM item;
  memset(&item, 0, sizeof(LVITEM));
  item.mask = LVIF_PARAM;
  int row_count = RowCount();
  for (int i = 0; i < row_count; ++i) {
    item.iItem = i;
    int model_index = view_to_model(i);
    if (length > 0 && model_index >= start)
      model_index += length;
    item.lParam = static_cast<LPARAM>(model_index);
    ListView_SetItem(list_view_, &item);
  }
}

void TableView::SortItemsAndUpdateMapping() {
  if (!is_sorted()) {
    ListView_SortItems(list_view_, &TableView::NaturalSortFunc, this);
    view_to_model_.reset(NULL);
    model_to_view_.reset(NULL);
    return;
  }

  PrepareForSort();

  // Sort the items.
  ListView_SortItems(list_view_, &TableView::SortFunc, this);

  model_->ClearCollator();

  // Update internal mapping to match how items were actually sorted.
  int row_count = RowCount();
  model_to_view_.reset(new int[row_count]);
  view_to_model_.reset(new int[row_count]);
  LVITEM item;
  memset(&item, 0, sizeof(LVITEM));
  item.mask = LVIF_PARAM;
  for (int i = 0; i < row_count; ++i) {
    item.iItem = i;
    ListView_GetItem(list_view_, &item);
    int model_index = static_cast<int>(item.lParam);
    view_to_model_[i] = model_index;
    model_to_view_[model_index] = i;
  }
}

// static
int CALLBACK TableView::SortFunc(LPARAM model_index_1_p,
                                 LPARAM model_index_2_p,
                                 LPARAM table_view_param) {
  int model_index_1 = static_cast<int>(model_index_1_p);
  int model_index_2 = static_cast<int>(model_index_2_p);
  TableView* table_view = reinterpret_cast<TableView*>(table_view_param);
  return table_view->CompareRows(model_index_1, model_index_2);
}

// static
int CALLBACK TableView::NaturalSortFunc(LPARAM model_index_1_p,
                                        LPARAM model_index_2_p,
                                        LPARAM table_view_param) {
  return model_index_1_p - model_index_2_p;
}

void TableView::ResetColumnSortImage(int column_id, SortDirection direction) {
  if (!list_view_ || column_id == -1)
    return;

  std::vector<int>::const_iterator i =
      std::find(visible_columns_.begin(), visible_columns_.end(), column_id);
  if (i == visible_columns_.end())
    return;

  HWND header = ListView_GetHeader(list_view_);
  if (!header)
    return;

  int column_index = static_cast<int>(i - visible_columns_.begin());
  HDITEM header_item;
  memset(&header_item, 0, sizeof(header_item));
  header_item.mask = HDI_FORMAT;
  Header_GetItem(header, column_index, &header_item);
  header_item.fmt &= ~(HDF_SORTUP | HDF_SORTDOWN);
  if (direction == ASCENDING_SORT)
    header_item.fmt |= HDF_SORTUP;
  else if (direction == DESCENDING_SORT)
    header_item.fmt |= HDF_SORTDOWN;
  Header_SetItem(header, column_index, &header_item);
}

void TableView::InsertColumn(const TableColumn& tc, int index) {
  if (!list_view_)
    return;

  LVCOLUMN column = { 0 };
  column.mask = LVCF_TEXT|LVCF_FMT;
  column.pszText = const_cast<LPWSTR>(tc.title.c_str());
  switch (tc.alignment) {
      case TableColumn::LEFT:
        column.fmt = LVCFMT_LEFT;
        break;
      case TableColumn::RIGHT:
        column.fmt = LVCFMT_RIGHT;
        break;
      case TableColumn::CENTER:
        column.fmt = LVCFMT_CENTER;
        break;
      default:
        NOTREACHED();
  }
  if (tc.width != -1) {
    column.mask |= LVCF_WIDTH;
    column.cx = tc.width;
  }
  column.mask |= LVCF_SUBITEM;
  // Sub-items are 1s indexed.
  column.iSubItem = index + 1;
  SendMessage(list_view_, LVM_INSERTCOLUMN, index,
              reinterpret_cast<LPARAM>(&column));
  if (is_sorted() && sort_descriptors_[0].column_id == tc.id) {
    ResetColumnSortImage(
        tc.id,
        sort_descriptors_[0].ascending ? ASCENDING_SORT : DESCENDING_SORT);
  }
}

LRESULT TableView::OnNotify(int w_param, LPNMHDR hdr) {
  if (!model_)
    return 0;

  switch (hdr->code) {
    case NM_CUSTOMDRAW: {
      // Draw notification. dwDragState indicates the current stage of drawing.
      return OnCustomDraw(reinterpret_cast<NMLVCUSTOMDRAW*>(hdr));
    }

    case LVN_ITEMCHANGED: {
      // Notification that the state of an item has changed. The state
      // includes such things as whether the item is selected or checked.
      NMLISTVIEW* state_change = reinterpret_cast<NMLISTVIEW*>(hdr);
      if ((state_change->uChanged & LVIF_STATE) != 0) {
        if ((state_change->uOldState & LVIS_SELECTED) !=
            (state_change->uNewState & LVIS_SELECTED)) {
          // Selected state of the item changed.
          OnSelectedStateChanged();
        }
        if ((state_change->uOldState & LVIS_STATEIMAGEMASK) !=
            (state_change->uNewState & LVIS_STATEIMAGEMASK)) {
          // Checked state of the item changed.
          bool is_checked =
            ((state_change->uNewState & LVIS_STATEIMAGEMASK) ==
             INDEXTOSTATEIMAGEMASK(2));
          OnCheckedStateChanged(view_to_model(state_change->iItem),
                                is_checked);
        }
      }
      break;
    }

    case HDN_BEGINTRACKW:
    case HDN_BEGINTRACKA:
      // Prevent clicks so columns cannot be resized.
      if (!resizable_columns_)
        return TRUE;
      break;

    case NM_DBLCLK:
      OnDoubleClick();
      break;

    case LVN_COLUMNCLICK: {
      const TableColumn& column = GetColumnAtPosition(
          reinterpret_cast<NMLISTVIEW*>(hdr)->iSubItem);
      if (column.sortable)
        ToggleSortOrder(column.id);
      break;
    }

    case LVN_MARQUEEBEGIN:  // We don't want the marque selection.
      return 1;

    default:
      break;
  }
  return 0;
}

// Returns result, unless ascending is false in which case -result is returned.
static int SwapCompareResult(int result, bool ascending) {
  return ascending ? result : -result;
}

int TableView::CompareRows(int model_row1, int model_row2) {
  if (model_->HasGroups()) {
    // By default ListView sorts the elements regardless of groups. In such
    // a situation the groups display only the items they contain. This results
    // in the visual order differing from the item indices. I could not find
    // a way to iterate over the visual order in this situation. As a workaround
    // this forces the items to be sorted by groups as well, which means the
    // visual order matches the item indices.
    int g1 = model_->GetGroupID(model_row1);
    int g2 = model_->GetGroupID(model_row2);
    if (g1 != g2)
      return g1 - g2;
  }
  int sort_result = model_->CompareValues(
      model_row1, model_row2, sort_descriptors_[0].column_id);
  if (sort_result == 0 && sort_descriptors_.size() > 1 &&
      sort_descriptors_[1].column_id != -1) {
    // Try the secondary sort.
    return SwapCompareResult(
        model_->CompareValues(model_row1, model_row2,
                              sort_descriptors_[1].column_id),
        sort_descriptors_[1].ascending);
  }
  return SwapCompareResult(sort_result, sort_descriptors_[0].ascending);
}

int TableView::GetColumnWidth(int column_id) {
  if (!list_view_)
    return -1;

  std::vector<int>::const_iterator i =
      std::find(visible_columns_.begin(), visible_columns_.end(), column_id);
  if (i == visible_columns_.end())
    return -1;

  return ListView_GetColumnWidth(
      list_view_, static_cast<int>(i - visible_columns_.begin()));
}

void TableView::PaintAltText() {
  if (alt_text_.empty())
    return;

  HDC dc = GetDC(GetNativeControlHWND());
  gfx::Font font = GetAltTextFont();
  gfx::Rect bounds = GetAltTextBounds();
  gfx::Canvas canvas(bounds.width(), bounds.height(), false);
  // Pad by 1 for halo.
  canvas.DrawStringWithHalo(alt_text_, font, SK_ColorDKGRAY, SK_ColorWHITE, 1,
                            1, bounds.width() - 2, bounds.height() - 2,
                            gfx::Canvas::DefaultCanvasTextAlignment());
  canvas.getTopPlatformDevice().drawToHDC(dc, bounds.x(), bounds.y(), NULL);
  ReleaseDC(GetNativeControlHWND(), dc);
}

LRESULT TableView::OnCustomDraw(NMLVCUSTOMDRAW* draw_info) {
  switch (draw_info->nmcd.dwDrawStage) {
    case CDDS_PREPAINT: {
      return CDRF_NOTIFYITEMDRAW;
    }
    case CDDS_ITEMPREPAINT: {
      // The list-view is about to paint an item, tell it we want to
      // notified when it paints every subitem.
      LRESULT r = CDRF_NOTIFYSUBITEMDRAW;
      if (table_type_ == ICON_AND_TEXT)
        r |= CDRF_NOTIFYPOSTPAINT;
      return r;
    }
    case (CDDS_ITEMPREPAINT | CDDS_SUBITEM): {
      // The list-view is painting a subitem. See if the colors should be
      // changed from the default.
      if (custom_colors_enabled_) {
        // At this time, draw_info->clrText and draw_info->clrTextBk are not
        // set.  So we pass in an ItemColor to GetCellColors.  If
        // ItemColor.color_is_set is true, then we use the provided color.
        ItemColor foreground = {0};
        ItemColor background = {0};

        LOGFONT logfont;
        GetObject(GetWindowFont(list_view_), sizeof(logfont), &logfont);

        if (GetCellColors(view_to_model(
                              static_cast<int>(draw_info->nmcd.dwItemSpec)),
                          draw_info->iSubItem,
                          &foreground,
                          &background,
                          &logfont)) {
          // TODO(tc): Creating/deleting a font for every cell seems like a
          // waste if the font hasn't changed.  Maybe we should use a struct
          // with a bool like we do with colors?
          if (custom_cell_font_)
            DeleteObject(custom_cell_font_);
          l10n_util::AdjustUIFont(&logfont);
          custom_cell_font_ = CreateFontIndirect(&logfont);
          SelectObject(draw_info->nmcd.hdc, custom_cell_font_);
          draw_info->clrText = foreground.color_is_set
                               ? skia::SkColorToCOLORREF(foreground.color)
                               : CLR_DEFAULT;
          draw_info->clrTextBk = background.color_is_set
                                 ? skia::SkColorToCOLORREF(background.color)
                                 : CLR_DEFAULT;
          return CDRF_NEWFONT;
        }
      }
      return CDRF_DODEFAULT;
    }
    case CDDS_ITEMPOSTPAINT: {
      DCHECK((table_type_ == ICON_AND_TEXT) || (ImplementPostPaint()));
      int view_index = static_cast<int>(draw_info->nmcd.dwItemSpec);
      // We get notifications for empty items, just ignore them.
      if (view_index >= model_->RowCount())
        return CDRF_DODEFAULT;
      int model_index = view_to_model(view_index);
      LRESULT r = CDRF_DODEFAULT;
      // First let's take care of painting the right icon.
      if (table_type_ == ICON_AND_TEXT) {
        SkBitmap image = model_->GetIcon(model_index);
        if (!image.isNull()) {
          // Get the rect that holds the icon.
          RECT icon_rect, client_rect;
          if (ListView_GetItemRect(list_view_, view_index, &icon_rect,
                                   LVIR_ICON) &&
              GetClientRect(list_view_, &client_rect)) {
            RECT intersection;
            // Client rect includes the header but we need to make sure we don't
            // paint into it.
            client_rect.top += content_offset_;
            // Make sure the region need to paint is visible.
            if (IntersectRect(&intersection, &icon_rect, &client_rect)) {
              gfx::Canvas canvas(icon_rect.right - icon_rect.left,
                                 icon_rect.bottom - icon_rect.top, false);

              // It seems the state in nmcd.uItemState is not correct.
              // We'll retrieve it explicitly.
              int selected = ListView_GetItemState(
                  list_view_, view_index, LVIS_SELECTED | LVIS_DROPHILITED);
              bool drop_highlight = ((selected & LVIS_DROPHILITED) != 0);
              int bg_color_index;
              if (!IsEnabled())
                bg_color_index = COLOR_3DFACE;
              else if (drop_highlight)
                bg_color_index = COLOR_HIGHLIGHT;
              else if (selected)
                bg_color_index = HasFocus() ? COLOR_HIGHLIGHT : COLOR_3DFACE;
              else
                bg_color_index = COLOR_WINDOW;
              // NOTE: This may be invoked without the ListView filling in the
              // background (or rather windows paints background, then invokes
              // this twice). As such, we always fill in the background.
              canvas.drawColor(
                  skia::COLORREFToSkColor(GetSysColor(bg_color_index)),
                  SkXfermode::kSrc_Mode);
              // + 1 for padding (we declared the image as 18x18 in the list-
              // view when they are 16x16 so we get an extra pixel of padding).
              canvas.DrawBitmapInt(image, 0, 0,
                                   image.width(), image.height(),
                                   1, 1, kFavIconSize, kFavIconSize, true);

              // Only paint the visible region of the icon.
              RECT to_draw = { intersection.left - icon_rect.left,
                               intersection.top - icon_rect.top,
                               0, 0 };
              to_draw.right = to_draw.left +
                              (intersection.right - intersection.left);
              to_draw.bottom = to_draw.top +
                              (intersection.bottom - intersection.top);
              canvas.getTopPlatformDevice().drawToHDC(draw_info->nmcd.hdc,
                                                      intersection.left,
                                                      intersection.top,
                                                      &to_draw);
              r = CDRF_SKIPDEFAULT;
            }
          }
        }
      }
      if (ImplementPostPaint()) {
        RECT cell_rect;
        if (ListView_GetItemRect(list_view_, view_index, &cell_rect,
                                 LVIR_BOUNDS)) {
          PostPaint(model_index, 0, false, gfx::Rect(cell_rect),
                    draw_info->nmcd.hdc);
          r = CDRF_SKIPDEFAULT;
        }
      }
      return r;
    }
    default:
      return CDRF_DODEFAULT;
  }
}

void TableView::UpdateListViewCache(int start, int length, bool add) {
  ignore_listview_change_ = true;
  UpdateListViewCache0(start, length, add);
  ignore_listview_change_ = false;
}

void TableView::ResetColumnSizes() {
  if (!list_view_)
    return;

  // See comment in TableColumn for what this does.
  int width = this->width();
  RECT native_bounds;
  if (GetClientRect(GetNativeControlHWND(), &native_bounds)) {
    int window_width = native_bounds.right - native_bounds.left;
    if (window_width > 0) {
      // Prefer the bounds of the window over our bounds, which may be
      // different.
      width = window_width;
    }
  }

  float percent = 0;
  int fixed_width = 0;
  int autosize_width = 0;

  for (std::vector<int>::const_iterator i = visible_columns_.begin();
       i != visible_columns_.end(); ++i) {
    TableColumn& col = all_columns_[*i];
    int col_index = static_cast<int>(i - visible_columns_.begin());
    if (col.width == -1) {
      if (col.percent > 0) {
        percent += col.percent;
      } else {
        autosize_width += col.min_visible_width;
      }
    } else {
      fixed_width += ListView_GetColumnWidth(list_view_, col_index);
    }
  }

  // Now do a pass to set the actual sizes of auto-sized and
  // percent-sized columns.
  int available_width = width - fixed_width - autosize_width;
  for (std::vector<int>::const_iterator i = visible_columns_.begin();
       i != visible_columns_.end(); ++i) {
    TableColumn& col = all_columns_[*i];
    if (col.width == -1) {
      int col_index = static_cast<int>(i - visible_columns_.begin());
      if (col.percent > 0) {
        if (available_width > 0) {
          int col_width =
            static_cast<int>(available_width * (col.percent / percent));
          available_width -= col_width;
          percent -= col.percent;
          ListView_SetColumnWidth(list_view_, col_index, col_width);
        }
      } else {
        int col_width = col.min_visible_width;
        // If no "percent" columns, the last column acts as one, if auto-sized.
        if (percent == 0.f && available_width > 0 &&
            col_index == column_count_ - 1) {
          col_width += available_width;
        }
        ListView_SetColumnWidth(list_view_, col_index, col_width);
      }
    }
  }
}

gfx::Size TableView::GetPreferredSize() {
  return preferred_size_;
}

void TableView::SetPreferredSize(const gfx::Size& size) {
  preferred_size_ = size;
  PreferredSizeChanged();
}

void TableView::SetAltText(const std::wstring& alt_text) {
  if (alt_text == alt_text_)
    return;

  alt_text_ = alt_text;
  if (!GetNativeControlHWND())
    return;

  RECT alt_text_bounds = GetAltTextBounds().ToRECT();
  InvalidateRect(GetNativeControlHWND(), &alt_text_bounds, FALSE);
}

void TableView::UpdateListViewCache0(int start, int length, bool add) {
  if (is_sorted()) {
    if (add)
      UpdateItemsLParams(start, length);
    else
      UpdateItemsLParams(0, 0);
  }

  LVITEM item = {0};
  int start_column = 0;
  int max_row = start + length;
  const bool has_groups =
      (win_util::GetWinVersion() > win_util::WINVERSION_2000 &&
       model_->HasGroups());
  if (add) {
    if (has_groups)
      item.mask = LVIF_GROUPID;
    item.mask |= LVIF_PARAM;
    for (int i = start; i < max_row; ++i) {
      item.iItem = i;
      if (has_groups)
        item.iGroupId = model_->GetGroupID(i);
      item.lParam = i;
      ListView_InsertItem(list_view_, &item);
    }
  }

  memset(&item, 0, sizeof(LVITEM));

  // NOTE: I don't quite get why the iSubItem in the following is not offset
  // by 1. According to the docs it should be offset by one, but that doesn't
  // work.
  if (table_type_ == CHECK_BOX_AND_TEXT) {
    start_column = 1;
    item.iSubItem = 0;
    item.mask = LVIF_TEXT | LVIF_STATE;
    item.stateMask = LVIS_STATEIMAGEMASK;
    for (int i = start; i < max_row; ++i) {
      std::wstring text = model_->GetText(i, visible_columns_[0]);
      item.iItem = add ? i : model_to_view(i);
      item.pszText = const_cast<LPWSTR>(text.c_str());
      item.state = INDEXTOSTATEIMAGEMASK(model_->IsChecked(i) ? 2 : 1);
      ListView_SetItem(list_view_, &item);
    }
  }

  item.stateMask = 0;
  item.mask = LVIF_TEXT;
  if (table_type_ == ICON_AND_TEXT) {
    item.mask |= LVIF_IMAGE;
  }
  for (int j = start_column; j < column_count_; ++j) {
    TableColumn& col = all_columns_[visible_columns_[j]];
    int max_text_width = ListView_GetStringWidth(list_view_, col.title.c_str());
    for (int i = start; i < max_row; ++i) {
      item.iItem = add ? i : model_to_view(i);
      item.iSubItem = j;
      std::wstring text = model_->GetText(i, visible_columns_[j]);
      item.pszText = const_cast<LPWSTR>(text.c_str());
      item.iImage = 0;
      ListView_SetItem(list_view_, &item);

      // Compute width in px, using current font.
      int string_width = ListView_GetStringWidth(list_view_, item.pszText);
      // The width of an icon belongs to the first column.
      if (j == 0 && table_type_ == ICON_AND_TEXT)
        string_width += kListViewIconWidthAndPadding;
      max_text_width = std::max(string_width, max_text_width);
    }

    // ListView_GetStringWidth must be padded or else truncation will occur
    // (MSDN). 15px matches the Win32/LVSCW_AUTOSIZE_USEHEADER behavior.
    max_text_width += kListViewTextPadding;

    // Protect against partial update.
    if (max_text_width > col.min_visible_width ||
        (start == 0 && length == model_->RowCount())) {
      col.min_visible_width = max_text_width;
    }
  }

  if (is_sorted()) {
    // NOTE: As most of our tables are smallish I'm not going to optimize this.
    // If our tables become large and frequently update, then it'll make sense
    // to optimize this.

    SortItemsAndUpdateMapping();
  }
}

void TableView::OnDoubleClick() {
  if (!ignore_listview_change_ && table_view_observer_) {
    table_view_observer_->OnDoubleClick();
  }
}

void TableView::OnMiddleClick() {
  if (!ignore_listview_change_ && table_view_observer_)
    table_view_observer_->OnMiddleClick();
}

void TableView::OnSelectedStateChanged() {
  if (!ignore_listview_change_ && table_view_observer_) {
    table_view_observer_->OnSelectionChanged();
  }
}

bool TableView::OnKeyDown(base::KeyboardCode virtual_keycode) {
  if (!ignore_listview_change_ && table_view_observer_) {
    table_view_observer_->OnKeyDown(virtual_keycode);
  }
  return false;  // Let the key event be processed as ususal.
}

void TableView::OnCheckedStateChanged(int model_row, bool is_checked) {
  if (!ignore_listview_change_)
    model_->SetChecked(model_row, is_checked);
}

int TableView::PreviousSelectedViewIndex(int view_index) {
  DCHECK_GE(view_index, 0);
  if (!list_view_ || view_index <= 0)
    return -1;

  int row_count = RowCount();
  if (row_count == 0)
    return -1;  // Empty table, nothing can be selected.

  // For some reason
  // ListView_GetNextItem(list_view_,item, LVNI_SELECTED | LVNI_ABOVE)
  // fails on Vista (always returns -1), so we iterate through the indices.
  view_index = std::min(view_index, row_count);
  while (--view_index >= 0 && !IsItemSelected(view_to_model(view_index)));
  return view_index;
}

int TableView::LastSelectedViewIndex() {
  return PreviousSelectedViewIndex(RowCount());
}

void TableView::UpdateContentOffset() {
  content_offset_ = 0;

  if (!list_view_)
    return;

  HWND header = ListView_GetHeader(list_view_);
  if (!header)
    return;

  POINT origin = {0, 0};
  MapWindowPoints(header, list_view_, &origin, 1);

  RECT header_bounds;
  GetWindowRect(header, &header_bounds);

  content_offset_ = origin.y + header_bounds.bottom - header_bounds.top;
}

gfx::Rect TableView::GetAltTextBounds() {
  static const int kXOffset = 16;
  DCHECK(GetNativeControlHWND());
  RECT client_rect_rect;
  GetClientRect(GetNativeControlHWND(), &client_rect_rect);
  gfx::Rect client_rect(client_rect_rect);
  gfx::Font font = GetAltTextFont();
  // Pad height by 2 for halo.
  return gfx::Rect(kXOffset, content_offset(), client_rect.width() - kXOffset,
                   std::max(kImageSize, font.height() + 2));
}

gfx::Font TableView::GetAltTextFont() {
  return ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::BaseFont);
}


//
// TableSelectionIterator
//
TableSelectionIterator::TableSelectionIterator(TableView* view,
                                               int view_index)
    : table_view_(view),
      view_index_(view_index) {
  UpdateModelIndexFromViewIndex();
}

TableSelectionIterator& TableSelectionIterator::operator=(
    const TableSelectionIterator& other) {
  view_index_ = other.view_index_;
  model_index_ = other.model_index_;
  return *this;
}

bool TableSelectionIterator::operator==(const TableSelectionIterator& other) {
  return (other.view_index_ == view_index_);
}

bool TableSelectionIterator::operator!=(const TableSelectionIterator& other) {
  return (other.view_index_ != view_index_);
}

TableSelectionIterator& TableSelectionIterator::operator++() {
  view_index_ = table_view_->PreviousSelectedViewIndex(view_index_);
  UpdateModelIndexFromViewIndex();
  return *this;
}

int TableSelectionIterator::operator*() {
  return model_index_;
}

void TableSelectionIterator::UpdateModelIndexFromViewIndex() {
  if (view_index_ == -1)
    model_index_ = -1;
  else
    model_index_ = table_view_->view_to_model(view_index_);
}

}  // namespace views

Generated by  Doxygen 1.6.0   Back to index