diff options
Diffstat (limited to 'chrome/views/table_view.cc')
-rw-r--r-- | chrome/views/table_view.cc | 1025 |
1 files changed, 1025 insertions, 0 deletions
diff --git a/chrome/views/table_view.cc b/chrome/views/table_view.cc new file mode 100644 index 0000000..5f985b7 --- /dev/null +++ b/chrome/views/table_view.cc @@ -0,0 +1,1025 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <windowsx.h> + +#include "chrome/views/table_view.h" + +#include "base/string_util.h" +#include "base/win_util.h" +#include "base/gfx/skia_utils.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/gfx/favicon_size.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/common/win_util.h" +#include "chrome/views/hwnd_view.h" +#include "chrome/views/view_container.h" +#include "SkBitmap.h" +#include "SkColorFilter.h" + +namespace ChromeViews { + +// Added to column width to prevent truncation. +const int kListViewTextPadding = 15; +// Additional column width necessary if column has icons. +const int kListViewIconWidthAndPadding = 18; + +SkBitmap TableModel::GetIcon(int row) { + return SkBitmap(); +} + +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())), + cache_data_(true), + 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), + list_view_original_handler_(NULL), + header_original_handler_(NULL), + table_view_wrapper_(this), + custom_cell_font_(NULL), + content_offset_(0) { + DCHECK(model); + 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) { + model_ = model; + if (model_) + OnModelChanged(); +} + +void TableView::DidChangeBounds(const CRect& previous, + const CRect& current) { + if (!list_view_) + return; + SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(FALSE), 0); + Layout(); + if ((!sized_columns_ || autosize_columns_) && GetWidth() > 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 item) { + if (!list_view_) + return; + + DCHECK(item >= 0 && item < 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. + ListView_SetItemState(list_view_, item, LVIS_SELECTED | LVIS_FOCUSED, + LVIS_SELECTED | LVIS_FOCUSED); + + // Make it visible. + ListView_EnsureVisible(list_view_, item, 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 item, bool state) { + if (!list_view_) + return; + + DCHECK(item >= 0 && item < RowCount()); + + ignore_listview_change_ = true; + + // Select the specified item. + ListView_SetItemState(list_view_, item, LVIS_SELECTED, LVIS_SELECTED); + + ignore_listview_change_ = false; +} + +void TableView::SetFocusOnItem(int item) { + if (!list_view_) + return; + + DCHECK(item >= 0 && item < RowCount()); + + ignore_listview_change_ = true; + + // Set the focus to the given item. + ListView_SetItemState(list_view_, item, LVIS_FOCUSED, LVIS_FOCUSED); + + ignore_listview_change_ = false; +} + +int TableView::FirstSelectedRow() { + if (!list_view_) + return -1; + + return ListView_GetNextItem(list_view_, -1, LVNI_ALL | LVIS_SELECTED); +} + + +bool TableView::IsItemSelected(int item) { + if (!list_view_) + return false; + + DCHECK(item >= 0 && item < RowCount()); + return (ListView_GetItemState(list_view_, item, LVIS_SELECTED) == + LVIS_SELECTED); +} + +bool TableView::ItemHasTheFocus(int item) { + if (!list_view_) + return false; + + DCHECK(item >= 0 && item < RowCount()); + return (ListView_GetItemState(list_view_, item, LVIS_FOCUSED) == + LVIS_FOCUSED); +} + +TableView::iterator TableView::SelectionBegin() { + return TableView::iterator(this, LastSelectedIndex()); +} + +TableView::iterator TableView::SelectionEnd() { + return TableView::iterator(this, -1); +} + +void TableView::OnItemsChanged(int start, int length) { + if (!list_view_) + return; + + if (length == -1) { + DCHECK(start >= 0); + if (cache_data_) { + length = model_->RowCount() - start; + } else { + length = 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 = 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); + } + } + if (!cache_data_) { + ListView_RedrawItems(list_view_, start, start + length); + } else { + 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_->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); + if (!cache_data_) { + ListView_SetItemCount(list_view_, model_->RowCount()); + ListView_RedrawItems(list_view_, start, start + length); + } else { + 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; + + DCHECK(start >= 0 && length > 0 && start + length <= RowCount()); + SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(FALSE), 0); + bool had_selection = (SelectedRowCount() > 0); + if (!cache_data_) { + // TODO(sky): Make sure this triggers a repaint. + ListView_SetItemCount(list_view_, model_->RowCount()); + } else { + // Update the cache. + if (start == 0 && length == RowCount()) { + ListView_DeleteAllItems(list_view_); + } else { + for (int i = 0; i < length; ++i) { + ListView_DeleteItem(list_view_, start); + } + } + } + SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(TRUE), 0); + // We don't seem to get notification in this case. + if (table_view_observer_ && had_selection && RowCount() == 0) + table_view_observer_->OnSelectionChanged(); +} + +void TableView::AddColumn(const TableColumn& col) { + DCHECK(all_columns_.count(col.id) == 0); + all_columns_[col.id] = col; +} + +void TableView::SetColumns(const std::vector<TableColumn>& columns) { + all_columns_.empty(); + for (std::vector<TableColumn>::const_iterator i = columns.begin(); + i != columns.end(); ++i) { + AddColumn(*i); + } +} + +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 + // container). 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; +} + +void TableView::SetCustomColorsEnabled(bool custom_colors_enabled) { + custom_colors_enabled_ = custom_colors_enabled; +} + +bool TableView::GetCellColors(int row, + int column, + ItemColor* foreground, + ItemColor* background, + LOGFONT* logfont) { + return false; +} + +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 (!cache_data_) + style |= LVS_OWNERDATA; + // 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, ChromeViews::TableColumn>::const_iterator first = + all_columns_.begin(); + if (first->second.title.empty()) + style |= LVS_NOCOLUMNHEADER; + } + list_view_ = ::CreateWindowEx(WS_EX_CLIENTEDGE | GetAdditionalExStyle(), + WC_LISTVIEW, + L"", + style, + 0, 0, GetWidth(), GetHeight(), + parent_container, NULL, NULL, NULL); + model_->SetObserver(this); + + // 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); + + // 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())); + } + + // Add the groups. + if (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 (cache_data_) { + UpdateListViewCache(0, model_->RowCount(), true); + } else if (table_type_ == CHECK_BOX_AND_TEXT) { + ListView_SetItemCount(list_view_, model_->RowCount()); + ListView_SetCallbackMask(list_view_, LVIS_STATEIMAGEMASK); + } + + // Load the default icon. + if (table_type_ == ICON_AND_TEXT) { + HIMAGELIST image_list = ImageList_Create(18, 18, ILC_COLOR, 1, 1); + // TODO(jcampan): include a default icon image. + // This icon will not be found. The list view will still layout with the + // right space for the icon so we can paint our own icon. + HBITMAP image = LoadBitmap(NULL, L"IDR_WHATEVER"); + ImageList_Add(image_list, image, NULL); + DeleteObject(image); + // 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). + image = LoadBitmap(NULL, L"IDR_WHATEVER_AGAIN"); + ImageList_Add(image_list, image, NULL); + DeleteObject(image); + 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); + } + + // 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::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)); +} + +LRESULT TableView::OnNotify(int w_param, NMHDR* hdr) { + 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. + bool is_selected = ((state_change->uNewState & LVIS_SELECTED) != 0); + OnSelectedStateChanged(state_change->iItem, is_selected); + } + 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(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; + + // If we see a key down message, we need to invoke the OnKeyDown handler + // in order to give our class (or any subclass) and opportunity to perform + // a key down triggered action, if such action is necessary. + case LVN_KEYDOWN: { + NMLVKEYDOWN* key_down_message = reinterpret_cast<NMLVKEYDOWN*>(hdr); + OnKeyDown(key_down_message->wVKey); + break; + } + + default: + break; + } + return 0; +} + +void TableView::OnDestroy() { + if (table_type_ == ICON_AND_TEXT) { + HIMAGELIST image_list = + ListView_GetImageList(GetNativeControlHWND(), LVSIL_SMALL); + DCHECK(image_list); + if (image_list) + ImageList_Destroy(image_list); + } +} + +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(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_); + custom_cell_font_ = CreateFontIndirect(&logfont); + SelectObject(draw_info->nmcd.hdc, custom_cell_font_); + draw_info->clrText = foreground.color_is_set + ? gfx::SkColorToCOLORREF(foreground.color) + : CLR_DEFAULT; + draw_info->clrTextBk = background.color_is_set + ? gfx::SkColorToCOLORREF(background.color) + : CLR_DEFAULT; + return CDRF_NEWFONT; + } + } + return CDRF_DODEFAULT; + } + case CDDS_ITEMPOSTPAINT: { + DCHECK((table_type_ == ICON_AND_TEXT) || (ImplementPostPaint())); + int n_item = static_cast<int>(draw_info->nmcd.dwItemSpec); + // We get notifications for empty items, just ignore them. + if (n_item >= model_->RowCount()) { + return CDRF_DODEFAULT; + } + LRESULT r = CDRF_DODEFAULT; + // First let's take care of painting the right icon. + if (table_type_ == ICON_AND_TEXT) { + SkBitmap image = model_->GetIcon(n_item); + if (!image.isNull()) { + // Get the rect that holds the icon. + CRect icon_rect, client_rect; + if (ListView_GetItemRect(list_view_, n_item, &icon_rect, + LVIR_ICON) && + GetClientRect(list_view_, &client_rect)) { + CRect 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 (intersection.IntersectRect(&icon_rect, &client_rect)) { + ChromeCanvas canvas(icon_rect.Width(), icon_rect.Height(), false); + + // It seems the state in nmcd.uItemState is not correct. + // We'll retrieve it explicitly. + int selected = ListView_GetItemState(list_view_, n_item, + LVIS_SELECTED); + int bg_color_index; + if (!IsEnabled()) + bg_color_index = COLOR_3DFACE; + 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( + gfx::COLORREFToSkColor(GetSysColor(bg_color_index)), + SkPorterDuff::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()) { + CRect cell_rect; + if (ListView_GetItemRect(list_view_, n_item, &cell_rect, LVIR_BOUNDS)) { + PostPaint(n_item, 0, false, 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. + CRect bounds; + GetLocalBounds(&bounds, false); // false so it doesn't include the border. + int width = bounds.Width(); + if (GetClientRect(GetNativeControlHWND(), &bounds) && + bounds.Width() > 0) { + // Prefer the bounds of the window over our bounds, which may be different. + width = bounds.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); + } + } + } +} + +void TableView::SetPreferredSize(const CSize& preferred_size) { + preferred_size_ = preferred_size; +} + +void TableView::GetPreferredSize(CSize* out) { + DCHECK(out); + *out = preferred_size_; +} + + +void TableView::UpdateListViewCache0(int start, int length, bool add) { + 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 (has_groups) + item.mask = LVIF_GROUPID; + if (add) { + for (int i = start; i < max_row; ++i) { + item.iItem = i; + if (has_groups) + item.iGroupId = model_->GetGroupID(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 = i; + item.pszText = const_cast<LPWSTR>(text.c_str()); + item.state = INDEXTOSTATEIMAGEMASK(model_->IsChecked(i) ? 2 : 1) ; + ListView_SetItem(list_view_, &item); + } + } + if (start_column == column_count_) + return; + + 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 = 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; + } + } +} + +void TableView::OnDoubleClick() { + if (!ignore_listview_change_ && table_view_observer_) { + table_view_observer_->OnDoubleClick(); + } +} + +void TableView::OnSelectedStateChanged(int item, bool is_selected) { + if (!ignore_listview_change_ && table_view_observer_) { + table_view_observer_->OnSelectionChanged(); + } +} + +void TableView::OnCheckedStateChanged(int item, bool is_checked) { + if (!ignore_listview_change_) { + model_->SetChecked(item, is_checked); + } +} + +int TableView::NextSelectedIndex(int item) { + if (!list_view_) + return -1; + DCHECK(item >= 0); + if (item >= RowCount()) { + return LastSelectedIndex(); + } + // It seems if the list has only 1 element and it is selected that + // ListView_GetNextItem always returns 0. + if (RowCount() == 1) { + return -1; + } + return ListView_GetNextItem(list_view_, + item, LVNI_ALL | LVNI_SELECTED | LVNI_ABOVE); +} + +int TableView::LastSelectedIndex() { + if (!list_view_) + return -1; + int row_count = RowCount(); + int last_selected_row = -1; + if (row_count > 0) { + if (ListView_GetItemState(list_view_, row_count - 1, + LVIS_SELECTED) == LVIS_SELECTED) { + last_selected_row = row_count - 1; + } else { + last_selected_row = ListView_GetNextItem(list_view_, + row_count - 1, LVNI_ALL | LVNI_SELECTED | LVNI_ABOVE); + } + } + return last_selected_row; +} + +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); + + CRect header_bounds; + GetWindowRect(header, &header_bounds); + + content_offset_ = origin.y + header_bounds.Height(); +} + +// +// TableSelectionIterator +// +TableSelectionIterator::TableSelectionIterator(TableView* view, + int index) + : table_view_(view), index_(index) { +} + +TableSelectionIterator& TableSelectionIterator::operator=( + const TableSelectionIterator& other) { + index_ = other.index_; + return *this; +} + +bool TableSelectionIterator::operator==(const TableSelectionIterator& other) { + return (other.index_ == index_); +} + +bool TableSelectionIterator::operator!=(const TableSelectionIterator& other) { + return (other.index_ != index_); +} + +TableSelectionIterator& TableSelectionIterator::operator++() { + index_ = table_view_->NextSelectedIndex(index_); + return *this; +} + +int TableSelectionIterator::operator*() { + return index_; +} + +} // namespace |