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