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 |