browser_actions_container.cc revision 03b57e008b61dfcb1fbad3aea950ae0e001748b0
13c827367444ee418f129b2c238299f49d3264554Jarkko Poyry// Copyright 2013 The Chromium Authors. All rights reserved. 23c827367444ee418f129b2c238299f49d3264554Jarkko Poyry// Use of this source code is governed by a BSD-style license that can be 33c827367444ee418f129b2c238299f49d3264554Jarkko Poyry// found in the LICENSE file. 43c827367444ee418f129b2c238299f49d3264554Jarkko Poyry 53c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/ui/views/toolbar/browser_actions_container.h" 63c827367444ee418f129b2c238299f49d3264554Jarkko Poyry 73c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "base/compiler_specific.h" 83c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "base/stl_util.h" 93c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/extensions/extension_action_manager.h" 103c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/extensions/extension_util.h" 113c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/extensions/extension_view_host.h" 123c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/extensions/tab_helper.h" 133c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/profiles/profile.h" 143c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/ui/browser.h" 153c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/ui/browser_window.h" 163c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/ui/tabs/tab_strip_model.h" 173c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/ui/view_ids.h" 183c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/ui/views/extensions/browser_action_drag_data.h" 193c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/ui/views/extensions/extension_keybinding_registry_views.h" 203c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/ui/views/extensions/extension_popup.h" 213c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/ui/views/toolbar/browser_actions_container_observer.h" 223c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/browser/ui/views/toolbar/toolbar_view.h" 233c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "chrome/common/extensions/command.h" 243c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "extensions/browser/extension_registry.h" 253c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "extensions/browser/extension_system.h" 263c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "extensions/browser/runtime_data.h" 273c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "extensions/common/feature_switch.h" 283c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "grit/generated_resources.h" 293c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "grit/theme_resources.h" 303c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "third_party/skia/include/core/SkColor.h" 313c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "ui/accessibility/ax_view_state.h" 323c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "ui/base/dragdrop/drag_utils.h" 333c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "ui/base/l10n/l10n_util.h" 343c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "ui/base/nine_image_painter_factory.h" 353c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "ui/base/resource/resource_bundle.h" 363c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "ui/base/theme_provider.h" 373c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "ui/gfx/animation/slide_animation.h" 383c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "ui/gfx/canvas.h" 393c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "ui/gfx/geometry/rect.h" 403c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "ui/resources/grit/ui_resources.h" 413c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "ui/views/controls/button/label_button_border.h" 423c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "ui/views/controls/button/menu_button.h" 433c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "ui/views/controls/resize_area.h" 443c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "ui/views/metrics.h" 453c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "ui/views/painter.h" 463c827367444ee418f129b2c238299f49d3264554Jarkko Poyry#include "ui/views/widget/widget.h" 473c827367444ee418f129b2c238299f49d3264554Jarkko Poyry 483c827367444ee418f129b2c238299f49d3264554Jarkko Poyryusing extensions::Extension; 493c827367444ee418f129b2c238299f49d3264554Jarkko Poyry 503c827367444ee418f129b2c238299f49d3264554Jarkko Poyrynamespace { 513c827367444ee418f129b2c238299f49d3264554Jarkko Poyry 523c827367444ee418f129b2c238299f49d3264554Jarkko Poyry// Horizontal spacing between most items in the container, as well as after the 533c827367444ee418f129b2c238299f49d3264554Jarkko Poyry// last item or chevron (if visible). 54const int kItemSpacing = ToolbarView::kStandardSpacing; 55 56// Horizontal spacing before the chevron (if visible). 57const int kChevronSpacing = kItemSpacing - 2; 58 59// The maximum number of icons to show per row when in overflow mode (showing 60// icons in the application menu). 61// TODO(devlin): Compute the right number of icons to show, depending on the 62// menu width. 63#if defined(OS_LINUX) 64const int kIconsPerMenuRow = 8; // The menu on Linux is wider. 65#else 66const int kIconsPerMenuRow = 7; 67#endif 68 69// Returns the ExtensionAction for the given |extension|. 70ExtensionAction* GetExtensionAction(const Extension& extension, 71 Profile* profile) { 72 extensions::ExtensionActionManager* action_manager = 73 extensions::ExtensionActionManager::Get(profile); 74 ExtensionAction* action = action_manager->GetBrowserAction(extension); 75 return action ? action : action_manager->GetPageAction(extension); 76} 77 78// A version of MenuButton with almost empty insets to fit properly on the 79// toolbar. 80class ChevronMenuButton : public views::MenuButton { 81 public: 82 ChevronMenuButton(views::ButtonListener* listener, 83 const base::string16& text, 84 views::MenuButtonListener* menu_button_listener, 85 bool show_menu_marker) 86 : views::MenuButton(listener, 87 text, 88 menu_button_listener, 89 show_menu_marker) { 90 } 91 92 virtual ~ChevronMenuButton() {} 93 94 virtual scoped_ptr<views::LabelButtonBorder> CreateDefaultBorder() const 95 OVERRIDE { 96 // The chevron resource was designed to not have any insets. 97 scoped_ptr<views::LabelButtonBorder> border = 98 views::MenuButton::CreateDefaultBorder(); 99 border->set_insets(gfx::Insets()); 100 return border.Pass(); 101 } 102 103 private: 104 DISALLOW_COPY_AND_ASSIGN(ChevronMenuButton); 105}; 106 107} // namespace 108 109//////////////////////////////////////////////////////////////////////////////// 110// BrowserActionsContainer::DropPosition 111 112struct BrowserActionsContainer::DropPosition { 113 DropPosition(size_t row, size_t icon_in_row); 114 115 // The (0-indexed) row into which the action will be dropped. 116 size_t row; 117 118 // The (0-indexed) icon in the row before the action will be dropped. 119 size_t icon_in_row; 120}; 121 122BrowserActionsContainer::DropPosition::DropPosition( 123 size_t row, size_t icon_in_row) 124 : row(row), icon_in_row(icon_in_row) { 125} 126 127//////////////////////////////////////////////////////////////////////////////// 128// BrowserActionsContainer 129 130// static 131bool BrowserActionsContainer::disable_animations_during_testing_ = false; 132 133BrowserActionsContainer::BrowserActionsContainer( 134 Browser* browser, 135 View* owner_view, 136 BrowserActionsContainer* main_container) 137 : profile_(browser->profile()), 138 browser_(browser), 139 owner_view_(owner_view), 140 main_container_(main_container), 141 popup_owner_(NULL), 142 model_(NULL), 143 container_width_(0), 144 resize_area_(NULL), 145 chevron_(NULL), 146 overflow_menu_(NULL), 147 suppress_chevron_(false), 148 resize_amount_(0), 149 animation_target_size_(0), 150 task_factory_(this), 151 show_menu_task_factory_(this) { 152 set_id(VIEW_ID_BROWSER_ACTION_TOOLBAR); 153 154 model_ = extensions::ExtensionToolbarModel::Get(browser->profile()); 155 if (model_) 156 model_->AddObserver(this); 157 158 bool overflow_experiment = 159 extensions::FeatureSwitch::extension_action_redesign()->IsEnabled(); 160 DCHECK(!in_overflow_mode() || overflow_experiment); 161 162 if (!in_overflow_mode()) { 163 extension_keybinding_registry_.reset(new ExtensionKeybindingRegistryViews( 164 browser->profile(), 165 owner_view->GetFocusManager(), 166 extensions::ExtensionKeybindingRegistry::ALL_EXTENSIONS, 167 this)); 168 169 resize_animation_.reset(new gfx::SlideAnimation(this)); 170 resize_area_ = new views::ResizeArea(this); 171 AddChildView(resize_area_); 172 173 // 'Main' mode doesn't need a chevron overflow when overflow is shown inside 174 // the Chrome menu. 175 if (!overflow_experiment) { 176 chevron_ = new ChevronMenuButton(NULL, base::string16(), this, false); 177 chevron_->SetBorder(views::Border::NullBorder()); 178 chevron_->EnableCanvasFlippingForRTLUI(true); 179 chevron_->SetAccessibleName( 180 l10n_util::GetStringUTF16(IDS_ACCNAME_EXTENSIONS_CHEVRON)); 181 chevron_->SetVisible(false); 182 AddChildView(chevron_); 183 } 184 } 185} 186 187BrowserActionsContainer::~BrowserActionsContainer() { 188 FOR_EACH_OBSERVER(BrowserActionsContainerObserver, 189 observers_, 190 OnBrowserActionsContainerDestroyed()); 191 192 if (overflow_menu_) 193 overflow_menu_->set_observer(NULL); 194 if (model_) 195 model_->RemoveObserver(this); 196 StopShowFolderDropMenuTimer(); 197 HideActivePopup(); 198 DeleteBrowserActionViews(); 199} 200 201void BrowserActionsContainer::Init() { 202 LoadImages(); 203 204 // We wait to set the container width until now so that the chevron images 205 // will be loaded. The width calculation needs to know the chevron size. 206 if (model_ && model_->extensions_initialized()) 207 SetContainerWidth(); 208} 209 210BrowserActionView* BrowserActionsContainer::GetBrowserActionView( 211 ExtensionAction* action) { 212 for (BrowserActionViews::iterator i(browser_action_views_.begin()); 213 i != browser_action_views_.end(); ++i) { 214 if ((*i)->extension_action() == action) 215 return *i; 216 } 217 return NULL; 218} 219 220void BrowserActionsContainer::RefreshBrowserActionViews() { 221 for (size_t i = 0; i < browser_action_views_.size(); ++i) 222 browser_action_views_[i]->UpdateState(); 223} 224 225void BrowserActionsContainer::CreateBrowserActionViews() { 226 DCHECK(browser_action_views_.empty()); 227 if (!model_) 228 return; 229 230 const extensions::ExtensionList& toolbar_items = model_->toolbar_items(); 231 for (extensions::ExtensionList::const_iterator i(toolbar_items.begin()); 232 i != toolbar_items.end(); ++i) { 233 if (!ShouldDisplayBrowserAction(i->get())) 234 continue; 235 236 BrowserActionView* view = 237 new BrowserActionView(i->get(), 238 GetExtensionAction(*i->get(), profile_), 239 browser_, 240 this); 241 browser_action_views_.push_back(view); 242 AddChildView(view); 243 } 244} 245 246void BrowserActionsContainer::DeleteBrowserActionViews() { 247 HideActivePopup(); 248 if (overflow_menu_) 249 overflow_menu_->NotifyBrowserActionViewsDeleting(); 250 STLDeleteElements(&browser_action_views_); 251} 252 253size_t BrowserActionsContainer::VisibleBrowserActions() const { 254 size_t visible_actions = 0; 255 for (size_t i = 0; i < browser_action_views_.size(); ++i) { 256 if (browser_action_views_[i]->visible()) 257 ++visible_actions; 258 } 259 return visible_actions; 260} 261 262size_t BrowserActionsContainer::VisibleBrowserActionsAfterAnimation() const { 263 if (!animating()) 264 return VisibleBrowserActions(); 265 266 return WidthToIconCount(animation_target_size_); 267} 268 269void BrowserActionsContainer::ExecuteExtensionCommand( 270 const extensions::Extension* extension, 271 const extensions::Command& command) { 272 // Global commands are handled by the ExtensionCommandsGlobalRegistry 273 // instance. 274 DCHECK(!command.global()); 275 extension_keybinding_registry_->ExecuteCommand(extension->id(), 276 command.accelerator()); 277} 278 279bool BrowserActionsContainer::ShownInsideMenu() const { 280 return in_overflow_mode(); 281} 282 283void BrowserActionsContainer::OnBrowserActionViewDragDone() { 284 // We notify here as well as in OnPerformDrop because the dragged view is 285 // removed in OnPerformDrop, so it will never get its OnDragDone() call. 286 // TODO(devlin): we should see about fixing that. 287 FOR_EACH_OBSERVER(BrowserActionsContainerObserver, 288 observers_, 289 OnBrowserActionDragDone()); 290} 291 292views::View* BrowserActionsContainer::GetOverflowReferenceView() { 293 // We should only need an overflow reference when using the traditional 294 // chevron overflow. 295 DCHECK(chevron_); 296 return chevron_; 297} 298 299void BrowserActionsContainer::SetPopupOwner(BrowserActionView* popup_owner) { 300 // We should never be setting a popup owner when one already exists. 301 DCHECK(!popup_owner_ || !popup_owner); 302 popup_owner_ = popup_owner; 303} 304 305void BrowserActionsContainer::HideActivePopup() { 306 if (popup_owner_) 307 popup_owner_->view_controller()->HidePopup(); 308} 309 310void BrowserActionsContainer::AddObserver( 311 BrowserActionsContainerObserver* observer) { 312 observers_.AddObserver(observer); 313} 314 315void BrowserActionsContainer::RemoveObserver( 316 BrowserActionsContainerObserver* observer) { 317 observers_.RemoveObserver(observer); 318} 319 320gfx::Size BrowserActionsContainer::GetPreferredSize() const { 321 size_t icon_count = GetIconCount(); 322 323 // If there are no actions to show, or we are in overflow mode and the main 324 // container is already showing them all, then no further work is required. 325 if (icon_count == 0) 326 return gfx::Size(); 327 328 if (in_overflow_mode()) { 329 // When in overflow, y is multiline, so the pixel count is IconHeight() 330 // times the number of rows needed. 331 return gfx::Size( 332 IconCountToWidth(kIconsPerMenuRow, false), 333 (((icon_count - 1) / kIconsPerMenuRow) + 1) * IconHeight()); 334 } 335 336 // We calculate the size of the view by taking the current width and 337 // subtracting resize_amount_ (the latter represents how far the user is 338 // resizing the view or, if animating the snapping, how far to animate it). 339 // But we also clamp it to a minimum size and the maximum size, so that the 340 // container can never shrink too far or take up more space than it needs. 341 // In other words: MinimumNonemptyWidth() < width() - resize < ClampTo(MAX). 342 int preferred_width = std::min( 343 std::max(MinimumNonemptyWidth(), container_width_ - resize_amount_), 344 IconCountToWidth(-1, false)); 345 return gfx::Size(preferred_width, IconHeight()); 346} 347 348gfx::Size BrowserActionsContainer::GetMinimumSize() const { 349 int min_width = std::min(MinimumNonemptyWidth(), IconCountToWidth(-1, false)); 350 return gfx::Size(min_width, IconHeight()); 351} 352 353void BrowserActionsContainer::Layout() { 354 if (browser_action_views_.empty()) { 355 SetVisible(false); 356 return; 357 } 358 359 SetVisible(true); 360 if (resize_area_) 361 resize_area_->SetBounds(0, 0, kItemSpacing, height()); 362 363 // If the icons don't all fit, show the chevron (unless suppressed). 364 int max_x = GetPreferredSize().width(); 365 if ((IconCountToWidth(-1, false) > max_x) && !suppress_chevron_ && chevron_) { 366 chevron_->SetVisible(true); 367 gfx::Size chevron_size(chevron_->GetPreferredSize()); 368 max_x -= 369 ToolbarView::kStandardSpacing + chevron_size.width() + kChevronSpacing; 370 chevron_->SetBounds( 371 width() - ToolbarView::kStandardSpacing - chevron_size.width(), 372 0, 373 chevron_size.width(), 374 chevron_size.height()); 375 } else if (chevron_) { 376 chevron_->SetVisible(false); 377 } 378 379 // Now draw the icons for the browser actions in the available space. 380 int icon_width = IconWidth(false); 381 if (in_overflow_mode()) { 382 for (size_t i = 0; 383 i < main_container_->VisibleBrowserActionsAfterAnimation(); ++i) { 384 // Ensure that any browser actions shown in the main view are hidden in 385 // the overflow view. 386 browser_action_views_[i]->SetVisible(false); 387 } 388 389 for (size_t i = main_container_->VisibleBrowserActionsAfterAnimation(); 390 i < browser_action_views_.size(); ++i) { 391 BrowserActionView* view = browser_action_views_[i]; 392 size_t index = i - main_container_->VisibleBrowserActionsAfterAnimation(); 393 int row_index = static_cast<int>(index) / kIconsPerMenuRow; 394 int x = kItemSpacing + (index * IconWidth(true)) - 395 (row_index * IconWidth(true) * kIconsPerMenuRow); 396 gfx::Rect rect_bounds( 397 x, IconHeight() * row_index, icon_width, IconHeight()); 398 view->SetBoundsRect(rect_bounds); 399 view->SetVisible(true); 400 } 401 } else { 402 for (BrowserActionViews::const_iterator it = browser_action_views_.begin(); 403 it < browser_action_views_.end(); ++it) { 404 BrowserActionView* view = *it; 405 int x = ToolbarView::kStandardSpacing + 406 ((it - browser_action_views_.begin()) * IconWidth(true)); 407 view->SetVisible(x + icon_width <= max_x); 408 if (view->visible()) 409 view->SetBounds(x, 0, icon_width, IconHeight()); 410 } 411 } 412} 413 414bool BrowserActionsContainer::GetDropFormats( 415 int* formats, 416 std::set<OSExchangeData::CustomFormat>* custom_formats) { 417 return BrowserActionDragData::GetDropFormats(custom_formats); 418} 419 420bool BrowserActionsContainer::AreDropTypesRequired() { 421 return BrowserActionDragData::AreDropTypesRequired(); 422} 423 424bool BrowserActionsContainer::CanDrop(const OSExchangeData& data) { 425 return BrowserActionDragData::CanDrop(data, profile_); 426} 427 428void BrowserActionsContainer::OnDragEntered( 429 const ui::DropTargetEvent& event) { 430} 431 432int BrowserActionsContainer::OnDragUpdated( 433 const ui::DropTargetEvent& event) { 434 // First check if we are above the chevron (overflow) menu. 435 if (GetEventHandlerForPoint(event.location()) == chevron_) { 436 if (!show_menu_task_factory_.HasWeakPtrs() && !overflow_menu_) 437 StartShowFolderDropMenuTimer(); 438 return ui::DragDropTypes::DRAG_MOVE; 439 } 440 StopShowFolderDropMenuTimer(); 441 442 // Figure out where to display the indicator. This is a complex calculation: 443 444 // First, we figure out how much space is to the left of the icon area, so we 445 // can calculate the true offset into the icon area. The easiest way to do 446 // this is to just find where the first icon starts. 447 int width_before_icons = 448 browser_action_views_[GetFirstVisibleIconIndex()]->x(); 449 450 // If we're right-to-left, we flip the mirror the event.x() so that our 451 // calculations are consistent with left-to-right. 452 int offset_into_icon_area = 453 GetMirroredXInView(event.x()) - width_before_icons; 454 455 // Next, figure out what row we're on. This only matters for overflow mode, 456 // but the calculation is the same for both. 457 size_t row_index = event.y() / IconHeight(); 458 459 // Sanity check - we should never be on a different row in the main container. 460 DCHECK(in_overflow_mode() || row_index == 0); 461 462 // Next, we determine which icon to place the indicator in front of. We want 463 // to place the indicator in front of icon n when the cursor is between the 464 // midpoints of icons (n - 1) and n. To do this we take the offset into the 465 // icon area and transform it as follows: 466 // 467 // Real icon area: 468 // 0 a * b c 469 // | | | | 470 // |[IC|ON] [IC|ON] [IC|ON] 471 // We want to be before icon 0 for 0 < x <= a, icon 1 for a < x <= b, etc. 472 // Here the "*" represents the offset into the icon area, and since it's 473 // between a and b, we want to return "1". 474 // 475 // Transformed "icon area": 476 // 0 a * b c 477 // | | | | 478 // |[ICON] |[ICON] |[ICON] | 479 // If we shift both our offset and our divider points later by half an icon 480 // plus one spacing unit, then it becomes very easy to calculate how many 481 // divider points we've passed, because they're the multiples of "one icon 482 // plus padding". 483 int before_icon_unclamped = (offset_into_icon_area + (IconWidth(false) / 2) + 484 kItemSpacing) / IconWidth(true); 485 486 // We need to figure out how many icons are visible on the relevant row. 487 // In the main container, this will just be the visible actions. 488 int visible_icons_on_row = VisibleBrowserActionsAfterAnimation(); 489 if (in_overflow_mode()) { 490 // If this is the final row of the overflow, then this is the remainder of 491 // visible icons. Otherwise, it's a full row (kIconsPerRow). 492 visible_icons_on_row = 493 row_index == 494 static_cast<size_t>(visible_icons_on_row / kIconsPerMenuRow) ? 495 visible_icons_on_row % kIconsPerMenuRow : 496 kIconsPerMenuRow; 497 } 498 499 // Because the user can drag outside the container bounds, we need to clamp to 500 // the valid range. Note that the maximum allowable value is (num icons), not 501 // (num icons - 1), because we represent the indicator being past the last 502 // icon as being "before the (last + 1) icon". 503 size_t before_icon_in_row = 504 std::min(std::max(before_icon_unclamped, 0), visible_icons_on_row); 505 506 if (!drop_position_.get() || 507 !(drop_position_->row == row_index && 508 drop_position_->icon_in_row == before_icon_in_row)) { 509 drop_position_.reset(new DropPosition(row_index, before_icon_in_row)); 510 SchedulePaint(); 511 } 512 513 return ui::DragDropTypes::DRAG_MOVE; 514} 515 516void BrowserActionsContainer::OnDragExited() { 517 StopShowFolderDropMenuTimer(); 518 drop_position_.reset(); 519 SchedulePaint(); 520} 521 522int BrowserActionsContainer::OnPerformDrop( 523 const ui::DropTargetEvent& event) { 524 BrowserActionDragData data; 525 if (!data.Read(event.data())) 526 return ui::DragDropTypes::DRAG_NONE; 527 528 // Make sure we have the same view as we started with. 529 DCHECK_EQ(browser_action_views_[data.index()]->extension()->id(), 530 data.id()); 531 DCHECK(model_); 532 533 size_t i = 534 drop_position_->row * kIconsPerMenuRow + drop_position_->icon_in_row; 535 if (in_overflow_mode()) 536 i += GetFirstVisibleIconIndex(); 537 // |i| now points to the item to the right of the drop indicator*, which is 538 // correct when dragging an icon to the left. When dragging to the right, 539 // however, we want the icon being dragged to get the index of the item to 540 // the left of the drop indicator, so we subtract one. 541 // * Well, it can also point to the end, but not when dragging to the left. :) 542 if (i > data.index()) 543 --i; 544 545 if (profile_->IsOffTheRecord()) 546 i = model_->IncognitoIndexToOriginal(i); 547 548 model_->MoveExtensionIcon( 549 browser_action_views_[data.index()]->extension(), i); 550 551 OnDragExited(); // Perform clean up after dragging. 552 FOR_EACH_OBSERVER(BrowserActionsContainerObserver, 553 observers_, 554 OnBrowserActionDragDone()); 555 return ui::DragDropTypes::DRAG_MOVE; 556} 557 558void BrowserActionsContainer::GetAccessibleState( 559 ui::AXViewState* state) { 560 state->role = ui::AX_ROLE_GROUP; 561 state->name = l10n_util::GetStringUTF16(IDS_ACCNAME_EXTENSIONS); 562} 563 564void BrowserActionsContainer::OnMenuButtonClicked(views::View* source, 565 const gfx::Point& point) { 566 if (source == chevron_) { 567 overflow_menu_ = 568 new BrowserActionOverflowMenuController(this, 569 browser_, 570 chevron_, 571 browser_action_views_, 572 VisibleBrowserActions(), 573 false); 574 overflow_menu_->set_observer(this); 575 overflow_menu_->RunMenu(GetWidget()); 576 } 577} 578 579void BrowserActionsContainer::WriteDragDataForView(View* sender, 580 const gfx::Point& press_pt, 581 OSExchangeData* data) { 582 DCHECK(data); 583 584 for (size_t i = 0; i < browser_action_views_.size(); ++i) { 585 BrowserActionView* view = browser_action_views_[i]; 586 if (view == sender) { 587 // Set the dragging image for the icon. 588 gfx::ImageSkia badge(view->GetIconWithBadge()); 589 drag_utils::SetDragImageOnDataObject(badge, 590 press_pt.OffsetFromOrigin(), 591 data); 592 593 // Fill in the remaining info. 594 BrowserActionDragData drag_data(view->extension()->id(), i); 595 drag_data.Write(profile_, data); 596 break; 597 } 598 } 599} 600 601int BrowserActionsContainer::GetDragOperationsForView(View* sender, 602 const gfx::Point& p) { 603 return ui::DragDropTypes::DRAG_MOVE; 604} 605 606bool BrowserActionsContainer::CanStartDragForView(View* sender, 607 const gfx::Point& press_pt, 608 const gfx::Point& p) { 609 // We don't allow dragging while we're highlighting. 610 return !model_->is_highlighting(); 611} 612 613void BrowserActionsContainer::OnResize(int resize_amount, bool done_resizing) { 614 if (!done_resizing) { 615 resize_amount_ = resize_amount; 616 OnBrowserActionVisibilityChanged(); 617 return; 618 } 619 620 // Up until now we've only been modifying the resize_amount, but now it is 621 // time to set the container size to the size we have resized to, and then 622 // animate to the nearest icon count size if necessary (which may be 0). 623 int max_width = IconCountToWidth(-1, false); 624 container_width_ = 625 std::min(std::max(0, container_width_ - resize_amount), max_width); 626 627 // Save off the desired number of visible icons. We do this now instead of at 628 // the end of the animation so that even if the browser is shut down while 629 // animating, the right value will be restored on next run. 630 // NOTE: Don't save the icon count in incognito because there may be fewer 631 // icons in that mode. The result is that the container in a normal window is 632 // always at least as wide as in an incognito window. 633 int visible_icons = WidthToIconCount(container_width_); 634 if (!profile_->IsOffTheRecord()) 635 model_->SetVisibleIconCount(visible_icons); 636 637 Animate(gfx::Tween::EASE_OUT, visible_icons); 638} 639 640void BrowserActionsContainer::AnimationProgressed( 641 const gfx::Animation* animation) { 642 DCHECK_EQ(resize_animation_.get(), animation); 643 resize_amount_ = static_cast<int>(resize_animation_->GetCurrentValue() * 644 (container_width_ - animation_target_size_)); 645 OnBrowserActionVisibilityChanged(); 646} 647 648void BrowserActionsContainer::AnimationEnded(const gfx::Animation* animation) { 649 container_width_ = animation_target_size_; 650 animation_target_size_ = 0; 651 resize_amount_ = 0; 652 suppress_chevron_ = false; 653 OnBrowserActionVisibilityChanged(); 654 655 FOR_EACH_OBSERVER(BrowserActionsContainerObserver, 656 observers_, 657 OnBrowserActionsContainerAnimationEnded()); 658} 659 660void BrowserActionsContainer::NotifyMenuDeleted( 661 BrowserActionOverflowMenuController* controller) { 662 DCHECK_EQ(overflow_menu_, controller); 663 overflow_menu_ = NULL; 664} 665 666content::WebContents* BrowserActionsContainer::GetCurrentWebContents() { 667 return browser_->tab_strip_model()->GetActiveWebContents(); 668} 669 670extensions::ActiveTabPermissionGranter* 671 BrowserActionsContainer::GetActiveTabPermissionGranter() { 672 content::WebContents* web_contents = 673 browser_->tab_strip_model()->GetActiveWebContents(); 674 if (!web_contents) 675 return NULL; 676 return extensions::TabHelper::FromWebContents(web_contents)-> 677 active_tab_permission_granter(); 678} 679 680void BrowserActionsContainer::MoveBrowserAction(const std::string& extension_id, 681 size_t new_index) { 682 const Extension* extension = extensions::ExtensionRegistry::Get(profile_)-> 683 enabled_extensions().GetByID(extension_id); 684 model_->MoveExtensionIcon(extension, new_index); 685 SchedulePaint(); 686} 687 688size_t BrowserActionsContainer::GetFirstVisibleIconIndex() const { 689 return in_overflow_mode() ? model_->GetVisibleIconCount() : 0; 690} 691 692ExtensionPopup* BrowserActionsContainer::TestGetPopup() { 693 return popup_owner_ ? popup_owner_->view_controller()->popup() : NULL; 694} 695 696void BrowserActionsContainer::TestSetIconVisibilityCount(size_t icons) { 697 model_->SetVisibleIconCount(icons); 698 chevron_->SetVisible(icons < browser_action_views_.size()); 699 container_width_ = IconCountToWidth(icons, chevron_->visible()); 700 Layout(); 701 SchedulePaint(); 702} 703 704void BrowserActionsContainer::OnPaint(gfx::Canvas* canvas) { 705 // If the views haven't been initialized yet, wait for the next call to 706 // paint (one will be triggered by entering highlight mode). 707 if (model_->is_highlighting() && !browser_action_views_.empty()) { 708 views::Painter::PaintPainterAt( 709 canvas, highlight_painter_.get(), GetLocalBounds()); 710 } 711 712 // TODO(sky/glen): Instead of using a drop indicator, animate the icons while 713 // dragging (like we do for tab dragging). 714 if (drop_position_.get()) { 715 // The two-pixel width drop indicator. 716 static const int kDropIndicatorWidth = 2; 717 718 // Convert back to a pixel offset into the container. First find the X 719 // coordinate of the drop icon. 720 int drop_icon_x = browser_action_views_[GetFirstVisibleIconIndex()]->x() + 721 (drop_position_->icon_in_row * IconWidth(true)); 722 // Next, find the space before the drop icon. This will either be 723 // kItemSpacing or ToolbarView::kStandardSpacing, depending on whether this 724 // is the first icon. 725 // NOTE: Right now, these are the same. But let's do this right for if they 726 // ever aren't. 727 int space_before_drop_icon = drop_position_->icon_in_row == 0 ? 728 ToolbarView::kStandardSpacing : kItemSpacing; 729 // Now place the drop indicator halfway between this and the end of the 730 // previous icon. If there is an odd amount of available space between the 731 // two icons (or the icon and the address bar) after subtracting the drop 732 // indicator width, this calculation puts the extra pixel on the left side 733 // of the indicator, since when the indicator is between the address bar and 734 // the first icon, it looks better closer to the icon. 735 int drop_indicator_x = drop_icon_x - 736 ((space_before_drop_icon + kDropIndicatorWidth) / 2); 737 int row_height = IconHeight(); 738 int drop_indicator_y = row_height * drop_position_->row; 739 gfx::Rect indicator_bounds(drop_indicator_x, 740 drop_indicator_y, 741 kDropIndicatorWidth, 742 row_height); 743 indicator_bounds.set_x(GetMirroredXForRect(indicator_bounds)); 744 745 // Color of the drop indicator. 746 static const SkColor kDropIndicatorColor = SK_ColorBLACK; 747 canvas->FillRect(indicator_bounds, kDropIndicatorColor); 748 } 749} 750 751void BrowserActionsContainer::OnThemeChanged() { 752 LoadImages(); 753} 754 755void BrowserActionsContainer::ViewHierarchyChanged( 756 const ViewHierarchyChangedDetails& details) { 757 // No extensions (e.g., incognito). 758 if (!model_) 759 return; 760 761 if (details.is_add && details.child == this) { 762 // Initial toolbar button creation and placement in the widget hierarchy. 763 // We do this here instead of in the constructor because AddBrowserAction 764 // calls Layout on the Toolbar, which needs this object to be constructed 765 // before its Layout function is called. 766 CreateBrowserActionViews(); 767 } 768} 769 770// static 771int BrowserActionsContainer::IconWidth(bool include_padding) { 772 static bool initialized = false; 773 static int icon_width = 0; 774 if (!initialized) { 775 initialized = true; 776 icon_width = ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( 777 IDR_BROWSER_ACTION)->width(); 778 } 779 return icon_width + (include_padding ? kItemSpacing : 0); 780} 781 782// static 783int BrowserActionsContainer::IconHeight() { 784 static bool initialized = false; 785 static int icon_height = 0; 786 if (!initialized) { 787 initialized = true; 788 icon_height = ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( 789 IDR_BROWSER_ACTION)->height(); 790 } 791 return icon_height; 792} 793 794void BrowserActionsContainer::ToolbarExtensionAdded(const Extension* extension, 795 int index) { 796#if defined(DEBUG) 797 for (size_t i = 0; i < browser_action_views_.size(); ++i) { 798 DCHECK(browser_action_views_[i]->extension() != extension) << 799 "Asked to add a browser action view for an extension that already " 800 "exists."; 801 } 802#endif 803 CloseOverflowMenu(); 804 805 if (!ShouldDisplayBrowserAction(extension)) 806 return; 807 808 // Add the new browser action to the vector and the view hierarchy. 809 if (profile_->IsOffTheRecord()) 810 index = model_->OriginalIndexToIncognito(index); 811 BrowserActionView* view = 812 new BrowserActionView(extension, 813 GetExtensionAction(*extension, profile_), 814 browser_, 815 this); 816 browser_action_views_.insert(browser_action_views_.begin() + index, view); 817 AddChildViewAt(view, index); 818 819 // If we are still initializing the container, don't bother animating. 820 if (!model_->extensions_initialized()) 821 return; 822 823 // Enlarge the container if it was already at maximum size and we're not in 824 // the middle of upgrading. 825 if ((model_->GetVisibleIconCount() < 0) && 826 !extensions::ExtensionSystem::Get(profile_)->runtime_data()-> 827 IsBeingUpgraded(extension)) { 828 suppress_chevron_ = true; 829 Animate(gfx::Tween::LINEAR, browser_action_views_.size()); 830 } else { 831 // Just redraw the (possibly modified) visible icon set. 832 OnBrowserActionVisibilityChanged(); 833 } 834} 835 836void BrowserActionsContainer::ToolbarExtensionRemoved( 837 const Extension* extension) { 838 CloseOverflowMenu(); 839 840 size_t visible_actions = VisibleBrowserActionsAfterAnimation(); 841 for (BrowserActionViews::iterator i(browser_action_views_.begin()); 842 i != browser_action_views_.end(); ++i) { 843 if ((*i)->extension() == extension) { 844 delete *i; 845 browser_action_views_.erase(i); 846 847 // If the extension is being upgraded we don't want the bar to shrink 848 // because the icon is just going to get re-added to the same location. 849 if (extensions::ExtensionSystem::Get(profile_)->runtime_data()-> 850 IsBeingUpgraded(extension)) 851 return; 852 853 if (browser_action_views_.size() > visible_actions) { 854 // If we have more icons than we can show, then we must not be changing 855 // the container size (since we either removed an icon from the main 856 // area and one from the overflow list will have shifted in, or we 857 // removed an entry directly from the overflow list). 858 OnBrowserActionVisibilityChanged(); 859 } else { 860 // Either we went from overflow to no-overflow, or we shrunk the no- 861 // overflow container by 1. Either way the size changed, so animate. 862 if (chevron_) 863 chevron_->SetVisible(false); 864 Animate(gfx::Tween::EASE_OUT, browser_action_views_.size()); 865 } 866 return; // We have found the action to remove, bail out. 867 } 868 } 869} 870 871void BrowserActionsContainer::ToolbarExtensionMoved(const Extension* extension, 872 int index) { 873 if (!ShouldDisplayBrowserAction(extension)) 874 return; 875 876 if (profile_->IsOffTheRecord()) 877 index = model_->OriginalIndexToIncognito(index); 878 879 DCHECK(index >= 0 && index < static_cast<int>(browser_action_views_.size())); 880 881 BrowserActionViews::iterator iter = browser_action_views_.begin(); 882 int old_index = 0; 883 while (iter != browser_action_views_.end() && 884 (*iter)->extension() != extension) { 885 ++iter; 886 ++old_index; 887 } 888 889 DCHECK(iter != browser_action_views_.end()); 890 if (old_index == index) 891 return; // Already in place. 892 893 BrowserActionView* moved_view = *iter; 894 browser_action_views_.erase(iter); 895 browser_action_views_.insert( 896 browser_action_views_.begin() + index, moved_view); 897 898 Layout(); 899 SchedulePaint(); 900} 901 902void BrowserActionsContainer::ToolbarExtensionUpdated( 903 const Extension* extension) { 904 BrowserActionView* view = GetViewForExtension(extension); 905 if (view) 906 view->UpdateState(); 907} 908 909bool BrowserActionsContainer::ShowExtensionActionPopup( 910 const Extension* extension) { 911 return ShowPopupForExtension(extension, false, false); 912} 913 914void BrowserActionsContainer::ToolbarVisibleCountChanged() { 915 int old_container_width = container_width_; 916 SetContainerWidth(); 917 if (old_container_width != container_width_) 918 Animate(gfx::Tween::EASE_OUT, GetIconCount()); 919} 920 921void BrowserActionsContainer::ToolbarHighlightModeChanged( 922 bool is_highlighting) { 923 // The visual highlighting is done in OnPaint(). It's a bit of a pain that 924 // we delete and recreate everything here, but given everything else going on 925 // (the lack of highlight, n more extensions appearing, etc), it's not worth 926 // the extra complexity to create and insert only the new extensions. 927 DeleteBrowserActionViews(); 928 CreateBrowserActionViews(); 929 Animate(gfx::Tween::LINEAR, browser_action_views_.size()); 930} 931 932void BrowserActionsContainer::LoadImages() { 933 ui::ThemeProvider* tp = GetThemeProvider(); 934 if (!tp || !chevron_) 935 return; 936 937 chevron_->SetImage(views::Button::STATE_NORMAL, 938 *tp->GetImageSkiaNamed(IDR_BROWSER_ACTIONS_OVERFLOW)); 939 940 const int kImages[] = IMAGE_GRID(IDR_DEVELOPER_MODE_HIGHLIGHT); 941 highlight_painter_.reset(views::Painter::CreateImageGridPainter(kImages)); 942} 943 944void BrowserActionsContainer::OnBrowserActionVisibilityChanged() { 945 SetVisible(!browser_action_views_.empty()); 946 if (owner_view_) { 947 owner_view_->Layout(); 948 owner_view_->SchedulePaint(); 949 } 950} 951 952void BrowserActionsContainer::SetContainerWidth() { 953 int visible_actions = GetIconCount(); 954 if (chevron_) { 955 chevron_->SetVisible( 956 static_cast<size_t>(visible_actions) < model_->toolbar_items().size()); 957 } 958 container_width_ = 959 IconCountToWidth(visible_actions, chevron_ && chevron_->visible()); 960} 961 962void BrowserActionsContainer::CloseOverflowMenu() { 963 if (overflow_menu_) 964 overflow_menu_->CancelMenu(); 965} 966 967void BrowserActionsContainer::StopShowFolderDropMenuTimer() { 968 show_menu_task_factory_.InvalidateWeakPtrs(); 969} 970 971void BrowserActionsContainer::StartShowFolderDropMenuTimer() { 972 base::MessageLoop::current()->PostDelayedTask( 973 FROM_HERE, 974 base::Bind(&BrowserActionsContainer::ShowDropFolder, 975 show_menu_task_factory_.GetWeakPtr()), 976 base::TimeDelta::FromMilliseconds(views::GetMenuShowDelay())); 977} 978 979void BrowserActionsContainer::ShowDropFolder() { 980 DCHECK(!overflow_menu_); 981 drop_position_.reset(); 982 overflow_menu_ = 983 new BrowserActionOverflowMenuController(this, 984 browser_, 985 chevron_, 986 browser_action_views_, 987 VisibleBrowserActions(), 988 true); 989 overflow_menu_->set_observer(this); 990 overflow_menu_->RunMenu(GetWidget()); 991} 992 993int BrowserActionsContainer::IconCountToWidth(int icons, 994 bool display_chevron) const { 995 if (icons < 0) 996 icons = browser_action_views_.size(); 997 if ((icons == 0) && !display_chevron) 998 return ToolbarView::kStandardSpacing; 999 int icons_size = 1000 (icons == 0) ? 0 : ((icons * IconWidth(true)) - kItemSpacing); 1001 int chevron_size = chevron_ && display_chevron ? 1002 (kChevronSpacing + chevron_->GetPreferredSize().width()) : 0; 1003 return ToolbarView::kStandardSpacing + icons_size + chevron_size + 1004 ToolbarView::kStandardSpacing; 1005} 1006 1007size_t BrowserActionsContainer::WidthToIconCount(int pixels) const { 1008 // Check for widths large enough to show the entire icon set. 1009 if (pixels >= IconCountToWidth(-1, false)) 1010 return browser_action_views_.size(); 1011 1012 // We need to reserve space for the resize area, chevron, and the spacing on 1013 // either side of the chevron. 1014 int available_space = pixels - ToolbarView::kStandardSpacing - 1015 (chevron_ ? chevron_->GetPreferredSize().width() : 0) - 1016 kChevronSpacing - ToolbarView::kStandardSpacing; 1017 // Now we add an extra between-item padding value so the space can be divided 1018 // evenly by (size of icon with padding). 1019 return static_cast<size_t>( 1020 std::max(0, available_space + kItemSpacing) / IconWidth(true)); 1021} 1022 1023int BrowserActionsContainer::MinimumNonemptyWidth() const { 1024 if (!chevron_) 1025 return ToolbarView::kStandardSpacing; 1026 return (ToolbarView::kStandardSpacing * 2) + kChevronSpacing + 1027 chevron_->GetPreferredSize().width(); 1028} 1029 1030void BrowserActionsContainer::Animate(gfx::Tween::Type tween_type, 1031 size_t num_visible_icons) { 1032 int target_size = IconCountToWidth(num_visible_icons, 1033 num_visible_icons < browser_action_views_.size()); 1034 if (resize_animation_ && !disable_animations_during_testing_) { 1035 // Animate! We have to set the animation_target_size_ after calling Reset(), 1036 // because that could end up calling AnimationEnded which clears the value. 1037 resize_animation_->Reset(); 1038 resize_animation_->SetTweenType(tween_type); 1039 animation_target_size_ = target_size; 1040 resize_animation_->Show(); 1041 } else { 1042 animation_target_size_ = target_size; 1043 AnimationEnded(resize_animation_.get()); 1044 } 1045} 1046 1047bool BrowserActionsContainer::ShouldDisplayBrowserAction( 1048 const Extension* extension) const { 1049 // Only display incognito-enabled extensions while in incognito mode. 1050 return !profile_->IsOffTheRecord() || 1051 extensions::util::IsIncognitoEnabled(extension->id(), profile_); 1052} 1053 1054bool BrowserActionsContainer::ShowPopupForExtension( 1055 const extensions::Extension* extension, 1056 bool grant_tab_permissions, 1057 bool can_override) { 1058 // If the popup cannot override other views, then no other popups can be 1059 // showing, and it must be shown in the active widow with a visible toolbar. 1060 // TODO(justinlin): Remove toolbar check when http://crbug.com/308645 is 1061 // fixed. 1062 if (!can_override && 1063 (popup_owner_ || 1064 !browser_->window()->IsActive() || 1065 !browser_->window()->IsToolbarVisible())) { 1066 return false; 1067 } 1068 1069 BrowserActionView* view = GetViewForExtension(extension); 1070 return view ? view->view_controller()->ExecuteAction( 1071 ExtensionPopup::SHOW, grant_tab_permissions) : false; 1072} 1073 1074BrowserActionView* BrowserActionsContainer::GetViewForExtension( 1075 const Extension* extension) { 1076 for (BrowserActionViews::iterator view = browser_action_views_.begin(); 1077 view != browser_action_views_.end(); ++view) { 1078 if ((*view)->extension() == extension) 1079 return *view; 1080 } 1081 1082 return NULL; 1083} 1084 1085size_t BrowserActionsContainer::GetIconCount() const { 1086 if (!model_) 1087 return 0u; 1088 // Find the number of icons which could be displayed. 1089 size_t displayable_icon_count = 0u; 1090 const extensions::ExtensionList& extensions = model_->toolbar_items(); 1091 for (extensions::ExtensionList::const_iterator iter = extensions.begin(); 1092 iter != extensions.end(); ++iter) { 1093 displayable_icon_count += ShouldDisplayBrowserAction(*iter) ? 1u : 0u; 1094 } 1095 // Find the absolute value for the model's visible count. 1096 int model_size = model_->GetVisibleIconCount(); 1097 size_t absolute_model_size = 1098 model_size == -1 ? extensions.size() : model_size; 1099 1100 // The main container will try to show |model_size| icons, but reduce if there 1101 // aren't enough displayable icons to do so. 1102 size_t main_displayed = std::min(displayable_icon_count, absolute_model_size); 1103 // The overflow will display the extras, if any. 1104 return in_overflow_mode() ? 1105 displayable_icon_count - main_displayed : main_displayed; 1106} 1107