browser_action_view.cc revision 5f1c94371a64b3196d4be9466099bb892df9b88e
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/extensions/extension_action_manager.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/view_ids.h" 18#include "chrome/browser/ui/views/frame/browser_view.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/button/label_button_border.h" 33 34using extensions::Extension; 35using views::LabelButtonBorder; 36 37namespace { 38 39// We have smaller insets than normal STYLE_TEXTBUTTON buttons so that we can 40// fit user supplied icons in without clipping them. 41const int kBorderInset = 4; 42 43} // namespace 44 45//////////////////////////////////////////////////////////////////////////////// 46// BrowserActionView 47 48BrowserActionView::BrowserActionView(const Extension* extension, 49 Browser* browser, 50 BrowserActionView::Delegate* delegate) 51 : delegate_(delegate) { 52 set_id(VIEW_ID_BROWSER_ACTION); 53 button_.reset(new BrowserActionButton(extension, browser, delegate_)); 54 button_->set_drag_controller(delegate_); 55 button_->set_owned_by_client(); 56 AddChildView(button_.get()); 57 button_->UpdateState(); 58} 59 60BrowserActionView::~BrowserActionView() { 61} 62 63gfx::ImageSkia BrowserActionView::GetIconWithBadge() { 64 return button_->GetIconWithBadge(); 65} 66 67void BrowserActionView::Layout() { 68 button_->SetBounds(0, 0, width(), height()); 69} 70 71void BrowserActionView::GetAccessibleState(ui::AXViewState* state) { 72 state->name = l10n_util::GetStringUTF16( 73 IDS_ACCNAME_EXTENSIONS_BROWSER_ACTION); 74 state->role = ui::AX_ROLE_GROUP; 75} 76 77gfx::Size BrowserActionView::GetPreferredSize() const { 78 return gfx::Size(BrowserActionsContainer::IconWidth(false), 79 BrowserActionsContainer::IconHeight()); 80} 81 82void BrowserActionView::PaintChildren(gfx::Canvas* canvas, 83 const views::CullSet& cull_set) { 84 View::PaintChildren(canvas, cull_set); 85 ExtensionAction* action = button_->extension_action(); 86 int tab_id = button_->view_controller()->GetCurrentTabId(); 87 if (tab_id >= 0) 88 action->PaintBadge(canvas, GetLocalBounds(), tab_id); 89} 90 91//////////////////////////////////////////////////////////////////////////////// 92// BrowserActionButton 93 94BrowserActionButton::BrowserActionButton(const Extension* extension, 95 Browser* browser, 96 BrowserActionView::Delegate* delegate) 97 : MenuButton(this, base::string16(), NULL, false), 98 view_controller_(new ExtensionActionViewController( 99 extension, 100 browser, 101 extensions::ExtensionActionManager::Get(browser->profile())-> 102 GetBrowserAction(*extension), 103 this)), 104 delegate_(delegate), 105 called_registered_extension_command_(false), 106 icon_observer_(NULL) { 107 SetHorizontalAlignment(gfx::ALIGN_CENTER); 108 set_context_menu_controller(view_controller_.get()); 109 110 // No UpdateState() here because View hierarchy not setup yet. Our parent 111 // should call UpdateState() after creation. 112 113 content::NotificationSource notification_source = 114 content::Source<Profile>(browser->profile()->GetOriginalProfile()); 115 registrar_.Add(this, 116 extensions::NOTIFICATION_EXTENSION_BROWSER_ACTION_UPDATED, 117 content::Source<ExtensionAction>(extension_action())); 118 registrar_.Add(this, 119 extensions::NOTIFICATION_EXTENSION_COMMAND_ADDED, 120 notification_source); 121 registrar_.Add(this, 122 extensions::NOTIFICATION_EXTENSION_COMMAND_REMOVED, 123 notification_source); 124 125 // We also listen for browser theme changes on linux because a switch from or 126 // to GTK requires that we regrab our browser action images. 127 registrar_.Add( 128 this, 129 chrome::NOTIFICATION_BROWSER_THEME_CHANGED, 130 content::Source<ThemeService>( 131 ThemeServiceFactory::GetForProfile(browser->profile()))); 132} 133 134void BrowserActionButton::ViewHierarchyChanged( 135 const ViewHierarchyChangedDetails& details) { 136 if (details.is_add && !called_registered_extension_command_ && 137 GetFocusManager()) { 138 view_controller_->RegisterCommand(); 139 called_registered_extension_command_ = true; 140 } 141 142 MenuButton::ViewHierarchyChanged(details); 143} 144 145void BrowserActionButton::OnDragDone() { 146 delegate_->OnBrowserActionViewDragDone(); 147} 148 149void BrowserActionButton::GetAccessibleState(ui::AXViewState* state) { 150 views::MenuButton::GetAccessibleState(state); 151 state->role = ui::AX_ROLE_BUTTON; 152} 153 154void BrowserActionButton::ButtonPressed(views::Button* sender, 155 const ui::Event& event) { 156 view_controller_->ExecuteActionByUser(); 157} 158 159void BrowserActionButton::UpdateState() { 160 int tab_id = view_controller_->GetCurrentTabId(); 161 if (tab_id < 0) 162 return; 163 164 if (!IsEnabled(tab_id)) { 165 SetState(views::CustomButton::STATE_DISABLED); 166 } else { 167 SetState(menu_visible_ ? 168 views::CustomButton::STATE_PRESSED : 169 views::CustomButton::STATE_NORMAL); 170 } 171 172 gfx::ImageSkia icon = *view_controller_->GetIcon(tab_id).ToImageSkia(); 173 174 if (!icon.isNull()) { 175 if (!extension_action()->GetIsVisible(tab_id)) 176 icon = gfx::ImageSkiaOperations::CreateTransparentImage(icon, .25); 177 178 ThemeService* theme = ThemeServiceFactory::GetForProfile( 179 view_controller_->browser()->profile()); 180 181 gfx::ImageSkia bg = *theme->GetImageSkiaNamed(IDR_BROWSER_ACTION); 182 SetImage(views::Button::STATE_NORMAL, 183 gfx::ImageSkiaOperations::CreateSuperimposedImage(bg, icon)); 184 } 185 186 // If the browser action name is empty, show the extension name instead. 187 std::string title = extension_action()->GetTitle(tab_id); 188 base::string16 name = 189 base::UTF8ToUTF16(title.empty() ? extension()->name() : title); 190 SetTooltipText(name); 191 SetAccessibleName(name); 192 193 parent()->SchedulePaint(); 194} 195 196bool BrowserActionButton::IsPopup() { 197 int tab_id = view_controller_->GetCurrentTabId(); 198 return (tab_id < 0) ? false : extension_action()->HasPopup(tab_id); 199} 200 201void BrowserActionButton::Observe(int type, 202 const content::NotificationSource& source, 203 const content::NotificationDetails& details) { 204 switch (type) { 205 case extensions::NOTIFICATION_EXTENSION_BROWSER_ACTION_UPDATED: 206 UpdateState(); 207 // The browser action may have become visible/hidden so we need to make 208 // sure the state gets updated. 209 delegate_->OnBrowserActionVisibilityChanged(); 210 break; 211 case extensions::NOTIFICATION_EXTENSION_COMMAND_ADDED: 212 case extensions::NOTIFICATION_EXTENSION_COMMAND_REMOVED: { 213 std::pair<const std::string, const std::string>* payload = 214 content::Details<std::pair<const std::string, const std::string> >( 215 details).ptr(); 216 if (extension()->id() == payload->first && 217 payload->second == 218 extensions::manifest_values::kBrowserActionCommandEvent) { 219 if (type == extensions::NOTIFICATION_EXTENSION_COMMAND_ADDED) 220 view_controller_->RegisterCommand(); 221 else 222 view_controller_->UnregisterCommand(true); 223 } 224 break; 225 } 226 case chrome::NOTIFICATION_BROWSER_THEME_CHANGED: 227 UpdateState(); 228 break; 229 default: 230 NOTREACHED(); 231 break; 232 } 233} 234 235bool BrowserActionButton::Activate() { 236 if (!IsPopup()) 237 return true; 238 239 view_controller_->ExecuteActionByUser(); 240 241 // TODO(erikkay): Run a nested modal loop while the mouse is down to 242 // enable menu-like drag-select behavior. 243 244 // The return value of this method is returned via OnMousePressed. 245 // We need to return false here since we're handing off focus to another 246 // widget/view, and true will grab it right back and try to send events 247 // to us. 248 return false; 249} 250 251bool BrowserActionButton::OnMousePressed(const ui::MouseEvent& event) { 252 if (!event.IsRightMouseButton()) { 253 return IsPopup() ? MenuButton::OnMousePressed(event) : 254 LabelButton::OnMousePressed(event); 255 } 256 257 if (!views::View::ShouldShowContextMenuOnMousePress()) { 258 // See comments in MenuButton::Activate() as to why this is needed. 259 SetMouseHandler(NULL); 260 261 ShowContextMenu(gfx::Point(), ui::MENU_SOURCE_MOUSE); 262 } 263 return false; 264} 265 266void BrowserActionButton::OnMouseReleased(const ui::MouseEvent& event) { 267 if (IsPopup() || view_controller_->is_menu_running()) { 268 // TODO(erikkay) this never actually gets called (probably because of the 269 // loss of focus). 270 MenuButton::OnMouseReleased(event); 271 } else { 272 LabelButton::OnMouseReleased(event); 273 } 274} 275 276void BrowserActionButton::OnMouseExited(const ui::MouseEvent& event) { 277 if (IsPopup() || view_controller_->is_menu_running()) 278 MenuButton::OnMouseExited(event); 279 else 280 LabelButton::OnMouseExited(event); 281} 282 283bool BrowserActionButton::OnKeyReleased(const ui::KeyEvent& event) { 284 return IsPopup() ? MenuButton::OnKeyReleased(event) : 285 LabelButton::OnKeyReleased(event); 286} 287 288void BrowserActionButton::OnGestureEvent(ui::GestureEvent* event) { 289 if (IsPopup()) 290 MenuButton::OnGestureEvent(event); 291 else 292 LabelButton::OnGestureEvent(event); 293} 294 295scoped_ptr<LabelButtonBorder> BrowserActionButton::CreateDefaultBorder() const { 296 scoped_ptr<LabelButtonBorder> border = LabelButton::CreateDefaultBorder(); 297 border->set_insets(gfx::Insets(kBorderInset, kBorderInset, 298 kBorderInset, kBorderInset)); 299 return border.Pass(); 300} 301 302void BrowserActionButton::SetButtonPushed() { 303 SetState(views::CustomButton::STATE_PRESSED); 304 menu_visible_ = true; 305} 306 307void BrowserActionButton::SetButtonNotPushed() { 308 SetState(views::CustomButton::STATE_NORMAL); 309 menu_visible_ = false; 310} 311 312bool BrowserActionButton::IsEnabled(int tab_id) const { 313 return view_controller_->extension_action()->GetIsVisible(tab_id); 314} 315 316gfx::ImageSkia BrowserActionButton::GetIconWithBadge() { 317 int tab_id = view_controller_->GetCurrentTabId(); 318 gfx::Size spacing(0, ToolbarView::kVertSpacing); 319 gfx::ImageSkia icon = *view_controller_->GetIcon(tab_id).ToImageSkia(); 320 if (!IsEnabled(tab_id)) 321 icon = gfx::ImageSkiaOperations::CreateTransparentImage(icon, .25); 322 return extension_action()->GetIconWithBadge(icon, tab_id, spacing); 323} 324 325gfx::ImageSkia BrowserActionButton::GetIconForTest() { 326 return GetImage(views::Button::STATE_NORMAL); 327} 328 329BrowserActionButton::~BrowserActionButton() { 330} 331 332void BrowserActionButton::OnIconUpdated() { 333 UpdateState(); 334 if (icon_observer_) 335 icon_observer_->OnIconUpdated(GetIconWithBadge()); 336} 337 338views::View* BrowserActionButton::GetAsView() { 339 return this; 340} 341 342bool BrowserActionButton::IsShownInMenu() { 343 return delegate_->ShownInsideMenu(); 344} 345 346views::FocusManager* BrowserActionButton::GetFocusManagerForAccelerator() { 347 return GetFocusManager(); 348} 349 350views::Widget* BrowserActionButton::GetParentForContextMenu() { 351 // RunMenuAt expects a nested menu to be parented by the same widget as the 352 // already visible menu, in this case the Chrome menu. 353 return delegate_->ShownInsideMenu() ? 354 BrowserView::GetBrowserViewForBrowser(view_controller_->browser()) 355 ->toolbar()->app_menu()->GetWidget() : 356 GetWidget(); 357} 358 359views::View* BrowserActionButton::GetReferenceViewForPopup() { 360 // Browser actions in the overflow menu can still show popups, so we may need 361 // a reference view other than this button's parent. If so, use the overflow 362 // view. 363 return parent()->visible() ? this : delegate_->GetOverflowReferenceView(); 364} 365 366content::WebContents* BrowserActionButton::GetCurrentWebContents() { 367 return delegate_->GetCurrentWebContents(); 368} 369 370void BrowserActionButton::HideActivePopup() { 371 delegate_->HideActivePopup(); 372} 373 374void BrowserActionButton::OnPopupShown(bool grant_tab_permissions) { 375 delegate_->SetPopupOwner(this); 376 if (grant_tab_permissions) 377 SetButtonPushed(); 378} 379 380void BrowserActionButton::CleanupPopup() { 381 // We need to do these actions synchronously (instead of closing and then 382 // performing the rest of the cleanup in OnWidgetDestroyed()) because 383 // OnWidgetDestroyed() can be called asynchronously from Close(), and we need 384 // to keep the delegate's popup owner up-to-date. 385 SetButtonNotPushed(); 386 delegate_->SetPopupOwner(NULL); 387} 388 389void BrowserActionButton::OnWillShowContextMenus() { 390 SetButtonPushed(); 391} 392 393void BrowserActionButton::OnContextMenuDone() { 394 SetButtonNotPushed(); 395} 396