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 {
namespace {
const int kVerticalPadding = 4;
// 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.
const int kResizePadding = 5;
constexpr int kResizePadding = 5;
// Amount of space above/below the separator.
const int kSeparatorPadding = 4;
constexpr int kSeparatorPadding = 4;
// Size of the sort indicator (doesn't include padding).
const int kSortIndicatorSize = 8;
constexpr int kSortIndicatorSize = 8;
} // namespace
......@@ -231,6 +234,29 @@ void TableHeader::OnNativeThemeChanged(const ui::NativeTheme* theme) {
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) {
if (is_resizing())
return false;
......
......@@ -7,13 +7,12 @@
#include "base/macros.h"
#include "ui/gfx/font_list.h"
#include "ui/views/controls/table/table_view.h"
#include "ui/views/view.h"
#include "ui/views/views_export.h"
namespace views {
class TableView;
// Views used to render the header for the table.
class VIEWS_EXPORT TableHeader : public views::View {
public:
......@@ -31,6 +30,9 @@ class VIEWS_EXPORT TableHeader : public views::View {
const gfx::FontList& font_list() const { return font_list_; }
void ResizeColumnViaKeyboard(int index,
TableView::AdvanceDirection direction);
// views::View overrides.
void Layout() override;
void OnPaint(gfx::Canvas* canvas) override;
......
......@@ -5,6 +5,7 @@
#include "ui/views/controls/table/table_view.h"
#include <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <map>
......@@ -12,23 +13,29 @@
#include "base/auto_reset.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 "build/build_config.h"
#include "cc/paint/paint_flags.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/events/event.h"
#include "ui/gfx/canvas.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/skia_util.h"
#include "ui/gfx/text_utils.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/table/table_grouper.h"
#include "ui/views/controls/table/table_header.h"
#include "ui/views/controls/table/table_utils.h"
#include "ui/views/controls/table/table_view_observer.h"
#include "ui/views/layout/layout_provider.h"
#include "ui/views/style/platform_style.h"
#include "ui/views/style/typography.h"
namespace views {
......@@ -135,6 +142,7 @@ TableView::TableView(ui::TableModel* model,
bool single_selection)
: model_(NULL),
columns_(columns),
active_visible_column_index_(-1),
header_(NULL),
table_type_(table_type),
single_selection_(single_selection),
......@@ -155,6 +163,7 @@ TableView::TableView(ui::TableModel* model,
visible_column.column = columns[i];
visible_columns_.push_back(visible_column);
}
// Always focusable, even on Mac (consistent with NSTableView).
SetFocusBehavior(FocusBehavior::ALWAYS);
SetModel(model);
......@@ -220,6 +229,11 @@ void TableView::SetColumnVisibility(int id, bool is_visible) {
for (size_t i = 0; i < visible_columns_.size(); ++i) {
if (visible_columns_[i].column.id == id) {
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;
}
}
......@@ -385,16 +399,75 @@ bool TableView::OnKeyPressed(const ui::KeyEvent& event) {
return true;
case ui::VKEY_UP:
#if defined(OS_MACOSX)
if (event.IsAltDown()) {
if (RowCount())
SelectByViewIndex(0);
} else {
AdvanceSelection(ADVANCE_DECREMENT);
}
#else
AdvanceSelection(ADVANCE_DECREMENT);
#endif
return true;
case ui::VKEY_DOWN:
#if defined(OS_MACOSX)
if (event.IsAltDown()) {
if (RowCount())
SelectByViewIndex(RowCount() - 1);
} else {
AdvanceSelection(ADVANCE_INCREMENT);
}
#else
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;
}
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:
break;
}
if (observer_)
observer_->OnKeyDown(event.key_code());
return false;
......@@ -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() {
selection_model_.Clear();
NumRowsChanged();
......@@ -525,6 +657,7 @@ void TableView::OnItemsRemoved(int start, int length) {
selection_model_.set_active(FirstSelectedRow());
if (!selection_model_.empty() && selection_model_.anchor() == -1)
selection_model_.set_anchor(FirstSelectedRow());
NotifyAccessibilityEvent(ax::mojom::Event::kSelection, true);
if (observer_)
observer_->OnSelectionChanged();
}
......@@ -846,6 +979,49 @@ ui::TableColumn TableView::FindColumnByID(int id) const {
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) {
ui::ListSelectionModel new_selection;
if (view_index != -1) {
......@@ -875,12 +1051,16 @@ void TableView::SetSelectionModel(ui::ListSelectionModel new_selection) {
vis_rect.set_y(start_y);
vis_rect.set_height(end_y - start_y);
ScrollRectToVisible(vis_rect);
if (active_visible_column_index_ == -1)
SetActiveVisibleColumnIndex(0);
} else {
SetActiveVisibleColumnIndex(-1);
}
NotifyAccessibilityEvent(ax::mojom::Event::kSelection, true);
if (observer_)
observer_->OnSelectionChanged();
NotifyAccessibilityEvent(ax::mojom::Event::kFocus, true);
}
void TableView::AdvanceSelection(AdvanceDirection direction) {
......
......@@ -54,6 +54,14 @@ class VIEWS_EXPORT TableView
// Internal class name.
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.
struct VIEWS_EXPORT VisibleColumn {
VisibleColumn();
......@@ -135,6 +143,10 @@ class VIEWS_EXPORT TableView
void set_observer(TableViewObserver* observer) { observer_ = observer; }
TableViewObserver* observer() const { return observer_; }
int GetActiveVisibleColumnIndex() const;
void SetActiveVisibleColumnIndex(int index);
const std::vector<VisibleColumn>& visible_columns() const {
return visible_columns_;
}
......@@ -181,6 +193,7 @@ class VIEWS_EXPORT TableView
bool GetTooltipTextOrigin(const gfx::Point& p,
gfx::Point* loc) const override;
void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
bool HandleAccessibleAction(const ui::AXActionData& action_data) override;
// ui::TableModelObserver overrides:
void OnModelChanged() override;
......@@ -215,13 +228,6 @@ class VIEWS_EXPORT TableView
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
// contents.
int GetCellMargin() const;
......@@ -275,6 +281,10 @@ class VIEWS_EXPORT TableView
// Returns the TableColumn matching the specified id.
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).
void SelectByViewIndex(int view_index);
......@@ -315,6 +325,10 @@ class VIEWS_EXPORT TableView
// may contain a subset of |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 first column has a non-empty title.
TableHeader* header_;
......
......@@ -56,6 +56,7 @@ const Button::NotifyAction PlatformStyle::kMenuNotifyActivationAction =
const Button::KeyClickAction PlatformStyle::kKeyClickActionOnSpace =
Button::CLICK_ON_KEY_RELEASE;
const bool PlatformStyle::kReturnClicksFocusedControl = true;
const bool PlatformStyle::kTableViewSupportsKeyboardNavigationByCell = true;
const bool PlatformStyle::kTreeViewSelectionPaintsEntireRow = false;
const bool PlatformStyle::kUseRipples = true;
const bool PlatformStyle::kTextfieldScrollsToStartOnFocusChange = false;
......
......@@ -55,6 +55,11 @@ class VIEWS_EXPORT PlatformStyle {
// Otherwise, Return does nothing unless it is handled by an accelerator.
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
// label for that row.
static const bool kTreeViewSelectionPaintsEntireRow;
......
......@@ -39,6 +39,7 @@ const bool PlatformStyle::kSelectWordOnRightClick = true;
const bool PlatformStyle::kSelectAllOnRightClickWhenUnfocused = true;
const bool PlatformStyle::kTextfieldScrollsToStartOnFocusChange = true;
const bool PlatformStyle::kTextfieldUsesDragCursorWhenDraggable = false;
const bool PlatformStyle::kTableViewSupportsKeyboardNavigationByCell = false;
const bool PlatformStyle::kTreeViewSelectionPaintsEntireRow = true;
const bool PlatformStyle::kUseRipples = false;
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