Home | History | Annotate | Download | only in tabbed_pane
      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 
     17 namespace {
     18 
     19 // TODO(markusheintz|msw): Use NativeTheme colors.
     20 const SkColor kTabTitleColor_Inactive = SkColorSetRGB(0x64, 0x64, 0x64);
     21 const SkColor kTabTitleColor_Active = SK_ColorBLACK;
     22 const SkColor kTabTitleColor_Hovered = SK_ColorBLACK;
     23 const SkColor kTabBorderColor = SkColorSetRGB(0xC8, 0xC8, 0xC8);
     24 const SkScalar kTabBorderThickness = 1.0f;
     25 
     26 }  // namespace
     27 
     28 namespace views {
     29 
     30 // static
     31 const char TabbedPane::kViewClassName[] = "TabbedPane";
     32 
     33 // The tab view shown in the tab strip.
     34 class 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.
     75 class 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 
     91 Tab::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 
    103 Tab::~Tab() {}
    104 
    105 void Tab::SetSelected(bool selected) {
    106   contents_->SetVisible(selected);
    107   SetState(selected ? TAB_ACTIVE : TAB_INACTIVE);
    108 }
    109 
    110 bool Tab::OnMousePressed(const ui::MouseEvent& event) {
    111   SetState(TAB_PRESSED);
    112   return true;
    113 }
    114 
    115 void 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 
    121 void Tab::OnMouseCaptureLost() {
    122   SetState(TAB_INACTIVE);
    123 }
    124 
    125 void Tab::OnMouseEntered(const ui::MouseEvent& event) {
    126   SetState(selected() ? TAB_ACTIVE : TAB_HOVERED);
    127 }
    128 
    129 void Tab::OnMouseExited(const ui::MouseEvent& event) {
    130   SetState(selected() ? TAB_ACTIVE : TAB_INACTIVE);
    131 }
    132 
    133 void 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 
    151 gfx::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 
    160 void 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 
    167 void 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 
    192 TabStrip::TabStrip(TabbedPane* tabbed_pane) : tabbed_pane_(tabbed_pane) {}
    193 
    194 TabStrip::~TabStrip() {}
    195 
    196 gfx::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 
    206 void 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 
    216 void 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 
    251 TabbedPane::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 
    268 TabbedPane::~TabbedPane() {}
    269 
    270 int TabbedPane::GetTabCount() {
    271   DCHECK_EQ(tab_strip_->child_count(), contents_->child_count());
    272   return contents_->child_count();
    273 }
    274 
    275 View* TabbedPane::GetSelectedTab() {
    276   return selected_tab_index() < 0 ?
    277       NULL : GetTabAt(selected_tab_index())->contents();
    278 }
    279 
    280 void TabbedPane::AddTab(const string16& title, View* contents) {
    281   AddTabAtIndex(tab_strip_->child_count(), title, contents);
    282 }
    283 
    284 void 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 
    298 void 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 
    323 void TabbedPane::SelectTab(Tab* tab) {
    324   const int index = tab_strip_->GetIndexOf(tab);
    325   if (index >= 0)
    326     SelectTabAt(index);
    327 }
    328 
    329 gfx::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 
    337 Tab* TabbedPane::GetTabAt(int index) {
    338   return static_cast<Tab*>(tab_strip_->child_at(index));
    339 }
    340 
    341 void 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 
    350 void 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 
    360 bool 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 
    375 const char* TabbedPane::GetClassName() const {
    376   return kViewClassName;
    377 }
    378 
    379 void 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 
    389 void TabbedPane::GetAccessibleState(ui::AccessibleViewState* state) {
    390   state->role = ui::AccessibilityTypes::ROLE_PAGETABLIST;
    391 }
    392 
    393 }  // namespace views
    394