| OLD | NEW |
| (Empty) |
| 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 "extensions/common/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/l10n/l10n_util.h" | |
| 25 #include "ui/base/resource/resource_bundle.h" | |
| 26 #include "ui/events/event.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 extensions::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 } | |
| OLD | NEW |