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