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_header.h"
      6 
      7 #include "third_party/skia/include/core/SkColor.h"
      8 #include "ui/base/cursor/cursor.h"
      9 #include "ui/gfx/canvas.h"
     10 #include "ui/gfx/text_utils.h"
     11 #include "ui/native_theme/native_theme.h"
     12 #include "ui/views/background.h"
     13 #include "ui/views/controls/table/table_utils.h"
     14 #include "ui/views/controls/table/table_view.h"
     15 #include "ui/views/native_cursor.h"
     16 
     17 namespace views {
     18 
     19 namespace {
     20 
     21 const int kVerticalPadding = 4;
     22 
     23 // The minimum width we allow a column to go down to.
     24 const int kMinColumnWidth = 10;
     25 
     26 // Distace from edge columns can be resized by.
     27 const int kResizePadding = 5;
     28 
     29 // Amount of space above/below the separator.
     30 const int kSeparatorPadding = 4;
     31 
     32 const SkColor kTextColor = SK_ColorBLACK;
     33 const SkColor kBackgroundColor1 = SkColorSetRGB(0xF9, 0xF9, 0xF9);
     34 const SkColor kBackgroundColor2 = SkColorSetRGB(0xE8, 0xE8, 0xE8);
     35 const SkColor kSeparatorColor = SkColorSetRGB(0xAA, 0xAA, 0xAA);
     36 
     37 // Size of the sort indicator (doesn't include padding).
     38 const int kSortIndicatorSize = 8;
     39 
     40 }  // namespace
     41 
     42 // static
     43 const int TableHeader::kHorizontalPadding = 7;
     44 // static
     45 const int TableHeader::kSortIndicatorWidth = kSortIndicatorSize +
     46     TableHeader::kHorizontalPadding * 2;
     47 
     48 typedef std::vector<TableView::VisibleColumn> Columns;
     49 
     50 TableHeader::TableHeader(TableView* table) : table_(table) {
     51   set_background(Background::CreateVerticalGradientBackground(
     52                      kBackgroundColor1, kBackgroundColor2));
     53 }
     54 
     55 TableHeader::~TableHeader() {
     56 }
     57 
     58 void TableHeader::Layout() {
     59   SetBounds(x(), y(), table_->width(), GetPreferredSize().height());
     60 }
     61 
     62 void TableHeader::OnPaint(gfx::Canvas* canvas) {
     63   // Paint the background and a separator at the bottom. The separator color
     64   // matches that of the border around the scrollview.
     65   OnPaintBackground(canvas);
     66   SkColor border_color = GetNativeTheme()->GetSystemColor(
     67       ui::NativeTheme::kColorId_UnfocusedBorderColor);
     68   canvas->DrawLine(gfx::Point(0, height() - 1),
     69                    gfx::Point(width(), height() - 1), border_color);
     70 
     71   const Columns& columns = table_->visible_columns();
     72   const int sorted_column_id = table_->sort_descriptors().empty() ? -1 :
     73       table_->sort_descriptors()[0].column_id;
     74   for (size_t i = 0; i < columns.size(); ++i) {
     75     if (columns[i].width >= 2) {
     76       const int separator_x = GetMirroredXInView(
     77           columns[i].x + columns[i].width - 1);
     78       canvas->DrawLine(gfx::Point(separator_x, kSeparatorPadding),
     79                        gfx::Point(separator_x, height() - kSeparatorPadding),
     80                        kSeparatorColor);
     81     }
     82 
     83     const int x = columns[i].x + kHorizontalPadding;
     84     int width = columns[i].width - kHorizontalPadding - kHorizontalPadding;
     85     if (width <= 0)
     86       continue;
     87 
     88     const int title_width =
     89         gfx::GetStringWidth(columns[i].column.title, font_list_);
     90     const bool paint_sort_indicator =
     91         (columns[i].column.id == sorted_column_id &&
     92          title_width + kSortIndicatorWidth <= width);
     93 
     94     if (paint_sort_indicator &&
     95         columns[i].column.alignment == ui::TableColumn::RIGHT) {
     96       width -= kSortIndicatorWidth;
     97     }
     98 
     99     canvas->DrawStringRectWithFlags(
    100         columns[i].column.title, font_list_, kTextColor,
    101         gfx::Rect(GetMirroredXWithWidthInView(x, width), kVerticalPadding,
    102                   width, height() - kVerticalPadding * 2),
    103         TableColumnAlignmentToCanvasAlignment(columns[i].column.alignment));
    104 
    105     if (paint_sort_indicator) {
    106       SkPaint paint;
    107       paint.setColor(kTextColor);
    108       paint.setStyle(SkPaint::kFill_Style);
    109       paint.setAntiAlias(true);
    110 
    111       int indicator_x = 0;
    112       ui::TableColumn::Alignment alignment = columns[i].column.alignment;
    113       if (base::i18n::IsRTL()) {
    114         if (alignment == ui::TableColumn::LEFT)
    115           alignment = ui::TableColumn::RIGHT;
    116         else if (alignment == ui::TableColumn::RIGHT)
    117           alignment = ui::TableColumn::LEFT;
    118       }
    119       switch (alignment) {
    120         case ui::TableColumn::LEFT:
    121           indicator_x = x + title_width;
    122           break;
    123         case ui::TableColumn::CENTER:
    124           indicator_x = x + width / 2;
    125           break;
    126         case ui::TableColumn::RIGHT:
    127           indicator_x = x + width;
    128           break;
    129       }
    130 
    131       const int scale = base::i18n::IsRTL() ? -1 : 1;
    132       indicator_x += (kSortIndicatorWidth - kSortIndicatorSize) / 2;
    133       indicator_x = GetMirroredXInView(indicator_x);
    134       int indicator_y = height() / 2 - kSortIndicatorSize / 2;
    135       SkPath indicator_path;
    136       if (table_->sort_descriptors()[0].ascending) {
    137         indicator_path.moveTo(
    138             SkIntToScalar(indicator_x),
    139             SkIntToScalar(indicator_y + kSortIndicatorSize));
    140         indicator_path.lineTo(
    141             SkIntToScalar(indicator_x + kSortIndicatorSize * scale),
    142             SkIntToScalar(indicator_y + kSortIndicatorSize));
    143         indicator_path.lineTo(
    144             SkIntToScalar(indicator_x + kSortIndicatorSize / 2 * scale),
    145             SkIntToScalar(indicator_y));
    146       } else {
    147         indicator_path.moveTo(SkIntToScalar(indicator_x),
    148                               SkIntToScalar(indicator_y));
    149         indicator_path.lineTo(
    150             SkIntToScalar(indicator_x + kSortIndicatorSize * scale),
    151             SkIntToScalar(indicator_y));
    152         indicator_path.lineTo(
    153             SkIntToScalar(indicator_x + kSortIndicatorSize / 2 * scale),
    154             SkIntToScalar(indicator_y + kSortIndicatorSize));
    155       }
    156       indicator_path.close();
    157       canvas->DrawPath(indicator_path, paint);
    158     }
    159   }
    160 }
    161 
    162 gfx::Size TableHeader::GetPreferredSize() const {
    163   return gfx::Size(1, kVerticalPadding * 2 + font_list_.GetHeight());
    164 }
    165 
    166 gfx::NativeCursor TableHeader::GetCursor(const ui::MouseEvent& event) {
    167   return GetResizeColumn(GetMirroredXInView(event.x())) != -1 ?
    168       GetNativeColumnResizeCursor() : View::GetCursor(event);
    169 }
    170 
    171 bool TableHeader::OnMousePressed(const ui::MouseEvent& event) {
    172   if (event.IsOnlyLeftMouseButton()) {
    173     StartResize(event);
    174     return true;
    175   }
    176 
    177   // Return false so that context menus on ancestors work.
    178   return false;
    179 }
    180 
    181 bool TableHeader::OnMouseDragged(const ui::MouseEvent& event) {
    182   ContinueResize(event);
    183   return true;
    184 }
    185 
    186 void TableHeader::OnMouseReleased(const ui::MouseEvent& event) {
    187   const bool was_resizing = resize_details_ != NULL;
    188   resize_details_.reset();
    189   if (!was_resizing && event.IsOnlyLeftMouseButton())
    190     ToggleSortOrder(event);
    191 }
    192 
    193 void TableHeader::OnMouseCaptureLost() {
    194   if (is_resizing()) {
    195     table_->SetVisibleColumnWidth(resize_details_->column_index,
    196                                   resize_details_->initial_width);
    197   }
    198   resize_details_.reset();
    199 }
    200 
    201 void TableHeader::OnGestureEvent(ui::GestureEvent* event) {
    202   switch (event->type()) {
    203     case ui::ET_GESTURE_TAP:
    204       if (!resize_details_.get())
    205         ToggleSortOrder(*event);
    206       break;
    207     case ui::ET_GESTURE_SCROLL_BEGIN:
    208       StartResize(*event);
    209       break;
    210     case ui::ET_GESTURE_SCROLL_UPDATE:
    211       ContinueResize(*event);
    212       break;
    213     case ui::ET_GESTURE_SCROLL_END:
    214       resize_details_.reset();
    215       break;
    216     default:
    217       return;
    218   }
    219   event->SetHandled();
    220 }
    221 
    222 bool TableHeader::StartResize(const ui::LocatedEvent& event) {
    223   if (is_resizing())
    224     return false;
    225 
    226   const int index = GetResizeColumn(GetMirroredXInView(event.x()));
    227   if (index == -1)
    228     return false;
    229 
    230   resize_details_.reset(new ColumnResizeDetails);
    231   resize_details_->column_index = index;
    232   resize_details_->initial_x = event.root_location().x();
    233   resize_details_->initial_width = table_->visible_columns()[index].width;
    234   return true;
    235 }
    236 
    237 void TableHeader::ContinueResize(const ui::LocatedEvent& event) {
    238   if (!is_resizing())
    239     return;
    240 
    241   const int scale = base::i18n::IsRTL() ? -1 : 1;
    242   const int delta = scale *
    243       (event.root_location().x() - resize_details_->initial_x);
    244   table_->SetVisibleColumnWidth(
    245       resize_details_->column_index,
    246       std::max(kMinColumnWidth, resize_details_->initial_width + delta));
    247 }
    248 
    249 void TableHeader::ToggleSortOrder(const ui::LocatedEvent& event) {
    250   if (table_->visible_columns().empty())
    251     return;
    252 
    253   const int x = GetMirroredXInView(event.x());
    254   const int index = GetClosestVisibleColumnIndex(table_, x);
    255   const TableView::VisibleColumn& column(table_->visible_columns()[index]);
    256   if (x >= column.x && x < column.x + column.width && event.y() >= 0 &&
    257       event.y() < height())
    258     table_->ToggleSortOrder(index);
    259 }
    260 
    261 int TableHeader::GetResizeColumn(int x) const {
    262   const Columns& columns(table_->visible_columns());
    263   if (columns.empty())
    264     return -1;
    265 
    266   const int index = GetClosestVisibleColumnIndex(table_, x);
    267   DCHECK_NE(-1, index);
    268   const TableView::VisibleColumn& column(table_->visible_columns()[index]);
    269   if (index > 0 && x >= column.x - kResizePadding &&
    270       x <= column.x + kResizePadding) {
    271     return index - 1;
    272   }
    273   const int max_x = column.x + column.width;
    274   return (x >= max_x - kResizePadding && x <= max_x + kResizePadding) ?
    275       index : -1;
    276 }
    277 
    278 }  // namespace views
    279