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.
4
5 #include "ui/views/controls/table/table_view.h"
6
7 #include <map>
8
9 #include "base/auto_reset.h"
10 #include "base/i18n/rtl.h"
11 #include "ui/events/event.h"
12 #include "ui/gfx/canvas.h"
13 #include "ui/gfx/image/image_skia.h"
14 #include "ui/gfx/rect_conversions.h"
15 #include "ui/gfx/skia_util.h"
16 #include "ui/native_theme/native_theme.h"
17 #include "ui/views/controls/scroll_view.h"
18 #include "ui/views/controls/table/table_grouper.h"
19 #include "ui/views/controls/table/table_header.h"
20 #include "ui/views/controls/table/table_utils.h"
21 #include "ui/views/controls/table/table_view_observer.h"
22 #include "ui/views/controls/table/table_view_row_background_painter.h"
23
24 // Padding around the text (on each side).
25 static const int kTextVerticalPadding = 3;
26 static const int kTextHorizontalPadding = 6;
27
28 // Size of images.
29 static const int kImageSize = 16;
30
31 static const int kGroupingIndicatorSize = 6;
32
33 namespace views {
34
35 namespace {
36
37 // Returns result, unless ascending is false in which case -result is returned.
SwapCompareResult(int result,bool ascending)38 int SwapCompareResult(int result, bool ascending) {
39 return ascending ? result : -result;
40 }
41
42 // Populates |model_index_to_range_start| based on the |grouper|.
GetModelIndexToRangeStart(TableGrouper * grouper,int row_count,std::map<int,int> * model_index_to_range_start)43 void GetModelIndexToRangeStart(TableGrouper* grouper,
44 int row_count,
45 std::map<int, int>* model_index_to_range_start) {
46 for (int model_index = 0; model_index < row_count;) {
47 GroupRange range;
48 grouper->GetGroupRange(model_index, &range);
49 DCHECK_GT(range.length, 0);
50 for (int range_counter = 0; range_counter < range.length; range_counter++)
51 (*model_index_to_range_start)[range_counter + model_index] = model_index;
52 model_index += range.length;
53 }
54 }
55
56 // Returns the color id for the background of selected text. |has_focus|
57 // indicates if the table has focus.
text_background_color_id(bool has_focus)58 ui::NativeTheme::ColorId text_background_color_id(bool has_focus) {
59 return has_focus ?
60 ui::NativeTheme::kColorId_TableSelectionBackgroundFocused :
61 ui::NativeTheme::kColorId_TableSelectionBackgroundUnfocused;
62 }
63
64 // Returns the color id for text. |has_focus| indicates if the table has focus.
selected_text_color_id(bool has_focus)65 ui::NativeTheme::ColorId selected_text_color_id(bool has_focus) {
66 return has_focus ? ui::NativeTheme::kColorId_TableSelectedText :
67 ui::NativeTheme::kColorId_TableSelectedTextUnfocused;
68 }
69
70 } // namespace
71
72 // Used as the comparator to sort the contents of the table.
73 struct TableView::SortHelper {
SortHelperviews::TableView::SortHelper74 explicit SortHelper(TableView* table) : table(table) {}
75
operator ()views::TableView::SortHelper76 bool operator()(int model_index1, int model_index2) {
77 return table->CompareRows(model_index1, model_index2) < 0;
78 }
79
80 TableView* table;
81 };
82
83 // Used as the comparator to sort the contents of the table when a TableGrouper
84 // is present. When groups are present we sort the groups based on the first row
85 // in the group and within the groups we keep the same order as the model.
86 struct TableView::GroupSortHelper {
GroupSortHelperviews::TableView::GroupSortHelper87 explicit GroupSortHelper(TableView* table) : table(table) {}
88
operator ()views::TableView::GroupSortHelper89 bool operator()(int model_index1, int model_index2) {
90 const int range1 = model_index_to_range_start[model_index1];
91 const int range2 = model_index_to_range_start[model_index2];
92 if (range1 == range2) {
93 // The two rows are in the same group, sort so that items in the same
94 // group always appear in the same order.
95 return model_index1 < model_index2;
96 }
97 return table->CompareRows(range1, range2) < 0;
98 }
99
100 TableView* table;
101 std::map<int, int> model_index_to_range_start;
102 };
103
VisibleColumn()104 TableView::VisibleColumn::VisibleColumn() : x(0), width(0) {}
105
~VisibleColumn()106 TableView::VisibleColumn::~VisibleColumn() {}
107
PaintRegion()108 TableView::PaintRegion::PaintRegion()
109 : min_row(0),
110 max_row(0),
111 min_column(0),
112 max_column(0) {
113 }
114
~PaintRegion()115 TableView::PaintRegion::~PaintRegion() {}
116
TableView(ui::TableModel * model,const std::vector<ui::TableColumn> & columns,TableTypes table_type,bool single_selection)117 TableView::TableView(ui::TableModel* model,
118 const std::vector<ui::TableColumn>& columns,
119 TableTypes table_type,
120 bool single_selection)
121 : model_(NULL),
122 columns_(columns),
123 header_(NULL),
124 table_type_(table_type),
125 single_selection_(single_selection),
126 table_view_observer_(NULL),
127 row_height_(font_.GetHeight() + kTextVerticalPadding * 2),
128 last_parent_width_(0),
129 layout_width_(0),
130 grouper_(NULL),
131 in_set_visible_column_width_(false) {
132 for (size_t i = 0; i < columns.size(); ++i) {
133 VisibleColumn visible_column;
134 visible_column.column = columns[i];
135 visible_columns_.push_back(visible_column);
136 }
137 SetFocusable(true);
138 SetModel(model);
139 }
140
~TableView()141 TableView::~TableView() {
142 if (model_)
143 model_->SetObserver(NULL);
144 }
145
146 // TODO: this doesn't support arbitrarily changing the model, rename this to
147 // ClearModel() or something.
SetModel(ui::TableModel * model)148 void TableView::SetModel(ui::TableModel* model) {
149 if (model == model_)
150 return;
151
152 if (model_)
153 model_->SetObserver(NULL);
154 model_ = model;
155 selection_model_.Clear();
156 if (model_)
157 model_->SetObserver(this);
158 }
159
CreateParentIfNecessary()160 View* TableView::CreateParentIfNecessary() {
161 ScrollView* scroll_view = ScrollView::CreateScrollViewWithBorder();
162 scroll_view->SetContents(this);
163 CreateHeaderIfNecessary();
164 if (header_)
165 scroll_view->SetHeader(header_);
166 return scroll_view;
167 }
168
SetRowBackgroundPainter(scoped_ptr<TableViewRowBackgroundPainter> painter)169 void TableView::SetRowBackgroundPainter(
170 scoped_ptr<TableViewRowBackgroundPainter> painter) {
171 row_background_painter_ = painter.Pass();
172 }
173
SetGrouper(TableGrouper * grouper)174 void TableView::SetGrouper(TableGrouper* grouper) {
175 grouper_ = grouper;
176 SortItemsAndUpdateMapping();
177 }
178
RowCount() const179 int TableView::RowCount() const {
180 return model_ ? model_->RowCount() : 0;
181 }
182
SelectedRowCount()183 int TableView::SelectedRowCount() {
184 return static_cast<int>(selection_model_.size());
185 }
186
Select(int model_row)187 void TableView::Select(int model_row) {
188 if (!model_)
189 return;
190
191 SelectByViewIndex(model_row == -1 ? -1 : ModelToView(model_row));
192 }
193
FirstSelectedRow()194 int TableView::FirstSelectedRow() {
195 return SelectedRowCount() == 0 ? -1 : selection_model_.selected_indices()[0];
196 }
197
SetColumnVisibility(int id,bool is_visible)198 void TableView::SetColumnVisibility(int id, bool is_visible) {
199 if (is_visible == IsColumnVisible(id))
200 return;
201
202 if (is_visible) {
203 VisibleColumn visible_column;
204 visible_column.column = FindColumnByID(id);
205 visible_columns_.push_back(visible_column);
206 } else {
207 for (size_t i = 0; i < visible_columns_.size(); ++i) {
208 if (visible_columns_[i].column.id == id) {
209 visible_columns_.erase(visible_columns_.begin() + i);
210 break;
211 }
212 }
213 }
214 UpdateVisibleColumnSizes();
215 PreferredSizeChanged();
216 SchedulePaint();
217 if (header_)
218 header_->SchedulePaint();
219 }
220
ToggleSortOrder(int visible_column_index)221 void TableView::ToggleSortOrder(int visible_column_index) {
222 DCHECK(visible_column_index >= 0 &&
223 visible_column_index < static_cast<int>(visible_columns_.size()));
224 if (!visible_columns_[visible_column_index].column.sortable)
225 return;
226 const int column_id = visible_columns_[visible_column_index].column.id;
227 SortDescriptors sort(sort_descriptors_);
228 if (!sort.empty() && sort[0].column_id == column_id) {
229 sort[0].ascending = !sort[0].ascending;
230 } else {
231 SortDescriptor descriptor(column_id, true);
232 sort.insert(sort.begin(), descriptor);
233 // Only persist two sort descriptors.
234 if (sort.size() > 2)
235 sort.resize(2);
236 }
237 SetSortDescriptors(sort);
238 }
239
IsColumnVisible(int id) const240 bool TableView::IsColumnVisible(int id) const {
241 for (size_t i = 0; i < visible_columns_.size(); ++i) {
242 if (visible_columns_[i].column.id == id)
243 return true;
244 }
245 return false;
246 }
247
AddColumn(const ui::TableColumn & col)248 void TableView::AddColumn(const ui::TableColumn& col) {
249 DCHECK(!HasColumn(col.id));
250 columns_.push_back(col);
251 }
252
HasColumn(int id) const253 bool TableView::HasColumn(int id) const {
254 for (size_t i = 0; i < columns_.size(); ++i) {
255 if (columns_[i].id == id)
256 return true;
257 }
258 return false;
259 }
260
SetVisibleColumnWidth(int index,int width)261 void TableView::SetVisibleColumnWidth(int index, int width) {
262 DCHECK(index >= 0 && index < static_cast<int>(visible_columns_.size()));
263 if (visible_columns_[index].width == width)
264 return;
265 base::AutoReset<bool> reseter(&in_set_visible_column_width_, true);
266 visible_columns_[index].width = width;
267 for (size_t i = index + 1; i < visible_columns_.size(); ++i) {
268 visible_columns_[i].x =
269 visible_columns_[i - 1].x + visible_columns_[i - 1].width;
270 }
271 PreferredSizeChanged();
272 SchedulePaint();
273 }
274
ModelToView(int model_index) const275 int TableView::ModelToView(int model_index) const {
276 if (!is_sorted())
277 return model_index;
278 DCHECK_GE(model_index, 0) << " negative model_index " << model_index;
279 DCHECK_LT(model_index, RowCount()) << " out of bounds model_index " <<
280 model_index;
281 return model_to_view_[model_index];
282 }
283
ViewToModel(int view_index) const284 int TableView::ViewToModel(int view_index) const {
285 if (!is_sorted())
286 return view_index;
287 DCHECK_GE(view_index, 0) << " negative view_index " << view_index;
288 DCHECK_LT(view_index, RowCount()) << " out of bounds view_index " <<
289 view_index;
290 return view_to_model_[view_index];
291 }
292
Layout()293 void TableView::Layout() {
294 // parent()->parent() is the scrollview. When its width changes we force
295 // recalculating column sizes.
296 View* scroll_view = parent() ? parent()->parent() : NULL;
297 if (scroll_view) {
298 const int scroll_view_width = scroll_view->GetContentsBounds().width();
299 if (scroll_view_width != last_parent_width_) {
300 last_parent_width_ = scroll_view_width;
301 if (!in_set_visible_column_width_) {
302 // Layout to the parent (the Viewport), which differs from
303 // |scroll_view_width| when scrollbars are present.
304 layout_width_ = parent()->width();
305 UpdateVisibleColumnSizes();
306 }
307 }
308 }
309 // We have to override Layout like this since we're contained in a ScrollView.
310 gfx::Size pref = GetPreferredSize();
311 int width = pref.width();
312 int height = pref.height();
313 if (parent()) {
314 width = std::max(parent()->width(), width);
315 height = std::max(parent()->height(), height);
316 }
317 SetBounds(x(), y(), width, height);
318 }
319
GetPreferredSize()320 gfx::Size TableView::GetPreferredSize() {
321 int width = 50;
322 if (header_ && !visible_columns_.empty())
323 width = visible_columns_.back().x + visible_columns_.back().width;
324 return gfx::Size(width, RowCount() * row_height_);
325 }
326
OnKeyPressed(const ui::KeyEvent & event)327 bool TableView::OnKeyPressed(const ui::KeyEvent& event) {
328 if (!HasFocus())
329 return false;
330
331 switch (event.key_code()) {
332 case ui::VKEY_A:
333 // control-a selects all.
334 if (event.IsControlDown() && !single_selection_ && RowCount()) {
335 ui::ListSelectionModel selection_model;
336 selection_model.SetSelectedIndex(selection_model_.active());
337 for (int i = 0; i < RowCount(); ++i)
338 selection_model.AddIndexToSelection(i);
339 SetSelectionModel(selection_model);
340 return true;
341 }
342 break;
343
344 case ui::VKEY_HOME:
345 if (RowCount())
346 SelectByViewIndex(0);
347 return true;
348
349 case ui::VKEY_END:
350 if (RowCount())
351 SelectByViewIndex(RowCount() - 1);
352 return true;
353
354 case ui::VKEY_UP:
355 AdvanceSelection(ADVANCE_DECREMENT);
356 return true;
357
358 case ui::VKEY_DOWN:
359 AdvanceSelection(ADVANCE_INCREMENT);
360 return true;
361
362 default:
363 break;
364 }
365 if (table_view_observer_)
366 table_view_observer_->OnKeyDown(event.key_code());
367 return false;
368 }
369
OnMousePressed(const ui::MouseEvent & event)370 bool TableView::OnMousePressed(const ui::MouseEvent& event) {
371 RequestFocus();
372 if (!event.IsOnlyLeftMouseButton())
373 return true;
374
375 const int row = event.y() / row_height_;
376 if (row < 0 || row >= RowCount())
377 return true;
378
379 if (event.GetClickCount() == 2) {
380 SelectByViewIndex(row);
381 if (table_view_observer_)
382 table_view_observer_->OnDoubleClick();
383 } else if (event.GetClickCount() == 1) {
384 ui::ListSelectionModel new_model;
385 ConfigureSelectionModelForEvent(event, &new_model);
386 SetSelectionModel(new_model);
387 }
388
389 return true;
390 }
391
OnGestureEvent(ui::GestureEvent * event)392 void TableView::OnGestureEvent(ui::GestureEvent* event) {
393 if (event->type() != ui::ET_GESTURE_TAP)
394 return;
395
396 const int row = event->y() / row_height_;
397 if (row < 0 || row >= RowCount())
398 return;
399
400 event->StopPropagation();
401 ui::ListSelectionModel new_model;
402 ConfigureSelectionModelForEvent(*event, &new_model);
403 SetSelectionModel(new_model);
404 }
405
GetTooltipText(const gfx::Point & p,string16 * tooltip) const406 bool TableView::GetTooltipText(const gfx::Point& p,
407 string16* tooltip) const {
408 return GetTooltipImpl(p, tooltip, NULL);
409 }
410
GetTooltipTextOrigin(const gfx::Point & p,gfx::Point * loc) const411 bool TableView::GetTooltipTextOrigin(const gfx::Point& p,
412 gfx::Point* loc) const {
413 return GetTooltipImpl(p, NULL, loc);
414 }
415
OnModelChanged()416 void TableView::OnModelChanged() {
417 selection_model_.Clear();
418 NumRowsChanged();
419 }
420
OnItemsChanged(int start,int length)421 void TableView::OnItemsChanged(int start, int length) {
422 SortItemsAndUpdateMapping();
423 }
424
OnItemsAdded(int start,int length)425 void TableView::OnItemsAdded(int start, int length) {
426 for (int i = 0; i < length; ++i)
427 selection_model_.IncrementFrom(start);
428 NumRowsChanged();
429 }
430
OnItemsRemoved(int start,int length)431 void TableView::OnItemsRemoved(int start, int length) {
432 // Determine the currently selected index in terms of the view. We inline the
433 // implementation here since ViewToModel() has DCHECKs that fail since the
434 // model has changed but |model_to_view_| has not been updated yet.
435 const int previously_selected_model_index = FirstSelectedRow();
436 int previously_selected_view_index = previously_selected_model_index;
437 if (previously_selected_model_index != -1 && is_sorted())
438 previously_selected_view_index =
439 model_to_view_[previously_selected_model_index];
440 for (int i = 0; i < length; ++i)
441 selection_model_.DecrementFrom(start);
442 NumRowsChanged();
443 // If the selection was empty and is no longer empty select the same visual
444 // index.
445 if (selection_model_.empty() && previously_selected_view_index != -1 &&
446 RowCount()) {
447 selection_model_.SetSelectedIndex(
448 ViewToModel(std::min(RowCount() - 1, previously_selected_view_index)));
449 }
450 if (table_view_observer_)
451 table_view_observer_->OnSelectionChanged();
452 }
453
GetKeyboardContextMenuLocation()454 gfx::Point TableView::GetKeyboardContextMenuLocation() {
455 int first_selected = FirstSelectedRow();
456 gfx::Rect vis_bounds(GetVisibleBounds());
457 int y = vis_bounds.height() / 2;
458 if (first_selected != -1) {
459 gfx::Rect cell_bounds(GetRowBounds(first_selected));
460 if (cell_bounds.bottom() >= vis_bounds.y() &&
461 cell_bounds.bottom() < vis_bounds.bottom()) {
462 y = cell_bounds.bottom();
463 }
464 }
465 gfx::Point screen_loc(0, y);
466 if (base::i18n::IsRTL())
467 screen_loc.set_x(width());
468 ConvertPointToScreen(this, &screen_loc);
469 return screen_loc;
470 }
471
OnPaint(gfx::Canvas * canvas)472 void TableView::OnPaint(gfx::Canvas* canvas) {
473 // Don't invoke View::OnPaint so that we can render our own focus border.
474
475 canvas->DrawColor(GetNativeTheme()->GetSystemColor(
476 ui::NativeTheme::kColorId_TableBackground));
477
478 if (!RowCount() || visible_columns_.empty())
479 return;
480
481 const PaintRegion region(GetPaintRegion(GetPaintBounds(canvas)));
482 if (region.min_column == -1)
483 return; // No need to paint anything.
484
485 const SkColor selected_bg_color = GetNativeTheme()->GetSystemColor(
486 text_background_color_id(HasFocus()));
487 const SkColor fg_color = GetNativeTheme()->GetSystemColor(
488 ui::NativeTheme::kColorId_TableText);
489 const SkColor selected_fg_color = GetNativeTheme()->GetSystemColor(
490 selected_text_color_id(HasFocus()));
491 for (int i = region.min_row; i < region.max_row; ++i) {
492 const int model_index = ViewToModel(i);
493 const bool is_selected = selection_model_.IsSelected(model_index);
494 if (is_selected) {
495 canvas->FillRect(GetRowBounds(i), selected_bg_color);
496 } else if (row_background_painter_) {
497 row_background_painter_->PaintRowBackground(model_index,
498 GetRowBounds(i),
499 canvas);
500 }
501 if (selection_model_.active() == i && HasFocus())
502 canvas->DrawFocusRect(GetRowBounds(i));
503 for (int j = region.min_column; j < region.max_column; ++j) {
504 const gfx::Rect cell_bounds(GetCellBounds(i, j));
505 int text_x = kTextHorizontalPadding + cell_bounds.x();
506
507 // Provide space for the grouping indicator, but draw it separately.
508 if (j == 0 && grouper_)
509 text_x += kGroupingIndicatorSize + kTextHorizontalPadding;
510
511 // Always paint the icon in the first visible column.
512 if (j == 0 && table_type_ == ICON_AND_TEXT) {
513 gfx::ImageSkia image = model_->GetIcon(model_index);
514 if (!image.isNull()) {
515 int image_x = GetMirroredXWithWidthInView(text_x, kImageSize);
516 canvas->DrawImageInt(
517 image, 0, 0, image.width(), image.height(),
518 image_x,
519 cell_bounds.y() + (cell_bounds.height() - kImageSize) / 2,
520 kImageSize, kImageSize, true);
521 }
522 text_x += kImageSize + kTextHorizontalPadding;
523 }
524 if (text_x < cell_bounds.right() - kTextHorizontalPadding) {
525 canvas->DrawStringInt(
526 model_->GetText(model_index, visible_columns_[j].column.id), font_,
527 is_selected ? selected_fg_color : fg_color,
528 GetMirroredXWithWidthInView(text_x, cell_bounds.right() - text_x -
529 kTextHorizontalPadding),
530 cell_bounds.y() + kTextVerticalPadding,
531 cell_bounds.right() - text_x,
532 cell_bounds.height() - kTextVerticalPadding * 2,
533 TableColumnAlignmentToCanvasAlignment(
534 visible_columns_[j].column.alignment));
535 }
536 }
537 }
538
539 if (!grouper_ || region.min_column > 0)
540 return;
541
542 const SkColor grouping_color = GetNativeTheme()->GetSystemColor(
543 ui::NativeTheme::kColorId_TableGroupingIndicatorColor);
544 SkPaint grouping_paint;
545 grouping_paint.setColor(grouping_color);
546 grouping_paint.setStyle(SkPaint::kFill_Style);
547 grouping_paint.setAntiAlias(true);
548 const int group_indicator_x = GetMirroredXInView(GetCellBounds(0, 0).x() +
549 kTextHorizontalPadding + kGroupingIndicatorSize / 2);
550 for (int i = region.min_row; i < region.max_row; ) {
551 const int model_index = ViewToModel(i);
552 GroupRange range;
553 grouper_->GetGroupRange(model_index, &range);
554 DCHECK_GT(range.length, 0);
555 // The order of rows in a group is consistent regardless of sort, so it's ok
556 // to do this calculation.
557 const int start = i - (model_index - range.start);
558 const int last = start + range.length - 1;
559 const gfx::Rect start_cell_bounds(GetCellBounds(start, 0));
560 if (start != last) {
561 const gfx::Rect last_cell_bounds(GetCellBounds(last, 0));
562 canvas->FillRect(gfx::Rect(
563 group_indicator_x - kGroupingIndicatorSize / 2,
564 start_cell_bounds.CenterPoint().y(),
565 kGroupingIndicatorSize,
566 last_cell_bounds.y() - start_cell_bounds.y()),
567 grouping_color);
568 canvas->DrawCircle(gfx::Point(group_indicator_x,
569 last_cell_bounds.CenterPoint().y()),
570 kGroupingIndicatorSize / 2, grouping_paint);
571 }
572 canvas->DrawCircle(gfx::Point(group_indicator_x,
573 start_cell_bounds.CenterPoint().y()),
574 kGroupingIndicatorSize / 2, grouping_paint);
575 i = last + 1;
576 }
577 }
578
OnFocus()579 void TableView::OnFocus() {
580 SchedulePaintForSelection();
581 }
582
OnBlur()583 void TableView::OnBlur() {
584 SchedulePaintForSelection();
585 }
586
NumRowsChanged()587 void TableView::NumRowsChanged() {
588 SortItemsAndUpdateMapping();
589 PreferredSizeChanged();
590 SchedulePaint();
591 }
592
SetSortDescriptors(const SortDescriptors & sort_descriptors)593 void TableView::SetSortDescriptors(const SortDescriptors& sort_descriptors) {
594 sort_descriptors_ = sort_descriptors;
595 SortItemsAndUpdateMapping();
596 if (header_)
597 header_->SchedulePaint();
598 }
599
SortItemsAndUpdateMapping()600 void TableView::SortItemsAndUpdateMapping() {
601 if (!is_sorted()) {
602 view_to_model_.clear();
603 model_to_view_.clear();
604 } else {
605 const int row_count = RowCount();
606 view_to_model_.resize(row_count);
607 model_to_view_.resize(row_count);
608 for (int i = 0; i < row_count; ++i)
609 view_to_model_[i] = i;
610 if (grouper_) {
611 GroupSortHelper sort_helper(this);
612 GetModelIndexToRangeStart(grouper_, RowCount(),
613 &sort_helper.model_index_to_range_start);
614 std::sort(view_to_model_.begin(), view_to_model_.end(), sort_helper);
615 } else {
616 std::sort(view_to_model_.begin(), view_to_model_.end(), SortHelper(this));
617 }
618 for (int i = 0; i < row_count; ++i)
619 model_to_view_[view_to_model_[i]] = i;
620 model_->ClearCollator();
621 }
622 SchedulePaint();
623 }
624
CompareRows(int model_row1,int model_row2)625 int TableView::CompareRows(int model_row1, int model_row2) {
626 const int sort_result = model_->CompareValues(
627 model_row1, model_row2, sort_descriptors_[0].column_id);
628 if (sort_result == 0 && sort_descriptors_.size() > 1) {
629 // Try the secondary sort.
630 return SwapCompareResult(
631 model_->CompareValues(model_row1, model_row2,
632 sort_descriptors_[1].column_id),
633 sort_descriptors_[1].ascending);
634 }
635 return SwapCompareResult(sort_result, sort_descriptors_[0].ascending);
636 }
637
GetRowBounds(int row) const638 gfx::Rect TableView::GetRowBounds(int row) const {
639 return gfx::Rect(0, row * row_height_, width(), row_height_);
640 }
641
GetCellBounds(int row,int visible_column_index) const642 gfx::Rect TableView::GetCellBounds(int row, int visible_column_index) const {
643 if (!header_)
644 return GetRowBounds(row);
645 const VisibleColumn& vis_col(visible_columns_[visible_column_index]);
646 return gfx::Rect(vis_col.x, row * row_height_, vis_col.width, row_height_);
647 }
648
AdjustCellBoundsForText(int visible_column_index,gfx::Rect * bounds) const649 void TableView::AdjustCellBoundsForText(int visible_column_index,
650 gfx::Rect* bounds) const {
651 int text_x = kTextHorizontalPadding + bounds->x();
652 if (visible_column_index == 0) {
653 if (grouper_)
654 text_x += kGroupingIndicatorSize + kTextHorizontalPadding;
655 if (table_type_ == ICON_AND_TEXT)
656 text_x += kImageSize + kTextHorizontalPadding;
657 }
658 bounds->set_x(text_x);
659 bounds->set_width(
660 std::max(0, bounds->right() - kTextHorizontalPadding - text_x));
661 }
662
CreateHeaderIfNecessary()663 void TableView::CreateHeaderIfNecessary() {
664 // Only create a header if there is more than one column or the title of the
665 // only column is not empty.
666 if (header_ || (columns_.size() == 1 && columns_[0].title.empty()))
667 return;
668
669 header_ = new TableHeader(this);
670 }
671
UpdateVisibleColumnSizes()672 void TableView::UpdateVisibleColumnSizes() {
673 if (!header_)
674 return;
675
676 std::vector<ui::TableColumn> columns;
677 for (size_t i = 0; i < visible_columns_.size(); ++i)
678 columns.push_back(visible_columns_[i].column);
679
680 int first_column_padding = 0;
681 if (table_type_ == ICON_AND_TEXT && header_)
682 first_column_padding += kImageSize + kTextHorizontalPadding;
683 if (grouper_)
684 first_column_padding += kGroupingIndicatorSize + kTextHorizontalPadding;
685
686 std::vector<int> sizes = views::CalculateTableColumnSizes(
687 layout_width_, first_column_padding, header_->font(), font_,
688 std::max(kTextHorizontalPadding, TableHeader::kHorizontalPadding) * 2,
689 TableHeader::kSortIndicatorWidth, columns, model_);
690 DCHECK_EQ(visible_columns_.size(), sizes.size());
691 int x = 0;
692 for (size_t i = 0; i < visible_columns_.size(); ++i) {
693 visible_columns_[i].x = x;
694 visible_columns_[i].width = sizes[i];
695 x += sizes[i];
696 }
697 }
698
GetPaintRegion(const gfx::Rect & bounds) const699 TableView::PaintRegion TableView::GetPaintRegion(
700 const gfx::Rect& bounds) const {
701 DCHECK(!visible_columns_.empty());
702 DCHECK(RowCount());
703
704 PaintRegion region;
705 region.min_row = std::min(RowCount() - 1,
706 std::max(0, bounds.y() / row_height_));
707 region.max_row = bounds.bottom() / row_height_;
708 if (bounds.bottom() % row_height_ != 0)
709 region.max_row++;
710 region.max_row = std::min(region.max_row, RowCount());
711
712 if (!header_) {
713 region.max_column = 1;
714 return region;
715 }
716
717 const int paint_x = GetMirroredXForRect(bounds);
718 const int paint_max_x = paint_x + bounds.width();
719 region.min_column = -1;
720 region.max_column = visible_columns_.size();
721 for (size_t i = 0; i < visible_columns_.size(); ++i) {
722 int max_x = visible_columns_[i].x + visible_columns_[i].width;
723 if (region.min_column == -1 && max_x >= paint_x)
724 region.min_column = static_cast<int>(i);
725 if (region.min_column != -1 && visible_columns_[i].x >= paint_max_x) {
726 region.max_column = i;
727 break;
728 }
729 }
730 return region;
731 }
732
GetPaintBounds(gfx::Canvas * canvas) const733 gfx::Rect TableView::GetPaintBounds(gfx::Canvas* canvas) const {
734 SkRect sk_clip_rect;
735 if (canvas->sk_canvas()->getClipBounds(&sk_clip_rect))
736 return gfx::ToEnclosingRect(gfx::SkRectToRectF(sk_clip_rect));
737 return GetVisibleBounds();
738 }
739
SchedulePaintForSelection()740 void TableView::SchedulePaintForSelection() {
741 if (selection_model_.size() == 1) {
742 const int first_model_row = FirstSelectedRow();
743 SchedulePaintInRect(GetRowBounds(ModelToView(first_model_row)));
744 if (first_model_row != selection_model_.active())
745 SchedulePaintInRect(GetRowBounds(ModelToView(selection_model_.active())));
746 } else if (selection_model_.size() > 1) {
747 SchedulePaint();
748 }
749 }
750
FindColumnByID(int id) const751 ui::TableColumn TableView::FindColumnByID(int id) const {
752 for (size_t i = 0; i < columns_.size(); ++i) {
753 if (columns_[i].id == id)
754 return columns_[i];
755 }
756 NOTREACHED();
757 return ui::TableColumn();
758 }
759
SelectByViewIndex(int view_index)760 void TableView::SelectByViewIndex(int view_index) {
761 ui::ListSelectionModel new_selection;
762 if (view_index != -1) {
763 SelectRowsInRangeFrom(view_index, true, &new_selection);
764 new_selection.set_anchor(ViewToModel(view_index));
765 new_selection.set_active(ViewToModel(view_index));
766 }
767
768 SetSelectionModel(new_selection);
769 }
770
SetSelectionModel(const ui::ListSelectionModel & new_selection)771 void TableView::SetSelectionModel(const ui::ListSelectionModel& new_selection) {
772 if (new_selection.Equals(selection_model_))
773 return;
774
775 SchedulePaintForSelection();
776 selection_model_.Copy(new_selection);
777 SchedulePaintForSelection();
778
779 // Scroll the group for the active item to visible.
780 if (selection_model_.active() != -1) {
781 gfx::Rect vis_rect(GetVisibleBounds());
782 const GroupRange range(GetGroupRange(selection_model_.active()));
783 const int start_y = GetRowBounds(ModelToView(range.start)).y();
784 const int end_y =
785 GetRowBounds(ModelToView(range.start + range.length - 1)).bottom();
786 vis_rect.set_y(start_y);
787 vis_rect.set_height(end_y - start_y);
788 ScrollRectToVisible(vis_rect);
789 }
790
791 if (table_view_observer_)
792 table_view_observer_->OnSelectionChanged();
793 }
794
AdvanceSelection(AdvanceDirection direction)795 void TableView::AdvanceSelection(AdvanceDirection direction) {
796 if (selection_model_.active() == -1) {
797 SelectByViewIndex(0);
798 return;
799 }
800 int view_index = ModelToView(selection_model_.active());
801 if (direction == ADVANCE_DECREMENT)
802 view_index = std::max(0, view_index - 1);
803 else
804 view_index = std::min(RowCount() - 1, view_index + 1);
805 SelectByViewIndex(view_index);
806 }
807
ConfigureSelectionModelForEvent(const ui::LocatedEvent & event,ui::ListSelectionModel * model) const808 void TableView::ConfigureSelectionModelForEvent(
809 const ui::LocatedEvent& event,
810 ui::ListSelectionModel* model) const {
811 const int view_index = event.y() / row_height_;
812 DCHECK(view_index >= 0 && view_index < RowCount());
813
814 if (selection_model_.anchor() == -1 ||
815 single_selection_ ||
816 (!event.IsControlDown() && !event.IsShiftDown())) {
817 SelectRowsInRangeFrom(view_index, true, model);
818 model->set_anchor(ViewToModel(view_index));
819 model->set_active(ViewToModel(view_index));
820 return;
821 }
822 if ((event.IsControlDown() && event.IsShiftDown()) || event.IsShiftDown()) {
823 // control-shift: copy existing model and make sure rows between anchor and
824 // |view_index| are selected.
825 // shift: reset selection so that only rows between anchor and |view_index|
826 // are selected.
827 if (event.IsControlDown() && event.IsShiftDown())
828 model->Copy(selection_model_);
829 else
830 model->set_anchor(selection_model_.anchor());
831 for (int i = std::min(view_index, ModelToView(model->anchor())),
832 end = std::max(view_index, ModelToView(model->anchor()));
833 i <= end; ++i) {
834 SelectRowsInRangeFrom(i, true, model);
835 }
836 model->set_active(ViewToModel(view_index));
837 } else {
838 DCHECK(event.IsControlDown());
839 // Toggle the selection state of |view_index| and set the anchor/active to
840 // it and don't change the state of any other rows.
841 model->Copy(selection_model_);
842 model->set_anchor(ViewToModel(view_index));
843 model->set_active(ViewToModel(view_index));
844 SelectRowsInRangeFrom(view_index,
845 !model->IsSelected(ViewToModel(view_index)),
846 model);
847 }
848 }
849
SelectRowsInRangeFrom(int view_index,bool select,ui::ListSelectionModel * model) const850 void TableView::SelectRowsInRangeFrom(int view_index,
851 bool select,
852 ui::ListSelectionModel* model) const {
853 const GroupRange range(GetGroupRange(ViewToModel(view_index)));
854 for (int i = 0; i < range.length; ++i) {
855 if (select)
856 model->AddIndexToSelection(range.start + i);
857 else
858 model->RemoveIndexFromSelection(range.start + i);
859 }
860 }
861
GetGroupRange(int model_index) const862 GroupRange TableView::GetGroupRange(int model_index) const {
863 GroupRange range;
864 if (grouper_) {
865 grouper_->GetGroupRange(model_index, &range);
866 } else {
867 range.start = model_index;
868 range.length = 1;
869 }
870 return range;
871 }
872
GetTooltipImpl(const gfx::Point & location,string16 * tooltip,gfx::Point * tooltip_origin) const873 bool TableView::GetTooltipImpl(const gfx::Point& location,
874 string16* tooltip,
875 gfx::Point* tooltip_origin) const {
876 const int row = location.y() / row_height_;
877 if (row < 0 || row >= RowCount() || visible_columns_.empty())
878 return false;
879
880 const int x = GetMirroredXInView(location.x());
881 const int column = GetClosestVisibleColumnIndex(this, x);
882 if (x < visible_columns_[column].x ||
883 x > (visible_columns_[column].x + visible_columns_[column].width))
884 return false;
885
886 const string16 text(model_->GetText(ViewToModel(row),
887 visible_columns_[column].column.id));
888 if (text.empty())
889 return false;
890
891 gfx::Rect cell_bounds(GetCellBounds(row, column));
892 AdjustCellBoundsForText(column, &cell_bounds);
893 const int right = std::min(GetVisibleBounds().right(), cell_bounds.right());
894 if (right > cell_bounds.x() &&
895 font_.GetStringWidth(text) <= (right - cell_bounds.x()))
896 return false;
897
898 if (tooltip)
899 *tooltip = text;
900 if (tooltip_origin) {
901 tooltip_origin->SetPoint(cell_bounds.x(),
902 cell_bounds.y() + kTextVerticalPadding);
903 }
904 return true;
905 }
906
907 } // namespace views
908