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
, true);
239 sort
.insert(sort
.begin(), descriptor
);
240 // Only persist two sort descriptors.
244 SetSortDescriptors(sort
);
247 bool TableView::IsColumnVisible(int id
) const {
248 for (size_t i
= 0; i
< visible_columns_
.size(); ++i
) {
249 if (visible_columns_
[i
].column
.id
== id
)
255 void TableView::AddColumn(const ui::TableColumn
& col
) {
256 DCHECK(!HasColumn(col
.id
));
257 columns_
.push_back(col
);
260 bool TableView::HasColumn(int id
) const {
261 for (size_t i
= 0; i
< columns_
.size(); ++i
) {
262 if (columns_
[i
].id
== id
)
268 void TableView::SetVisibleColumnWidth(int index
, int width
) {
269 DCHECK(index
>= 0 && index
< static_cast<int>(visible_columns_
.size()));
270 if (visible_columns_
[index
].width
== width
)
272 base::AutoReset
<bool> reseter(&in_set_visible_column_width_
, true);
273 visible_columns_
[index
].width
= width
;
274 for (size_t i
= index
+ 1; i
< visible_columns_
.size(); ++i
) {
275 visible_columns_
[i
].x
=
276 visible_columns_
[i
- 1].x
+ visible_columns_
[i
- 1].width
;
278 PreferredSizeChanged();
282 int TableView::ModelToView(int model_index
) const {
285 DCHECK_GE(model_index
, 0) << " negative model_index " << model_index
;
286 DCHECK_LT(model_index
, RowCount()) << " out of bounds model_index " <<
288 return model_to_view_
[model_index
];
291 int TableView::ViewToModel(int view_index
) const {
294 DCHECK_GE(view_index
, 0) << " negative view_index " << view_index
;
295 DCHECK_LT(view_index
, RowCount()) << " out of bounds view_index " <<
297 return view_to_model_
[view_index
];
300 void TableView::Layout() {
301 // parent()->parent() is the scrollview. When its width changes we force
302 // recalculating column sizes.
303 View
* scroll_view
= parent() ? parent()->parent() : NULL
;
305 const int scroll_view_width
= scroll_view
->GetContentsBounds().width();
306 if (scroll_view_width
!= last_parent_width_
) {
307 last_parent_width_
= scroll_view_width
;
308 if (!in_set_visible_column_width_
) {
309 // Layout to the parent (the Viewport), which differs from
310 // |scroll_view_width| when scrollbars are present.
311 layout_width_
= parent()->width();
312 UpdateVisibleColumnSizes();
316 // We have to override Layout like this since we're contained in a ScrollView.
317 gfx::Size pref
= GetPreferredSize();
318 int width
= pref
.width();
319 int height
= pref
.height();
321 width
= std::max(parent()->width(), width
);
322 height
= std::max(parent()->height(), height
);
324 SetBounds(x(), y(), width
, height
);
327 const char* TableView::GetClassName() const {
328 return kViewClassName
;
331 gfx::Size
TableView::GetPreferredSize() const {
333 if (header_
&& !visible_columns_
.empty())
334 width
= visible_columns_
.back().x
+ visible_columns_
.back().width
;
335 return gfx::Size(width
, RowCount() * row_height_
);
338 bool TableView::OnKeyPressed(const ui::KeyEvent
& event
) {
342 switch (event
.key_code()) {
344 // control-a selects all.
345 if (event
.IsControlDown() && !single_selection_
&& RowCount()) {
346 ui::ListSelectionModel selection_model
;
347 selection_model
.SetSelectedIndex(selection_model_
.active());
348 for (int i
= 0; i
< RowCount(); ++i
)
349 selection_model
.AddIndexToSelection(i
);
350 SetSelectionModel(selection_model
);
357 SelectByViewIndex(0);
362 SelectByViewIndex(RowCount() - 1);
366 AdvanceSelection(ADVANCE_DECREMENT
);
370 AdvanceSelection(ADVANCE_INCREMENT
);
376 if (table_view_observer_
)
377 table_view_observer_
->OnKeyDown(event
.key_code());
381 bool TableView::OnMousePressed(const ui::MouseEvent
& event
) {
383 if (!event
.IsOnlyLeftMouseButton())
386 const int row
= event
.y() / row_height_
;
387 if (row
< 0 || row
>= RowCount())
390 if (event
.GetClickCount() == 2) {
391 SelectByViewIndex(row
);
392 if (table_view_observer_
)
393 table_view_observer_
->OnDoubleClick();
394 } else if (event
.GetClickCount() == 1) {
395 ui::ListSelectionModel new_model
;
396 ConfigureSelectionModelForEvent(event
, &new_model
);
397 SetSelectionModel(new_model
);
403 void TableView::OnGestureEvent(ui::GestureEvent
* event
) {
404 if (event
->type() != ui::ET_GESTURE_TAP
)
407 const int row
= event
->y() / row_height_
;
408 if (row
< 0 || row
>= RowCount())
411 event
->StopPropagation();
412 ui::ListSelectionModel new_model
;
413 ConfigureSelectionModelForEvent(*event
, &new_model
);
414 SetSelectionModel(new_model
);
417 bool TableView::GetTooltipText(const gfx::Point
& p
,
418 base::string16
* tooltip
) const {
419 return GetTooltipImpl(p
, tooltip
, NULL
);
422 bool TableView::GetTooltipTextOrigin(const gfx::Point
& p
,
423 gfx::Point
* loc
) const {
424 return GetTooltipImpl(p
, NULL
, loc
);
427 void TableView::GetAccessibleState(ui::AXViewState
* state
) {
428 state
->role
= ui::AX_ROLE_TABLE
;
429 state
->AddStateFlag(ui::AX_STATE_READ_ONLY
);
430 state
->count
= RowCount();
432 if (selection_model_
.active() != ui::ListSelectionModel::kUnselectedIndex
) {
433 // Get information about the active item, this is not the same as the set
434 // of selected items (of which there could be more than one).
435 state
->role
= ui::AX_ROLE_ROW
;
436 state
->index
= selection_model_
.active();
437 if (selection_model_
.IsSelected(selection_model_
.active())) {
438 state
->AddStateFlag(ui::AX_STATE_SELECTED
);
441 std::vector
<base::string16
> name_parts
;
442 for (const VisibleColumn
& visible_column
: visible_columns_
) {
443 base::string16 value
= model_
->GetText(
444 selection_model_
.active(), visible_column
.column
.id
);
445 if (!value
.empty()) {
446 name_parts
.push_back(visible_column
.column
.title
);
447 name_parts
.push_back(value
);
450 state
->name
= JoinString(name_parts
, base::ASCIIToUTF16(", "));
454 void TableView::OnModelChanged() {
455 selection_model_
.Clear();
459 void TableView::OnItemsChanged(int start
, int length
) {
460 SortItemsAndUpdateMapping();
463 void TableView::OnItemsAdded(int start
, int length
) {
464 for (int i
= 0; i
< length
; ++i
)
465 selection_model_
.IncrementFrom(start
);
469 void TableView::OnItemsRemoved(int start
, int length
) {
470 // Determine the currently selected index in terms of the view. We inline the
471 // implementation here since ViewToModel() has DCHECKs that fail since the
472 // model has changed but |model_to_view_| has not been updated yet.
473 const int previously_selected_model_index
= FirstSelectedRow();
474 int previously_selected_view_index
= previously_selected_model_index
;
475 if (previously_selected_model_index
!= -1 && is_sorted())
476 previously_selected_view_index
=
477 model_to_view_
[previously_selected_model_index
];
478 for (int i
= 0; i
< length
; ++i
)
479 selection_model_
.DecrementFrom(start
);
481 // If the selection was empty and is no longer empty select the same visual
483 if (selection_model_
.empty() && previously_selected_view_index
!= -1 &&
485 selection_model_
.SetSelectedIndex(
486 ViewToModel(std::min(RowCount() - 1, previously_selected_view_index
)));
488 if (table_view_observer_
)
489 table_view_observer_
->OnSelectionChanged();
492 gfx::Point
TableView::GetKeyboardContextMenuLocation() {
493 int first_selected
= FirstSelectedRow();
494 gfx::Rect
vis_bounds(GetVisibleBounds());
495 int y
= vis_bounds
.height() / 2;
496 if (first_selected
!= -1) {
497 gfx::Rect
cell_bounds(GetRowBounds(first_selected
));
498 if (cell_bounds
.bottom() >= vis_bounds
.y() &&
499 cell_bounds
.bottom() < vis_bounds
.bottom()) {
500 y
= cell_bounds
.bottom();
503 gfx::Point
screen_loc(0, y
);
504 if (base::i18n::IsRTL())
505 screen_loc
.set_x(width());
506 ConvertPointToScreen(this, &screen_loc
);
510 void TableView::OnPaint(gfx::Canvas
* canvas
) {
511 // Don't invoke View::OnPaint so that we can render our own focus border.
513 canvas
->DrawColor(GetNativeTheme()->GetSystemColor(
514 ui::NativeTheme::kColorId_TableBackground
));
516 if (!RowCount() || visible_columns_
.empty())
519 const PaintRegion
region(GetPaintRegion(GetPaintBounds(canvas
)));
520 if (region
.min_column
== -1)
521 return; // No need to paint anything.
523 const SkColor selected_bg_color
= GetNativeTheme()->GetSystemColor(
524 text_background_color_id(HasFocus()));
525 const SkColor fg_color
= GetNativeTheme()->GetSystemColor(
526 ui::NativeTheme::kColorId_TableText
);
527 const SkColor selected_fg_color
= GetNativeTheme()->GetSystemColor(
528 selected_text_color_id(HasFocus()));
529 for (int i
= region
.min_row
; i
< region
.max_row
; ++i
) {
530 const int model_index
= ViewToModel(i
);
531 const bool is_selected
= selection_model_
.IsSelected(model_index
);
533 canvas
->FillRect(GetRowBounds(i
), selected_bg_color
);
534 } else if (row_background_painter_
) {
535 row_background_painter_
->PaintRowBackground(model_index
,
539 if (selection_model_
.active() == i
&& HasFocus())
540 canvas
->DrawFocusRect(GetRowBounds(i
));
541 for (int j
= region
.min_column
; j
< region
.max_column
; ++j
) {
542 const gfx::Rect
cell_bounds(GetCellBounds(i
, j
));
543 int text_x
= kTextHorizontalPadding
+ cell_bounds
.x();
545 // Provide space for the grouping indicator, but draw it separately.
546 if (j
== 0 && grouper_
)
547 text_x
+= kGroupingIndicatorSize
+ kTextHorizontalPadding
;
549 // Always paint the icon in the first visible column.
550 if (j
== 0 && table_type_
== ICON_AND_TEXT
) {
551 gfx::ImageSkia image
= model_
->GetIcon(model_index
);
552 if (!image
.isNull()) {
553 int image_x
= GetMirroredXWithWidthInView(text_x
, kImageSize
);
554 canvas
->DrawImageInt(
555 image
, 0, 0, image
.width(), image
.height(),
557 cell_bounds
.y() + (cell_bounds
.height() - kImageSize
) / 2,
558 kImageSize
, kImageSize
, true);
560 text_x
+= kImageSize
+ kTextHorizontalPadding
;
562 if (text_x
< cell_bounds
.right() - kTextHorizontalPadding
) {
563 canvas
->DrawStringRectWithFlags(
564 model_
->GetText(model_index
, visible_columns_
[j
].column
.id
),
565 font_list_
, is_selected
? selected_fg_color
: fg_color
,
566 gfx::Rect(GetMirroredXWithWidthInView(
567 text_x
, cell_bounds
.right() - text_x
- kTextHorizontalPadding
),
568 cell_bounds
.y() + kTextVerticalPadding
,
569 cell_bounds
.right() - text_x
,
570 cell_bounds
.height() - kTextVerticalPadding
* 2),
571 TableColumnAlignmentToCanvasAlignment(
572 visible_columns_
[j
].column
.alignment
));
577 if (!grouper_
|| region
.min_column
> 0)
580 const SkColor grouping_color
= GetNativeTheme()->GetSystemColor(
581 ui::NativeTheme::kColorId_TableGroupingIndicatorColor
);
582 SkPaint grouping_paint
;
583 grouping_paint
.setColor(grouping_color
);
584 grouping_paint
.setStyle(SkPaint::kFill_Style
);
585 grouping_paint
.setAntiAlias(true);
586 const int group_indicator_x
= GetMirroredXInView(GetCellBounds(0, 0).x() +
587 kTextHorizontalPadding
+ kGroupingIndicatorSize
/ 2);
588 for (int i
= region
.min_row
; i
< region
.max_row
; ) {
589 const int model_index
= ViewToModel(i
);
591 grouper_
->GetGroupRange(model_index
, &range
);
592 DCHECK_GT(range
.length
, 0);
593 // The order of rows in a group is consistent regardless of sort, so it's ok
594 // to do this calculation.
595 const int start
= i
- (model_index
- range
.start
);
596 const int last
= start
+ range
.length
- 1;
597 const gfx::Rect
start_cell_bounds(GetCellBounds(start
, 0));
599 const gfx::Rect
last_cell_bounds(GetCellBounds(last
, 0));
600 canvas
->FillRect(gfx::Rect(
601 group_indicator_x
- kGroupingIndicatorSize
/ 2,
602 start_cell_bounds
.CenterPoint().y(),
603 kGroupingIndicatorSize
,
604 last_cell_bounds
.y() - start_cell_bounds
.y()),
606 canvas
->DrawCircle(gfx::Point(group_indicator_x
,
607 last_cell_bounds
.CenterPoint().y()),
608 kGroupingIndicatorSize
/ 2, grouping_paint
);
610 canvas
->DrawCircle(gfx::Point(group_indicator_x
,
611 start_cell_bounds
.CenterPoint().y()),
612 kGroupingIndicatorSize
/ 2, grouping_paint
);
617 void TableView::OnFocus() {
618 SchedulePaintForSelection();
619 NotifyAccessibilityEvent(ui::AX_EVENT_FOCUS
, true);
622 void TableView::OnBlur() {
623 SchedulePaintForSelection();
626 void TableView::NumRowsChanged() {
627 SortItemsAndUpdateMapping();
628 PreferredSizeChanged();
632 void TableView::SetSortDescriptors(const SortDescriptors
& sort_descriptors
) {
633 sort_descriptors_
= sort_descriptors
;
634 SortItemsAndUpdateMapping();
636 header_
->SchedulePaint();
639 void TableView::SortItemsAndUpdateMapping() {
641 view_to_model_
.clear();
642 model_to_view_
.clear();
644 const int row_count
= RowCount();
645 view_to_model_
.resize(row_count
);
646 model_to_view_
.resize(row_count
);
647 for (int i
= 0; i
< row_count
; ++i
)
648 view_to_model_
[i
] = i
;
650 GroupSortHelper
sort_helper(this);
651 GetModelIndexToRangeStart(grouper_
, RowCount(),
652 &sort_helper
.model_index_to_range_start
);
653 std::sort(view_to_model_
.begin(), view_to_model_
.end(), sort_helper
);
655 std::sort(view_to_model_
.begin(), view_to_model_
.end(), SortHelper(this));
657 for (int i
= 0; i
< row_count
; ++i
)
658 model_to_view_
[view_to_model_
[i
]] = i
;
659 model_
->ClearCollator();
664 int TableView::CompareRows(int model_row1
, int model_row2
) {
665 const int sort_result
= model_
->CompareValues(
666 model_row1
, model_row2
, sort_descriptors_
[0].column_id
);
667 if (sort_result
== 0 && sort_descriptors_
.size() > 1) {
668 // Try the secondary sort.
669 return SwapCompareResult(
670 model_
->CompareValues(model_row1
, model_row2
,
671 sort_descriptors_
[1].column_id
),
672 sort_descriptors_
[1].ascending
);
674 return SwapCompareResult(sort_result
, sort_descriptors_
[0].ascending
);
677 gfx::Rect
TableView::GetRowBounds(int row
) const {
678 return gfx::Rect(0, row
* row_height_
, width(), row_height_
);
681 gfx::Rect
TableView::GetCellBounds(int row
, int visible_column_index
) const {
683 return GetRowBounds(row
);
684 const VisibleColumn
& vis_col(visible_columns_
[visible_column_index
]);
685 return gfx::Rect(vis_col
.x
, row
* row_height_
, vis_col
.width
, row_height_
);
688 void TableView::AdjustCellBoundsForText(int visible_column_index
,
689 gfx::Rect
* bounds
) const {
690 int text_x
= kTextHorizontalPadding
+ bounds
->x();
691 if (visible_column_index
== 0) {
693 text_x
+= kGroupingIndicatorSize
+ kTextHorizontalPadding
;
694 if (table_type_
== ICON_AND_TEXT
)
695 text_x
+= kImageSize
+ kTextHorizontalPadding
;
697 bounds
->set_x(text_x
);
699 std::max(0, bounds
->right() - kTextHorizontalPadding
- text_x
));
702 void TableView::CreateHeaderIfNecessary() {
703 // Only create a header if there is more than one column or the title of the
704 // only column is not empty.
705 if (header_
|| (columns_
.size() == 1 && columns_
[0].title
.empty()))
708 header_
= new TableHeader(this);
711 void TableView::UpdateVisibleColumnSizes() {
715 std::vector
<ui::TableColumn
> columns
;
716 for (size_t i
= 0; i
< visible_columns_
.size(); ++i
)
717 columns
.push_back(visible_columns_
[i
].column
);
719 int first_column_padding
= 0;
720 if (table_type_
== ICON_AND_TEXT
&& header_
)
721 first_column_padding
+= kImageSize
+ kTextHorizontalPadding
;
723 first_column_padding
+= kGroupingIndicatorSize
+ kTextHorizontalPadding
;
725 std::vector
<int> sizes
= views::CalculateTableColumnSizes(
726 layout_width_
, first_column_padding
, header_
->font_list(), font_list_
,
727 std::max(kTextHorizontalPadding
, TableHeader::kHorizontalPadding
) * 2,
728 TableHeader::kSortIndicatorWidth
, columns
, model_
);
729 DCHECK_EQ(visible_columns_
.size(), sizes
.size());
731 for (size_t i
= 0; i
< visible_columns_
.size(); ++i
) {
732 visible_columns_
[i
].x
= x
;
733 visible_columns_
[i
].width
= sizes
[i
];
738 TableView::PaintRegion
TableView::GetPaintRegion(
739 const gfx::Rect
& bounds
) const {
740 DCHECK(!visible_columns_
.empty());
744 region
.min_row
= std::min(RowCount() - 1,
745 std::max(0, bounds
.y() / row_height_
));
746 region
.max_row
= bounds
.bottom() / row_height_
;
747 if (bounds
.bottom() % row_height_
!= 0)
749 region
.max_row
= std::min(region
.max_row
, RowCount());
752 region
.max_column
= 1;
756 const int paint_x
= GetMirroredXForRect(bounds
);
757 const int paint_max_x
= paint_x
+ bounds
.width();
758 region
.min_column
= -1;
759 region
.max_column
= visible_columns_
.size();
760 for (size_t i
= 0; i
< visible_columns_
.size(); ++i
) {
761 int max_x
= visible_columns_
[i
].x
+ visible_columns_
[i
].width
;
762 if (region
.min_column
== -1 && max_x
>= paint_x
)
763 region
.min_column
= static_cast<int>(i
);
764 if (region
.min_column
!= -1 && visible_columns_
[i
].x
>= paint_max_x
) {
765 region
.max_column
= i
;
772 gfx::Rect
TableView::GetPaintBounds(gfx::Canvas
* canvas
) const {
774 if (canvas
->sk_canvas()->getClipBounds(&sk_clip_rect
))
775 return gfx::ToEnclosingRect(gfx::SkRectToRectF(sk_clip_rect
));
776 return GetVisibleBounds();
779 void TableView::SchedulePaintForSelection() {
780 if (selection_model_
.size() == 1) {
781 const int first_model_row
= FirstSelectedRow();
782 SchedulePaintInRect(GetRowBounds(ModelToView(first_model_row
)));
783 if (first_model_row
!= selection_model_
.active())
784 SchedulePaintInRect(GetRowBounds(ModelToView(selection_model_
.active())));
785 } else if (selection_model_
.size() > 1) {
790 ui::TableColumn
TableView::FindColumnByID(int id
) const {
791 for (size_t i
= 0; i
< columns_
.size(); ++i
) {
792 if (columns_
[i
].id
== id
)
796 return ui::TableColumn();
799 void TableView::SelectByViewIndex(int view_index
) {
800 ui::ListSelectionModel new_selection
;
801 if (view_index
!= -1) {
802 SelectRowsInRangeFrom(view_index
, true, &new_selection
);
803 new_selection
.set_anchor(ViewToModel(view_index
));
804 new_selection
.set_active(ViewToModel(view_index
));
807 SetSelectionModel(new_selection
);
810 void TableView::SetSelectionModel(const ui::ListSelectionModel
& new_selection
) {
811 if (new_selection
.Equals(selection_model_
))
814 SchedulePaintForSelection();
815 selection_model_
.Copy(new_selection
);
816 SchedulePaintForSelection();
818 // Scroll the group for the active item to visible.
819 if (selection_model_
.active() != -1) {
820 gfx::Rect
vis_rect(GetVisibleBounds());
821 const GroupRange
range(GetGroupRange(selection_model_
.active()));
822 const int start_y
= GetRowBounds(ModelToView(range
.start
)).y();
824 GetRowBounds(ModelToView(range
.start
+ range
.length
- 1)).bottom();
825 vis_rect
.set_y(start_y
);
826 vis_rect
.set_height(end_y
- start_y
);
827 ScrollRectToVisible(vis_rect
);
830 if (table_view_observer_
)
831 table_view_observer_
->OnSelectionChanged();
833 NotifyAccessibilityEvent(ui::AX_EVENT_FOCUS
, true);
836 void TableView::AdvanceSelection(AdvanceDirection direction
) {
837 if (selection_model_
.active() == -1) {
838 SelectByViewIndex(0);
841 int view_index
= ModelToView(selection_model_
.active());
842 if (direction
== ADVANCE_DECREMENT
)
843 view_index
= std::max(0, view_index
- 1);
845 view_index
= std::min(RowCount() - 1, view_index
+ 1);
846 SelectByViewIndex(view_index
);
849 void TableView::ConfigureSelectionModelForEvent(
850 const ui::LocatedEvent
& event
,
851 ui::ListSelectionModel
* model
) const {
852 const int view_index
= event
.y() / row_height_
;
853 DCHECK(view_index
>= 0 && view_index
< RowCount());
855 if (selection_model_
.anchor() == -1 ||
857 (!event
.IsControlDown() && !event
.IsShiftDown())) {
858 SelectRowsInRangeFrom(view_index
, true, model
);
859 model
->set_anchor(ViewToModel(view_index
));
860 model
->set_active(ViewToModel(view_index
));
863 if ((event
.IsControlDown() && event
.IsShiftDown()) || event
.IsShiftDown()) {
864 // control-shift: copy existing model and make sure rows between anchor and
865 // |view_index| are selected.
866 // shift: reset selection so that only rows between anchor and |view_index|
868 if (event
.IsControlDown() && event
.IsShiftDown())
869 model
->Copy(selection_model_
);
871 model
->set_anchor(selection_model_
.anchor());
872 for (int i
= std::min(view_index
, ModelToView(model
->anchor())),
873 end
= std::max(view_index
, ModelToView(model
->anchor()));
875 SelectRowsInRangeFrom(i
, true, model
);
877 model
->set_active(ViewToModel(view_index
));
879 DCHECK(event
.IsControlDown());
880 // Toggle the selection state of |view_index| and set the anchor/active to
881 // it and don't change the state of any other rows.
882 model
->Copy(selection_model_
);
883 model
->set_anchor(ViewToModel(view_index
));
884 model
->set_active(ViewToModel(view_index
));
885 SelectRowsInRangeFrom(view_index
,
886 !model
->IsSelected(ViewToModel(view_index
)),
891 void TableView::SelectRowsInRangeFrom(int view_index
,
893 ui::ListSelectionModel
* model
) const {
894 const GroupRange
range(GetGroupRange(ViewToModel(view_index
)));
895 for (int i
= 0; i
< range
.length
; ++i
) {
897 model
->AddIndexToSelection(range
.start
+ i
);
899 model
->RemoveIndexFromSelection(range
.start
+ i
);
903 GroupRange
TableView::GetGroupRange(int model_index
) const {
906 grouper_
->GetGroupRange(model_index
, &range
);
908 range
.start
= model_index
;
914 bool TableView::GetTooltipImpl(const gfx::Point
& location
,
915 base::string16
* tooltip
,
916 gfx::Point
* tooltip_origin
) const {
917 const int row
= location
.y() / row_height_
;
918 if (row
< 0 || row
>= RowCount() || visible_columns_
.empty())
921 const int x
= GetMirroredXInView(location
.x());
922 const int column
= GetClosestVisibleColumnIndex(this, x
);
923 if (x
< visible_columns_
[column
].x
||
924 x
> (visible_columns_
[column
].x
+ visible_columns_
[column
].width
))
927 const base::string16
text(model_
->GetText(ViewToModel(row
),
928 visible_columns_
[column
].column
.id
));
932 gfx::Rect
cell_bounds(GetCellBounds(row
, column
));
933 AdjustCellBoundsForText(column
, &cell_bounds
);
934 const int right
= std::min(GetVisibleBounds().right(), cell_bounds
.right());
935 if (right
> cell_bounds
.x() &&
936 gfx::GetStringWidth(text
, font_list_
) <= (right
- cell_bounds
.x()))
941 if (tooltip_origin
) {
942 tooltip_origin
->SetPoint(cell_bounds
.x(),
943 cell_bounds
.y() + kTextVerticalPadding
);