Add ICU message format support
[chromium-blink-merge.git] / ui / views / controls / table / table_view.cc
blob6c37e52c3a12c8fcfe3b1244fca53ff9e32d06bd
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"
7 #include <map>
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;
32 // Size of images.
33 static const int kImageSize = 16;
35 static const int kGroupingIndicatorSize = 6;
37 namespace views {
39 namespace {
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,
48 int row_count,
49 std::map<int, int>* model_index_to_range_start) {
50 for (int model_index = 0; model_index < row_count;) {
51 GroupRange range;
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) {
63 return 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;
74 } // namespace
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;
84 TableView* table;
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;
104 TableView* table;
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()
113 : min_row(0),
114 max_row(0),
115 min_column(0),
116 max_column(0) {
119 TableView::PaintRegion::~PaintRegion() {}
121 // static
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)
128 : model_(NULL),
129 columns_(columns),
130 header_(NULL),
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),
136 layout_width_(0),
137 grouper_(NULL),
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);
144 SetFocusable(true);
145 SetModel(model);
148 TableView::~TableView() {
149 if (model_)
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) {
156 if (model == model_)
157 return;
159 if (model_)
160 model_->SetObserver(NULL);
161 model_ = model;
162 selection_model_.Clear();
163 if (model_)
164 model_->SetObserver(this);
167 View* TableView::CreateParentIfNecessary() {
168 ScrollView* scroll_view = ScrollView::CreateScrollViewWithBorder();
169 scroll_view->SetContents(this);
170 CreateHeaderIfNecessary();
171 if (header_)
172 scroll_view->SetHeader(header_);
173 return scroll_view;
176 void TableView::SetRowBackgroundPainter(
177 scoped_ptr<TableViewRowBackgroundPainter> painter) {
178 row_background_painter_ = painter.Pass();
181 void TableView::SetGrouper(TableGrouper* grouper) {
182 grouper_ = 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) {
195 if (!model_)
196 return;
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))
207 return;
209 if (is_visible) {
210 VisibleColumn visible_column;
211 visible_column.column = FindColumnByID(id);
212 visible_columns_.push_back(visible_column);
213 } else {
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);
217 break;
221 UpdateVisibleColumnSizes();
222 PreferredSizeChanged();
223 SchedulePaint();
224 if (header_)
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)
232 return;
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;
237 } else {
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.
242 if (sort.size() > 2)
243 sort.resize(2);
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)
251 return true;
253 return false;
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)
264 return true;
266 return false;
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)
272 return;
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();
280 SchedulePaint();
283 int TableView::ModelToView(int model_index) const {
284 if (!is_sorted())
285 return model_index;
286 DCHECK_GE(model_index, 0) << " negative model_index " << model_index;
287 DCHECK_LT(model_index, RowCount()) << " out of bounds model_index " <<
288 model_index;
289 return model_to_view_[model_index];
292 int TableView::ViewToModel(int view_index) const {
293 if (!is_sorted())
294 return view_index;
295 DCHECK_GE(view_index, 0) << " negative view_index " << view_index;
296 DCHECK_LT(view_index, RowCount()) << " out of bounds view_index " <<
297 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;
305 if (scroll_view) {
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();
321 if (parent()) {
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 {
333 int width = 50;
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) {
340 if (!HasFocus())
341 return false;
343 switch (event.key_code()) {
344 case ui::VKEY_A:
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);
352 return true;
354 break;
356 case ui::VKEY_HOME:
357 if (RowCount())
358 SelectByViewIndex(0);
359 return true;
361 case ui::VKEY_END:
362 if (RowCount())
363 SelectByViewIndex(RowCount() - 1);
364 return true;
366 case ui::VKEY_UP:
367 AdvanceSelection(ADVANCE_DECREMENT);
368 return true;
370 case ui::VKEY_DOWN:
371 AdvanceSelection(ADVANCE_INCREMENT);
372 return true;
374 default:
375 break;
377 if (table_view_observer_)
378 table_view_observer_->OnKeyDown(event.key_code());
379 return false;
382 bool TableView::OnMousePressed(const ui::MouseEvent& event) {
383 RequestFocus();
384 if (!event.IsOnlyLeftMouseButton())
385 return true;
387 const int row = event.y() / row_height_;
388 if (row < 0 || row >= RowCount())
389 return true;
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);
401 return true;
404 void TableView::OnGestureEvent(ui::GestureEvent* event) {
405 if (event->type() != ui::ET_GESTURE_TAP)
406 return;
408 const int row = event->y() / row_height_;
409 if (row < 0 || row >= RowCount())
410 return;
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();
457 NumRowsChanged();
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);
467 NumRowsChanged();
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);
481 NumRowsChanged();
482 // If the selection was empty and is no longer empty select the same visual
483 // index.
484 if (selection_model_.empty() && previously_selected_view_index != -1 &&
485 RowCount()) {
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);
512 return 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())
522 return;
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);
537 if (is_selected) {
538 canvas->FillRect(GetRowBounds(i), selected_bg_color);
539 } else if (row_background_painter_) {
540 row_background_painter_->PaintRowBackground(model_index,
541 GetRowBounds(i),
542 canvas);
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(),
561 image_x,
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)
583 return;
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);
595 GroupRange range;
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));
603 if (start != last) {
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()),
610 grouping_color);
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);
618 i = last + 1;
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();
634 SchedulePaint();
637 void TableView::SetSortDescriptors(const SortDescriptors& sort_descriptors) {
638 sort_descriptors_ = sort_descriptors;
639 SortItemsAndUpdateMapping();
640 if (header_)
641 header_->SchedulePaint();
644 void TableView::SortItemsAndUpdateMapping() {
645 if (!is_sorted()) {
646 view_to_model_.clear();
647 model_to_view_.clear();
648 } else {
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;
654 if (grouper_) {
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);
659 } else {
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();
666 SchedulePaint();
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 {
687 if (!header_)
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) {
697 if (grouper_)
698 text_x += kGroupingIndicatorSize + kTextHorizontalPadding;
699 if (table_type_ == ICON_AND_TEXT)
700 text_x += kImageSize + kTextHorizontalPadding;
702 bounds->set_x(text_x);
703 bounds->set_width(
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()))
711 return;
713 header_ = new TableHeader(this);
716 void TableView::UpdateVisibleColumnSizes() {
717 if (!header_)
718 return;
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;
727 if (grouper_)
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());
735 int x = 0;
736 for (size_t i = 0; i < visible_columns_.size(); ++i) {
737 visible_columns_[i].x = x;
738 visible_columns_[i].width = sizes[i];
739 x += sizes[i];
743 TableView::PaintRegion TableView::GetPaintRegion(
744 const gfx::Rect& bounds) const {
745 DCHECK(!visible_columns_.empty());
746 DCHECK(RowCount());
748 PaintRegion region;
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)
753 region.max_row++;
754 region.max_row = std::min(region.max_row, RowCount());
756 if (!header_) {
757 region.max_column = 1;
758 return region;
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;
771 break;
774 return region;
777 gfx::Rect TableView::GetPaintBounds(gfx::Canvas* canvas) const {
778 SkRect sk_clip_rect;
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) {
791 SchedulePaint();
795 ui::TableColumn TableView::FindColumnByID(int id) const {
796 for (size_t i = 0; i < columns_.size(); ++i) {
797 if (columns_[i].id == id)
798 return columns_[i];
800 NOTREACHED();
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_))
817 return;
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();
828 const int end_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);
844 return;
846 int view_index = ModelToView(selection_model_.active());
847 if (direction == ADVANCE_DECREMENT)
848 view_index = std::max(0, view_index - 1);
849 else
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 ||
861 single_selection_ ||
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));
866 return;
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|
872 // are selected.
873 if (event.IsControlDown() && event.IsShiftDown())
874 model->Copy(selection_model_);
875 else
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()));
879 i <= end; ++i) {
880 SelectRowsInRangeFrom(i, true, model);
882 model->set_active(ViewToModel(view_index));
883 } else {
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)),
892 model);
896 void TableView::SelectRowsInRangeFrom(int view_index,
897 bool select,
898 ui::ListSelectionModel* model) const {
899 const GroupRange range(GetGroupRange(ViewToModel(view_index)));
900 for (int i = 0; i < range.length; ++i) {
901 if (select)
902 model->AddIndexToSelection(range.start + i);
903 else
904 model->RemoveIndexFromSelection(range.start + i);
908 GroupRange TableView::GetGroupRange(int model_index) const {
909 GroupRange range;
910 if (grouper_) {
911 grouper_->GetGroupRange(model_index, &range);
912 } else {
913 range.start = model_index;
914 range.length = 1;
916 return range;
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())
924 return false;
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))
930 return false;
932 const base::string16 text(model_->GetText(ViewToModel(row),
933 visible_columns_[column].column.id));
934 if (text.empty())
935 return false;
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()))
942 return false;
944 if (tooltip)
945 *tooltip = text;
946 if (tooltip_origin) {
947 tooltip_origin->SetPoint(cell_bounds.x(),
948 cell_bounds.y() + kTextVerticalPadding);
950 return true;
953 } // namespace views