diff options
author | jcampan@chromium.org <jcampan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-11-12 00:26:44 +0000 |
---|---|---|
committer | jcampan@chromium.org <jcampan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-11-12 00:26:44 +0000 |
commit | d0545b7f65cfee49e1bdfc10d256886c1fd89818 (patch) | |
tree | 65b8d32b4937e379fff7f522937095bbdf9f3654 /views | |
parent | cdcb3bd006d0823d45187f2c7fe3c3adc0ebb031 (diff) | |
download | chromium_src-d0545b7f65cfee49e1bdfc10d256886c1fd89818.zip chromium_src-d0545b7f65cfee49e1bdfc10d256886c1fd89818.tar.gz chromium_src-d0545b7f65cfee49e1bdfc10d256886c1fd89818.tar.bz2 |
Refactoring of the TableView so it uses NativeViewControl.
At this point it does not support grouping or sorting.
Mostly refactoring things from TableView to TableView2 and NativeTableWin.
BUG=None
TEST=Unit-tests and view examples for now.
R=sky
Review URL: http://codereview.chromium.org/387021
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@31741 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'views')
-rw-r--r-- | views/controls/table/native_table_win.cc | 893 | ||||
-rw-r--r-- | views/controls/table/native_table_win.h | 130 | ||||
-rw-r--r-- | views/controls/table/native_table_wrapper.h | 76 | ||||
-rw-r--r-- | views/controls/table/table_view2.cc | 338 | ||||
-rw-r--r-- | views/controls/table/table_view2.h | 238 | ||||
-rw-r--r-- | views/controls/table/table_view_observer.h | 4 | ||||
-rw-r--r-- | views/controls/table/table_view_unittest.cc | 213 | ||||
-rw-r--r-- | views/examples/examples_main.cc | 14 | ||||
-rw-r--r-- | views/examples/examples_main.h | 2 | ||||
-rw-r--r-- | views/examples/table2_example.h | 168 | ||||
-rw-r--r-- | views/examples/table_example.h | 169 | ||||
-rw-r--r-- | views/views.gyp | 7 |
12 files changed, 2249 insertions, 3 deletions
diff --git a/views/controls/table/native_table_win.cc b/views/controls/table/native_table_win.cc new file mode 100644 index 0000000..a7564d6 --- /dev/null +++ b/views/controls/table/native_table_win.cc @@ -0,0 +1,893 @@ +// Copyright (c) 2009 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/native_table_win.h" + +#include <commctrl.h> +#include <windowsx.h> + +#include "app/gfx/canvas.h" +#include "app/gfx/favicon_size.h" +#include "app/gfx/icon_util.h" +#include "app/l10n_util.h" +#include "app/l10n_util_win.h" +#include "app/table_model.h" +#include "base/logging.h" +#include "base/win_util.h" +#include "skia/ext/skia_utils_win.h" +#include "views/controls/table/table_view2.h" +#include "views/controls/table/table_view_observer.h" +#include "views/widget/widget.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; + +// static +const int NativeTableWin::kImageSize = 18; + +//////////////////////////////////////////////////////////////////////////////// +// NativeTableWin, public: + +NativeTableWin::NativeTableWin(TableView2* table) + : ignore_listview_change_(false), + table_(table), + content_offset_(0), + header_original_handler_(NULL), + original_handler_(NULL) { + // Associates the actual HWND with the table so the table is the one + // considered as having the focus (not the wrapper) when the HWND is + // focused directly (with a click for example). + set_focus_view(table); +} + +NativeTableWin::~NativeTableWin() { +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeTableWin, NativeTableWrapper implementation: + +int NativeTableWin::GetRowCount() { + if (!native_view()) + return 0; + return ListView_GetItemCount(native_view()); +} + +void NativeTableWin::InsertColumn(const TableColumn& tc, int index) { + if (!native_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(native_view(), LVM_INSERTCOLUMN, index, + reinterpret_cast<LPARAM>(&column)); +} + +void NativeTableWin::RemoveColumn(int index) { + if (!native_view()) + return; + SendMessage(native_view(), LVM_DELETECOLUMN, index, 0); + if (table_->model()->RowCount() > 0) + OnRowsChanged(0, table_->model()->RowCount() - 1); +} + +View* NativeTableWin::GetView() { + return this; +} + +void NativeTableWin::SetFocus() { + // Focus the associated HWND. + Focus(); +} + +gfx::NativeView NativeTableWin::GetTestingHandle() const { + return native_view(); +} + +int NativeTableWin::GetColumnWidth(int column_index) { + if (!native_view()) + return 0; + return ListView_GetColumnWidth(native_view(), column_index); +} + +void NativeTableWin::SetColumnWidth(int column_index, int width) { + if (!native_view()) + return; + ListView_SetColumnWidth(native_view(), column_index, width); +} + +int NativeTableWin::GetSelectedRowCount() { + if (!native_view()) + return 0; + return ListView_GetSelectedCount(native_view()); +} + +int NativeTableWin::GetFirstSelectedRow() { + if (!native_view()) + return -1; + return ListView_GetNextItem(native_view(), -1, LVNI_ALL | LVIS_SELECTED); +} + +int NativeTableWin::GetFirstFocusedRow() { + if (!native_view()) + return -1; + return ListView_GetNextItem(native_view(), -1, LVNI_ALL | LVIS_FOCUSED); +} + +void NativeTableWin::ClearSelection() { + if (native_view()) + ListView_SetItemState(native_view(), -1, 0, LVIS_SELECTED); +} + +void NativeTableWin::ClearRowFocus() { + if (native_view()) + ListView_SetItemState(native_view(), -1, 0, LVIS_FOCUSED); +} + +void NativeTableWin::SetSelectedState(int model_row, bool state) { + if (!native_view()) + return; + + SendMessage(native_view(), WM_SETREDRAW, static_cast<WPARAM>(FALSE), 0); + ListView_SetItemState(native_view(), model_row, + state ? LVIS_SELECTED : 0, LVIS_SELECTED); + // Make the selected row visible. + ListView_EnsureVisible(native_view(), model_row, FALSE); + SendMessage(native_view(), WM_SETREDRAW, static_cast<WPARAM>(TRUE), 0); +} + +void NativeTableWin::SetFocusState(int model_row, bool state) { + if (!native_view()) + return; + ListView_SetItemState(native_view(), model_row, + state ? LVIS_FOCUSED : 0, LVIS_FOCUSED) +} + +bool NativeTableWin::IsRowSelected(int model_row) { + if (!native_view()) + return false; + return ListView_GetItemState(native_view(), model_row, LVIS_SELECTED) == + LVIS_SELECTED; +} + +bool NativeTableWin::IsRowFocused(int model_row) { + if (!native_view()) + return false; + return ListView_GetItemState(native_view(), model_row, LVIS_FOCUSED) == + LVIS_FOCUSED; +} + +void NativeTableWin::OnRowsChanged(int start, int length) { + if (!native_view()) + return; + + SendMessage(native_view(), WM_SETREDRAW, static_cast<WPARAM>(FALSE), 0); + UpdateListViewCache(start, length, false); + SendMessage(native_view(), WM_SETREDRAW, static_cast<WPARAM>(TRUE), 0); +} + +void NativeTableWin::OnRowsAdded(int start, int length) { + if (!native_view()) + return; + + SendMessage(native_view(), WM_SETREDRAW, static_cast<WPARAM>(FALSE), 0); + UpdateListViewCache(start, length, true); + SendMessage(native_view(), WM_SETREDRAW, static_cast<WPARAM>(TRUE), 0); +} + +void NativeTableWin::OnRowsRemoved(int start, int length) { + if (!native_view()) + return; + + SendMessage(native_view(), WM_SETREDRAW, static_cast<WPARAM>(FALSE), 0); + + bool had_selection = (GetSelectedRowCount() > 0); + int old_row_count = GetRowCount(); + if (start == 0 && length == GetRowCount()) { + // Everything was removed. + ListView_DeleteAllItems(native_view()); + } else { + for (int i = 0; i < length; ++i) + ListView_DeleteItem(native_view(), start); + } + + SendMessage(native_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 (table_->model() && table_->observer() && had_selection && + GetRowCount() == 0) { + table_->observer()->OnSelectionChanged(); + } +} + +gfx::Rect NativeTableWin::GetBounds() { + RECT native_bounds; + if (!native_view() || GetClientRect(native_view(), &native_bounds)) + return gfx::Rect(); + return gfx::Rect(native_bounds); +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeTableWin, View overrides: + +gfx::Size NativeTableWin::GetPreferredSize() { + SIZE sz = {0}; + SendMessage(native_view(), BCM_GETIDEALSIZE, 0, + reinterpret_cast<LPARAM>(&sz)); + + return gfx::Size(sz.cx, sz.cy); +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeTableWin, NativeControlWin overrides: + +bool NativeTableWin::ProcessMessage(UINT message, WPARAM w_param, + LPARAM l_param, LRESULT* result) { + if (message == WM_NOTIFY) { + LPNMHDR hdr = reinterpret_cast<LPNMHDR>(l_param); + switch (hdr->code) { + case NM_CUSTOMDRAW: { + // Draw notification. dwDragState indicates the current stage of + // drawing. + *result = OnCustomDraw(reinterpret_cast<NMLVCUSTOMDRAW*>(hdr)); + return true; + } + + 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(state_change->iItem, is_checked); + } + } + break; + } + + case HDN_BEGINTRACKW: + case HDN_BEGINTRACKA: + // Prevent clicks so columns cannot be resized. + if (!table_->resizable_columns()) + return true; + break; + + case NM_DBLCLK: + OnDoubleClick(); + break; + + case LVN_COLUMNCLICK: { + const TableColumn& column = table_->GetVisibleColumnAt( + reinterpret_cast<NMLISTVIEW*>(hdr)->iSubItem); + break; + } + + case LVN_MARQUEEBEGIN: // We don't want the marque selection. + return true; + + default: + break; + } + } + + return NativeControlWin::ProcessMessage(message, w_param, l_param, result); +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeTableWin, protected: + +void NativeTableWin::CreateNativeControl() { + int style = WS_CHILD | LVS_REPORT | LVS_SHOWSELALWAYS; + if (table_->single_selection()) + style |= LVS_SINGLESEL; + // If there's only one column and the title string is empty, don't show a + // header. + if (table_->GetVisibleColumnCount() == 1U) { + if (table_->GetVisibleColumnAt(1).title.empty()) + style |= LVS_NOCOLUMNHEADER; + } + HWND hwnd = ::CreateWindowEx(WS_EX_CLIENTEDGE | GetAdditionalRTLStyle(), + WC_LISTVIEW, + L"", + style, + 0, 0, width(), height(), + table_->GetWidget()->GetNativeView(), + 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(hwnd, 0, list_view_style); + l10n_util::AdjustUIFontForWindow(hwnd); + + NativeControlCreated(hwnd); + // native_view() is now valid. + + // Add the columns. + for (size_t i = 0; i < table_->GetVisibleColumnCount(); ++i) + InsertColumn(table_->GetVisibleColumnAt(i), i); + + if (table_->model()) + UpdateListViewCache(0, table_->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(native_view(), image_list, LVSIL_SMALL); + } + + if (!table_->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(native_view()); + DCHECK(header); + SetWindowLongPtr(header, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this)); + header_original_handler_ = + win_util::SetWindowProc(header, &NativeTableWin::TableHeaderWndProc); + } + + SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this)); + original_handler_ = + win_util::SetWindowProc(hwnd, &NativeTableWin::TableWndProc); + + // Bug 964884: detach the IME attached to this window. + // We should attach IMEs only when we need to input CJK strings. + ::ImmAssociateContextEx(hwnd, NULL, 0); + + UpdateContentOffset(); +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeTableWin, private: + +void NativeTableWin::Select(int model_row) { + if (!native_view()) + return; + + DCHECK(model_row >= 0 && model_row < table_->model()->RowCount()); + SendMessage(native_view(), WM_SETREDRAW, static_cast<WPARAM>(FALSE), 0); + ignore_listview_change_ = true; + + // Unselect everything. + ClearSelection(); + + // Select the specified item. + SetSelectedState(model_row, true); + SetFocusState(model_row, true); + + // Make it visible. + ListView_EnsureVisible(native_view(), model_row, FALSE); + ignore_listview_change_ = false; + SendMessage(native_view(), WM_SETREDRAW, static_cast<WPARAM>(TRUE), 0); + if (table_->observer()) + table_->observer()->OnSelectionChanged(); +} + +void NativeTableWin::OnSelectedStateChanged() { + if (!ignore_listview_change_ && table_->observer()) + table_->observer()->OnSelectionChanged(); +} + +void NativeTableWin::OnDoubleClick() { + if (!ignore_listview_change_ && table_->observer()) + table_->observer()->OnDoubleClick(); +} + +void NativeTableWin::OnMiddleClick() { + if (!ignore_listview_change_ && table_->observer()) + table_->observer()->OnMiddleClick(); +} + +bool NativeTableWin::OnKeyDown(base::KeyboardCode virtual_keycode) { + if (!ignore_listview_change_ && table_->observer()) + table_->observer()->OnKeyDown(virtual_keycode); + return false; // Let the key event be processed as ususal. +} + +void NativeTableWin::OnCheckedStateChanged(int model_row, bool is_checked) { + if (!ignore_listview_change_) + table_->model()->SetChecked(model_row, is_checked); +} + +LRESULT NativeTableWin::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): { + // TODO(jcampan): implement custom colors and fonts. + return CDRF_DODEFAULT; + } + case CDDS_ITEMPOSTPAINT: { + DCHECK(table_->type() == ICON_AND_TEXT); + int view_index = static_cast<int>(draw_info->nmcd.dwItemSpec); + // We get notifications for empty items, just ignore them. + if (view_index >= table_->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 = table_->model()->GetIcon(view_index); + if (!image.isNull()) { + // Get the rect that holds the icon. + RECT icon_rect, client_rect; + if (ListView_GetItemRect(native_view(), view_index, &icon_rect, + LVIR_ICON) && + GetClientRect(native_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( + native_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; + } + } + } + } + return r; + } + default: + return CDRF_DODEFAULT; + } +} + +void NativeTableWin::UpdateListViewCache(int start, int length, bool add) { + LVITEM item = {0}; + int start_column = 0; + int max_row = start + length; + if (add) { + item.mask |= LVIF_PARAM; + for (int i = start; i < max_row; ++i) { + item.iItem = i; + item.lParam = i; + // We do not want to notify of the check state when we insert the items. + ignore_listview_change_ = true; + ListView_InsertItem(native_view(), &item); + ignore_listview_change_ = false; + if (table_->type() == CHECK_BOX_AND_TEXT && + table_->model()->IsChecked(i)) { + // Setting the state notifies of the check state change. + ListView_SetCheckState(native_view(), i, true); + } + } + } + + memset(&item, 0, sizeof(LVITEM)); + item.stateMask = 0; + item.mask = LVIF_TEXT; + if (table_->type() == ICON_AND_TEXT) + item.mask |= LVIF_IMAGE; + + for (size_t j = start_column; j < table_->GetVisibleColumnCount(); ++j) { + TableColumn& col = table_->GetVisibleColumnAt(j); + int max_text_width = ListView_GetStringWidth(native_view(), + col.title.c_str()); + for (int i = start; i < max_row; ++i) { + item.iItem = i; + item.iSubItem = j; + std::wstring text = table_->model()->GetText(i, col.id); + item.pszText = const_cast<LPWSTR>(text.c_str()); + item.iImage = 0; + ListView_SetItem(native_view(), &item); + + // Compute width in px, using current font. + int string_width = ListView_GetStringWidth(native_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 == table_->model()->RowCount())) { + col.min_visible_width = max_text_width; + } + } +} + +void NativeTableWin::UpdateContentOffset() { + content_offset_ = 0; + + if (!native_view()) + return; + + HWND header = ListView_GetHeader(native_view()); + if (!header) + return; + + POINT origin = {0, 0}; + MapWindowPoints(header, native_view(), &origin, 1); + + RECT header_bounds; + GetWindowRect(header, &header_bounds); + + content_offset_ = origin.y + header_bounds.bottom - header_bounds.top; +} + +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 NativeTableWin::TableWndProc(HWND window, + UINT message, + WPARAM w_param, + LPARAM l_param) { + NativeTableWin* native_table_win = reinterpret_cast<NativeTableWin*>( + GetWindowLongPtr(window, GWLP_USERDATA)); + TableView2* table = native_table_win->table_; + + // 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->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); + // TODO(jcampan): fix code below + // if (view_index != -1 && table->IsItemSelected(view_index)) + // table->Select(view_index); + // table->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_KEYDOWN: { + if (!table->single_selection() && w_param == 'A' && + GetKeyState(VK_CONTROL) < 0 && table->model()->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->observer()) { + table->observer()->OnTableView2Delete(table); + return 0; + } + // else case: fall through to default processing. + break; + } + + case WM_LBUTTONDBLCLK: { + if (w_param == MK_LBUTTON) + native_table_win->OnDoubleClick(); + return 0; + } + + case WM_MBUTTONDOWN: { + if (w_param == MK_MBUTTON) { + int view_index = GetViewIndexFromMouseEvent(window, l_param); + if (view_index != -1) { + // Clear all and select the row that was middle clicked. + native_table_win->Select(view_index); + native_table_win->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) + native_table_win->Select(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) { + native_table_win->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); + bool select = true; + if (w_param & MK_CONTROL) { + select = false; + if (!native_table_win->IsRowSelected(view_index)) { + if (table->single_selection()) { + // Single selection mode and the row isn't selected, select + // only it. + native_table_win->Select(view_index); + } else { + // Not single selection, add this row to the selection. + native_table_win->SetSelectedState(view_index, true); + } + } else { + // Remove this row from the selection. + native_table_win->SetSelectedState(view_index, false); + } + ListView_SetSelectionMark(window, view_index); + } else if (!table->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) { + native_table_win->SetSelectedState(i, true); + } + } + } + // Make the row the user clicked on the focused row. + ListView_SetItemState(window, view_index, LVIS_FOCUSED, + LVIS_FOCUSED); + if (select) { + if (!native_table_win->IsRowSelected(view_index)) { + // Clear all. + ListView_SetItemState(window, -1, 0, LVIS_SELECTED); + // And select the row the user clicked on. + native_table_win->SetSelectedState(view_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); + } + native_table_win->ignore_listview_change_ = false; + table->observer()->OnSelectionChanged(); + 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(native_table_win->original_handler_); + return CallWindowProc(native_table_win->original_handler_, window, message, + w_param, l_param); +} + +LRESULT CALLBACK NativeTableWin::TableHeaderWndProc(HWND window, UINT message, + WPARAM w_param, + LPARAM l_param) { + NativeTableWin* native_table_win = reinterpret_cast<NativeTableWin*>( + GetWindowLongPtr(window, GWLP_USERDATA)); + + switch (message) { + case WM_SETCURSOR: + if (!native_table_win->table_->resizable_columns()) + // Prevents the cursor from changing to the resize cursor. + return TRUE; + break; + // TODO(jcampan): we should also block single click messages on the + // separator as right now columns can still be resized. + case WM_LBUTTONDBLCLK: + if (!native_table_win->table_->resizable_columns()) + // Prevents the double-click on the column separator from auto-resizing + // the column. + return TRUE; + break; + default: + break; + } + DCHECK(native_table_win->header_original_handler_); + return CallWindowProc(native_table_win->header_original_handler_, + window, message, w_param, l_param); +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeButtonWrapper, public: + +// static +NativeTableWrapper* NativeTableWrapper::CreateNativeWrapper(TableView2* table) { + return new NativeTableWin(table); +} + +} // namespace views diff --git a/views/controls/table/native_table_win.h b/views/controls/table/native_table_win.h new file mode 100644 index 0000000..6b6c8ef --- /dev/null +++ b/views/controls/table/native_table_win.h @@ -0,0 +1,130 @@ +// Copyright (c) 2009 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. + +#ifndef VIEWS_CONTROLS_TABLE_NATIVE_TABLE_WIN_H_ +#define VIEWS_CONTROLS_TABLE_NATIVE_TABLE_WIN_H_ + +#include <windows.h> + +#include "app/table_model.h" +#include "views/controls/native_control_win.h" +#include "views/controls/table/native_table_wrapper.h" + +typedef struct tagNMLVCUSTOMDRAW NMLVCUSTOMDRAW; + +namespace views { + +class TableView2; + +// A View that hosts a native Windows table. +class NativeTableWin : public NativeControlWin, public NativeTableWrapper { + public: + explicit NativeTableWin(TableView2* table); + virtual ~NativeTableWin(); + + // NativeTableWrapper implementation: + virtual int GetRowCount(); + virtual View* GetView(); + virtual void SetFocus(); + virtual gfx::NativeView GetTestingHandle() const; + virtual void InsertColumn(const TableColumn& column, int index); + virtual void RemoveColumn(int index); + virtual int GetColumnWidth(int column_index); + virtual void SetColumnWidth(int column_index, int width); + virtual int GetSelectedRowCount(); + virtual int GetFirstSelectedRow(); + virtual int GetFirstFocusedRow(); + virtual void ClearSelection(); + virtual void ClearRowFocus(); + virtual void SetSelectedState(int model_row, bool state); + virtual void SetFocusState(int model_row, bool state); + virtual bool IsRowSelected(int model_row); + virtual bool IsRowFocused(int model_row); + virtual void OnRowsChanged(int start, int length); + virtual void OnRowsAdded(int start, int length); + virtual void OnRowsRemoved(int start, int length); + virtual gfx::Rect GetBounds(); + + // Overridden from View: + virtual gfx::Size GetPreferredSize(); + + // Overridden from NativeControlWin: + virtual bool ProcessMessage(UINT message, + WPARAM w_param, + LPARAM l_param, + LRESULT* result); + + protected: + virtual void CreateNativeControl(); + + private: + // Makes |model_row| the only selected and focused row. + void Select(int model_row); + + // Notification from the ListView that the selected state of an item has + // changed. + virtual void OnSelectedStateChanged(); + + // Notification from the ListView that the used double clicked the table. + virtual void OnDoubleClick(); + + // Notification from the ListView that the user middle clicked the table. + virtual void OnMiddleClick(); + + // Overridden from NativeControl. Notifies the observer. + virtual bool OnKeyDown(base::KeyboardCode virtual_keycode); + + // Notification from the ListView that the checked state of the item has + // changed. + void OnCheckedStateChanged(int model_row, bool is_checked); + + // Custom drawing of our icons. + LRESULT OnCustomDraw(NMLVCUSTOMDRAW* draw_info); + + void UpdateListViewCache(int start, int length, bool add); + + void UpdateContentOffset(); + + // Window procedure of the list view class. We subclass the list view to + // ignore WM_ERASEBKGND, which gives smoother painting during resizing. + static LRESULT CALLBACK TableWndProc(HWND window, + UINT message, + WPARAM w_param, + LPARAM l_param); + + // Window procedure of the header class. We subclass the header of the table + // to disable resizing of columns. + static LRESULT CALLBACK TableHeaderWndProc(HWND window, + UINT message, + WPARAM w_param, + LPARAM l_param); + + // If true, any events that would normally be propagated to the observer + // are ignored. For example, if this is true and the selection changes in + // the listview, the observer is not notified. + bool ignore_listview_change_; + + // The Table we are bound to. + TableView2* table_; + + // The Y offset from the top of the table to the actual content (passed the + // header if any). + int content_offset_; + + // The list view's header original proc handler. It is required when + // subclassing. + WNDPROC header_original_handler_; + + // Window procedure of the listview before we subclassed it. + WNDPROC original_handler_; + + // Size (width and height) of images. + static const int kImageSize; + + DISALLOW_COPY_AND_ASSIGN(NativeTableWin); +}; + +} // namespace views + +#endif // VIEWS_CONTROLS_TABLE_NATIVE_TABLE_WIN_H_ diff --git a/views/controls/table/native_table_wrapper.h b/views/controls/table/native_table_wrapper.h new file mode 100644 index 0000000..5f635e5 --- /dev/null +++ b/views/controls/table/native_table_wrapper.h @@ -0,0 +1,76 @@ +// Copyright (c) 2009 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. + +#ifndef VIEWS_CONTROLS_TABLE_NATIVE_TABLE_WRAPPER_H_ +#define VIEWS_CONTROLS_TABLE_NATIVE_TABLE_WRAPPER_H_ + +#include "app/gfx/native_widget_types.h" + +namespace views { + +class TableView2; +class View; + +// An interface implemented by an object that provides a platform-native +// table. +class NativeTableWrapper { + public: + // Returns the number of rows in the table. + virtual int GetRowCount() = 0; + + // Inserts/removes a column at the specified index. + virtual void InsertColumn(const TableColumn& column, int index) = 0; + virtual void RemoveColumn(int index) = 0; + + // Returns the number of rows that are selected. + virtual int GetSelectedRowCount() = 0; + + // Returns the first row that is selected/focused in terms of the model. + virtual int GetFirstSelectedRow() = 0; + virtual int GetFirstFocusedRow() = 0; + + // Unselect all rows. + virtual void ClearSelection() = 0; + + virtual void ClearRowFocus() = 0; + + virtual void SetSelectedState(int model_row, bool state) = 0; + + virtual void SetFocusState(int model_row, bool state) = 0; + + // Returns true if the row at the specified index is selected. + virtual bool IsRowSelected(int model_row) = 0; + + // Returns true if the row at the specified index has the focus. + virtual bool IsRowFocused(int model_row) = 0; + + // Retrieves the views::View that hosts the native control. + virtual View* GetView() = 0; + + // Sets the focus to the table. + virtual void SetFocus() = 0; + + // Returns a handle to the underlying native view for testing. + virtual gfx::NativeView GetTestingHandle() const = 0; + + // Gets/sets the columns width. + virtual int GetColumnWidth(int column_index) = 0; + virtual void SetColumnWidth(int column_index, int width) = 0; + + // Called by the table view to indicate that some rows have changed, been + // added or been removed. + virtual void OnRowsChanged(int start, int length) = 0; + virtual void OnRowsAdded(int start, int length) = 0; + virtual void OnRowsRemoved(int start, int length) = 0; + + // Returns the bounds of the native table. + virtual gfx::Rect GetBounds() = 0; + + // Creates an appropriate NativeButtonWrapper for the platform. + static NativeTableWrapper* CreateNativeWrapper(TableView2* table); +}; + +} // namespace views + +#endif // VIEWS_CONTROLS_TABLE_NATIVE_TABLE_WRAPPER_H_ diff --git a/views/controls/table/table_view2.cc b/views/controls/table/table_view2.cc new file mode 100644 index 0000000..36ba072 --- /dev/null +++ b/views/controls/table/table_view2.cc @@ -0,0 +1,338 @@ + +// Copyright (c) 2006-2008 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_view2.h" + +#include "app/table_model.h" +#include "views/controls/table/table_view_observer.h" + +namespace views { + +// TableView2 ------------------------------------------------------------------ + +TableView2::TableView2(TableModel* model, + const std::vector<TableColumn>& columns, + TableTypes table_type, + bool single_selection, + bool resizable_columns, + bool autosize_columns) + : model_(model), + table_type_(table_type), + table_view_observer_(NULL), + visible_columns_(), + all_columns_(), + column_count_(static_cast<int>(columns.size())), + single_selection_(single_selection), + resizable_columns_(resizable_columns), + autosize_columns_(autosize_columns), + native_wrapper_(NULL) { + for (std::vector<TableColumn>::const_iterator i = columns.begin(); + i != columns.end(); ++i) { + AddColumn(*i); + visible_columns_.push_back(i->id); + } +} + +TableView2::~TableView2() { + if (model_) + model_->SetObserver(NULL); +} + +void TableView2::SetModel(TableModel* model) { + if (model == model_) + return; + + if (model_) + model_->SetObserver(NULL); + model_ = model; + if (model_) + model_->SetObserver(this); + if (native_wrapper_) + OnModelChanged(); +} + +int TableView2::GetRowCount() { + if (!native_wrapper_) + return 0; + return native_wrapper_->GetRowCount(); +} + +int TableView2::SelectedRowCount() { + if (!native_wrapper_) + return 0; + return native_wrapper_->GetSelectedRowCount(); +} + +void TableView2::ClearSelection() { + if (native_wrapper_) + native_wrapper_->ClearSelection(); +} + +void TableView2::ClearRowFocus() { + if (native_wrapper_) + native_wrapper_->ClearRowFocus(); +} + +int TableView2::GetFirstSelectedRow() { + if (!native_wrapper_) + return -1; + return native_wrapper_->GetFirstSelectedRow(); +} + +int TableView2::GetFirstFocusedRow() { + if (!native_wrapper_) + return -1; + return native_wrapper_->GetFirstFocusedRow(); +} + +void TableView2::SelectRow(int model_row) { + if (!native_wrapper_) + return; + + native_wrapper_->ClearSelection(); + native_wrapper_->SetSelectedState(model_row, true); + if (table_view_observer_) + table_view_observer_->OnSelectionChanged(); +} + +void TableView2::FocusRow(int model_row) { + if (!native_wrapper_) + return; + + native_wrapper_->SetFocusState(model_row, true); +} + +bool TableView2::IsRowSelected(int model_row) { + if (!native_wrapper_) + return false; + + return native_wrapper_->IsRowSelected(model_row); +} + +bool TableView2::IsRowFocused(int model_row) { + if (!native_wrapper_) + return false; + + return native_wrapper_->IsRowFocused(model_row); +} + +void TableView2::OnModelChanged() { + if (!native_wrapper_) + return; + + int current_row_count = native_wrapper_->GetRowCount(); + if (current_row_count > 0) + OnItemsRemoved(0, current_row_count); + if (model_ && model_->RowCount()) + OnItemsAdded(0, model_->RowCount()); +} + +void TableView2::OnItemsChanged(int start, int length) { + if (!native_wrapper_) + return; + + if (length == -1) { + DCHECK_GE(start, 0); + length = model_->RowCount() - start; + } + native_wrapper_->OnRowsChanged(start, length); +} + +void TableView2::OnItemsAdded(int start, int length) { + if (!native_wrapper_) + return; + + DCHECK(start >= 0 && length >= 0 && start <= native_wrapper_->GetRowCount()); + + native_wrapper_->OnRowsAdded(start, length); +} + +void TableView2::OnItemsRemoved(int start, int length) { + if (!native_wrapper_) + return; + + DCHECK(start >= 0 && length >= 0 && + start + length <= native_wrapper_->GetRowCount()); + + native_wrapper_->OnRowsRemoved(start, length); +} + +void TableView2::AddColumn(const TableColumn& col) { + DCHECK_EQ(0U, all_columns_.count(col.id)); + all_columns_[col.id] = col; +} + +void TableView2::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); + } +} + +void TableView2::OnColumnsChanged() { + column_count_ = static_cast<int>(visible_columns_.size()); + ResetColumnSizes(); +} + +bool TableView2::HasColumn(int id) { + return all_columns_.count(id) > 0; +} + +void TableView2::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 (native_wrapper_) + native_wrapper_->RemoveColumn(index); + visible_columns_.erase(i); + changed = true; + break; + } + } + } + if (is_visible) { + DCHECK(native_wrapper_); + visible_columns_.push_back(id); + TableColumn& column = all_columns_[id]; + native_wrapper_->InsertColumn(column, column_count_); + changed = true; + } + if (changed) + OnColumnsChanged(); + +} + +bool TableView2::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; +} + +void TableView2::ResetColumnSizes() { + if (!native_wrapper_) + return; + + // See comment in TableColumn for what this does. + int width = this->width(); + gfx::Rect native_bounds = native_wrapper_->GetBounds(); + if (!native_bounds.IsEmpty()) { + if (native_bounds.width() > 0) { + // Prefer the bounds of the window over our bounds, which may be + // different. + width = native_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 += native_wrapper_->GetColumnWidth(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; + native_wrapper_->SetColumnWidth(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; + } + native_wrapper_->SetColumnWidth(col_index, col_width); + } + } + } +} + +void TableView2::DidChangeBounds(const gfx::Rect& previous, + const gfx::Rect& current) { + Layout(); +} + +void TableView2::Layout() { + if (native_wrapper_) { + native_wrapper_->GetView()->SetBounds(0, 0, width(), height()); + native_wrapper_->GetView()->Layout(); + } +} + +size_t TableView2::GetVisibleColumnCount() { + return visible_columns_.size(); +} + +void TableView2::ViewHierarchyChanged(bool is_add, View* parent, View* child) { + if (is_add && !native_wrapper_ && GetWidget()) { + // The native wrapper's lifetime will be managed by the view hierarchy after + // we call AddChildView. + native_wrapper_ = CreateWrapper(); + AddChildView(native_wrapper_->GetView()); + } +} + +gfx::NativeView TableView2::GetTestingHandle() { + return native_wrapper_->GetTestingHandle(); +} + +TableColumn TableView2::GetVisibleColumnAt(int index) { + DCHECK(index < static_cast<int>(visible_columns_.size())); + std::map<int, TableColumn>::iterator iter = + all_columns_.find(index); + DCHECK(iter != all_columns_.end()); + return iter->second; +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeTable2, protected: + +NativeTableWrapper* TableView2::CreateWrapper() { + return NativeTableWrapper::CreateNativeWrapper(this); +} + +} // namespace views diff --git a/views/controls/table/table_view2.h b/views/controls/table/table_view2.h new file mode 100644 index 0000000..3c8e821 --- /dev/null +++ b/views/controls/table/table_view2.h @@ -0,0 +1,238 @@ +// Copyright (c) 2006-2008 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. + +#ifndef VIEWS_CONTROLS_TABLE_TABLE_VIEW2_H_ +#define VIEWS_CONTROLS_TABLE_TABLE_VIEW2_H_ + +#include "build/build_config.h" + +#include <map> +#include <vector> + +#include "app/table_model_observer.h" +#include "base/gfx/rect.h" +#include "base/scoped_ptr.h" +#include "views/controls/table/table_view.h" +#include "views/controls/table/native_table_wrapper.h" +#include "views/view.h" +#include "third_party/skia/include/core/SkColor.h" + +struct TableColumn; +class TableModel; +class SkBitmap; + +// A TableView2 is a view that displays multiple rows with any number of +// columns. +// TableView is driven by a TableModel. The model returns the contents +// to display. TableModel also has an Observer which is used to notify +// TableView of changes to the model so that the display may be updated +// appropriately. +// +// TableView2 itself has an observer that is notified when the selection +// changes. +// +// TableView2 is the current port of TableView to use a NativeControl for +// portability. +// +// TODO(jcampan): add sorting. +// TODO(jcampan): add group support. + +namespace views { + +class ListView; +class ListViewParent; +class TableView; +class TableViewObserver; +class View; + +class TableView2 : public View, public TableModelObserver { + public: + typedef TableSelectionIterator iterator; + + // A helper struct for GetCellColors. Set |color_is_set| to true if color is + // set. See OnCustomDraw for more details on why we need this. + struct ItemColor { + bool color_is_set; + SkColor color; + }; + + // Creates a new table using the model and columns specified. + // The table type applies to the content of the first column (text, icon and + // text, checkbox and text). + // When autosize_columns is true, columns always fill the available width. If + // false, columns are not resized when the table is resized. An extra empty + // column at the right fills the remaining space. + // When resizable_columns is true, users can resize columns by dragging the + // separator on the column header. NOTE: Right now this is always true. The + // code to set it false is still in place to be a base for future, better + // resizing behavior (see http://b/issue?id=874646 ), but no one uses or + // tests the case where this flag is false. + // Note that setting both resizable_columns and autosize_columns to false is + // probably not a good idea, as there is no way for the user to increase a + // column's size in that case. + TableView2(TableModel* model, const std::vector<TableColumn>& columns, + TableTypes table_type, bool single_selection, + bool resizable_columns, bool autosize_columns); + virtual ~TableView2(); + + // Assigns a new model to the table view, detaching the old one if present. + // If |model| is NULL, the table view cannot be used after this call. This + // should be called in the containing view's destructor to avoid destruction + // issues when the model needs to be deleted before the table. + void SetModel(TableModel* model); + TableModel* model() const { return model_; } + + // Returns the number of rows in the table. + int GetRowCount(); + + // Returns the number of selected rows. + int SelectedRowCount(); + + // Makes all row not selected. + void ClearSelection(); + + // Makes all row not focused. + void ClearRowFocus(); + + // Returns the index of the first selected row. + int GetFirstSelectedRow(); + + // Returns the index of the first focused row. + int GetFirstFocusedRow(); + + // Selects the specified row, making sure it's visible. + void SelectRow(int model_row); + + // Sets the focus to the row at the given index. + void FocusRow(int model_row); + + // Returns true if the row at the specified index is selected. + bool IsRowSelected(int model_row); + + // Returns true if the row at the specified index has the focus. + bool IsRowFocused(int model_row); + + // Returns an iterator over the selection. The iterator proceeds from the + // last index to the first. + // + // NOTE: the iterator iterates over the visual order (but returns coordinates + // in terms of the model). + iterator SelectionBegin(); + iterator SelectionEnd(); + + // TableModelObserver methods. + virtual void OnModelChanged(); + virtual void OnItemsChanged(int start, int length); + virtual void OnItemsAdded(int start, int length); + virtual void OnItemsRemoved(int start, int length); + + void SetObserver(TableViewObserver* observer) { + table_view_observer_ = observer; + } + TableViewObserver* observer() const { return table_view_observer_; } + + // Replaces the set of known columns without changing the current visible + // columns. + void SetColumns(const std::vector<TableColumn>& columns); + void AddColumn(const TableColumn& col); + bool HasColumn(int id); + + // Sets which columns (by id) are displayed. All transient size and position + // information is lost. + void SetVisibleColumns(const std::vector<int>& columns); + void SetColumnVisibility(int id, bool is_visible); + bool IsColumnVisible(int id) const; + + TableColumn GetVisibleColumnAt(int index); + size_t GetVisibleColumnCount(); + + // Resets the size of the columns based on the sizes passed to the + // constructor. Your normally needn't invoked this, it's done for you the + // first time the TableView is given a valid size. + void ResetColumnSizes(); + + bool single_selection() const { + return single_selection_; + } + + TableTypes type() const { + return table_type_; + } + + bool resizable_columns() const { + return resizable_columns_; + } + + bool autosize_columns() const { + return autosize_columns_; + } + + virtual void DidChangeBounds(const gfx::Rect& previous, + const gfx::Rect& current); + virtual void Layout(); + + // Used by tests. + virtual gfx::NativeView GetTestingHandle(); + + protected: + virtual NativeTableWrapper* CreateWrapper(); + virtual void ViewHierarchyChanged(bool is_add, View* parent, View* child); + + private: + // We need this wrapper to pass the table view to the windows proc handler + // when subclassing the list view and list view header, as the reinterpret + // cast from GetWindowLongPtr would break the pointer if it is pointing to a + // subclass (in the OO sense of TableView). + struct TableViewWrapper { + explicit TableViewWrapper(TableView2* view) : table_view(view) { } + TableView2* table_view; + }; + + friend class ListViewParent; + friend class TableSelectionIterator; + + // Adds a new column. + void InsertColumn(const TableColumn& tc, int index); + + // Update headers and internal state after columns have changed + void OnColumnsChanged(); + + TableModel* model_; + TableTypes table_type_; + TableViewObserver* table_view_observer_; + + // An ordered list of id's into all_columns_ representing current visible + // columns. + std::vector<int> visible_columns_; + + // Mapping of an int id to a TableColumn representing all possible columns. + std::map<int, TableColumn> all_columns_; + + // Cached value of columns_.size() + int column_count_; + + // Selection mode. + bool single_selection_; + + // Whether or not the user can resize columns. + bool resizable_columns_; + + // Whether or not columns should automatically be resized to fill the + // the available width when the list view is resized. + bool autosize_columns_; + + // Mappings used when sorted. + // scoped_array<int> view_to_model_; + // scoped_array<int> model_to_view_; + + // The object that actually implements the table. + NativeTableWrapper* native_wrapper_; + + DISALLOW_COPY_AND_ASSIGN(TableView2); +}; + +} // namespace views + +#endif // VIEWS_CONTROLS_TABLE_TABLE_VIEW2_H_ + diff --git a/views/controls/table/table_view_observer.h b/views/controls/table/table_view_observer.h index f4cacbf..a86bb8e 100644 --- a/views/controls/table/table_view_observer.h +++ b/views/controls/table/table_view_observer.h @@ -10,6 +10,7 @@ namespace views { class TableView; +class TableView2; // TableViewObserver is notified about the TableView selection. class TableViewObserver { @@ -30,6 +31,9 @@ class TableViewObserver { // Invoked when the user presses the delete key. virtual void OnTableViewDelete(TableView* table_view) {} + + // Invoked when the user presses the delete key. + virtual void OnTableView2Delete(TableView2* table_view) {} }; } // namespace views diff --git a/views/controls/table/table_view_unittest.cc b/views/controls/table/table_view_unittest.cc index d79a5bc..7ced10c 100644 --- a/views/controls/table/table_view_unittest.cc +++ b/views/controls/table/table_view_unittest.cc @@ -10,6 +10,7 @@ #include "base/string_util.h" #include "testing/gtest/include/gtest/gtest.h" #include "views/controls/table/table_view.h" +#include "views/controls/table/table_view2.h" #include "views/window/window_delegate.h" #include "views/window/window_win.h" @@ -29,6 +30,11 @@ class TestTableModel : public TableModel { public: TestTableModel(); + struct CheckNotification { + int row; + bool state; + }; + // Adds a new row at index |row| with values |c1_value| and |c2_value|. void AddRow(int row, int c1_value, int c2_value); @@ -43,6 +49,11 @@ class TestTableModel : public TableModel { virtual std::wstring GetText(int row, int column_id); virtual void SetObserver(TableModelObserver* observer); virtual int CompareValues(int row1, int row2, int column_id); + virtual bool IsChecked(int row); + virtual void SetChecked(int row, bool is_checked); + + // Contains a record of the SetChecked calls. + std::vector<CheckNotification> check_notifications_; private: TableModelObserver* observer_; @@ -99,6 +110,16 @@ int TestTableModel::CompareValues(int row1, int row2, int column_id) { return rows_[row1][column_id] - rows_[row2][column_id]; } +bool TestTableModel::IsChecked(int row) { + // Let's make the first row the only checked one. + return (row == 1); +} + +void TestTableModel::SetChecked(int row, bool is_checked) { + CheckNotification check_notification = { row, is_checked }; + check_notifications_.push_back(check_notification); +} + // TableViewTest --------------------------------------------------------------- class TableViewTest : public testing::Test, views::WindowDelegate { @@ -149,9 +170,8 @@ void TableViewTest::SetUp() { table_ = new TableView(model_.get(), columns, views::ICON_AND_TEXT, false, false, false); window_ = - views::Window::CreateChromeWindow(NULL, - gfx::Rect(100, 100, 512, 512), - this); + views::Window::CreateChromeWindow(NULL, gfx::Rect(100, 100, 512, 512), + this); } void TableViewTest::TearDown() { @@ -381,3 +401,190 @@ TEST_F(NullModelTableViewTest, NullModel) { // There's nothing explicit to test. If there is a bug in TableView relating // to a NULL model we'll crash. } + +//////////////////////////////////////////////////////////////////////////////// +// TableView2 Tests + +class TableView2Test : public testing::Test, views::WindowDelegate { + public: + virtual void SetUp(); + virtual void TearDown(); + + virtual views::View* GetContentsView() { + return table_; + } + + // Returns the contents of a cell in the table. + std::wstring GetCellValue(int row, int column); + + protected: + // Creates the model. + TestTableModel* CreateModel(); + + virtual views::TableTypes GetTableType() { + return views::TEXT_ONLY; + } + + scoped_ptr<TestTableModel> model_; + + // The table. This is owned by the window. + views::TableView2* table_; + + private: + MessageLoopForUI message_loop_; + views::Window* window_; +}; + +void TableView2Test::SetUp() { + OleInitialize(NULL); + model_.reset(CreateModel()); + std::vector<TableColumn> columns; + columns.resize(2); + columns[0].id = 0; + columns[1].id = 1; + table_ = new views::TableView2(model_.get(), columns, GetTableType(), + false, false, false); + window_ = views::Window::CreateChromeWindow(NULL, + gfx::Rect(100, 100, 512, 512), + this); + window_->Show(); +} + +void TableView2Test::TearDown() { + window_->Close(); + // Temporary workaround to avoid leak of RootView::pending_paint_task_. + message_loop_.RunAllPending(); + OleUninitialize(); +} + +TestTableModel* TableView2Test::CreateModel() { + return new TestTableModel(); +} + +std::wstring TableView2Test::GetCellValue(int row, int column) { +#if defined(OS_WIN) + wchar_t str[128] = {0}; + LVITEM item = {0}; + item.mask = LVIF_TEXT; + item.iItem = row; + item.iSubItem = column; + item.pszText = str; + item.cchTextMax = 128; + BOOL r = ListView_GetItem(table_->GetTestingHandle(), &item); + DCHECK(r); + return std::wstring(str); +#else + NOTIMPLEMENTED(); +#endif +} + +// Tests that the table correctly reflects changes to the model. +TEST_F(TableView2Test, ModelChangesTest) { + EXPECT_EQ(3, table_->GetRowCount()); + EXPECT_EQ(L"0", GetCellValue(0, 0)); + EXPECT_EQ(L"1", GetCellValue(1, 0)); + EXPECT_EQ(L"2", GetCellValue(2, 1)); + + // Test adding rows and that OnItemsAdded works. + model_->AddRow(3, 3, 3); + model_->AddRow(4, 4, 4); + table_->OnItemsAdded(3, 2); + EXPECT_EQ(5, table_->GetRowCount()); + EXPECT_EQ(L"3", GetCellValue(3, 0)); + EXPECT_EQ(L"4", GetCellValue(4, 1)); + + // Test removing rows and that OnItemsRemoved works. + model_->RemoveRow(1); + model_->RemoveRow(1); + table_->OnItemsRemoved(1, 2); + EXPECT_EQ(3, table_->GetRowCount()); + EXPECT_EQ(L"0", GetCellValue(0, 0)); + EXPECT_EQ(L"3", GetCellValue(1, 0)); + EXPECT_EQ(L"4", GetCellValue(2, 1)); + + // Test changing rows and that OnItemsChanged works. + model_->ChangeRow(1, 1, 1); + model_->ChangeRow(2, 2, 2); + table_->OnItemsChanged(1, 2); + EXPECT_EQ(L"0", GetCellValue(0, 0)); + EXPECT_EQ(L"1", GetCellValue(1, 0)); + EXPECT_EQ(L"2", GetCellValue(2, 1)); + + // Test adding and removing rows and using OnModelChanged. + model_->RemoveRow(2); + model_->AddRow(2, 5, 5); + model_->AddRow(3, 6, 6); + table_->OnModelChanged(); + EXPECT_EQ(4, table_->GetRowCount()); + EXPECT_EQ(L"0", GetCellValue(0, 0)); + EXPECT_EQ(L"1", GetCellValue(1, 0)); + EXPECT_EQ(L"5", GetCellValue(2, 1)); + EXPECT_EQ(L"6", GetCellValue(3, 1)); +} + +// Test the selection on a single-selection table. +TEST_F(TableView2Test, SingleSelectionTest) { + EXPECT_EQ(0, table_->SelectedRowCount()); + EXPECT_EQ(-1, table_->GetFirstSelectedRow()); + + table_->SelectRow(0); + EXPECT_EQ(1, table_->SelectedRowCount()); + EXPECT_EQ(0, table_->GetFirstSelectedRow()); + + table_->SelectRow(2); + EXPECT_EQ(1, table_->SelectedRowCount()); + EXPECT_EQ(2, table_->GetFirstSelectedRow()); + + table_->ClearSelection(); + EXPECT_EQ(0, table_->SelectedRowCount()); + EXPECT_EQ(-1, table_->GetFirstSelectedRow()); +} + +// Test the row focus on a single-selection table. +TEST_F(TableView2Test, RowFocusTest) { + EXPECT_EQ(-1, table_->GetFirstFocusedRow()); + + table_->FocusRow(0); + EXPECT_EQ(0, table_->GetFirstFocusedRow()); + + table_->FocusRow(2); + EXPECT_EQ(2, table_->GetFirstFocusedRow()); + + table_->ClearRowFocus(); + EXPECT_EQ(-1, table_->GetFirstSelectedRow()); +} + +class CheckTableView2Test : public TableView2Test { + protected: + virtual views::TableTypes GetTableType() { + return views::CHECK_BOX_AND_TEXT; + } + + // Sets the row check state natively. + void SetRowCheckState(int row, bool state) { +#if defined(OS_WIN) + ListView_SetCheckState(table_->GetTestingHandle(), row, state); +#else + NOTIMPLEMENTED(); +#endif + } +}; + +TEST_F(CheckTableView2Test, TestCheckTable) { + // Test that we were notified of the initial check states. + ASSERT_EQ(1U, model_->check_notifications_.size()); + EXPECT_EQ(1, model_->check_notifications_[0].row); + + // Test that we get the notification correctly. + model_->check_notifications_.clear(); + SetRowCheckState(1, false); + SetRowCheckState(0, true); + SetRowCheckState(0, false); + ASSERT_LE(3U, model_->check_notifications_.size()); + EXPECT_EQ(1, model_->check_notifications_[0].row); + EXPECT_FALSE(model_->check_notifications_[0].state); + EXPECT_EQ(0, model_->check_notifications_[1].row); + EXPECT_TRUE(model_->check_notifications_[1].state); + EXPECT_EQ(0, model_->check_notifications_[2].row); + EXPECT_FALSE(model_->check_notifications_[2].state); +} diff --git a/views/examples/examples_main.cc b/views/examples/examples_main.cc index d061871..4750c89 100644 --- a/views/examples/examples_main.cc +++ b/views/examples/examples_main.cc @@ -17,6 +17,8 @@ #include "views/examples/radio_button_example.h" #include "views/examples/scroll_view_example.h" #include "views/examples/tabbed_pane_example.h" +#include "views/examples/table_example.h" +#include "views/examples/table2_example.h" #include "views/examples/textfield_example.h" #include "views/focus/accelerator_handler.h" #include "views/grid_layout.h" @@ -28,6 +30,10 @@ views::View* ExamplesMain::GetContentsView() { return contents_; } +void ExamplesMain::WindowClosing() { + MessageLoopForUI::current()->Quit(); +} + void ExamplesMain::SetStatus(const std::wstring& status) { status_label_->SetText(status); } @@ -99,6 +105,14 @@ void ExamplesMain::Run() { tabbed_pane->AddTab(scroll_view_example.GetExampleTitle(), scroll_view_example.GetExampleView()); + examples::TableExample table_example(this); + tabbed_pane->AddTab(table_example.GetExampleTitle(), + table_example.GetExampleView()); + + examples::Table2Example table2_example(this); + tabbed_pane->AddTab(table2_example.GetExampleTitle(), + table2_example.GetExampleView()); + window->Show(); views::AcceleratorHandler accelerator_handler; MessageLoopForUI::current()->Run(&accelerator_handler); diff --git a/views/examples/examples_main.h b/views/examples/examples_main.h index c1d4a2b7..2b8366e 100644 --- a/views/examples/examples_main.h +++ b/views/examples/examples_main.h @@ -24,7 +24,9 @@ class ExamplesMain : public views::WindowDelegate { virtual ~ExamplesMain() {} // views::WindowDelegate implementation: + virtual bool CanResize() const { return true; } virtual views::View* GetContentsView(); + virtual void WindowClosing(); // Prints a message in the status area, at the bottom of the window. void SetStatus(const std::wstring& status); diff --git a/views/examples/table2_example.h b/views/examples/table2_example.h new file mode 100644 index 0000000..fc8f900 --- /dev/null +++ b/views/examples/table2_example.h @@ -0,0 +1,168 @@ +// Copyright (c) 2009 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. + +#ifndef VIEWS_EXAMPLES_TABLE2_EXAMPLE_H_ +#define VIEWS_EXAMPLES_TABLE2_EXAMPLE_H_ + +#include <vector> + +#include "app/table_model.h" +#include "base/string_util.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "views/controls/button/checkbox.h" +#include "views/controls/table/table_view2.h" +#include "views/examples/example_base.h" + +namespace examples { + +class Table2Example + : public ExampleBase, + public TableModel, + public views::ButtonListener, + public views::TableViewObserver { + public: + explicit Table2Example(ExamplesMain* main) : ExampleBase(main) { + } + + virtual ~Table2Example() {} + + virtual std::wstring GetExampleTitle() { + return L"Table2"; + } + + virtual void CreateExampleView(views::View* container) { + column1_visible_checkbox_ = new views::Checkbox(L"Fruit column visible"); + column1_visible_checkbox_->SetChecked(true); + column1_visible_checkbox_->set_listener(this); + column2_visible_checkbox_ = new views::Checkbox(L"Color column visible"); + column2_visible_checkbox_->SetChecked(true); + column2_visible_checkbox_->set_listener(this); + column3_visible_checkbox_ = new views::Checkbox(L"Origin column visible"); + column3_visible_checkbox_->SetChecked(true); + column3_visible_checkbox_->set_listener(this); + column4_visible_checkbox_ = new views::Checkbox(L"Price column visible"); + column4_visible_checkbox_->SetChecked(true); + column4_visible_checkbox_->set_listener(this); + + views::GridLayout* layout = new views::GridLayout(container); + container->SetLayoutManager(layout); + + std::vector<TableColumn> columns; + columns.push_back(TableColumn(0, L"Fruit", TableColumn::LEFT, 100)); + columns.push_back(TableColumn(1, L"Color", TableColumn::LEFT, 100)); + columns.push_back(TableColumn(2, L"Origin", TableColumn::LEFT, 100)); + columns.push_back(TableColumn(3, L"Price", TableColumn::LEFT, 100)); + table_ = new views::TableView2(this, columns, views::ICON_AND_TEXT, + true, true, true); + table_->SetObserver(this); + icon1.setConfig(SkBitmap::kARGB_8888_Config, 16, 16); + icon1.allocPixels(); + SkCanvas canvas1(icon1); + canvas1.drawColor(SK_ColorRED); + + icon2.setConfig(SkBitmap::kARGB_8888_Config, 16, 16); + icon2.allocPixels(); + SkCanvas canvas2(icon2); + canvas2.drawColor(SK_ColorBLUE); + + views::ColumnSet* column_set = layout->AddColumnSet(0); + column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1, + views::GridLayout::USE_PREF, 0, 0); + layout->StartRow(1 /* expand */, 0); + layout->AddView(table_); + + column_set = layout->AddColumnSet(1); + column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, + 0.5f, views::GridLayout::USE_PREF, 0, 0); + column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, + 0.5f, views::GridLayout::USE_PREF, 0, 0); + column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, + 0.5f, views::GridLayout::USE_PREF, 0, 0); + column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, + 0.5f, views::GridLayout::USE_PREF, 0, 0); + + layout->StartRow(0 /* no expand */, 1); + + layout->AddView(column1_visible_checkbox_); + layout->AddView(column2_visible_checkbox_); + layout->AddView(column3_visible_checkbox_); + layout->AddView(column4_visible_checkbox_); + } + + // TableModel implementation: + virtual int RowCount() { + return 10; + } + + virtual std::wstring GetText(int row, int column_id) { + std::wstring cells[5][5] = { + { L"Orange", L"Orange", L"South america", L"$5" }, + { L"Apple", L"Green", L"Canada", L"$3" }, + { L"Blue berries", L"Blue", L"Mexico", L"$10.3" }, + { L"Strawberries", L"Red", L"California", L"$7" }, + { L"Cantaloupe", L"Orange", L"South america", L"$5" }, + }; + return cells[row % 5][column_id]; + } + + virtual SkBitmap GetIcon(int row) { + return row % 2 ? icon1 : icon2; + } + + virtual void SetObserver(TableModelObserver* observer) { + } + + // TableViewObserver implementation: + virtual void OnSelectionChanged() { + PrintStatus(L"Selection changed"); + } + + virtual void OnDoubleClick() {} + + virtual void OnMiddleClick() {} + + virtual void OnKeyDown(base::KeyboardCode virtual_keycode) {} + + virtual void OnTableViewDelete(views::TableView* table_view) {} + + virtual void OnTableView2Delete(views::TableView2* table_view) {} + + // ButtonListener implementation: + virtual void ButtonPressed(views::Button* sender, const views::Event& event) { + int index = 0; + bool show = true; + if (sender == column1_visible_checkbox_) { + index = 0; + show = column1_visible_checkbox_->checked(); + } else if (sender == column2_visible_checkbox_) { + index = 1; + show = column2_visible_checkbox_->checked(); + } else if (sender == column3_visible_checkbox_) { + index = 2; + show = column3_visible_checkbox_->checked(); + } else if (sender == column4_visible_checkbox_) { + index = 3; + show = column4_visible_checkbox_->checked(); + } + table_->SetColumnVisibility(index, show); + } + + private: + // The table to be tested. + views::TableView2* table_; + + views::Checkbox* column1_visible_checkbox_; + views::Checkbox* column2_visible_checkbox_; + views::Checkbox* column3_visible_checkbox_; + views::Checkbox* column4_visible_checkbox_; + + SkBitmap icon1; + SkBitmap icon2; + + DISALLOW_COPY_AND_ASSIGN(Table2Example); +}; + +} // namespace examples + +#endif // VIEWS_EXAMPLES_TABLE2_EXAMPLE_H_ diff --git a/views/examples/table_example.h b/views/examples/table_example.h new file mode 100644 index 0000000..1fcf33f --- /dev/null +++ b/views/examples/table_example.h @@ -0,0 +1,169 @@ +// Copyright (c) 2009 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. + +#ifndef VIEWS_EXAMPLES_TABLE_EXAMPLE_H_ +#define VIEWS_EXAMPLES_TABLE_EXAMPLE_H_ + +#include <vector> + +#include "app/table_model.h" +#include "base/string_util.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "views/controls/table/table_view.h" +#include "views/controls/table/table_view_observer.h" +#include "views/examples/example_base.h" +#include "views/fill_layout.h" + +namespace examples { + +class TableExample + : public ExampleBase, + public TableModel, + public views::ButtonListener, + public views::TableViewObserver { + public: + explicit TableExample(ExamplesMain* main) : ExampleBase(main) { + } + + virtual ~TableExample() {} + + virtual std::wstring GetExampleTitle() { + return L"Table"; + } + + virtual void CreateExampleView(views::View* container) { + column1_visible_checkbox_ = new views::Checkbox(L"Fruit column visible"); + column1_visible_checkbox_->SetChecked(true); + column1_visible_checkbox_->set_listener(this); + column2_visible_checkbox_ = new views::Checkbox(L"Color column visible"); + column2_visible_checkbox_->SetChecked(true); + column2_visible_checkbox_->set_listener(this); + column3_visible_checkbox_ = new views::Checkbox(L"Origin column visible"); + column3_visible_checkbox_->SetChecked(true); + column3_visible_checkbox_->set_listener(this); + column4_visible_checkbox_ = new views::Checkbox(L"Price column visible"); + column4_visible_checkbox_->SetChecked(true); + column4_visible_checkbox_->set_listener(this); + + views::GridLayout* layout = new views::GridLayout(container); + container->SetLayoutManager(layout); + + std::vector<TableColumn> columns; + columns.push_back(TableColumn(0, L"Fruit", TableColumn::LEFT, 100)); + columns.push_back(TableColumn(1, L"Color", TableColumn::LEFT, 100)); + columns.push_back(TableColumn(2, L"Origin", TableColumn::LEFT, 100)); + columns.push_back(TableColumn(3, L"Price", TableColumn::LEFT, 100)); + table_ = new views::TableView(this, columns, views::ICON_AND_TEXT, + true, true, true); + table_->SetObserver(this); + icon1.setConfig(SkBitmap::kARGB_8888_Config, 16, 16); + icon1.allocPixels(); + SkCanvas canvas1(icon1); + canvas1.drawColor(SK_ColorRED); + + icon2.setConfig(SkBitmap::kARGB_8888_Config, 16, 16); + icon2.allocPixels(); + SkCanvas canvas2(icon2); + canvas2.drawColor(SK_ColorBLUE); + + views::ColumnSet* column_set = layout->AddColumnSet(0); + column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1, + views::GridLayout::USE_PREF, 0, 0); + layout->StartRow(1 /* expand */, 0); + layout->AddView(table_); + + column_set = layout->AddColumnSet(1); + column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, + 0.5f, views::GridLayout::USE_PREF, 0, 0); + column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, + 0.5f, views::GridLayout::USE_PREF, 0, 0); + column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, + 0.5f, views::GridLayout::USE_PREF, 0, 0); + column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, + 0.5f, views::GridLayout::USE_PREF, 0, 0); + + layout->StartRow(0 /* no expand */, 1); + + layout->AddView(column1_visible_checkbox_); + layout->AddView(column2_visible_checkbox_); + layout->AddView(column3_visible_checkbox_); + layout->AddView(column4_visible_checkbox_); + } + + // TableModel implementation: + virtual int RowCount() { + return 10; + } + + virtual std::wstring GetText(int row, int column_id) { + std::wstring cells[5][5] = { + { L"Orange", L"Orange", L"South america", L"$5" }, + { L"Apple", L"Green", L"Canada", L"$3" }, + { L"Blue berries", L"Blue", L"Mexico", L"$10.3" }, + { L"Strawberries", L"Red", L"California", L"$7" }, + { L"Cantaloupe", L"Orange", L"South america", L"$5" }, + }; + return cells[row % 5][column_id]; + } + + virtual SkBitmap GetIcon(int row) { + return row % 2 ? icon1 : icon2; + } + + virtual void SetObserver(TableModelObserver* observer) { + } + + // TableViewObserver implementation: + virtual void OnSelectionChanged() { + PrintStatus(L"Selection changed"); + } + + virtual void OnDoubleClick() {} + + virtual void OnMiddleClick() {} + + virtual void OnKeyDown(base::KeyboardCode virtual_keycode) {} + + virtual void OnTableViewDelete(views::TableView* table_view) {} + + virtual void OnTableView2Delete(views::TableView2* table_view) {} + + // ButtonListener implementation: + virtual void ButtonPressed(views::Button* sender, const views::Event& event) { + int index = 0; + bool show = true; + if (sender == column1_visible_checkbox_) { + index = 0; + show = column1_visible_checkbox_->checked(); + } else if (sender == column2_visible_checkbox_) { + index = 1; + show = column2_visible_checkbox_->checked(); + } else if (sender == column3_visible_checkbox_) { + index = 2; + show = column3_visible_checkbox_->checked(); + } else if (sender == column4_visible_checkbox_) { + index = 3; + show = column4_visible_checkbox_->checked(); + } + table_->SetColumnVisibility(index, show); + } + + private: + // The table to be tested. + views::TableView* table_; + + SkBitmap icon1; + SkBitmap icon2; + + views::Checkbox* column1_visible_checkbox_; + views::Checkbox* column2_visible_checkbox_; + views::Checkbox* column3_visible_checkbox_; + views::Checkbox* column4_visible_checkbox_; + + DISALLOW_COPY_AND_ASSIGN(TableExample); +}; + +} // namespace examples + +#endif // VIEWS_EXAMPLES_TABLE_EXAMPLE_H_ diff --git a/views/views.gyp b/views/views.gyp index 4e86d4d..60d89f6 100644 --- a/views/views.gyp +++ b/views/views.gyp @@ -184,10 +184,15 @@ 'controls/tabbed_pane/native_tabbed_pane_win.cc', 'controls/tabbed_pane/native_tabbed_pane_win.h', 'controls/tabbed_pane/native_tabbed_pane_wrapper.h', + 'controls/table/native_table_wrapper.h', + 'controls/table/native_table_win.cc', + 'controls/table/native_table_win.h', 'controls/table/group_table_view.cc', 'controls/table/group_table_view.h', 'controls/table/table_view.cc', 'controls/table/table_view.h', + 'controls/table/table_view2.cc', + 'controls/table/table_view2.h', 'controls/table/table_view_observer.h', 'controls/textfield/textfield.cc', 'controls/textfield/textfield.h', @@ -344,6 +349,8 @@ 'dependencies': [ '../base/base.gyp:base', '../skia/skia.gyp:skia', + '../third_party/icu/icu.gyp:icui18n', + '../third_party/icu/icu.gyp:icuuc', 'views', ], 'include_dirs': [ |