// 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