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