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/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" 13 14 #if defined(USE_AURA) 15 #include "ui/base/cursor/cursor.h" 16 #endif 17 18 namespace views { 19 20 namespace { 21 22 const int kVerticalPadding = 4; 23 24 // The minimum width we allow a column to go down to. 25 const int kMinColumnWidth = 10; 26 27 // Distace from edge columns can be resized by. 28 const int kResizePadding = 5; 29 30 // Amount of space above/below the separator. 31 const int kSeparatorPadding = 4; 32 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); 37 38 // Size of the sort indicator (doesn't include padding). 39 const int kSortIndicatorSize = 8; 40 41 gfx::NativeCursor GetResizeCursor() { 42 #if defined(USE_AURA) 43 return ui::kCursorColumnResize; 44 #elif defined(OS_WIN) 45 static HCURSOR g_hand_cursor = LoadCursor(NULL, IDC_SIZEWE); 46 return g_hand_cursor; 47 #endif 48 } 49 50 } // namespace 51 52 // static 53 const int TableHeader::kHorizontalPadding = 7; 54 // static 55 const int TableHeader::kSortIndicatorWidth = kSortIndicatorSize + 56 TableHeader::kHorizontalPadding * 2; 57 58 typedef std::vector<TableView::VisibleColumn> Columns; 59 60 TableHeader::TableHeader(TableView* table) : table_(table) { 61 set_background(Background::CreateVerticalGradientBackground( 62 kBackgroundColor1, kBackgroundColor2)); 63 } 64 65 TableHeader::~TableHeader() { 66 } 67 68 void TableHeader::Layout() { 69 SetBounds(x(), y(), table_->width(), GetPreferredSize().height()); 70 } 71 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); 80 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), 90 kSeparatorColor); 91 } 92 93 const int x = columns[i].x + kHorizontalPadding; 94 int width = columns[i].width - kHorizontalPadding - kHorizontalPadding; 95 if (width <= 0) 96 continue; 97 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); 102 103 if (paint_sort_indicator && 104 columns[i].column.alignment == ui::TableColumn::RIGHT) { 105 width -= kSortIndicatorWidth; 106 } 107 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)); 113 114 if (paint_sort_indicator) { 115 SkPaint paint; 116 paint.setColor(kTextColor); 117 paint.setStyle(SkPaint::kFill_Style); 118 paint.setAntiAlias(true); 119 120 int indicator_x = 0; 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; 127 } 128 switch (alignment) { 129 case ui::TableColumn::LEFT: 130 indicator_x = x + title_width; 131 break; 132 case ui::TableColumn::CENTER: 133 indicator_x = x + width / 2; 134 break; 135 case ui::TableColumn::RIGHT: 136 indicator_x = x + width; 137 break; 138 } 139 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)); 155 } else { 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)); 164 } 165 indicator_path.close(); 166 canvas->DrawPath(indicator_path, paint); 167 } 168 } 169 } 170 171 gfx::Size TableHeader::GetPreferredSize() { 172 return gfx::Size(1, kVerticalPadding * 2 + font_.GetHeight()); 173 } 174 175 gfx::NativeCursor TableHeader::GetCursor(const ui::MouseEvent& event) { 176 return GetResizeColumn(GetMirroredXInView(event.x())) != -1 ? 177 GetResizeCursor() : View::GetCursor(event); 178 } 179 180 bool TableHeader::OnMousePressed(const ui::MouseEvent& event) { 181 if (event.IsOnlyLeftMouseButton()) { 182 StartResize(event); 183 return true; 184 } 185 186 // Return false so that context menus on ancestors work. 187 return false; 188 } 189 190 bool TableHeader::OnMouseDragged(const ui::MouseEvent& event) { 191 ContinueResize(event); 192 return true; 193 } 194 195 void TableHeader::OnMouseReleased(const ui::MouseEvent& event) { 196 const bool was_resizing = resize_details_ != NULL; 197 resize_details_.reset(); 198 if (!was_resizing && event.IsOnlyLeftMouseButton()) 199 ToggleSortOrder(event); 200 } 201 202 void TableHeader::OnMouseCaptureLost() { 203 if (is_resizing()) { 204 table_->SetVisibleColumnWidth(resize_details_->column_index, 205 resize_details_->initial_width); 206 } 207 resize_details_.reset(); 208 } 209 210 void TableHeader::OnGestureEvent(ui::GestureEvent* event) { 211 switch (event->type()) { 212 case ui::ET_GESTURE_TAP: 213 if (!resize_details_.get()) 214 ToggleSortOrder(*event); 215 break; 216 case ui::ET_GESTURE_SCROLL_BEGIN: 217 StartResize(*event); 218 break; 219 case ui::ET_GESTURE_SCROLL_UPDATE: 220 ContinueResize(*event); 221 break; 222 case ui::ET_GESTURE_SCROLL_END: 223 resize_details_.reset(); 224 break; 225 default: 226 return; 227 } 228 event->SetHandled(); 229 } 230 231 bool TableHeader::StartResize(const ui::LocatedEvent& event) { 232 if (is_resizing()) 233 return false; 234 235 const int index = GetResizeColumn(GetMirroredXInView(event.x())); 236 if (index == -1) 237 return false; 238 239 resize_details_.reset(new ColumnResizeDetails); 240 resize_details_->column_index = index; 241 resize_details_->initial_x = event.root_location().x(); 242 resize_details_->initial_width = table_->visible_columns()[index].width; 243 return true; 244 } 245 246 void TableHeader::ContinueResize(const ui::LocatedEvent& event) { 247 if (!is_resizing()) 248 return; 249 250 const int scale = base::i18n::IsRTL() ? -1 : 1; 251 const int delta = scale * 252 (event.root_location().x() - resize_details_->initial_x); 253 table_->SetVisibleColumnWidth( 254 resize_details_->column_index, 255 std::max(kMinColumnWidth, resize_details_->initial_width + delta)); 256 } 257 258 void TableHeader::ToggleSortOrder(const ui::LocatedEvent& event) { 259 if (table_->visible_columns().empty()) 260 return; 261 262 const int x = GetMirroredXInView(event.x()); 263 const int index = GetClosestVisibleColumnIndex(table_, x); 264 const TableView::VisibleColumn& column(table_->visible_columns()[index]); 265 if (x >= column.x && x < column.x + column.width && event.y() >= 0 && 266 event.y() < height()) 267 table_->ToggleSortOrder(index); 268 } 269 270 int TableHeader::GetResizeColumn(int x) const { 271 const Columns& columns(table_->visible_columns()); 272 if (columns.empty()) 273 return -1; 274 275 const int index = GetClosestVisibleColumnIndex(table_, x); 276 DCHECK_NE(-1, index); 277 const TableView::VisibleColumn& column(table_->visible_columns()[index]); 278 if (index > 0 && x >= column.x - kResizePadding && 279 x <= column.x + kResizePadding) { 280 return index - 1; 281 } 282 const int max_x = column.x + column.width; 283 return (x >= max_x - kResizePadding && x <= max_x + kResizePadding) ? 284 index : -1; 285 } 286 287 } // namespace views 288