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/tabbed_pane/tabbed_pane.h"
6
7#include "base/logging.h"
8#include "ui/base/accessibility/accessible_view_state.h"
9#include "ui/base/keycodes/keyboard_codes.h"
10#include "ui/gfx/canvas.h"
11#include "ui/gfx/font.h"
12#include "ui/views/controls/label.h"
13#include "ui/views/controls/tabbed_pane/tabbed_pane_listener.h"
14#include "ui/views/layout/layout_manager.h"
15#include "ui/views/widget/widget.h"
16
17namespace {
18
19// TODO(markusheintz|msw): Use NativeTheme colors.
20const SkColor kTabTitleColor_Inactive = SkColorSetRGB(0x64, 0x64, 0x64);
21const SkColor kTabTitleColor_Active = SK_ColorBLACK;
22const SkColor kTabTitleColor_Hovered = SK_ColorBLACK;
23const SkColor kTabBorderColor = SkColorSetRGB(0xC8, 0xC8, 0xC8);
24const SkScalar kTabBorderThickness = 1.0f;
25
26}  // namespace
27
28namespace views {
29
30// static
31const char TabbedPane::kViewClassName[] = "TabbedPane";
32
33// The tab view shown in the tab strip.
34class Tab : public View {
35 public:
36  Tab(TabbedPane* tabbed_pane, const string16& title, View* contents);
37  virtual ~Tab();
38
39  View* contents() const { return contents_; }
40
41  bool selected() const { return contents_->visible(); }
42  void SetSelected(bool selected);
43
44  // Overridden from View:
45  virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE;
46  virtual void OnMouseReleased(const ui::MouseEvent& event) OVERRIDE;
47  virtual void OnMouseCaptureLost() OVERRIDE;
48  virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE;
49  virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE;
50  virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE;
51  virtual gfx::Size GetPreferredSize() OVERRIDE;
52  virtual void Layout() OVERRIDE;
53
54 private:
55  enum TabState {
56    TAB_INACTIVE,
57    TAB_ACTIVE,
58    TAB_PRESSED,
59    TAB_HOVERED,
60  };
61
62  void SetState(TabState tab_state);
63
64  TabbedPane* tabbed_pane_;
65  Label* title_;
66  gfx::Size preferred_title_size_;
67  TabState tab_state_;
68  // The content view associated with this tab.
69  View* contents_;
70
71  DISALLOW_COPY_AND_ASSIGN(Tab);
72};
73
74// The tab strip shown above the tab contents.
75class TabStrip : public View {
76 public:
77  explicit TabStrip(TabbedPane* tabbed_pane);
78  virtual ~TabStrip();
79
80  // Overridden from View:
81  virtual gfx::Size GetPreferredSize() OVERRIDE;
82  virtual void Layout() OVERRIDE;
83  virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
84
85 private:
86  TabbedPane* tabbed_pane_;
87
88  DISALLOW_COPY_AND_ASSIGN(TabStrip);
89};
90
91Tab::Tab(TabbedPane* tabbed_pane, const string16& title, View* contents)
92    : tabbed_pane_(tabbed_pane),
93      title_(new Label(title, gfx::Font().DeriveFont(0, gfx::Font::BOLD))),
94      tab_state_(TAB_ACTIVE),
95      contents_(contents) {
96  // Calculate this now while the font is guaranteed to be bold.
97  preferred_title_size_ = title_->GetPreferredSize();
98
99  SetState(TAB_INACTIVE);
100  AddChildView(title_);
101}
102
103Tab::~Tab() {}
104
105void Tab::SetSelected(bool selected) {
106  contents_->SetVisible(selected);
107  SetState(selected ? TAB_ACTIVE : TAB_INACTIVE);
108}
109
110bool Tab::OnMousePressed(const ui::MouseEvent& event) {
111  SetState(TAB_PRESSED);
112  return true;
113}
114
115void Tab::OnMouseReleased(const ui::MouseEvent& event) {
116  SetState(selected() ? TAB_ACTIVE : TAB_HOVERED);
117  if (GetLocalBounds().Contains(event.location()))
118    tabbed_pane_->SelectTab(this);
119}
120
121void Tab::OnMouseCaptureLost() {
122  SetState(TAB_INACTIVE);
123}
124
125void Tab::OnMouseEntered(const ui::MouseEvent& event) {
126  SetState(selected() ? TAB_ACTIVE : TAB_HOVERED);
127}
128
129void Tab::OnMouseExited(const ui::MouseEvent& event) {
130  SetState(selected() ? TAB_ACTIVE : TAB_INACTIVE);
131}
132
133void Tab::OnGestureEvent(ui::GestureEvent* event) {
134  switch (event->type()) {
135    case ui::ET_GESTURE_TAP_DOWN:
136      SetState(TAB_PRESSED);
137      break;
138    case ui::ET_GESTURE_TAP:
139      // SelectTab also sets the right tab color.
140      tabbed_pane_->SelectTab(this);
141      break;
142    case ui::ET_GESTURE_TAP_CANCEL:
143      SetState(selected() ? TAB_ACTIVE : TAB_INACTIVE);
144      break;
145    default:
146      break;
147  }
148  event->SetHandled();
149}
150
151gfx::Size Tab::GetPreferredSize() {
152  gfx::Size size(preferred_title_size_);
153  size.Enlarge(21, 9);
154  const int kTabMinWidth = 54;
155  if (size.width() < kTabMinWidth)
156    size.set_width(kTabMinWidth);
157  return size;
158}
159
160void Tab::Layout() {
161  gfx::Rect bounds = GetLocalBounds();
162  bounds.Inset(0, 1, 0, 0);
163  bounds.ClampToCenteredSize(preferred_title_size_);
164  title_->SetBoundsRect(bounds);
165}
166
167void Tab::SetState(TabState tab_state) {
168  if (tab_state == tab_state_)
169    return;
170  tab_state_ = tab_state;
171
172  switch (tab_state) {
173    case TAB_INACTIVE:
174      title_->SetEnabledColor(kTabTitleColor_Inactive);
175      title_->SetFont(gfx::Font());
176      break;
177    case TAB_ACTIVE:
178      title_->SetEnabledColor(kTabTitleColor_Active);
179      title_->SetFont(gfx::Font().DeriveFont(0, gfx::Font::BOLD));
180      break;
181    case TAB_PRESSED:
182      // No visual distinction for pressed state.
183      break;
184    case TAB_HOVERED:
185      title_->SetEnabledColor(kTabTitleColor_Hovered);
186      title_->SetFont(gfx::Font());
187      break;
188  }
189  SchedulePaint();
190}
191
192TabStrip::TabStrip(TabbedPane* tabbed_pane) : tabbed_pane_(tabbed_pane) {}
193
194TabStrip::~TabStrip() {}
195
196gfx::Size TabStrip::GetPreferredSize() {
197  gfx::Size size;
198  for (int i = 0; i < child_count(); ++i) {
199    const gfx::Size child_size = child_at(i)->GetPreferredSize();
200    size.SetSize(size.width() + child_size.width(),
201                 std::max(size.height(), child_size.height()));
202  }
203  return size;
204}
205
206void TabStrip::Layout() {
207  const int kTabOffset = 9;
208  int x = kTabOffset;  // Layout tabs with an offset to the tabstrip border.
209  for (int i = 0; i < child_count(); ++i) {
210    gfx::Size ps = child_at(i)->GetPreferredSize();
211    child_at(i)->SetBounds(x, 0, ps.width(), ps.height());
212    x = child_at(i)->bounds().right();
213  }
214}
215
216void TabStrip::OnPaint(gfx::Canvas* canvas) {
217  OnPaintBackground(canvas);
218
219  // Draw the TabStrip border.
220  SkPaint paint;
221  paint.setColor(kTabBorderColor);
222  paint.setStrokeWidth(kTabBorderThickness);
223  SkScalar line_y = SkIntToScalar(height()) - (kTabBorderThickness / 2);
224  SkScalar line_end = SkIntToScalar(width());
225  int selected_tab_index = tabbed_pane_->selected_tab_index();
226  if (selected_tab_index >= 0) {
227    Tab* selected_tab = tabbed_pane_->GetTabAt(selected_tab_index);
228    SkPath path;
229    SkScalar tab_height =
230        SkIntToScalar(selected_tab->height()) - kTabBorderThickness;
231    SkScalar tab_width =
232        SkIntToScalar(selected_tab->width()) - kTabBorderThickness;
233    SkScalar tab_start = SkIntToScalar(selected_tab->x());
234    path.moveTo(0, line_y);
235    path.rLineTo(tab_start, 0);
236    path.rLineTo(0, -tab_height);
237    path.rLineTo(tab_width, 0);
238    path.rLineTo(0, tab_height);
239    path.lineTo(line_end, line_y);
240
241    SkPaint paint;
242    paint.setStyle(SkPaint::kStroke_Style);
243    paint.setColor(kTabBorderColor);
244    paint.setStrokeWidth(kTabBorderThickness);
245    canvas->DrawPath(path, paint);
246  } else {
247    canvas->sk_canvas()->drawLine(0, line_y, line_end, line_y, paint);
248  }
249}
250
251TabbedPane::TabbedPane(bool draw_border)
252  : listener_(NULL),
253    tab_strip_(new TabStrip(this)),
254    contents_(new View()),
255    selected_tab_index_(-1) {
256  set_focusable(true);
257  AddChildView(tab_strip_);
258  AddChildView(contents_);
259  if (draw_border) {
260    contents_->set_border(Border::CreateSolidSidedBorder(0,
261                                                         kTabBorderThickness,
262                                                         kTabBorderThickness,
263                                                         kTabBorderThickness,
264                                                         kTabBorderColor));
265  }
266}
267
268TabbedPane::~TabbedPane() {}
269
270int TabbedPane::GetTabCount() {
271  DCHECK_EQ(tab_strip_->child_count(), contents_->child_count());
272  return contents_->child_count();
273}
274
275View* TabbedPane::GetSelectedTab() {
276  return selected_tab_index() < 0 ?
277      NULL : GetTabAt(selected_tab_index())->contents();
278}
279
280void TabbedPane::AddTab(const string16& title, View* contents) {
281  AddTabAtIndex(tab_strip_->child_count(), title, contents);
282}
283
284void TabbedPane::AddTabAtIndex(int index,
285                               const string16& title,
286                               View* contents) {
287  DCHECK(index >= 0 && index <= GetTabCount());
288  contents->SetVisible(false);
289
290  tab_strip_->AddChildViewAt(new Tab(this, title, contents), index);
291  contents_->AddChildViewAt(contents, index);
292  if (selected_tab_index() < 0)
293    SelectTabAt(index);
294
295  PreferredSizeChanged();
296}
297
298void TabbedPane::SelectTabAt(int index) {
299  DCHECK(index >= 0 && index < GetTabCount());
300  if (index == selected_tab_index())
301    return;
302
303  if (selected_tab_index() >= 0)
304    GetTabAt(selected_tab_index())->SetSelected(false);
305
306  selected_tab_index_ = index;
307  Tab* tab = GetTabAt(index);
308  tab->SetSelected(true);
309  tab_strip_->SchedulePaint();
310
311  FocusManager* focus_manager = tab->contents()->GetFocusManager();
312  if (focus_manager) {
313    const View* focused_view = focus_manager->GetFocusedView();
314    if (focused_view && contents_->Contains(focused_view) &&
315        !tab->contents()->Contains(focused_view))
316      focus_manager->SetFocusedView(tab->contents());
317  }
318
319  if (listener())
320    listener()->TabSelectedAt(index);
321}
322
323void TabbedPane::SelectTab(Tab* tab) {
324  const int index = tab_strip_->GetIndexOf(tab);
325  if (index >= 0)
326    SelectTabAt(index);
327}
328
329gfx::Size TabbedPane::GetPreferredSize() {
330  gfx::Size size;
331  for (int i = 0; i < contents_->child_count(); ++i)
332    size.SetToMax(contents_->child_at(i)->GetPreferredSize());
333  size.Enlarge(0, tab_strip_->GetPreferredSize().height());
334  return size;
335}
336
337Tab* TabbedPane::GetTabAt(int index) {
338  return static_cast<Tab*>(tab_strip_->child_at(index));
339}
340
341void TabbedPane::Layout() {
342  const gfx::Size size = tab_strip_->GetPreferredSize();
343  tab_strip_->SetBounds(0, 0, width(), size.height());
344  contents_->SetBounds(0, tab_strip_->bounds().bottom(), width(),
345                       std::max(0, height() - size.height()));
346  for (int i = 0; i < contents_->child_count(); ++i)
347    contents_->child_at(i)->SetSize(contents_->size());
348}
349
350void TabbedPane::ViewHierarchyChanged(
351    const ViewHierarchyChangedDetails& details) {
352  if (details.is_add) {
353    // Support navigating tabs by Ctrl+Tab and Ctrl+Shift+Tab.
354    AddAccelerator(ui::Accelerator(ui::VKEY_TAB,
355                                   ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN));
356    AddAccelerator(ui::Accelerator(ui::VKEY_TAB, ui::EF_CONTROL_DOWN));
357  }
358}
359
360bool TabbedPane::AcceleratorPressed(const ui::Accelerator& accelerator) {
361  // Handle Ctrl+Tab and Ctrl+Shift+Tab navigation of pages.
362  DCHECK(accelerator.key_code() == ui::VKEY_TAB && accelerator.IsCtrlDown());
363  const int tab_count = GetTabCount();
364  if (tab_count <= 1)
365    return false;
366  const int increment = accelerator.IsShiftDown() ? -1 : 1;
367  int next_tab_index = (selected_tab_index() + increment) % tab_count;
368  // Wrap around.
369  if (next_tab_index < 0)
370    next_tab_index += tab_count;
371  SelectTabAt(next_tab_index);
372  return true;
373}
374
375const char* TabbedPane::GetClassName() const {
376  return kViewClassName;
377}
378
379void TabbedPane::OnFocus() {
380  View::OnFocus();
381
382  View* selected_tab = GetSelectedTab();
383  if (selected_tab) {
384    selected_tab->NotifyAccessibilityEvent(
385        ui::AccessibilityTypes::EVENT_FOCUS, true);
386  }
387}
388
389void TabbedPane::GetAccessibleState(ui::AccessibleViewState* state) {
390  state->role = ui::AccessibilityTypes::ROLE_PAGETABLIST;
391}
392
393}  // namespace views
394