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"
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
;
34 const char TabbedPane::kViewClassName
[] = "TabbedPane";
36 // The tab view shown in the tab strip.
37 class Tab
: public View
{
39 // Internal class name.
40 static const char kViewClassName
[];
42 Tab(TabbedPane
* tabbed_pane
, const base::string16
& title
, View
* contents
);
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
;
66 void SetState(TabState tab_state
);
68 TabbedPane
* tabbed_pane_
;
70 gfx::Size preferred_title_size_
;
72 // The content view associated with this tab.
75 DISALLOW_COPY_AND_ASSIGN(Tab
);
78 // The tab strip shown above the tab contents.
79 class TabStrip
: public View
{
81 // Internal class name.
82 static const char kViewClassName
[];
84 explicit TabStrip(TabbedPane
* tabbed_pane
);
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
;
94 TabbedPane
* tabbed_pane_
;
96 DISALLOW_COPY_AND_ASSIGN(TabStrip
);
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_
);
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);
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
:
142 case ui::ET_GESTURE_TAP
:
143 // SelectTab also sets the right tab color.
144 tabbed_pane_
->SelectTab(this);
146 case ui::ET_GESTURE_TAP_CANCEL
:
147 SetState(selected() ? TAB_ACTIVE
: TAB_INACTIVE
);
155 gfx::Size
Tab::GetPreferredSize() const {
156 gfx::Size
size(preferred_title_size_
);
158 const int kTabMinWidth
= 54;
159 if (size
.width() < kTabMinWidth
)
160 size
.set_width(kTabMinWidth
);
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_
)
178 tab_state_
= tab_state
;
180 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
183 title_
->SetEnabledColor(kTabTitleColor_Inactive
);
184 title_
->SetFontList(rb
.GetFontList(ui::ResourceBundle::BaseFont
));
187 title_
->SetEnabledColor(kTabTitleColor_Active
);
188 title_
->SetFontList(rb
.GetFontList(ui::ResourceBundle::BoldFont
));
191 title_
->SetEnabledColor(kTabTitleColor_Hovered
);
192 title_
->SetFontList(rb
.GetFontList(ui::ResourceBundle::BaseFont
));
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 {
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()));
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.
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
);
242 SkScalar tab_height
=
243 SkIntToScalar(selected_tab
->height()) - kTabBorderThickness
;
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
);
255 paint
.setStyle(SkPaint::kStroke_Style
);
256 paint
.setColor(kTabBorderColor
);
257 paint
.setStrokeWidth(kTabBorderThickness
);
258 canvas
->DrawPath(path
, paint
);
260 canvas
->sk_canvas()->drawLine(0, line_y
, line_end
, line_y
, paint
);
264 TabbedPane::TabbedPane()
266 tab_strip_(new TabStrip(this)),
267 contents_(new View()),
268 selected_tab_index_(-1) {
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
,
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)
301 PreferredSizeChanged();
304 void TabbedPane::SelectTabAt(int index
) {
305 DCHECK(index
>= 0 && index
< GetTabCount());
306 if (index
== selected_tab_index())
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();
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());
326 listener()->TabSelectedAt(index
);
329 void TabbedPane::SelectTab(Tab
* tab
) {
330 const int index
= tab_strip_
->GetIndexOf(tab
);
335 gfx::Size
TabbedPane::GetPreferredSize() const {
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());
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();
372 const int increment
= accelerator
.IsShiftDown() ? -1 : 1;
373 int next_tab_index
= (selected_tab_index() + increment
) % tab_count
;
375 if (next_tab_index
< 0)
376 next_tab_index
+= tab_count
;
377 SelectTabAt(next_tab_index
);
381 const char* TabbedPane::GetClassName() const {
382 return kViewClassName
;
385 void TabbedPane::OnFocus() {
388 View
* selected_tab
= GetSelectedTab();
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
;