Home | History | Annotate | Download | only in gtk
      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/gtk/browser_actions_toolbar_gtk.h"
      6 
      7 #include <gtk/gtk.h>
      8 
      9 #include <algorithm>
     10 #include <vector>
     11 
     12 #include "base/bind.h"
     13 #include "base/i18n/rtl.h"
     14 #include "base/message_loop/message_loop.h"
     15 #include "base/strings/utf_string_conversions.h"
     16 #include "chrome/browser/chrome_notification_types.h"
     17 #include "chrome/browser/extensions/api/commands/command_service.h"
     18 #include "chrome/browser/extensions/extension_action.h"
     19 #include "chrome/browser/extensions/extension_action_icon_factory.h"
     20 #include "chrome/browser/extensions/extension_action_manager.h"
     21 #include "chrome/browser/extensions/extension_context_menu_model.h"
     22 #include "chrome/browser/extensions/extension_service.h"
     23 #include "chrome/browser/extensions/extension_system.h"
     24 #include "chrome/browser/profiles/profile.h"
     25 #include "chrome/browser/sessions/session_tab_helper.h"
     26 #include "chrome/browser/ui/browser.h"
     27 #include "chrome/browser/ui/gtk/browser_window_gtk.h"
     28 #include "chrome/browser/ui/gtk/custom_button.h"
     29 #include "chrome/browser/ui/gtk/extensions/extension_popup_gtk.h"
     30 #include "chrome/browser/ui/gtk/gtk_chrome_button.h"
     31 #include "chrome/browser/ui/gtk/gtk_chrome_shrinkable_hbox.h"
     32 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
     33 #include "chrome/browser/ui/gtk/gtk_util.h"
     34 #include "chrome/browser/ui/gtk/hover_controller_gtk.h"
     35 #include "chrome/browser/ui/gtk/menu_gtk.h"
     36 #include "chrome/browser/ui/gtk/view_id_util.h"
     37 #include "chrome/browser/ui/tabs/tab_strip_model.h"
     38 #include "chrome/common/extensions/extension.h"
     39 #include "chrome/common/extensions/extension_manifest_constants.h"
     40 #include "content/public/browser/notification_details.h"
     41 #include "content/public/browser/notification_source.h"
     42 #include "grit/theme_resources.h"
     43 #include "grit/ui_resources.h"
     44 #include "ui/base/accelerators/platform_accelerator_gtk.h"
     45 #include "ui/base/gtk/gtk_compat.h"
     46 #include "ui/base/resource/resource_bundle.h"
     47 #include "ui/gfx/canvas_skia_paint.h"
     48 #include "ui/gfx/gtk_util.h"
     49 #include "ui/gfx/image/image.h"
     50 #include "ui/gfx/image/image_skia_operations.h"
     51 
     52 using extensions::Extension;
     53 using extensions::ExtensionActionManager;
     54 
     55 namespace {
     56 
     57 // The width of the browser action buttons.
     58 const int kButtonWidth = 27;
     59 
     60 // The padding between browser action buttons.
     61 const int kButtonPadding = 4;
     62 
     63 // The padding to the right of the browser action buttons (between the buttons
     64 // and chevron if they are both showing).
     65 const int kButtonChevronPadding = 2;
     66 
     67 // The padding to the left, top and bottom of the browser actions toolbar
     68 // separator.
     69 const int kSeparatorPadding = 2;
     70 
     71 // Width of the invisible gripper for resizing the toolbar.
     72 const int kResizeGripperWidth = 4;
     73 
     74 const char kDragTarget[] = "application/x-chrome-browseraction";
     75 
     76 GtkTargetEntry GetDragTargetEntry() {
     77   GtkTargetEntry drag_target;
     78   drag_target.target = const_cast<char*>(kDragTarget);
     79   drag_target.flags = GTK_TARGET_SAME_APP;
     80   drag_target.info = 0;
     81   return drag_target;
     82 }
     83 
     84 // The minimum width in pixels of the button hbox if |icon_count| icons are
     85 // showing.
     86 gint WidthForIconCount(gint icon_count) {
     87   return std::max((kButtonWidth + kButtonPadding) * icon_count - kButtonPadding,
     88                   0);
     89 }
     90 
     91 }  // namespace
     92 
     93 using ui::SimpleMenuModel;
     94 
     95 class BrowserActionButton : public content::NotificationObserver,
     96                             public ExtensionActionIconFactory::Observer,
     97                             public ExtensionContextMenuModel::PopupDelegate,
     98                             public MenuGtk::Delegate {
     99  public:
    100   BrowserActionButton(BrowserActionsToolbarGtk* toolbar,
    101                       const Extension* extension,
    102                       GtkThemeService* theme_provider)
    103       : toolbar_(toolbar),
    104         extension_(extension),
    105         image_(NULL),
    106         icon_factory_(toolbar->browser()->profile(), extension,
    107                       browser_action(), this),
    108         accel_group_(NULL) {
    109     button_.reset(new CustomDrawButton(
    110         theme_provider,
    111         IDR_BROWSER_ACTION,
    112         IDR_BROWSER_ACTION_P,
    113         IDR_BROWSER_ACTION_H,
    114         0,
    115         NULL));
    116     gtk_widget_set_size_request(button(), kButtonWidth, kButtonWidth);
    117     alignment_.Own(gtk_alignment_new(0, 0, 1, 1));
    118     gtk_container_add(GTK_CONTAINER(alignment_.get()), button());
    119     gtk_widget_show(button());
    120 
    121     DCHECK(browser_action());
    122 
    123     UpdateState();
    124 
    125     signals_.Connect(button(), "button-press-event",
    126                      G_CALLBACK(OnButtonPress), this);
    127     signals_.Connect(button(), "clicked",
    128                      G_CALLBACK(OnClicked), this);
    129     signals_.Connect(button(), "drag-begin",
    130                      G_CALLBACK(OnDragBegin), this);
    131     signals_.ConnectAfter(widget(), "expose-event",
    132                      G_CALLBACK(OnExposeEvent), this);
    133     if (toolbar_->browser()->window()) {
    134       // If the window exists already, then the browser action button has been
    135       // recreated after the window was created, for example when the extension
    136       // is reloaded.
    137       ConnectBrowserActionPopupAccelerator();
    138     } else {
    139       // Window doesn't exist yet, wait for it.
    140       signals_.Connect(toolbar->widget(), "realize",
    141                        G_CALLBACK(OnRealize), this);
    142     }
    143 
    144     registrar_.Add(
    145         this, chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_UPDATED,
    146         content::Source<ExtensionAction>(browser_action()));
    147     registrar_.Add(
    148         this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
    149         content::Source<Profile>(
    150             toolbar->browser()->profile()->GetOriginalProfile()));
    151     registrar_.Add(
    152         this, chrome::NOTIFICATION_EXTENSION_COMMAND_ADDED,
    153         content::Source<Profile>(
    154         toolbar->browser()->profile()->GetOriginalProfile()));
    155     registrar_.Add(
    156         this, chrome::NOTIFICATION_EXTENSION_COMMAND_REMOVED,
    157         content::Source<Profile>(
    158         toolbar->browser()->profile()->GetOriginalProfile()));
    159   }
    160 
    161   virtual ~BrowserActionButton() {
    162     DisconnectBrowserActionPopupAccelerator();
    163 
    164     alignment_.Destroy();
    165   }
    166 
    167   GtkWidget* button() { return button_->widget(); }
    168 
    169   GtkWidget* widget() { return alignment_.get(); }
    170 
    171   const Extension* extension() { return extension_; }
    172 
    173   // NotificationObserver implementation.
    174   virtual void Observe(int type,
    175                        const content::NotificationSource& source,
    176                        const content::NotificationDetails& details) OVERRIDE {
    177     switch (type) {
    178      case chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_UPDATED:
    179       UpdateState();
    180       break;
    181      case chrome::NOTIFICATION_EXTENSION_UNLOADED:
    182      case chrome::NOTIFICATION_WINDOW_CLOSED:
    183       DisconnectBrowserActionPopupAccelerator();
    184       break;
    185      case chrome::NOTIFICATION_EXTENSION_COMMAND_ADDED:
    186      case chrome::NOTIFICATION_EXTENSION_COMMAND_REMOVED: {
    187       std::pair<const std::string, const std::string>* payload =
    188           content::Details<std::pair<const std::string, const std::string> >(
    189               details).ptr();
    190       if (extension_->id() == payload->first &&
    191           payload->second ==
    192               extension_manifest_values::kBrowserActionCommandEvent) {
    193         if (type == chrome::NOTIFICATION_EXTENSION_COMMAND_ADDED)
    194           ConnectBrowserActionPopupAccelerator();
    195         else
    196           DisconnectBrowserActionPopupAccelerator();
    197       }
    198       break;
    199      }
    200      default:
    201       NOTREACHED();
    202       break;
    203     }
    204   }
    205 
    206   // ExtensionActionIconFactory::Observer implementation.
    207   virtual void OnIconUpdated() OVERRIDE {
    208     UpdateState();
    209   }
    210 
    211   // Updates the button based on the latest state from the associated
    212   // browser action.
    213   void UpdateState() {
    214     int tab_id = toolbar_->GetCurrentTabId();
    215     if (tab_id < 0)
    216       return;
    217 
    218     std::string tooltip = browser_action()->GetTitle(tab_id);
    219     if (tooltip.empty())
    220       gtk_widget_set_has_tooltip(button(), FALSE);
    221     else
    222       gtk_widget_set_tooltip_text(button(), tooltip.c_str());
    223 
    224     enabled_ = browser_action()->GetIsVisible(tab_id);
    225     if (!enabled_)
    226       button_->SetPaintOverride(GTK_STATE_INSENSITIVE);
    227     else
    228       button_->UnsetPaintOverride();
    229 
    230     gfx::Image image = icon_factory_.GetIcon(tab_id);
    231     if (!image.IsEmpty()) {
    232       if (enabled_) {
    233         SetImage(image);
    234       } else {
    235         SetImage(gfx::Image(gfx::ImageSkiaOperations::CreateTransparentImage(
    236             image.AsImageSkia(), .25)));
    237       }
    238     }
    239 
    240     gtk_widget_queue_draw(button());
    241   }
    242 
    243   gfx::Image GetIcon() {
    244     return icon_factory_.GetIcon(toolbar_->GetCurrentTabId());
    245   }
    246 
    247   MenuGtk* GetContextMenu() {
    248     if (!extension_->ShowConfigureContextMenus())
    249       return NULL;
    250 
    251     context_menu_model_ =
    252         new ExtensionContextMenuModel(extension_, toolbar_->browser(), this);
    253     context_menu_.reset(
    254         new MenuGtk(this, context_menu_model_.get()));
    255     return context_menu_.get();
    256   }
    257 
    258  private:
    259   // Activate the browser action.
    260   void Activate(GtkWidget* widget) {
    261     ExtensionToolbarModel* model = toolbar_->model();
    262     const Extension* extension = extension_;
    263     Browser* browser = toolbar_->browser();
    264     GURL popup_url;
    265 
    266     switch (model->ExecuteBrowserAction(extension, browser, &popup_url)) {
    267       case ExtensionToolbarModel::ACTION_NONE:
    268         break;
    269       case ExtensionToolbarModel::ACTION_SHOW_POPUP:
    270         ExtensionPopupGtk::Show(popup_url, browser, widget,
    271                                 ExtensionPopupGtk::SHOW);
    272         break;
    273     }
    274   }
    275 
    276   // MenuGtk::Delegate implementation.
    277   virtual void StoppedShowing() OVERRIDE {
    278     if (enabled_)
    279       button_->UnsetPaintOverride();
    280     else
    281       button_->SetPaintOverride(GTK_STATE_INSENSITIVE);
    282 
    283     // If the context menu was showing for the overflow menu, re-assert the
    284     // grab that was shadowed.
    285     if (toolbar_->overflow_menu_.get())
    286       gtk_util::GrabAllInput(toolbar_->overflow_menu_->widget());
    287   }
    288 
    289   virtual void CommandWillBeExecuted() OVERRIDE {
    290     // If the context menu was showing for the overflow menu, and a command
    291     // is executed, then stop showing the overflow menu.
    292     if (toolbar_->overflow_menu_.get())
    293       toolbar_->overflow_menu_->Cancel();
    294   }
    295 
    296   // ExtensionContextMenuModel::PopupDelegate implementation.
    297   virtual void InspectPopup(ExtensionAction* action) OVERRIDE {
    298     GURL popup_url = action->GetPopupUrl(toolbar_->GetCurrentTabId());
    299     ExtensionPopupGtk::Show(popup_url, toolbar_->browser(), widget(),
    300                             ExtensionPopupGtk::SHOW_AND_INSPECT);
    301   }
    302 
    303   void SetImage(const gfx::Image& image) {
    304     if (!image_) {
    305       image_ = gtk_image_new_from_pixbuf(image.ToGdkPixbuf());
    306       gtk_button_set_image(GTK_BUTTON(button()), image_);
    307     } else {
    308       gtk_image_set_from_pixbuf(GTK_IMAGE(image_), image.ToGdkPixbuf());
    309     }
    310   }
    311 
    312   static gboolean OnButtonPress(GtkWidget* widget,
    313                                 GdkEventButton* event,
    314                                 BrowserActionButton* button) {
    315     if (event->button != 3)
    316       return FALSE;
    317 
    318     MenuGtk* menu = button->GetContextMenu();
    319     if (!menu)
    320       return FALSE;
    321 
    322     button->button_->SetPaintOverride(GTK_STATE_ACTIVE);
    323     menu->PopupForWidget(widget, event->button, event->time);
    324 
    325     return TRUE;
    326   }
    327 
    328   static void OnClicked(GtkWidget* widget, BrowserActionButton* button) {
    329     if (button->enabled_)
    330       button->Activate(widget);
    331   }
    332 
    333   static gboolean OnExposeEvent(GtkWidget* widget,
    334                                 GdkEventExpose* event,
    335                                 BrowserActionButton* button) {
    336     int tab_id = button->toolbar_->GetCurrentTabId();
    337     if (tab_id < 0)
    338       return FALSE;
    339 
    340     ExtensionAction* action = button->browser_action();
    341     if (action->GetBadgeText(tab_id).empty())
    342       return FALSE;
    343 
    344     gfx::CanvasSkiaPaint canvas(event, false);
    345     GtkAllocation allocation;
    346     gtk_widget_get_allocation(widget, &allocation);
    347     action->PaintBadge(&canvas, gfx::Rect(allocation), tab_id);
    348     return FALSE;
    349   }
    350 
    351   static void OnDragBegin(GtkWidget* widget,
    352                           GdkDragContext* drag_context,
    353                           BrowserActionButton* button) {
    354     // Simply pass along the notification to the toolbar. The point of this
    355     // function is to tell the toolbar which BrowserActionButton initiated the
    356     // drag.
    357     button->toolbar_->DragStarted(button, drag_context);
    358   }
    359 
    360   // The accelerator handler for when the shortcuts to open the popup is struck.
    361   static gboolean OnGtkAccelerator(GtkAccelGroup* accel_group,
    362                                    GObject* acceleratable,
    363                                    guint keyval,
    364                                    GdkModifierType modifier,
    365                                    BrowserActionButton* button) {
    366     // Open the popup for this extension.
    367     GtkWidget* anchor = button->widget();
    368     // The anchor might be in the overflow menu. Then we point to the chevron.
    369     if (!gtk_widget_get_visible(anchor))
    370       anchor = button->toolbar_->chevron();
    371     button->Activate(anchor);
    372     return TRUE;
    373   }
    374 
    375   // The handler for when the browser action is realized. |user_data| contains a
    376   // pointer to the BrowserAction shown.
    377   static void OnRealize(GtkWidget* widget, void* user_data) {
    378     BrowserActionButton* button = static_cast<BrowserActionButton*>(user_data);
    379     button->ConnectBrowserActionPopupAccelerator();
    380   }
    381 
    382   // Connect the accelerator for the browser action popup.
    383   void ConnectBrowserActionPopupAccelerator() {
    384     extensions::CommandService* command_service =
    385         extensions::CommandService::Get(toolbar_->browser()->profile());
    386     extensions::Command command;
    387     if (command_service->GetBrowserActionCommand(extension_->id(),
    388         extensions::CommandService::ACTIVE_ONLY,
    389         &command,
    390         NULL)) {
    391       // Found the browser action shortcut command, register it.
    392       keybinding_ = command.accelerator();
    393 
    394       gfx::NativeWindow window =
    395           toolbar_->browser()->window()->GetNativeWindow();
    396       accel_group_ = gtk_accel_group_new();
    397       gtk_window_add_accel_group(window, accel_group_);
    398 
    399       gtk_accel_group_connect(
    400           accel_group_,
    401           ui::GetGdkKeyCodeForAccelerator(keybinding_),
    402           ui::GetGdkModifierForAccelerator(keybinding_),
    403           GtkAccelFlags(0),
    404           g_cclosure_new(G_CALLBACK(OnGtkAccelerator), this, NULL));
    405 
    406       // Since we've added an accelerator, we'll need to unregister it before
    407       // the window is closed, so we listen for the window being closed.
    408       registrar_.Add(this,
    409                      chrome::NOTIFICATION_WINDOW_CLOSED,
    410                      content::Source<GtkWindow>(window));
    411     }
    412   }
    413 
    414   // Disconnect the accelerator for the browser action popup and delete clean up
    415   // the accelerator group registration.
    416   void DisconnectBrowserActionPopupAccelerator() {
    417     if (accel_group_) {
    418       gfx::NativeWindow window =
    419           toolbar_->browser()->window()->GetNativeWindow();
    420       gtk_accel_group_disconnect_key(
    421           accel_group_,
    422           ui::GetGdkKeyCodeForAccelerator(keybinding_),
    423           GetGdkModifierForAccelerator(keybinding_));
    424       gtk_window_remove_accel_group(window, accel_group_);
    425       g_object_unref(accel_group_);
    426       accel_group_ = NULL;
    427       keybinding_ = ui::Accelerator();
    428 
    429       // We've removed the accelerator, so no need to listen to this anymore.
    430       registrar_.Remove(this,
    431                         chrome::NOTIFICATION_WINDOW_CLOSED,
    432                         content::Source<GtkWindow>(window));
    433     }
    434   }
    435 
    436   ExtensionAction* browser_action() const {
    437     return ExtensionActionManager::Get(toolbar_->browser()->profile())->
    438         GetBrowserAction(*extension_);
    439   }
    440 
    441   // The toolbar containing this button.
    442   BrowserActionsToolbarGtk* toolbar_;
    443 
    444   // The extension that contains this browser action.
    445   const Extension* extension_;
    446 
    447   // The button for this browser action.
    448   scoped_ptr<CustomDrawButton> button_;
    449 
    450   // Whether the browser action is enabled (equivalent to whether a page action
    451   // is visible).
    452   bool enabled_;
    453 
    454   // The top level widget (parent of |button_|).
    455   ui::OwnedWidgetGtk alignment_;
    456 
    457   // The one image subwidget in |button_|. We keep this out so we don't alter
    458   // the widget hierarchy while changing the button image because changing the
    459   // GTK widget hierarchy invalidates all tooltips and several popular
    460   // extensions change browser action icon in a loop.
    461   GtkWidget* image_;
    462 
    463   // The object that will be used to get the browser action icon for us.
    464   // It may load the icon asynchronously (in which case the initial icon
    465   // returned by the factory will be transparent), so we have to observe it for
    466   // updates to the icon.
    467   ExtensionActionIconFactory icon_factory_;
    468 
    469   // Same as |default_icon_|, but stored as SkBitmap.
    470   SkBitmap default_skbitmap_;
    471 
    472   ui::GtkSignalRegistrar signals_;
    473   content::NotificationRegistrar registrar_;
    474 
    475   // The accelerator group used to handle accelerators, owned by this object.
    476   GtkAccelGroup* accel_group_;
    477 
    478   // The keybinding accelerator registered to show the browser action popup.
    479   ui::Accelerator keybinding_;
    480 
    481   // The context menu view and model for this extension action.
    482   scoped_ptr<MenuGtk> context_menu_;
    483   scoped_refptr<ExtensionContextMenuModel> context_menu_model_;
    484 
    485   friend class BrowserActionsToolbarGtk;
    486 };
    487 
    488 // BrowserActionsToolbarGtk ----------------------------------------------------
    489 
    490 BrowserActionsToolbarGtk::BrowserActionsToolbarGtk(Browser* browser)
    491     : browser_(browser),
    492       profile_(browser->profile()),
    493       theme_service_(GtkThemeService::GetFrom(browser->profile())),
    494       model_(NULL),
    495       hbox_(gtk_hbox_new(FALSE, 0)),
    496       button_hbox_(gtk_chrome_shrinkable_hbox_new(TRUE, FALSE, kButtonPadding)),
    497       drag_button_(NULL),
    498       drop_index_(-1),
    499       resize_animation_(this),
    500       desired_width_(0),
    501       start_width_(0),
    502       weak_factory_(this) {
    503   ExtensionService* extension_service =
    504       extensions::ExtensionSystem::Get(profile_)->extension_service();
    505   if (!extension_service)
    506     return;
    507 
    508   overflow_button_.reset(new CustomDrawButton(
    509       theme_service_,
    510       IDR_BROWSER_ACTIONS_OVERFLOW,
    511       IDR_BROWSER_ACTIONS_OVERFLOW_P,
    512       IDR_BROWSER_ACTIONS_OVERFLOW_H,
    513       0,
    514       gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE)));
    515 
    516   GtkWidget* gripper = gtk_button_new();
    517   gtk_widget_set_size_request(gripper, kResizeGripperWidth, -1);
    518   gtk_widget_set_can_focus(gripper, FALSE);
    519 
    520   gtk_widget_add_events(gripper, GDK_POINTER_MOTION_MASK);
    521   signals_.Connect(gripper, "motion-notify-event",
    522                    G_CALLBACK(OnGripperMotionNotifyThunk), this);
    523   signals_.Connect(gripper, "expose-event",
    524                    G_CALLBACK(OnGripperExposeThunk), this);
    525   signals_.Connect(gripper, "enter-notify-event",
    526                    G_CALLBACK(OnGripperEnterNotifyThunk), this);
    527   signals_.Connect(gripper, "leave-notify-event",
    528                    G_CALLBACK(OnGripperLeaveNotifyThunk), this);
    529   signals_.Connect(gripper, "button-release-event",
    530                    G_CALLBACK(OnGripperButtonReleaseThunk), this);
    531   signals_.Connect(gripper, "button-press-event",
    532                    G_CALLBACK(OnGripperButtonPressThunk), this);
    533   signals_.Connect(chevron(), "button-press-event",
    534                    G_CALLBACK(OnOverflowButtonPressThunk), this);
    535 
    536   // |overflow_alignment| adds padding to the right of the browser action
    537   // buttons, but only appears when the overflow menu is showing.
    538   overflow_alignment_.Own(gtk_alignment_new(0, 0, 1, 1));
    539   gtk_container_add(GTK_CONTAINER(overflow_alignment_.get()), chevron());
    540 
    541   // |overflow_area_| holds the overflow chevron and the separator, which
    542   // is only shown in GTK+ theme mode.
    543   overflow_area_.Own(gtk_hbox_new(FALSE, 0));
    544   gtk_box_pack_start(GTK_BOX(overflow_area_.get()), overflow_alignment_.get(),
    545                      FALSE, FALSE, 0);
    546 
    547   separator_.Own(gtk_vseparator_new());
    548   gtk_box_pack_start(GTK_BOX(overflow_area_.get()), separator_.get(),
    549                      FALSE, FALSE, 0);
    550   gtk_widget_set_no_show_all(separator_.get(), TRUE);
    551 
    552   gtk_widget_show_all(overflow_area_.get());
    553   gtk_widget_set_no_show_all(overflow_area_.get(), TRUE);
    554 
    555   gtk_box_pack_start(GTK_BOX(hbox_.get()), gripper, FALSE, FALSE, 0);
    556   gtk_box_pack_start(GTK_BOX(hbox_.get()), button_hbox_.get(), TRUE, TRUE, 0);
    557   gtk_box_pack_start(GTK_BOX(hbox_.get()), overflow_area_.get(), FALSE, FALSE,
    558                      0);
    559 
    560   model_ = extension_service->toolbar_model();
    561   model_->AddObserver(this);
    562   SetupDrags();
    563 
    564   if (model_->extensions_initialized()) {
    565     CreateAllButtons();
    566     SetContainerWidth();
    567   }
    568 
    569   // We want to connect to "set-focus" on the toplevel window; we have to wait
    570   // until we are added to a toplevel window to do so.
    571   signals_.Connect(widget(), "hierarchy-changed",
    572                    G_CALLBACK(OnHierarchyChangedThunk), this);
    573 
    574   ViewIDUtil::SetID(button_hbox_.get(), VIEW_ID_BROWSER_ACTION_TOOLBAR);
    575 
    576   registrar_.Add(this,
    577                  chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
    578                  content::Source<ThemeService>(theme_service_));
    579   theme_service_->InitThemesFor(this);
    580 }
    581 
    582 BrowserActionsToolbarGtk::~BrowserActionsToolbarGtk() {
    583   if (model_)
    584     model_->RemoveObserver(this);
    585   button_hbox_.Destroy();
    586   hbox_.Destroy();
    587 }
    588 
    589 int BrowserActionsToolbarGtk::GetCurrentTabId() const {
    590   content::WebContents* active_tab =
    591       browser_->tab_strip_model()->GetActiveWebContents();
    592   if (!active_tab)
    593     return -1;
    594 
    595   return SessionTabHelper::FromWebContents(active_tab)->session_id().id();
    596 }
    597 
    598 void BrowserActionsToolbarGtk::Update() {
    599   for (ExtensionButtonMap::iterator iter = extension_button_map_.begin();
    600        iter != extension_button_map_.end(); ++iter) {
    601     iter->second->UpdateState();
    602   }
    603 }
    604 
    605 void BrowserActionsToolbarGtk::Observe(
    606     int type,
    607     const content::NotificationSource& source,
    608     const content::NotificationDetails& details) {
    609   DCHECK(chrome::NOTIFICATION_BROWSER_THEME_CHANGED == type);
    610   gtk_widget_set_visible(separator_.get(), theme_service_->UsingNativeTheme());
    611 }
    612 
    613 void BrowserActionsToolbarGtk::SetupDrags() {
    614   GtkTargetEntry drag_target = GetDragTargetEntry();
    615   gtk_drag_dest_set(button_hbox_.get(), GTK_DEST_DEFAULT_DROP, &drag_target, 1,
    616                     GDK_ACTION_MOVE);
    617 
    618   signals_.Connect(button_hbox_.get(), "drag-motion",
    619                    G_CALLBACK(OnDragMotionThunk), this);
    620 }
    621 
    622 void BrowserActionsToolbarGtk::CreateAllButtons() {
    623   extension_button_map_.clear();
    624 
    625   int i = 0;
    626   const extensions::ExtensionList& toolbar_items = model_->toolbar_items();
    627   for (extensions::ExtensionList::const_iterator iter = toolbar_items.begin();
    628        iter != toolbar_items.end(); ++iter) {
    629     CreateButtonForExtension(iter->get(), i++);
    630   }
    631 }
    632 
    633 void BrowserActionsToolbarGtk::SetContainerWidth() {
    634   int showing_actions = model_->GetVisibleIconCount();
    635   if (showing_actions >= 0)
    636     SetButtonHBoxWidth(WidthForIconCount(showing_actions));
    637 }
    638 
    639 void BrowserActionsToolbarGtk::CreateButtonForExtension(
    640     const Extension* extension, int index) {
    641   if (!ShouldDisplayBrowserAction(extension))
    642     return;
    643 
    644   if (profile_->IsOffTheRecord())
    645     index = model_->OriginalIndexToIncognito(index);
    646 
    647   RemoveButtonForExtension(extension);
    648   linked_ptr<BrowserActionButton> button(
    649       new BrowserActionButton(this, extension, theme_service_));
    650   gtk_chrome_shrinkable_hbox_pack_start(
    651       GTK_CHROME_SHRINKABLE_HBOX(button_hbox_.get()), button->widget(), 0);
    652   gtk_box_reorder_child(GTK_BOX(button_hbox_.get()), button->widget(), index);
    653   extension_button_map_[extension->id()] = button;
    654 
    655   GtkTargetEntry drag_target = GetDragTargetEntry();
    656   gtk_drag_source_set(button->button(), GDK_BUTTON1_MASK, &drag_target, 1,
    657                       GDK_ACTION_MOVE);
    658   // We ignore whether the drag was a "success" or "failure" in Gtk's opinion.
    659   signals_.Connect(button->button(), "drag-end",
    660                    G_CALLBACK(&OnDragEndThunk), this);
    661   signals_.Connect(button->button(), "drag-failed",
    662                    G_CALLBACK(&OnDragFailedThunk), this);
    663 
    664   // Any time a browser action button is shown or hidden we have to update
    665   // the chevron state.
    666   signals_.Connect(button->widget(), "show",
    667                    G_CALLBACK(&OnButtonShowOrHideThunk), this);
    668   signals_.Connect(button->widget(), "hide",
    669                    G_CALLBACK(&OnButtonShowOrHideThunk), this);
    670 
    671   gtk_widget_show(button->widget());
    672 
    673   UpdateVisibility();
    674 }
    675 
    676 GtkWidget* BrowserActionsToolbarGtk::GetBrowserActionWidget(
    677     const Extension* extension) {
    678   ExtensionButtonMap::iterator it = extension_button_map_.find(
    679       extension->id());
    680   if (it == extension_button_map_.end())
    681     return NULL;
    682 
    683   return it->second.get()->widget();
    684 }
    685 
    686 void BrowserActionsToolbarGtk::RemoveButtonForExtension(
    687     const Extension* extension) {
    688   if (extension_button_map_.erase(extension->id()))
    689     UpdateVisibility();
    690   UpdateChevronVisibility();
    691 }
    692 
    693 void BrowserActionsToolbarGtk::UpdateVisibility() {
    694   gtk_widget_set_visible(widget(), button_count() != 0);
    695 }
    696 
    697 bool BrowserActionsToolbarGtk::ShouldDisplayBrowserAction(
    698     const Extension* extension) {
    699   // Only display incognito-enabled extensions while in incognito mode.
    700   return (!profile_->IsOffTheRecord() ||
    701           extensions::ExtensionSystem::Get(profile_)->extension_service()->
    702               IsIncognitoEnabled(extension->id()));
    703 }
    704 
    705 void BrowserActionsToolbarGtk::HidePopup() {
    706   ExtensionPopupGtk* popup = ExtensionPopupGtk::get_current_extension_popup();
    707   if (popup)
    708     popup->DestroyPopup();
    709 }
    710 
    711 void BrowserActionsToolbarGtk::AnimateToShowNIcons(int count) {
    712   desired_width_ = WidthForIconCount(count);
    713 
    714   GtkAllocation allocation;
    715   gtk_widget_get_allocation(button_hbox_.get(), &allocation);
    716   start_width_ = allocation.width;
    717 
    718   resize_animation_.Reset();
    719   resize_animation_.Show();
    720 }
    721 
    722 void BrowserActionsToolbarGtk::BrowserActionAdded(const Extension* extension,
    723                                                   int index) {
    724   overflow_menu_.reset();
    725 
    726   CreateButtonForExtension(extension, index);
    727 
    728   // If we are still initializing the container, don't bother animating.
    729   if (!model_->extensions_initialized())
    730     return;
    731 
    732   // Animate the addition if we are showing all browser action buttons.
    733   if (!gtk_widget_get_visible(overflow_area_.get())) {
    734     AnimateToShowNIcons(button_count());
    735     model_->SetVisibleIconCount(button_count());
    736   }
    737 }
    738 
    739 void BrowserActionsToolbarGtk::BrowserActionRemoved(
    740     const Extension* extension) {
    741   overflow_menu_.reset();
    742 
    743   if (drag_button_ != NULL) {
    744     // Break the current drag.
    745     gtk_grab_remove(button_hbox_.get());
    746   }
    747 
    748   RemoveButtonForExtension(extension);
    749 
    750   if (!gtk_widget_get_visible(overflow_area_.get())) {
    751     AnimateToShowNIcons(button_count());
    752     model_->SetVisibleIconCount(button_count());
    753   }
    754 }
    755 
    756 void BrowserActionsToolbarGtk::BrowserActionMoved(const Extension* extension,
    757                                                   int index) {
    758   // We initiated this move action, and have already moved the button.
    759   if (drag_button_ != NULL)
    760     return;
    761 
    762   GtkWidget* button_widget = GetBrowserActionWidget(extension);
    763   if (!button_widget) {
    764     if (ShouldDisplayBrowserAction(extension))
    765       NOTREACHED();
    766     return;
    767   }
    768 
    769   if (profile_->IsOffTheRecord())
    770     index = model_->OriginalIndexToIncognito(index);
    771 
    772   gtk_box_reorder_child(GTK_BOX(button_hbox_.get()), button_widget, index);
    773 }
    774 
    775 void BrowserActionsToolbarGtk::ModelLoaded() {
    776   SetContainerWidth();
    777 }
    778 
    779 void BrowserActionsToolbarGtk::AnimationProgressed(
    780     const ui::Animation* animation) {
    781   int width = start_width_ + (desired_width_ - start_width_) *
    782       animation->GetCurrentValue();
    783   gtk_widget_set_size_request(button_hbox_.get(), width, -1);
    784 
    785   if (width == desired_width_)
    786     resize_animation_.Reset();
    787 }
    788 
    789 void BrowserActionsToolbarGtk::AnimationEnded(const ui::Animation* animation) {
    790   gtk_widget_set_size_request(button_hbox_.get(), desired_width_, -1);
    791   UpdateChevronVisibility();
    792 }
    793 
    794 bool BrowserActionsToolbarGtk::IsCommandIdChecked(int command_id) const {
    795   return false;
    796 }
    797 
    798 bool BrowserActionsToolbarGtk::IsCommandIdEnabled(int command_id) const {
    799   const Extension* extension = model_->toolbar_items()[command_id].get();
    800   return ExtensionActionManager::Get(profile_)->GetBrowserAction(*extension)
    801       ->GetIsVisible(GetCurrentTabId());
    802 }
    803 
    804 bool BrowserActionsToolbarGtk::GetAcceleratorForCommandId(
    805     int command_id,
    806     ui::Accelerator* accelerator) {
    807   return false;
    808 }
    809 
    810 void BrowserActionsToolbarGtk::ExecuteCommand(int command_id, int event_flags) {
    811   const Extension* extension = model_->toolbar_items()[command_id].get();
    812   GURL popup_url;
    813 
    814   switch (model_->ExecuteBrowserAction(extension, browser(), &popup_url)) {
    815     case ExtensionToolbarModel::ACTION_NONE:
    816       break;
    817     case ExtensionToolbarModel::ACTION_SHOW_POPUP:
    818       ExtensionPopupGtk::Show(popup_url, browser(), chevron(),
    819                               ExtensionPopupGtk::SHOW);
    820       break;
    821   }
    822 }
    823 
    824 void BrowserActionsToolbarGtk::StoppedShowing() {
    825   overflow_button_->UnsetPaintOverride();
    826 }
    827 
    828 bool BrowserActionsToolbarGtk::AlwaysShowIconForCmd(int command_id) const {
    829   return true;
    830 }
    831 
    832 void BrowserActionsToolbarGtk::DragStarted(BrowserActionButton* button,
    833                                            GdkDragContext* drag_context) {
    834   // No representation of the widget following the cursor.
    835   GdkPixbuf* pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, 1, 1);
    836   gtk_drag_set_icon_pixbuf(drag_context, pixbuf, 0, 0);
    837   g_object_unref(pixbuf);
    838 
    839   DCHECK(!drag_button_);
    840   drag_button_ = button;
    841 }
    842 
    843 void BrowserActionsToolbarGtk::SetButtonHBoxWidth(int new_width) {
    844   gint max_width = WidthForIconCount(button_count());
    845   new_width = std::min(max_width, new_width);
    846   new_width = std::max(new_width, 0);
    847   gtk_widget_set_size_request(button_hbox_.get(), new_width, -1);
    848 }
    849 
    850 void BrowserActionsToolbarGtk::UpdateChevronVisibility() {
    851   int showing_icon_count =
    852       gtk_chrome_shrinkable_hbox_get_visible_child_count(
    853           GTK_CHROME_SHRINKABLE_HBOX(button_hbox_.get()));
    854   if (showing_icon_count == 0) {
    855     gtk_alignment_set_padding(GTK_ALIGNMENT(overflow_alignment_.get()),
    856                               0, 0, 0, 0);
    857   } else {
    858     gtk_alignment_set_padding(GTK_ALIGNMENT(overflow_alignment_.get()),
    859                               0, 0, kButtonChevronPadding, 0);
    860   }
    861 
    862   if (button_count() > showing_icon_count) {
    863     if (!gtk_widget_get_visible(overflow_area_.get())) {
    864       if (drag_button_) {
    865         // During drags, when the overflow chevron shows for the first time,
    866         // take that much space away from |button_hbox_| to make the drag look
    867         // smoother.
    868         GtkRequisition req;
    869         gtk_widget_size_request(chevron(), &req);
    870         gint overflow_width = req.width;
    871         gtk_widget_size_request(button_hbox_.get(), &req);
    872         gint button_hbox_width = req.width;
    873         button_hbox_width = std::max(button_hbox_width - overflow_width, 0);
    874         gtk_widget_set_size_request(button_hbox_.get(), button_hbox_width, -1);
    875       }
    876 
    877       gtk_widget_show(overflow_area_.get());
    878     }
    879   } else {
    880     gtk_widget_hide(overflow_area_.get());
    881   }
    882 }
    883 
    884 gboolean BrowserActionsToolbarGtk::OnDragMotion(GtkWidget* widget,
    885                                                 GdkDragContext* drag_context,
    886                                                 gint x, gint y, guint time) {
    887   // Only handle drags we initiated.
    888   if (!drag_button_)
    889     return FALSE;
    890 
    891   if (base::i18n::IsRTL()) {
    892     GtkAllocation allocation;
    893     gtk_widget_get_allocation(widget, &allocation);
    894     x = allocation.width - x;
    895   }
    896 
    897   drop_index_ = x < kButtonWidth ? 0 : x / (kButtonWidth + kButtonPadding);
    898 
    899   // We will go ahead and reorder the child in order to provide visual feedback
    900   // to the user. We don't inform the model that it has moved until the drag
    901   // ends.
    902   gtk_box_reorder_child(GTK_BOX(button_hbox_.get()), drag_button_->widget(),
    903                         drop_index_);
    904 
    905   gdk_drag_status(drag_context, GDK_ACTION_MOVE, time);
    906   return TRUE;
    907 }
    908 
    909 void BrowserActionsToolbarGtk::OnDragEnd(GtkWidget* button,
    910                                          GdkDragContext* drag_context) {
    911   if (drop_index_ != -1) {
    912     if (profile_->IsOffTheRecord())
    913       drop_index_ = model_->IncognitoIndexToOriginal(drop_index_);
    914 
    915     model_->MoveBrowserAction(drag_button_->extension(), drop_index_);
    916   }
    917 
    918   drag_button_ = NULL;
    919   drop_index_ = -1;
    920 }
    921 
    922 gboolean BrowserActionsToolbarGtk::OnDragFailed(GtkWidget* widget,
    923                                                 GdkDragContext* drag_context,
    924                                                 GtkDragResult result) {
    925   // We connect to this signal and return TRUE so that the default failure
    926   // animation (wherein the drag widget floats back to the start of the drag)
    927   // does not show, and the drag-end signal is emitted immediately instead of
    928   // several seconds later.
    929   return TRUE;
    930 }
    931 
    932 void BrowserActionsToolbarGtk::OnHierarchyChanged(
    933     GtkWidget* widget, GtkWidget* previous_toplevel) {
    934   GtkWidget* toplevel = gtk_widget_get_toplevel(widget);
    935   if (!gtk_widget_is_toplevel(toplevel))
    936     return;
    937 
    938   signals_.Connect(toplevel, "set-focus", G_CALLBACK(OnSetFocusThunk), this);
    939 }
    940 
    941 void BrowserActionsToolbarGtk::OnSetFocus(GtkWidget* widget,
    942                                           GtkWidget* focus_widget) {
    943   ExtensionPopupGtk* popup = ExtensionPopupGtk::get_current_extension_popup();
    944   // The focus of the parent window has changed. Close the popup. Delay the hide
    945   // because it will destroy the RenderViewHost, which may still be on the
    946   // call stack.
    947   if (!popup || popup->being_inspected())
    948     return;
    949   base::MessageLoop::current()->PostTask(
    950       FROM_HERE,
    951       base::Bind(&BrowserActionsToolbarGtk::HidePopup,
    952                  weak_factory_.GetWeakPtr()));
    953 }
    954 
    955 gboolean BrowserActionsToolbarGtk::OnGripperMotionNotify(
    956     GtkWidget* widget, GdkEventMotion* event) {
    957   if (!(event->state & GDK_BUTTON1_MASK))
    958     return FALSE;
    959 
    960   // Calculate how much the user dragged the gripper and subtract that off the
    961   // button container's width.
    962   int distance_dragged;
    963   if (base::i18n::IsRTL()) {
    964     distance_dragged = -event->x;
    965   } else {
    966     GtkAllocation widget_allocation;
    967     gtk_widget_get_allocation(widget, &widget_allocation);
    968     distance_dragged = event->x - widget_allocation.width;
    969   }
    970 
    971   GtkAllocation button_hbox_allocation;
    972   gtk_widget_get_allocation(button_hbox_.get(), &button_hbox_allocation);
    973   gint new_width = button_hbox_allocation.width - distance_dragged;
    974   SetButtonHBoxWidth(new_width);
    975 
    976   return FALSE;
    977 }
    978 
    979 gboolean BrowserActionsToolbarGtk::OnGripperExpose(GtkWidget* gripper,
    980                                                    GdkEventExpose* expose) {
    981   return TRUE;
    982 }
    983 
    984 // These three signal handlers (EnterNotify, LeaveNotify, and ButtonRelease)
    985 // are used to give the gripper the resize cursor. Since it doesn't have its
    986 // own window, we have to set the cursor whenever the pointer moves into the
    987 // button or leaves the button, and be sure to leave it on when the user is
    988 // dragging.
    989 gboolean BrowserActionsToolbarGtk::OnGripperEnterNotify(
    990     GtkWidget* gripper, GdkEventCrossing* event) {
    991   gdk_window_set_cursor(gtk_widget_get_window(gripper),
    992                         gfx::GetCursor(GDK_SB_H_DOUBLE_ARROW));
    993   return FALSE;
    994 }
    995 
    996 gboolean BrowserActionsToolbarGtk::OnGripperLeaveNotify(
    997     GtkWidget* gripper, GdkEventCrossing* event) {
    998   if (!(event->state & GDK_BUTTON1_MASK))
    999     gdk_window_set_cursor(gtk_widget_get_window(gripper), NULL);
   1000   return FALSE;
   1001 }
   1002 
   1003 gboolean BrowserActionsToolbarGtk::OnGripperButtonRelease(
   1004     GtkWidget* gripper, GdkEventButton* event) {
   1005   GtkAllocation allocation;
   1006   gtk_widget_get_allocation(gripper, &allocation);
   1007   gfx::Rect gripper_rect(0, 0, allocation.width, allocation.height);
   1008 
   1009   gfx::Point release_point(event->x, event->y);
   1010   if (!gripper_rect.Contains(release_point))
   1011     gdk_window_set_cursor(gtk_widget_get_window(gripper), NULL);
   1012 
   1013   // After the user resizes the toolbar, we want to smartly resize it to be
   1014   // the perfect size to fit the buttons.
   1015   int visible_icon_count =
   1016       gtk_chrome_shrinkable_hbox_get_visible_child_count(
   1017           GTK_CHROME_SHRINKABLE_HBOX(button_hbox_.get()));
   1018   AnimateToShowNIcons(visible_icon_count);
   1019   model_->SetVisibleIconCount(visible_icon_count);
   1020 
   1021   return FALSE;
   1022 }
   1023 
   1024 gboolean BrowserActionsToolbarGtk::OnGripperButtonPress(
   1025     GtkWidget* gripper, GdkEventButton* event) {
   1026   resize_animation_.Reset();
   1027 
   1028   return FALSE;
   1029 }
   1030 
   1031 gboolean BrowserActionsToolbarGtk::OnOverflowButtonPress(
   1032     GtkWidget* overflow, GdkEventButton* event) {
   1033   overflow_menu_model_.reset(new SimpleMenuModel(this));
   1034 
   1035   int visible_icon_count =
   1036       gtk_chrome_shrinkable_hbox_get_visible_child_count(
   1037           GTK_CHROME_SHRINKABLE_HBOX(button_hbox_.get()));
   1038   for (int i = visible_icon_count; i < button_count(); ++i) {
   1039     int model_index = i;
   1040     if (profile_->IsOffTheRecord())
   1041       model_index = model_->IncognitoIndexToOriginal(i);
   1042 
   1043     const Extension* extension = model_->toolbar_items()[model_index].get();
   1044     BrowserActionButton* button = extension_button_map_[extension->id()].get();
   1045 
   1046     overflow_menu_model_->AddItem(model_index, UTF8ToUTF16(extension->name()));
   1047     overflow_menu_model_->SetIcon(overflow_menu_model_->GetItemCount() - 1,
   1048                                   button->GetIcon());
   1049 
   1050     // TODO(estade): set the menu item's tooltip.
   1051   }
   1052 
   1053   overflow_menu_.reset(new MenuGtk(this, overflow_menu_model_.get()));
   1054   signals_.Connect(overflow_menu_->widget(), "button-press-event",
   1055                    G_CALLBACK(OnOverflowMenuButtonPressThunk), this);
   1056 
   1057   overflow_button_->SetPaintOverride(GTK_STATE_ACTIVE);
   1058   overflow_menu_->PopupAsFromKeyEvent(chevron());
   1059 
   1060   return FALSE;
   1061 }
   1062 
   1063 gboolean BrowserActionsToolbarGtk::OnOverflowMenuButtonPress(
   1064     GtkWidget* overflow, GdkEventButton* event) {
   1065   if (event->button != 3)
   1066     return FALSE;
   1067 
   1068   GtkWidget* menu_item = GTK_MENU_SHELL(overflow)->active_menu_item;
   1069   if (!menu_item)
   1070     return FALSE;
   1071 
   1072   int item_index = g_list_index(GTK_MENU_SHELL(overflow)->children, menu_item);
   1073   if (item_index == -1) {
   1074     NOTREACHED();
   1075     return FALSE;
   1076   }
   1077 
   1078   item_index += gtk_chrome_shrinkable_hbox_get_visible_child_count(
   1079       GTK_CHROME_SHRINKABLE_HBOX(button_hbox_.get()));
   1080   if (profile_->IsOffTheRecord())
   1081     item_index = model_->IncognitoIndexToOriginal(item_index);
   1082 
   1083   const Extension* extension = model_->toolbar_items()[item_index].get();
   1084   ExtensionButtonMap::iterator it = extension_button_map_.find(extension->id());
   1085   if (it == extension_button_map_.end()) {
   1086     NOTREACHED();
   1087     return FALSE;
   1088   }
   1089 
   1090   MenuGtk* menu = it->second.get()->GetContextMenu();
   1091   if (!menu)
   1092     return FALSE;
   1093 
   1094   menu->PopupAsContext(gfx::Point(event->x_root, event->y_root),
   1095                        event->time);
   1096   return TRUE;
   1097 }
   1098 
   1099 void BrowserActionsToolbarGtk::OnButtonShowOrHide(GtkWidget* sender) {
   1100   if (!resize_animation_.is_animating())
   1101     UpdateChevronVisibility();
   1102 }
   1103