Disable TabDragController tests that fail with a real compositor.
[chromium-blink-merge.git] / chrome / browser / ui / gtk / browser_actions_toolbar_gtk.cc
blob2df92c07179a21e230b5a470f459bcbfbbd62007
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.
5 #include "chrome/browser/ui/gtk/browser_actions_toolbar_gtk.h"
7 #include <gtk/gtk.h>
9 #include <algorithm>
10 #include <utility>
11 #include <vector>
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_system.h"
25 #include "chrome/browser/extensions/extension_toolbar_model.h"
26 #include "chrome/browser/extensions/extension_util.h"
27 #include "chrome/browser/profiles/profile.h"
28 #include "chrome/browser/sessions/session_tab_helper.h"
29 #include "chrome/browser/ui/browser.h"
30 #include "chrome/browser/ui/gtk/browser_window_gtk.h"
31 #include "chrome/browser/ui/gtk/custom_button.h"
32 #include "chrome/browser/ui/gtk/extensions/extension_popup_gtk.h"
33 #include "chrome/browser/ui/gtk/gtk_chrome_button.h"
34 #include "chrome/browser/ui/gtk/gtk_chrome_shrinkable_hbox.h"
35 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
36 #include "chrome/browser/ui/gtk/gtk_util.h"
37 #include "chrome/browser/ui/gtk/hover_controller_gtk.h"
38 #include "chrome/browser/ui/gtk/menu_gtk.h"
39 #include "chrome/browser/ui/gtk/view_id_util.h"
40 #include "chrome/browser/ui/tabs/tab_strip_model.h"
41 #include "content/public/browser/notification_details.h"
42 #include "content/public/browser/notification_source.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"
55 using extensions::Extension;
56 using extensions::ExtensionActionManager;
58 namespace {
60 // The width of the browser action buttons.
61 const int kButtonWidth = 27;
63 // The padding between browser action buttons.
64 const int kButtonPadding = 4;
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;
70 // Width of the invisible gripper for resizing the toolbar.
71 const int kResizeGripperWidth = 4;
73 const char kDragTarget[] = "application/x-chrome-browseraction";
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;
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);
90 } // namespace
92 using ui::SimpleMenuModel;
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,
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());
120 DCHECK(browser_action());
122 UpdateState();
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);
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()));
160 virtual ~BrowserActionButton() {
161 DisconnectBrowserActionPopupAccelerator();
163 alignment_.Destroy();
166 GtkWidget* button() { return button_->widget(); }
168 GtkWidget* widget() { return alignment_.get(); }
170 const Extension* extension() { return extension_; }
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();
197 break;
199 default:
200 NOTREACHED();
201 break;
205 // ExtensionActionIconFactory::Observer implementation.
206 virtual void OnIconUpdated() OVERRIDE {
207 UpdateState();
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;
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());
223 enabled_ = browser_action()->GetIsVisible(tab_id);
224 if (!enabled_)
225 button_->SetPaintOverride(GTK_STATE_INSENSITIVE);
226 else
227 button_->UnsetPaintOverride();
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)));
239 gtk_widget_queue_draw(button());
242 gfx::Image GetIcon() {
243 return icon_factory_.GetIcon(toolbar_->GetCurrentTabId());
246 MenuGtk* GetContextMenu() {
247 if (!extension_->ShowConfigureContextMenus())
248 return NULL;
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();
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;
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;
276 return false;
279 // MenuGtk::Delegate implementation.
280 virtual void StoppedShowing() OVERRIDE {
281 if (enabled_)
282 button_->UnsetPaintOverride();
283 else
284 button_->SetPaintOverride(GTK_STATE_INSENSITIVE);
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());
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();
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);
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());
315 static gboolean OnButtonPress(GtkWidget* widget,
316 GdkEventButton* event,
317 BrowserActionButton* button) {
318 if (event->button != 3)
319 return FALSE;
321 MenuGtk* menu = button->GetContextMenu();
322 if (!menu)
323 return FALSE;
325 button->button_->SetPaintOverride(GTK_STATE_ACTIVE);
326 menu->PopupForWidget(widget, event->button, event->time);
328 return TRUE;
331 static void OnClicked(GtkWidget* widget, BrowserActionButton* button) {
332 if (button->enabled_)
333 button->Activate(widget, true);
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;
343 ExtensionAction* action = button->browser_action();
344 if (action->GetBadgeText(tab_id).empty())
345 return FALSE;
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;
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);
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;
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();
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();
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_);
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));
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));
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();
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));
439 ExtensionAction* browser_action() const {
440 return ExtensionActionManager::Get(toolbar_->browser()->profile())->
441 GetBrowserAction(*extension_);
444 // The toolbar containing this button.
445 BrowserActionsToolbarGtk* toolbar_;
447 // The extension that contains this browser action.
448 const Extension* extension_;
450 // The button for this browser action.
451 scoped_ptr<CustomDrawButton> button_;
453 // Whether the browser action is enabled (equivalent to whether a page action
454 // is visible).
455 bool enabled_;
457 // The top level widget (parent of |button_|).
458 ui::OwnedWidgetGtk alignment_;
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_;
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_;
472 // Same as |default_icon_|, but stored as SkBitmap.
473 SkBitmap default_skbitmap_;
475 ui::GtkSignalRegistrar signals_;
476 content::NotificationRegistrar registrar_;
478 // The accelerator group used to handle accelerators, owned by this object.
479 GtkAccelGroup* accel_group_;
481 // The keybinding accelerator registered to show the browser action popup.
482 ui::Accelerator keybinding_;
484 // The context menu view and model for this extension action.
485 scoped_ptr<MenuGtk> context_menu_;
486 scoped_refptr<ExtensionContextMenuModel> context_menu_model_;
488 friend class BrowserActionsToolbarGtk;
491 // BrowserActionsToolbarGtk ----------------------------------------------------
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;
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,
516 gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE)));
518 GtkWidget* gripper = gtk_button_new();
519 gtk_widget_set_size_request(gripper, kResizeGripperWidth, -1);
520 gtk_widget_set_can_focus(gripper, FALSE);
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);
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());
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);
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);
554 gtk_widget_show_all(overflow_area_.get());
555 gtk_widget_set_no_show_all(overflow_area_.get(), TRUE);
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,
562 model_->AddObserver(this);
563 SetupDrags();
565 if (model_->extensions_initialized()) {
566 CreateAllButtons();
567 SetContainerWidth();
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);
575 ViewIDUtil::SetID(button_hbox_.get(), VIEW_ID_BROWSER_ACTION_TOOLBAR);
577 registrar_.Add(this,
578 chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
579 content::Source<ThemeService>(theme_service_));
580 theme_service_->InitThemesFor(this);
583 BrowserActionsToolbarGtk::~BrowserActionsToolbarGtk() {
584 if (model_)
585 model_->RemoveObserver(this);
586 button_hbox_.Destroy();
587 hbox_.Destroy();
590 int BrowserActionsToolbarGtk::GetCurrentTabId() const {
591 content::WebContents* active_tab =
592 browser_->tab_strip_model()->GetActiveWebContents();
593 if (!active_tab)
594 return -1;
596 return SessionTabHelper::FromWebContents(active_tab)->session_id().id();
599 void BrowserActionsToolbarGtk::Update() {
600 for (ExtensionButtonMap::iterator iter = extension_button_map_.begin();
601 iter != extension_button_map_.end(); ++iter) {
602 iter->second->UpdateState();
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());
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);
619 signals_.Connect(button_hbox_.get(), "drag-motion",
620 G_CALLBACK(OnDragMotionThunk), this);
623 void BrowserActionsToolbarGtk::CreateAllButtons() {
624 extension_button_map_.clear();
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++);
634 void BrowserActionsToolbarGtk::SetContainerWidth() {
635 int showing_actions = model_->GetVisibleIconCount();
636 if (showing_actions >= 0)
637 SetButtonHBoxWidth(WidthForIconCount(showing_actions));
640 void BrowserActionsToolbarGtk::CreateButtonForExtension(
641 const Extension* extension, int index) {
642 if (!ShouldDisplayBrowserAction(extension))
643 return;
645 if (profile_->IsOffTheRecord())
646 index = model_->OriginalIndexToIncognito(index);
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;
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);
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);
672 gtk_widget_show(button->widget());
674 UpdateVisibility();
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();
684 GtkWidget* BrowserActionsToolbarGtk::GetBrowserActionWidget(
685 const Extension* extension) {
686 BrowserActionButton* button = GetBrowserActionButton(extension);
687 return button == NULL ? NULL : button->widget();
690 void BrowserActionsToolbarGtk::RemoveButtonForExtension(
691 const Extension* extension) {
692 if (extension_button_map_.erase(extension->id()))
693 UpdateVisibility();
694 UpdateChevronVisibility();
697 void BrowserActionsToolbarGtk::UpdateVisibility() {
698 gtk_widget_set_visible(widget(), button_count() != 0);
701 bool BrowserActionsToolbarGtk::ShouldDisplayBrowserAction(
702 const Extension* extension) {
703 // Only display incognito-enabled extensions while in incognito mode.
704 return (!profile_->IsOffTheRecord() ||
705 extension_util:: IsIncognitoEnabled(
706 extension->id(),
707 extensions::ExtensionSystem::Get(profile_)->extension_service()));
710 void BrowserActionsToolbarGtk::HidePopup() {
711 ExtensionPopupGtk* popup = ExtensionPopupGtk::get_current_extension_popup();
712 if (popup)
713 popup->DestroyPopup();
716 void BrowserActionsToolbarGtk::AnimateToShowNIcons(int count) {
717 desired_width_ = WidthForIconCount(count);
719 GtkAllocation allocation;
720 gtk_widget_get_allocation(button_hbox_.get(), &allocation);
721 start_width_ = allocation.width;
723 resize_animation_.Reset();
724 resize_animation_.Show();
727 void BrowserActionsToolbarGtk::BrowserActionAdded(const Extension* extension,
728 int index) {
729 overflow_menu_.reset();
731 CreateButtonForExtension(extension, index);
733 // If we are still initializing the container, don't bother animating.
734 if (!model_->extensions_initialized())
735 return;
737 // Animate the addition if we are showing all browser action buttons.
738 if (!gtk_widget_get_visible(overflow_area_.get())) {
739 AnimateToShowNIcons(button_count());
740 model_->SetVisibleIconCount(button_count());
744 void BrowserActionsToolbarGtk::BrowserActionRemoved(
745 const Extension* extension) {
746 overflow_menu_.reset();
748 if (drag_button_ != NULL) {
749 // Break the current drag.
750 gtk_grab_remove(button_hbox_.get());
753 RemoveButtonForExtension(extension);
755 if (!gtk_widget_get_visible(overflow_area_.get())) {
756 AnimateToShowNIcons(button_count());
757 model_->SetVisibleIconCount(button_count());
761 void BrowserActionsToolbarGtk::BrowserActionMoved(const Extension* extension,
762 int index) {
763 // We initiated this move action, and have already moved the button.
764 if (drag_button_ != NULL)
765 return;
767 GtkWidget* button_widget = GetBrowserActionWidget(extension);
768 if (!button_widget) {
769 if (ShouldDisplayBrowserAction(extension))
770 NOTREACHED();
771 return;
774 if (profile_->IsOffTheRecord())
775 index = model_->OriginalIndexToIncognito(index);
777 gtk_box_reorder_child(GTK_BOX(button_hbox_.get()), button_widget, index);
780 bool BrowserActionsToolbarGtk::BrowserActionShowPopup(
781 const Extension* extension) {
782 // Do not override other popups and only show in active window.
783 if (ExtensionPopupGtk::get_current_extension_popup() ||
784 !browser_->window()->IsActive()) {
785 return false;
788 BrowserActionButton* button = GetBrowserActionButton(extension);
789 if (button == NULL || button->widget() == NULL)
790 return false;
792 GtkWidget* anchor = button->widget();
793 if (!gtk_widget_get_visible(anchor))
794 anchor = button->toolbar_->chevron();
795 return button->Activate(anchor, false);
798 void BrowserActionsToolbarGtk::VisibleCountChanged() {
799 SetContainerWidth();
802 void BrowserActionsToolbarGtk::AnimationProgressed(
803 const gfx::Animation* animation) {
804 int width = start_width_ + (desired_width_ - start_width_) *
805 animation->GetCurrentValue();
806 gtk_widget_set_size_request(button_hbox_.get(), width, -1);
808 if (width == desired_width_)
809 resize_animation_.Reset();
812 void BrowserActionsToolbarGtk::AnimationEnded(const gfx::Animation* animation) {
813 gtk_widget_set_size_request(button_hbox_.get(), desired_width_, -1);
814 UpdateChevronVisibility();
817 bool BrowserActionsToolbarGtk::IsCommandIdChecked(int command_id) const {
818 return false;
821 bool BrowserActionsToolbarGtk::IsCommandIdEnabled(int command_id) const {
822 const Extension* extension = model_->toolbar_items()[command_id].get();
823 return ExtensionActionManager::Get(profile_)->GetBrowserAction(*extension)
824 ->GetIsVisible(GetCurrentTabId());
827 bool BrowserActionsToolbarGtk::GetAcceleratorForCommandId(
828 int command_id,
829 ui::Accelerator* accelerator) {
830 return false;
833 void BrowserActionsToolbarGtk::ExecuteCommand(int command_id, int event_flags) {
834 const Extension* extension = model_->toolbar_items()[command_id].get();
835 GURL popup_url;
837 switch (model_->ExecuteBrowserAction(
838 extension, browser(), &popup_url, true)) {
839 case ExtensionToolbarModel::ACTION_NONE:
840 break;
841 case ExtensionToolbarModel::ACTION_SHOW_POPUP:
842 ExtensionPopupGtk::Show(popup_url, browser(), chevron(),
843 ExtensionPopupGtk::SHOW);
844 break;
848 void BrowserActionsToolbarGtk::StoppedShowing() {
849 overflow_button_->UnsetPaintOverride();
852 bool BrowserActionsToolbarGtk::AlwaysShowIconForCmd(int command_id) const {
853 return true;
856 void BrowserActionsToolbarGtk::DragStarted(BrowserActionButton* button,
857 GdkDragContext* drag_context) {
858 // No representation of the widget following the cursor.
859 GdkPixbuf* pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, 1, 1);
860 gtk_drag_set_icon_pixbuf(drag_context, pixbuf, 0, 0);
861 g_object_unref(pixbuf);
863 DCHECK(!drag_button_);
864 drag_button_ = button;
867 void BrowserActionsToolbarGtk::SetButtonHBoxWidth(int new_width) {
868 gint max_width = WidthForIconCount(button_count());
869 new_width = std::min(max_width, new_width);
870 new_width = std::max(new_width, 0);
871 gtk_widget_set_size_request(button_hbox_.get(), new_width, -1);
874 void BrowserActionsToolbarGtk::UpdateChevronVisibility() {
875 int showing_icon_count =
876 gtk_chrome_shrinkable_hbox_get_visible_child_count(
877 GTK_CHROME_SHRINKABLE_HBOX(button_hbox_.get()));
878 if (showing_icon_count == 0) {
879 gtk_alignment_set_padding(GTK_ALIGNMENT(overflow_alignment_.get()),
880 0, 0, 0, 0);
881 } else {
882 gtk_alignment_set_padding(GTK_ALIGNMENT(overflow_alignment_.get()),
883 0, 0, kButtonChevronPadding, 0);
886 if (button_count() > showing_icon_count) {
887 if (!gtk_widget_get_visible(overflow_area_.get())) {
888 if (drag_button_) {
889 // During drags, when the overflow chevron shows for the first time,
890 // take that much space away from |button_hbox_| to make the drag look
891 // smoother.
892 GtkRequisition req;
893 gtk_widget_size_request(chevron(), &req);
894 gint overflow_width = req.width;
895 gtk_widget_size_request(button_hbox_.get(), &req);
896 gint button_hbox_width = req.width;
897 button_hbox_width = std::max(button_hbox_width - overflow_width, 0);
898 gtk_widget_set_size_request(button_hbox_.get(), button_hbox_width, -1);
901 gtk_widget_show(overflow_area_.get());
903 } else {
904 gtk_widget_hide(overflow_area_.get());
908 gboolean BrowserActionsToolbarGtk::OnDragMotion(GtkWidget* widget,
909 GdkDragContext* drag_context,
910 gint x, gint y, guint time) {
911 // Only handle drags we initiated.
912 if (!drag_button_)
913 return FALSE;
915 if (base::i18n::IsRTL()) {
916 GtkAllocation allocation;
917 gtk_widget_get_allocation(widget, &allocation);
918 x = allocation.width - x;
921 drop_index_ = x < kButtonWidth ? 0 : x / (kButtonWidth + kButtonPadding);
923 // We will go ahead and reorder the child in order to provide visual feedback
924 // to the user. We don't inform the model that it has moved until the drag
925 // ends.
926 gtk_box_reorder_child(GTK_BOX(button_hbox_.get()), drag_button_->widget(),
927 drop_index_);
929 gdk_drag_status(drag_context, GDK_ACTION_MOVE, time);
930 return TRUE;
933 void BrowserActionsToolbarGtk::OnDragEnd(GtkWidget* button,
934 GdkDragContext* drag_context) {
935 if (drop_index_ != -1) {
936 if (profile_->IsOffTheRecord())
937 drop_index_ = model_->IncognitoIndexToOriginal(drop_index_);
939 model_->MoveBrowserAction(drag_button_->extension(), drop_index_);
942 drag_button_ = NULL;
943 drop_index_ = -1;
946 gboolean BrowserActionsToolbarGtk::OnDragFailed(GtkWidget* widget,
947 GdkDragContext* drag_context,
948 GtkDragResult result) {
949 // We connect to this signal and return TRUE so that the default failure
950 // animation (wherein the drag widget floats back to the start of the drag)
951 // does not show, and the drag-end signal is emitted immediately instead of
952 // several seconds later.
953 return TRUE;
956 void BrowserActionsToolbarGtk::OnHierarchyChanged(
957 GtkWidget* widget, GtkWidget* previous_toplevel) {
958 GtkWidget* toplevel = gtk_widget_get_toplevel(widget);
959 if (!gtk_widget_is_toplevel(toplevel))
960 return;
962 signals_.Connect(toplevel, "set-focus", G_CALLBACK(OnSetFocusThunk), this);
965 void BrowserActionsToolbarGtk::OnSetFocus(GtkWidget* widget,
966 GtkWidget* focus_widget) {
967 ExtensionPopupGtk* popup = ExtensionPopupGtk::get_current_extension_popup();
968 // The focus of the parent window has changed. Close the popup. Delay the hide
969 // because it will destroy the RenderViewHost, which may still be on the
970 // call stack.
971 if (!popup || popup->being_inspected())
972 return;
973 base::MessageLoop::current()->PostTask(
974 FROM_HERE,
975 base::Bind(&BrowserActionsToolbarGtk::HidePopup,
976 weak_factory_.GetWeakPtr()));
979 gboolean BrowserActionsToolbarGtk::OnGripperMotionNotify(
980 GtkWidget* widget, GdkEventMotion* event) {
981 if (!(event->state & GDK_BUTTON1_MASK))
982 return FALSE;
984 // Calculate how much the user dragged the gripper and subtract that off the
985 // button container's width.
986 int distance_dragged;
987 if (base::i18n::IsRTL()) {
988 distance_dragged = -event->x;
989 } else {
990 GtkAllocation widget_allocation;
991 gtk_widget_get_allocation(widget, &widget_allocation);
992 distance_dragged = event->x - widget_allocation.width;
995 GtkAllocation button_hbox_allocation;
996 gtk_widget_get_allocation(button_hbox_.get(), &button_hbox_allocation);
997 gint new_width = button_hbox_allocation.width - distance_dragged;
998 SetButtonHBoxWidth(new_width);
1000 return FALSE;
1003 gboolean BrowserActionsToolbarGtk::OnGripperExpose(GtkWidget* gripper,
1004 GdkEventExpose* expose) {
1005 return TRUE;
1008 // These three signal handlers (EnterNotify, LeaveNotify, and ButtonRelease)
1009 // are used to give the gripper the resize cursor. Since it doesn't have its
1010 // own window, we have to set the cursor whenever the pointer moves into the
1011 // button or leaves the button, and be sure to leave it on when the user is
1012 // dragging.
1013 gboolean BrowserActionsToolbarGtk::OnGripperEnterNotify(
1014 GtkWidget* gripper, GdkEventCrossing* event) {
1015 gdk_window_set_cursor(gtk_widget_get_window(gripper),
1016 gfx::GetCursor(GDK_SB_H_DOUBLE_ARROW));
1017 return FALSE;
1020 gboolean BrowserActionsToolbarGtk::OnGripperLeaveNotify(
1021 GtkWidget* gripper, GdkEventCrossing* event) {
1022 if (!(event->state & GDK_BUTTON1_MASK))
1023 gdk_window_set_cursor(gtk_widget_get_window(gripper), NULL);
1024 return FALSE;
1027 gboolean BrowserActionsToolbarGtk::OnGripperButtonRelease(
1028 GtkWidget* gripper, GdkEventButton* event) {
1029 GtkAllocation allocation;
1030 gtk_widget_get_allocation(gripper, &allocation);
1031 gfx::Rect gripper_rect(0, 0, allocation.width, allocation.height);
1033 gfx::Point release_point(event->x, event->y);
1034 if (!gripper_rect.Contains(release_point))
1035 gdk_window_set_cursor(gtk_widget_get_window(gripper), NULL);
1037 // After the user resizes the toolbar, we want to smartly resize it to be
1038 // the perfect size to fit the buttons.
1039 int visible_icon_count =
1040 gtk_chrome_shrinkable_hbox_get_visible_child_count(
1041 GTK_CHROME_SHRINKABLE_HBOX(button_hbox_.get()));
1042 AnimateToShowNIcons(visible_icon_count);
1043 model_->SetVisibleIconCount(visible_icon_count);
1045 return FALSE;
1048 gboolean BrowserActionsToolbarGtk::OnGripperButtonPress(
1049 GtkWidget* gripper, GdkEventButton* event) {
1050 resize_animation_.Reset();
1052 return FALSE;
1055 gboolean BrowserActionsToolbarGtk::OnOverflowButtonPress(
1056 GtkWidget* overflow, GdkEventButton* event) {
1057 overflow_menu_model_.reset(new SimpleMenuModel(this));
1059 int visible_icon_count =
1060 gtk_chrome_shrinkable_hbox_get_visible_child_count(
1061 GTK_CHROME_SHRINKABLE_HBOX(button_hbox_.get()));
1062 for (int i = visible_icon_count; i < button_count(); ++i) {
1063 int model_index = i;
1064 if (profile_->IsOffTheRecord())
1065 model_index = model_->IncognitoIndexToOriginal(i);
1067 const Extension* extension = model_->toolbar_items()[model_index].get();
1068 BrowserActionButton* button = extension_button_map_[extension->id()].get();
1070 overflow_menu_model_->AddItem(model_index,
1071 base::UTF8ToUTF16(extension->name()));
1072 overflow_menu_model_->SetIcon(overflow_menu_model_->GetItemCount() - 1,
1073 button->GetIcon());
1075 // TODO(estade): set the menu item's tooltip.
1078 overflow_menu_.reset(new MenuGtk(this, overflow_menu_model_.get()));
1079 signals_.Connect(overflow_menu_->widget(), "button-press-event",
1080 G_CALLBACK(OnOverflowMenuButtonPressThunk), this);
1082 overflow_button_->SetPaintOverride(GTK_STATE_ACTIVE);
1083 overflow_menu_->PopupAsFromKeyEvent(chevron());
1085 return FALSE;
1088 gboolean BrowserActionsToolbarGtk::OnOverflowMenuButtonPress(
1089 GtkWidget* overflow, GdkEventButton* event) {
1090 if (event->button != 3)
1091 return FALSE;
1093 GtkWidget* menu_item = GTK_MENU_SHELL(overflow)->active_menu_item;
1094 if (!menu_item)
1095 return FALSE;
1097 int item_index = g_list_index(GTK_MENU_SHELL(overflow)->children, menu_item);
1098 if (item_index == -1) {
1099 NOTREACHED();
1100 return FALSE;
1103 item_index += gtk_chrome_shrinkable_hbox_get_visible_child_count(
1104 GTK_CHROME_SHRINKABLE_HBOX(button_hbox_.get()));
1105 if (profile_->IsOffTheRecord())
1106 item_index = model_->IncognitoIndexToOriginal(item_index);
1108 const Extension* extension = model_->toolbar_items()[item_index].get();
1109 BrowserActionButton* button = GetBrowserActionButton(extension);
1110 if (button == NULL) {
1111 NOTREACHED();
1112 return FALSE;
1115 MenuGtk* menu = button->GetContextMenu();
1116 if (!menu)
1117 return FALSE;
1119 menu->PopupAsContext(gfx::Point(event->x_root, event->y_root),
1120 event->time);
1121 return TRUE;
1124 void BrowserActionsToolbarGtk::OnButtonShowOrHide(GtkWidget* sender) {
1125 if (!resize_animation_.is_animating())
1126 UpdateChevronVisibility();