Home | History | Annotate | Download | only in infobars
      1 // Copyright (c) 2011 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 "chrome/browser/ui/views/infobars/infobar_view.h"
      6 
      7 #include "base/message_loop.h"
      8 #include "base/utf_string_conversions.h"
      9 #include "chrome/browser/tab_contents/infobar_delegate.h"
     10 #include "chrome/browser/ui/views/infobars/infobar_background.h"
     11 #include "chrome/browser/ui/views/infobars/infobar_button_border.h"
     12 #include "grit/generated_resources.h"
     13 #include "grit/theme_resources.h"
     14 #include "third_party/skia/include/effects/SkGradientShader.h"
     15 #include "ui/base/accessibility/accessible_view_state.h"
     16 #include "ui/base/animation/slide_animation.h"
     17 #include "ui/base/l10n/l10n_util.h"
     18 #include "ui/base/resource/resource_bundle.h"
     19 #include "ui/gfx/canvas_skia_paint.h"
     20 #include "views/controls/button/image_button.h"
     21 #include "views/controls/button/menu_button.h"
     22 #include "views/controls/button/text_button.h"
     23 #include "views/controls/image_view.h"
     24 #include "views/controls/label.h"
     25 #include "views/controls/link.h"
     26 #include "views/focus/external_focus_tracker.h"
     27 #include "views/widget/widget.h"
     28 #include "views/window/non_client_view.h"
     29 
     30 #if defined(OS_WIN)
     31 #include <shellapi.h>
     32 
     33 #include "base/win/win_util.h"
     34 #include "base/win/windows_version.h"
     35 #include "ui/base/win/hwnd_util.h"
     36 #include "ui/gfx/icon_util.h"
     37 #endif
     38 
     39 // static
     40 const int InfoBar::kSeparatorLineHeight =
     41     views::NonClientFrameView::kClientEdgeThickness;
     42 const int InfoBar::kDefaultArrowTargetHeight = 9;
     43 const int InfoBar::kMaximumArrowTargetHeight = 24;
     44 const int InfoBar::kDefaultArrowTargetHalfWidth = kDefaultArrowTargetHeight;
     45 const int InfoBar::kMaximumArrowTargetHalfWidth = 14;
     46 const int InfoBar::kDefaultBarTargetHeight = 36;
     47 
     48 const int InfoBarView::kButtonButtonSpacing = 10;
     49 const int InfoBarView::kEndOfLabelSpacing = 16;
     50 const int InfoBarView::kHorizontalPadding = 6;
     51 
     52 InfoBarView::InfoBarView(InfoBarDelegate* delegate)
     53     : InfoBar(delegate),
     54       icon_(NULL),
     55       close_button_(NULL),
     56       ALLOW_THIS_IN_INITIALIZER_LIST(delete_factory_(this)),
     57       fill_path_(new SkPath),
     58       stroke_path_(new SkPath) {
     59   set_parent_owned(false);  // InfoBar deletes itself at the appropriate time.
     60   set_background(new InfoBarBackground(delegate->GetInfoBarType()));
     61 }
     62 
     63 InfoBarView::~InfoBarView() {
     64 }
     65 
     66 // static
     67 views::Label* InfoBarView::CreateLabel(const string16& text) {
     68   views::Label* label = new views::Label(UTF16ToWideHack(text),
     69       ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::MediumFont));
     70   label->SetColor(SK_ColorBLACK);
     71   label->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
     72   return label;
     73 }
     74 
     75 // static
     76 views::Link* InfoBarView::CreateLink(const string16& text,
     77                                      views::LinkController* controller,
     78                                      const SkColor& background_color) {
     79   views::Link* link = new views::Link;
     80   link->SetText(UTF16ToWideHack(text));
     81   link->SetFont(
     82       ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::MediumFont));
     83   link->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
     84   link->SetController(controller);
     85   link->MakeReadableOverBackgroundColor(background_color);
     86   return link;
     87 }
     88 
     89 // static
     90 views::MenuButton* InfoBarView::CreateMenuButton(
     91     const string16& text,
     92     bool normal_has_border,
     93     views::ViewMenuDelegate* menu_delegate) {
     94   views::MenuButton* menu_button =
     95       new views::MenuButton(NULL, UTF16ToWideHack(text), menu_delegate, true);
     96   menu_button->set_border(new InfoBarButtonBorder);
     97   ResourceBundle& rb = ResourceBundle::GetSharedInstance();
     98   menu_button->set_menu_marker(
     99       rb.GetBitmapNamed(IDR_INFOBARBUTTON_MENU_DROPARROW));
    100   if (normal_has_border) {
    101     menu_button->SetNormalHasBorder(true);
    102     menu_button->SetAnimationDuration(0);
    103   }
    104   menu_button->SetEnabledColor(SK_ColorBLACK);
    105   menu_button->SetHighlightColor(SK_ColorBLACK);
    106   menu_button->SetHoverColor(SK_ColorBLACK);
    107   menu_button->SetFont(rb.GetFont(ResourceBundle::MediumFont));
    108   return menu_button;
    109 }
    110 
    111 // static
    112 views::TextButton* InfoBarView::CreateTextButton(
    113     views::ButtonListener* listener,
    114     const string16& text,
    115     bool needs_elevation) {
    116   views::TextButton* text_button =
    117       new views::TextButton(listener, UTF16ToWideHack(text));
    118   text_button->set_border(new InfoBarButtonBorder);
    119   text_button->SetNormalHasBorder(true);
    120   text_button->SetAnimationDuration(0);
    121   text_button->SetEnabledColor(SK_ColorBLACK);
    122   text_button->SetHighlightColor(SK_ColorBLACK);
    123   text_button->SetHoverColor(SK_ColorBLACK);
    124   ResourceBundle& rb = ResourceBundle::GetSharedInstance();
    125   text_button->SetFont(rb.GetFont(ResourceBundle::MediumFont));
    126 #if defined(OS_WIN)
    127   if (needs_elevation &&
    128       (base::win::GetVersion() >= base::win::VERSION_VISTA) &&
    129       base::win::UserAccountControlIsEnabled()) {
    130     SHSTOCKICONINFO icon_info = { sizeof SHSTOCKICONINFO };
    131     // Even with the runtime guard above, we have to use GetProcAddress() here,
    132     // because otherwise the loader will try to resolve the function address on
    133     // startup, which will break on XP.
    134     typedef HRESULT (STDAPICALLTYPE *GetStockIconInfo)(SHSTOCKICONID, UINT,
    135                                                        SHSTOCKICONINFO*);
    136     GetStockIconInfo func = reinterpret_cast<GetStockIconInfo>(
    137         GetProcAddress(GetModuleHandle(L"shell32.dll"), "SHGetStockIconInfo"));
    138     (*func)(SIID_SHIELD, SHGSI_ICON | SHGSI_SMALLICON, &icon_info);
    139     text_button->SetIcon(*IconUtil::CreateSkBitmapFromHICON(icon_info.hIcon,
    140         gfx::Size(GetSystemMetrics(SM_CXSMICON),
    141                   GetSystemMetrics(SM_CYSMICON))));
    142   }
    143 #endif
    144   return text_button;
    145 }
    146 
    147 void InfoBarView::Layout() {
    148   // Calculate the fill and stroke paths.  We do this here, rather than in
    149   // PlatformSpecificRecalculateHeight(), because this is also reached when our
    150   // width is changed, which affects both paths.
    151   stroke_path_->rewind();
    152   fill_path_->rewind();
    153   const InfoBarContainer::Delegate* delegate = container_delegate();
    154   if (delegate) {
    155     static_cast<InfoBarBackground*>(background())->set_separator_color(
    156         delegate->GetInfoBarSeparatorColor());
    157     int arrow_x;
    158     SkScalar arrow_fill_height =
    159         SkIntToScalar(std::max(arrow_height() - kSeparatorLineHeight, 0));
    160     SkScalar arrow_fill_half_width = SkIntToScalar(arrow_half_width());
    161     SkScalar separator_height = SkIntToScalar(kSeparatorLineHeight);
    162     if (delegate->DrawInfoBarArrows(&arrow_x) && arrow_fill_height) {
    163       // Skia pixel centers are at the half-values, so the arrow is horizontally
    164       // centered at |arrow_x| + 0.5.  Vertically, the stroke path is the center
    165       // of the separator, while the fill path is a closed path that extends up
    166       // through the entire height of the separator and down to the bottom of
    167       // the arrow where it joins the bar.
    168       stroke_path_->moveTo(
    169           SkIntToScalar(arrow_x) + SK_ScalarHalf - arrow_fill_half_width,
    170           SkIntToScalar(arrow_height()) - (separator_height * SK_ScalarHalf));
    171       stroke_path_->rLineTo(arrow_fill_half_width, -arrow_fill_height);
    172       stroke_path_->rLineTo(arrow_fill_half_width, arrow_fill_height);
    173 
    174       *fill_path_ = *stroke_path_;
    175       // Move the top of the fill path up to the top of the separator and then
    176       // extend it down all the way through.
    177       fill_path_->offset(0, -separator_height * SK_ScalarHalf);
    178       // This 0.01 hack prevents the fill from filling more pixels on the right
    179       // edge of the arrow than on the left.
    180       const SkScalar epsilon = 0.01f;
    181       fill_path_->rLineTo(-epsilon, 0);
    182       fill_path_->rLineTo(0, separator_height);
    183       fill_path_->rLineTo(epsilon - (arrow_fill_half_width * 2), 0);
    184       fill_path_->close();
    185     }
    186   }
    187   if (bar_height()) {
    188     fill_path_->addRect(0.0, SkIntToScalar(arrow_height()),
    189         SkIntToScalar(width()), SkIntToScalar(height() - kSeparatorLineHeight));
    190   }
    191 
    192   int start_x = kHorizontalPadding;
    193   if (icon_ != NULL) {
    194     gfx::Size icon_size = icon_->GetPreferredSize();
    195     icon_->SetBounds(start_x, OffsetY(icon_size), icon_size.width(),
    196                      icon_size.height());
    197   }
    198 
    199   gfx::Size button_size = close_button_->GetPreferredSize();
    200   close_button_->SetBounds(std::max(start_x + ContentMinimumWidth(),
    201       width() - kHorizontalPadding - button_size.width()), OffsetY(button_size),
    202       button_size.width(), button_size.height());
    203 }
    204 
    205 void InfoBarView::ViewHierarchyChanged(bool is_add, View* parent, View* child) {
    206   View::ViewHierarchyChanged(is_add, parent, child);
    207 
    208   if (child == this) {
    209     if (is_add) {
    210 #if defined(OS_WIN)
    211       // When we're added to a view hierarchy within a widget, we create an
    212       // external focus tracker to track what was focused in case we obtain
    213       // focus so that we can restore focus when we're removed.
    214       views::Widget* widget = GetWidget();
    215       if (widget) {
    216         focus_tracker_.reset(
    217             new views::ExternalFocusTracker(this, GetFocusManager()));
    218       }
    219 #endif
    220       if (GetFocusManager())
    221         GetFocusManager()->AddFocusChangeListener(this);
    222       if (GetWidget()) {
    223         GetWidget()->NotifyAccessibilityEvent(
    224             this, ui::AccessibilityTypes::EVENT_ALERT, true);
    225       }
    226 
    227       if (close_button_ == NULL) {
    228         SkBitmap* image = delegate()->GetIcon();
    229         if (image) {
    230           icon_ = new views::ImageView;
    231           icon_->SetImage(image);
    232           AddChildView(icon_);
    233         }
    234 
    235         close_button_ = new views::ImageButton(this);
    236         ResourceBundle& rb = ResourceBundle::GetSharedInstance();
    237         close_button_->SetImage(views::CustomButton::BS_NORMAL,
    238                                 rb.GetBitmapNamed(IDR_CLOSE_BAR));
    239         close_button_->SetImage(views::CustomButton::BS_HOT,
    240                                 rb.GetBitmapNamed(IDR_CLOSE_BAR_H));
    241         close_button_->SetImage(views::CustomButton::BS_PUSHED,
    242                                 rb.GetBitmapNamed(IDR_CLOSE_BAR_P));
    243         close_button_->SetAccessibleName(
    244             l10n_util::GetStringUTF16(IDS_ACCNAME_CLOSE));
    245         close_button_->SetFocusable(true);
    246         AddChildView(close_button_);
    247       }
    248     } else {
    249       DestroyFocusTracker(false);
    250       animation()->Stop();
    251       // Finally, clean ourselves up when we're removed from the view hierarchy
    252       // since no-one refers to us now.
    253       MessageLoop::current()->PostTask(FROM_HERE,
    254           delete_factory_.NewRunnableMethod(&InfoBarView::DeleteSelf));
    255       if (GetFocusManager())
    256         GetFocusManager()->RemoveFocusChangeListener(this);
    257     }
    258   }
    259 
    260   // For accessibility, ensure the close button is the last child view.
    261   if ((close_button_ != NULL) && (parent == this) && (child != close_button_) &&
    262       (close_button_->parent() == this) &&
    263       (GetChildViewAt(child_count() - 1) != close_button_)) {
    264     RemoveChildView(close_button_);
    265     AddChildView(close_button_);
    266   }
    267 }
    268 
    269 void InfoBarView::PaintChildren(gfx::Canvas* canvas) {
    270   canvas->Save();
    271 
    272   // TODO(scr): This really should be the |fill_path_|, but the clipPath seems
    273   // broken on non-Windows platforms (crbug.com/75154). For now, just clip to
    274   // the bar bounds.
    275   //
    276   // gfx::CanvasSkia* canvas_skia = canvas->AsCanvasSkia();
    277   // canvas_skia->clipPath(*fill_path_);
    278   DCHECK_EQ(total_height(), height())
    279       << "Infobar piecewise heights do not match overall height";
    280   canvas->ClipRectInt(0, arrow_height(), width(), bar_height());
    281   views::View::PaintChildren(canvas);
    282   canvas->Restore();
    283 }
    284 
    285 void InfoBarView::ButtonPressed(views::Button* sender,
    286                                 const views::Event& event) {
    287   if (sender == close_button_) {
    288     if (delegate())
    289       delegate()->InfoBarDismissed();
    290     RemoveInfoBar();
    291   }
    292 }
    293 
    294 int InfoBarView::ContentMinimumWidth() const {
    295   return 0;
    296 }
    297 
    298 int InfoBarView::StartX() const {
    299   // Ensure we don't return a value greater than EndX(), so children can safely
    300   // set something's width to "EndX() - StartX()" without risking that being
    301   // negative.
    302   return std::min(EndX(),
    303       ((icon_ != NULL) ? icon_->bounds().right() : 0) + kHorizontalPadding);
    304 }
    305 
    306 int InfoBarView::EndX() const {
    307   const int kCloseButtonSpacing = 12;
    308   return close_button_->x() - kCloseButtonSpacing;
    309 }
    310 
    311 const InfoBarContainer::Delegate* InfoBarView::container_delegate() const {
    312   const InfoBarContainer* infobar_container = container();
    313   return infobar_container ? infobar_container->delegate() : NULL;
    314 }
    315 
    316 void InfoBarView::PlatformSpecificHide(bool animate) {
    317   if (!animate)
    318     return;
    319 
    320   bool restore_focus = true;
    321 #if defined(OS_WIN)
    322   // Do not restore focus (and active state with it) on Windows if some other
    323   // top-level window became active.
    324   if (GetWidget() &&
    325       !ui::DoesWindowBelongToActiveWindow(GetWidget()->GetNativeView()))
    326     restore_focus = false;
    327 #endif  // defined(OS_WIN)
    328   DestroyFocusTracker(restore_focus);
    329 }
    330 
    331 void InfoBarView::PlatformSpecificOnHeightsRecalculated() {
    332   // Ensure that notifying our container of our size change will result in a
    333   // re-layout.
    334   InvalidateLayout();
    335 }
    336 
    337 void InfoBarView::GetAccessibleState(ui::AccessibleViewState* state) {
    338   if (delegate()) {
    339     state->name = l10n_util::GetStringUTF16(
    340         (delegate()->GetInfoBarType() == InfoBarDelegate::WARNING_TYPE) ?
    341         IDS_ACCNAME_INFOBAR_WARNING : IDS_ACCNAME_INFOBAR_PAGE_ACTION);
    342   }
    343   state->role = ui::AccessibilityTypes::ROLE_ALERT;
    344 }
    345 
    346 gfx::Size InfoBarView::GetPreferredSize() {
    347   return gfx::Size(0, total_height());
    348 }
    349 
    350 void InfoBarView::FocusWillChange(View* focused_before, View* focused_now) {
    351   // This will trigger some screen readers to read the entire contents of this
    352   // infobar.
    353   if (focused_before && focused_now && !this->Contains(focused_before) &&
    354       this->Contains(focused_now) && GetWidget()) {
    355     GetWidget()->NotifyAccessibilityEvent(
    356         this, ui::AccessibilityTypes::EVENT_ALERT, true);
    357   }
    358 }
    359 
    360 void InfoBarView::DestroyFocusTracker(bool restore_focus) {
    361   if (focus_tracker_ != NULL) {
    362     if (restore_focus)
    363       focus_tracker_->FocusLastFocusedExternalView();
    364     focus_tracker_->SetFocusManager(NULL);
    365     focus_tracker_.reset();
    366   }
    367 }
    368 
    369 void InfoBarView::DeleteSelf() {
    370   delete this;
    371 }
    372