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