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. 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 SetFocusable(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 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 406 bool TableView::GetTooltipText(const gfx::Point& p, 407 string16* tooltip) const { 408 return GetTooltipImpl(p, tooltip, NULL); 409 } 410 411 bool TableView::GetTooltipTextOrigin(const gfx::Point& p, 412 gfx::Point* loc) const { 413 return GetTooltipImpl(p, NULL, loc); 414 } 415 416 void TableView::OnModelChanged() { 417 selection_model_.Clear(); 418 NumRowsChanged(); 419 } 420 421 void TableView::OnItemsChanged(int start, int length) { 422 SortItemsAndUpdateMapping(); 423 } 424 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 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 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 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 579 void TableView::OnFocus() { 580 SchedulePaintForSelection(); 581 } 582 583 void TableView::OnBlur() { 584 SchedulePaintForSelection(); 585 } 586 587 void TableView::NumRowsChanged() { 588 SortItemsAndUpdateMapping(); 589 PreferredSizeChanged(); 590 SchedulePaint(); 591 } 592 593 void TableView::SetSortDescriptors(const SortDescriptors& sort_descriptors) { 594 sort_descriptors_ = sort_descriptors; 595 SortItemsAndUpdateMapping(); 596 if (header_) 597 header_->SchedulePaint(); 598 } 599 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 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 638 gfx::Rect TableView::GetRowBounds(int row) const { 639 return gfx::Rect(0, row * row_height_, width(), row_height_); 640 } 641 642 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 649 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 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 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 699 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 733 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 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 751 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 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 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 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 808 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 850 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 862 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 873 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