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); AdvanceSelection(ADVANCE_DECREMENT);
}
#else
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;
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; 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_;
......
...@@ -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