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 "ui/accessibility/ax_view_state.h"
9 #include "ui/base/resource/resource_bundle.h"
10 #include "ui/events/keycodes/keyboard_codes.h"
11 #include "ui/gfx/canvas.h"
12 #include "ui/gfx/font_list.h"
13 #include "ui/views/controls/label.h"
14 #include "ui/views/controls/tabbed_pane/tabbed_pane_listener.h"
15 #include "ui/views/layout/layout_manager.h"
16 #include "ui/views/widget/widget.h"
20 // TODO(markusheintz|msw): Use NativeTheme colors.
21 const SkColor kTabTitleColor_Inactive
= SkColorSetRGB(0x64, 0x64, 0x64);
22 const SkColor kTabTitleColor_Active
= SK_ColorBLACK
;
23 const SkColor kTabTitleColor_Hovered
= SK_ColorBLACK
;
24 const SkColor kTabBorderColor
= SkColorSetRGB(0xC8, 0xC8, 0xC8);
25 const SkScalar kTabBorderThickness
= 1.0f
;
32 const char TabbedPane::kViewClassName
[] = "TabbedPane";
34 // The tab view shown in the tab strip.
35 class Tab
: public View
{
37 // Internal class name.
38 static const char kViewClassName
[];
40 Tab(TabbedPane
* tabbed_pane
, const base::string16
& title
, View
* contents
);
43 View
* contents() const { return contents_
; }
45 bool selected() const { return contents_
->visible(); }
46 void SetSelected(bool selected
);
48 // Overridden from View:
49 bool OnMousePressed(const ui::MouseEvent
& event
) override
;
50 void OnMouseEntered(const ui::MouseEvent
& event
) override
;
51 void OnMouseExited(const ui::MouseEvent
& event
) override
;
52 void OnGestureEvent(ui::GestureEvent
* event
) override
;
53 gfx::Size
GetPreferredSize() const override
;
54 void Layout() override
;
55 const char* GetClassName() const override
;
64 void SetState(TabState tab_state
);
66 TabbedPane
* tabbed_pane_
;
68 gfx::Size preferred_title_size_
;
70 // The content view associated with this tab.
73 DISALLOW_COPY_AND_ASSIGN(Tab
);
76 // The tab strip shown above the tab contents.
77 class TabStrip
: public View
{
79 // Internal class name.
80 static const char kViewClassName
[];
82 explicit TabStrip(TabbedPane
* tabbed_pane
);
85 // Overridden from View:
86 gfx::Size
GetPreferredSize() const override
;
87 void Layout() override
;
88 const char* GetClassName() const override
;
89 void OnPaint(gfx::Canvas
* canvas
) override
;
92 TabbedPane
* tabbed_pane_
;
94 DISALLOW_COPY_AND_ASSIGN(TabStrip
);
98 const char Tab::kViewClassName
[] = "Tab";
100 Tab::Tab(TabbedPane
* tabbed_pane
, const base::string16
& title
, View
* contents
)
101 : tabbed_pane_(tabbed_pane
),
102 title_(new Label(title
,
103 ui::ResourceBundle::GetSharedInstance().GetFontList(
104 ui::ResourceBundle::BoldFont
))),
105 tab_state_(TAB_ACTIVE
),
106 contents_(contents
) {
107 // Calculate this now while the font list is guaranteed to be bold.
108 preferred_title_size_
= title_
->GetPreferredSize();
110 SetState(TAB_INACTIVE
);
111 AddChildView(title_
);
116 void Tab::SetSelected(bool selected
) {
117 contents_
->SetVisible(selected
);
118 SetState(selected
? TAB_ACTIVE
: TAB_INACTIVE
);
121 bool Tab::OnMousePressed(const ui::MouseEvent
& event
) {
122 if (event
.IsOnlyLeftMouseButton() &&
123 GetLocalBounds().Contains(event
.location()))
124 tabbed_pane_
->SelectTab(this);
128 void Tab::OnMouseEntered(const ui::MouseEvent
& event
) {
129 SetState(selected() ? TAB_ACTIVE
: TAB_HOVERED
);
132 void Tab::OnMouseExited(const ui::MouseEvent
& event
) {
133 SetState(selected() ? TAB_ACTIVE
: TAB_INACTIVE
);
136 void Tab::OnGestureEvent(ui::GestureEvent
* event
) {
137 switch (event
->type()) {
138 case ui::ET_GESTURE_TAP_DOWN
:
140 case ui::ET_GESTURE_TAP
:
141 // SelectTab also sets the right tab color.
142 tabbed_pane_
->SelectTab(this);
144 case ui::ET_GESTURE_TAP_CANCEL
:
145 SetState(selected() ? TAB_ACTIVE
: TAB_INACTIVE
);
153 gfx::Size
Tab::GetPreferredSize() const {
154 gfx::Size
size(preferred_title_size_
);
156 const int kTabMinWidth
= 54;
157 if (size
.width() < kTabMinWidth
)
158 size
.set_width(kTabMinWidth
);
163 gfx::Rect bounds
= GetLocalBounds();
164 bounds
.Inset(0, 1, 0, 0);
165 bounds
.ClampToCenteredSize(preferred_title_size_
);
166 title_
->SetBoundsRect(bounds
);
169 const char* Tab::GetClassName() const {
170 return kViewClassName
;
173 void Tab::SetState(TabState tab_state
) {
174 if (tab_state
== tab_state_
)
176 tab_state_
= tab_state
;
178 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
181 title_
->SetEnabledColor(kTabTitleColor_Inactive
);
182 title_
->SetFontList(rb
.GetFontList(ui::ResourceBundle::BaseFont
));
185 title_
->SetEnabledColor(kTabTitleColor_Active
);
186 title_
->SetFontList(rb
.GetFontList(ui::ResourceBundle::BoldFont
));
189 title_
->SetEnabledColor(kTabTitleColor_Hovered
);
190 title_
->SetFontList(rb
.GetFontList(ui::ResourceBundle::BaseFont
));
197 const char TabStrip::kViewClassName
[] = "TabStrip";
199 TabStrip::TabStrip(TabbedPane
* tabbed_pane
) : tabbed_pane_(tabbed_pane
) {}
201 TabStrip::~TabStrip() {}
203 gfx::Size
TabStrip::GetPreferredSize() const {
205 for (int i
= 0; i
< child_count(); ++i
) {
206 const gfx::Size child_size
= child_at(i
)->GetPreferredSize();
207 size
.SetSize(size
.width() + child_size
.width(),
208 std::max(size
.height(), child_size
.height()));
213 void TabStrip::Layout() {
214 const int kTabOffset
= 9;
215 int x
= kTabOffset
; // Layout tabs with an offset to the tabstrip border.
216 for (int i
= 0; i
< child_count(); ++i
) {
217 gfx::Size ps
= child_at(i
)->GetPreferredSize();
218 child_at(i
)->SetBounds(x
, 0, ps
.width(), ps
.height());
219 x
= child_at(i
)->bounds().right();
223 const char* TabStrip::GetClassName() const {
224 return kViewClassName
;
227 void TabStrip::OnPaint(gfx::Canvas
* canvas
) {
228 OnPaintBackground(canvas
);
230 // Draw the TabStrip border.
232 paint
.setColor(kTabBorderColor
);
233 paint
.setStrokeWidth(kTabBorderThickness
);
234 SkScalar line_y
= SkIntToScalar(height()) - (kTabBorderThickness
/ 2);
235 SkScalar line_end
= SkIntToScalar(width());
236 int selected_tab_index
= tabbed_pane_
->selected_tab_index();
237 if (selected_tab_index
>= 0) {
238 Tab
* selected_tab
= tabbed_pane_
->GetTabAt(selected_tab_index
);
240 SkScalar tab_height
=
241 SkIntToScalar(selected_tab
->height()) - kTabBorderThickness
;
243 SkIntToScalar(selected_tab
->width()) - kTabBorderThickness
;
244 SkScalar tab_start
= SkIntToScalar(selected_tab
->GetMirroredX());
245 path
.moveTo(0, line_y
);
246 path
.rLineTo(tab_start
, 0);
247 path
.rLineTo(0, -tab_height
);
248 path
.rLineTo(tab_width
, 0);
249 path
.rLineTo(0, tab_height
);
250 path
.lineTo(line_end
, line_y
);
253 paint
.setStyle(SkPaint::kStroke_Style
);
254 paint
.setColor(kTabBorderColor
);
255 paint
.setStrokeWidth(kTabBorderThickness
);
256 canvas
->DrawPath(path
, paint
);
258 canvas
->sk_canvas()->drawLine(0, line_y
, line_end
, line_y
, paint
);
262 TabbedPane::TabbedPane()
264 tab_strip_(new TabStrip(this)),
265 contents_(new View()),
266 selected_tab_index_(-1) {
268 AddChildView(tab_strip_
);
269 AddChildView(contents_
);
272 TabbedPane::~TabbedPane() {}
274 int TabbedPane::GetTabCount() {
275 DCHECK_EQ(tab_strip_
->child_count(), contents_
->child_count());
276 return contents_
->child_count();
279 View
* TabbedPane::GetSelectedTab() {
280 return selected_tab_index() < 0 ?
281 NULL
: GetTabAt(selected_tab_index())->contents();
284 void TabbedPane::AddTab(const base::string16
& title
, View
* contents
) {
285 AddTabAtIndex(tab_strip_
->child_count(), title
, contents
);
288 void TabbedPane::AddTabAtIndex(int index
,
289 const base::string16
& title
,
291 DCHECK(index
>= 0 && index
<= GetTabCount());
292 contents
->SetVisible(false);
294 tab_strip_
->AddChildViewAt(new Tab(this, title
, contents
), index
);
295 contents_
->AddChildViewAt(contents
, index
);
296 if (selected_tab_index() < 0)
299 PreferredSizeChanged();
302 void TabbedPane::SelectTabAt(int index
) {
303 DCHECK(index
>= 0 && index
< GetTabCount());
304 if (index
== selected_tab_index())
307 if (selected_tab_index() >= 0)
308 GetTabAt(selected_tab_index())->SetSelected(false);
310 selected_tab_index_
= index
;
311 Tab
* tab
= GetTabAt(index
);
312 tab
->SetSelected(true);
313 tab_strip_
->SchedulePaint();
315 FocusManager
* focus_manager
= tab
->contents()->GetFocusManager();
317 const View
* focused_view
= focus_manager
->GetFocusedView();
318 if (focused_view
&& contents_
->Contains(focused_view
) &&
319 !tab
->contents()->Contains(focused_view
))
320 focus_manager
->SetFocusedView(tab
->contents());
324 listener()->TabSelectedAt(index
);
327 void TabbedPane::SelectTab(Tab
* tab
) {
328 const int index
= tab_strip_
->GetIndexOf(tab
);
333 gfx::Size
TabbedPane::GetPreferredSize() const {
335 for (int i
= 0; i
< contents_
->child_count(); ++i
)
336 size
.SetToMax(contents_
->child_at(i
)->GetPreferredSize());
337 size
.Enlarge(0, tab_strip_
->GetPreferredSize().height());
341 Tab
* TabbedPane::GetTabAt(int index
) {
342 return static_cast<Tab
*>(tab_strip_
->child_at(index
));
345 void TabbedPane::Layout() {
346 const gfx::Size size
= tab_strip_
->GetPreferredSize();
347 tab_strip_
->SetBounds(0, 0, width(), size
.height());
348 contents_
->SetBounds(0, tab_strip_
->bounds().bottom(), width(),
349 std::max(0, height() - size
.height()));
350 for (int i
= 0; i
< contents_
->child_count(); ++i
)
351 contents_
->child_at(i
)->SetSize(contents_
->size());
354 void TabbedPane::ViewHierarchyChanged(
355 const ViewHierarchyChangedDetails
& details
) {
356 if (details
.is_add
) {
357 // Support navigating tabs by Ctrl+Tab and Ctrl+Shift+Tab.
358 AddAccelerator(ui::Accelerator(ui::VKEY_TAB
,
359 ui::EF_CONTROL_DOWN
| ui::EF_SHIFT_DOWN
));
360 AddAccelerator(ui::Accelerator(ui::VKEY_TAB
, ui::EF_CONTROL_DOWN
));
364 bool TabbedPane::AcceleratorPressed(const ui::Accelerator
& accelerator
) {
365 // Handle Ctrl+Tab and Ctrl+Shift+Tab navigation of pages.
366 DCHECK(accelerator
.key_code() == ui::VKEY_TAB
&& accelerator
.IsCtrlDown());
367 const int tab_count
= GetTabCount();
370 const int increment
= accelerator
.IsShiftDown() ? -1 : 1;
371 int next_tab_index
= (selected_tab_index() + increment
) % tab_count
;
373 if (next_tab_index
< 0)
374 next_tab_index
+= tab_count
;
375 SelectTabAt(next_tab_index
);
379 const char* TabbedPane::GetClassName() const {
380 return kViewClassName
;
383 void TabbedPane::OnFocus() {
386 View
* selected_tab
= GetSelectedTab();
388 selected_tab
->NotifyAccessibilityEvent(
389 ui::AX_EVENT_FOCUS
, true);
393 void TabbedPane::GetAccessibleState(ui::AXViewState
* state
) {
394 state
->role
= ui::AX_ROLE_TAB_LIST
;