Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / ui / views / controls / tabbed_pane / tabbed_pane.cc
blob78edc3eaf7269ba76f4ca458bb04f6ca8e20c81f
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/tabbed_pane/tabbed_pane.h"
7 #include "base/logging.h"
8 #include "third_party/skia/include/core/SkPaint.h"
9 #include "third_party/skia/include/core/SkPath.h"
10 #include "ui/accessibility/ax_view_state.h"
11 #include "ui/base/resource/resource_bundle.h"
12 #include "ui/events/keycodes/keyboard_codes.h"
13 #include "ui/gfx/canvas.h"
14 #include "ui/gfx/font_list.h"
15 #include "ui/views/controls/label.h"
16 #include "ui/views/controls/tabbed_pane/tabbed_pane_listener.h"
17 #include "ui/views/layout/layout_manager.h"
18 #include "ui/views/widget/widget.h"
20 namespace {
22 // TODO(markusheintz|msw): Use NativeTheme colors.
23 const SkColor kTabTitleColor_Inactive = SkColorSetRGB(0x64, 0x64, 0x64);
24 const SkColor kTabTitleColor_Active = SK_ColorBLACK;
25 const SkColor kTabTitleColor_Hovered = SK_ColorBLACK;
26 const SkColor kTabBorderColor = SkColorSetRGB(0xC8, 0xC8, 0xC8);
27 const SkScalar kTabBorderThickness = 1.0f;
29 } // namespace
31 namespace views {
33 // static
34 const char TabbedPane::kViewClassName[] = "TabbedPane";
36 // The tab view shown in the tab strip.
37 class Tab : public View {
38 public:
39 // Internal class name.
40 static const char kViewClassName[];
42 Tab(TabbedPane* tabbed_pane, const base::string16& title, View* contents);
43 ~Tab() override;
45 View* contents() const { return contents_; }
47 bool selected() const { return contents_->visible(); }
48 void SetSelected(bool selected);
50 // Overridden from View:
51 bool OnMousePressed(const ui::MouseEvent& event) override;
52 void OnMouseEntered(const ui::MouseEvent& event) override;
53 void OnMouseExited(const ui::MouseEvent& event) override;
54 void OnGestureEvent(ui::GestureEvent* event) override;
55 gfx::Size GetPreferredSize() const override;
56 void Layout() override;
57 const char* GetClassName() const override;
59 private:
60 enum TabState {
61 TAB_INACTIVE,
62 TAB_ACTIVE,
63 TAB_HOVERED,
66 void SetState(TabState tab_state);
68 TabbedPane* tabbed_pane_;
69 Label* title_;
70 gfx::Size preferred_title_size_;
71 TabState tab_state_;
72 // The content view associated with this tab.
73 View* contents_;
75 DISALLOW_COPY_AND_ASSIGN(Tab);
78 // The tab strip shown above the tab contents.
79 class TabStrip : public View {
80 public:
81 // Internal class name.
82 static const char kViewClassName[];
84 explicit TabStrip(TabbedPane* tabbed_pane);
85 ~TabStrip() override;
87 // Overridden from View:
88 gfx::Size GetPreferredSize() const override;
89 void Layout() override;
90 const char* GetClassName() const override;
91 void OnPaint(gfx::Canvas* canvas) override;
93 private:
94 TabbedPane* tabbed_pane_;
96 DISALLOW_COPY_AND_ASSIGN(TabStrip);
99 // static
100 const char Tab::kViewClassName[] = "Tab";
102 Tab::Tab(TabbedPane* tabbed_pane, const base::string16& title, View* contents)
103 : tabbed_pane_(tabbed_pane),
104 title_(new Label(title,
105 ui::ResourceBundle::GetSharedInstance().GetFontList(
106 ui::ResourceBundle::BoldFont))),
107 tab_state_(TAB_ACTIVE),
108 contents_(contents) {
109 // Calculate this now while the font list is guaranteed to be bold.
110 preferred_title_size_ = title_->GetPreferredSize();
112 SetState(TAB_INACTIVE);
113 AddChildView(title_);
116 Tab::~Tab() {}
118 void Tab::SetSelected(bool selected) {
119 contents_->SetVisible(selected);
120 SetState(selected ? TAB_ACTIVE : TAB_INACTIVE);
123 bool Tab::OnMousePressed(const ui::MouseEvent& event) {
124 if (event.IsOnlyLeftMouseButton() &&
125 GetLocalBounds().Contains(event.location()))
126 tabbed_pane_->SelectTab(this);
127 return true;
130 void Tab::OnMouseEntered(const ui::MouseEvent& event) {
131 SetState(selected() ? TAB_ACTIVE : TAB_HOVERED);
134 void Tab::OnMouseExited(const ui::MouseEvent& event) {
135 SetState(selected() ? TAB_ACTIVE : TAB_INACTIVE);
138 void Tab::OnGestureEvent(ui::GestureEvent* event) {
139 switch (event->type()) {
140 case ui::ET_GESTURE_TAP_DOWN:
141 // Fallthrough.
142 case ui::ET_GESTURE_TAP:
143 // SelectTab also sets the right tab color.
144 tabbed_pane_->SelectTab(this);
145 break;
146 case ui::ET_GESTURE_TAP_CANCEL:
147 SetState(selected() ? TAB_ACTIVE : TAB_INACTIVE);
148 break;
149 default:
150 break;
152 event->SetHandled();
155 gfx::Size Tab::GetPreferredSize() const {
156 gfx::Size size(preferred_title_size_);
157 size.Enlarge(21, 9);
158 const int kTabMinWidth = 54;
159 if (size.width() < kTabMinWidth)
160 size.set_width(kTabMinWidth);
161 return size;
164 void Tab::Layout() {
165 gfx::Rect bounds = GetLocalBounds();
166 bounds.Inset(0, 1, 0, 0);
167 bounds.ClampToCenteredSize(preferred_title_size_);
168 title_->SetBoundsRect(bounds);
171 const char* Tab::GetClassName() const {
172 return kViewClassName;
175 void Tab::SetState(TabState tab_state) {
176 if (tab_state == tab_state_)
177 return;
178 tab_state_ = tab_state;
180 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
181 switch (tab_state) {
182 case TAB_INACTIVE:
183 title_->SetEnabledColor(kTabTitleColor_Inactive);
184 title_->SetFontList(rb.GetFontList(ui::ResourceBundle::BaseFont));
185 break;
186 case TAB_ACTIVE:
187 title_->SetEnabledColor(kTabTitleColor_Active);
188 title_->SetFontList(rb.GetFontList(ui::ResourceBundle::BoldFont));
189 break;
190 case TAB_HOVERED:
191 title_->SetEnabledColor(kTabTitleColor_Hovered);
192 title_->SetFontList(rb.GetFontList(ui::ResourceBundle::BaseFont));
193 break;
195 SchedulePaint();
198 // static
199 const char TabStrip::kViewClassName[] = "TabStrip";
201 TabStrip::TabStrip(TabbedPane* tabbed_pane) : tabbed_pane_(tabbed_pane) {}
203 TabStrip::~TabStrip() {}
205 gfx::Size TabStrip::GetPreferredSize() const {
206 gfx::Size size;
207 for (int i = 0; i < child_count(); ++i) {
208 const gfx::Size child_size = child_at(i)->GetPreferredSize();
209 size.SetSize(size.width() + child_size.width(),
210 std::max(size.height(), child_size.height()));
212 return size;
215 void TabStrip::Layout() {
216 const int kTabOffset = 9;
217 int x = kTabOffset; // Layout tabs with an offset to the tabstrip border.
218 for (int i = 0; i < child_count(); ++i) {
219 gfx::Size ps = child_at(i)->GetPreferredSize();
220 child_at(i)->SetBounds(x, 0, ps.width(), ps.height());
221 x = child_at(i)->bounds().right();
225 const char* TabStrip::GetClassName() const {
226 return kViewClassName;
229 void TabStrip::OnPaint(gfx::Canvas* canvas) {
230 OnPaintBackground(canvas);
232 // Draw the TabStrip border.
233 SkPaint paint;
234 paint.setColor(kTabBorderColor);
235 paint.setStrokeWidth(kTabBorderThickness);
236 SkScalar line_y = SkIntToScalar(height()) - (kTabBorderThickness / 2);
237 SkScalar line_end = SkIntToScalar(width());
238 int selected_tab_index = tabbed_pane_->selected_tab_index();
239 if (selected_tab_index >= 0) {
240 Tab* selected_tab = tabbed_pane_->GetTabAt(selected_tab_index);
241 SkPath path;
242 SkScalar tab_height =
243 SkIntToScalar(selected_tab->height()) - kTabBorderThickness;
244 SkScalar tab_width =
245 SkIntToScalar(selected_tab->width()) - kTabBorderThickness;
246 SkScalar tab_start = SkIntToScalar(selected_tab->GetMirroredX());
247 path.moveTo(0, line_y);
248 path.rLineTo(tab_start, 0);
249 path.rLineTo(0, -tab_height);
250 path.rLineTo(tab_width, 0);
251 path.rLineTo(0, tab_height);
252 path.lineTo(line_end, line_y);
254 SkPaint paint;
255 paint.setStyle(SkPaint::kStroke_Style);
256 paint.setColor(kTabBorderColor);
257 paint.setStrokeWidth(kTabBorderThickness);
258 canvas->DrawPath(path, paint);
259 } else {
260 canvas->sk_canvas()->drawLine(0, line_y, line_end, line_y, paint);
264 TabbedPane::TabbedPane()
265 : listener_(NULL),
266 tab_strip_(new TabStrip(this)),
267 contents_(new View()),
268 selected_tab_index_(-1) {
269 SetFocusable(true);
270 AddChildView(tab_strip_);
271 AddChildView(contents_);
274 TabbedPane::~TabbedPane() {}
276 int TabbedPane::GetTabCount() {
277 DCHECK_EQ(tab_strip_->child_count(), contents_->child_count());
278 return contents_->child_count();
281 View* TabbedPane::GetSelectedTab() {
282 return selected_tab_index() < 0 ?
283 NULL : GetTabAt(selected_tab_index())->contents();
286 void TabbedPane::AddTab(const base::string16& title, View* contents) {
287 AddTabAtIndex(tab_strip_->child_count(), title, contents);
290 void TabbedPane::AddTabAtIndex(int index,
291 const base::string16& title,
292 View* contents) {
293 DCHECK(index >= 0 && index <= GetTabCount());
294 contents->SetVisible(false);
296 tab_strip_->AddChildViewAt(new Tab(this, title, contents), index);
297 contents_->AddChildViewAt(contents, index);
298 if (selected_tab_index() < 0)
299 SelectTabAt(index);
301 PreferredSizeChanged();
304 void TabbedPane::SelectTabAt(int index) {
305 DCHECK(index >= 0 && index < GetTabCount());
306 if (index == selected_tab_index())
307 return;
309 if (selected_tab_index() >= 0)
310 GetTabAt(selected_tab_index())->SetSelected(false);
312 selected_tab_index_ = index;
313 Tab* tab = GetTabAt(index);
314 tab->SetSelected(true);
315 tab_strip_->SchedulePaint();
317 FocusManager* focus_manager = tab->contents()->GetFocusManager();
318 if (focus_manager) {
319 const View* focused_view = focus_manager->GetFocusedView();
320 if (focused_view && contents_->Contains(focused_view) &&
321 !tab->contents()->Contains(focused_view))
322 focus_manager->SetFocusedView(tab->contents());
325 if (listener())
326 listener()->TabSelectedAt(index);
329 void TabbedPane::SelectTab(Tab* tab) {
330 const int index = tab_strip_->GetIndexOf(tab);
331 if (index >= 0)
332 SelectTabAt(index);
335 gfx::Size TabbedPane::GetPreferredSize() const {
336 gfx::Size size;
337 for (int i = 0; i < contents_->child_count(); ++i)
338 size.SetToMax(contents_->child_at(i)->GetPreferredSize());
339 size.Enlarge(0, tab_strip_->GetPreferredSize().height());
340 return size;
343 Tab* TabbedPane::GetTabAt(int index) {
344 return static_cast<Tab*>(tab_strip_->child_at(index));
347 void TabbedPane::Layout() {
348 const gfx::Size size = tab_strip_->GetPreferredSize();
349 tab_strip_->SetBounds(0, 0, width(), size.height());
350 contents_->SetBounds(0, tab_strip_->bounds().bottom(), width(),
351 std::max(0, height() - size.height()));
352 for (int i = 0; i < contents_->child_count(); ++i)
353 contents_->child_at(i)->SetSize(contents_->size());
356 void TabbedPane::ViewHierarchyChanged(
357 const ViewHierarchyChangedDetails& details) {
358 if (details.is_add) {
359 // Support navigating tabs by Ctrl+Tab and Ctrl+Shift+Tab.
360 AddAccelerator(ui::Accelerator(ui::VKEY_TAB,
361 ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN));
362 AddAccelerator(ui::Accelerator(ui::VKEY_TAB, ui::EF_CONTROL_DOWN));
366 bool TabbedPane::AcceleratorPressed(const ui::Accelerator& accelerator) {
367 // Handle Ctrl+Tab and Ctrl+Shift+Tab navigation of pages.
368 DCHECK(accelerator.key_code() == ui::VKEY_TAB && accelerator.IsCtrlDown());
369 const int tab_count = GetTabCount();
370 if (tab_count <= 1)
371 return false;
372 const int increment = accelerator.IsShiftDown() ? -1 : 1;
373 int next_tab_index = (selected_tab_index() + increment) % tab_count;
374 // Wrap around.
375 if (next_tab_index < 0)
376 next_tab_index += tab_count;
377 SelectTabAt(next_tab_index);
378 return true;
381 const char* TabbedPane::GetClassName() const {
382 return kViewClassName;
385 void TabbedPane::OnFocus() {
386 View::OnFocus();
388 View* selected_tab = GetSelectedTab();
389 if (selected_tab) {
390 selected_tab->NotifyAccessibilityEvent(
391 ui::AX_EVENT_FOCUS, true);
395 void TabbedPane::GetAccessibleState(ui::AXViewState* state) {
396 state->role = ui::AX_ROLE_TAB_LIST;
399 } // namespace views