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 "base/strings/utf_string_conversions.h"
      8 #include "chrome/browser/chrome_notification_types.h"
      9 #include "chrome/browser/extensions/api/commands/command_service.h"
     10 #include "chrome/browser/extensions/extension_action.h"
     11 #include "chrome/browser/extensions/extension_action_manager.h"
     12 #include "chrome/browser/extensions/extension_context_menu_model.h"
     13 #include "chrome/browser/extensions/extension_service.h"
     14 #include "chrome/browser/profiles/profile.h"
     15 #include "chrome/browser/themes/theme_service.h"
     16 #include "chrome/browser/themes/theme_service_factory.h"
     17 #include "chrome/browser/ui/browser.h"
     18 #include "chrome/browser/ui/view_ids.h"
     19 #include "chrome/browser/ui/views/toolbar/browser_actions_container.h"
     20 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
     21 #include "extensions/common/extension.h"
     22 #include "extensions/common/manifest_constants.h"
     23 #include "grit/generated_resources.h"
     24 #include "grit/theme_resources.h"
     25 #include "ui/accessibility/ax_view_state.h"
     26 #include "ui/base/l10n/l10n_util.h"
     27 #include "ui/base/resource/resource_bundle.h"
     28 #include "ui/events/event.h"
     29 #include "ui/gfx/image/image_skia.h"
     30 #include "ui/gfx/image/image_skia_operations.h"
     31 #include "ui/gfx/image/image_skia_source.h"
     32 #include "ui/views/controls/menu/menu_item_view.h"
     33 #include "ui/views/controls/menu/menu_runner.h"
     34 
     35 using extensions::Extension;
     36 
     37 ////////////////////////////////////////////////////////////////////////////////
     38 // BrowserActionView
     39 
     40 BrowserActionView::BrowserActionView(const Extension* extension,
     41                                      Browser* browser,
     42                                      BrowserActionView::Delegate* delegate)
     43     : browser_(browser),
     44       delegate_(delegate),
     45       button_(NULL),
     46       extension_(extension) {
     47   set_id(VIEW_ID_BROWSER_ACTION);
     48   button_ = new BrowserActionButton(extension_, browser_, delegate_);
     49   button_->set_drag_controller(delegate_);
     50   button_->set_owned_by_client();
     51   AddChildView(button_);
     52   button_->UpdateState();
     53 }
     54 
     55 BrowserActionView::~BrowserActionView() {
     56   button_->Destroy();
     57 }
     58 
     59 gfx::ImageSkia BrowserActionView::GetIconWithBadge() {
     60   return button_->GetIconWithBadge();
     61 }
     62 
     63 void BrowserActionView::Layout() {
     64   button_->SetBounds(0, y(), width(), height());
     65 }
     66 
     67 void BrowserActionView::GetAccessibleState(ui::AXViewState* state) {
     68   state->name = l10n_util::GetStringUTF16(
     69       IDS_ACCNAME_EXTENSIONS_BROWSER_ACTION);
     70   state->role = ui::AX_ROLE_GROUP;
     71 }
     72 
     73 gfx::Size BrowserActionView::GetPreferredSize() const {
     74   return gfx::Size(BrowserActionsContainer::IconWidth(false),
     75                    BrowserActionsContainer::IconHeight());
     76 }
     77 
     78 void BrowserActionView::PaintChildren(gfx::Canvas* canvas,
     79                                       const views::CullSet& cull_set) {
     80   View::PaintChildren(canvas, cull_set);
     81   ExtensionAction* action = button()->browser_action();
     82   int tab_id = delegate_->GetCurrentTabId();
     83   if (tab_id >= 0)
     84     action->PaintBadge(canvas, GetLocalBounds(), tab_id);
     85 }
     86 
     87 ////////////////////////////////////////////////////////////////////////////////
     88 // BrowserActionButton
     89 
     90 BrowserActionButton::BrowserActionButton(const Extension* extension,
     91                                          Browser* browser,
     92                                          BrowserActionView::Delegate* delegate)
     93     : MenuButton(this, base::string16(), NULL, false),
     94       browser_(browser),
     95       browser_action_(
     96           extensions::ExtensionActionManager::Get(browser->profile())->
     97           GetBrowserAction(*extension)),
     98       extension_(extension),
     99       icon_factory_(browser->profile(), extension, browser_action_, this),
    100       delegate_(delegate),
    101       context_menu_(NULL),
    102       called_registered_extension_command_(false),
    103       icon_observer_(NULL) {
    104   SetBorder(views::Border::NullBorder());
    105   SetHorizontalAlignment(gfx::ALIGN_CENTER);
    106   set_context_menu_controller(this);
    107 
    108   // No UpdateState() here because View hierarchy not setup yet. Our parent
    109   // should call UpdateState() after creation.
    110 
    111   content::NotificationSource notification_source =
    112       content::Source<Profile>(browser_->profile()->GetOriginalProfile());
    113   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_UPDATED,
    114                  content::Source<ExtensionAction>(browser_action_));
    115   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_COMMAND_ADDED,
    116                  notification_source);
    117   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_COMMAND_REMOVED,
    118                  notification_source);
    119 
    120   // We also listen for browser theme changes on linux because a switch from or
    121   // to GTK requires that we regrab our browser action images.
    122   registrar_.Add(
    123       this,
    124       chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
    125       content::Source<ThemeService>(
    126           ThemeServiceFactory::GetForProfile(browser->profile())));
    127 }
    128 
    129 void BrowserActionButton::Destroy() {
    130   MaybeUnregisterExtensionCommand(false);
    131 
    132   if (context_menu_) {
    133     context_menu_->Cancel();
    134     base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
    135   } else {
    136     delete this;
    137   }
    138 }
    139 
    140 void BrowserActionButton::ViewHierarchyChanged(
    141     const ViewHierarchyChangedDetails& details) {
    142   if (details.is_add && !called_registered_extension_command_ &&
    143       GetFocusManager()) {
    144     MaybeRegisterExtensionCommand();
    145     called_registered_extension_command_ = true;
    146   }
    147 
    148   MenuButton::ViewHierarchyChanged(details);
    149 }
    150 
    151 bool BrowserActionButton::CanHandleAccelerators() const {
    152   // View::CanHandleAccelerators() checks to see if the view is visible before
    153   // allowing it to process accelerators. This is not appropriate for browser
    154   // actions buttons, which can be hidden inside the overflow area.
    155   return true;
    156 }
    157 
    158 void BrowserActionButton::GetAccessibleState(ui::AXViewState* state) {
    159   views::MenuButton::GetAccessibleState(state);
    160   state->role = ui::AX_ROLE_BUTTON;
    161 }
    162 
    163 void BrowserActionButton::ButtonPressed(views::Button* sender,
    164                                         const ui::Event& event) {
    165   delegate_->OnBrowserActionExecuted(this);
    166 }
    167 
    168 void BrowserActionButton::ShowContextMenuForView(
    169     View* source,
    170     const gfx::Point& point,
    171     ui::MenuSourceType source_type) {
    172   if (!extension()->ShowConfigureContextMenus())
    173     return;
    174 
    175   SetButtonPushed();
    176 
    177   // Reconstructs the menu every time because the menu's contents are dynamic.
    178   scoped_refptr<ExtensionContextMenuModel> context_menu_contents_(
    179       new ExtensionContextMenuModel(extension(), browser_, delegate_));
    180   menu_runner_.reset(new views::MenuRunner(context_menu_contents_.get()));
    181 
    182   context_menu_ = menu_runner_->GetMenu();
    183   gfx::Point screen_loc;
    184   views::View::ConvertPointToScreen(this, &screen_loc);
    185   if (menu_runner_->RunMenuAt(
    186           GetWidget(),
    187           NULL,
    188           gfx::Rect(screen_loc, size()),
    189           views::MENU_ANCHOR_TOPLEFT,
    190           source_type,
    191           views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU) ==
    192       views::MenuRunner::MENU_DELETED) {
    193     return;
    194   }
    195 
    196   menu_runner_.reset();
    197   SetButtonNotPushed();
    198   context_menu_ = NULL;
    199 }
    200 
    201 void BrowserActionButton::UpdateState() {
    202   int tab_id = delegate_->GetCurrentTabId();
    203   if (tab_id < 0)
    204     return;
    205 
    206   if (!IsEnabled(tab_id)) {
    207     SetState(views::CustomButton::STATE_DISABLED);
    208   } else {
    209     SetState(menu_visible_ ?
    210              views::CustomButton::STATE_PRESSED :
    211              views::CustomButton::STATE_NORMAL);
    212   }
    213 
    214   gfx::ImageSkia icon = *icon_factory_.GetIcon(tab_id).ToImageSkia();
    215 
    216   if (!icon.isNull()) {
    217     if (!browser_action()->GetIsVisible(tab_id))
    218       icon = gfx::ImageSkiaOperations::CreateTransparentImage(icon, .25);
    219 
    220     ThemeService* theme =
    221         ThemeServiceFactory::GetForProfile(browser_->profile());
    222 
    223     gfx::ImageSkia bg = *theme->GetImageSkiaNamed(IDR_BROWSER_ACTION);
    224     SetImage(views::Button::STATE_NORMAL,
    225              gfx::ImageSkiaOperations::CreateSuperimposedImage(bg, icon));
    226 
    227     gfx::ImageSkia bg_h = *theme->GetImageSkiaNamed(IDR_BROWSER_ACTION_H);
    228     SetImage(views::Button::STATE_HOVERED,
    229              gfx::ImageSkiaOperations::CreateSuperimposedImage(bg_h, icon));
    230 
    231     gfx::ImageSkia bg_p = *theme->GetImageSkiaNamed(IDR_BROWSER_ACTION_P);
    232     SetImage(views::Button::STATE_PRESSED,
    233              gfx::ImageSkiaOperations::CreateSuperimposedImage(bg_p, icon));
    234   }
    235 
    236   // If the browser action name is empty, show the extension name instead.
    237   std::string title = browser_action()->GetTitle(tab_id);
    238   base::string16 name =
    239       base::UTF8ToUTF16(title.empty() ? extension()->name() : title);
    240   SetTooltipText(name);
    241   SetAccessibleName(name);
    242 
    243   parent()->SchedulePaint();
    244 }
    245 
    246 bool BrowserActionButton::IsPopup() {
    247   int tab_id = delegate_->GetCurrentTabId();
    248   return (tab_id < 0) ? false : browser_action_->HasPopup(tab_id);
    249 }
    250 
    251 GURL BrowserActionButton::GetPopupUrl() {
    252   int tab_id = delegate_->GetCurrentTabId();
    253   return (tab_id < 0) ? GURL() : browser_action_->GetPopupUrl(tab_id);
    254 }
    255 
    256 void BrowserActionButton::Observe(int type,
    257                                   const content::NotificationSource& source,
    258                                   const content::NotificationDetails& details) {
    259   switch (type) {
    260     case chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_UPDATED:
    261       UpdateState();
    262       // The browser action may have become visible/hidden so we need to make
    263       // sure the state gets updated.
    264       delegate_->OnBrowserActionVisibilityChanged();
    265       break;
    266     case chrome::NOTIFICATION_EXTENSION_COMMAND_ADDED:
    267     case chrome::NOTIFICATION_EXTENSION_COMMAND_REMOVED: {
    268       std::pair<const std::string, const std::string>* payload =
    269           content::Details<std::pair<const std::string, const std::string> >(
    270               details).ptr();
    271       if (extension_->id() == payload->first &&
    272           payload->second ==
    273               extensions::manifest_values::kBrowserActionCommandEvent) {
    274         if (type == chrome::NOTIFICATION_EXTENSION_COMMAND_ADDED)
    275           MaybeRegisterExtensionCommand();
    276         else
    277           MaybeUnregisterExtensionCommand(true);
    278       }
    279       break;
    280     }
    281     case chrome::NOTIFICATION_BROWSER_THEME_CHANGED:
    282       UpdateState();
    283       break;
    284     default:
    285       NOTREACHED();
    286       break;
    287   }
    288 }
    289 
    290 void BrowserActionButton::OnIconUpdated() {
    291   UpdateState();
    292   if (icon_observer_)
    293     icon_observer_->OnIconUpdated(GetIconWithBadge());
    294 }
    295 
    296 bool BrowserActionButton::Activate() {
    297   if (!IsPopup())
    298     return true;
    299 
    300   delegate_->OnBrowserActionExecuted(this);
    301 
    302   // TODO(erikkay): Run a nested modal loop while the mouse is down to
    303   // enable menu-like drag-select behavior.
    304 
    305   // The return value of this method is returned via OnMousePressed.
    306   // We need to return false here since we're handing off focus to another
    307   // widget/view, and true will grab it right back and try to send events
    308   // to us.
    309   return false;
    310 }
    311 
    312 bool BrowserActionButton::OnMousePressed(const ui::MouseEvent& event) {
    313   if (!event.IsRightMouseButton()) {
    314     return IsPopup() ? MenuButton::OnMousePressed(event) :
    315                        LabelButton::OnMousePressed(event);
    316   }
    317 
    318   if (!views::View::ShouldShowContextMenuOnMousePress()) {
    319     // See comments in MenuButton::Activate() as to why this is needed.
    320     SetMouseHandler(NULL);
    321 
    322     ShowContextMenu(gfx::Point(), ui::MENU_SOURCE_MOUSE);
    323   }
    324   return false;
    325 }
    326 
    327 void BrowserActionButton::OnMouseReleased(const ui::MouseEvent& event) {
    328   if (IsPopup() || context_menu_) {
    329     // TODO(erikkay) this never actually gets called (probably because of the
    330     // loss of focus).
    331     MenuButton::OnMouseReleased(event);
    332   } else {
    333     LabelButton::OnMouseReleased(event);
    334   }
    335 }
    336 
    337 void BrowserActionButton::OnMouseExited(const ui::MouseEvent& event) {
    338   if (IsPopup() || context_menu_)
    339     MenuButton::OnMouseExited(event);
    340   else
    341     LabelButton::OnMouseExited(event);
    342 }
    343 
    344 bool BrowserActionButton::OnKeyReleased(const ui::KeyEvent& event) {
    345   return IsPopup() ? MenuButton::OnKeyReleased(event) :
    346                      LabelButton::OnKeyReleased(event);
    347 }
    348 
    349 void BrowserActionButton::OnGestureEvent(ui::GestureEvent* event) {
    350   if (IsPopup())
    351     MenuButton::OnGestureEvent(event);
    352   else
    353     LabelButton::OnGestureEvent(event);
    354 }
    355 
    356 bool BrowserActionButton::AcceleratorPressed(
    357     const ui::Accelerator& accelerator) {
    358   delegate_->OnBrowserActionExecuted(this);
    359   return true;
    360 }
    361 
    362 void BrowserActionButton::SetButtonPushed() {
    363   SetState(views::CustomButton::STATE_PRESSED);
    364   menu_visible_ = true;
    365 }
    366 
    367 void BrowserActionButton::SetButtonNotPushed() {
    368   SetState(views::CustomButton::STATE_NORMAL);
    369   menu_visible_ = false;
    370 }
    371 
    372 bool BrowserActionButton::IsEnabled(int tab_id) const {
    373   return browser_action_->GetIsVisible(tab_id);
    374 }
    375 
    376 gfx::ImageSkia BrowserActionButton::GetIconWithBadge() {
    377   int tab_id = delegate_->GetCurrentTabId();
    378   gfx::Size spacing(0, ToolbarView::kVertSpacing);
    379   gfx::ImageSkia icon = *icon_factory_.GetIcon(tab_id).ToImageSkia();
    380   if (!IsEnabled(tab_id))
    381     icon = gfx::ImageSkiaOperations::CreateTransparentImage(icon, .25);
    382   return browser_action_->GetIconWithBadge(icon, tab_id, spacing);
    383 }
    384 
    385 gfx::ImageSkia BrowserActionButton::GetIconForTest() {
    386   return GetImage(views::Button::STATE_NORMAL);
    387 }
    388 
    389 BrowserActionButton::~BrowserActionButton() {
    390 }
    391 
    392 void BrowserActionButton::MaybeRegisterExtensionCommand() {
    393   extensions::CommandService* command_service =
    394       extensions::CommandService::Get(browser_->profile());
    395   extensions::Command browser_action_command;
    396   if (command_service->GetBrowserActionCommand(
    397           extension_->id(),
    398           extensions::CommandService::ACTIVE_ONLY,
    399           &browser_action_command,
    400           NULL)) {
    401     keybinding_.reset(new ui::Accelerator(
    402         browser_action_command.accelerator()));
    403     GetFocusManager()->RegisterAccelerator(
    404         *keybinding_.get(), ui::AcceleratorManager::kHighPriority, this);
    405   }
    406 }
    407 
    408 void BrowserActionButton::MaybeUnregisterExtensionCommand(bool only_if_active) {
    409   if (!keybinding_.get() || !GetFocusManager())
    410     return;
    411 
    412   extensions::CommandService* command_service =
    413       extensions::CommandService::Get(browser_->profile());
    414 
    415   extensions::Command browser_action_command;
    416   if (!only_if_active || !command_service->GetBrowserActionCommand(
    417           extension_->id(),
    418           extensions::CommandService::ACTIVE_ONLY,
    419           &browser_action_command,
    420           NULL)) {
    421     GetFocusManager()->UnregisterAccelerator(*keybinding_.get(), this);
    422     keybinding_.reset(NULL);
    423   }
    424 }
    425