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