Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / gtk / browser_toolbar_gtk.cc
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_toolbar_gtk.h"
6
7 #include <gdk/gdkkeysyms.h>
8 #include <gtk/gtk.h>
9 #include <X11/XF86keysym.h>
10
11 #include "base/base_paths.h"
12 #include "base/command_line.h"
13 #include "base/debug/trace_event.h"
14 #include "base/i18n/rtl.h"
15 #include "base/logging.h"
16 #include "base/memory/singleton.h"
17 #include "base/path_service.h"
18 #include "base/prefs/pref_service.h"
19 #include "chrome/app/chrome_command_ids.h"
20 #include "chrome/browser/chrome_notification_types.h"
21 #include "chrome/browser/profiles/profile.h"
22 #include "chrome/browser/themes/theme_properties.h"
23 #include "chrome/browser/themes/theme_service.h"
24 #include "chrome/browser/ui/browser.h"
25 #include "chrome/browser/ui/browser_commands.h"
26 #include "chrome/browser/ui/global_error/global_error.h"
27 #include "chrome/browser/ui/global_error/global_error_service.h"
28 #include "chrome/browser/ui/global_error/global_error_service_factory.h"
29 #include "chrome/browser/ui/gtk/accelerators_gtk.h"
30 #include "chrome/browser/ui/gtk/back_forward_button_gtk.h"
31 #include "chrome/browser/ui/gtk/bookmarks/bookmark_sub_menu_model_gtk.h"
32 #include "chrome/browser/ui/gtk/browser_actions_toolbar_gtk.h"
33 #include "chrome/browser/ui/gtk/browser_window_gtk.h"
34 #include "chrome/browser/ui/gtk/custom_button.h"
35 #include "chrome/browser/ui/gtk/event_utils.h"
36 #include "chrome/browser/ui/gtk/gtk_chrome_button.h"
37 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
38 #include "chrome/browser/ui/gtk/gtk_util.h"
39 #include "chrome/browser/ui/gtk/location_bar_view_gtk.h"
40 #include "chrome/browser/ui/gtk/reload_button_gtk.h"
41 #include "chrome/browser/ui/gtk/rounded_window.h"
42 #include "chrome/browser/ui/gtk/tabs/tab_strip_gtk.h"
43 #include "chrome/browser/ui/gtk/view_id_util.h"
44 #include "chrome/browser/ui/toolbar/encoding_menu_controller.h"
45 #include "chrome/browser/upgrade_detector.h"
46 #include "chrome/common/net/url_fixer_upper.h"
47 #include "chrome/common/pref_names.h"
48 #include "chrome/common/url_constants.h"
49 #include "content/public/browser/host_zoom_map.h"
50 #include "content/public/browser/notification_details.h"
51 #include "content/public/browser/notification_service.h"
52 #include "content/public/browser/user_metrics.h"
53 #include "content/public/browser/web_contents.h"
54 #include "grit/chromium_strings.h"
55 #include "grit/generated_resources.h"
56 #include "grit/theme_resources.h"
57 #include "ui/base/dragdrop/gtk_dnd_util.h"
58 #include "ui/base/l10n/l10n_util.h"
59 #include "ui/base/resource/resource_bundle.h"
60 #include "ui/gfx/canvas_skia_paint.h"
61 #include "ui/gfx/gtk_util.h"
62 #include "ui/gfx/image/cairo_cached_surface.h"
63 #include "ui/gfx/skbitmap_operations.h"
64
65 using base::UserMetricsAction;
66 using content::HostZoomMap;
67 using content::WebContents;
68
69 namespace {
70
71 // Padding on left and right of the left toolbar buttons (back, forward, reload,
72 // etc.).
73 const int kToolbarLeftAreaPadding = 4;
74
75 // Height of the toolbar in pixels (not counting padding).
76 const int kToolbarHeight = 29;
77
78 // Padding within the toolbar above the buttons and location bar.
79 const int kTopBottomPadding = 3;
80
81 // Height of the toolbar in pixels when we only show the location bar.
82 const int kToolbarHeightLocationBarOnly = kToolbarHeight - 2;
83
84 // Interior spacing between toolbar widgets.
85 const int kToolbarWidgetSpacing = 1;
86
87 // Amount of rounding on top corners of toolbar. Only used in Gtk theme mode.
88 const int kToolbarCornerSize = 3;
89
90 void SetWidgetHeightRequest(GtkWidget* widget, gpointer user_data) {
91   gtk_widget_set_size_request(widget, -1, GPOINTER_TO_INT(user_data));
92 }
93
94 }  // namespace
95
96 // BrowserToolbarGtk, public ---------------------------------------------------
97
98 BrowserToolbarGtk::BrowserToolbarGtk(Browser* browser, BrowserWindowGtk* window)
99     : toolbar_(NULL),
100       location_bar_(new LocationBarViewGtk(browser)),
101       is_wrench_menu_model_valid_(true),
102       browser_(browser),
103       window_(window) {
104   wrench_menu_model_.reset(new WrenchMenuModel(this, browser_, false));
105
106   chrome::AddCommandObserver(browser_, IDC_BACK, this);
107   chrome::AddCommandObserver(browser_, IDC_FORWARD, this);
108   chrome::AddCommandObserver(browser_, IDC_HOME, this);
109   chrome::AddCommandObserver(browser_, IDC_BOOKMARK_PAGE, this);
110
111   registrar_.Add(this,
112                  chrome::NOTIFICATION_UPGRADE_RECOMMENDED,
113                  content::NotificationService::AllSources());
114   registrar_.Add(this,
115                  chrome::NOTIFICATION_GLOBAL_ERRORS_CHANGED,
116                  content::Source<Profile>(browser_->profile()));
117 }
118
119 BrowserToolbarGtk::~BrowserToolbarGtk() {
120   chrome::RemoveCommandObserver(browser_, IDC_BACK, this);
121   chrome::RemoveCommandObserver(browser_, IDC_FORWARD, this);
122   chrome::RemoveCommandObserver(browser_, IDC_HOME, this);
123   chrome::RemoveCommandObserver(browser_, IDC_BOOKMARK_PAGE, this);
124
125   offscreen_entry_.Destroy();
126
127   wrench_menu_.reset();
128 }
129
130 void BrowserToolbarGtk::Init(GtkWindow* top_level_window) {
131   Profile* profile = browser_->profile();
132   theme_service_ = GtkThemeService::GetFrom(profile);
133   registrar_.Add(this,
134                  chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
135                  content::Source<ThemeService>(theme_service_));
136
137   offscreen_entry_.Own(gtk_entry_new());
138
139   base::Closure callback =
140       base::Bind(&BrowserToolbarGtk::SetUpDragForHomeButton,
141                  base::Unretained(this));
142
143   show_home_button_.Init(prefs::kShowHomeButton, profile->GetPrefs(),
144                          base::Bind(&BrowserToolbarGtk::UpdateShowHomeButton,
145                                     base::Unretained(this)));
146   home_page_.Init(prefs::kHomePage, profile->GetPrefs(), callback);
147   home_page_is_new_tab_page_.Init(prefs::kHomePageIsNewTabPage,
148                                   profile->GetPrefs(),
149                                   callback);
150
151   event_box_ = gtk_event_box_new();
152   // Make the event box transparent so themes can use transparent toolbar
153   // backgrounds.
154   if (!theme_service_->UsingNativeTheme())
155     gtk_event_box_set_visible_window(GTK_EVENT_BOX(event_box_), FALSE);
156
157   toolbar_ = gtk_hbox_new(FALSE, 0);
158   alignment_ = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
159   UpdateForBookmarkBarVisibility(false);
160   g_signal_connect(alignment_, "expose-event",
161                    G_CALLBACK(&OnAlignmentExposeThunk), this);
162   gtk_container_add(GTK_CONTAINER(event_box_), alignment_);
163   gtk_container_add(GTK_CONTAINER(alignment_), toolbar_);
164
165   toolbar_left_ = gtk_hbox_new(FALSE, kToolbarWidgetSpacing);
166
167   GtkSizeGroup* size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
168   back_.reset(new BackForwardButtonGtk(browser_, false));
169   g_signal_connect(back_->widget(), "clicked",
170                    G_CALLBACK(OnButtonClickThunk), this);
171   gtk_size_group_add_widget(size_group, back_->widget());
172   gtk_box_pack_start(GTK_BOX(toolbar_left_), back_->widget(), FALSE,
173                      FALSE, 0);
174
175   forward_.reset(new BackForwardButtonGtk(browser_, true));
176   g_signal_connect(forward_->widget(), "clicked",
177                    G_CALLBACK(OnButtonClickThunk), this);
178   gtk_size_group_add_widget(size_group, forward_->widget());
179   gtk_box_pack_start(GTK_BOX(toolbar_left_), forward_->widget(), FALSE,
180                      FALSE, 0);
181
182   reload_.reset(new ReloadButtonGtk(location_bar_.get(), browser_));
183   gtk_size_group_add_widget(size_group, reload_->widget());
184   gtk_box_pack_start(GTK_BOX(toolbar_left_), reload_->widget(), FALSE, FALSE,
185                      0);
186
187   home_.reset(new CustomDrawButton(theme_service_, IDR_HOME, IDR_HOME_P,
188       IDR_HOME_H, 0, GTK_STOCK_HOME, GTK_ICON_SIZE_SMALL_TOOLBAR));
189   gtk_widget_set_tooltip_text(home_->widget(),
190       l10n_util::GetStringUTF8(IDS_TOOLTIP_HOME).c_str());
191   g_signal_connect(home_->widget(), "clicked",
192                    G_CALLBACK(OnButtonClickThunk), this);
193   gtk_size_group_add_widget(size_group, home_->widget());
194   gtk_box_pack_start(GTK_BOX(toolbar_left_), home_->widget(), FALSE, FALSE,
195                      kToolbarWidgetSpacing);
196   gtk_util::SetButtonTriggersNavigation(home_->widget());
197
198   gtk_box_pack_start(GTK_BOX(toolbar_), toolbar_left_, FALSE, FALSE,
199                      kToolbarLeftAreaPadding);
200
201   g_object_unref(size_group);
202
203   location_hbox_ = gtk_hbox_new(FALSE, 0);
204   location_bar_->Init(ShouldOnlyShowLocation());
205   gtk_box_pack_start(GTK_BOX(location_hbox_), location_bar_->widget(), TRUE,
206                      TRUE, 0);
207
208   g_signal_connect(location_hbox_, "expose-event",
209                    G_CALLBACK(OnLocationHboxExposeThunk), this);
210   gtk_box_pack_start(GTK_BOX(toolbar_), location_hbox_, TRUE, TRUE,
211       ShouldOnlyShowLocation() ? 1 : 0);
212
213   if (!ShouldOnlyShowLocation()) {
214     actions_toolbar_.reset(new BrowserActionsToolbarGtk(browser_));
215     gtk_box_pack_start(GTK_BOX(toolbar_), actions_toolbar_->widget(),
216                        FALSE, FALSE, 0);
217   }
218
219   wrench_menu_image_ = gtk_image_new_from_pixbuf(
220       theme_service_->GetRTLEnabledPixbufNamed(IDR_TOOLS));
221   wrench_menu_button_.reset(new CustomDrawButton(theme_service_, IDR_TOOLS,
222       IDR_TOOLS_P, IDR_TOOLS_H, 0, wrench_menu_image_));
223   GtkWidget* wrench_button = wrench_menu_button_->widget();
224
225   gtk_widget_set_tooltip_text(
226       wrench_button,
227       l10n_util::GetStringUTF8(IDS_APPMENU_TOOLTIP).c_str());
228   g_signal_connect(wrench_button, "button-press-event",
229                    G_CALLBACK(OnMenuButtonPressEventThunk), this);
230   gtk_widget_set_can_focus(wrench_button, FALSE);
231
232   // Put the wrench button in a box so that we can paint the update notification
233   // over it.
234   GtkWidget* wrench_box = gtk_alignment_new(0, 0, 1, 1);
235   g_signal_connect_after(wrench_box, "expose-event",
236                          G_CALLBACK(OnWrenchMenuButtonExposeThunk), this);
237   gtk_container_add(GTK_CONTAINER(wrench_box), wrench_button);
238   gtk_box_pack_start(GTK_BOX(toolbar_), wrench_box, FALSE, FALSE, 4);
239
240   wrench_menu_.reset(new MenuGtk(this, wrench_menu_model_.get()));
241   // The bookmark menu model needs to be able to force the wrench menu to close.
242   wrench_menu_model_->bookmark_sub_menu_model()->SetMenuGtk(wrench_menu_.get());
243
244   zoom_subscription_ = HostZoomMap::GetForBrowserContext(
245       browser()->profile())->AddZoomLevelChangedCallback(
246           base::Bind(&BrowserToolbarGtk::OnZoomLevelChanged,
247                      base::Unretained(this)));
248
249   if (ShouldOnlyShowLocation()) {
250     gtk_widget_show(event_box_);
251     gtk_widget_show(alignment_);
252     gtk_widget_show(toolbar_);
253     gtk_widget_show_all(location_hbox_);
254     gtk_widget_hide(reload_->widget());
255   } else {
256     gtk_widget_show_all(event_box_);
257     if (actions_toolbar_->button_count() == 0)
258       gtk_widget_hide(actions_toolbar_->widget());
259   }
260
261   // Initialize pref-dependent UI state.
262   UpdateShowHomeButton();
263   SetUpDragForHomeButton();
264
265   // Because the above does a recursive show all on all widgets we need to
266   // update the icon visibility to hide them.
267   location_bar_->UpdateContentSettingsIcons();
268
269   SetViewIDs();
270   theme_service_->InitThemesFor(this);
271 }
272
273 void BrowserToolbarGtk::SetViewIDs() {
274   ViewIDUtil::SetID(widget(), VIEW_ID_TOOLBAR);
275   ViewIDUtil::SetID(back_->widget(), VIEW_ID_BACK_BUTTON);
276   ViewIDUtil::SetID(forward_->widget(), VIEW_ID_FORWARD_BUTTON);
277   ViewIDUtil::SetID(reload_->widget(), VIEW_ID_RELOAD_BUTTON);
278   ViewIDUtil::SetID(home_->widget(), VIEW_ID_HOME_BUTTON);
279   ViewIDUtil::SetID(location_bar_->widget(), VIEW_ID_OMNIBOX);
280   ViewIDUtil::SetID(wrench_menu_button_->widget(), VIEW_ID_APP_MENU);
281 }
282
283 void BrowserToolbarGtk::Show() {
284   gtk_widget_show(toolbar_);
285 }
286
287 void BrowserToolbarGtk::Hide() {
288   gtk_widget_hide(toolbar_);
289 }
290
291 LocationBar* BrowserToolbarGtk::GetLocationBar() const {
292   return location_bar_.get();
293 }
294
295 void BrowserToolbarGtk::UpdateForBookmarkBarVisibility(
296     bool show_bottom_padding) {
297   gtk_alignment_set_padding(GTK_ALIGNMENT(alignment_),
298       ShouldOnlyShowLocation() ? 0 : kTopBottomPadding,
299       !show_bottom_padding || ShouldOnlyShowLocation() ? 0 : kTopBottomPadding,
300       0, 0);
301 }
302
303 void BrowserToolbarGtk::ShowAppMenu() {
304   wrench_menu_->Cancel();
305
306   if (!is_wrench_menu_model_valid_)
307     RebuildWrenchMenu();
308
309   wrench_menu_button_->SetPaintOverride(GTK_STATE_ACTIVE);
310   content::RecordAction(UserMetricsAction("ShowAppMenu"));
311   wrench_menu_->PopupAsFromKeyEvent(wrench_menu_button_->widget());
312 }
313
314 // CommandObserver -------------------------------------------------------------
315
316 void BrowserToolbarGtk::EnabledStateChangedForCommand(int id, bool enabled) {
317   GtkWidget* widget = NULL;
318   switch (id) {
319     case IDC_BACK:
320       widget = back_->widget();
321       break;
322     case IDC_FORWARD:
323       widget = forward_->widget();
324       break;
325     case IDC_HOME:
326       if (home_.get())
327         widget = home_->widget();
328       break;
329   }
330   if (widget) {
331     if (!enabled && gtk_widget_get_state(widget) == GTK_STATE_PRELIGHT) {
332       // If we're disabling a widget, GTK will helpfully restore it to its
333       // previous state when we re-enable it, even if that previous state
334       // is the prelight.  This looks bad.  See the bug for a simple repro.
335       // http://code.google.com/p/chromium/issues/detail?id=13729
336       gtk_widget_set_state(widget, GTK_STATE_NORMAL);
337     }
338     gtk_widget_set_sensitive(widget, enabled);
339   }
340 }
341
342 // MenuGtk::Delegate -----------------------------------------------------------
343
344 void BrowserToolbarGtk::StoppedShowing() {
345   // Without these calls, the hover state can get stuck since the leave-notify
346   // event is not sent when clicking a button brings up the menu.
347   gtk_chrome_button_set_hover_state(
348       GTK_CHROME_BUTTON(wrench_menu_button_->widget()), 0.0);
349   wrench_menu_button_->UnsetPaintOverride();
350 }
351
352 GtkIconSet* BrowserToolbarGtk::GetIconSetForId(int idr) {
353   return theme_service_->GetIconSetForId(idr);
354 }
355
356 // Always show images because we desire that some icons always show
357 // regardless of the system setting.
358 bool BrowserToolbarGtk::AlwaysShowIconForCmd(int command_id) const {
359   return command_id == IDC_UPGRADE_DIALOG ||
360       BookmarkSubMenuModel::IsBookmarkItemCommandId(command_id);
361 }
362
363 // ui::AcceleratorProvider
364
365 bool BrowserToolbarGtk::GetAcceleratorForCommandId(
366     int id,
367     ui::Accelerator* out_accelerator) {
368   const ui::Accelerator* accelerator =
369       AcceleratorsGtk::GetInstance()->GetPrimaryAcceleratorForCommand(id);
370   if (!accelerator)
371     return false;
372   *out_accelerator = *accelerator;
373   return true;
374 }
375
376 // content::NotificationObserver -----------------------------------------------
377
378 void BrowserToolbarGtk::Observe(int type,
379                                 const content::NotificationSource& source,
380                                 const content::NotificationDetails& details) {
381   if (type == chrome::NOTIFICATION_BROWSER_THEME_CHANGED) {
382     // Update the spacing around the menu buttons
383     bool use_gtk = theme_service_->UsingNativeTheme();
384     int border = use_gtk ? 0 : 2;
385     gtk_container_set_border_width(
386         GTK_CONTAINER(wrench_menu_button_->widget()), border);
387
388     // Force the height of the toolbar so we get the right amount of padding
389     // above and below the location bar. We always force the size of the widgets
390     // to either side of the location box, but we only force the location box
391     // size in chrome-theme mode because that's the only time we try to control
392     // the font size.
393     int toolbar_height = ShouldOnlyShowLocation() ?
394                          kToolbarHeightLocationBarOnly : kToolbarHeight;
395     gtk_container_foreach(GTK_CONTAINER(toolbar_), SetWidgetHeightRequest,
396                           GINT_TO_POINTER(toolbar_height));
397     gtk_widget_set_size_request(location_hbox_, -1,
398                                 use_gtk ? -1 : toolbar_height);
399
400     // When using the GTK+ theme, we need to have the event box be visible so
401     // buttons don't get a halo color from the background.  When using Chromium
402     // themes, we want to let the background show through the toolbar.
403     gtk_event_box_set_visible_window(GTK_EVENT_BOX(event_box_), use_gtk);
404
405     if (use_gtk) {
406       // We need to manually update the icon if we are in GTK mode. (Note that
407       // we set the initial value in Init()).
408       gtk_image_set_from_pixbuf(
409           GTK_IMAGE(wrench_menu_image_),
410           theme_service_->GetRTLEnabledPixbufNamed(IDR_TOOLS));
411     }
412
413     UpdateRoundedness();
414   } else if (type == chrome::NOTIFICATION_UPGRADE_RECOMMENDED) {
415     // Redraw the wrench menu to update the badge.
416     gtk_widget_queue_draw(wrench_menu_button_->widget());
417   } else if (type == chrome::NOTIFICATION_GLOBAL_ERRORS_CHANGED) {
418     is_wrench_menu_model_valid_ = false;
419     gtk_widget_queue_draw(wrench_menu_button_->widget());
420   } else {
421     NOTREACHED();
422   }
423 }
424
425 // BrowserToolbarGtk, public ---------------------------------------------------
426
427 void BrowserToolbarGtk::UpdateWebContents(WebContents* contents) {
428   location_bar_->Update(contents);
429
430   if (actions_toolbar_.get())
431     actions_toolbar_->Update();
432 }
433
434 bool BrowserToolbarGtk::IsWrenchMenuShowing() const {
435   return wrench_menu_.get() && gtk_widget_get_visible(wrench_menu_->widget());
436 }
437
438 // BrowserToolbarGtk, private --------------------------------------------------
439
440 void BrowserToolbarGtk::OnZoomLevelChanged(
441     const HostZoomMap::ZoomLevelChange& change) {
442   // Since BrowserToolbarGtk create a new |wrench_menu_model_| in
443   // RebuildWrenchMenu(), the ordering of the observers of HostZoomMap
444   // can change, and result in subtle bugs like http://crbug.com/118823.
445   // Rather than depending on the ordering of the observers, always update
446   // the WrenchMenuModel before updating the WrenchMenu.
447   wrench_menu_model_->UpdateZoomControls();
448
449   // If our zoom level changed, we need to tell the menu to update its state,
450   // since the menu could still be open.
451   wrench_menu_->UpdateMenu();
452 }
453
454
455 void BrowserToolbarGtk::SetUpDragForHomeButton() {
456   if (!home_page_.IsManaged() && !home_page_is_new_tab_page_.IsManaged()) {
457     gtk_drag_dest_set(home_->widget(), GTK_DEST_DEFAULT_ALL,
458                       NULL, 0, GDK_ACTION_COPY);
459     static const int targets[] = { ui::TEXT_PLAIN, ui::TEXT_URI_LIST, -1 };
460     ui::SetDestTargetList(home_->widget(), targets);
461
462     drop_handler_.reset(new ui::GtkSignalRegistrar());
463     drop_handler_->Connect(home_->widget(), "drag-data-received",
464                            G_CALLBACK(OnDragDataReceivedThunk), this);
465   } else {
466     gtk_drag_dest_unset(home_->widget());
467     drop_handler_.reset(NULL);
468   }
469 }
470
471 bool BrowserToolbarGtk::UpdateRoundedness() {
472   // We still round the corners if we are in chrome theme mode, but we do it by
473   // drawing theme resources rather than changing the physical shape of the
474   // widget.
475   bool should_be_rounded = theme_service_->UsingNativeTheme() &&
476       window_->ShouldDrawContentDropShadow();
477
478   if (should_be_rounded == gtk_util::IsActingAsRoundedWindow(alignment_))
479     return false;
480
481   if (should_be_rounded) {
482     gtk_util::ActAsRoundedWindow(alignment_, GdkColor(), kToolbarCornerSize,
483                                  gtk_util::ROUNDED_TOP,
484                                  gtk_util::BORDER_NONE);
485   } else {
486     gtk_util::StopActingAsRoundedWindow(alignment_);
487   }
488
489   return true;
490 }
491
492 gboolean BrowserToolbarGtk::OnAlignmentExpose(GtkWidget* widget,
493                                               GdkEventExpose* e) {
494   TRACE_EVENT0("ui::gtk", "BrowserToolbarGtk::OnAlignmentExpose");
495
496   // We may need to update the roundedness of the toolbar's top corners. In
497   // this case, don't draw; we'll be called again soon enough.
498   if (UpdateRoundedness())
499     return TRUE;
500
501   // We don't need to render the toolbar image in GTK mode.
502   if (theme_service_->UsingNativeTheme())
503     return FALSE;
504
505   cairo_t* cr = gdk_cairo_create(gtk_widget_get_window(widget));
506   gdk_cairo_rectangle(cr, &e->area);
507   cairo_clip(cr);
508
509   gfx::Point tabstrip_origin =
510       window_->tabstrip()->GetTabStripOriginForWidget(widget);
511   // Fill the entire region with the toolbar color.
512   GdkColor color = theme_service_->GetGdkColor(
513       ThemeProperties::COLOR_TOOLBAR);
514   gdk_cairo_set_source_color(cr, &color);
515   cairo_fill(cr);
516
517   // The horizontal size of the top left and right corner images.
518   const int kCornerWidth = 4;
519   // The thickness of the shadow outside the toolbar's bounds; the offset
520   // between the edge of the toolbar and where we anchor the corner images.
521   const int kShadowThickness = 2;
522
523   GtkAllocation allocation;
524   gtk_widget_get_allocation(widget, &allocation);
525   gfx::Rect area(e->area);
526   gfx::Rect right(allocation.x + allocation.width - kCornerWidth,
527                   allocation.y - kShadowThickness,
528                   kCornerWidth,
529                   allocation.height + kShadowThickness);
530   gfx::Rect left(allocation.x - kShadowThickness,
531                  allocation.y - kShadowThickness,
532                  kCornerWidth,
533                  allocation.height + kShadowThickness);
534
535   if (window_->ShouldDrawContentDropShadow()) {
536     // Leave room to draw rounded corners.
537     area.Subtract(right);
538     area.Subtract(left);
539   }
540
541   gfx::Image background = theme_service_->GetImageNamed(IDR_THEME_TOOLBAR);
542   background.ToCairo()->SetSource(
543       cr, widget, tabstrip_origin.x(), tabstrip_origin.y());
544   cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT);
545   cairo_rectangle(cr, area.x(), area.y(), area.width(), area.height());
546   cairo_fill(cr);
547
548   if (!window_->ShouldDrawContentDropShadow()) {
549     // The rest of this function is for rounded corners. Our work is done here.
550     cairo_destroy(cr);
551     return FALSE;
552   }
553
554   bool draw_left_corner = left.Intersects(gfx::Rect(e->area));
555   bool draw_right_corner = right.Intersects(gfx::Rect(e->area));
556
557   if (draw_left_corner || draw_right_corner) {
558     // Create a mask which is composed of the left and/or right corners.
559     cairo_surface_t* target = cairo_surface_create_similar(
560         cairo_get_target(cr),
561         CAIRO_CONTENT_COLOR_ALPHA,
562         allocation.x + allocation.width,
563         allocation.y + allocation.height);
564     cairo_t* copy_cr = cairo_create(target);
565
566     ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
567
568     cairo_set_operator(copy_cr, CAIRO_OPERATOR_SOURCE);
569     if (draw_left_corner) {
570       rb.GetNativeImageNamed(IDR_CONTENT_TOP_LEFT_CORNER_MASK).ToCairo()->
571           SetSource(copy_cr, widget, left.x(), left.y());
572       cairo_paint(copy_cr);
573     }
574     if (draw_right_corner) {
575       rb.GetNativeImageNamed(IDR_CONTENT_TOP_RIGHT_CORNER_MASK).ToCairo()->
576           SetSource(copy_cr, widget, right.x(), right.y());
577       // We fill a path rather than just painting because we don't want to
578       // overwrite the left corner.
579       cairo_rectangle(copy_cr, right.x(), right.y(),
580                       right.width(), right.height());
581       cairo_fill(copy_cr);
582     }
583
584     // Draw the background. CAIRO_OPERATOR_IN uses the existing pixel data as
585     // an alpha mask.
586     background.ToCairo()->SetSource(copy_cr, widget,
587                                      tabstrip_origin.x(), tabstrip_origin.y());
588     cairo_set_operator(copy_cr, CAIRO_OPERATOR_IN);
589     cairo_pattern_set_extend(cairo_get_source(copy_cr), CAIRO_EXTEND_REPEAT);
590     cairo_paint(copy_cr);
591     cairo_destroy(copy_cr);
592
593     // Copy the temporary surface to the screen.
594     cairo_set_source_surface(cr, target, 0, 0);
595     cairo_paint(cr);
596     cairo_surface_destroy(target);
597   }
598
599   cairo_destroy(cr);
600
601   return FALSE;  // Allow subwidgets to paint.
602 }
603
604 gboolean BrowserToolbarGtk::OnLocationHboxExpose(GtkWidget* location_hbox,
605                                                  GdkEventExpose* e) {
606   TRACE_EVENT0("ui::gtk", "BrowserToolbarGtk::OnLocationHboxExpose");
607   if (theme_service_->UsingNativeTheme()) {
608     GtkAllocation allocation;
609     gtk_widget_get_allocation(location_hbox, &allocation);
610     gtk_util::DrawTextEntryBackground(offscreen_entry_.get(),
611                                       location_hbox, &e->area,
612                                       &allocation);
613   }
614
615   return FALSE;
616 }
617
618 void BrowserToolbarGtk::OnButtonClick(GtkWidget* button) {
619   if ((button == back_->widget()) || (button == forward_->widget())) {
620     if (event_utils::DispositionForCurrentButtonPressEvent() == CURRENT_TAB)
621       location_bar_->Revert();
622     return;
623   }
624
625   DCHECK(home_.get() && button == home_->widget()) <<
626       "Unexpected button click callback";
627   chrome::Home(browser_, event_utils::DispositionForCurrentButtonPressEvent());
628 }
629
630 gboolean BrowserToolbarGtk::OnMenuButtonPressEvent(GtkWidget* button,
631                                                    GdkEventButton* event) {
632   if (event->button != 1)
633     return FALSE;
634
635   if (!is_wrench_menu_model_valid_)
636     RebuildWrenchMenu();
637
638   wrench_menu_button_->SetPaintOverride(GTK_STATE_ACTIVE);
639   wrench_menu_->PopupForWidget(button, event->button, event->time);
640
641   return TRUE;
642 }
643
644 void BrowserToolbarGtk::OnDragDataReceived(GtkWidget* widget,
645     GdkDragContext* drag_context, gint x, gint y,
646     GtkSelectionData* data, guint info, guint time) {
647   if (info != ui::TEXT_PLAIN) {
648     NOTIMPLEMENTED() << "Only support plain text drops for now, sorry!";
649     return;
650   }
651
652   GURL url(reinterpret_cast<const char*>(gtk_selection_data_get_data(data)));
653   if (!url.is_valid())
654     return;
655
656   bool url_is_newtab = url.SchemeIs(content::kChromeUIScheme) &&
657                        url.host() == chrome::kChromeUINewTabHost;
658   home_page_is_new_tab_page_.SetValue(url_is_newtab);
659   if (!url_is_newtab)
660     home_page_.SetValue(url.spec());
661 }
662
663 bool BrowserToolbarGtk::ShouldOnlyShowLocation() const {
664   // If we're a popup window, only show the location bar (omnibox).
665   return !browser_->is_type_tabbed();
666 }
667
668 void BrowserToolbarGtk::RebuildWrenchMenu() {
669   wrench_menu_model_.reset(new WrenchMenuModel(this, browser_, false));
670   wrench_menu_.reset(new MenuGtk(this, wrench_menu_model_.get()));
671   // The bookmark menu model needs to be able to force the wrench menu to close.
672   wrench_menu_model_->bookmark_sub_menu_model()->SetMenuGtk(wrench_menu_.get());
673   is_wrench_menu_model_valid_ = true;
674 }
675
676 gboolean BrowserToolbarGtk::OnWrenchMenuButtonExpose(GtkWidget* sender,
677                                                      GdkEventExpose* expose) {
678   TRACE_EVENT0("ui::gtk", "BrowserToolbarGtk::OnWrenchMenuButtonExpose");
679   int resource_id = 0;
680   if (UpgradeDetector::GetInstance()->notify_upgrade()) {
681     resource_id = UpgradeDetector::GetInstance()->GetIconResourceID(
682             UpgradeDetector::UPGRADE_ICON_TYPE_BADGE);
683   } else {
684     GlobalError* error = GlobalErrorServiceFactory::GetForProfile(
685         browser_->profile())->GetHighestSeverityGlobalErrorWithWrenchMenuItem();
686     if (error) {
687       switch (error->GetSeverity()) {
688         case GlobalError::SEVERITY_LOW:
689           resource_id = IDR_UPDATE_BADGE;
690           break;
691         case GlobalError::SEVERITY_MEDIUM:
692           resource_id = IDR_UPDATE_BADGE4;
693           break;
694         case GlobalError::SEVERITY_HIGH:
695           resource_id = IDR_UPDATE_BADGE3;
696           break;
697       }
698     }
699   }
700
701   if (!resource_id)
702     return FALSE;
703
704   GtkAllocation allocation;
705   gtk_widget_get_allocation(sender, &allocation);
706
707   // Draw the chrome app menu icon onto the canvas.
708   const gfx::ImageSkia* badge = theme_service_->GetImageSkiaNamed(resource_id);
709   gfx::CanvasSkiaPaint canvas(expose, false);
710   int x_offset = base::i18n::IsRTL() ? 0 : allocation.width - badge->width();
711   int y_offset = 0;
712   canvas.DrawImageInt(*badge,
713                       allocation.x + x_offset,
714                       allocation.y + y_offset);
715
716   return FALSE;
717 }
718
719 void BrowserToolbarGtk::UpdateShowHomeButton() {
720   bool visible = show_home_button_.GetValue() && !ShouldOnlyShowLocation();
721   gtk_widget_set_visible(home_->widget(), visible);
722 }