Commit 66744c7c authored by Nektarios Paisios's avatar Nektarios Paisios Committed by Commit Bot

TableView: Adds keyboard and programmatic support for navigating through,...

TableView: Adds keyboard and programmatic support for navigating through, selecting and resizing columns

A followup patch will add AXVirtualView objects for every row, column and table cell, and refine the accessibility actions further.
R=dmazzoni@chromium.org

Change-Id: I0d1d599ef8cc63f3965bbcbc5d01281912707e51
Bug: 101762
Reviewed-on: https://chromium-review.googlesource.com/c/1320974Reviewed-by: default avatarScott Violet <sky@chromium.org>
Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Reviewed-by: default avatarElly Fong-Jones <ellyjones@chromium.org>
Commit-Queue: Nektarios Paisios <nektar@chromium.org>
Cr-Commit-Position: refs/heads/master@{#612885}
parent 8fe6cbf5
...@@ -22,19 +22,22 @@ namespace views { ...@@ -22,19 +22,22 @@ namespace views {
namespace { namespace {
const int kVerticalPadding = 4;
// The minimum width we allow a column to go down to. // The minimum width we allow a column to go down to.
const int kMinColumnWidth = 10; constexpr int kMinColumnWidth = 10;
// Amount that a column is resized when using the keyboard.
constexpr int kResizeKeyboardAmount = 5;
constexpr int kVerticalPadding = 4;
// Distace from edge columns can be resized by. // Distace from edge columns can be resized by.
const int kResizePadding = 5; constexpr int kResizePadding = 5;
// Amount of space above/below the separator. // Amount of space above/below the separator.
const int kSeparatorPadding = 4; constexpr int kSeparatorPadding = 4;
// Size of the sort indicator (doesn't include padding). // Size of the sort indicator (doesn't include padding).
const int kSortIndicatorSize = 8; constexpr int kSortIndicatorSize = 8;
} // namespace } // namespace
...@@ -231,6 +234,29 @@ void TableHeader::OnNativeThemeChanged(const ui::NativeTheme* theme) { ...@@ -231,6 +234,29 @@ void TableHeader::OnNativeThemeChanged(const ui::NativeTheme* theme) {
theme->GetSystemColor(ui::NativeTheme::kColorId_TableHeaderBackground))); theme->GetSystemColor(ui::NativeTheme::kColorId_TableHeaderBackground)));
} }
void TableHeader::ResizeColumnViaKeyboard(
int index,
TableView::AdvanceDirection direction) {
DCHECK_GE(index, 0);
const TableView::VisibleColumn& column = table_->GetVisibleColumn(index);
const int needed_for_title =
gfx::GetStringWidth(column.column.title, font_list_) +
2 * kHorizontalPadding;
int new_width = column.width;
switch (direction) {
case TableView::ADVANCE_INCREMENT:
new_width += kResizeKeyboardAmount;
break;
case TableView::ADVANCE_DECREMENT:
new_width -= kResizeKeyboardAmount;
break;
}
table_->SetVisibleColumnWidth(
index, std::max({kMinColumnWidth, needed_for_title, new_width}));
}
bool TableHeader::StartResize(const ui::LocatedEvent& event) { bool TableHeader::StartResize(const ui::LocatedEvent& event) {
if (is_resizing()) if (is_resizing())
return false; return false;
......
...@@ -7,13 +7,12 @@ ...@@ -7,13 +7,12 @@
#include "base/macros.h" #include "base/macros.h"
#include "ui/gfx/font_list.h" #include "ui/gfx/font_list.h"
#include "ui/views/controls/table/table_view.h"
#include "ui/views/view.h" #include "ui/views/view.h"
#include "ui/views/views_export.h" #include "ui/views/views_export.h"
namespace views { namespace views {
class TableView;
// Views used to render the header for the table. // Views used to render the header for the table.
class VIEWS_EXPORT TableHeader : public views::View { class VIEWS_EXPORT TableHeader : public views::View {
public: public:
...@@ -31,6 +30,9 @@ class VIEWS_EXPORT TableHeader : public views::View { ...@@ -31,6 +30,9 @@ class VIEWS_EXPORT TableHeader : public views::View {
const gfx::FontList& font_list() const { return font_list_; } const gfx::FontList& font_list() const { return font_list_; }
void ResizeColumnViaKeyboard(int index,
TableView::AdvanceDirection direction);
// views::View overrides. // views::View overrides.
void Layout() override; void Layout() override;
void OnPaint(gfx::Canvas* canvas) override; void OnPaint(gfx::Canvas* canvas) override;
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#include "ui/views/controls/table/table_view.h" #include "ui/views/controls/table/table_view.h"
#include <stddef.h> #include <stddef.h>
#include <stdint.h>
#include <algorithm> #include <algorithm>
#include <map> #include <map>
...@@ -12,23 +13,29 @@ ...@@ -12,23 +13,29 @@
#include "base/auto_reset.h" #include "base/auto_reset.h"
#include "base/i18n/rtl.h" #include "base/i18n/rtl.h"
#include "base/strings/string_util.h" #include "base/memory/ptr_util.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "cc/paint/paint_flags.h" #include "cc/paint/paint_flags.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_node_data.h" #include "ui/accessibility/ax_node_data.h"
#include "ui/events/event.h" #include "ui/events/event.h"
#include "ui/gfx/canvas.h" #include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/rect_conversions.h" #include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/image/image_skia.h" #include "ui/gfx/image/image_skia.h"
#include "ui/gfx/skia_util.h" #include "ui/gfx/skia_util.h"
#include "ui/gfx/text_utils.h" #include "ui/gfx/text_utils.h"
#include "ui/native_theme/native_theme.h" #include "ui/native_theme/native_theme.h"
#include "ui/views/accessibility/ax_virtual_view.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/scroll_view.h" #include "ui/views/controls/scroll_view.h"
#include "ui/views/controls/table/table_grouper.h" #include "ui/views/controls/table/table_grouper.h"
#include "ui/views/controls/table/table_header.h" #include "ui/views/controls/table/table_header.h"
#include "ui/views/controls/table/table_utils.h" #include "ui/views/controls/table/table_utils.h"
#include "ui/views/controls/table/table_view_observer.h" #include "ui/views/controls/table/table_view_observer.h"
#include "ui/views/layout/layout_provider.h" #include "ui/views/layout/layout_provider.h"
#include "ui/views/style/platform_style.h"
#include "ui/views/style/typography.h" #include "ui/views/style/typography.h"
namespace views { namespace views {
...@@ -135,6 +142,7 @@ TableView::TableView(ui::TableModel* model, ...@@ -135,6 +142,7 @@ TableView::TableView(ui::TableModel* model,
bool single_selection) bool single_selection)
: model_(NULL), : model_(NULL),
columns_(columns), columns_(columns),
active_visible_column_index_(-1),
header_(NULL), header_(NULL),
table_type_(table_type), table_type_(table_type),
single_selection_(single_selection), single_selection_(single_selection),
...@@ -155,6 +163,7 @@ TableView::TableView(ui::TableModel* model, ...@@ -155,6 +163,7 @@ TableView::TableView(ui::TableModel* model,
visible_column.column = columns[i]; visible_column.column = columns[i];
visible_columns_.push_back(visible_column); visible_columns_.push_back(visible_column);
} }
// Always focusable, even on Mac (consistent with NSTableView). // Always focusable, even on Mac (consistent with NSTableView).
SetFocusBehavior(FocusBehavior::ALWAYS); SetFocusBehavior(FocusBehavior::ALWAYS);
SetModel(model); SetModel(model);
...@@ -220,6 +229,11 @@ void TableView::SetColumnVisibility(int id, bool is_visible) { ...@@ -220,6 +229,11 @@ void TableView::SetColumnVisibility(int id, bool is_visible) {
for (size_t i = 0; i < visible_columns_.size(); ++i) { for (size_t i = 0; i < visible_columns_.size(); ++i) {
if (visible_columns_[i].column.id == id) { if (visible_columns_[i].column.id == id) {
visible_columns_.erase(visible_columns_.begin() + i); visible_columns_.erase(visible_columns_.begin() + i);
if (active_visible_column_index_ >=
static_cast<int>(visible_columns_.size())) {
SetActiveVisibleColumnIndex(
static_cast<int>(visible_columns_.size()) - 1);
}
break; break;
} }
} }
...@@ -385,16 +399,75 @@ bool TableView::OnKeyPressed(const ui::KeyEvent& event) { ...@@ -385,16 +399,75 @@ bool TableView::OnKeyPressed(const ui::KeyEvent& event) {
return true; return true;
case ui::VKEY_UP: case ui::VKEY_UP:
#if defined(OS_MACOSX)
if (event.IsAltDown()) {
if (RowCount())
SelectByViewIndex(0);
} else {
AdvanceSelection(ADVANCE_DECREMENT);
}
#else
AdvanceSelection(ADVANCE_DECREMENT); AdvanceSelection(ADVANCE_DECREMENT);
#endif
return true; return true;
case ui::VKEY_DOWN: case ui::VKEY_DOWN:
#if defined(OS_MACOSX)
if (event.IsAltDown()) {
if (RowCount())
SelectByViewIndex(RowCount() - 1);
} else {
AdvanceSelection(ADVANCE_INCREMENT);
}
#else
AdvanceSelection(ADVANCE_INCREMENT); AdvanceSelection(ADVANCE_INCREMENT);
#endif
return true; return true;
case ui::VKEY_LEFT:
if (PlatformStyle::kTableViewSupportsKeyboardNavigationByCell) {
if (IsCmdOrCtrl(event)) {
if (active_visible_column_index_ != -1 && header_) {
const AdvanceDirection direction =
base::i18n::IsRTL() ? ADVANCE_INCREMENT : ADVANCE_DECREMENT;
header_->ResizeColumnViaKeyboard(active_visible_column_index_,
direction);
}
} else {
AdvanceActiveVisibleColumn(ADVANCE_DECREMENT);
}
return true;
}
break;
case ui::VKEY_RIGHT:
if (PlatformStyle::kTableViewSupportsKeyboardNavigationByCell) {
if (IsCmdOrCtrl(event)) {
if (active_visible_column_index_ != -1 && header_) {
const AdvanceDirection direction =
base::i18n::IsRTL() ? ADVANCE_DECREMENT : ADVANCE_INCREMENT;
header_->ResizeColumnViaKeyboard(active_visible_column_index_,
direction);
}
} else {
AdvanceActiveVisibleColumn(ADVANCE_INCREMENT);
}
return true;
}
break;
case ui::VKEY_SPACE:
if (PlatformStyle::kTableViewSupportsKeyboardNavigationByCell &&
active_visible_column_index_ != -1) {
ToggleSortOrder(active_visible_column_index_);
return true;
}
break;
default: default:
break; break;
} }
if (observer_) if (observer_)
observer_->OnKeyDown(event.key_code()); observer_->OnKeyDown(event.key_code());
return false; return false;
...@@ -481,6 +554,65 @@ void TableView::GetAccessibleNodeData(ui::AXNodeData* node_data) { ...@@ -481,6 +554,65 @@ void TableView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
} }
} }
bool TableView::HandleAccessibleAction(const ui::AXActionData& action_data) {
if (!RowCount())
return false;
int active_row = selection_model_.active();
if (active_row == ui::ListSelectionModel::kUnselectedIndex)
active_row = ModelToView(0);
switch (action_data.action) {
case ax::mojom::Action::kDoDefault:
RequestFocus();
SelectByViewIndex(ModelToView(active_row));
if (observer_)
observer_->OnDoubleClick();
break;
case ax::mojom::Action::kFocus:
RequestFocus();
// Setting focus should not affect the current selection.
if (selection_model_.empty())
SelectByViewIndex(0);
break;
case ax::mojom::Action::kScrollRight: {
const AdvanceDirection direction =
base::i18n::IsRTL() ? ADVANCE_DECREMENT : ADVANCE_INCREMENT;
AdvanceActiveVisibleColumn(direction);
break;
}
case ax::mojom::Action::kScrollLeft: {
const AdvanceDirection direction =
base::i18n::IsRTL() ? ADVANCE_INCREMENT : ADVANCE_DECREMENT;
AdvanceActiveVisibleColumn(direction);
break;
}
case ax::mojom::Action::kScrollToMakeVisible:
ScrollRectToVisible(GetRowBounds(ModelToView(active_row)));
break;
case ax::mojom::Action::kSetSelection:
// TODO(nektar): Retrieve the anchor and focus nodes once AXVirtualView is
// implemented in this class.
SelectByViewIndex(active_row);
break;
case ax::mojom::Action::kShowContextMenu:
ShowContextMenu(GetBoundsInScreen().CenterPoint(),
ui::MENU_SOURCE_KEYBOARD);
break;
default:
return false;
}
return true;
}
void TableView::OnModelChanged() { void TableView::OnModelChanged() {
selection_model_.Clear(); selection_model_.Clear();
NumRowsChanged(); NumRowsChanged();
...@@ -525,6 +657,7 @@ void TableView::OnItemsRemoved(int start, int length) { ...@@ -525,6 +657,7 @@ void TableView::OnItemsRemoved(int start, int length) {
selection_model_.set_active(FirstSelectedRow()); selection_model_.set_active(FirstSelectedRow());
if (!selection_model_.empty() && selection_model_.anchor() == -1) if (!selection_model_.empty() && selection_model_.anchor() == -1)
selection_model_.set_anchor(FirstSelectedRow()); selection_model_.set_anchor(FirstSelectedRow());
NotifyAccessibilityEvent(ax::mojom::Event::kSelection, true);
if (observer_) if (observer_)
observer_->OnSelectionChanged(); observer_->OnSelectionChanged();
} }
...@@ -846,6 +979,49 @@ ui::TableColumn TableView::FindColumnByID(int id) const { ...@@ -846,6 +979,49 @@ ui::TableColumn TableView::FindColumnByID(int id) const {
return ui::TableColumn(); return ui::TableColumn();
} }
void TableView::AdvanceActiveVisibleColumn(AdvanceDirection direction) {
if (visible_columns_.empty()) {
SetActiveVisibleColumnIndex(-1);
return;
}
if (active_visible_column_index_ == -1) {
if (selection_model_.active() == -1)
SelectByViewIndex(0);
SetActiveVisibleColumnIndex(0);
return;
}
if (direction == ADVANCE_DECREMENT) {
SetActiveVisibleColumnIndex(std::max(0, active_visible_column_index_ - 1));
} else {
SetActiveVisibleColumnIndex(
std::min(static_cast<int>(visible_columns_.size()) - 1,
active_visible_column_index_ + 1));
}
}
int TableView::GetActiveVisibleColumnIndex() const {
return active_visible_column_index_;
}
void TableView::SetActiveVisibleColumnIndex(int index) {
if (active_visible_column_index_ == index)
return;
active_visible_column_index_ = index;
if (selection_model_.active() != ui::ListSelectionModel::kUnselectedIndex &&
active_visible_column_index_ != -1) {
ScrollRectToVisible(GetCellBounds(ModelToView(selection_model_.active()),
active_visible_column_index_));
}
// Notify assistive technology that the active cell has changed. Without this,
// some screen readers will not announce the active cell.
if (HasFocus())
NotifyAccessibilityEvent(ax::mojom::Event::kFocus, true);
}
void TableView::SelectByViewIndex(int view_index) { void TableView::SelectByViewIndex(int view_index) {
ui::ListSelectionModel new_selection; ui::ListSelectionModel new_selection;
if (view_index != -1) { if (view_index != -1) {
...@@ -875,12 +1051,16 @@ void TableView::SetSelectionModel(ui::ListSelectionModel new_selection) { ...@@ -875,12 +1051,16 @@ void TableView::SetSelectionModel(ui::ListSelectionModel new_selection) {
vis_rect.set_y(start_y); vis_rect.set_y(start_y);
vis_rect.set_height(end_y - start_y); vis_rect.set_height(end_y - start_y);
ScrollRectToVisible(vis_rect); ScrollRectToVisible(vis_rect);
if (active_visible_column_index_ == -1)
SetActiveVisibleColumnIndex(0);
} else {
SetActiveVisibleColumnIndex(-1);
} }
NotifyAccessibilityEvent(ax::mojom::Event::kSelection, true);
if (observer_) if (observer_)
observer_->OnSelectionChanged(); observer_->OnSelectionChanged();
NotifyAccessibilityEvent(ax::mojom::Event::kFocus, true);
} }
void TableView::AdvanceSelection(AdvanceDirection direction) { void TableView::AdvanceSelection(AdvanceDirection direction) {
......
...@@ -54,6 +54,14 @@ class VIEWS_EXPORT TableView ...@@ -54,6 +54,14 @@ class VIEWS_EXPORT TableView
// Internal class name. // Internal class name.
static const char kViewClassName[]; static const char kViewClassName[];
// Used by AdvanceActiveVisibleColumn(), AdvanceSelection() and
// ResizeColumnViaKeyboard() to determine the direction to change the
// selection.
enum AdvanceDirection {
ADVANCE_DECREMENT,
ADVANCE_INCREMENT,
};
// Used to track a visible column. Useful only for the header. // Used to track a visible column. Useful only for the header.
struct VIEWS_EXPORT VisibleColumn { struct VIEWS_EXPORT VisibleColumn {
VisibleColumn(); VisibleColumn();
...@@ -135,6 +143,10 @@ class VIEWS_EXPORT TableView ...@@ -135,6 +143,10 @@ class VIEWS_EXPORT TableView
void set_observer(TableViewObserver* observer) { observer_ = observer; } void set_observer(TableViewObserver* observer) { observer_ = observer; }
TableViewObserver* observer() const { return observer_; } TableViewObserver* observer() const { return observer_; }
int GetActiveVisibleColumnIndex() const;
void SetActiveVisibleColumnIndex(int index);
const std::vector<VisibleColumn>& visible_columns() const { const std::vector<VisibleColumn>& visible_columns() const {
return visible_columns_; return visible_columns_;
} }
...@@ -181,6 +193,7 @@ class VIEWS_EXPORT TableView ...@@ -181,6 +193,7 @@ class VIEWS_EXPORT TableView
bool GetTooltipTextOrigin(const gfx::Point& p, bool GetTooltipTextOrigin(const gfx::Point& p,
gfx::Point* loc) const override; gfx::Point* loc) const override;
void GetAccessibleNodeData(ui::AXNodeData* node_data) override; void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
bool HandleAccessibleAction(const ui::AXActionData& action_data) override;
// ui::TableModelObserver overrides: // ui::TableModelObserver overrides:
void OnModelChanged() override; void OnModelChanged() override;
...@@ -215,13 +228,6 @@ class VIEWS_EXPORT TableView ...@@ -215,13 +228,6 @@ class VIEWS_EXPORT TableView
int max_column; int max_column;
}; };
// Used by AdvanceSelection() to determine the direction to change the
// selection.
enum AdvanceDirection {
ADVANCE_DECREMENT,
ADVANCE_INCREMENT,
};
// Returns the horizontal margin between the bounds of a cell and its // Returns the horizontal margin between the bounds of a cell and its
// contents. // contents.
int GetCellMargin() const; int GetCellMargin() const;
...@@ -275,6 +281,10 @@ class VIEWS_EXPORT TableView ...@@ -275,6 +281,10 @@ class VIEWS_EXPORT TableView
// Returns the TableColumn matching the specified id. // Returns the TableColumn matching the specified id.
ui::TableColumn FindColumnByID(int id) const; ui::TableColumn FindColumnByID(int id) const;
// Advances the active visible column (from the active visible column index)
// in the specified direction.
void AdvanceActiveVisibleColumn(AdvanceDirection direction);
// Sets the selection to the specified index (in terms of the view). // Sets the selection to the specified index (in terms of the view).
void SelectByViewIndex(int view_index); void SelectByViewIndex(int view_index);
...@@ -315,6 +325,10 @@ class VIEWS_EXPORT TableView ...@@ -315,6 +325,10 @@ class VIEWS_EXPORT TableView
// may contain a subset of |columns_|. // may contain a subset of |columns_|.
std::vector<VisibleColumn> visible_columns_; std::vector<VisibleColumn> visible_columns_;
// The active visible column. Used for keyboard access to functionality such
// as sorting and resizing. -1 if no visible column is active.
int active_visible_column_index_;
// The header. This is only created if more than one column is specified or // The header. This is only created if more than one column is specified or
// the first column has a non-empty title. // the first column has a non-empty title.
TableHeader* header_; TableHeader* header_;
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include "base/macros.h" #include "base/macros.h"
#include "base/strings/string_number_conversions.h" #include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "ui/events/event_utils.h" #include "ui/events/event_utils.h"
#include "ui/events/test/event_generator.h" #include "ui/events/test/event_generator.h"
...@@ -16,6 +17,7 @@ ...@@ -16,6 +17,7 @@
#include "ui/views/controls/table/table_grouper.h" #include "ui/views/controls/table/table_grouper.h"
#include "ui/views/controls/table/table_header.h" #include "ui/views/controls/table/table_header.h"
#include "ui/views/controls/table/table_view_observer.h" #include "ui/views/controls/table/table_view_observer.h"
#include "ui/views/style/platform_style.h"
#include "ui/views/test/focus_manager_test.h" #include "ui/views/test/focus_manager_test.h"
#include "ui/views/test/views_test_base.h" #include "ui/views/test/views_test_base.h"
#include "ui/views/widget/widget.h" #include "ui/views/widget/widget.h"
...@@ -42,6 +44,9 @@ class TableViewTestHelper { ...@@ -42,6 +44,9 @@ class TableViewTestHelper {
return table_->visible_columns().size(); return table_->visible_columns().size();
} }
int GetActiveVisibleColumnIndex() {
return table_->GetActiveVisibleColumnIndex();
}
TableHeader* header() { return table_->header_; } TableHeader* header() { return table_->header_; }
void SetSelectionModel(const ui::ListSelectionModel& new_selection) { void SetSelectionModel(const ui::ListSelectionModel& new_selection) {
...@@ -301,9 +306,11 @@ class TableViewTest : public ViewsTestBase { ...@@ -301,9 +306,11 @@ class TableViewTest : public ViewsTestBase {
return result; return result;
} }
void PressKey(ui::KeyboardCode code) { void PressKey(ui::KeyboardCode code) { PressKey(code, ui::EF_NONE); }
void PressKey(ui::KeyboardCode code, int flags) {
ui::test::EventGenerator generator(GetRootWindow(widget_.get())); ui::test::EventGenerator generator(GetRootWindow(widget_.get()));
generator.PressKey(code, ui::EF_NONE); generator.PressKey(code, flags);
} }
protected: protected:
...@@ -370,7 +377,7 @@ TEST_F(TableViewTest, ColumnVisibility) { ...@@ -370,7 +377,7 @@ TEST_F(TableViewTest, ColumnVisibility) {
EXPECT_EQ("rows=0 4 cols=0 2", helper_->GetPaintRegion(table_->bounds())); EXPECT_EQ("rows=0 4 cols=0 2", helper_->GetPaintRegion(table_->bounds()));
} }
// Verifies resizing a column works. // Verifies resizing a column using the mouse works.
TEST_F(TableViewTest, Resize) { TEST_F(TableViewTest, Resize) {
const int x = table_->GetVisibleColumn(0).width; const int x = table_->GetVisibleColumn(0).width;
EXPECT_NE(0, x); EXPECT_NE(0, x);
...@@ -402,6 +409,40 @@ TEST_F(TableViewTest, ResizeViaGesture) { ...@@ -402,6 +409,40 @@ TEST_F(TableViewTest, ResizeViaGesture) {
EXPECT_EQ(x - 1, table_->GetVisibleColumn(1).x); EXPECT_EQ(x - 1, table_->GetVisibleColumn(1).x);
} }
// Verifies resizing a column works with the keyboard.
// The resize keyboard amount is 5 pixels.
TEST_F(TableViewTest, ResizeViaKeyboard) {
if (!PlatformStyle::kTableViewSupportsKeyboardNavigationByCell)
return;
table_->RequestFocus();
const int x = table_->GetVisibleColumn(0).width;
EXPECT_NE(0, x);
// Table starts off with no visible column being active.
ASSERT_EQ(-1, helper_->GetActiveVisibleColumnIndex());
ui::ListSelectionModel new_selection;
new_selection.SetSelectedIndex(1);
helper_->SetSelectionModel(new_selection);
ASSERT_EQ(0, helper_->GetActiveVisibleColumnIndex());
PressKey(ui::VKEY_LEFT, ui::EF_CONTROL_DOWN);
// This should shrink the first column and pull the second column in.
EXPECT_EQ(x - 5, table_->GetVisibleColumn(0).width);
EXPECT_EQ(x - 5, table_->GetVisibleColumn(1).x);
PressKey(ui::VKEY_RIGHT, ui::EF_CONTROL_DOWN);
// This should restore the columns to their original sizes.
EXPECT_EQ(x, table_->GetVisibleColumn(0).width);
EXPECT_EQ(x, table_->GetVisibleColumn(1).x);
PressKey(ui::VKEY_RIGHT, ui::EF_CONTROL_DOWN);
// This should expand the first column and push the second column out.
EXPECT_EQ(x + 5, table_->GetVisibleColumn(0).width);
EXPECT_EQ(x + 5, table_->GetVisibleColumn(1).x);
}
// Verifies resizing a column won't reduce the column width below the width of // Verifies resizing a column won't reduce the column width below the width of
// the column's title text. // the column's title text.
TEST_F(TableViewTest, ResizeHonorsMinimum) { TEST_F(TableViewTest, ResizeHonorsMinimum) {
...@@ -552,7 +593,7 @@ TEST_F(TableViewTest, Sort) { ...@@ -552,7 +593,7 @@ TEST_F(TableViewTest, Sort) {
GetRowsInViewOrderAsString(table_)); GetRowsInViewOrderAsString(table_));
} }
// Verfies clicking on the header sorts. // Verifies clicking on the header sorts.
TEST_F(TableViewTest, SortOnMouse) { TEST_F(TableViewTest, SortOnMouse) {
EXPECT_TRUE(table_->sort_descriptors().empty()); EXPECT_TRUE(table_->sort_descriptors().empty());
...@@ -568,6 +609,43 @@ TEST_F(TableViewTest, SortOnMouse) { ...@@ -568,6 +609,43 @@ TEST_F(TableViewTest, SortOnMouse) {
EXPECT_TRUE(table_->sort_descriptors()[0].ascending); EXPECT_TRUE(table_->sort_descriptors()[0].ascending);
} }
// Verifies that pressing the space bar when a particular visible column is
// active will sort by that column.
TEST_F(TableViewTest, SortOnSpaceBar) {
if (!PlatformStyle::kTableViewSupportsKeyboardNavigationByCell)
return;
table_->RequestFocus();
ASSERT_TRUE(table_->sort_descriptors().empty());
// Table starts off with no visible column being active.
ASSERT_EQ(-1, helper_->GetActiveVisibleColumnIndex());
ui::ListSelectionModel new_selection;
new_selection.SetSelectedIndex(1);
helper_->SetSelectionModel(new_selection);
ASSERT_EQ(0, helper_->GetActiveVisibleColumnIndex());
PressKey(ui::VKEY_SPACE);
ASSERT_EQ(1u, table_->sort_descriptors().size());
EXPECT_EQ(0, table_->sort_descriptors()[0].column_id);
EXPECT_TRUE(table_->sort_descriptors()[0].ascending);
PressKey(ui::VKEY_SPACE);
ASSERT_EQ(1u, table_->sort_descriptors().size());
EXPECT_EQ(0, table_->sort_descriptors()[0].column_id);
EXPECT_FALSE(table_->sort_descriptors()[0].ascending);
PressKey(ui::VKEY_RIGHT);
ASSERT_EQ(1, helper_->GetActiveVisibleColumnIndex());
PressKey(ui::VKEY_SPACE);
ASSERT_EQ(2u, table_->sort_descriptors().size());
EXPECT_EQ(1, table_->sort_descriptors()[0].column_id);
EXPECT_EQ(0, table_->sort_descriptors()[1].column_id);
EXPECT_TRUE(table_->sort_descriptors()[0].ascending);
EXPECT_FALSE(table_->sort_descriptors()[1].ascending);
}
namespace { namespace {
class TableGrouperImpl : public TableGrouper { class TableGrouperImpl : public TableGrouper {
...@@ -877,7 +955,7 @@ TEST_F(TableViewTest, SelectOnTap) { ...@@ -877,7 +955,7 @@ TEST_F(TableViewTest, SelectOnTap) {
} }
#endif #endif
// Verifies up/down correctly navigates through groups. // Verifies up/down correctly navigate through groups.
TEST_F(TableViewTest, KeyUpDown) { TEST_F(TableViewTest, KeyUpDown) {
// Configure the grouper so that there are three groups: // Configure the grouper so that there are three groups:
// A 0 // A 0
...@@ -1008,6 +1086,105 @@ TEST_F(TableViewTest, KeyUpDown) { ...@@ -1008,6 +1086,105 @@ TEST_F(TableViewTest, KeyUpDown) {
table_->set_observer(NULL); table_->set_observer(NULL);
} }
// Verifies left/right correctly navigate through visible columns.
TEST_F(TableViewTest, KeyLeftRight) {
if (!PlatformStyle::kTableViewSupportsKeyboardNavigationByCell)
return;
TableViewObserverImpl observer;
table_->set_observer(&observer);
table_->RequestFocus();
// Initially no active visible column.
EXPECT_EQ(-1, helper_->GetActiveVisibleColumnIndex());
PressKey(ui::VKEY_RIGHT);
EXPECT_EQ(0, helper_->GetActiveVisibleColumnIndex());
EXPECT_EQ(1, observer.GetChangedCountAndClear());
EXPECT_EQ("active=0 anchor=0 selection=0", SelectionStateAsString());
helper_->SetSelectionModel(ui::ListSelectionModel());
EXPECT_EQ(-1, helper_->GetActiveVisibleColumnIndex());
EXPECT_EQ(1, observer.GetChangedCountAndClear());
PressKey(ui::VKEY_LEFT);
EXPECT_EQ(0, helper_->GetActiveVisibleColumnIndex());
EXPECT_EQ(1, observer.GetChangedCountAndClear());
EXPECT_EQ("active=0 anchor=0 selection=0", SelectionStateAsString());
PressKey(ui::VKEY_RIGHT);
EXPECT_EQ(1, helper_->GetActiveVisibleColumnIndex());
EXPECT_EQ(0, observer.GetChangedCountAndClear());
EXPECT_EQ("active=0 anchor=0 selection=0", SelectionStateAsString());
PressKey(ui::VKEY_RIGHT);
EXPECT_EQ(1, helper_->GetActiveVisibleColumnIndex());
EXPECT_EQ(0, observer.GetChangedCountAndClear());
EXPECT_EQ("active=0 anchor=0 selection=0", SelectionStateAsString());
ui::ListSelectionModel new_selection;
new_selection.SetSelectedIndex(1);
helper_->SetSelectionModel(new_selection);
EXPECT_EQ(1, helper_->GetActiveVisibleColumnIndex());
EXPECT_EQ(1, observer.GetChangedCountAndClear());
EXPECT_EQ("active=1 anchor=1 selection=1", SelectionStateAsString());
PressKey(ui::VKEY_LEFT);
EXPECT_EQ(0, helper_->GetActiveVisibleColumnIndex());
EXPECT_EQ(0, observer.GetChangedCountAndClear());
EXPECT_EQ("active=1 anchor=1 selection=1", SelectionStateAsString());
PressKey(ui::VKEY_LEFT);
EXPECT_EQ(0, helper_->GetActiveVisibleColumnIndex());
EXPECT_EQ(0, observer.GetChangedCountAndClear());
EXPECT_EQ("active=1 anchor=1 selection=1", SelectionStateAsString());
table_->SetColumnVisibility(0, false);
EXPECT_EQ(0, helper_->GetActiveVisibleColumnIndex());
EXPECT_EQ(0, observer.GetChangedCountAndClear());
EXPECT_EQ("active=1 anchor=1 selection=1", SelectionStateAsString());
// Since the first column was hidden, the active visible column should not
// advance.
PressKey(ui::VKEY_RIGHT);
EXPECT_EQ(0, helper_->GetActiveVisibleColumnIndex());
EXPECT_EQ(0, observer.GetChangedCountAndClear());
EXPECT_EQ("active=1 anchor=1 selection=1", SelectionStateAsString());
// If visibility to the first column is restored, the active visible column
// should be unchanged because columns are always added to the end.
table_->SetColumnVisibility(0, true);
EXPECT_EQ(0, helper_->GetActiveVisibleColumnIndex());
EXPECT_EQ(0, observer.GetChangedCountAndClear());
EXPECT_EQ("active=1 anchor=1 selection=1", SelectionStateAsString());
PressKey(ui::VKEY_RIGHT);
EXPECT_EQ(1, helper_->GetActiveVisibleColumnIndex());
// If visibility to the first column is removed, the active visible column
// should be decreased by one.
table_->SetColumnVisibility(0, false);
EXPECT_EQ(0, helper_->GetActiveVisibleColumnIndex());
EXPECT_EQ(0, observer.GetChangedCountAndClear());
EXPECT_EQ("active=1 anchor=1 selection=1", SelectionStateAsString());
PressKey(ui::VKEY_LEFT);
EXPECT_EQ(0, helper_->GetActiveVisibleColumnIndex());
EXPECT_EQ(0, observer.GetChangedCountAndClear());
EXPECT_EQ("active=1 anchor=1 selection=1", SelectionStateAsString());
table_->SetColumnVisibility(0, true);
EXPECT_EQ(0, helper_->GetActiveVisibleColumnIndex());
EXPECT_EQ(0, observer.GetChangedCountAndClear());
EXPECT_EQ("active=1 anchor=1 selection=1", SelectionStateAsString());
PressKey(ui::VKEY_RIGHT);
EXPECT_EQ(1, helper_->GetActiveVisibleColumnIndex());
EXPECT_EQ(0, observer.GetChangedCountAndClear());
EXPECT_EQ("active=1 anchor=1 selection=1", SelectionStateAsString());
table_->set_observer(NULL);
}
// Verifies home/end do the right thing. // Verifies home/end do the right thing.
TEST_F(TableViewTest, HomeEnd) { TEST_F(TableViewTest, HomeEnd) {
// Configure the grouper so that there are three groups: // Configure the grouper so that there are three groups:
...@@ -1018,10 +1195,7 @@ TEST_F(TableViewTest, HomeEnd) { ...@@ -1018,10 +1195,7 @@ TEST_F(TableViewTest, HomeEnd) {
// 3 // 3
model_->AddRow(2, 5, 0); model_->AddRow(2, 5, 0);
TableGrouperImpl grouper; TableGrouperImpl grouper;
std::vector<int> ranges; std::vector<int> ranges{2, 1, 2};
ranges.push_back(2);
ranges.push_back(1);
ranges.push_back(2);
grouper.SetRanges(ranges); grouper.SetRanges(ranges);
table_->SetGrouper(&grouper); table_->SetGrouper(&grouper);
......
...@@ -56,6 +56,7 @@ const Button::NotifyAction PlatformStyle::kMenuNotifyActivationAction = ...@@ -56,6 +56,7 @@ const Button::NotifyAction PlatformStyle::kMenuNotifyActivationAction =
const Button::KeyClickAction PlatformStyle::kKeyClickActionOnSpace = const Button::KeyClickAction PlatformStyle::kKeyClickActionOnSpace =
Button::CLICK_ON_KEY_RELEASE; Button::CLICK_ON_KEY_RELEASE;
const bool PlatformStyle::kReturnClicksFocusedControl = true; const bool PlatformStyle::kReturnClicksFocusedControl = true;
const bool PlatformStyle::kTableViewSupportsKeyboardNavigationByCell = true;
const bool PlatformStyle::kTreeViewSelectionPaintsEntireRow = false; const bool PlatformStyle::kTreeViewSelectionPaintsEntireRow = false;
const bool PlatformStyle::kUseRipples = true; const bool PlatformStyle::kUseRipples = true;
const bool PlatformStyle::kTextfieldScrollsToStartOnFocusChange = false; const bool PlatformStyle::kTextfieldScrollsToStartOnFocusChange = false;
......
...@@ -55,6 +55,11 @@ class VIEWS_EXPORT PlatformStyle { ...@@ -55,6 +55,11 @@ class VIEWS_EXPORT PlatformStyle {
// Otherwise, Return does nothing unless it is handled by an accelerator. // Otherwise, Return does nothing unless it is handled by an accelerator.
static const bool kReturnClicksFocusedControl; static const bool kReturnClicksFocusedControl;
// Whether cursor left and right can be used in a TableView to select and
// resize columns and whether a focus ring should be shown around the active
// cell.
static const bool kTableViewSupportsKeyboardNavigationByCell;
// Whether selecting a row in a TreeView selects the entire row or only the // Whether selecting a row in a TreeView selects the entire row or only the
// label for that row. // label for that row.
static const bool kTreeViewSelectionPaintsEntireRow; static const bool kTreeViewSelectionPaintsEntireRow;
......
...@@ -39,6 +39,7 @@ const bool PlatformStyle::kSelectWordOnRightClick = true; ...@@ -39,6 +39,7 @@ const bool PlatformStyle::kSelectWordOnRightClick = true;
const bool PlatformStyle::kSelectAllOnRightClickWhenUnfocused = true; const bool PlatformStyle::kSelectAllOnRightClickWhenUnfocused = true;
const bool PlatformStyle::kTextfieldScrollsToStartOnFocusChange = true; const bool PlatformStyle::kTextfieldScrollsToStartOnFocusChange = true;
const bool PlatformStyle::kTextfieldUsesDragCursorWhenDraggable = false; const bool PlatformStyle::kTextfieldUsesDragCursorWhenDraggable = false;
const bool PlatformStyle::kTableViewSupportsKeyboardNavigationByCell = false;
const bool PlatformStyle::kTreeViewSelectionPaintsEntireRow = true; const bool PlatformStyle::kTreeViewSelectionPaintsEntireRow = true;
const bool PlatformStyle::kUseRipples = false; const bool PlatformStyle::kUseRipples = false;
const bool PlatformStyle::kPreferFocusRings = true; const bool PlatformStyle::kPreferFocusRings = true;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment