OLD | NEW |
1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/gtk/browser_actions_toolbar_gtk.h" | 5 #include "chrome/browser/gtk/browser_actions_toolbar_gtk.h" |
6 | 6 |
7 #include <gtk/gtk.h> | |
8 #include <vector> | 7 #include <vector> |
9 | 8 |
10 #include "app/gfx/canvas_paint.h" | 9 #include "app/gfx/canvas_paint.h" |
11 #include "app/gfx/gtk_util.h" | 10 #include "app/gfx/gtk_util.h" |
12 #include "chrome/browser/browser.h" | 11 #include "chrome/browser/browser.h" |
13 #include "chrome/browser/extensions/extension_browser_event_router.h" | 12 #include "chrome/browser/extensions/extension_browser_event_router.h" |
14 #include "chrome/browser/extensions/extensions_service.h" | 13 #include "chrome/browser/extensions/extensions_service.h" |
15 #include "chrome/browser/extensions/image_loading_tracker.h" | 14 #include "chrome/browser/extensions/image_loading_tracker.h" |
16 #include "chrome/browser/gtk/extension_popup_gtk.h" | 15 #include "chrome/browser/gtk/extension_popup_gtk.h" |
17 #include "chrome/browser/gtk/gtk_chrome_button.h" | 16 #include "chrome/browser/gtk/gtk_chrome_button.h" |
18 #include "chrome/browser/gtk/gtk_theme_provider.h" | 17 #include "chrome/browser/gtk/gtk_theme_provider.h" |
19 #include "chrome/browser/profile.h" | 18 #include "chrome/browser/profile.h" |
20 #include "chrome/browser/tab_contents/tab_contents.h" | 19 #include "chrome/browser/tab_contents/tab_contents.h" |
21 #include "chrome/common/extensions/extension.h" | 20 #include "chrome/common/extensions/extension.h" |
22 #include "chrome/common/extensions/extension_action.h" | 21 #include "chrome/common/extensions/extension_action.h" |
23 #include "chrome/common/gtk_util.h" | 22 #include "chrome/common/gtk_util.h" |
24 #include "chrome/common/notification_details.h" | 23 #include "chrome/common/notification_details.h" |
25 #include "chrome/common/notification_service.h" | 24 #include "chrome/common/notification_service.h" |
26 #include "chrome/common/notification_source.h" | 25 #include "chrome/common/notification_source.h" |
27 #include "chrome/common/notification_type.h" | 26 #include "chrome/common/notification_type.h" |
28 | 27 |
| 28 namespace { |
| 29 |
29 // The size of each button on the toolbar. | 30 // The size of each button on the toolbar. |
30 static const int kButtonSize = 29; | 31 const int kButtonSize = 29; |
31 | 32 |
32 // The padding between browser action buttons. Visually, the actual number of | 33 // The padding between browser action buttons. Visually, the actual number of |
33 // "empty" (non-drawing) pixels is this value + 2 when adjacent browser icons | 34 // "empty" (non-drawing) pixels is this value + 2 when adjacent browser icons |
34 // use their maximum allowed size. | 35 // use their maximum allowed size. |
35 static const int kBrowserActionButtonPadding = 3; | 36 const int kButtonPadding = 3; |
| 37 |
| 38 const char* kDragTarget = "application/x-chrome-browseraction"; |
| 39 |
| 40 GtkTargetEntry GetDragTargetEntry() { |
| 41 static std::string drag_target_string(kDragTarget); |
| 42 GtkTargetEntry drag_target; |
| 43 drag_target.target = const_cast<char*>(drag_target_string.c_str()); |
| 44 drag_target.flags = GTK_TARGET_SAME_APP; |
| 45 drag_target.info = 0; |
| 46 return drag_target; |
| 47 } |
| 48 |
| 49 } // namespace |
36 | 50 |
37 class BrowserActionButton : public NotificationObserver, | 51 class BrowserActionButton : public NotificationObserver, |
38 public ImageLoadingTracker::Observer { | 52 public ImageLoadingTracker::Observer { |
39 public: | 53 public: |
40 BrowserActionButton(BrowserActionsToolbarGtk* toolbar, | 54 BrowserActionButton(BrowserActionsToolbarGtk* toolbar, |
41 Extension* extension) | 55 Extension* extension) |
42 : toolbar_(toolbar), | 56 : toolbar_(toolbar), |
43 extension_(extension), | 57 extension_(extension), |
44 button_(gtk_chrome_button_new()), | 58 button_(gtk_chrome_button_new()), |
45 tracker_(NULL), | 59 tracker_(NULL), |
46 tab_specific_icon_(NULL), | 60 tab_specific_icon_(NULL), |
47 default_icon_(NULL) { | 61 default_icon_(NULL) { |
48 DCHECK(extension_->browser_action()); | 62 DCHECK(extension_->browser_action()); |
49 | 63 |
50 gtk_widget_set_size_request(button_.get(), kButtonSize, kButtonSize); | 64 gtk_widget_set_size_request(button_.get(), kButtonSize, kButtonSize); |
51 | 65 |
52 UpdateState(); | 66 UpdateState(); |
53 | 67 |
54 // The Browser Action API does not allow the default icon path to be | 68 // The Browser Action API does not allow the default icon path to be |
55 // changed at runtime, so we can load this now and cache it. | 69 // changed at runtime, so we can load this now and cache it. |
56 std::string path = extension_->browser_action()->default_icon_path(); | 70 std::string path = extension_->browser_action()->default_icon_path(); |
57 if (!path.empty()) { | 71 if (!path.empty()) { |
58 tracker_ = new ImageLoadingTracker(this, 1); | 72 tracker_ = new ImageLoadingTracker(this, 1); |
59 tracker_->PostLoadImageTask(extension_->GetResource(path), | 73 tracker_->PostLoadImageTask(extension_->GetResource(path), |
60 gfx::Size(Extension::kBrowserActionIconMaxSize, | 74 gfx::Size(Extension::kBrowserActionIconMaxSize, |
61 Extension::kBrowserActionIconMaxSize)); | 75 Extension::kBrowserActionIconMaxSize)); |
62 } | 76 } |
63 | 77 |
64 // We need to hook up extension popups here. http://crbug.com/23897 | |
65 g_signal_connect(button_.get(), "clicked", | 78 g_signal_connect(button_.get(), "clicked", |
66 G_CALLBACK(OnButtonClicked), this); | 79 G_CALLBACK(OnButtonClicked), this); |
67 g_signal_connect_after(button_.get(), "expose-event", | 80 g_signal_connect_after(button_.get(), "expose-event", |
68 G_CALLBACK(OnExposeEvent), this); | 81 G_CALLBACK(OnExposeEvent), this); |
| 82 g_signal_connect(button_.get(), "drag-begin", |
| 83 G_CALLBACK(&OnDragBegin), this); |
69 | 84 |
70 registrar_.Add(this, NotificationType::EXTENSION_BROWSER_ACTION_UPDATED, | 85 registrar_.Add(this, NotificationType::EXTENSION_BROWSER_ACTION_UPDATED, |
71 Source<ExtensionAction>(extension->browser_action())); | 86 Source<ExtensionAction>(extension->browser_action())); |
72 registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED, | 87 registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED, |
73 NotificationService::AllSources()); | 88 NotificationService::AllSources()); |
74 | 89 |
75 OnThemeChanged(); | 90 OnThemeChanged(); |
76 } | 91 } |
77 | 92 |
78 ~BrowserActionButton() { | 93 ~BrowserActionButton() { |
79 if (tab_specific_icon_) | 94 if (tab_specific_icon_) |
80 g_object_unref(tab_specific_icon_); | 95 g_object_unref(tab_specific_icon_); |
81 | 96 |
82 if (default_icon_) | 97 if (default_icon_) |
83 g_object_unref(default_icon_); | 98 g_object_unref(default_icon_); |
84 | 99 |
85 button_.Destroy(); | 100 button_.Destroy(); |
86 | 101 |
87 if (tracker_) | 102 if (tracker_) |
88 tracker_->StopTrackingImageLoad(); | 103 tracker_->StopTrackingImageLoad(); |
89 } | 104 } |
90 | 105 |
91 GtkWidget* widget() { return button_.get(); } | 106 GtkWidget* widget() { return button_.get(); } |
92 | 107 |
| 108 Extension* extension() { return extension_; } |
| 109 |
| 110 // NotificationObserver implementation. |
93 void Observe(NotificationType type, | 111 void Observe(NotificationType type, |
94 const NotificationSource& source, | 112 const NotificationSource& source, |
95 const NotificationDetails& details) { | 113 const NotificationDetails& details) { |
96 if (type == NotificationType::EXTENSION_BROWSER_ACTION_UPDATED) | 114 if (type == NotificationType::EXTENSION_BROWSER_ACTION_UPDATED) |
97 UpdateState(); | 115 UpdateState(); |
98 else if (type == NotificationType::BROWSER_THEME_CHANGED) | 116 else if (type == NotificationType::BROWSER_THEME_CHANGED) |
99 OnThemeChanged(); | 117 OnThemeChanged(); |
100 else | 118 else |
101 NOTREACHED(); | 119 NOTREACHED(); |
102 } | 120 } |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
141 gtk_image_new_from_pixbuf(image)); | 159 gtk_image_new_from_pixbuf(image)); |
142 } | 160 } |
143 | 161 |
144 void OnThemeChanged() { | 162 void OnThemeChanged() { |
145 gtk_chrome_button_set_use_gtk_rendering(GTK_CHROME_BUTTON(button_.get()), | 163 gtk_chrome_button_set_use_gtk_rendering(GTK_CHROME_BUTTON(button_.get()), |
146 GtkThemeProvider::GetFrom( | 164 GtkThemeProvider::GetFrom( |
147 toolbar_->browser()->profile())->UseGtkTheme()); | 165 toolbar_->browser()->profile())->UseGtkTheme()); |
148 } | 166 } |
149 | 167 |
150 static void OnButtonClicked(GtkWidget* widget, BrowserActionButton* action) { | 168 static void OnButtonClicked(GtkWidget* widget, BrowserActionButton* action) { |
151 if (action->extension_->browser_action()->has_popup()) { | 169 if (action->extension_->browser_action()->has_popup()) { |
152 ExtensionPopupGtk::Show( | 170 ExtensionPopupGtk::Show( |
153 action->extension_->browser_action()->popup_url(), | 171 action->extension_->browser_action()->popup_url(), |
154 action->toolbar_->browser(), | 172 action->toolbar_->browser(), |
155 gtk_util::GetWidgetRectRelativeToToplevel(widget)); | 173 gtk_util::GetWidgetRectRelativeToToplevel(widget)); |
156 } else { | 174 } else { |
157 ExtensionBrowserEventRouter::GetInstance()->BrowserActionExecuted( | 175 ExtensionBrowserEventRouter::GetInstance()->BrowserActionExecuted( |
158 action->toolbar_->browser()->profile(), action->extension_->id(), | 176 action->toolbar_->browser()->profile(), action->extension_->id(), |
159 action->toolbar_->browser()); | 177 action->toolbar_->browser()); |
160 } | 178 } |
161 } | 179 } |
162 | 180 |
163 static gboolean OnExposeEvent(GtkWidget* widget, | 181 static gboolean OnExposeEvent(GtkWidget* widget, |
164 GdkEventExpose* event, | 182 GdkEventExpose* event, |
165 BrowserActionButton* button) { | 183 BrowserActionButton* button) { |
166 int tab_id = button->toolbar_->GetCurrentTabId(); | 184 int tab_id = button->toolbar_->GetCurrentTabId(); |
167 if (tab_id < 0) | 185 if (tab_id < 0) |
168 return FALSE; | 186 return FALSE; |
169 | 187 |
170 ExtensionAction* action = button->extension_->browser_action(); | 188 ExtensionAction* action = button->extension_->browser_action(); |
171 if (action->GetBadgeText(tab_id).empty()) | 189 if (action->GetBadgeText(tab_id).empty()) |
172 return FALSE; | 190 return FALSE; |
173 | 191 |
174 gfx::CanvasPaint canvas(event, false); | 192 gfx::CanvasPaint canvas(event, false); |
175 gfx::Rect bounding_rect(widget->allocation); | 193 gfx::Rect bounding_rect(widget->allocation); |
176 action->PaintBadge(&canvas, bounding_rect, tab_id); | 194 action->PaintBadge(&canvas, bounding_rect, tab_id); |
177 return FALSE; | 195 return FALSE; |
178 } | 196 } |
179 | 197 |
| 198 static void OnDragBegin(GtkWidget* widget, |
| 199 GdkDragContext* drag_context, |
| 200 BrowserActionButton* button) { |
| 201 // Simply pass along the notification to the toolbar. The point of this |
| 202 // function is to tell the toolbar which BrowserActionButton initiated the |
| 203 // drag. |
| 204 button->toolbar_->DragStarted(button, drag_context); |
| 205 } |
| 206 |
180 // The toolbar containing this button. | 207 // The toolbar containing this button. |
181 BrowserActionsToolbarGtk* toolbar_; | 208 BrowserActionsToolbarGtk* toolbar_; |
182 | 209 |
183 // The extension that contains this browser action. | 210 // The extension that contains this browser action. |
184 Extension* extension_; | 211 Extension* extension_; |
185 | 212 |
186 // The gtk widget for this browser action. | 213 // The gtk widget for this browser action. |
187 OwnedWidgetGtk button_; | 214 OwnedWidgetGtk button_; |
188 | 215 |
189 // Loads the button's icons for us on the file thread. | 216 // Loads the button's icons for us on the file thread. |
190 ImageLoadingTracker* tracker_; | 217 ImageLoadingTracker* tracker_; |
191 | 218 |
192 // If we are displaying a tab-specific icon, it will be here. | 219 // If we are displaying a tab-specific icon, it will be here. |
193 GdkPixbuf* tab_specific_icon_; | 220 GdkPixbuf* tab_specific_icon_; |
194 | 221 |
195 // If the browser action has a default icon, it will be here. | 222 // If the browser action has a default icon, it will be here. |
196 GdkPixbuf* default_icon_; | 223 GdkPixbuf* default_icon_; |
197 | 224 |
198 NotificationRegistrar registrar_; | 225 NotificationRegistrar registrar_; |
199 | 226 |
200 friend class BrowserActionsToolbarGtk; | 227 friend class BrowserActionsToolbarGtk; |
201 }; | 228 }; |
202 | 229 |
203 BrowserActionsToolbarGtk::BrowserActionsToolbarGtk(Browser* browser) | 230 BrowserActionsToolbarGtk::BrowserActionsToolbarGtk(Browser* browser) |
204 : browser_(browser), | 231 : browser_(browser), |
205 profile_(browser->profile()), | 232 profile_(browser->profile()), |
206 model_(NULL), | 233 model_(NULL), |
207 hbox_(gtk_hbox_new(FALSE, kBrowserActionButtonPadding)) { | 234 hbox_(gtk_hbox_new(FALSE, kButtonPadding)), |
| 235 drag_button_(NULL), |
| 236 drop_index_(-1) { |
208 ExtensionsService* extension_service = profile_->GetExtensionsService(); | 237 ExtensionsService* extension_service = profile_->GetExtensionsService(); |
209 // The |extension_service| can be NULL in Incognito. | 238 // The |extension_service| can be NULL in Incognito. |
210 if (extension_service) { | 239 if (!extension_service) |
211 model_ = extension_service->toolbar_model(); | 240 return; |
212 model_->AddObserver(this); | 241 |
213 CreateAllButtons(); | 242 model_ = extension_service->toolbar_model(); |
214 } | 243 model_->AddObserver(this); |
| 244 SetupDrags(); |
| 245 CreateAllButtons(); |
215 } | 246 } |
216 | 247 |
217 BrowserActionsToolbarGtk::~BrowserActionsToolbarGtk() { | 248 BrowserActionsToolbarGtk::~BrowserActionsToolbarGtk() { |
218 if (model_) | 249 if (model_) |
219 model_->RemoveObserver(this); | 250 model_->RemoveObserver(this); |
220 hbox_.Destroy(); | 251 hbox_.Destroy(); |
221 } | 252 } |
222 | 253 |
223 int BrowserActionsToolbarGtk::GetCurrentTabId() { | 254 int BrowserActionsToolbarGtk::GetCurrentTabId() { |
224 TabContents* selected_tab = browser_->GetSelectedTabContents(); | 255 TabContents* selected_tab = browser_->GetSelectedTabContents(); |
225 if (!selected_tab) | 256 if (!selected_tab) |
226 return -1; | 257 return -1; |
227 | 258 |
228 return selected_tab->controller().session_id().id(); | 259 return selected_tab->controller().session_id().id(); |
229 } | 260 } |
230 | 261 |
231 void BrowserActionsToolbarGtk::Update() { | 262 void BrowserActionsToolbarGtk::Update() { |
232 for (ExtensionButtonMap::iterator iter = extension_button_map_.begin(); | 263 for (ExtensionButtonMap::iterator iter = extension_button_map_.begin(); |
233 iter != extension_button_map_.end(); ++iter) { | 264 iter != extension_button_map_.end(); ++iter) { |
234 iter->second->UpdateState(); | 265 iter->second->UpdateState(); |
235 } | 266 } |
236 } | 267 } |
237 | 268 |
| 269 void BrowserActionsToolbarGtk::SetupDrags() { |
| 270 GtkTargetEntry drag_target = GetDragTargetEntry(); |
| 271 gtk_drag_dest_set(widget(), GTK_DEST_DEFAULT_DROP, &drag_target, 1, |
| 272 GDK_ACTION_MOVE); |
| 273 |
| 274 g_signal_connect(widget(), "drag-motion", |
| 275 G_CALLBACK(OnDragMotionThunk), this); |
| 276 } |
| 277 |
238 void BrowserActionsToolbarGtk::CreateAllButtons() { | 278 void BrowserActionsToolbarGtk::CreateAllButtons() { |
| 279 extension_button_map_.clear(); |
| 280 |
| 281 int i = 0; |
239 for (ExtensionList::iterator iter = model_->begin(); | 282 for (ExtensionList::iterator iter = model_->begin(); |
240 iter != model_->end(); ++iter) { | 283 iter != model_->end(); ++iter) { |
241 CreateButtonForExtension(*iter); | 284 CreateButtonForExtension(*iter, i++); |
242 } | 285 } |
243 } | 286 } |
244 | 287 |
245 void BrowserActionsToolbarGtk::CreateButtonForExtension(Extension* extension) { | 288 void BrowserActionsToolbarGtk::CreateButtonForExtension(Extension* extension, |
| 289 int index) { |
246 RemoveButtonForExtension(extension); | 290 RemoveButtonForExtension(extension); |
247 linked_ptr<BrowserActionButton> button( | 291 linked_ptr<BrowserActionButton> button( |
248 new BrowserActionButton(this, extension)); | 292 new BrowserActionButton(this, extension)); |
249 gtk_box_pack_end(GTK_BOX(hbox_.get()), button->widget(), FALSE, FALSE, 0); | 293 gtk_box_pack_start(GTK_BOX(hbox_.get()), button->widget(), FALSE, FALSE, 0); |
| 294 gtk_box_reorder_child(GTK_BOX(hbox_.get()), button->widget(), index); |
250 gtk_widget_show(button->widget()); | 295 gtk_widget_show(button->widget()); |
251 extension_button_map_[extension->id()] = button; | 296 extension_button_map_[extension->id()] = button; |
252 | 297 |
| 298 GtkTargetEntry drag_target = GetDragTargetEntry(); |
| 299 gtk_drag_source_set(button->widget(), GDK_BUTTON1_MASK, &drag_target, 1, |
| 300 GDK_ACTION_MOVE); |
| 301 // We ignore whether the drag was a "success" or "failure" in Gtk's opinion. |
| 302 g_signal_connect(button->widget(), "drag-end", |
| 303 G_CALLBACK(&OnDragEndThunk), this); |
| 304 |
253 UpdateVisibility(); | 305 UpdateVisibility(); |
254 } | 306 } |
255 | 307 |
256 void BrowserActionsToolbarGtk::RemoveButtonForExtension(Extension* extension) { | 308 void BrowserActionsToolbarGtk::RemoveButtonForExtension(Extension* extension) { |
257 if (extension_button_map_.erase(extension->id())) | 309 if (extension_button_map_.erase(extension->id())) |
258 UpdateVisibility(); | 310 UpdateVisibility(); |
259 } | 311 } |
260 | 312 |
261 void BrowserActionsToolbarGtk::UpdateVisibility() { | 313 void BrowserActionsToolbarGtk::UpdateVisibility() { |
262 if (button_count() == 0) | 314 if (button_count() == 0) |
263 gtk_widget_hide(widget()); | 315 gtk_widget_hide(widget()); |
264 else | 316 else |
265 gtk_widget_show(widget()); | 317 gtk_widget_show(widget()); |
266 } | 318 } |
267 | 319 |
268 void BrowserActionsToolbarGtk::BrowserActionAdded(Extension* extension, | 320 void BrowserActionsToolbarGtk::BrowserActionAdded(Extension* extension, |
269 int index) { | 321 int index) { |
270 // TODO(estade): respect |index|. | 322 CreateButtonForExtension(extension, index); |
271 CreateButtonForExtension(extension); | |
272 } | 323 } |
273 | 324 |
274 void BrowserActionsToolbarGtk::BrowserActionRemoved(Extension* extension) { | 325 void BrowserActionsToolbarGtk::BrowserActionRemoved(Extension* extension) { |
| 326 if (drag_button_ != NULL) { |
| 327 // Break the current drag. |
| 328 gtk_grab_remove(widget()); |
| 329 |
| 330 // Re-generate the toolbar to clean up unfinished drag business (i.e., we |
| 331 // may have re-ordered some buttons; this will put them back where they |
| 332 // belong). |
| 333 CreateAllButtons(); |
| 334 } |
| 335 |
275 RemoveButtonForExtension(extension); | 336 RemoveButtonForExtension(extension); |
276 } | 337 } |
| 338 |
| 339 void BrowserActionsToolbarGtk::BrowserActionMoved(Extension* extension, |
| 340 int index) { |
| 341 // We initiated this move action, and have already moved the button. |
| 342 if (drag_button_ != NULL) |
| 343 return; |
| 344 |
| 345 BrowserActionButton* button = extension_button_map_[extension->id()].get(); |
| 346 if (!button) { |
| 347 NOTREACHED(); |
| 348 return; |
| 349 } |
| 350 |
| 351 gtk_box_reorder_child(GTK_BOX(hbox_.get()), button->widget(), index); |
| 352 } |
| 353 |
| 354 void BrowserActionsToolbarGtk::DragStarted(BrowserActionButton* button, |
| 355 GdkDragContext* drag_context) { |
| 356 // No representation of the widget following the cursor. |
| 357 GdkPixbuf* pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, 1, 1); |
| 358 gtk_drag_set_icon_pixbuf(drag_context, pixbuf, 0, 0); |
| 359 g_object_unref(pixbuf); |
| 360 |
| 361 DCHECK(!drag_button_); |
| 362 drag_button_ = button; |
| 363 } |
| 364 |
| 365 gboolean BrowserActionsToolbarGtk::OnDragMotion(GtkWidget* widget, |
| 366 GdkDragContext* drag_context, |
| 367 gint x, gint y, guint time) { |
| 368 drop_index_ = x < kButtonSize ? 0 : x / (kButtonSize + kButtonPadding); |
| 369 // We will go ahead and reorder the child in order to provide visual feedback |
| 370 // to the user. We don't inform the model that it has moved until the drag |
| 371 // ends. |
| 372 gtk_box_reorder_child(GTK_BOX(hbox_.get()), drag_button_->widget(), |
| 373 drop_index_); |
| 374 |
| 375 gdk_drag_status(drag_context, GDK_ACTION_MOVE, time); |
| 376 return TRUE; |
| 377 } |
| 378 |
| 379 void BrowserActionsToolbarGtk::OnDragEnd(GtkWidget* button, |
| 380 GdkDragContext* drag_context) { |
| 381 if (drop_index_ != -1) |
| 382 model_->MoveBrowserAction(drag_button_->extension(), drop_index_); |
| 383 |
| 384 drag_button_ = NULL; |
| 385 drop_index_ = -1; |
| 386 } |
OLD | NEW |