// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ui/gtk/browser_actions_toolbar_gtk.h"

#include <gtk/gtk.h>

#include <algorithm>
#include <utility>
#include <vector>

#include "base/bind.h"
#include "base/i18n/rtl.h"
#include "base/message_loop/message_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/extensions/api/commands/command_service.h"
#include "chrome/browser/extensions/extension_action.h"
#include "chrome/browser/extensions/extension_action_icon_factory.h"
#include "chrome/browser/extensions/extension_action_manager.h"
#include "chrome/browser/extensions/extension_context_menu_model.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_system.h"
#include "chrome/browser/extensions/extension_toolbar_model.h"
#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sessions/session_tab_helper.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/gtk/browser_window_gtk.h"
#include "chrome/browser/ui/gtk/custom_button.h"
#include "chrome/browser/ui/gtk/extensions/extension_popup_gtk.h"
#include "chrome/browser/ui/gtk/gtk_chrome_button.h"
#include "chrome/browser/ui/gtk/gtk_chrome_shrinkable_hbox.h"
#include "chrome/browser/ui/gtk/gtk_theme_service.h"
#include "chrome/browser/ui/gtk/gtk_util.h"
#include "chrome/browser/ui/gtk/hover_controller_gtk.h"
#include "chrome/browser/ui/gtk/menu_gtk.h"
#include "chrome/browser/ui/gtk/view_id_util.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_source.h"
#include "extensions/common/extension.h"
#include "extensions/common/manifest_constants.h"
#include "grit/theme_resources.h"
#include "grit/ui_resources.h"
#include "ui/base/accelerators/platform_accelerator_gtk.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/canvas_skia_paint.h"
#include "ui/gfx/gtk_compat.h"
#include "ui/gfx/gtk_util.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia_operations.h"

using extensions::Extension;
using extensions::ExtensionActionManager;

namespace {

// The width of the browser action buttons.
const int kButtonWidth = 27;

// The padding between browser action buttons.
const int kButtonPadding = 4;

// The padding to the right of the browser action buttons (between the buttons
// and chevron if they are both showing).
const int kButtonChevronPadding = 2;

// Width of the invisible gripper for resizing the toolbar.
const int kResizeGripperWidth = 4;

const char kDragTarget[] = "application/x-chrome-browseraction";

GtkTargetEntry GetDragTargetEntry() {
  GtkTargetEntry drag_target;
  drag_target.target = const_cast<char*>(kDragTarget);
  drag_target.flags = GTK_TARGET_SAME_APP;
  drag_target.info = 0;
  return drag_target;
}

// The minimum width in pixels of the button hbox if |icon_count| icons are
// showing.
gint WidthForIconCount(gint icon_count) {
  return std::max((kButtonWidth + kButtonPadding) * icon_count - kButtonPadding,
                  0);
}

}  // namespace

using ui::SimpleMenuModel;

class BrowserActionButton : public content::NotificationObserver,
                            public ExtensionActionIconFactory::Observer,
                            public ExtensionContextMenuModel::PopupDelegate,
                            public MenuGtk::Delegate {
 public:
  BrowserActionButton(BrowserActionsToolbarGtk* toolbar,
                      const Extension* extension,
                      GtkThemeService* theme_provider)
      : toolbar_(toolbar),
        extension_(extension),
        image_(NULL),
        icon_factory_(toolbar->browser()->profile(), extension,
                      browser_action(), this),
        accel_group_(NULL) {
    button_.reset(new CustomDrawButton(
        theme_provider,
        IDR_BROWSER_ACTION,
        IDR_BROWSER_ACTION_P,
        IDR_BROWSER_ACTION_H,
        0,
        NULL));
    gtk_widget_set_size_request(button(), kButtonWidth, kButtonWidth);
    alignment_.Own(gtk_alignment_new(0, 0, 1, 1));
    gtk_container_add(GTK_CONTAINER(alignment_.get()), button());
    gtk_widget_show(button());

    DCHECK(browser_action());

    UpdateState();

    signals_.Connect(button(), "button-press-event",
                     G_CALLBACK(OnButtonPress), this);
    signals_.Connect(button(), "clicked",
                     G_CALLBACK(OnClicked), this);
    signals_.Connect(button(), "drag-begin",
                     G_CALLBACK(OnDragBegin), this);
    signals_.ConnectAfter(widget(), "expose-event",
                     G_CALLBACK(OnExposeEvent), this);
    if (toolbar_->browser()->window()) {
      // If the window exists already, then the browser action button has been
      // recreated after the window was created, for example when the extension
      // is reloaded.
      ConnectBrowserActionPopupAccelerator();
    } else {
      // Window doesn't exist yet, wait for it.
      signals_.Connect(toolbar->widget(), "realize",
                       G_CALLBACK(OnRealize), this);
    }

    registrar_.Add(
        this, chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_UPDATED,
        content::Source<ExtensionAction>(browser_action()));
    registrar_.Add(
        this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
        content::Source<Profile>(
            toolbar->browser()->profile()->GetOriginalProfile()));
    registrar_.Add(
        this, chrome::NOTIFICATION_EXTENSION_COMMAND_ADDED,
        content::Source<Profile>(
        toolbar->browser()->profile()->GetOriginalProfile()));
    registrar_.Add(
        this, chrome::NOTIFICATION_EXTENSION_COMMAND_REMOVED,
        content::Source<Profile>(
        toolbar->browser()->profile()->GetOriginalProfile()));
  }

  virtual ~BrowserActionButton() {
    DisconnectBrowserActionPopupAccelerator();

    alignment_.Destroy();
  }

  GtkWidget* button() { return button_->widget(); }

  GtkWidget* widget() { return alignment_.get(); }

  const Extension* extension() { return extension_; }

  // NotificationObserver implementation.
  virtual void Observe(int type,
                       const content::NotificationSource& source,
                       const content::NotificationDetails& details) OVERRIDE {
    switch (type) {
     case chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_UPDATED:
      UpdateState();
      break;
     case chrome::NOTIFICATION_EXTENSION_UNLOADED:
     case chrome::NOTIFICATION_WINDOW_CLOSED:
      DisconnectBrowserActionPopupAccelerator();
      break;
     case chrome::NOTIFICATION_EXTENSION_COMMAND_ADDED:
     case chrome::NOTIFICATION_EXTENSION_COMMAND_REMOVED: {
      std::pair<const std::string, const std::string>* payload =
          content::Details<std::pair<const std::string, const std::string> >(
              details).ptr();
      if (extension_->id() == payload->first &&
          payload->second ==
              extensions::manifest_values::kBrowserActionCommandEvent) {
        if (type == chrome::NOTIFICATION_EXTENSION_COMMAND_ADDED)
          ConnectBrowserActionPopupAccelerator();
        else
          DisconnectBrowserActionPopupAccelerator();
      }
      break;
     }
     default:
      NOTREACHED();
      break;
    }
  }

  // ExtensionActionIconFactory::Observer implementation.
  virtual void OnIconUpdated() OVERRIDE {
    UpdateState();
  }

  // Updates the button based on the latest state from the associated
  // browser action.
  void UpdateState() {
    int tab_id = toolbar_->GetCurrentTabId();
    if (tab_id < 0)
      return;

    std::string tooltip = browser_action()->GetTitle(tab_id);
    if (tooltip.empty())
      gtk_widget_set_has_tooltip(button(), FALSE);
    else
      gtk_widget_set_tooltip_text(button(), tooltip.c_str());

    enabled_ = browser_action()->GetIsVisible(tab_id);
    if (!enabled_)
      button_->SetPaintOverride(GTK_STATE_INSENSITIVE);
    else
      button_->UnsetPaintOverride();

    gfx::Image image = icon_factory_.GetIcon(tab_id);
    if (!image.IsEmpty()) {
      if (enabled_) {
        SetImage(image);
      } else {
        SetImage(gfx::Image(gfx::ImageSkiaOperations::CreateTransparentImage(
            image.AsImageSkia(), .25)));
      }
    }

    gtk_widget_queue_draw(button());
  }

  gfx::Image GetIcon() {
    return icon_factory_.GetIcon(toolbar_->GetCurrentTabId());
  }

  MenuGtk* GetContextMenu() {
    if (!extension_->ShowConfigureContextMenus())
      return NULL;

    context_menu_model_ =
        new ExtensionContextMenuModel(extension_, toolbar_->browser(), this);
    context_menu_.reset(
        new MenuGtk(this, context_menu_model_.get()));
    return context_menu_.get();
  }

 private:
  // Activate the browser action. Returns true if a popup was shown. Showing the
  // popup will grant tab permissions if |should_grant| is true. Popup's shown
  // via an API should not grant permissions.
  bool Activate(GtkWidget* widget, bool should_grant) {
    ExtensionToolbarModel* model = toolbar_->model();
    const Extension* extension = extension_;
    Browser* browser = toolbar_->browser();
    GURL popup_url;

    switch (model->ExecuteBrowserAction(
        extension, browser, &popup_url, should_grant)) {
      case ExtensionToolbarModel::ACTION_NONE:
        break;
      case ExtensionToolbarModel::ACTION_SHOW_POPUP:
        ExtensionPopupGtk::Show(popup_url, browser, widget,
                                ExtensionPopupGtk::SHOW);
        return true;
    }
    return false;
  }

  // MenuGtk::Delegate implementation.
  virtual void StoppedShowing() OVERRIDE {
    if (enabled_)
      button_->UnsetPaintOverride();
    else
      button_->SetPaintOverride(GTK_STATE_INSENSITIVE);

    // If the context menu was showing for the overflow menu, re-assert the
    // grab that was shadowed.
    if (toolbar_->overflow_menu_.get())
      gtk_util::GrabAllInput(toolbar_->overflow_menu_->widget());
  }

  virtual void CommandWillBeExecuted() OVERRIDE {
    // If the context menu was showing for the overflow menu, and a command
    // is executed, then stop showing the overflow menu.
    if (toolbar_->overflow_menu_.get())
      toolbar_->overflow_menu_->Cancel();
  }

  // ExtensionContextMenuModel::PopupDelegate implementation.
  virtual void InspectPopup(ExtensionAction* action) OVERRIDE {
    GURL popup_url = action->GetPopupUrl(toolbar_->GetCurrentTabId());
    ExtensionPopupGtk::Show(popup_url, toolbar_->browser(), widget(),
                            ExtensionPopupGtk::SHOW_AND_INSPECT);
  }

  void SetImage(const gfx::Image& image) {
    if (!image_) {
      image_ = gtk_image_new_from_pixbuf(image.ToGdkPixbuf());
      gtk_button_set_image(GTK_BUTTON(button()), image_);
    } else {
      gtk_image_set_from_pixbuf(GTK_IMAGE(image_), image.ToGdkPixbuf());
    }
  }

  static gboolean OnButtonPress(GtkWidget* widget,
                                GdkEventButton* event,
                                BrowserActionButton* button) {
    if (event->button != 3)
      return FALSE;

    MenuGtk* menu = button->GetContextMenu();
    if (!menu)
      return FALSE;

    button->button_->SetPaintOverride(GTK_STATE_ACTIVE);
    menu->PopupForWidget(widget, event->button, event->time);

    return TRUE;
  }

  static void OnClicked(GtkWidget* widget, BrowserActionButton* button) {
    if (button->enabled_)
      button->Activate(widget, true);
  }

  static gboolean OnExposeEvent(GtkWidget* widget,
                                GdkEventExpose* event,
                                BrowserActionButton* button) {
    int tab_id = button->toolbar_->GetCurrentTabId();
    if (tab_id < 0)
      return FALSE;

    ExtensionAction* action = button->browser_action();
    if (action->GetBadgeText(tab_id).empty())
      return FALSE;

    gfx::CanvasSkiaPaint canvas(event, false);
    GtkAllocation allocation;
    gtk_widget_get_allocation(widget, &allocation);
    action->PaintBadge(&canvas, gfx::Rect(allocation), tab_id);
    return FALSE;
  }

  static void OnDragBegin(GtkWidget* widget,
                          GdkDragContext* drag_context,
                          BrowserActionButton* button) {
    // Simply pass along the notification to the toolbar. The point of this
    // function is to tell the toolbar which BrowserActionButton initiated the
    // drag.
    button->toolbar_->DragStarted(button, drag_context);
  }

  // The accelerator handler for when the shortcuts to open the popup is struck.
  static gboolean OnGtkAccelerator(GtkAccelGroup* accel_group,
                                   GObject* acceleratable,
                                   guint keyval,
                                   GdkModifierType modifier,
                                   BrowserActionButton* button) {
    // Open the popup for this extension.
    GtkWidget* anchor = button->widget();
    // The anchor might be in the overflow menu. Then we point to the chevron.
    if (!gtk_widget_get_visible(anchor))
      anchor = button->toolbar_->chevron();
    button->Activate(anchor, true);
    return TRUE;
  }

  // The handler for when the browser action is realized. |user_data| contains a
  // pointer to the BrowserAction shown.
  static void OnRealize(GtkWidget* widget, void* user_data) {
    BrowserActionButton* button = static_cast<BrowserActionButton*>(user_data);
    button->ConnectBrowserActionPopupAccelerator();
  }

  // Connect the accelerator for the browser action popup.
  void ConnectBrowserActionPopupAccelerator() {
    extensions::CommandService* command_service =
        extensions::CommandService::Get(toolbar_->browser()->profile());
    extensions::Command command;
    if (command_service->GetBrowserActionCommand(extension_->id(),
        extensions::CommandService::ACTIVE_ONLY,
        &command,
        NULL)) {
      // Found the browser action shortcut command, register it.
      keybinding_ = command.accelerator();

      gfx::NativeWindow window =
          toolbar_->browser()->window()->GetNativeWindow();
      accel_group_ = gtk_accel_group_new();
      gtk_window_add_accel_group(window, accel_group_);

      gtk_accel_group_connect(
          accel_group_,
          ui::GetGdkKeyCodeForAccelerator(keybinding_),
          ui::GetGdkModifierForAccelerator(keybinding_),
          GtkAccelFlags(0),
          g_cclosure_new(G_CALLBACK(OnGtkAccelerator), this, NULL));

      // Since we've added an accelerator, we'll need to unregister it before
      // the window is closed, so we listen for the window being closed.
      registrar_.Add(this,
                     chrome::NOTIFICATION_WINDOW_CLOSED,
                     content::Source<GtkWindow>(window));
    }
  }

  // Disconnect the accelerator for the browser action popup and delete clean up
  // the accelerator group registration.
  void DisconnectBrowserActionPopupAccelerator() {
    if (accel_group_) {
      gfx::NativeWindow window =
          toolbar_->browser()->window()->GetNativeWindow();
      gtk_accel_group_disconnect_key(
          accel_group_,
          ui::GetGdkKeyCodeForAccelerator(keybinding_),
          GetGdkModifierForAccelerator(keybinding_));
      gtk_window_remove_accel_group(window, accel_group_);
      g_object_unref(accel_group_);
      accel_group_ = NULL;
      keybinding_ = ui::Accelerator();

      // We've removed the accelerator, so no need to listen to this anymore.
      registrar_.Remove(this,
                        chrome::NOTIFICATION_WINDOW_CLOSED,
                        content::Source<GtkWindow>(window));
    }
  }

  ExtensionAction* browser_action() const {
    return ExtensionActionManager::Get(toolbar_->browser()->profile())->
        GetBrowserAction(*extension_);
  }

  // The toolbar containing this button.
  BrowserActionsToolbarGtk* toolbar_;

  // The extension that contains this browser action.
  const Extension* extension_;

  // The button for this browser action.
  scoped_ptr<CustomDrawButton> button_;

  // Whether the browser action is enabled (equivalent to whether a page action
  // is visible).
  bool enabled_;

  // The top level widget (parent of |button_|).
  ui::OwnedWidgetGtk alignment_;

  // The one image subwidget in |button_|. We keep this out so we don't alter
  // the widget hierarchy while changing the button image because changing the
  // GTK widget hierarchy invalidates all tooltips and several popular
  // extensions change browser action icon in a loop.
  GtkWidget* image_;

  // The object that will be used to get the browser action icon for us.
  // It may load the icon asynchronously (in which case the initial icon
  // returned by the factory will be transparent), so we have to observe it for
  // updates to the icon.
  ExtensionActionIconFactory icon_factory_;

  // Same as |default_icon_|, but stored as SkBitmap.
  SkBitmap default_skbitmap_;

  ui::GtkSignalRegistrar signals_;
  content::NotificationRegistrar registrar_;

  // The accelerator group used to handle accelerators, owned by this object.
  GtkAccelGroup* accel_group_;

  // The keybinding accelerator registered to show the browser action popup.
  ui::Accelerator keybinding_;

  // The context menu view and model for this extension action.
  scoped_ptr<MenuGtk> context_menu_;
  scoped_refptr<ExtensionContextMenuModel> context_menu_model_;

  friend class BrowserActionsToolbarGtk;
};

// BrowserActionsToolbarGtk ----------------------------------------------------

BrowserActionsToolbarGtk::BrowserActionsToolbarGtk(Browser* browser)
    : browser_(browser),
      profile_(browser->profile()),
      theme_service_(GtkThemeService::GetFrom(browser->profile())),
      model_(NULL),
      hbox_(gtk_hbox_new(FALSE, 0)),
      button_hbox_(gtk_chrome_shrinkable_hbox_new(TRUE, FALSE, kButtonPadding)),
      drag_button_(NULL),
      drop_index_(-1),
      resize_animation_(this),
      desired_width_(0),
      start_width_(0),
      weak_factory_(this) {
  model_ = ExtensionToolbarModel::Get(profile_);
  if (!model_)
    return;

  overflow_button_.reset(new CustomDrawButton(
      theme_service_,
      IDR_BROWSER_ACTIONS_OVERFLOW,
      IDR_BROWSER_ACTIONS_OVERFLOW_P,
      IDR_BROWSER_ACTIONS_OVERFLOW_H,
      0,
      gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE)));

  GtkWidget* gripper = gtk_button_new();
  gtk_widget_set_size_request(gripper, kResizeGripperWidth, -1);
  gtk_widget_set_can_focus(gripper, FALSE);

  gtk_widget_add_events(gripper, GDK_POINTER_MOTION_MASK);
  signals_.Connect(gripper, "motion-notify-event",
                   G_CALLBACK(OnGripperMotionNotifyThunk), this);
  signals_.Connect(gripper, "expose-event",
                   G_CALLBACK(OnGripperExposeThunk), this);
  signals_.Connect(gripper, "enter-notify-event",
                   G_CALLBACK(OnGripperEnterNotifyThunk), this);
  signals_.Connect(gripper, "leave-notify-event",
                   G_CALLBACK(OnGripperLeaveNotifyThunk), this);
  signals_.Connect(gripper, "button-release-event",
                   G_CALLBACK(OnGripperButtonReleaseThunk), this);
  signals_.Connect(gripper, "button-press-event",
                   G_CALLBACK(OnGripperButtonPressThunk), this);
  signals_.Connect(chevron(), "button-press-event",
                   G_CALLBACK(OnOverflowButtonPressThunk), this);

  // |overflow_alignment| adds padding to the right of the browser action
  // buttons, but only appears when the overflow menu is showing.
  overflow_alignment_.Own(gtk_alignment_new(0, 0, 1, 1));
  gtk_container_add(GTK_CONTAINER(overflow_alignment_.get()), chevron());

  // |overflow_area_| holds the overflow chevron and the separator, which
  // is only shown in GTK+ theme mode.
  overflow_area_.Own(gtk_hbox_new(FALSE, 0));
  gtk_box_pack_start(GTK_BOX(overflow_area_.get()), overflow_alignment_.get(),
                     FALSE, FALSE, 0);

  separator_.Own(gtk_vseparator_new());
  gtk_box_pack_start(GTK_BOX(overflow_area_.get()), separator_.get(),
                     FALSE, FALSE, 0);
  gtk_widget_set_no_show_all(separator_.get(), TRUE);

  gtk_widget_show_all(overflow_area_.get());
  gtk_widget_set_no_show_all(overflow_area_.get(), TRUE);

  gtk_box_pack_start(GTK_BOX(hbox_.get()), gripper, FALSE, FALSE, 0);
  gtk_box_pack_start(GTK_BOX(hbox_.get()), button_hbox_.get(), TRUE, TRUE, 0);
  gtk_box_pack_start(GTK_BOX(hbox_.get()), overflow_area_.get(), FALSE, FALSE,
                     0);

  model_->AddObserver(this);
  SetupDrags();

  if (model_->extensions_initialized()) {
    CreateAllButtons();
    SetContainerWidth();
  }

  // We want to connect to "set-focus" on the toplevel window; we have to wait
  // until we are added to a toplevel window to do so.
  signals_.Connect(widget(), "hierarchy-changed",
                   G_CALLBACK(OnHierarchyChangedThunk), this);

  ViewIDUtil::SetID(button_hbox_.get(), VIEW_ID_BROWSER_ACTION_TOOLBAR);

  registrar_.Add(this,
                 chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
                 content::Source<ThemeService>(theme_service_));
  theme_service_->InitThemesFor(this);
}

BrowserActionsToolbarGtk::~BrowserActionsToolbarGtk() {
  if (model_)
    model_->RemoveObserver(this);
  button_hbox_.Destroy();
  hbox_.Destroy();
}

int BrowserActionsToolbarGtk::GetCurrentTabId() const {
  content::WebContents* active_tab =
      browser_->tab_strip_model()->GetActiveWebContents();
  if (!active_tab)
    return -1;

  return SessionTabHelper::FromWebContents(active_tab)->session_id().id();
}

void BrowserActionsToolbarGtk::Update() {
  for (ExtensionButtonMap::iterator iter = extension_button_map_.begin();
       iter != extension_button_map_.end(); ++iter) {
    iter->second->UpdateState();
  }
}

void BrowserActionsToolbarGtk::Observe(
    int type,
    const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  DCHECK(chrome::NOTIFICATION_BROWSER_THEME_CHANGED == type);
  gtk_widget_set_visible(separator_.get(), theme_service_->UsingNativeTheme());
}

void BrowserActionsToolbarGtk::SetupDrags() {
  GtkTargetEntry drag_target = GetDragTargetEntry();
  gtk_drag_dest_set(button_hbox_.get(), GTK_DEST_DEFAULT_DROP, &drag_target, 1,
                    GDK_ACTION_MOVE);

  signals_.Connect(button_hbox_.get(), "drag-motion",
                   G_CALLBACK(OnDragMotionThunk), this);
}

void BrowserActionsToolbarGtk::CreateAllButtons() {
  extension_button_map_.clear();

  int i = 0;
  const extensions::ExtensionList& toolbar_items = model_->toolbar_items();
  for (extensions::ExtensionList::const_iterator iter = toolbar_items.begin();
       iter != toolbar_items.end(); ++iter) {
    CreateButtonForExtension(iter->get(), i++);
  }
}

void BrowserActionsToolbarGtk::SetContainerWidth() {
  int showing_actions = model_->GetVisibleIconCount();
  if (showing_actions >= 0)
    SetButtonHBoxWidth(WidthForIconCount(showing_actions));
}

void BrowserActionsToolbarGtk::CreateButtonForExtension(
    const Extension* extension, int index) {
  if (!ShouldDisplayBrowserAction(extension))
    return;

  if (profile_->IsOffTheRecord())
    index = model_->OriginalIndexToIncognito(index);

  RemoveButtonForExtension(extension);
  linked_ptr<BrowserActionButton> button(
      new BrowserActionButton(this, extension, theme_service_));
  gtk_chrome_shrinkable_hbox_pack_start(
      GTK_CHROME_SHRINKABLE_HBOX(button_hbox_.get()), button->widget(), 0);
  gtk_box_reorder_child(GTK_BOX(button_hbox_.get()), button->widget(), index);
  extension_button_map_[extension->id()] = button;

  GtkTargetEntry drag_target = GetDragTargetEntry();
  gtk_drag_source_set(button->button(), GDK_BUTTON1_MASK, &drag_target, 1,
                      GDK_ACTION_MOVE);
  // We ignore whether the drag was a "success" or "failure" in Gtk's opinion.
  signals_.Connect(button->button(), "drag-end",
                   G_CALLBACK(&OnDragEndThunk), this);
  signals_.Connect(button->button(), "drag-failed",
                   G_CALLBACK(&OnDragFailedThunk), this);

  // Any time a browser action button is shown or hidden we have to update
  // the chevron state.
  signals_.Connect(button->widget(), "show",
                   G_CALLBACK(&OnButtonShowOrHideThunk), this);
  signals_.Connect(button->widget(), "hide",
                   G_CALLBACK(&OnButtonShowOrHideThunk), this);

  gtk_widget_show(button->widget());

  UpdateVisibility();
}

BrowserActionButton* BrowserActionsToolbarGtk::GetBrowserActionButton(
    const Extension* extension) {
  ExtensionButtonMap::iterator it = extension_button_map_.find(
      extension->id());
  return it == extension_button_map_.end() ? NULL : it->second.get();
}

GtkWidget* BrowserActionsToolbarGtk::GetBrowserActionWidget(
    const Extension* extension) {
  BrowserActionButton* button = GetBrowserActionButton(extension);
  return button == NULL ? NULL : button->widget();
}

void BrowserActionsToolbarGtk::RemoveButtonForExtension(
    const Extension* extension) {
  if (extension_button_map_.erase(extension->id()))
    UpdateVisibility();
  UpdateChevronVisibility();
}

void BrowserActionsToolbarGtk::UpdateVisibility() {
  gtk_widget_set_visible(widget(), button_count() != 0);
}

bool BrowserActionsToolbarGtk::ShouldDisplayBrowserAction(
    const Extension* extension) {
  // Only display incognito-enabled extensions while in incognito mode.
  return (!profile_->IsOffTheRecord() ||
          extension_util:: IsIncognitoEnabled(
              extension->id(),
              extensions::ExtensionSystem::Get(profile_)->extension_service()));
}

void BrowserActionsToolbarGtk::HidePopup() {
  ExtensionPopupGtk* popup = ExtensionPopupGtk::get_current_extension_popup();
  if (popup)
    popup->DestroyPopup();
}

void BrowserActionsToolbarGtk::AnimateToShowNIcons(int count) {
  desired_width_ = WidthForIconCount(count);

  GtkAllocation allocation;
  gtk_widget_get_allocation(button_hbox_.get(), &allocation);
  start_width_ = allocation.width;

  resize_animation_.Reset();
  resize_animation_.Show();
}

void BrowserActionsToolbarGtk::BrowserActionAdded(const Extension* extension,
                                                  int index) {
  overflow_menu_.reset();

  CreateButtonForExtension(extension, index);

  // If we are still initializing the container, don't bother animating.
  if (!model_->extensions_initialized())
    return;

  // Animate the addition if we are showing all browser action buttons.
  if (!gtk_widget_get_visible(overflow_area_.get())) {
    AnimateToShowNIcons(button_count());
    model_->SetVisibleIconCount(button_count());
  }
}

void BrowserActionsToolbarGtk::BrowserActionRemoved(
    const Extension* extension) {
  overflow_menu_.reset();

  if (drag_button_ != NULL) {
    // Break the current drag.
    gtk_grab_remove(button_hbox_.get());
  }

  RemoveButtonForExtension(extension);

  if (!gtk_widget_get_visible(overflow_area_.get())) {
    AnimateToShowNIcons(button_count());
    model_->SetVisibleIconCount(button_count());
  }
}

void BrowserActionsToolbarGtk::BrowserActionMoved(const Extension* extension,
                                                  int index) {
  // We initiated this move action, and have already moved the button.
  if (drag_button_ != NULL)
    return;

  GtkWidget* button_widget = GetBrowserActionWidget(extension);
  if (!button_widget) {
    if (ShouldDisplayBrowserAction(extension))
      NOTREACHED();
    return;
  }

  if (profile_->IsOffTheRecord())
    index = model_->OriginalIndexToIncognito(index);

  gtk_box_reorder_child(GTK_BOX(button_hbox_.get()), button_widget, index);
}

bool BrowserActionsToolbarGtk::BrowserActionShowPopup(
    const Extension* extension) {
  // Do not override other popups and only show in active window.
  if (ExtensionPopupGtk::get_current_extension_popup() ||
      !browser_->window()->IsActive()) {
    return false;
  }

  BrowserActionButton* button = GetBrowserActionButton(extension);
  if (button == NULL || button->widget() == NULL)
    return false;

  GtkWidget* anchor = button->widget();
  if (!gtk_widget_get_visible(anchor))
    anchor = button->toolbar_->chevron();
  return button->Activate(anchor, false);
}

void BrowserActionsToolbarGtk::VisibleCountChanged() {
  SetContainerWidth();
}

void BrowserActionsToolbarGtk::AnimationProgressed(
    const gfx::Animation* animation) {
  int width = start_width_ + (desired_width_ - start_width_) *
      animation->GetCurrentValue();
  gtk_widget_set_size_request(button_hbox_.get(), width, -1);

  if (width == desired_width_)
    resize_animation_.Reset();
}

void BrowserActionsToolbarGtk::AnimationEnded(const gfx::Animation* animation) {
  gtk_widget_set_size_request(button_hbox_.get(), desired_width_, -1);
  UpdateChevronVisibility();
}

bool BrowserActionsToolbarGtk::IsCommandIdChecked(int command_id) const {
  return false;
}

bool BrowserActionsToolbarGtk::IsCommandIdEnabled(int command_id) const {
  const Extension* extension = model_->toolbar_items()[command_id].get();
  return ExtensionActionManager::Get(profile_)->GetBrowserAction(*extension)
      ->GetIsVisible(GetCurrentTabId());
}

bool BrowserActionsToolbarGtk::GetAcceleratorForCommandId(
    int command_id,
    ui::Accelerator* accelerator) {
  return false;
}

void BrowserActionsToolbarGtk::ExecuteCommand(int command_id, int event_flags) {
  const Extension* extension = model_->toolbar_items()[command_id].get();
  GURL popup_url;

  switch (model_->ExecuteBrowserAction(
      extension, browser(), &popup_url, true)) {
    case ExtensionToolbarModel::ACTION_NONE:
      break;
    case ExtensionToolbarModel::ACTION_SHOW_POPUP:
      ExtensionPopupGtk::Show(popup_url, browser(), chevron(),
                              ExtensionPopupGtk::SHOW);
      break;
  }
}

void BrowserActionsToolbarGtk::StoppedShowing() {
  overflow_button_->UnsetPaintOverride();
}

bool BrowserActionsToolbarGtk::AlwaysShowIconForCmd(int command_id) const {
  return true;
}

void BrowserActionsToolbarGtk::DragStarted(BrowserActionButton* button,
                                           GdkDragContext* drag_context) {
  // No representation of the widget following the cursor.
  GdkPixbuf* pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, 1, 1);
  gtk_drag_set_icon_pixbuf(drag_context, pixbuf, 0, 0);
  g_object_unref(pixbuf);

  DCHECK(!drag_button_);
  drag_button_ = button;
}

void BrowserActionsToolbarGtk::SetButtonHBoxWidth(int new_width) {
  gint max_width = WidthForIconCount(button_count());
  new_width = std::min(max_width, new_width);
  new_width = std::max(new_width, 0);
  gtk_widget_set_size_request(button_hbox_.get(), new_width, -1);
}

void BrowserActionsToolbarGtk::UpdateChevronVisibility() {
  int showing_icon_count =
      gtk_chrome_shrinkable_hbox_get_visible_child_count(
          GTK_CHROME_SHRINKABLE_HBOX(button_hbox_.get()));
  if (showing_icon_count == 0) {
    gtk_alignment_set_padding(GTK_ALIGNMENT(overflow_alignment_.get()),
                              0, 0, 0, 0);
  } else {
    gtk_alignment_set_padding(GTK_ALIGNMENT(overflow_alignment_.get()),
                              0, 0, kButtonChevronPadding, 0);
  }

  if (button_count() > showing_icon_count) {
    if (!gtk_widget_get_visible(overflow_area_.get())) {
      if (drag_button_) {
        // During drags, when the overflow chevron shows for the first time,
        // take that much space away from |button_hbox_| to make the drag look
        // smoother.
        GtkRequisition req;
        gtk_widget_size_request(chevron(), &req);
        gint overflow_width = req.width;
        gtk_widget_size_request(button_hbox_.get(), &req);
        gint button_hbox_width = req.width;
        button_hbox_width = std::max(button_hbox_width - overflow_width, 0);
        gtk_widget_set_size_request(button_hbox_.get(), button_hbox_width, -1);
      }

      gtk_widget_show(overflow_area_.get());
    }
  } else {
    gtk_widget_hide(overflow_area_.get());
  }
}

gboolean BrowserActionsToolbarGtk::OnDragMotion(GtkWidget* widget,
                                                GdkDragContext* drag_context,
                                                gint x, gint y, guint time) {
  // Only handle drags we initiated.
  if (!drag_button_)
    return FALSE;

  if (base::i18n::IsRTL()) {
    GtkAllocation allocation;
    gtk_widget_get_allocation(widget, &allocation);
    x = allocation.width - x;
  }

  drop_index_ = x < kButtonWidth ? 0 : x / (kButtonWidth + kButtonPadding);

  // We will go ahead and reorder the child in order to provide visual feedback
  // to the user. We don't inform the model that it has moved until the drag
  // ends.
  gtk_box_reorder_child(GTK_BOX(button_hbox_.get()), drag_button_->widget(),
                        drop_index_);

  gdk_drag_status(drag_context, GDK_ACTION_MOVE, time);
  return TRUE;
}

void BrowserActionsToolbarGtk::OnDragEnd(GtkWidget* button,
                                         GdkDragContext* drag_context) {
  if (drop_index_ != -1) {
    if (profile_->IsOffTheRecord())
      drop_index_ = model_->IncognitoIndexToOriginal(drop_index_);

    model_->MoveBrowserAction(drag_button_->extension(), drop_index_);
  }

  drag_button_ = NULL;
  drop_index_ = -1;
}

gboolean BrowserActionsToolbarGtk::OnDragFailed(GtkWidget* widget,
                                                GdkDragContext* drag_context,
                                                GtkDragResult result) {
  // We connect to this signal and return TRUE so that the default failure
  // animation (wherein the drag widget floats back to the start of the drag)
  // does not show, and the drag-end signal is emitted immediately instead of
  // several seconds later.
  return TRUE;
}

void BrowserActionsToolbarGtk::OnHierarchyChanged(
    GtkWidget* widget, GtkWidget* previous_toplevel) {
  GtkWidget* toplevel = gtk_widget_get_toplevel(widget);
  if (!gtk_widget_is_toplevel(toplevel))
    return;

  signals_.Connect(toplevel, "set-focus", G_CALLBACK(OnSetFocusThunk), this);
}

void BrowserActionsToolbarGtk::OnSetFocus(GtkWidget* widget,
                                          GtkWidget* focus_widget) {
  ExtensionPopupGtk* popup = ExtensionPopupGtk::get_current_extension_popup();
  // The focus of the parent window has changed. Close the popup. Delay the hide
  // because it will destroy the RenderViewHost, which may still be on the
  // call stack.
  if (!popup || popup->being_inspected())
    return;
  base::MessageLoop::current()->PostTask(
      FROM_HERE,
      base::Bind(&BrowserActionsToolbarGtk::HidePopup,
                 weak_factory_.GetWeakPtr()));
}

gboolean BrowserActionsToolbarGtk::OnGripperMotionNotify(
    GtkWidget* widget, GdkEventMotion* event) {
  if (!(event->state & GDK_BUTTON1_MASK))
    return FALSE;

  // Calculate how much the user dragged the gripper and subtract that off the
  // button container's width.
  int distance_dragged;
  if (base::i18n::IsRTL()) {
    distance_dragged = -event->x;
  } else {
    GtkAllocation widget_allocation;
    gtk_widget_get_allocation(widget, &widget_allocation);
    distance_dragged = event->x - widget_allocation.width;
  }

  GtkAllocation button_hbox_allocation;
  gtk_widget_get_allocation(button_hbox_.get(), &button_hbox_allocation);
  gint new_width = button_hbox_allocation.width - distance_dragged;
  SetButtonHBoxWidth(new_width);

  return FALSE;
}

gboolean BrowserActionsToolbarGtk::OnGripperExpose(GtkWidget* gripper,
                                                   GdkEventExpose* expose) {
  return TRUE;
}

// These three signal handlers (EnterNotify, LeaveNotify, and ButtonRelease)
// are used to give the gripper the resize cursor. Since it doesn't have its
// own window, we have to set the cursor whenever the pointer moves into the
// button or leaves the button, and be sure to leave it on when the user is
// dragging.
gboolean BrowserActionsToolbarGtk::OnGripperEnterNotify(
    GtkWidget* gripper, GdkEventCrossing* event) {
  gdk_window_set_cursor(gtk_widget_get_window(gripper),
                        gfx::GetCursor(GDK_SB_H_DOUBLE_ARROW));
  return FALSE;
}

gboolean BrowserActionsToolbarGtk::OnGripperLeaveNotify(
    GtkWidget* gripper, GdkEventCrossing* event) {
  if (!(event->state & GDK_BUTTON1_MASK))
    gdk_window_set_cursor(gtk_widget_get_window(gripper), NULL);
  return FALSE;
}

gboolean BrowserActionsToolbarGtk::OnGripperButtonRelease(
    GtkWidget* gripper, GdkEventButton* event) {
  GtkAllocation allocation;
  gtk_widget_get_allocation(gripper, &allocation);
  gfx::Rect gripper_rect(0, 0, allocation.width, allocation.height);

  gfx::Point release_point(event->x, event->y);
  if (!gripper_rect.Contains(release_point))
    gdk_window_set_cursor(gtk_widget_get_window(gripper), NULL);

  // After the user resizes the toolbar, we want to smartly resize it to be
  // the perfect size to fit the buttons.
  int visible_icon_count =
      gtk_chrome_shrinkable_hbox_get_visible_child_count(
          GTK_CHROME_SHRINKABLE_HBOX(button_hbox_.get()));
  AnimateToShowNIcons(visible_icon_count);
  model_->SetVisibleIconCount(visible_icon_count);

  return FALSE;
}

gboolean BrowserActionsToolbarGtk::OnGripperButtonPress(
    GtkWidget* gripper, GdkEventButton* event) {
  resize_animation_.Reset();

  return FALSE;
}

gboolean BrowserActionsToolbarGtk::OnOverflowButtonPress(
    GtkWidget* overflow, GdkEventButton* event) {
  overflow_menu_model_.reset(new SimpleMenuModel(this));

  int visible_icon_count =
      gtk_chrome_shrinkable_hbox_get_visible_child_count(
          GTK_CHROME_SHRINKABLE_HBOX(button_hbox_.get()));
  for (int i = visible_icon_count; i < button_count(); ++i) {
    int model_index = i;
    if (profile_->IsOffTheRecord())
      model_index = model_->IncognitoIndexToOriginal(i);

    const Extension* extension = model_->toolbar_items()[model_index].get();
    BrowserActionButton* button = extension_button_map_[extension->id()].get();

    overflow_menu_model_->AddItem(model_index, UTF8ToUTF16(extension->name()));
    overflow_menu_model_->SetIcon(overflow_menu_model_->GetItemCount() - 1,
                                  button->GetIcon());

    // TODO(estade): set the menu item's tooltip.
  }

  overflow_menu_.reset(new MenuGtk(this, overflow_menu_model_.get()));
  signals_.Connect(overflow_menu_->widget(), "button-press-event",
                   G_CALLBACK(OnOverflowMenuButtonPressThunk), this);

  overflow_button_->SetPaintOverride(GTK_STATE_ACTIVE);
  overflow_menu_->PopupAsFromKeyEvent(chevron());

  return FALSE;
}

gboolean BrowserActionsToolbarGtk::OnOverflowMenuButtonPress(
    GtkWidget* overflow, GdkEventButton* event) {
  if (event->button != 3)
    return FALSE;

  GtkWidget* menu_item = GTK_MENU_SHELL(overflow)->active_menu_item;
  if (!menu_item)
    return FALSE;

  int item_index = g_list_index(GTK_MENU_SHELL(overflow)->children, menu_item);
  if (item_index == -1) {
    NOTREACHED();
    return FALSE;
  }

  item_index += gtk_chrome_shrinkable_hbox_get_visible_child_count(
      GTK_CHROME_SHRINKABLE_HBOX(button_hbox_.get()));
  if (profile_->IsOffTheRecord())
    item_index = model_->IncognitoIndexToOriginal(item_index);

  const Extension* extension = model_->toolbar_items()[item_index].get();
  BrowserActionButton* button = GetBrowserActionButton(extension);
  if (button == NULL) {
    NOTREACHED();
    return FALSE;
  }

  MenuGtk* menu = button->GetContextMenu();
  if (!menu)
    return FALSE;

  menu->PopupAsContext(gfx::Point(event->x_root, event->y_root),
                       event->time);
  return TRUE;
}

void BrowserActionsToolbarGtk::OnButtonShowOrHide(GtkWidget* sender) {
  if (!resize_animation_.is_animating())
    UpdateChevronVisibility();
}