| Index: views/controls/table/table_view.cc
|
| diff --git a/views/controls/table/table_view.cc b/views/controls/table/table_view.cc
|
| deleted file mode 100644
|
| index 987ca4048f8406af4320f31c6fb5dd9d15fff01f..0000000000000000000000000000000000000000
|
| --- a/views/controls/table/table_view.cc
|
| +++ /dev/null
|
| @@ -1,1645 +0,0 @@
|
| -// Copyright (c) 2011 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 "base/i18n/rtl.h"
|
| -#include "base/string_util.h"
|
| -#include "base/win/scoped_gdi_object.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 "ui/base/l10n/l10n_util.h"
|
| -#include "ui/base/l10n/l10n_util_win.h"
|
| -#include "ui/base/models/table_model.h"
|
| -#include "ui/base/resource/resource_bundle.h"
|
| -#include "ui/base/win/hwnd_util.h"
|
| -#include "ui/gfx/canvas_skia.h"
|
| -#include "ui/gfx/favicon_size.h"
|
| -#include "ui/gfx/font.h"
|
| -#include "ui/gfx/icon_util.h"
|
| -#include "views/controls/native/native_view_host.h"
|
| -#include "views/controls/table/table_view_observer.h"
|
| -
|
| -namespace {
|
| -
|
| -int GetViewIndexFromPoint(HWND window, const gfx::Point& p) {
|
| - LVHITTESTINFO hit_info = {0};
|
| - hit_info.pt.x = p.x();
|
| - hit_info.pt.y = p.y();
|
| - return ListView_HitTest(window, &hit_info);
|
| -}
|
| -
|
| -} // namespace
|
| -
|
| -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(ui::TableModel* model,
|
| - const std::vector<ui::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),
|
| - 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<ui::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(ui::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);
|
| -}
|
| -
|
| -int TableView::RowCount() const {
|
| - 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 = ModelToView(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_, ModelToView(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_, ModelToView(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 : ViewToModel(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_, ModelToView(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_, ModelToView(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 = ModelToView(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;
|
| -
|
| - UpdateGroups();
|
| -
|
| - 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 = ViewToModel(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 ui::TableColumn& col) {
|
| - DCHECK_EQ(0u, all_columns_.count(col.id));
|
| - all_columns_[col.id] = col;
|
| -}
|
| -
|
| -void TableView::SetColumns(const std::vector<ui::TableColumn>& columns) {
|
| - // Remove the currently visible columns.
|
| - while (!visible_columns_.empty())
|
| - SetColumnVisibility(visible_columns_.front(), false);
|
| -
|
| - all_columns_.clear();
|
| - for (std::vector<ui::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);
|
| - ui::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 ui::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 (base::i18n::IsRTL())
|
| - 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
|
| -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 (base::i18n::IsRTL() &&
|
| - (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.
|
| - int view_index =
|
| - GetViewIndexFromPoint(window, gfx::Point(table_point));
|
| - if (view_index != -1) {
|
| - int model_index = table_view->ViewToModel(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 = GetViewIndexFromPoint(window, gfx::Point(l_param));
|
| - if (view_index != -1) {
|
| - int model_index = table_view->ViewToModel(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 = GetViewIndexFromPoint(window, gfx::Point(l_param));
|
| - if (view_index != -1)
|
| - table_view->Select(table_view->ViewToModel(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 = GetViewIndexFromPoint(window, gfx::Point(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->ViewToModel(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;
|
| - if (!table_view->SelectMultiple(view_index, mark_view_index)) {
|
| - // Selection spans group boundary - reset selection to current.
|
| - table_view->SetSelectedState(model_index, true);
|
| - ListView_SetSelectionMark(window, view_index);
|
| - }
|
| - }
|
| - }
|
| - // 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, ui::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);
|
| - ui::CheckWindowCreated(list_view_);
|
| -
|
| - // Reduce overdraw/flicker artifacts by double buffering. Support tooltips
|
| - // and display elided items completely on hover (see comments in OnNotify()
|
| - // under LVN_GETINFOTIP). Make the selection extend across the row.
|
| - ListView_SetExtendedListViewStyle(list_view_,
|
| - LVS_EX_DOUBLEBUFFER | LVS_EX_INFOTIP | LVS_EX_FULLROWSELECT);
|
| - 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);
|
| -
|
| - UpdateGroups();
|
| -
|
| - // 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::CanvasSkia canvas(kImageSize, kImageSize, false);
|
| - // Make the background completely transparent.
|
| - canvas.sk_canvas()->drawColor(SK_ColorBLACK, SkXfermode::kClear_Mode);
|
| - {
|
| - base::win::ScopedHICON empty_icon(
|
| - IconUtil::CreateHICONFromSkBitmap(canvas.ExtractBitmap()));
|
| - ImageList_AddIcon(image_list, empty_icon);
|
| - ImageList_AddIcon(image_list, 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_ = ui::SetWindowProc(header,
|
| - &TableView::TableHeaderWndProc);
|
| - }
|
| -
|
| - SetWindowLongPtr(list_view_, GWLP_USERDATA,
|
| - reinterpret_cast<LONG_PTR>(&table_view_wrapper_));
|
| - original_handler_ =
|
| - ui::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();
|
| - column_sizes_valid_ = false;
|
| -
|
| - 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 = ViewToModel(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;
|
| - }
|
| -}
|
| -
|
| -bool TableView::SelectMultiple(int view_index, int mark_view_index) {
|
| - int group_id = 0;
|
| - if (model_->HasGroups()) {
|
| - group_id = model_->GetGroupID(ViewToModel(view_index));
|
| - if (group_id != model_->GetGroupID(ViewToModel(mark_view_index))) {
|
| - // User is trying to do a cross-group selection - bail out.
|
| - return 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) {
|
| - // Items between the view_index and mark_view_index are not necessarily in
|
| - // the same group, so don't select anything outside the group the user
|
| - // just clicked in.
|
| - if (model_->HasGroups() &&
|
| - model_->GetGroupID(ViewToModel(i)) != group_id) {
|
| - continue;
|
| - }
|
| - SetSelectedState(ViewToModel(i), true);
|
| - }
|
| - return true;
|
| -}
|
| -
|
| -// 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 ui::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 ui::TableColumn::LEFT:
|
| - column.fmt = LVCFMT_LEFT;
|
| - break;
|
| - case ui::TableColumn::RIGHT:
|
| - column.fmt = LVCFMT_RIGHT;
|
| - break;
|
| - case ui::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.
|
| - 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();
|
| - }
|
| - }
|
| - 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 ui::TableColumn& column = GetColumnAtPosition(
|
| - reinterpret_cast<NMLISTVIEW*>(hdr)->iSubItem);
|
| - if (column.sortable)
|
| - ToggleSortOrder(column.id);
|
| - break;
|
| - }
|
| -
|
| - case LVN_MARQUEEBEGIN: // We don't want the marquee selection.
|
| - return 1;
|
| -
|
| - case LVN_GETINFOTIP: {
|
| - // This is called when the user hovers items in column zero.
|
| - // * If the text in this column is not fully visible, the dwFlags field
|
| - // will be set to 0, and pszText will contain the full text. If you
|
| - // return without making any changes, this text will be displayed in a
|
| - // "labeltip" - a bubble that's overlaid (at the correct alignment!)
|
| - // on the item. If you return with a different pszText, it will be
|
| - // displayed as a tooltip if nonempty.
|
| - // * Otherwise, dwFlags will be LVGIT_UNFOLDED and pszText will be
|
| - // empty. On return, if pszText is nonempty, it will be displayed as
|
| - // a labeltip if dwFlags has been changed to 0 (even if it bears no
|
| - // resemblance to the item text), or as a tooltip otherwise.
|
| - //
|
| - // Once the tooltip for an item has been obtained, this will not be called
|
| - // again until the user hovers a different item. If after that the
|
| - // original item is hovered a second time, this will be called.
|
| - //
|
| - // When the user hovers items in other columns, they will be "unfolded"
|
| - // (displayed as labeltips) when necessary, but this function will never
|
| - // be called.
|
| - //
|
| - // Changing the LVS_EX_INFOTIP extended style to LVS_EX_LABELTIP will
|
| - // cause all of the above to be true except that this function will not be
|
| - // called when dwFlags would be LVGIT_UNFOLDED. Removing it entirely will
|
| - // disable all of the above behavior.
|
| - NMLVGETINFOTIP* info_tip = reinterpret_cast<NMLVGETINFOTIP*>(hdr);
|
| - string16 tooltip =
|
| - model_->GetTooltip(ViewToModel(info_tip->iItem));
|
| - CHECK_GE(info_tip->cchTextMax, 2);
|
| - if (tooltip.length() >= static_cast<size_t>(info_tip->cchTextMax)) {
|
| - tooltip.erase(info_tip->cchTextMax - 2); // Ellipsis + '\0'
|
| - const char16 kEllipsis = 0x2026;
|
| - tooltip.push_back(kEllipsis);
|
| - }
|
| - if (!tooltip.empty())
|
| - wcscpy_s(info_tip->pszText, tooltip.length() + 1, tooltip.c_str());
|
| - 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::CanvasSkia 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::CanvasSkia::DefaultCanvasTextAlignment());
|
| - skia::DrawToNativeContext(
|
| - canvas.sk_canvas(), 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(ViewToModel(
|
| - 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 = ViewToModel(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::CanvasSkia 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.sk_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,
|
| - gfx::kFaviconSize, gfx::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);
|
| - skia::DrawToNativeContext(canvas.sk_canvas(), 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;
|
| - // Only set the flag when we know the true width of the table.
|
| - column_sizes_valid_ = true;
|
| - }
|
| - }
|
| -
|
| - 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) {
|
| - ui::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) {
|
| - ui::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();
|
| -}
|
| -
|
| -int TableView::ModelToView(int model_index) const {
|
| - if (!model_to_view_.get())
|
| - return model_index;
|
| - DCHECK_GE(model_index, 0) << " negative model_index " << model_index;
|
| - DCHECK_LT(model_index, RowCount()) << " out of bounds model_index " <<
|
| - model_index;
|
| - return model_to_view_[model_index];
|
| -}
|
| -
|
| -int TableView::ViewToModel(int view_index) const {
|
| - if (!view_to_model_.get())
|
| - return view_index;
|
| - DCHECK_GE(view_index, 0) << " negative view_index " << view_index;
|
| - DCHECK_LT(view_index, RowCount()) << " out of bounds view_index " <<
|
| - view_index;
|
| - return view_to_model_[view_index];
|
| -}
|
| -
|
| -void TableView::SetAltText(const string16& 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};
|
| - if (add) {
|
| - const bool has_groups = model_->HasGroups();
|
| - for (int i = start; i < start + length; ++i) {
|
| - item.mask = has_groups ? (LVIF_GROUPID | LVIF_PARAM) : LVIF_PARAM;
|
| - item.iItem = i;
|
| - if (has_groups)
|
| - item.iGroupId = model_->GetGroupID(i);
|
| - if (model_->ShouldIndent(i)) {
|
| - item.mask |= LVIF_INDENT;
|
| - item.iIndent = 1;
|
| - }
|
| - item.lParam = i;
|
| - ListView_InsertItem(list_view_, &item);
|
| - }
|
| - }
|
| -
|
| - memset(&item, 0, sizeof(LVITEM));
|
| - item.mask =
|
| - (table_type_ == ICON_AND_TEXT) ? (LVIF_IMAGE | LVIF_TEXT) : LVIF_TEXT;
|
| - item.stateMask = 0;
|
| - for (int j = 0; j < column_count_; ++j) {
|
| - ui::TableColumn& col = all_columns_[visible_columns_[j]];
|
| - int max_text_width = ListView_GetStringWidth(list_view_, col.title.c_str());
|
| - for (int i = start; i < start + length; ++i) {
|
| - // Set item.
|
| - item.iItem = add ? i : ModelToView(i);
|
| - item.iSubItem = j;
|
| - string16 text = model_->GetText(i, visible_columns_[j]);
|
| - item.pszText = const_cast<LPWSTR>(text.c_str());
|
| - 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(ui::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::OnBoundsChanged(const gfx::Rect& previous_bounds) {
|
| - if (!list_view_)
|
| - return;
|
| - SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(FALSE), 0);
|
| - Layout();
|
| - if ((autosize_columns_ || !column_sizes_valid_) && width() > 0)
|
| - ResetColumnSizes();
|
| - UpdateContentOffset();
|
| - SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(TRUE), 0);
|
| -}
|
| -
|
| -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(ViewToModel(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;
|
| -}
|
| -
|
| -void TableView::UpdateGroups() {
|
| - // Add the groups.
|
| - if (model_ && model_->HasGroups()) {
|
| - ListView_RemoveAllGroups(list_view_);
|
| -
|
| - // Windows XP seems to disable groups if we remove them, so we
|
| - // re-enable them.
|
| - ListView_EnableGroupView(list_view_, true);
|
| -
|
| - ui::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);
|
| - }
|
| - }
|
| -}
|
| -
|
| -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.GetHeight() + 2));
|
| -}
|
| -
|
| -gfx::Font TableView::GetAltTextFont() {
|
| - return ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::BaseFont);
|
| -}
|
| -
|
| -void TableView::VisibilityChanged(View* starting_from, bool is_visible) {
|
| - // GetClientRect as used by ResetColumnSize to obtain the total width
|
| - // available to the columns only works when the native control is visible, so
|
| - // update the column sizes in case we become visible. This depends on
|
| - // VisibilityChanged() being called in post order on the view tree.
|
| - if (is_visible && (autosize_columns_ || !column_sizes_valid_) && width() > 0)
|
| - ResetColumnSizes();
|
| -}
|
| -
|
| -
|
| -//
|
| -// 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_->ViewToModel(view_index_);
|
| -}
|
| -
|
| -} // namespace views
|
|
|