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.
5 #include "ui/views/controls/table/table_header.h"
7 #include "third_party/skia/include/core/SkColor.h"
8 #include "ui/gfx/canvas.h"
9 #include "ui/native_theme/native_theme.h"
10 #include "ui/views/background.h"
11 #include "ui/views/controls/table/table_utils.h"
12 #include "ui/views/controls/table/table_view.h"
15 #include "ui/base/cursor/cursor.h"
22 const int kVerticalPadding = 4;
24 // The minimum width we allow a column to go down to.
25 const int kMinColumnWidth = 10;
27 // Distace from edge columns can be resized by.
28 const int kResizePadding = 5;
30 // Amount of space above/below the separator.
31 const int kSeparatorPadding = 4;
33 const SkColor kTextColor = SK_ColorBLACK;
34 const SkColor kBackgroundColor1 = SkColorSetRGB(0xF9, 0xF9, 0xF9);
35 const SkColor kBackgroundColor2 = SkColorSetRGB(0xE8, 0xE8, 0xE8);
36 const SkColor kSeparatorColor = SkColorSetRGB(0xAA, 0xAA, 0xAA);
38 // Size of the sort indicator (doesn't include padding).
39 const int kSortIndicatorSize = 8;
41 gfx::NativeCursor GetResizeCursor() {
43 return ui::kCursorColumnResize;
45 static HCURSOR g_hand_cursor = LoadCursor(NULL, IDC_SIZEWE);
53 const int TableHeader::kHorizontalPadding = 7;
55 const int TableHeader::kSortIndicatorWidth = kSortIndicatorSize +
56 TableHeader::kHorizontalPadding * 2;
58 typedef std::vector<TableView::VisibleColumn> Columns;
60 TableHeader::TableHeader(TableView* table) : table_(table) {
61 set_background(Background::CreateVerticalGradientBackground(
62 kBackgroundColor1, kBackgroundColor2));
65 TableHeader::~TableHeader() {
68 void TableHeader::Layout() {
69 SetBounds(x(), y(), table_->width(), GetPreferredSize().height());
72 void TableHeader::OnPaint(gfx::Canvas* canvas) {
73 // Paint the background and a separator at the bottom. The separator color
74 // matches that of the border around the scrollview.
75 OnPaintBackground(canvas);
76 SkColor border_color = GetNativeTheme()->GetSystemColor(
77 ui::NativeTheme::kColorId_UnfocusedBorderColor);
78 canvas->DrawLine(gfx::Point(0, height() - 1),
79 gfx::Point(width(), height() - 1), border_color);
81 const Columns& columns = table_->visible_columns();
82 const int sorted_column_id = table_->sort_descriptors().empty() ? -1 :
83 table_->sort_descriptors()[0].column_id;
84 for (size_t i = 0; i < columns.size(); ++i) {
85 if (columns[i].width >= 2) {
86 const int separator_x = GetMirroredXInView(
87 columns[i].x + columns[i].width - 1);
88 canvas->DrawLine(gfx::Point(separator_x, kSeparatorPadding),
89 gfx::Point(separator_x, height() - kSeparatorPadding),
93 const int x = columns[i].x + kHorizontalPadding;
94 int width = columns[i].width - kHorizontalPadding - kHorizontalPadding;
98 const int title_width = font_.GetStringWidth(columns[i].column.title);
99 const bool paint_sort_indicator =
100 (columns[i].column.id == sorted_column_id &&
101 title_width + kSortIndicatorWidth <= width);
103 if (paint_sort_indicator &&
104 columns[i].column.alignment == ui::TableColumn::RIGHT) {
105 width -= kSortIndicatorWidth;
108 canvas->DrawStringInt(
109 columns[i].column.title, font_, kTextColor,
110 GetMirroredXWithWidthInView(x, width), kVerticalPadding, width,
111 height() - kVerticalPadding * 2,
112 TableColumnAlignmentToCanvasAlignment(columns[i].column.alignment));
114 if (paint_sort_indicator) {
116 paint.setColor(kTextColor);
117 paint.setStyle(SkPaint::kFill_Style);
118 paint.setAntiAlias(true);
121 ui::TableColumn::Alignment alignment = columns[i].column.alignment;
122 if (base::i18n::IsRTL()) {
123 if (alignment == ui::TableColumn::LEFT)
124 alignment = ui::TableColumn::RIGHT;
125 else if (alignment == ui::TableColumn::RIGHT)
126 alignment = ui::TableColumn::LEFT;
129 case ui::TableColumn::LEFT:
130 indicator_x = x + title_width;
132 case ui::TableColumn::CENTER:
133 indicator_x = x + width / 2;
135 case ui::TableColumn::RIGHT:
136 indicator_x = x + width;
140 const int scale = base::i18n::IsRTL() ? -1 : 1;
141 indicator_x += (kSortIndicatorWidth - kSortIndicatorSize) / 2;
142 indicator_x = GetMirroredXInView(indicator_x);
143 int indicator_y = height() / 2 - kSortIndicatorSize / 2;
144 SkPath indicator_path;
145 if (table_->sort_descriptors()[0].ascending) {
146 indicator_path.moveTo(
147 SkIntToScalar(indicator_x),
148 SkIntToScalar(indicator_y + kSortIndicatorSize));
149 indicator_path.lineTo(
150 SkIntToScalar(indicator_x + kSortIndicatorSize * scale),
151 SkIntToScalar(indicator_y + kSortIndicatorSize));
152 indicator_path.lineTo(
153 SkIntToScalar(indicator_x + kSortIndicatorSize / 2 * scale),
154 SkIntToScalar(indicator_y));
156 indicator_path.moveTo(SkIntToScalar(indicator_x),
157 SkIntToScalar(indicator_y));
158 indicator_path.lineTo(
159 SkIntToScalar(indicator_x + kSortIndicatorSize * scale),
160 SkIntToScalar(indicator_y));
161 indicator_path.lineTo(
162 SkIntToScalar(indicator_x + kSortIndicatorSize / 2 * scale),
163 SkIntToScalar(indicator_y + kSortIndicatorSize));
165 indicator_path.close();
166 canvas->DrawPath(indicator_path, paint);
171 gfx::Size TableHeader::GetPreferredSize() {
172 return gfx::Size(1, kVerticalPadding * 2 + font_.GetHeight());
175 gfx::NativeCursor TableHeader::GetCursor(const ui::MouseEvent& event) {
176 return GetResizeColumn(GetMirroredXInView(event.x())) != -1 ?
177 GetResizeCursor() : View::GetCursor(event);
180 bool TableHeader::OnMousePressed(const ui::MouseEvent& event) {
181 if (event.IsOnlyLeftMouseButton() && StartResize(event))
184 // Return false so that context menus on ancestors work.
188 bool TableHeader::OnMouseDragged(const ui::MouseEvent& event) {
189 ContinueResize(event);
193 void TableHeader::OnMouseReleased(const ui::MouseEvent& event) {
194 const bool was_resizing = resize_details_ != NULL;
195 resize_details_.reset();
196 if (!was_resizing && event.IsOnlyLeftMouseButton())
197 ToggleSortOrder(event);
200 void TableHeader::OnMouseCaptureLost() {
202 table_->SetVisibleColumnWidth(resize_details_->column_index,
203 resize_details_->initial_width);
205 resize_details_.reset();
208 void TableHeader::OnGestureEvent(ui::GestureEvent* event) {
209 switch (event->type()) {
210 case ui::ET_GESTURE_TAP:
211 if (!resize_details_.get())
212 ToggleSortOrder(*event);
214 case ui::ET_GESTURE_SCROLL_BEGIN:
217 case ui::ET_GESTURE_SCROLL_UPDATE:
218 ContinueResize(*event);
220 case ui::ET_GESTURE_SCROLL_END:
221 resize_details_.reset();
229 bool TableHeader::StartResize(const ui::LocatedEvent& event) {
233 const int index = GetResizeColumn(GetMirroredXInView(event.x()));
237 resize_details_.reset(new ColumnResizeDetails);
238 resize_details_->column_index = index;
239 resize_details_->initial_x = event.root_location().x();
240 resize_details_->initial_width = table_->visible_columns()[index].width;
244 void TableHeader::ContinueResize(const ui::LocatedEvent& event) {
248 const int scale = base::i18n::IsRTL() ? -1 : 1;
249 const int delta = scale *
250 (event.root_location().x() - resize_details_->initial_x);
251 table_->SetVisibleColumnWidth(
252 resize_details_->column_index,
253 std::max(kMinColumnWidth, resize_details_->initial_width + delta));
256 void TableHeader::ToggleSortOrder(const ui::LocatedEvent& event) {
257 if (table_->visible_columns().empty())
260 const int x = GetMirroredXInView(event.x());
261 const int index = GetClosestVisibleColumnIndex(table_, x);
262 const TableView::VisibleColumn& column(table_->visible_columns()[index]);
263 if (x >= column.x && x < column.x + column.width && event.y() >= 0 &&
264 event.y() < height())
265 table_->ToggleSortOrder(index);
268 int TableHeader::GetResizeColumn(int x) const {
269 const Columns& columns(table_->visible_columns());
273 const int index = GetClosestVisibleColumnIndex(table_, x);
274 DCHECK_NE(-1, index);
275 const TableView::VisibleColumn& column(table_->visible_columns()[index]);
276 if (index > 0 && x >= column.x - kResizePadding &&
277 x <= column.x + kResizePadding) {
280 const int max_x = column.x + column.width;
281 return (x >= max_x - kResizePadding && x <= max_x + kResizePadding) ?