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