Home | History | Annotate | Download | only in toolbar
      1 // Copyright 2013 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/toolbar/browser_action_view.h"
      6 
      7 #include <string>
      8 
      9 #include "base/strings/utf_string_conversions.h"
     10 #include "chrome/browser/chrome_notification_types.h"
     11 #include "chrome/browser/extensions/extension_action.h"
     12 #include "chrome/browser/profiles/profile.h"
     13 #include "chrome/browser/themes/theme_service.h"
     14 #include "chrome/browser/themes/theme_service_factory.h"
     15 #include "chrome/browser/ui/browser.h"
     16 #include "chrome/browser/ui/view_ids.h"
     17 #include "chrome/browser/ui/views/frame/browser_view.h"
     18 #include "chrome/browser/ui/views/toolbar/browser_actions_container.h"
     19 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
     20 #include "chrome/grit/generated_resources.h"
     21 #include "extensions/common/extension.h"
     22 #include "extensions/common/manifest_constants.h"
     23 #include "grit/theme_resources.h"
     24 #include "ui/accessibility/ax_view_state.h"
     25 #include "ui/base/l10n/l10n_util.h"
     26 #include "ui/base/resource/resource_bundle.h"
     27 #include "ui/events/event.h"
     28 #include "ui/gfx/image/image_skia.h"
     29 #include "ui/gfx/image/image_skia_operations.h"
     30 #include "ui/gfx/image/image_skia_source.h"
     31 #include "ui/views/controls/button/label_button_border.h"
     32 
     33 using extensions::Extension;
     34 using views::LabelButtonBorder;
     35 
     36 namespace {
     37 
     38 // We have smaller insets than normal STYLE_TEXTBUTTON buttons so that we can
     39 // fit user supplied icons in without clipping them.
     40 const int kBorderInset = 4;
     41 
     42 }  // namespace
     43 
     44 ////////////////////////////////////////////////////////////////////////////////
     45 // BrowserActionView
     46 
     47 BrowserActionView::BrowserActionView(const Extension* extension,
     48                                      ExtensionAction* extension_action,
     49                                      Browser* browser,
     50                                      BrowserActionView::Delegate* delegate)
     51     : MenuButton(this, base::string16(), NULL, false),
     52       view_controller_(new ExtensionActionViewController(
     53           extension,
     54           browser,
     55           extension_action,
     56           this)),
     57       delegate_(delegate),
     58       called_registered_extension_command_(false),
     59       icon_observer_(NULL) {
     60   set_id(VIEW_ID_BROWSER_ACTION);
     61   SetHorizontalAlignment(gfx::ALIGN_CENTER);
     62   set_context_menu_controller(view_controller_.get());
     63   set_drag_controller(delegate_);
     64 
     65   content::NotificationSource notification_source =
     66       content::Source<Profile>(browser->profile()->GetOriginalProfile());
     67   registrar_.Add(this,
     68                  extensions::NOTIFICATION_EXTENSION_COMMAND_ADDED,
     69                  notification_source);
     70   registrar_.Add(this,
     71                  extensions::NOTIFICATION_EXTENSION_COMMAND_REMOVED,
     72                  notification_source);
     73 
     74   // We also listen for browser theme changes on linux because a switch from or
     75   // to GTK requires that we regrab our browser action images.
     76   registrar_.Add(
     77       this,
     78       chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
     79       content::Source<ThemeService>(
     80           ThemeServiceFactory::GetForProfile(browser->profile())));
     81 
     82   UpdateState();
     83 }
     84 
     85 BrowserActionView::~BrowserActionView() {
     86 }
     87 
     88 void BrowserActionView::ViewHierarchyChanged(
     89     const ViewHierarchyChangedDetails& details) {
     90   if (details.is_add && !called_registered_extension_command_ &&
     91       GetFocusManager()) {
     92     view_controller_->RegisterCommand();
     93     called_registered_extension_command_ = true;
     94   }
     95 
     96   MenuButton::ViewHierarchyChanged(details);
     97 }
     98 
     99 void BrowserActionView::OnDragDone() {
    100   delegate_->OnBrowserActionViewDragDone();
    101 }
    102 
    103 gfx::Size BrowserActionView::GetPreferredSize() const {
    104   return gfx::Size(BrowserActionsContainer::IconWidth(false),
    105                    BrowserActionsContainer::IconHeight());
    106 }
    107 
    108 void BrowserActionView::PaintChildren(gfx::Canvas* canvas,
    109                                       const views::CullSet& cull_set) {
    110   View::PaintChildren(canvas, cull_set);
    111   int tab_id = view_controller_->GetCurrentTabId();
    112   if (tab_id >= 0)
    113     extension_action()->PaintBadge(canvas, GetLocalBounds(), tab_id);
    114 }
    115 
    116 void BrowserActionView::GetAccessibleState(ui::AXViewState* state) {
    117   views::MenuButton::GetAccessibleState(state);
    118   state->name = l10n_util::GetStringUTF16(
    119       IDS_ACCNAME_EXTENSIONS_BROWSER_ACTION);
    120   state->role = ui::AX_ROLE_BUTTON;
    121 }
    122 
    123 void BrowserActionView::ButtonPressed(views::Button* sender,
    124                                       const ui::Event& event) {
    125   view_controller_->ExecuteActionByUser();
    126 }
    127 
    128 void BrowserActionView::UpdateState() {
    129   int tab_id = view_controller_->GetCurrentTabId();
    130   if (tab_id < 0)
    131     return;
    132 
    133   if (!IsEnabled(tab_id))
    134     SetState(views::CustomButton::STATE_DISABLED);
    135   else if (state() == views::CustomButton::STATE_DISABLED)
    136     SetState(views::CustomButton::STATE_NORMAL);
    137 
    138   gfx::ImageSkia icon = *view_controller_->GetIcon(tab_id).ToImageSkia();
    139 
    140   if (!icon.isNull()) {
    141     if (!extension_action()->GetIsVisible(tab_id))
    142       icon = gfx::ImageSkiaOperations::CreateTransparentImage(icon, .25);
    143 
    144     ThemeService* theme = ThemeServiceFactory::GetForProfile(
    145         view_controller_->browser()->profile());
    146 
    147     gfx::ImageSkia bg = *theme->GetImageSkiaNamed(IDR_BROWSER_ACTION);
    148     SetImage(views::Button::STATE_NORMAL,
    149              gfx::ImageSkiaOperations::CreateSuperimposedImage(bg, icon));
    150   }
    151 
    152   // If the browser action name is empty, show the extension name instead.
    153   std::string title = extension_action()->GetTitle(tab_id);
    154   base::string16 name =
    155       base::UTF8ToUTF16(title.empty() ? extension()->name() : title);
    156   SetTooltipText(name);
    157   SetAccessibleName(name);
    158 
    159   Layout();  // We need to layout since we may have added an icon as a result.
    160   SchedulePaint();
    161 }
    162 
    163 bool BrowserActionView::IsPopup() {
    164   int tab_id = view_controller_->GetCurrentTabId();
    165   return (tab_id < 0) ? false : extension_action()->HasPopup(tab_id);
    166 }
    167 
    168 void BrowserActionView::Observe(int type,
    169                                 const content::NotificationSource& source,
    170                                 const content::NotificationDetails& details) {
    171   switch (type) {
    172     case extensions::NOTIFICATION_EXTENSION_COMMAND_ADDED:
    173     case extensions::NOTIFICATION_EXTENSION_COMMAND_REMOVED: {
    174       std::pair<const std::string, const std::string>* payload =
    175           content::Details<std::pair<const std::string, const std::string> >(
    176               details).ptr();
    177       if (extension()->id() == payload->first &&
    178           payload->second ==
    179               extensions::manifest_values::kBrowserActionCommandEvent) {
    180         if (type == extensions::NOTIFICATION_EXTENSION_COMMAND_ADDED)
    181           view_controller_->RegisterCommand();
    182         else
    183           view_controller_->UnregisterCommand(true);
    184       }
    185       break;
    186     }
    187     case chrome::NOTIFICATION_BROWSER_THEME_CHANGED:
    188       UpdateState();
    189       break;
    190     default:
    191       NOTREACHED();
    192       break;
    193   }
    194 }
    195 
    196 bool BrowserActionView::Activate() {
    197   if (!IsPopup())
    198     return true;
    199 
    200   view_controller_->ExecuteActionByUser();
    201 
    202   // TODO(erikkay): Run a nested modal loop while the mouse is down to
    203   // enable menu-like drag-select behavior.
    204 
    205   // The return value of this method is returned via OnMousePressed.
    206   // We need to return false here since we're handing off focus to another
    207   // widget/view, and true will grab it right back and try to send events
    208   // to us.
    209   return false;
    210 }
    211 
    212 bool BrowserActionView::OnMousePressed(const ui::MouseEvent& event) {
    213   if (!event.IsRightMouseButton()) {
    214     return IsPopup() ? MenuButton::OnMousePressed(event) :
    215                        LabelButton::OnMousePressed(event);
    216   }
    217   return false;
    218 }
    219 
    220 void BrowserActionView::OnMouseReleased(const ui::MouseEvent& event) {
    221   if (IsPopup() || view_controller_->is_menu_running()) {
    222     // TODO(erikkay) this never actually gets called (probably because of the
    223     // loss of focus).
    224     MenuButton::OnMouseReleased(event);
    225   } else {
    226     LabelButton::OnMouseReleased(event);
    227   }
    228 }
    229 
    230 void BrowserActionView::OnMouseExited(const ui::MouseEvent& event) {
    231   if (IsPopup() || view_controller_->is_menu_running())
    232     MenuButton::OnMouseExited(event);
    233   else
    234     LabelButton::OnMouseExited(event);
    235 }
    236 
    237 bool BrowserActionView::OnKeyReleased(const ui::KeyEvent& event) {
    238   return IsPopup() ? MenuButton::OnKeyReleased(event) :
    239                      LabelButton::OnKeyReleased(event);
    240 }
    241 
    242 void BrowserActionView::OnGestureEvent(ui::GestureEvent* event) {
    243   if (IsPopup())
    244     MenuButton::OnGestureEvent(event);
    245   else
    246     LabelButton::OnGestureEvent(event);
    247 }
    248 
    249 scoped_ptr<LabelButtonBorder> BrowserActionView::CreateDefaultBorder() const {
    250   scoped_ptr<LabelButtonBorder> border = LabelButton::CreateDefaultBorder();
    251   border->set_insets(gfx::Insets(kBorderInset, kBorderInset,
    252                                  kBorderInset, kBorderInset));
    253   return border.Pass();
    254 }
    255 
    256 bool BrowserActionView::IsEnabled(int tab_id) const {
    257   return view_controller_->extension_action()->GetIsVisible(tab_id);
    258 }
    259 
    260 gfx::ImageSkia BrowserActionView::GetIconWithBadge() {
    261   int tab_id = view_controller_->GetCurrentTabId();
    262   gfx::Size spacing(0, ToolbarView::kVertSpacing);
    263   gfx::ImageSkia icon = *view_controller_->GetIcon(tab_id).ToImageSkia();
    264   if (!IsEnabled(tab_id))
    265     icon = gfx::ImageSkiaOperations::CreateTransparentImage(icon, .25);
    266   return extension_action()->GetIconWithBadge(icon, tab_id, spacing);
    267 }
    268 
    269 gfx::ImageSkia BrowserActionView::GetIconForTest() {
    270   return GetImage(views::Button::STATE_NORMAL);
    271 }
    272 
    273 void BrowserActionView::OnIconUpdated() {
    274   UpdateState();
    275   if (icon_observer_)
    276     icon_observer_->OnIconUpdated(GetIconWithBadge());
    277 }
    278 
    279 views::View* BrowserActionView::GetAsView() {
    280   return this;
    281 }
    282 
    283 bool BrowserActionView::IsShownInMenu() {
    284   return delegate_->ShownInsideMenu();
    285 }
    286 
    287 views::FocusManager* BrowserActionView::GetFocusManagerForAccelerator() {
    288   return GetFocusManager();
    289 }
    290 
    291 views::Widget* BrowserActionView::GetParentForContextMenu() {
    292   // RunMenuAt expects a nested menu to be parented by the same widget as the
    293   // already visible menu, in this case the Chrome menu.
    294   return delegate_->ShownInsideMenu() ?
    295       BrowserView::GetBrowserViewForBrowser(view_controller_->browser())
    296           ->toolbar()->app_menu()->GetWidget() :
    297       GetWidget();
    298 }
    299 
    300 ExtensionActionViewController*
    301 BrowserActionView::GetPreferredPopupViewController() {
    302   return delegate_->ShownInsideMenu() ?
    303       delegate_->GetMainViewForExtension(extension())->view_controller() :
    304       view_controller();
    305 }
    306 
    307 views::View* BrowserActionView::GetReferenceViewForPopup() {
    308   // Browser actions in the overflow menu can still show popups, so we may need
    309   // a reference view other than this button's parent. If so, use the overflow
    310   // view.
    311   return visible() ? this : delegate_->GetOverflowReferenceView();
    312 }
    313 
    314 views::MenuButton* BrowserActionView::GetContextMenuButton() {
    315   DCHECK(visible());  // We should never show a context menu for a hidden item.
    316   return this;
    317 }
    318 
    319 content::WebContents* BrowserActionView::GetCurrentWebContents() {
    320   return delegate_->GetCurrentWebContents();
    321 }
    322 
    323 void BrowserActionView::HideActivePopup() {
    324   delegate_->HideActivePopup();
    325 }
    326 
    327 void BrowserActionView::OnPopupShown(bool grant_tab_permissions) {
    328   delegate_->SetPopupOwner(this);
    329   // If this was through direct user action, we press the menu button.
    330   if (grant_tab_permissions) {
    331     // We set the state of the menu button we're using as a reference view,
    332     // which is either this or the overflow reference view.
    333     // This cast is safe because GetReferenceViewForPopup returns either |this|
    334     // or delegate_->GetOverflowReferenceView(), which returns a MenuButton.
    335     views::MenuButton* reference_view =
    336         static_cast<views::MenuButton*>(GetReferenceViewForPopup());
    337     pressed_lock_.reset(new views::MenuButton::PressedLock(reference_view));
    338   }
    339 }
    340 
    341 void BrowserActionView::CleanupPopup() {
    342   // We need to do these actions synchronously (instead of closing and then
    343   // performing the rest of the cleanup in OnWidgetDestroyed()) because
    344   // OnWidgetDestroyed() can be called asynchronously from Close(), and we need
    345   // to keep the delegate's popup owner up-to-date.
    346   delegate_->SetPopupOwner(NULL);
    347   pressed_lock_.reset();  // Unpress the menu button if it was pressed.
    348 }
    349