1 // Copyright (c) 2011 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 #ifndef CHROME_BROWSER_UI_VIEWS_BROWSER_ACTIONS_CONTAINER_H_ 6 #define CHROME_BROWSER_UI_VIEWS_BROWSER_ACTIONS_CONTAINER_H_ 7 #pragma once 8 9 #include <set> 10 #include <string> 11 #include <vector> 12 13 #include "base/compiler_specific.h" 14 #include "base/task.h" 15 #include "chrome/browser/extensions/extension_context_menu_model.h" 16 #include "chrome/browser/extensions/extension_toolbar_model.h" 17 #include "chrome/browser/extensions/image_loading_tracker.h" 18 #include "chrome/browser/ui/views/browser_bubble.h" 19 #include "chrome/browser/ui/views/extensions/browser_action_overflow_menu_controller.h" 20 #include "chrome/browser/ui/views/extensions/extension_popup.h" 21 #include "content/common/notification_observer.h" 22 #include "content/common/notification_registrar.h" 23 #include "ui/base/animation/animation_delegate.h" 24 #include "ui/base/animation/tween.h" 25 #include "views/controls/button/menu_button.h" 26 #include "views/controls/menu/view_menu_delegate.h" 27 #include "views/controls/resize_area.h" 28 #include "views/view.h" 29 30 class Browser; 31 class BrowserActionOverflowMenuController; 32 class BrowserActionsContainer; 33 class Extension; 34 class ExtensionAction; 35 class ExtensionPopup; 36 class PrefService; 37 class Profile; 38 39 namespace gfx { 40 class CanvasSkia; 41 } 42 43 namespace ui { 44 class SlideAnimation; 45 } 46 47 namespace views { 48 class Menu2; 49 } 50 51 //////////////////////////////////////////////////////////////////////////////// 52 // BrowserActionButton 53 54 // The BrowserActionButton is a specialization of the MenuButton class. 55 // It acts on a ExtensionAction, in this case a BrowserAction and handles 56 // loading the image for the button asynchronously on the file thread. 57 class BrowserActionButton : public views::MenuButton, 58 public views::ButtonListener, 59 public ImageLoadingTracker::Observer, 60 public NotificationObserver { 61 public: 62 BrowserActionButton(const Extension* extension, 63 BrowserActionsContainer* panel); 64 65 // Call this instead of delete. 66 void Destroy(); 67 68 ExtensionAction* browser_action() const { return browser_action_; } 69 const Extension* extension() { return extension_; } 70 71 // Called to update the display to match the browser action's state. 72 void UpdateState(); 73 74 // Returns the default icon, if any. 75 const SkBitmap& default_icon() const { return default_icon_; } 76 77 // Does this button's action have a popup? 78 virtual bool IsPopup(); 79 virtual GURL GetPopupUrl(); 80 81 // Overridden from views::ButtonListener: 82 virtual void ButtonPressed(views::Button* sender, 83 const views::Event& event) OVERRIDE; 84 85 // Overridden from ImageLoadingTracker. 86 virtual void OnImageLoaded(SkBitmap* image, 87 const ExtensionResource& resource, 88 int index) OVERRIDE; 89 90 // Overridden from NotificationObserver: 91 virtual void Observe(NotificationType type, 92 const NotificationSource& source, 93 const NotificationDetails& details) OVERRIDE; 94 95 // MenuButton behavior overrides. These methods all default to TextButton 96 // behavior unless this button is a popup. In that case, it uses MenuButton 97 // behavior. MenuButton has the notion of a child popup being shown where the 98 // button will stay in the pushed state until the "menu" (a popup in this 99 // case) is dismissed. 100 virtual bool Activate() OVERRIDE; 101 virtual bool OnMousePressed(const views::MouseEvent& event) OVERRIDE; 102 virtual void OnMouseReleased(const views::MouseEvent& event) OVERRIDE; 103 virtual void OnMouseExited(const views::MouseEvent& event) OVERRIDE; 104 virtual bool OnKeyReleased(const views::KeyEvent& event) OVERRIDE; 105 virtual void ShowContextMenu(const gfx::Point& p, 106 bool is_mouse_gesture) OVERRIDE; 107 108 // Notifications when to set button state to pushed/not pushed (for when the 109 // popup/context menu is hidden or shown by the container). 110 void SetButtonPushed(); 111 void SetButtonNotPushed(); 112 113 protected: 114 // Overridden from views::View: 115 virtual void ViewHierarchyChanged(bool is_add, 116 View* parent, 117 View* child) OVERRIDE; 118 119 private: 120 virtual ~BrowserActionButton(); 121 122 // The browser action this view represents. The ExtensionAction is not owned 123 // by this class. 124 ExtensionAction* browser_action_; 125 126 // The extension associated with the browser action we're displaying. 127 const Extension* extension_; 128 129 // The object that is waiting for the image loading to complete 130 // asynchronously. 131 ImageLoadingTracker tracker_; 132 133 // Whether we are currently showing/just finished showing a context menu. 134 bool showing_context_menu_; 135 136 // The default icon for our browser action. This might be non-empty if the 137 // browser action had a value for default_icon in the manifest. 138 SkBitmap default_icon_; 139 140 // The browser action shelf. 141 BrowserActionsContainer* panel_; 142 143 scoped_refptr<ExtensionContextMenuModel> context_menu_contents_; 144 scoped_ptr<views::Menu2> context_menu_menu_; 145 146 NotificationRegistrar registrar_; 147 148 friend class DeleteTask<BrowserActionButton>; 149 150 DISALLOW_COPY_AND_ASSIGN(BrowserActionButton); 151 }; 152 153 154 //////////////////////////////////////////////////////////////////////////////// 155 // BrowserActionView 156 // A single section in the browser action container. This contains the actual 157 // BrowserActionButton, as well as the logic to paint the badge. 158 159 class BrowserActionView : public views::View { 160 public: 161 BrowserActionView(const Extension* extension, BrowserActionsContainer* panel); 162 virtual ~BrowserActionView(); 163 164 BrowserActionButton* button() { return button_; } 165 166 // Allocates a canvas object on the heap and draws into it the icon for the 167 // view as well as the badge (if any). Caller is responsible for deleting the 168 // returned object. 169 gfx::Canvas* GetIconWithBadge(); 170 171 // Overridden from views::View: 172 virtual void Layout() OVERRIDE; 173 virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE; 174 175 protected: 176 // Overridden from views::View to paint the badge on top of children. 177 virtual void PaintChildren(gfx::Canvas* canvas) OVERRIDE; 178 179 private: 180 // The container for this view. 181 BrowserActionsContainer* panel_; 182 183 // The button this view contains. 184 BrowserActionButton* button_; 185 186 DISALLOW_COPY_AND_ASSIGN(BrowserActionView); 187 }; 188 189 //////////////////////////////////////////////////////////////////////////////// 190 // 191 // The BrowserActionsContainer is a container view, responsible for drawing the 192 // browser action icons (extensions that add icons to the toolbar). 193 // 194 // The container is placed flush against the omnibox and wrench menu, and its 195 // layout looks like: 196 // rI_I_IcCs 197 // Where the letters are as follows: 198 // r: An invisible resize area. This is ToolbarView::kStandardSpacing pixels 199 // wide and directly adjacent to the omnibox. 200 // I: An icon. This is as wide as the IDR_BROWSER_ACTION image. 201 // _: kItemSpacing pixels of empty space. 202 // c: kChevronSpacing pixels of empty space. Only present if C is present. 203 // C: An optional chevron, visible for overflow. As wide as the 204 // IDR_BROWSER_ACTIONS_OVERFLOW image. 205 // s: ToolbarView::kStandardSpacing pixels of empty space (before the wrench 206 // menu). 207 // The reason the container contains the trailing space "s", rather than having 208 // it be handled by the parent view, is so that when the chevron is invisible 209 // and the user starts dragging an icon around, we have the space to draw the 210 // ultimate drop indicator. (Otherwise, we'd be trying to draw it into the 211 // padding beyond our right edge, and it wouldn't appear.) 212 // 213 // The BrowserActionsContainer follows a few rules, in terms of user experience: 214 // 215 // 1) The container can never grow beyond the space needed to show all icons 216 // (hereby referred to as the max width). 217 // 2) The container can never shrink below the space needed to show just the 218 // initial padding and the chevron (ignoring the case where there are no icons 219 // to show, in which case the container won't be visible anyway). 220 // 3) The container snaps into place (to the pixel count that fits the visible 221 // icons) to make sure there is no wasted space at the edges of the container. 222 // 4) If the user adds or removes icons (read: installs/uninstalls browser 223 // actions) we grow and shrink the container as needed - but ONLY if the 224 // container was at max width to begin with. 225 // 5) If the container is NOT at max width (has an overflow menu), we respect 226 // that size when adding and removing icons and DON'T grow/shrink the container. 227 // This means that new icons (which always appear at the far right) will show up 228 // in the overflow menu. The install bubble for extensions points to the chevron 229 // menu in this case. 230 // 231 // Resizing the BrowserActionsContainer: 232 // 233 // The ResizeArea view sends OnResize messages to the BrowserActionsContainer 234 // class as the user drags it. This modifies the value for |resize_amount_|. 235 // That indicates to the container that a resize is in progress and is used to 236 // calculate the size in GetPreferredSize(), though that function never exceeds 237 // the defined minimum and maximum size of the container. 238 // 239 // When the user releases the mouse (ends the resize), we calculate a target 240 // size for the container (animation_target_size_), clamp that value to the 241 // containers min and max and then animate from the *current* position (that the 242 // user has dragged the view to) to the target size. 243 // 244 // Animating the BrowserActionsContainer: 245 // 246 // Animations are used when snapping the container to a value that fits all 247 // visible icons. This can be triggered when the user finishes resizing the 248 // container or when Browser Actions are added/removed. 249 // 250 // We always animate from the current width (container_width_) to the target 251 // size (animation_target_size_), using |resize_amount| to keep track of the 252 // animation progress. 253 // 254 // NOTE: When adding Browser Actions to a maximum width container (no overflow) 255 // we make sure to suppress the chevron menu if it wasn't visible. This is 256 // because we won't have enough space to show the new Browser Action until the 257 // animation ends and we don't want the chevron to flash into view while we are 258 // growing the container. 259 // 260 //////////////////////////////////////////////////////////////////////////////// 261 class BrowserActionsContainer 262 : public views::View, 263 public views::ViewMenuDelegate, 264 public views::DragController, 265 public views::ResizeArea::ResizeAreaDelegate, 266 public ui::AnimationDelegate, 267 public ExtensionToolbarModel::Observer, 268 public BrowserActionOverflowMenuController::Observer, 269 public ExtensionContextMenuModel::PopupDelegate, 270 public ExtensionPopup::Observer { 271 public: 272 BrowserActionsContainer(Browser* browser, views::View* owner_view); 273 virtual ~BrowserActionsContainer(); 274 275 static void RegisterUserPrefs(PrefService* prefs); 276 277 void Init(); 278 279 // Get the number of browser actions being displayed. 280 int num_browser_actions() const { return browser_action_views_.size(); } 281 282 // Whether we are performing resize animation on the container. 283 bool animating() const { return animation_target_size_ > 0; } 284 285 // Returns the chevron, if any. 286 const views::View* chevron() const { return chevron_; } 287 288 // Returns the profile this container is associated with. 289 Profile* profile() const { return profile_; } 290 291 // Returns the browser this container is associated with. 292 Browser* browser() const { return browser_; } 293 294 // Returns the current tab's ID, or -1 if there is no current tab. 295 int GetCurrentTabId() const; 296 297 // Get a particular browser action view. 298 BrowserActionView* GetBrowserActionViewAt(int index) { 299 return browser_action_views_[index]; 300 } 301 302 // Retrieve the BrowserActionView for |extension|. 303 BrowserActionView* GetBrowserActionView(ExtensionAction* action); 304 305 // Update the views to reflect the state of the browser action icons. 306 void RefreshBrowserActionViews(); 307 308 // Sets up the browser action view vector. 309 void CreateBrowserActionViews(); 310 311 // Delete all browser action views. 312 void DeleteBrowserActionViews(); 313 314 // Called when a browser action becomes visible/hidden. 315 void OnBrowserActionVisibilityChanged(); 316 317 // Returns how many browser actions are visible. 318 size_t VisibleBrowserActions() const; 319 320 // Called when the user clicks on the browser action icon. 321 void OnBrowserActionExecuted(BrowserActionButton* button, 322 bool inspect_with_devtools); 323 324 // Overridden from views::View: 325 virtual gfx::Size GetPreferredSize() OVERRIDE; 326 virtual void Layout() OVERRIDE; 327 virtual bool GetDropFormats(int* formats, 328 std::set<ui::OSExchangeData::CustomFormat>* custom_formats) OVERRIDE; 329 virtual bool AreDropTypesRequired() OVERRIDE; 330 virtual bool CanDrop(const ui::OSExchangeData& data) OVERRIDE; 331 virtual void OnDragEntered(const views::DropTargetEvent& event) OVERRIDE; 332 virtual int OnDragUpdated(const views::DropTargetEvent& event) OVERRIDE; 333 virtual void OnDragExited() OVERRIDE; 334 virtual int OnPerformDrop(const views::DropTargetEvent& event) OVERRIDE; 335 virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE; 336 337 // Overridden from views::ViewMenuDelegate: 338 virtual void RunMenu(View* source, const gfx::Point& pt) OVERRIDE; 339 340 // Overridden from views::DragController: 341 virtual void WriteDragDataForView(View* sender, 342 const gfx::Point& press_pt, 343 ui::OSExchangeData* data) OVERRIDE; 344 virtual int GetDragOperationsForView(View* sender, 345 const gfx::Point& p) OVERRIDE; 346 virtual bool CanStartDragForView(View* sender, 347 const gfx::Point& press_pt, 348 const gfx::Point& p) OVERRIDE; 349 350 // Overridden from ResizeArea::ResizeAreaDelegate: 351 virtual void OnResize(int resize_amount, bool done_resizing) OVERRIDE; 352 353 // Overridden from ui::AnimationDelegate: 354 virtual void AnimationProgressed(const ui::Animation* animation) OVERRIDE; 355 virtual void AnimationEnded(const ui::Animation* animation) OVERRIDE; 356 357 // Overridden from BrowserActionOverflowMenuController::Observer: 358 virtual void NotifyMenuDeleted( 359 BrowserActionOverflowMenuController* controller) OVERRIDE; 360 361 // Overridden from ExtensionContextMenuModel::PopupDelegate 362 virtual void InspectPopup(ExtensionAction* action) OVERRIDE; 363 364 // Overriden from ExtensionPopup::Delegate 365 virtual void ExtensionPopupIsClosing(ExtensionPopup* popup) OVERRIDE; 366 367 // Moves a browser action with |id| to |new_index|. 368 void MoveBrowserAction(const std::string& extension_id, size_t new_index); 369 370 // Hide the current popup. 371 void HidePopup(); 372 373 // Simulate a click on a browser action button. This should only be 374 // used by unit tests. 375 void TestExecuteBrowserAction(int index); 376 377 // Retrieve the current popup. This should only be used by unit tests. 378 ExtensionPopup* TestGetPopup() { return popup_; } 379 380 // Set how many icons the container should show. This should only be used by 381 // unit tests. 382 void TestSetIconVisibilityCount(size_t icons); 383 384 // During testing we can disable animations by setting this flag to true, 385 // so that the bar resizes instantly, instead of having to poll it while it 386 // animates to open/closed status. 387 static bool disable_animations_during_testing_; 388 389 protected: 390 // Overridden from views::View: 391 virtual void ViewHierarchyChanged(bool is_add, 392 views::View* parent, 393 views::View* child) OVERRIDE; 394 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE; 395 virtual void OnThemeChanged() OVERRIDE; 396 397 private: 398 friend class BrowserActionView; // So it can access IconHeight(). 399 friend class ShowFolderMenuTask; 400 401 typedef std::vector<BrowserActionView*> BrowserActionViews; 402 403 // Returns the width of an icon, optionally with its padding. 404 static int IconWidth(bool include_padding); 405 406 // Returns the height of an icon. 407 static int IconHeight(); 408 409 // ExtensionToolbarModel::Observer implementation. 410 virtual void BrowserActionAdded(const Extension* extension, int index); 411 virtual void BrowserActionRemoved(const Extension* extension); 412 virtual void BrowserActionMoved(const Extension* extension, int index); 413 virtual void ModelLoaded(); 414 415 void LoadImages(); 416 417 // Sets the initial container width. 418 void SetContainerWidth(); 419 420 // Closes the overflow menu if open. 421 void CloseOverflowMenu(); 422 423 // Cancels the timer for showing the drop down menu. 424 void StopShowFolderDropMenuTimer(); 425 426 // Show the drop down folder after a slight delay. 427 void StartShowFolderDropMenuTimer(); 428 429 // Show the overflow menu. 430 void ShowDropFolder(); 431 432 // Sets the drop indicator position (and schedules paint if the position has 433 // changed). 434 void SetDropIndicator(int x_pos); 435 436 // Given a number of |icons| and whether to |display_chevron|, returns the 437 // amount of pixels needed to draw the entire container. For convenience, 438 // callers can set |icons| to -1 to mean "all icons". 439 int IconCountToWidth(int icons, bool display_chevron) const; 440 441 // Given a pixel width, returns the number of icons that fit. (This 442 // automatically determines whether a chevron will be needed and includes it 443 // in the calculation.) 444 size_t WidthToIconCount(int pixels) const; 445 446 // Returns the absolute minimum size you can shrink the container down to and 447 // still show it. This assumes a visible chevron because the only way we 448 // would not have a chevron when shrinking down this far is if there were no 449 // icons, in which case the container wouldn't be shown at all. 450 int ContainerMinSize() const; 451 452 // Animate to the target size (unless testing, in which case we go straight to 453 // the target size). This also saves the target number of visible icons in 454 // the pref if we're not incognito. 455 void SaveDesiredSizeAndAnimate(ui::Tween::Type type, 456 size_t num_visible_icons); 457 458 // Returns true if this extension should be shown in this toolbar. This can 459 // return false if we are in an incognito window and the extension is disabled 460 // for incognito. 461 bool ShouldDisplayBrowserAction(const Extension* extension); 462 463 // The vector of browser actions (icons/image buttons for each action). Note 464 // that not every BrowserAction in the ToolbarModel will necessarily be in 465 // this collection. Some extensions may be disabled in incognito windows. 466 BrowserActionViews browser_action_views_; 467 468 Profile* profile_; 469 470 // The Browser object the container is associated with. 471 Browser* browser_; 472 473 // The view that owns us. 474 views::View* owner_view_; 475 476 // The current popup and the button it came from. NULL if no popup. 477 ExtensionPopup* popup_; 478 479 // The button that triggered the current popup (just a reference to a button 480 // from browser_action_views_). 481 BrowserActionButton* popup_button_; 482 483 // The model that tracks the order of the toolbar icons. 484 ExtensionToolbarModel* model_; 485 486 // The current width of the container. 487 int container_width_; 488 489 // The resize area for the container. 490 views::ResizeArea* resize_area_; 491 492 // The chevron for accessing the overflow items. 493 views::MenuButton* chevron_; 494 495 // The menu to show for the overflow button (chevron). This class manages its 496 // own lifetime so that it can stay alive during drag and drop operations. 497 BrowserActionOverflowMenuController* overflow_menu_; 498 499 // The animation that happens when the container snaps to place. 500 scoped_ptr<ui::SlideAnimation> resize_animation_; 501 502 // Don't show the chevron while animating. 503 bool suppress_chevron_; 504 505 // This is used while the user is resizing (and when the animations are in 506 // progress) to know how wide the delta is between the current state and what 507 // we should draw. 508 int resize_amount_; 509 510 // Keeps track of the absolute pixel width the container should have when we 511 // are done animating. 512 int animation_target_size_; 513 514 // The x position for where to draw the drop indicator. -1 if no indicator. 515 int drop_indicator_position_; 516 517 ScopedRunnableMethodFactory<BrowserActionsContainer> task_factory_; 518 519 // Handles delayed showing of the overflow menu when hovering. 520 ScopedRunnableMethodFactory<BrowserActionsContainer> show_menu_task_factory_; 521 522 DISALLOW_COPY_AND_ASSIGN(BrowserActionsContainer); 523 }; 524 525 #endif // CHROME_BROWSER_UI_VIEWS_BROWSER_ACTIONS_CONTAINER_H_ 526