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/base/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. 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|. 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. 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. 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 { 74 explicit SortHelper(TableView* table) : table(table) {} 75 76 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 { 87 explicit GroupSortHelper(TableView* table) : table(table) {} 88 89 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 104 TableView::VisibleColumn::VisibleColumn() : x(0), width(0) {} 105 106 TableView::VisibleColumn::~VisibleColumn() {} 107 108 TableView::PaintRegion::PaintRegion() 109 : min_row(0), 110 max_row(0), 111 min_column(0), 112 max_column(0) { 113 } 114 115 TableView::PaintRegion::~PaintRegion() {} 116 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 set_focusable(true); 138 SetModel(model); 139 } 140 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. 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 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 169 void TableView::SetRowBackgroundPainter( 170 scoped_ptr<TableViewRowBackgroundPainter> painter) { 171 row_background_painter_ = painter.Pass(); 172 } 173 174 void TableView::SetGrouper(TableGrouper* grouper) { 175 grouper_ = grouper; 176 SortItemsAndUpdateMapping(); 177 } 178 179 int TableView::RowCount() const { 180 return model_ ? model_->RowCount() : 0; 181 } 182 183 int TableView::SelectedRowCount() { 184 return static_cast<int>(selection_model_.size()); 185 } 186 187 void TableView::Select(int model_row) { 188 if (!model_) 189 return; 190 191 SelectByViewIndex(model_row == -1 ? -1 : ModelToView(model_row)); 192 } 193 194 int TableView::FirstSelectedRow() { 195 return SelectedRowCount() == 0 ? -1 : selection_model_.selected_indices()[0]; 196 } 197 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 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 240 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 248 void TableView::AddColumn(const ui::TableColumn& col) { 249 DCHECK(!HasColumn(col.id)); 250 columns_.push_back(col); 251 } 252 253 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 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 275 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 284 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 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 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 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 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 392 bool TableView::GetTooltipText(const gfx::Point& p, 393 string16* tooltip) const { 394 return GetTooltipImpl(p, tooltip, NULL); 395 } 396 397 bool TableView::GetTooltipTextOrigin(const gfx::Point& p, 398 gfx::Point* loc) const { 399 return GetTooltipImpl(p, NULL, loc); 400 } 401 402 void TableView::OnModelChanged() { 403 selection_model_.Clear(); 404 NumRowsChanged(); 405 } 406 407 void TableView::OnItemsChanged(int start, int length) { 408 SortItemsAndUpdateMapping(); 409 } 410 411 void TableView::OnItemsAdded(int start, int length) { 412 for (int i = 0; i < length; ++i) 413 selection_model_.IncrementFrom(start); 414 NumRowsChanged(); 415 } 416 417 void TableView::OnItemsRemoved(int start, int length) { 418 // Determine the currently selected index in terms of the view. We inline the 419 // implementation here since ViewToModel() has DCHECKs that fail since the 420 // model has changed but |model_to_view_| has not been updated yet. 421 const int previously_selected_model_index = FirstSelectedRow(); 422 int previously_selected_view_index = previously_selected_model_index; 423 if (previously_selected_model_index != -1 && is_sorted()) 424 previously_selected_view_index = 425 model_to_view_[previously_selected_model_index]; 426 for (int i = 0; i < length; ++i) 427 selection_model_.DecrementFrom(start); 428 NumRowsChanged(); 429 // If the selection was empty and is no longer empty select the same visual 430 // index. 431 if (selection_model_.empty() && previously_selected_view_index != -1 && 432 RowCount()) { 433 selection_model_.SetSelectedIndex( 434 ViewToModel(std::min(RowCount() - 1, previously_selected_view_index))); 435 } 436 if (table_view_observer_) 437 table_view_observer_->OnSelectionChanged(); 438 } 439 440 gfx::Point TableView::GetKeyboardContextMenuLocation() { 441 int first_selected = FirstSelectedRow(); 442 gfx::Rect vis_bounds(GetVisibleBounds()); 443 int y = vis_bounds.height() / 2; 444 if (first_selected != -1) { 445 gfx::Rect cell_bounds(GetRowBounds(first_selected)); 446 if (cell_bounds.bottom() >= vis_bounds.y() && 447 cell_bounds.bottom() < vis_bounds.bottom()) { 448 y = cell_bounds.bottom(); 449 } 450 } 451 gfx::Point screen_loc(0, y); 452 if (base::i18n::IsRTL()) 453 screen_loc.set_x(width()); 454 ConvertPointToScreen(this, &screen_loc); 455 return screen_loc; 456 } 457 458 void TableView::OnPaint(gfx::Canvas* canvas) { 459 // Don't invoke View::OnPaint so that we can render our own focus border. 460 461 canvas->DrawColor(GetNativeTheme()->GetSystemColor( 462 ui::NativeTheme::kColorId_TableBackground)); 463 464 if (!RowCount() || visible_columns_.empty()) 465 return; 466 467 const PaintRegion region(GetPaintRegion(GetPaintBounds(canvas))); 468 if (region.min_column == -1) 469 return; // No need to paint anything. 470 471 const SkColor selected_bg_color = GetNativeTheme()->GetSystemColor( 472 text_background_color_id(HasFocus())); 473 const SkColor fg_color = GetNativeTheme()->GetSystemColor( 474 ui::NativeTheme::kColorId_TableText); 475 const SkColor selected_fg_color = GetNativeTheme()->GetSystemColor( 476 selected_text_color_id(HasFocus())); 477 for (int i = region.min_row; i < region.max_row; ++i) { 478 const int model_index = ViewToModel(i); 479 const bool is_selected = selection_model_.IsSelected(model_index); 480 if (is_selected) { 481 canvas->FillRect(GetRowBounds(i), selected_bg_color); 482 } else if (row_background_painter_) { 483 row_background_painter_->PaintRowBackground(model_index, 484 GetRowBounds(i), 485 canvas); 486 } 487 if (selection_model_.active() == i && HasFocus()) 488 canvas->DrawFocusRect(GetRowBounds(i)); 489 for (int j = region.min_column; j < region.max_column; ++j) { 490 const gfx::Rect cell_bounds(GetCellBounds(i, j)); 491 int text_x = kTextHorizontalPadding + cell_bounds.x(); 492 493 // Provide space for the grouping indicator, but draw it separately. 494 if (j == 0 && grouper_) 495 text_x += kGroupingIndicatorSize + kTextHorizontalPadding; 496 497 // Always paint the icon in the first visible column. 498 if (j == 0 && table_type_ == ICON_AND_TEXT) { 499 gfx::ImageSkia image = model_->GetIcon(model_index); 500 if (!image.isNull()) { 501 int image_x = GetMirroredXWithWidthInView(text_x, kImageSize); 502 canvas->DrawImageInt( 503 image, 0, 0, image.width(), image.height(), 504 image_x, 505 cell_bounds.y() + (cell_bounds.height() - kImageSize) / 2, 506 kImageSize, kImageSize, true); 507 } 508 text_x += kImageSize + kTextHorizontalPadding; 509 } 510 if (text_x < cell_bounds.right() - kTextHorizontalPadding) { 511 canvas->DrawStringInt( 512 model_->GetText(model_index, visible_columns_[j].column.id), font_, 513 is_selected ? selected_fg_color : fg_color, 514 GetMirroredXWithWidthInView(text_x, cell_bounds.right() - text_x - 515 kTextHorizontalPadding), 516 cell_bounds.y() + kTextVerticalPadding, 517 cell_bounds.right() - text_x, 518 cell_bounds.height() - kTextVerticalPadding * 2, 519 TableColumnAlignmentToCanvasAlignment( 520 visible_columns_[j].column.alignment)); 521 } 522 } 523 } 524 525 if (!grouper_ || region.min_column > 0) 526 return; 527 528 const SkColor grouping_color = GetNativeTheme()->GetSystemColor( 529 ui::NativeTheme::kColorId_TableGroupingIndicatorColor); 530 SkPaint grouping_paint; 531 grouping_paint.setColor(grouping_color); 532 grouping_paint.setStyle(SkPaint::kFill_Style); 533 grouping_paint.setAntiAlias(true); 534 const int group_indicator_x = GetMirroredXInView(GetCellBounds(0, 0).x() + 535 kTextHorizontalPadding + kGroupingIndicatorSize / 2); 536 for (int i = region.min_row; i < region.max_row; ) { 537 const int model_index = ViewToModel(i); 538 GroupRange range; 539 grouper_->GetGroupRange(model_index, &range); 540 DCHECK_GT(range.length, 0); 541 // The order of rows in a group is consistent regardless of sort, so it's ok 542 // to do this calculation. 543 const int start = i - (model_index - range.start); 544 const int last = start + range.length - 1; 545 const gfx::Rect start_cell_bounds(GetCellBounds(start, 0)); 546 if (start != last) { 547 const gfx::Rect last_cell_bounds(GetCellBounds(last, 0)); 548 canvas->FillRect(gfx::Rect( 549 group_indicator_x - kGroupingIndicatorSize / 2, 550 start_cell_bounds.CenterPoint().y(), 551 kGroupingIndicatorSize, 552 last_cell_bounds.y() - start_cell_bounds.y()), 553 grouping_color); 554 canvas->DrawCircle(gfx::Point(group_indicator_x, 555 last_cell_bounds.CenterPoint().y()), 556 kGroupingIndicatorSize / 2, grouping_paint); 557 } 558 canvas->DrawCircle(gfx::Point(group_indicator_x, 559 start_cell_bounds.CenterPoint().y()), 560 kGroupingIndicatorSize / 2, grouping_paint); 561 i = last + 1; 562 } 563 } 564 565 void TableView::OnFocus() { 566 SchedulePaintForSelection(); 567 } 568 569 void TableView::OnBlur() { 570 SchedulePaintForSelection(); 571 } 572 573 void TableView::NumRowsChanged() { 574 SortItemsAndUpdateMapping(); 575 PreferredSizeChanged(); 576 SchedulePaint(); 577 } 578 579 void TableView::SetSortDescriptors(const SortDescriptors& sort_descriptors) { 580 sort_descriptors_ = sort_descriptors; 581 SortItemsAndUpdateMapping(); 582 if (header_) 583 header_->SchedulePaint(); 584 } 585 586 void TableView::SortItemsAndUpdateMapping() { 587 if (!is_sorted()) { 588 view_to_model_.clear(); 589 model_to_view_.clear(); 590 } else { 591 const int row_count = RowCount(); 592 view_to_model_.resize(row_count); 593 model_to_view_.resize(row_count); 594 for (int i = 0; i < row_count; ++i) 595 view_to_model_[i] = i; 596 if (grouper_) { 597 GroupSortHelper sort_helper(this); 598 GetModelIndexToRangeStart(grouper_, RowCount(), 599 &sort_helper.model_index_to_range_start); 600 std::sort(view_to_model_.begin(), view_to_model_.end(), sort_helper); 601 } else { 602 std::sort(view_to_model_.begin(), view_to_model_.end(), SortHelper(this)); 603 } 604 for (int i = 0; i < row_count; ++i) 605 model_to_view_[view_to_model_[i]] = i; 606 model_->ClearCollator(); 607 } 608 SchedulePaint(); 609 } 610 611 int TableView::CompareRows(int model_row1, int model_row2) { 612 const int sort_result = model_->CompareValues( 613 model_row1, model_row2, sort_descriptors_[0].column_id); 614 if (sort_result == 0 && sort_descriptors_.size() > 1) { 615 // Try the secondary sort. 616 return SwapCompareResult( 617 model_->CompareValues(model_row1, model_row2, 618 sort_descriptors_[1].column_id), 619 sort_descriptors_[1].ascending); 620 } 621 return SwapCompareResult(sort_result, sort_descriptors_[0].ascending); 622 } 623 624 gfx::Rect TableView::GetRowBounds(int row) const { 625 return gfx::Rect(0, row * row_height_, width(), row_height_); 626 } 627 628 gfx::Rect TableView::GetCellBounds(int row, int visible_column_index) const { 629 if (!header_) 630 return GetRowBounds(row); 631 const VisibleColumn& vis_col(visible_columns_[visible_column_index]); 632 return gfx::Rect(vis_col.x, row * row_height_, vis_col.width, row_height_); 633 } 634 635 void TableView::AdjustCellBoundsForText(int visible_column_index, 636 gfx::Rect* bounds) const { 637 int text_x = kTextHorizontalPadding + bounds->x(); 638 if (visible_column_index == 0) { 639 if (grouper_) 640 text_x += kGroupingIndicatorSize + kTextHorizontalPadding; 641 if (table_type_ == ICON_AND_TEXT) 642 text_x += kImageSize + kTextHorizontalPadding; 643 } 644 bounds->set_x(text_x); 645 bounds->set_width( 646 std::max(0, bounds->right() - kTextHorizontalPadding - text_x)); 647 } 648 649 void TableView::CreateHeaderIfNecessary() { 650 // Only create a header if there is more than one column or the title of the 651 // only column is not empty. 652 if (header_ || (columns_.size() == 1 && columns_[0].title.empty())) 653 return; 654 655 header_ = new TableHeader(this); 656 } 657 658 void TableView::UpdateVisibleColumnSizes() { 659 if (!header_) 660 return; 661 662 std::vector<ui::TableColumn> columns; 663 for (size_t i = 0; i < visible_columns_.size(); ++i) 664 columns.push_back(visible_columns_[i].column); 665 666 int first_column_padding = 0; 667 if (table_type_ == ICON_AND_TEXT && header_) 668 first_column_padding += kImageSize + kTextHorizontalPadding; 669 if (grouper_) 670 first_column_padding += kGroupingIndicatorSize + kTextHorizontalPadding; 671 672 std::vector<int> sizes = views::CalculateTableColumnSizes( 673 layout_width_, first_column_padding, header_->font(), font_, 674 std::max(kTextHorizontalPadding, TableHeader::kHorizontalPadding) * 2, 675 TableHeader::kSortIndicatorWidth, columns, model_); 676 DCHECK_EQ(visible_columns_.size(), sizes.size()); 677 int x = 0; 678 for (size_t i = 0; i < visible_columns_.size(); ++i) { 679 visible_columns_[i].x = x; 680 visible_columns_[i].width = sizes[i]; 681 x += sizes[i]; 682 } 683 } 684 685 TableView::PaintRegion TableView::GetPaintRegion( 686 const gfx::Rect& bounds) const { 687 DCHECK(!visible_columns_.empty()); 688 DCHECK(RowCount()); 689 690 PaintRegion region; 691 region.min_row = std::min(RowCount() - 1, 692 std::max(0, bounds.y() / row_height_)); 693 region.max_row = bounds.bottom() / row_height_; 694 if (bounds.bottom() % row_height_ != 0) 695 region.max_row++; 696 region.max_row = std::min(region.max_row, RowCount()); 697 698 if (!header_) { 699 region.max_column = 1; 700 return region; 701 } 702 703 const int paint_x = GetMirroredXForRect(bounds); 704 const int paint_max_x = paint_x + bounds.width(); 705 region.min_column = -1; 706 region.max_column = visible_columns_.size(); 707 for (size_t i = 0; i < visible_columns_.size(); ++i) { 708 int max_x = visible_columns_[i].x + visible_columns_[i].width; 709 if (region.min_column == -1 && max_x >= paint_x) 710 region.min_column = static_cast<int>(i); 711 if (region.min_column != -1 && visible_columns_[i].x >= paint_max_x) { 712 region.max_column = i; 713 break; 714 } 715 } 716 return region; 717 } 718 719 gfx::Rect TableView::GetPaintBounds(gfx::Canvas* canvas) const { 720 SkRect sk_clip_rect; 721 if (canvas->sk_canvas()->getClipBounds(&sk_clip_rect)) 722 return gfx::ToEnclosingRect(gfx::SkRectToRectF(sk_clip_rect)); 723 return GetVisibleBounds(); 724 } 725 726 void TableView::SchedulePaintForSelection() { 727 if (selection_model_.size() == 1) { 728 const int first_model_row = FirstSelectedRow(); 729 SchedulePaintInRect(GetRowBounds(ModelToView(first_model_row))); 730 if (first_model_row != selection_model_.active()) 731 SchedulePaintInRect(GetRowBounds(ModelToView(selection_model_.active()))); 732 } else if (selection_model_.size() > 1) { 733 SchedulePaint(); 734 } 735 } 736 737 ui::TableColumn TableView::FindColumnByID(int id) const { 738 for (size_t i = 0; i < columns_.size(); ++i) { 739 if (columns_[i].id == id) 740 return columns_[i]; 741 } 742 NOTREACHED(); 743 return ui::TableColumn(); 744 } 745 746 void TableView::SelectByViewIndex(int view_index) { 747 ui::ListSelectionModel new_selection; 748 if (view_index != -1) { 749 SelectRowsInRangeFrom(view_index, true, &new_selection); 750 new_selection.set_anchor(ViewToModel(view_index)); 751 new_selection.set_active(ViewToModel(view_index)); 752 } 753 754 SetSelectionModel(new_selection); 755 } 756 757 void TableView::SetSelectionModel(const ui::ListSelectionModel& new_selection) { 758 if (new_selection.Equals(selection_model_)) 759 return; 760 761 SchedulePaintForSelection(); 762 selection_model_.Copy(new_selection); 763 SchedulePaintForSelection(); 764 765 // Scroll the group for the active item to visible. 766 if (selection_model_.active() != -1) { 767 gfx::Rect vis_rect(GetVisibleBounds()); 768 const GroupRange range(GetGroupRange(selection_model_.active())); 769 const int start_y = GetRowBounds(ModelToView(range.start)).y(); 770 const int end_y = 771 GetRowBounds(ModelToView(range.start + range.length - 1)).bottom(); 772 vis_rect.set_y(start_y); 773 vis_rect.set_height(end_y - start_y); 774 ScrollRectToVisible(vis_rect); 775 } 776 777 if (table_view_observer_) 778 table_view_observer_->OnSelectionChanged(); 779 } 780 781 void TableView::AdvanceSelection(AdvanceDirection direction) { 782 if (selection_model_.active() == -1) { 783 SelectByViewIndex(0); 784 return; 785 } 786 int view_index = ModelToView(selection_model_.active()); 787 if (direction == ADVANCE_DECREMENT) 788 view_index = std::max(0, view_index - 1); 789 else 790 view_index = std::min(RowCount() - 1, view_index + 1); 791 SelectByViewIndex(view_index); 792 } 793 794 void TableView::ConfigureSelectionModelForEvent( 795 const ui::MouseEvent& event, 796 ui::ListSelectionModel* model) const { 797 const int view_index = event.y() / row_height_; 798 DCHECK(view_index >= 0 && view_index < RowCount()); 799 800 if (selection_model_.anchor() == -1 || 801 single_selection_ || 802 (!event.IsControlDown() && !event.IsShiftDown())) { 803 SelectRowsInRangeFrom(view_index, true, model); 804 model->set_anchor(ViewToModel(view_index)); 805 model->set_active(ViewToModel(view_index)); 806 return; 807 } 808 if ((event.IsControlDown() && event.IsShiftDown()) || event.IsShiftDown()) { 809 // control-shift: copy existing model and make sure rows between anchor and 810 // |view_index| are selected. 811 // shift: reset selection so that only rows between anchor and |view_index| 812 // are selected. 813 if (event.IsControlDown() && event.IsShiftDown()) 814 model->Copy(selection_model_); 815 else 816 model->set_anchor(selection_model_.anchor()); 817 for (int i = std::min(view_index, ModelToView(model->anchor())), 818 end = std::max(view_index, ModelToView(model->anchor())); 819 i <= end; ++i) { 820 SelectRowsInRangeFrom(i, true, model); 821 } 822 model->set_active(ViewToModel(view_index)); 823 } else { 824 DCHECK(event.IsControlDown()); 825 // Toggle the selection state of |view_index| and set the anchor/active to 826 // it and don't change the state of any other rows. 827 model->Copy(selection_model_); 828 model->set_anchor(ViewToModel(view_index)); 829 model->set_active(ViewToModel(view_index)); 830 SelectRowsInRangeFrom(view_index, 831 !model->IsSelected(ViewToModel(view_index)), 832 model); 833 } 834 } 835 836 void TableView::SelectRowsInRangeFrom(int view_index, 837 bool select, 838 ui::ListSelectionModel* model) const { 839 const GroupRange range(GetGroupRange(ViewToModel(view_index))); 840 for (int i = 0; i < range.length; ++i) { 841 if (select) 842 model->AddIndexToSelection(range.start + i); 843 else 844 model->RemoveIndexFromSelection(range.start + i); 845 } 846 } 847 848 GroupRange TableView::GetGroupRange(int model_index) const { 849 GroupRange range; 850 if (grouper_) { 851 grouper_->GetGroupRange(model_index, &range); 852 } else { 853 range.start = model_index; 854 range.length = 1; 855 } 856 return range; 857 } 858 859 bool TableView::GetTooltipImpl(const gfx::Point& location, 860 string16* tooltip, 861 gfx::Point* tooltip_origin) const { 862 const int row = location.y() / row_height_; 863 if (row < 0 || row >= RowCount() || visible_columns_.empty()) 864 return false; 865 866 const int x = GetMirroredXInView(location.x()); 867 const int column = GetClosestVisibleColumnIndex(this, x); 868 if (x < visible_columns_[column].x || 869 x > (visible_columns_[column].x + visible_columns_[column].width)) 870 return false; 871 872 const string16 text(model_->GetText(ViewToModel(row), 873 visible_columns_[column].column.id)); 874 if (text.empty()) 875 return false; 876 877 gfx::Rect cell_bounds(GetCellBounds(row, column)); 878 AdjustCellBoundsForText(column, &cell_bounds); 879 const int right = std::min(GetVisibleBounds().right(), cell_bounds.right()); 880 if (right > cell_bounds.x() && 881 font_.GetStringWidth(text) <= (right - cell_bounds.x())) 882 return false; 883 884 if (tooltip) 885 *tooltip = text; 886 if (tooltip_origin) { 887 tooltip_origin->SetPoint(cell_bounds.x(), 888 cell_bounds.y() + kTextVerticalPadding); 889 } 890 return true; 891 } 892 893 } // namespace views 894