| OLD | NEW |
| (Empty) |
| 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 | |
| OLD | NEW |