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"
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
;
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
;
83 // The minimum width in pixels of the button hbox if |icon_count| icons are
85 gint
WidthForIconCount(gint icon_count
) {
86 return std::max((kButtonWidth
+ kButtonPadding
) * icon_count
- kButtonPadding
,
92 using ui::SimpleMenuModel
;
94 class BrowserActionButton
: public content::NotificationObserver
,
95 public ExtensionActionIconFactory::Observer
,
96 public ExtensionContextMenuModel::PopupDelegate
,
97 public MenuGtk::Delegate
{
99 BrowserActionButton(BrowserActionsToolbarGtk
* toolbar
,
100 const Extension
* extension
,
101 GtkThemeService
* theme_provider
)
103 extension_(extension
),
105 icon_factory_(toolbar
->browser()->profile(), extension
,
106 browser_action(), this),
108 button_
.reset(new CustomDrawButton(
111 IDR_BROWSER_ACTION_P
,
112 IDR_BROWSER_ACTION_H
,
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());
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
136 ConnectBrowserActionPopupAccelerator();
138 // Window doesn't exist yet, wait for it.
139 signals_
.Connect(toolbar
->widget(), "realize",
140 G_CALLBACK(OnRealize
), this);
144 this, chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_UPDATED
,
145 content::Source
<ExtensionAction
>(browser_action()));
147 this, chrome::NOTIFICATION_EXTENSION_UNLOADED
,
148 content::Source
<Profile
>(
149 toolbar
->browser()->profile()->GetOriginalProfile()));
151 this, chrome::NOTIFICATION_EXTENSION_COMMAND_ADDED
,
152 content::Source
<Profile
>(
153 toolbar
->browser()->profile()->GetOriginalProfile()));
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
{
177 case chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_UPDATED
:
180 case chrome::NOTIFICATION_EXTENSION_UNLOADED
:
181 case chrome::NOTIFICATION_WINDOW_CLOSED
:
182 DisconnectBrowserActionPopupAccelerator();
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
> >(
189 if (extension_
->id() == payload
->first
&&
191 extensions::manifest_values::kBrowserActionCommandEvent
) {
192 if (type
== chrome::NOTIFICATION_EXTENSION_COMMAND_ADDED
)
193 ConnectBrowserActionPopupAccelerator();
195 DisconnectBrowserActionPopupAccelerator();
205 // ExtensionActionIconFactory::Observer implementation.
206 virtual void OnIconUpdated() OVERRIDE
{
210 // Updates the button based on the latest state from the associated
213 int tab_id
= toolbar_
->GetCurrentTabId();
217 std::string tooltip
= browser_action()->GetTitle(tab_id
);
219 gtk_widget_set_has_tooltip(button(), FALSE
);
221 gtk_widget_set_tooltip_text(button(), tooltip
.c_str());
223 enabled_
= browser_action()->GetIsVisible(tab_id
);
225 button_
->SetPaintOverride(GTK_STATE_INSENSITIVE
);
227 button_
->UnsetPaintOverride();
229 gfx::Image image
= icon_factory_
.GetIcon(tab_id
);
230 if (!image
.IsEmpty()) {
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())
250 context_menu_model_
=
251 new ExtensionContextMenuModel(extension_
, toolbar_
->browser(), this);
253 new MenuGtk(this, context_menu_model_
.get()));
254 return context_menu_
.get();
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();
267 switch (model
->ExecuteBrowserAction(
268 extension
, browser
, &popup_url
, should_grant
)) {
269 case ExtensionToolbarModel::ACTION_NONE
:
271 case ExtensionToolbarModel::ACTION_SHOW_POPUP
:
272 ExtensionPopupGtk::Show(popup_url
, browser
, widget
,
273 ExtensionPopupGtk::SHOW
);
279 // MenuGtk::Delegate implementation.
280 virtual void StoppedShowing() OVERRIDE
{
282 button_
->UnsetPaintOverride();
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
) {
308 image_
= gtk_image_new_from_pixbuf(image
.ToGdkPixbuf());
309 gtk_button_set_image(GTK_BUTTON(button()), image_
);
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)
321 MenuGtk
* menu
= button
->GetContextMenu();
325 button
->button_
->SetPaintOverride(GTK_STATE_ACTIVE
);
326 menu
->PopupForWidget(widget
, event
->button
, event
->time
);
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();
343 ExtensionAction
* action
= button
->browser_action();
344 if (action
->GetBadgeText(tab_id
).empty())
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
);
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
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
,
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);
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
,
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(
404 ui::GetGdkKeyCodeForAccelerator(keybinding_
),
405 ui::GetGdkModifierForAccelerator(keybinding_
),
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.
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() {
421 gfx::NativeWindow window
=
422 toolbar_
->browser()->window()->GetNativeWindow();
423 gtk_accel_group_disconnect_key(
425 ui::GetGdkKeyCodeForAccelerator(keybinding_
),
426 GetGdkModifierForAccelerator(keybinding_
));
427 gtk_window_remove_accel_group(window
, accel_group_
);
428 g_object_unref(accel_group_
);
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
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.
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
)
495 profile_(browser
->profile()),
496 theme_service_(GtkThemeService::GetFrom(browser
->profile())),
498 hbox_(gtk_hbox_new(FALSE
, 0)),
499 button_hbox_(gtk_chrome_shrinkable_hbox_new(TRUE
, FALSE
, kButtonPadding
)),
502 resize_animation_(this),
505 weak_factory_(this) {
506 model_
= ExtensionToolbarModel::Get(profile_
);
510 overflow_button_
.reset(new CustomDrawButton(
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(),
549 separator_
.Own(gtk_vseparator_new());
550 gtk_box_pack_start(GTK_BOX(overflow_area_
.get()), separator_
.get(),
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);
565 if (model_
->extensions_initialized()) {
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
);
578 chrome::NOTIFICATION_BROWSER_THEME_CHANGED
,
579 content::Source
<ThemeService
>(theme_service_
));
580 theme_service_
->InitThemesFor(this);
583 BrowserActionsToolbarGtk::~BrowserActionsToolbarGtk() {
585 model_
->RemoveObserver(this);
586 button_hbox_
.Destroy();
590 int BrowserActionsToolbarGtk::GetCurrentTabId() const {
591 content::WebContents
* active_tab
=
592 browser_
->tab_strip_model()->GetActiveWebContents();
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(
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,
619 signals_
.Connect(button_hbox_
.get(), "drag-motion",
620 G_CALLBACK(OnDragMotionThunk
), this);
623 void BrowserActionsToolbarGtk::CreateAllButtons() {
624 extension_button_map_
.clear();
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
))
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,
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());
677 BrowserActionButton
* BrowserActionsToolbarGtk::GetBrowserActionButton(
678 const Extension
* extension
) {
679 ExtensionButtonMap::iterator it
= extension_button_map_
.find(
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()))
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(
707 extensions::ExtensionSystem::Get(profile_
)->extension_service()));
710 void BrowserActionsToolbarGtk::HidePopup() {
711 ExtensionPopupGtk
* popup
= ExtensionPopupGtk::get_current_extension_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
,
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())
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
,
763 // We initiated this move action, and have already moved the button.
764 if (drag_button_
!= NULL
)
767 GtkWidget
* button_widget
= GetBrowserActionWidget(extension
);
768 if (!button_widget
) {
769 if (ShouldDisplayBrowserAction(extension
))
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()) {
788 BrowserActionButton
* button
= GetBrowserActionButton(extension
);
789 if (button
== NULL
|| button
->widget() == NULL
)
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() {
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 {
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(
829 ui::Accelerator
* accelerator
) {
833 void BrowserActionsToolbarGtk::ExecuteCommand(int command_id
, int event_flags
) {
834 const Extension
* extension
= model_
->toolbar_items()[command_id
].get();
837 switch (model_
->ExecuteBrowserAction(
838 extension
, browser(), &popup_url
, true)) {
839 case ExtensionToolbarModel::ACTION_NONE
:
841 case ExtensionToolbarModel::ACTION_SHOW_POPUP
:
842 ExtensionPopupGtk::Show(popup_url
, browser(), chevron(),
843 ExtensionPopupGtk::SHOW
);
848 void BrowserActionsToolbarGtk::StoppedShowing() {
849 overflow_button_
->UnsetPaintOverride();
852 bool BrowserActionsToolbarGtk::AlwaysShowIconForCmd(int command_id
) const {
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()),
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())) {
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
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());
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.
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
926 gtk_box_reorder_child(GTK_BOX(button_hbox_
.get()), drag_button_
->widget(),
929 gdk_drag_status(drag_context
, GDK_ACTION_MOVE
, time
);
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_
);
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.
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
))
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
971 if (!popup
|| popup
->being_inspected())
973 base::MessageLoop::current()->PostTask(
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
))
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
;
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
);
1003 gboolean
BrowserActionsToolbarGtk::OnGripperExpose(GtkWidget
* gripper
,
1004 GdkEventExpose
* expose
) {
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
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
));
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
);
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
);
1048 gboolean
BrowserActionsToolbarGtk::OnGripperButtonPress(
1049 GtkWidget
* gripper
, GdkEventButton
* event
) {
1050 resize_animation_
.Reset();
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,
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());
1088 gboolean
BrowserActionsToolbarGtk::OnOverflowMenuButtonPress(
1089 GtkWidget
* overflow
, GdkEventButton
* event
) {
1090 if (event
->button
!= 3)
1093 GtkWidget
* menu_item
= GTK_MENU_SHELL(overflow
)->active_menu_item
;
1097 int item_index
= g_list_index(GTK_MENU_SHELL(overflow
)->children
, menu_item
);
1098 if (item_index
== -1) {
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
) {
1115 MenuGtk
* menu
= button
->GetContextMenu();
1119 menu
->PopupAsContext(gfx::Point(event
->x_root
, event
->y_root
),
1124 void BrowserActionsToolbarGtk::OnButtonShowOrHide(GtkWidget
* sender
) {
1125 if (!resize_animation_
.is_animating())
1126 UpdateChevronVisibility();