1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "ui/views/controls/table/table_view.h"
9 #include "base/auto_reset.h"
10 #include "base/i18n/rtl.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "ui/accessibility/ax_view_state.h"
14 #include "ui/events/event.h"
15 #include "ui/gfx/canvas.h"
16 #include "ui/gfx/geometry/rect_conversions.h"
17 #include "ui/gfx/image/image_skia.h"
18 #include "ui/gfx/skia_util.h"
19 #include "ui/gfx/text_utils.h"
20 #include "ui/native_theme/native_theme.h"
21 #include "ui/views/controls/scroll_view.h"
22 #include "ui/views/controls/table/table_grouper.h"
23 #include "ui/views/controls/table/table_header.h"
24 #include "ui/views/controls/table/table_utils.h"
25 #include "ui/views/controls/table/table_view_observer.h"
26 #include "ui/views/controls/table/table_view_row_background_painter.h"
28 // Padding around the text (on each side).
29 static const int kTextVerticalPadding
= 3;
30 static const int kTextHorizontalPadding
= 6;
33 static const int kImageSize
= 16;
35 static const int kGroupingIndicatorSize
= 6;
41 // Returns result, unless ascending is false in which case -result is returned.
42 int SwapCompareResult(int result
, bool ascending
) {
43 return ascending
? result
: -result
;
46 // Populates |model_index_to_range_start| based on the |grouper|.
47 void GetModelIndexToRangeStart(TableGrouper
* grouper
,
49 std::map
<int, int>* model_index_to_range_start
) {
50 for (int model_index
= 0; model_index
< row_count
;) {
52 grouper
->GetGroupRange(model_index
, &range
);
53 DCHECK_GT(range
.length
, 0);
54 for (int range_counter
= 0; range_counter
< range
.length
; range_counter
++)
55 (*model_index_to_range_start
)[range_counter
+ model_index
] = model_index
;
56 model_index
+= range
.length
;
60 // Returns the color id for the background of selected text. |has_focus|
61 // indicates if the table has focus.
62 ui::NativeTheme::ColorId
text_background_color_id(bool has_focus
) {
64 ui::NativeTheme::kColorId_TableSelectionBackgroundFocused
:
65 ui::NativeTheme::kColorId_TableSelectionBackgroundUnfocused
;
68 // Returns the color id for text. |has_focus| indicates if the table has focus.
69 ui::NativeTheme::ColorId
selected_text_color_id(bool has_focus
) {
70 return has_focus
? ui::NativeTheme::kColorId_TableSelectedText
:
71 ui::NativeTheme::kColorId_TableSelectedTextUnfocused
;
76 // Used as the comparator to sort the contents of the table.
77 struct TableView::SortHelper
{
78 explicit SortHelper(TableView
* table
) : table(table
) {}
80 bool operator()(int model_index1
, int model_index2
) {
81 return table
->CompareRows(model_index1
, model_index2
) < 0;
87 // Used as the comparator to sort the contents of the table when a TableGrouper
88 // is present. When groups are present we sort the groups based on the first row
89 // in the group and within the groups we keep the same order as the model.
90 struct TableView::GroupSortHelper
{
91 explicit GroupSortHelper(TableView
* table
) : table(table
) {}
93 bool operator()(int model_index1
, int model_index2
) {
94 const int range1
= model_index_to_range_start
[model_index1
];
95 const int range2
= model_index_to_range_start
[model_index2
];
96 if (range1
== range2
) {
97 // The two rows are in the same group, sort so that items in the same
98 // group always appear in the same order.
99 return model_index1
< model_index2
;
101 return table
->CompareRows(range1
, range2
) < 0;
105 std::map
<int, int> model_index_to_range_start
;
108 TableView::VisibleColumn::VisibleColumn() : x(0), width(0) {}
110 TableView::VisibleColumn::~VisibleColumn() {}
112 TableView::PaintRegion::PaintRegion()
119 TableView::PaintRegion::~PaintRegion() {}
122 const char TableView::kViewClassName
[] = "TableView";
124 TableView::TableView(ui::TableModel
* model
,
125 const std::vector
<ui::TableColumn
>& columns
,
126 TableTypes table_type
,
127 bool single_selection
)
131 table_type_(table_type
),
132 single_selection_(single_selection
),
133 table_view_observer_(NULL
),
134 row_height_(font_list_
.GetHeight() + kTextVerticalPadding
* 2),
135 last_parent_width_(0),
138 in_set_visible_column_width_(false) {
139 for (size_t i
= 0; i
< columns
.size(); ++i
) {
140 VisibleColumn visible_column
;
141 visible_column
.column
= columns
[i
];
142 visible_columns_
.push_back(visible_column
);
148 TableView::~TableView() {
150 model_
->SetObserver(NULL
);
153 // TODO: this doesn't support arbitrarily changing the model, rename this to
154 // ClearModel() or something.
155 void TableView::SetModel(ui::TableModel
* model
) {
160 model_
->SetObserver(NULL
);
162 selection_model_
.Clear();
164 model_
->SetObserver(this);
167 View
* TableView::CreateParentIfNecessary() {
168 ScrollView
* scroll_view
= ScrollView::CreateScrollViewWithBorder();
169 scroll_view
->SetContents(this);
170 CreateHeaderIfNecessary();
172 scroll_view
->SetHeader(header_
);
176 void TableView::SetRowBackgroundPainter(
177 scoped_ptr
<TableViewRowBackgroundPainter
> painter
) {
178 row_background_painter_
= painter
.Pass();
181 void TableView::SetGrouper(TableGrouper
* grouper
) {
183 SortItemsAndUpdateMapping();
186 int TableView::RowCount() const {
187 return model_
? model_
->RowCount() : 0;
190 int TableView::SelectedRowCount() {
191 return static_cast<int>(selection_model_
.size());
194 void TableView::Select(int model_row
) {
198 SelectByViewIndex(model_row
== -1 ? -1 : ModelToView(model_row
));
201 int TableView::FirstSelectedRow() {
202 return SelectedRowCount() == 0 ? -1 : selection_model_
.selected_indices()[0];
205 void TableView::SetColumnVisibility(int id
, bool is_visible
) {
206 if (is_visible
== IsColumnVisible(id
))
210 VisibleColumn visible_column
;
211 visible_column
.column
= FindColumnByID(id
);
212 visible_columns_
.push_back(visible_column
);
214 for (size_t i
= 0; i
< visible_columns_
.size(); ++i
) {
215 if (visible_columns_
[i
].column
.id
== id
) {
216 visible_columns_
.erase(visible_columns_
.begin() + i
);
221 UpdateVisibleColumnSizes();
222 PreferredSizeChanged();
225 header_
->SchedulePaint();
228 void TableView::ToggleSortOrder(int visible_column_index
) {
229 DCHECK(visible_column_index
>= 0 &&
230 visible_column_index
< static_cast<int>(visible_columns_
.size()));
231 if (!visible_columns_
[visible_column_index
].column
.sortable
)
233 const int column_id
= visible_columns_
[visible_column_index
].column
.id
;
234 SortDescriptors
sort(sort_descriptors_
);
235 if (!sort
.empty() && sort
[0].column_id
== column_id
) {
236 sort
[0].ascending
= !sort
[0].ascending
;
238 SortDescriptor
descriptor(column_id
, visible_columns_
[
239 visible_column_index
].column
.initial_sort_is_ascending
);
240 sort
.insert(sort
.begin(), descriptor
);
241 // Only persist two sort descriptors.
245 SetSortDescriptors(sort
);
248 bool TableView::IsColumnVisible(int id
) const {
249 for (size_t i
= 0; i
< visible_columns_
.size(); ++i
) {
250 if (visible_columns_
[i
].column
.id
== id
)
256 void TableView::AddColumn(const ui::TableColumn
& col
) {
257 DCHECK(!HasColumn(col
.id
));
258 columns_
.push_back(col
);
261 bool TableView::HasColumn(int id
) const {
262 for (size_t i
= 0; i
< columns_
.size(); ++i
) {
263 if (columns_
[i
].id
== id
)
269 void TableView::SetVisibleColumnWidth(int index
, int width
) {
270 DCHECK(index
>= 0 && index
< static_cast<int>(visible_columns_
.size()));
271 if (visible_columns_
[index
].width
== width
)
273 base::AutoReset
<bool> reseter(&in_set_visible_column_width_
, true);
274 visible_columns_
[index
].width
= width
;
275 for (size_t i
= index
+ 1; i
< visible_columns_
.size(); ++i
) {
276 visible_columns_
[i
].x
=
277 visible_columns_
[i
- 1].x
+ visible_columns_
[i
- 1].width
;
279 PreferredSizeChanged();
283 int TableView::ModelToView(int model_index
) const {
286 DCHECK_GE(model_index
, 0) << " negative model_index " << model_index
;
287 DCHECK_LT(model_index
, RowCount()) << " out of bounds model_index " <<
289 return model_to_view_
[model_index
];
292 int TableView::ViewToModel(int view_index
) const {
295 DCHECK_GE(view_index
, 0) << " negative view_index " << view_index
;
296 DCHECK_LT(view_index
, RowCount()) << " out of bounds view_index " <<
298 return view_to_model_
[view_index
];
301 void TableView::Layout() {
302 // parent()->parent() is the scrollview. When its width changes we force
303 // recalculating column sizes.
304 View
* scroll_view
= parent() ? parent()->parent() : NULL
;
306 const int scroll_view_width
= scroll_view
->GetContentsBounds().width();
307 if (scroll_view_width
!= last_parent_width_
) {
308 last_parent_width_
= scroll_view_width
;
309 if (!in_set_visible_column_width_
) {
310 // Layout to the parent (the Viewport), which differs from
311 // |scroll_view_width| when scrollbars are present.
312 layout_width_
= parent()->width();
313 UpdateVisibleColumnSizes();
317 // We have to override Layout like this since we're contained in a ScrollView.
318 gfx::Size pref
= GetPreferredSize();
319 int width
= pref
.width();
320 int height
= pref
.height();
322 width
= std::max(parent()->width(), width
);
323 height
= std::max(parent()->height(), height
);
325 SetBounds(x(), y(), width
, height
);
328 const char* TableView::GetClassName() const {
329 return kViewClassName
;
332 gfx::Size
TableView::GetPreferredSize() const {
334 if (header_
&& !visible_columns_
.empty())
335 width
= visible_columns_
.back().x
+ visible_columns_
.back().width
;
336 return gfx::Size(width
, RowCount() * row_height_
);
339 bool TableView::OnKeyPressed(const ui::KeyEvent
& event
) {
343 switch (event
.key_code()) {
345 // control-a selects all.
346 if (event
.IsControlDown() && !single_selection_
&& RowCount()) {
347 ui::ListSelectionModel selection_model
;
348 selection_model
.SetSelectedIndex(selection_model_
.active());
349 for (int i
= 0; i
< RowCount(); ++i
)
350 selection_model
.AddIndexToSelection(i
);
351 SetSelectionModel(selection_model
);
358 SelectByViewIndex(0);
363 SelectByViewIndex(RowCount() - 1);
367 AdvanceSelection(ADVANCE_DECREMENT
);
371 AdvanceSelection(ADVANCE_INCREMENT
);
377 if (table_view_observer_
)
378 table_view_observer_
->OnKeyDown(event
.key_code());
382 bool TableView::OnMousePressed(const ui::MouseEvent
& event
) {
384 if (!event
.IsOnlyLeftMouseButton())
387 const int row
= event
.y() / row_height_
;
388 if (row
< 0 || row
>= RowCount())
391 if (event
.GetClickCount() == 2) {
392 SelectByViewIndex(row
);
393 if (table_view_observer_
)
394 table_view_observer_
->OnDoubleClick();
395 } else if (event
.GetClickCount() == 1) {
396 ui::ListSelectionModel new_model
;
397 ConfigureSelectionModelForEvent(event
, &new_model
);
398 SetSelectionModel(new_model
);
404 void TableView::OnGestureEvent(ui::GestureEvent
* event
) {
405 if (event
->type() != ui::ET_GESTURE_TAP
)
408 const int row
= event
->y() / row_height_
;
409 if (row
< 0 || row
>= RowCount())
412 event
->StopPropagation();
413 ui::ListSelectionModel new_model
;
414 ConfigureSelectionModelForEvent(*event
, &new_model
);
415 SetSelectionModel(new_model
);
418 bool TableView::GetTooltipText(const gfx::Point
& p
,
419 base::string16
* tooltip
) const {
420 return GetTooltipImpl(p
, tooltip
, NULL
);
423 bool TableView::GetTooltipTextOrigin(const gfx::Point
& p
,
424 gfx::Point
* loc
) const {
425 return GetTooltipImpl(p
, NULL
, loc
);
428 void TableView::GetAccessibleState(ui::AXViewState
* state
) {
429 state
->role
= ui::AX_ROLE_TABLE
;
430 state
->AddStateFlag(ui::AX_STATE_READ_ONLY
);
431 state
->count
= RowCount();
433 if (selection_model_
.active() != ui::ListSelectionModel::kUnselectedIndex
) {
434 // Get information about the active item, this is not the same as the set
435 // of selected items (of which there could be more than one).
436 state
->role
= ui::AX_ROLE_ROW
;
437 state
->index
= selection_model_
.active();
438 if (selection_model_
.IsSelected(selection_model_
.active())) {
439 state
->AddStateFlag(ui::AX_STATE_SELECTED
);
442 std::vector
<base::string16
> name_parts
;
443 for (const VisibleColumn
& visible_column
: visible_columns_
) {
444 base::string16 value
= model_
->GetText(
445 selection_model_
.active(), visible_column
.column
.id
);
446 if (!value
.empty()) {
447 name_parts
.push_back(visible_column
.column
.title
);
448 name_parts
.push_back(value
);
451 state
->name
= base::JoinString(name_parts
, base::ASCIIToUTF16(", "));
455 void TableView::OnModelChanged() {
456 selection_model_
.Clear();
460 void TableView::OnItemsChanged(int start
, int length
) {
461 SortItemsAndUpdateMapping();
464 void TableView::OnItemsAdded(int start
, int length
) {
465 for (int i
= 0; i
< length
; ++i
)
466 selection_model_
.IncrementFrom(start
);
470 void TableView::OnItemsRemoved(int start
, int length
) {
471 // Determine the currently selected index in terms of the view. We inline the
472 // implementation here since ViewToModel() has DCHECKs that fail since the
473 // model has changed but |model_to_view_| has not been updated yet.
474 const int previously_selected_model_index
= FirstSelectedRow();
475 int previously_selected_view_index
= previously_selected_model_index
;
476 if (previously_selected_model_index
!= -1 && is_sorted())
477 previously_selected_view_index
=
478 model_to_view_
[previously_selected_model_index
];
479 for (int i
= 0; i
< length
; ++i
)
480 selection_model_
.DecrementFrom(start
);
482 // If the selection was empty and is no longer empty select the same visual
484 if (selection_model_
.empty() && previously_selected_view_index
!= -1 &&
486 selection_model_
.SetSelectedIndex(
487 ViewToModel(std::min(RowCount() - 1, previously_selected_view_index
)));
489 if (!selection_model_
.empty() && selection_model_
.active() == -1)
490 selection_model_
.set_active(FirstSelectedRow());
491 if (!selection_model_
.empty() && selection_model_
.anchor() == -1)
492 selection_model_
.set_anchor(FirstSelectedRow());
493 if (table_view_observer_
)
494 table_view_observer_
->OnSelectionChanged();
497 gfx::Point
TableView::GetKeyboardContextMenuLocation() {
498 int first_selected
= FirstSelectedRow();
499 gfx::Rect
vis_bounds(GetVisibleBounds());
500 int y
= vis_bounds
.height() / 2;
501 if (first_selected
!= -1) {
502 gfx::Rect
cell_bounds(GetRowBounds(first_selected
));
503 if (cell_bounds
.bottom() >= vis_bounds
.y() &&
504 cell_bounds
.bottom() < vis_bounds
.bottom()) {
505 y
= cell_bounds
.bottom();
508 gfx::Point
screen_loc(0, y
);
509 if (base::i18n::IsRTL())
510 screen_loc
.set_x(width());
511 ConvertPointToScreen(this, &screen_loc
);
515 void TableView::OnPaint(gfx::Canvas
* canvas
) {
516 // Don't invoke View::OnPaint so that we can render our own focus border.
518 canvas
->DrawColor(GetNativeTheme()->GetSystemColor(
519 ui::NativeTheme::kColorId_TableBackground
));
521 if (!RowCount() || visible_columns_
.empty())
524 const PaintRegion
region(GetPaintRegion(GetPaintBounds(canvas
)));
525 if (region
.min_column
== -1)
526 return; // No need to paint anything.
528 const SkColor selected_bg_color
= GetNativeTheme()->GetSystemColor(
529 text_background_color_id(HasFocus()));
530 const SkColor fg_color
= GetNativeTheme()->GetSystemColor(
531 ui::NativeTheme::kColorId_TableText
);
532 const SkColor selected_fg_color
= GetNativeTheme()->GetSystemColor(
533 selected_text_color_id(HasFocus()));
534 for (int i
= region
.min_row
; i
< region
.max_row
; ++i
) {
535 const int model_index
= ViewToModel(i
);
536 const bool is_selected
= selection_model_
.IsSelected(model_index
);
538 canvas
->FillRect(GetRowBounds(i
), selected_bg_color
);
539 } else if (row_background_painter_
) {
540 row_background_painter_
->PaintRowBackground(model_index
,
544 if (selection_model_
.active() == i
&& HasFocus())
545 canvas
->DrawFocusRect(GetRowBounds(i
));
546 for (int j
= region
.min_column
; j
< region
.max_column
; ++j
) {
547 const gfx::Rect
cell_bounds(GetCellBounds(i
, j
));
548 int text_x
= kTextHorizontalPadding
+ cell_bounds
.x();
550 // Provide space for the grouping indicator, but draw it separately.
551 if (j
== 0 && grouper_
)
552 text_x
+= kGroupingIndicatorSize
+ kTextHorizontalPadding
;
554 // Always paint the icon in the first visible column.
555 if (j
== 0 && table_type_
== ICON_AND_TEXT
) {
556 gfx::ImageSkia image
= model_
->GetIcon(model_index
);
557 if (!image
.isNull()) {
558 int image_x
= GetMirroredXWithWidthInView(text_x
, kImageSize
);
559 canvas
->DrawImageInt(
560 image
, 0, 0, image
.width(), image
.height(),
562 cell_bounds
.y() + (cell_bounds
.height() - kImageSize
) / 2,
563 kImageSize
, kImageSize
, true);
565 text_x
+= kImageSize
+ kTextHorizontalPadding
;
567 if (text_x
< cell_bounds
.right() - kTextHorizontalPadding
) {
568 canvas
->DrawStringRectWithFlags(
569 model_
->GetText(model_index
, visible_columns_
[j
].column
.id
),
570 font_list_
, is_selected
? selected_fg_color
: fg_color
,
571 gfx::Rect(GetMirroredXWithWidthInView(
572 text_x
, cell_bounds
.right() - text_x
- kTextHorizontalPadding
),
573 cell_bounds
.y() + kTextVerticalPadding
,
574 cell_bounds
.right() - text_x
,
575 cell_bounds
.height() - kTextVerticalPadding
* 2),
576 TableColumnAlignmentToCanvasAlignment(
577 visible_columns_
[j
].column
.alignment
));
582 if (!grouper_
|| region
.min_column
> 0)
585 const SkColor grouping_color
= GetNativeTheme()->GetSystemColor(
586 ui::NativeTheme::kColorId_TableGroupingIndicatorColor
);
587 SkPaint grouping_paint
;
588 grouping_paint
.setColor(grouping_color
);
589 grouping_paint
.setStyle(SkPaint::kFill_Style
);
590 grouping_paint
.setAntiAlias(true);
591 const int group_indicator_x
= GetMirroredXInView(GetCellBounds(0, 0).x() +
592 kTextHorizontalPadding
+ kGroupingIndicatorSize
/ 2);
593 for (int i
= region
.min_row
; i
< region
.max_row
; ) {
594 const int model_index
= ViewToModel(i
);
596 grouper_
->GetGroupRange(model_index
, &range
);
597 DCHECK_GT(range
.length
, 0);
598 // The order of rows in a group is consistent regardless of sort, so it's ok
599 // to do this calculation.
600 const int start
= i
- (model_index
- range
.start
);
601 const int last
= start
+ range
.length
- 1;
602 const gfx::Rect
start_cell_bounds(GetCellBounds(start
, 0));
604 const gfx::Rect
last_cell_bounds(GetCellBounds(last
, 0));
605 canvas
->FillRect(gfx::Rect(
606 group_indicator_x
- kGroupingIndicatorSize
/ 2,
607 start_cell_bounds
.CenterPoint().y(),
608 kGroupingIndicatorSize
,
609 last_cell_bounds
.y() - start_cell_bounds
.y()),
611 canvas
->DrawCircle(gfx::Point(group_indicator_x
,
612 last_cell_bounds
.CenterPoint().y()),
613 kGroupingIndicatorSize
/ 2, grouping_paint
);
615 canvas
->DrawCircle(gfx::Point(group_indicator_x
,
616 start_cell_bounds
.CenterPoint().y()),
617 kGroupingIndicatorSize
/ 2, grouping_paint
);
622 void TableView::OnFocus() {
623 SchedulePaintForSelection();
624 NotifyAccessibilityEvent(ui::AX_EVENT_FOCUS
, true);
627 void TableView::OnBlur() {
628 SchedulePaintForSelection();
631 void TableView::NumRowsChanged() {
632 SortItemsAndUpdateMapping();
633 PreferredSizeChanged();
637 void TableView::SetSortDescriptors(const SortDescriptors
& sort_descriptors
) {
638 sort_descriptors_
= sort_descriptors
;
639 SortItemsAndUpdateMapping();
641 header_
->SchedulePaint();
644 void TableView::SortItemsAndUpdateMapping() {
646 view_to_model_
.clear();
647 model_to_view_
.clear();
649 const int row_count
= RowCount();
650 view_to_model_
.resize(row_count
);
651 model_to_view_
.resize(row_count
);
652 for (int i
= 0; i
< row_count
; ++i
)
653 view_to_model_
[i
] = i
;
655 GroupSortHelper
sort_helper(this);
656 GetModelIndexToRangeStart(grouper_
, RowCount(),
657 &sort_helper
.model_index_to_range_start
);
658 std::sort(view_to_model_
.begin(), view_to_model_
.end(), sort_helper
);
660 std::sort(view_to_model_
.begin(), view_to_model_
.end(), SortHelper(this));
662 for (int i
= 0; i
< row_count
; ++i
)
663 model_to_view_
[view_to_model_
[i
]] = i
;
664 model_
->ClearCollator();
669 int TableView::CompareRows(int model_row1
, int model_row2
) {
670 const int sort_result
= model_
->CompareValues(
671 model_row1
, model_row2
, sort_descriptors_
[0].column_id
);
672 if (sort_result
== 0 && sort_descriptors_
.size() > 1) {
673 // Try the secondary sort.
674 return SwapCompareResult(
675 model_
->CompareValues(model_row1
, model_row2
,
676 sort_descriptors_
[1].column_id
),
677 sort_descriptors_
[1].ascending
);
679 return SwapCompareResult(sort_result
, sort_descriptors_
[0].ascending
);
682 gfx::Rect
TableView::GetRowBounds(int row
) const {
683 return gfx::Rect(0, row
* row_height_
, width(), row_height_
);
686 gfx::Rect
TableView::GetCellBounds(int row
, int visible_column_index
) const {
688 return GetRowBounds(row
);
689 const VisibleColumn
& vis_col(visible_columns_
[visible_column_index
]);
690 return gfx::Rect(vis_col
.x
, row
* row_height_
, vis_col
.width
, row_height_
);
693 void TableView::AdjustCellBoundsForText(int visible_column_index
,
694 gfx::Rect
* bounds
) const {
695 int text_x
= kTextHorizontalPadding
+ bounds
->x();
696 if (visible_column_index
== 0) {
698 text_x
+= kGroupingIndicatorSize
+ kTextHorizontalPadding
;
699 if (table_type_
== ICON_AND_TEXT
)
700 text_x
+= kImageSize
+ kTextHorizontalPadding
;
702 bounds
->set_x(text_x
);
704 std::max(0, bounds
->right() - kTextHorizontalPadding
- text_x
));
707 void TableView::CreateHeaderIfNecessary() {
708 // Only create a header if there is more than one column or the title of the
709 // only column is not empty.
710 if (header_
|| (columns_
.size() == 1 && columns_
[0].title
.empty()))
713 header_
= new TableHeader(this);
716 void TableView::UpdateVisibleColumnSizes() {
720 std::vector
<ui::TableColumn
> columns
;
721 for (size_t i
= 0; i
< visible_columns_
.size(); ++i
)
722 columns
.push_back(visible_columns_
[i
].column
);
724 int first_column_padding
= 0;
725 if (table_type_
== ICON_AND_TEXT
&& header_
)
726 first_column_padding
+= kImageSize
+ kTextHorizontalPadding
;
728 first_column_padding
+= kGroupingIndicatorSize
+ kTextHorizontalPadding
;
730 std::vector
<int> sizes
= views::CalculateTableColumnSizes(
731 layout_width_
, first_column_padding
, header_
->font_list(), font_list_
,
732 std::max(kTextHorizontalPadding
, TableHeader::kHorizontalPadding
) * 2,
733 TableHeader::kSortIndicatorWidth
, columns
, model_
);
734 DCHECK_EQ(visible_columns_
.size(), sizes
.size());
736 for (size_t i
= 0; i
< visible_columns_
.size(); ++i
) {
737 visible_columns_
[i
].x
= x
;
738 visible_columns_
[i
].width
= sizes
[i
];
743 TableView::PaintRegion
TableView::GetPaintRegion(
744 const gfx::Rect
& bounds
) const {
745 DCHECK(!visible_columns_
.empty());
749 region
.min_row
= std::min(RowCount() - 1,
750 std::max(0, bounds
.y() / row_height_
));
751 region
.max_row
= bounds
.bottom() / row_height_
;
752 if (bounds
.bottom() % row_height_
!= 0)
754 region
.max_row
= std::min(region
.max_row
, RowCount());
757 region
.max_column
= 1;
761 const int paint_x
= GetMirroredXForRect(bounds
);
762 const int paint_max_x
= paint_x
+ bounds
.width();
763 region
.min_column
= -1;
764 region
.max_column
= visible_columns_
.size();
765 for (size_t i
= 0; i
< visible_columns_
.size(); ++i
) {
766 int max_x
= visible_columns_
[i
].x
+ visible_columns_
[i
].width
;
767 if (region
.min_column
== -1 && max_x
>= paint_x
)
768 region
.min_column
= static_cast<int>(i
);
769 if (region
.min_column
!= -1 && visible_columns_
[i
].x
>= paint_max_x
) {
770 region
.max_column
= i
;
777 gfx::Rect
TableView::GetPaintBounds(gfx::Canvas
* canvas
) const {
779 if (canvas
->sk_canvas()->getClipBounds(&sk_clip_rect
))
780 return gfx::ToEnclosingRect(gfx::SkRectToRectF(sk_clip_rect
));
781 return GetVisibleBounds();
784 void TableView::SchedulePaintForSelection() {
785 if (selection_model_
.size() == 1) {
786 const int first_model_row
= FirstSelectedRow();
787 SchedulePaintInRect(GetRowBounds(ModelToView(first_model_row
)));
788 if (first_model_row
!= selection_model_
.active())
789 SchedulePaintInRect(GetRowBounds(ModelToView(selection_model_
.active())));
790 } else if (selection_model_
.size() > 1) {
795 ui::TableColumn
TableView::FindColumnByID(int id
) const {
796 for (size_t i
= 0; i
< columns_
.size(); ++i
) {
797 if (columns_
[i
].id
== id
)
801 return ui::TableColumn();
804 void TableView::SelectByViewIndex(int view_index
) {
805 ui::ListSelectionModel new_selection
;
806 if (view_index
!= -1) {
807 SelectRowsInRangeFrom(view_index
, true, &new_selection
);
808 new_selection
.set_anchor(ViewToModel(view_index
));
809 new_selection
.set_active(ViewToModel(view_index
));
812 SetSelectionModel(new_selection
);
815 void TableView::SetSelectionModel(const ui::ListSelectionModel
& new_selection
) {
816 if (new_selection
.Equals(selection_model_
))
819 SchedulePaintForSelection();
820 selection_model_
.Copy(new_selection
);
821 SchedulePaintForSelection();
823 // Scroll the group for the active item to visible.
824 if (selection_model_
.active() != -1) {
825 gfx::Rect
vis_rect(GetVisibleBounds());
826 const GroupRange
range(GetGroupRange(selection_model_
.active()));
827 const int start_y
= GetRowBounds(ModelToView(range
.start
)).y();
829 GetRowBounds(ModelToView(range
.start
+ range
.length
- 1)).bottom();
830 vis_rect
.set_y(start_y
);
831 vis_rect
.set_height(end_y
- start_y
);
832 ScrollRectToVisible(vis_rect
);
835 if (table_view_observer_
)
836 table_view_observer_
->OnSelectionChanged();
838 NotifyAccessibilityEvent(ui::AX_EVENT_FOCUS
, true);
841 void TableView::AdvanceSelection(AdvanceDirection direction
) {
842 if (selection_model_
.active() == -1) {
843 SelectByViewIndex(0);
846 int view_index
= ModelToView(selection_model_
.active());
847 if (direction
== ADVANCE_DECREMENT
)
848 view_index
= std::max(0, view_index
- 1);
850 view_index
= std::min(RowCount() - 1, view_index
+ 1);
851 SelectByViewIndex(view_index
);
854 void TableView::ConfigureSelectionModelForEvent(
855 const ui::LocatedEvent
& event
,
856 ui::ListSelectionModel
* model
) const {
857 const int view_index
= event
.y() / row_height_
;
858 DCHECK(view_index
>= 0 && view_index
< RowCount());
860 if (selection_model_
.anchor() == -1 ||
862 (!event
.IsControlDown() && !event
.IsShiftDown())) {
863 SelectRowsInRangeFrom(view_index
, true, model
);
864 model
->set_anchor(ViewToModel(view_index
));
865 model
->set_active(ViewToModel(view_index
));
868 if ((event
.IsControlDown() && event
.IsShiftDown()) || event
.IsShiftDown()) {
869 // control-shift: copy existing model and make sure rows between anchor and
870 // |view_index| are selected.
871 // shift: reset selection so that only rows between anchor and |view_index|
873 if (event
.IsControlDown() && event
.IsShiftDown())
874 model
->Copy(selection_model_
);
876 model
->set_anchor(selection_model_
.anchor());
877 for (int i
= std::min(view_index
, ModelToView(model
->anchor())),
878 end
= std::max(view_index
, ModelToView(model
->anchor()));
880 SelectRowsInRangeFrom(i
, true, model
);
882 model
->set_active(ViewToModel(view_index
));
884 DCHECK(event
.IsControlDown());
885 // Toggle the selection state of |view_index| and set the anchor/active to
886 // it and don't change the state of any other rows.
887 model
->Copy(selection_model_
);
888 model
->set_anchor(ViewToModel(view_index
));
889 model
->set_active(ViewToModel(view_index
));
890 SelectRowsInRangeFrom(view_index
,
891 !model
->IsSelected(ViewToModel(view_index
)),
896 void TableView::SelectRowsInRangeFrom(int view_index
,
898 ui::ListSelectionModel
* model
) const {
899 const GroupRange
range(GetGroupRange(ViewToModel(view_index
)));
900 for (int i
= 0; i
< range
.length
; ++i
) {
902 model
->AddIndexToSelection(range
.start
+ i
);
904 model
->RemoveIndexFromSelection(range
.start
+ i
);
908 GroupRange
TableView::GetGroupRange(int model_index
) const {
911 grouper_
->GetGroupRange(model_index
, &range
);
913 range
.start
= model_index
;
919 bool TableView::GetTooltipImpl(const gfx::Point
& location
,
920 base::string16
* tooltip
,
921 gfx::Point
* tooltip_origin
) const {
922 const int row
= location
.y() / row_height_
;
923 if (row
< 0 || row
>= RowCount() || visible_columns_
.empty())
926 const int x
= GetMirroredXInView(location
.x());
927 const int column
= GetClosestVisibleColumnIndex(this, x
);
928 if (x
< visible_columns_
[column
].x
||
929 x
> (visible_columns_
[column
].x
+ visible_columns_
[column
].width
))
932 const base::string16
text(model_
->GetText(ViewToModel(row
),
933 visible_columns_
[column
].column
.id
));
937 gfx::Rect
cell_bounds(GetCellBounds(row
, column
));
938 AdjustCellBoundsForText(column
, &cell_bounds
);
939 const int right
= std::min(GetVisibleBounds().right(), cell_bounds
.right());
940 if (right
> cell_bounds
.x() &&
941 gfx::GetStringWidth(text
, font_list_
) <= (right
- cell_bounds
.x()))
946 if (tooltip_origin
) {
947 tooltip_origin
->SetPoint(cell_bounds
.x(),
948 cell_bounds
.y() + kTextVerticalPadding
);