| OLD | NEW |
| (Empty) |
| 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_DEPRECATED, | |
| 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_DEPRECATED: | |
| 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 extensions::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 extensions::ExtensionToolbarModel::ACTION_NONE: | |
| 270 break; | |
| 271 case extensions::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_ = extensions::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 extensions::ExtensionToolbarModel::ACTION_NONE: | |
| 838 break; | |
| 839 case extensions::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 } | |
| OLD | NEW |