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