| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/views/controls/table/table_header.h" |
| |
| #include "third_party/skia/include/core/SkColor.h" |
| #include "ui/base/cursor/cursor.h" |
| #include "ui/gfx/canvas.h" |
| #include "ui/gfx/text_utils.h" |
| #include "ui/native_theme/native_theme.h" |
| #include "ui/views/background.h" |
| #include "ui/views/controls/table/table_utils.h" |
| #include "ui/views/controls/table/table_view.h" |
| #include "ui/views/native_cursor.h" |
| |
| namespace views { |
| |
| namespace { |
| |
| const int kVerticalPadding = 4; |
| |
| // The minimum width we allow a column to go down to. |
| const int kMinColumnWidth = 10; |
| |
| // Distace from edge columns can be resized by. |
| const int kResizePadding = 5; |
| |
| // Amount of space above/below the separator. |
| const int kSeparatorPadding = 4; |
| |
| const SkColor kTextColor = SK_ColorBLACK; |
| const SkColor kBackgroundColor1 = SkColorSetRGB(0xF9, 0xF9, 0xF9); |
| const SkColor kBackgroundColor2 = SkColorSetRGB(0xE8, 0xE8, 0xE8); |
| const SkColor kSeparatorColor = SkColorSetRGB(0xAA, 0xAA, 0xAA); |
| |
| // Size of the sort indicator (doesn't include padding). |
| const int kSortIndicatorSize = 8; |
| |
| } // namespace |
| |
| // static |
| const int TableHeader::kHorizontalPadding = 7; |
| // static |
| const int TableHeader::kSortIndicatorWidth = kSortIndicatorSize + |
| TableHeader::kHorizontalPadding * 2; |
| |
| typedef std::vector<TableView::VisibleColumn> Columns; |
| |
| TableHeader::TableHeader(TableView* table) : table_(table) { |
| set_background(Background::CreateVerticalGradientBackground( |
| kBackgroundColor1, kBackgroundColor2)); |
| } |
| |
| TableHeader::~TableHeader() { |
| } |
| |
| void TableHeader::Layout() { |
| SetBounds(x(), y(), table_->width(), GetPreferredSize().height()); |
| } |
| |
| void TableHeader::OnPaint(gfx::Canvas* canvas) { |
| // Paint the background and a separator at the bottom. The separator color |
| // matches that of the border around the scrollview. |
| OnPaintBackground(canvas); |
| SkColor border_color = GetNativeTheme()->GetSystemColor( |
| ui::NativeTheme::kColorId_UnfocusedBorderColor); |
| canvas->DrawLine(gfx::Point(0, height() - 1), |
| gfx::Point(width(), height() - 1), border_color); |
| |
| const Columns& columns = table_->visible_columns(); |
| const int sorted_column_id = table_->sort_descriptors().empty() ? -1 : |
| table_->sort_descriptors()[0].column_id; |
| for (size_t i = 0; i < columns.size(); ++i) { |
| if (columns[i].width >= 2) { |
| const int separator_x = GetMirroredXInView( |
| columns[i].x + columns[i].width - 1); |
| canvas->DrawLine(gfx::Point(separator_x, kSeparatorPadding), |
| gfx::Point(separator_x, height() - kSeparatorPadding), |
| kSeparatorColor); |
| } |
| |
| const int x = columns[i].x + kHorizontalPadding; |
| int width = columns[i].width - kHorizontalPadding - kHorizontalPadding; |
| if (width <= 0) |
| continue; |
| |
| const int title_width = |
| gfx::GetStringWidth(columns[i].column.title, font_list_); |
| const bool paint_sort_indicator = |
| (columns[i].column.id == sorted_column_id && |
| title_width + kSortIndicatorWidth <= width); |
| |
| if (paint_sort_indicator && |
| columns[i].column.alignment == ui::TableColumn::RIGHT) { |
| width -= kSortIndicatorWidth; |
| } |
| |
| canvas->DrawStringRectWithFlags( |
| columns[i].column.title, font_list_, kTextColor, |
| gfx::Rect(GetMirroredXWithWidthInView(x, width), kVerticalPadding, |
| width, height() - kVerticalPadding * 2), |
| TableColumnAlignmentToCanvasAlignment(columns[i].column.alignment)); |
| |
| if (paint_sort_indicator) { |
| SkPaint paint; |
| paint.setColor(kTextColor); |
| paint.setStyle(SkPaint::kFill_Style); |
| paint.setAntiAlias(true); |
| |
| int indicator_x = 0; |
| ui::TableColumn::Alignment alignment = columns[i].column.alignment; |
| if (base::i18n::IsRTL()) { |
| if (alignment == ui::TableColumn::LEFT) |
| alignment = ui::TableColumn::RIGHT; |
| else if (alignment == ui::TableColumn::RIGHT) |
| alignment = ui::TableColumn::LEFT; |
| } |
| switch (alignment) { |
| case ui::TableColumn::LEFT: |
| indicator_x = x + title_width; |
| break; |
| case ui::TableColumn::CENTER: |
| indicator_x = x + width / 2; |
| break; |
| case ui::TableColumn::RIGHT: |
| indicator_x = x + width; |
| break; |
| } |
| |
| const int scale = base::i18n::IsRTL() ? -1 : 1; |
| indicator_x += (kSortIndicatorWidth - kSortIndicatorSize) / 2; |
| indicator_x = GetMirroredXInView(indicator_x); |
| int indicator_y = height() / 2 - kSortIndicatorSize / 2; |
| SkPath indicator_path; |
| if (table_->sort_descriptors()[0].ascending) { |
| indicator_path.moveTo( |
| SkIntToScalar(indicator_x), |
| SkIntToScalar(indicator_y + kSortIndicatorSize)); |
| indicator_path.lineTo( |
| SkIntToScalar(indicator_x + kSortIndicatorSize * scale), |
| SkIntToScalar(indicator_y + kSortIndicatorSize)); |
| indicator_path.lineTo( |
| SkIntToScalar(indicator_x + kSortIndicatorSize / 2 * scale), |
| SkIntToScalar(indicator_y)); |
| } else { |
| indicator_path.moveTo(SkIntToScalar(indicator_x), |
| SkIntToScalar(indicator_y)); |
| indicator_path.lineTo( |
| SkIntToScalar(indicator_x + kSortIndicatorSize * scale), |
| SkIntToScalar(indicator_y)); |
| indicator_path.lineTo( |
| SkIntToScalar(indicator_x + kSortIndicatorSize / 2 * scale), |
| SkIntToScalar(indicator_y + kSortIndicatorSize)); |
| } |
| indicator_path.close(); |
| canvas->DrawPath(indicator_path, paint); |
| } |
| } |
| } |
| |
| gfx::Size TableHeader::GetPreferredSize() const { |
| return gfx::Size(1, kVerticalPadding * 2 + font_list_.GetHeight()); |
| } |
| |
| gfx::NativeCursor TableHeader::GetCursor(const ui::MouseEvent& event) { |
| return GetResizeColumn(GetMirroredXInView(event.x())) != -1 ? |
| GetNativeColumnResizeCursor() : View::GetCursor(event); |
| } |
| |
| bool TableHeader::OnMousePressed(const ui::MouseEvent& event) { |
| if (event.IsOnlyLeftMouseButton()) { |
| StartResize(event); |
| return true; |
| } |
| |
| // Return false so that context menus on ancestors work. |
| return false; |
| } |
| |
| bool TableHeader::OnMouseDragged(const ui::MouseEvent& event) { |
| ContinueResize(event); |
| return true; |
| } |
| |
| void TableHeader::OnMouseReleased(const ui::MouseEvent& event) { |
| const bool was_resizing = resize_details_ != NULL; |
| resize_details_.reset(); |
| if (!was_resizing && event.IsOnlyLeftMouseButton()) |
| ToggleSortOrder(event); |
| } |
| |
| void TableHeader::OnMouseCaptureLost() { |
| if (is_resizing()) { |
| table_->SetVisibleColumnWidth(resize_details_->column_index, |
| resize_details_->initial_width); |
| } |
| resize_details_.reset(); |
| } |
| |
| void TableHeader::OnGestureEvent(ui::GestureEvent* event) { |
| switch (event->type()) { |
| case ui::ET_GESTURE_TAP: |
| if (!resize_details_.get()) |
| ToggleSortOrder(*event); |
| break; |
| case ui::ET_GESTURE_SCROLL_BEGIN: |
| StartResize(*event); |
| break; |
| case ui::ET_GESTURE_SCROLL_UPDATE: |
| ContinueResize(*event); |
| break; |
| case ui::ET_GESTURE_SCROLL_END: |
| resize_details_.reset(); |
| break; |
| default: |
| return; |
| } |
| event->SetHandled(); |
| } |
| |
| bool TableHeader::StartResize(const ui::LocatedEvent& event) { |
| if (is_resizing()) |
| return false; |
| |
| const int index = GetResizeColumn(GetMirroredXInView(event.x())); |
| if (index == -1) |
| return false; |
| |
| resize_details_.reset(new ColumnResizeDetails); |
| resize_details_->column_index = index; |
| resize_details_->initial_x = event.root_location().x(); |
| resize_details_->initial_width = table_->visible_columns()[index].width; |
| return true; |
| } |
| |
| void TableHeader::ContinueResize(const ui::LocatedEvent& event) { |
| if (!is_resizing()) |
| return; |
| |
| const int scale = base::i18n::IsRTL() ? -1 : 1; |
| const int delta = scale * |
| (event.root_location().x() - resize_details_->initial_x); |
| table_->SetVisibleColumnWidth( |
| resize_details_->column_index, |
| std::max(kMinColumnWidth, resize_details_->initial_width + delta)); |
| } |
| |
| void TableHeader::ToggleSortOrder(const ui::LocatedEvent& event) { |
| if (table_->visible_columns().empty()) |
| return; |
| |
| const int x = GetMirroredXInView(event.x()); |
| const int index = GetClosestVisibleColumnIndex(table_, x); |
| const TableView::VisibleColumn& column(table_->visible_columns()[index]); |
| if (x >= column.x && x < column.x + column.width && event.y() >= 0 && |
| event.y() < height()) |
| table_->ToggleSortOrder(index); |
| } |
| |
| int TableHeader::GetResizeColumn(int x) const { |
| const Columns& columns(table_->visible_columns()); |
| if (columns.empty()) |
| return -1; |
| |
| const int index = GetClosestVisibleColumnIndex(table_, x); |
| DCHECK_NE(-1, index); |
| const TableView::VisibleColumn& column(table_->visible_columns()[index]); |
| if (index > 0 && x >= column.x - kResizePadding && |
| x <= column.x + kResizePadding) { |
| return index - 1; |
| } |
| const int max_x = column.x + column.width; |
| return (x >= max_x - kResizePadding && x <= max_x + kResizePadding) ? |
| index : -1; |
| } |
| |
| } // namespace views |