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 content::HostZoomMap; 66 using content::UserMetricsAction; 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 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 122 BrowserToolbarGtk::~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 136 void 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 277 void 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 287 void BrowserToolbarGtk::Show() { 288 gtk_widget_show(toolbar_); 289 } 290 291 void BrowserToolbarGtk::Hide() { 292 gtk_widget_hide(toolbar_); 293 } 294 295 LocationBar* BrowserToolbarGtk::GetLocationBar() const { 296 return location_bar_.get(); 297 } 298 299 void 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 307 void 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 320 void 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 348 void 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 356 GtkIconSet* 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. 362 bool BrowserToolbarGtk::AlwaysShowIconForCmd(int command_id) const { 363 return command_id == IDC_UPGRADE_DIALOG || 364 BookmarkSubMenuModel::IsBookmarkItemCommandId(command_id); 365 } 366 367 // ui::AcceleratorProvider 368 369 bool 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 382 void 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 431 void 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 439 bool BrowserToolbarGtk::IsWrenchMenuShowing() const { 440 return wrench_menu_.get() && gtk_widget_get_visible(wrench_menu_->widget()); 441 } 442 443 // BrowserToolbarGtk, private -------------------------------------------------- 444 445 void 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 460 void 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 476 bool 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 497 gboolean 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 609 gboolean 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 623 void 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 635 gboolean 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 649 void 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 668 bool BrowserToolbarGtk::ShouldOnlyShowLocation() const { 669 // If we're a popup window, only show the location bar (omnibox). 670 return !browser_->is_type_tabbed(); 671 } 672 673 void 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 681 gboolean 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 724 void BrowserToolbarGtk::UpdateShowHomeButton() { 725 bool visible = show_home_button_.GetValue() && !ShouldOnlyShowLocation(); 726 gtk_widget_set_visible(home_->widget(), visible); 727 } 728