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 const int index = GetResizeColumn(GetMirroredXInView(event.x())); 183 if (index != -1) { 184 DCHECK(!is_resizing()); 185 resize_details_.reset(new ColumnResizeDetails); 186 resize_details_->column_index = index; 187 resize_details_->initial_x = event.root_location().x(); 188 resize_details_->initial_width = 189 table_->visible_columns()[index].width; 190 } 191 return true; 192 } 193 // Return false so that context menus on ancestors work. 194 return false; 195 } 196 197 bool TableHeader::OnMouseDragged(const ui::MouseEvent& event) { 198 if (is_resizing()) { 199 const int scale = base::i18n::IsRTL() ? -1 : 1; 200 const int delta = scale * 201 (event.root_location().x() - resize_details_->initial_x); 202 table_->SetVisibleColumnWidth( 203 resize_details_->column_index, 204 std::max(kMinColumnWidth, resize_details_->initial_width + delta)); 205 } 206 return true; 207 } 208 209 void TableHeader::OnMouseReleased(const ui::MouseEvent& event) { 210 const bool was_resizing = resize_details_ != NULL; 211 resize_details_.reset(); 212 if (!was_resizing && event.IsOnlyLeftMouseButton() && 213 !table_->visible_columns().empty()) { 214 const int x = GetMirroredXInView(event.x()); 215 const int index = GetClosestVisibleColumnIndex(table_, x); 216 const TableView::VisibleColumn& column(table_->visible_columns()[index]); 217 if (x >= column.x && x < column.x + column.width && event.y() >= 0 && 218 event.y() < height()) 219 table_->ToggleSortOrder(index); 220 } 221 } 222 223 void TableHeader::OnMouseCaptureLost() { 224 if (is_resizing()) { 225 table_->SetVisibleColumnWidth(resize_details_->column_index, 226 resize_details_->initial_width); 227 } 228 resize_details_.reset(); 229 } 230 231 int TableHeader::GetResizeColumn(int x) const { 232 const Columns& columns(table_->visible_columns()); 233 if (columns.empty()) 234 return -1; 235 236 const int index = GetClosestVisibleColumnIndex(table_, x); 237 DCHECK_NE(-1, index); 238 const TableView::VisibleColumn& column(table_->visible_columns()[index]); 239 if (index > 0 && x >= column.x - kResizePadding && 240 x <= column.x + kResizePadding) { 241 return index - 1; 242 } 243 const int max_x = column.x + column.width; 244 return (x >= max_x - kResizePadding && x <= max_x + kResizePadding) ? 245 index : -1; 246 } 247 248 } // namespace views 249