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 Tab(TabbedPane
* tabbed_pane
, const base::string16
& title
, View
* contents
);
40 View
* contents() const { return contents_
; }
42 bool selected() const { return contents_
->visible(); }
43 void SetSelected(bool selected
);
45 // Overridden from View:
46 virtual bool OnMousePressed(const ui::MouseEvent
& event
) OVERRIDE
;
47 virtual void OnMouseEntered(const ui::MouseEvent
& event
) OVERRIDE
;
48 virtual void OnMouseExited(const ui::MouseEvent
& event
) OVERRIDE
;
49 virtual void OnGestureEvent(ui::GestureEvent
* event
) OVERRIDE
;
50 virtual gfx::Size
GetPreferredSize() const OVERRIDE
;
51 virtual void Layout() OVERRIDE
;
60 void SetState(TabState tab_state
);
62 TabbedPane
* tabbed_pane_
;
64 gfx::Size preferred_title_size_
;
66 // The content view associated with this tab.
69 DISALLOW_COPY_AND_ASSIGN(Tab
);
72 // The tab strip shown above the tab contents.
73 class TabStrip
: public View
{
75 explicit TabStrip(TabbedPane
* tabbed_pane
);
78 // Overridden from View:
79 virtual gfx::Size
GetPreferredSize() const OVERRIDE
;
80 virtual void Layout() OVERRIDE
;
81 virtual void OnPaint(gfx::Canvas
* canvas
) OVERRIDE
;
84 TabbedPane
* tabbed_pane_
;
86 DISALLOW_COPY_AND_ASSIGN(TabStrip
);
89 Tab::Tab(TabbedPane
* tabbed_pane
, const base::string16
& title
, View
* contents
)
90 : tabbed_pane_(tabbed_pane
),
91 title_(new Label(title
,
92 ui::ResourceBundle::GetSharedInstance().GetFontList(
93 ui::ResourceBundle::BoldFont
))),
94 tab_state_(TAB_ACTIVE
),
96 // Calculate this now while the font list is guaranteed to be bold.
97 preferred_title_size_
= title_
->GetPreferredSize();
99 SetState(TAB_INACTIVE
);
100 AddChildView(title_
);
105 void Tab::SetSelected(bool selected
) {
106 contents_
->SetVisible(selected
);
107 SetState(selected
? TAB_ACTIVE
: TAB_INACTIVE
);
110 bool Tab::OnMousePressed(const ui::MouseEvent
& event
) {
111 if (event
.IsOnlyLeftMouseButton() &&
112 GetLocalBounds().Contains(event
.location()))
113 tabbed_pane_
->SelectTab(this);
117 void Tab::OnMouseEntered(const ui::MouseEvent
& event
) {
118 SetState(selected() ? TAB_ACTIVE
: TAB_HOVERED
);
121 void Tab::OnMouseExited(const ui::MouseEvent
& event
) {
122 SetState(selected() ? TAB_ACTIVE
: TAB_INACTIVE
);
125 void Tab::OnGestureEvent(ui::GestureEvent
* event
) {
126 switch (event
->type()) {
127 case ui::ET_GESTURE_TAP_DOWN
:
129 case ui::ET_GESTURE_TAP
:
130 // SelectTab also sets the right tab color.
131 tabbed_pane_
->SelectTab(this);
133 case ui::ET_GESTURE_TAP_CANCEL
:
134 SetState(selected() ? TAB_ACTIVE
: TAB_INACTIVE
);
142 gfx::Size
Tab::GetPreferredSize() const {
143 gfx::Size
size(preferred_title_size_
);
145 const int kTabMinWidth
= 54;
146 if (size
.width() < kTabMinWidth
)
147 size
.set_width(kTabMinWidth
);
152 gfx::Rect bounds
= GetLocalBounds();
153 bounds
.Inset(0, 1, 0, 0);
154 bounds
.ClampToCenteredSize(preferred_title_size_
);
155 title_
->SetBoundsRect(bounds
);
158 void Tab::SetState(TabState tab_state
) {
159 if (tab_state
== tab_state_
)
161 tab_state_
= tab_state
;
163 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
166 title_
->SetEnabledColor(kTabTitleColor_Inactive
);
167 title_
->SetFontList(rb
.GetFontList(ui::ResourceBundle::BaseFont
));
170 title_
->SetEnabledColor(kTabTitleColor_Active
);
171 title_
->SetFontList(rb
.GetFontList(ui::ResourceBundle::BoldFont
));
174 title_
->SetEnabledColor(kTabTitleColor_Hovered
);
175 title_
->SetFontList(rb
.GetFontList(ui::ResourceBundle::BaseFont
));
181 TabStrip::TabStrip(TabbedPane
* tabbed_pane
) : tabbed_pane_(tabbed_pane
) {}
183 TabStrip::~TabStrip() {}
185 gfx::Size
TabStrip::GetPreferredSize() const {
187 for (int i
= 0; i
< child_count(); ++i
) {
188 const gfx::Size child_size
= child_at(i
)->GetPreferredSize();
189 size
.SetSize(size
.width() + child_size
.width(),
190 std::max(size
.height(), child_size
.height()));
195 void TabStrip::Layout() {
196 const int kTabOffset
= 9;
197 int x
= kTabOffset
; // Layout tabs with an offset to the tabstrip border.
198 for (int i
= 0; i
< child_count(); ++i
) {
199 gfx::Size ps
= child_at(i
)->GetPreferredSize();
200 child_at(i
)->SetBounds(x
, 0, ps
.width(), ps
.height());
201 x
= child_at(i
)->bounds().right();
205 void TabStrip::OnPaint(gfx::Canvas
* canvas
) {
206 OnPaintBackground(canvas
);
208 // Draw the TabStrip border.
210 paint
.setColor(kTabBorderColor
);
211 paint
.setStrokeWidth(kTabBorderThickness
);
212 SkScalar line_y
= SkIntToScalar(height()) - (kTabBorderThickness
/ 2);
213 SkScalar line_end
= SkIntToScalar(width());
214 int selected_tab_index
= tabbed_pane_
->selected_tab_index();
215 if (selected_tab_index
>= 0) {
216 Tab
* selected_tab
= tabbed_pane_
->GetTabAt(selected_tab_index
);
218 SkScalar tab_height
=
219 SkIntToScalar(selected_tab
->height()) - kTabBorderThickness
;
221 SkIntToScalar(selected_tab
->width()) - kTabBorderThickness
;
222 SkScalar tab_start
= SkIntToScalar(selected_tab
->GetMirroredX());
223 path
.moveTo(0, line_y
);
224 path
.rLineTo(tab_start
, 0);
225 path
.rLineTo(0, -tab_height
);
226 path
.rLineTo(tab_width
, 0);
227 path
.rLineTo(0, tab_height
);
228 path
.lineTo(line_end
, line_y
);
231 paint
.setStyle(SkPaint::kStroke_Style
);
232 paint
.setColor(kTabBorderColor
);
233 paint
.setStrokeWidth(kTabBorderThickness
);
234 canvas
->DrawPath(path
, paint
);
236 canvas
->sk_canvas()->drawLine(0, line_y
, line_end
, line_y
, paint
);
240 TabbedPane::TabbedPane()
242 tab_strip_(new TabStrip(this)),
243 contents_(new View()),
244 selected_tab_index_(-1) {
246 AddChildView(tab_strip_
);
247 AddChildView(contents_
);
250 TabbedPane::~TabbedPane() {}
252 int TabbedPane::GetTabCount() {
253 DCHECK_EQ(tab_strip_
->child_count(), contents_
->child_count());
254 return contents_
->child_count();
257 View
* TabbedPane::GetSelectedTab() {
258 return selected_tab_index() < 0 ?
259 NULL
: GetTabAt(selected_tab_index())->contents();
262 void TabbedPane::AddTab(const base::string16
& title
, View
* contents
) {
263 AddTabAtIndex(tab_strip_
->child_count(), title
, contents
);
266 void TabbedPane::AddTabAtIndex(int index
,
267 const base::string16
& title
,
269 DCHECK(index
>= 0 && index
<= GetTabCount());
270 contents
->SetVisible(false);
272 tab_strip_
->AddChildViewAt(new Tab(this, title
, contents
), index
);
273 contents_
->AddChildViewAt(contents
, index
);
274 if (selected_tab_index() < 0)
277 PreferredSizeChanged();
280 void TabbedPane::SelectTabAt(int index
) {
281 DCHECK(index
>= 0 && index
< GetTabCount());
282 if (index
== selected_tab_index())
285 if (selected_tab_index() >= 0)
286 GetTabAt(selected_tab_index())->SetSelected(false);
288 selected_tab_index_
= index
;
289 Tab
* tab
= GetTabAt(index
);
290 tab
->SetSelected(true);
291 tab_strip_
->SchedulePaint();
293 FocusManager
* focus_manager
= tab
->contents()->GetFocusManager();
295 const View
* focused_view
= focus_manager
->GetFocusedView();
296 if (focused_view
&& contents_
->Contains(focused_view
) &&
297 !tab
->contents()->Contains(focused_view
))
298 focus_manager
->SetFocusedView(tab
->contents());
302 listener()->TabSelectedAt(index
);
305 void TabbedPane::SelectTab(Tab
* tab
) {
306 const int index
= tab_strip_
->GetIndexOf(tab
);
311 gfx::Size
TabbedPane::GetPreferredSize() const {
313 for (int i
= 0; i
< contents_
->child_count(); ++i
)
314 size
.SetToMax(contents_
->child_at(i
)->GetPreferredSize());
315 size
.Enlarge(0, tab_strip_
->GetPreferredSize().height());
319 Tab
* TabbedPane::GetTabAt(int index
) {
320 return static_cast<Tab
*>(tab_strip_
->child_at(index
));
323 void TabbedPane::Layout() {
324 const gfx::Size size
= tab_strip_
->GetPreferredSize();
325 tab_strip_
->SetBounds(0, 0, width(), size
.height());
326 contents_
->SetBounds(0, tab_strip_
->bounds().bottom(), width(),
327 std::max(0, height() - size
.height()));
328 for (int i
= 0; i
< contents_
->child_count(); ++i
)
329 contents_
->child_at(i
)->SetSize(contents_
->size());
332 void TabbedPane::ViewHierarchyChanged(
333 const ViewHierarchyChangedDetails
& details
) {
334 if (details
.is_add
) {
335 // Support navigating tabs by Ctrl+Tab and Ctrl+Shift+Tab.
336 AddAccelerator(ui::Accelerator(ui::VKEY_TAB
,
337 ui::EF_CONTROL_DOWN
| ui::EF_SHIFT_DOWN
));
338 AddAccelerator(ui::Accelerator(ui::VKEY_TAB
, ui::EF_CONTROL_DOWN
));
342 bool TabbedPane::AcceleratorPressed(const ui::Accelerator
& accelerator
) {
343 // Handle Ctrl+Tab and Ctrl+Shift+Tab navigation of pages.
344 DCHECK(accelerator
.key_code() == ui::VKEY_TAB
&& accelerator
.IsCtrlDown());
345 const int tab_count
= GetTabCount();
348 const int increment
= accelerator
.IsShiftDown() ? -1 : 1;
349 int next_tab_index
= (selected_tab_index() + increment
) % tab_count
;
351 if (next_tab_index
< 0)
352 next_tab_index
+= tab_count
;
353 SelectTabAt(next_tab_index
);
357 const char* TabbedPane::GetClassName() const {
358 return kViewClassName
;
361 void TabbedPane::OnFocus() {
364 View
* selected_tab
= GetSelectedTab();
366 selected_tab
->NotifyAccessibilityEvent(
367 ui::AX_EVENT_FOCUS
, true);
371 void TabbedPane::GetAccessibleState(ui::AXViewState
* state
) {
372 state
->role
= ui::AX_ROLE_TAB_LIST
;