1 // Copyright (c) 2011 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_titlebar.h" 6 7 #include <gdk/gdkkeysyms.h> 8 #include <gtk/gtk.h> 9 10 #include <string> 11 #include <vector> 12 13 #include "base/command_line.h" 14 #include "base/memory/singleton.h" 15 #include "base/string_piece.h" 16 #include "base/string_tokenizer.h" 17 #include "base/utf_string_conversions.h" 18 #include "chrome/app/chrome_command_ids.h" 19 #include "chrome/browser/prefs/pref_service.h" 20 #include "chrome/browser/profiles/profile.h" 21 #include "chrome/browser/ui/browser.h" 22 #include "chrome/browser/ui/gtk/accelerators_gtk.h" 23 #include "chrome/browser/ui/gtk/browser_window_gtk.h" 24 #include "chrome/browser/ui/gtk/custom_button.h" 25 #if defined(USE_GCONF) 26 #include "chrome/browser/ui/gtk/gconf_titlebar_listener.h" 27 #endif 28 #include "chrome/browser/ui/gtk/gtk_theme_service.h" 29 #include "chrome/browser/ui/gtk/gtk_util.h" 30 #include "chrome/browser/ui/gtk/menu_gtk.h" 31 #include "chrome/browser/ui/gtk/nine_box.h" 32 #include "chrome/browser/ui/gtk/tabs/tab_strip_gtk.h" 33 #include "chrome/browser/ui/toolbar/encoding_menu_controller.h" 34 #include "chrome/browser/ui/toolbar/wrench_menu_model.h" 35 #include "chrome/common/pref_names.h" 36 #include "content/browser/tab_contents/tab_contents.h" 37 #include "content/common/notification_service.h" 38 #include "grit/app_resources.h" 39 #include "grit/generated_resources.h" 40 #include "grit/theme_resources.h" 41 #include "ui/base/l10n/l10n_util.h" 42 #include "ui/base/resource/resource_bundle.h" 43 #include "ui/gfx/gtk_util.h" 44 #include "ui/gfx/skbitmap_operations.h" 45 46 namespace { 47 48 // The space above the titlebars. 49 const int kTitlebarHeight = 14; 50 51 // The thickness in pixels of the tab border. 52 const int kTabOuterThickness = 1; 53 54 // Amount to offset the tab images relative to the background. 55 const int kNormalVerticalOffset = kTitlebarHeight + kTabOuterThickness; 56 57 // A linux specific menu item for toggling window decorations. 58 const int kShowWindowDecorationsCommand = 200; 59 60 const int kOTRBottomSpacing = 1; 61 // There are 2 px on each side of the OTR avatar (between the frame border and 62 // it on the left, and between it and the tabstrip on the right). 63 const int kOTRSideSpacing = 2; 64 65 // The thickness of the custom frame border; we need it here to enlarge the 66 // close button whent the custom frame border isn't showing but the custom 67 // titlebar is showing. 68 const int kFrameBorderThickness = 4; 69 70 // There is a 4px gap between the icon and the title text. 71 const int kIconTitleSpacing = 4; 72 73 // Padding around the icon when in app mode or popup mode. 74 const int kAppModePaddingTop = 5; 75 const int kAppModePaddingBottom = 4; 76 const int kAppModePaddingLeft = 2; 77 78 // The left padding of the tab strip. In Views, the tab strip has a left 79 // margin of FrameBorderThickness + kClientEdgeThickness. This offset is to 80 // account for kClientEdgeThickness. 81 const int kTabStripLeftPadding = 1; 82 83 // Spacing between buttons of the titlebar. 84 const int kButtonSpacing = 2; 85 86 // Spacing around outside of titlebar buttons. 87 const int kButtonOuterPadding = 2; 88 89 // Spacing between tabstrip and window control buttons (when the window is 90 // maximized). 91 const int kMaximizedTabstripPadding = 16; 92 93 gboolean OnMouseMoveEvent(GtkWidget* widget, GdkEventMotion* event, 94 BrowserWindowGtk* browser_window) { 95 // Reset to the default mouse cursor. 96 browser_window->ResetCustomFrameCursor(); 97 return TRUE; 98 } 99 100 GdkPixbuf* GetOTRAvatar() { 101 static GdkPixbuf* otr_avatar = NULL; 102 if (!otr_avatar) { 103 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); 104 otr_avatar = rb.GetRTLEnabledPixbufNamed(IDR_OTR_ICON); 105 } 106 return otr_avatar; 107 } 108 109 // Converts a GdkColor to a color_utils::HSL. 110 color_utils::HSL GdkColorToHSL(const GdkColor* color) { 111 color_utils::HSL hsl; 112 color_utils::SkColorToHSL(SkColorSetRGB(color->red >> 8, 113 color->green >> 8, 114 color->blue >> 8), &hsl); 115 return hsl; 116 } 117 118 // Returns either |one| or |two| based on which has a greater difference in 119 // luminosity. 120 GdkColor PickLuminosityContrastingColor(const GdkColor* base, 121 const GdkColor* one, 122 const GdkColor* two) { 123 // Convert all GdkColors to color_utils::HSLs. 124 color_utils::HSL baseHSL = GdkColorToHSL(base); 125 color_utils::HSL oneHSL = GdkColorToHSL(one); 126 color_utils::HSL twoHSL = GdkColorToHSL(two); 127 double one_difference = fabs(baseHSL.l - oneHSL.l); 128 double two_difference = fabs(baseHSL.l - twoHSL.l); 129 130 // Be biased towards the first color presented. 131 if (two_difference > one_difference + 0.1) 132 return *two; 133 else 134 return *one; 135 } 136 137 } // namespace 138 139 //////////////////////////////////////////////////////////////////////////////// 140 // PopupPageMenuModel 141 142 // A menu model that builds the contents of the menu shown for popups (when the 143 // user clicks on the favicon) and all of its submenus. 144 class PopupPageMenuModel : public ui::SimpleMenuModel { 145 public: 146 explicit PopupPageMenuModel(ui::SimpleMenuModel::Delegate* delegate, 147 Browser* browser); 148 virtual ~PopupPageMenuModel() { } 149 150 private: 151 void Build(); 152 153 // Models for submenus referenced by this model. SimpleMenuModel only uses 154 // weak references so these must be kept for the lifetime of the top-level 155 // model. 156 scoped_ptr<ZoomMenuModel> zoom_menu_model_; 157 scoped_ptr<EncodingMenuModel> encoding_menu_model_; 158 Browser* browser_; // weak 159 160 DISALLOW_COPY_AND_ASSIGN(PopupPageMenuModel); 161 }; 162 163 PopupPageMenuModel::PopupPageMenuModel( 164 ui::SimpleMenuModel::Delegate* delegate, 165 Browser* browser) 166 : ui::SimpleMenuModel(delegate), browser_(browser) { 167 Build(); 168 } 169 170 void PopupPageMenuModel::Build() { 171 AddItemWithStringId(IDC_BACK, IDS_CONTENT_CONTEXT_BACK); 172 AddItemWithStringId(IDC_FORWARD, IDS_CONTENT_CONTEXT_FORWARD); 173 AddItemWithStringId(IDC_RELOAD, IDS_APP_MENU_RELOAD); 174 AddSeparator(); 175 AddItemWithStringId(IDC_SHOW_AS_TAB, IDS_SHOW_AS_TAB); 176 AddItemWithStringId(IDC_COPY_URL, IDS_APP_MENU_COPY_URL); 177 AddSeparator(); 178 AddItemWithStringId(IDC_CUT, IDS_CUT); 179 AddItemWithStringId(IDC_COPY, IDS_COPY); 180 AddItemWithStringId(IDC_PASTE, IDS_PASTE); 181 AddSeparator(); 182 AddItemWithStringId(IDC_FIND, IDS_FIND); 183 AddItemWithStringId(IDC_PRINT, IDS_PRINT); 184 zoom_menu_model_.reset(new ZoomMenuModel(delegate())); 185 AddSubMenuWithStringId(IDC_ZOOM_MENU, IDS_ZOOM_MENU, zoom_menu_model_.get()); 186 187 encoding_menu_model_.reset(new EncodingMenuModel(browser_)); 188 AddSubMenuWithStringId(IDC_ENCODING_MENU, IDS_ENCODING_MENU, 189 encoding_menu_model_.get()); 190 191 AddSeparator(); 192 AddItemWithStringId(IDC_CLOSE_WINDOW, IDS_CLOSE); 193 } 194 195 //////////////////////////////////////////////////////////////////////////////// 196 // BrowserTitlebar 197 198 // static 199 const char BrowserTitlebar::kDefaultButtonString[] = ":minimize,maximize,close"; 200 201 BrowserTitlebar::BrowserTitlebar(BrowserWindowGtk* browser_window, 202 GtkWindow* window) 203 : browser_window_(browser_window), 204 window_(window), 205 titlebar_left_buttons_vbox_(NULL), 206 titlebar_right_buttons_vbox_(NULL), 207 titlebar_left_buttons_hbox_(NULL), 208 titlebar_right_buttons_hbox_(NULL), 209 titlebar_left_spy_frame_(NULL), 210 titlebar_right_spy_frame_(NULL), 211 top_padding_left_(NULL), 212 top_padding_right_(NULL), 213 app_mode_favicon_(NULL), 214 app_mode_title_(NULL), 215 using_custom_frame_(false), 216 window_has_focus_(false), 217 theme_service_(NULL) { 218 Init(); 219 } 220 221 void BrowserTitlebar::Init() { 222 // The widget hierarchy is shown below. 223 // 224 // +- EventBox (container_) ------------------------------------------------+ 225 // +- HBox (container_hbox_) -----------------------------------------------+ 226 // |+ VBox ---++- Algn. -++- Alignment --------------++- Algn. -++ VBox ---+| 227 // || titlebar||titlebar || (titlebar_alignment_) ||titlebar || titlebar|| 228 // || left ||left || ||right || right || 229 // || button ||spy || ||spy || button || 230 // || vbox ||frame ||+- TabStripGtk ---------+||frame || vbox || 231 // || || || tab tab tabclose ||| || || 232 // || || ||+------------------------+|| || || 233 // |+---------++---------++--------------------------++---------++---------+| 234 // +------------------------------------------------------------------------+ 235 // 236 // There are two vboxes on either side of |container_hbox_| because when the 237 // desktop is GNOME, the button placement is configurable based on a metacity 238 // gconf key. We can't just have one vbox and move it back and forth because 239 // the gconf language allows you to place buttons on both sides of the 240 // window. This being Linux, I'm sure there's a bunch of people who have 241 // already configured their window manager to do so and we don't want to get 242 // confused when that happens. The actual contents of these vboxes are lazily 243 // generated so they don't interfere with alignment when everything is 244 // stuffed in the other box. 245 // 246 // Each vbox has the contains the following hierarchy if it contains buttons: 247 // 248 // +- VBox (titlebar_{l,r}_buttons_vbox_) ---------+ 249 // |+- Fixed (top_padding_{l,r}_) ----------------+| 250 // ||+- HBox (titlebar_{l,r}_buttons_hbox_ ------+|| 251 // ||| (buttons of a configurable layout) ||| 252 // ||+-------------------------------------------+|| 253 // |+---------------------------------------------+| 254 // +-----------------------------------------------+ 255 // 256 // The two spy alignments are only allocated if this window is an incognito 257 // window. Only one of them holds the spy image. 258 // 259 // If we're a popup window or in app mode, we don't display the spy guy or 260 // the tab strip. Instead, put an hbox in titlebar_alignment_ in place of 261 // the tab strip. 262 // +- Alignment (titlebar_alignment_) -----------------------------------+ 263 // |+ HBox -------------------------------------------------------------+| 264 // ||+- TabStripGtk -++- Image ----------------++- Label --------------+|| 265 // ||| hidden ++ (app_mode_favicon_) || (app_mode_title_) ||| 266 // ||| || favicon || page title ||| 267 // ||+---------------++------------------------++----------------------+|| 268 // |+-------------------------------------------------------------------+| 269 // +---------------------------------------------------------------------+ 270 container_hbox_ = gtk_hbox_new(FALSE, 0); 271 272 container_ = gtk_event_box_new(); 273 gtk_widget_set_name(container_, "chrome-browser-titlebar"); 274 gtk_event_box_set_visible_window(GTK_EVENT_BOX(container_), FALSE); 275 gtk_container_add(GTK_CONTAINER(container_), container_hbox_); 276 277 g_signal_connect(container_, "scroll-event", G_CALLBACK(OnScrollThunk), this); 278 279 g_signal_connect(window_, "window-state-event", 280 G_CALLBACK(OnWindowStateChangedThunk), this); 281 282 // Allocate the two button boxes on the left and right parts of the bar, and 283 // spyguy frames in case of incognito mode. 284 titlebar_left_buttons_vbox_ = gtk_vbox_new(FALSE, 0); 285 gtk_box_pack_start(GTK_BOX(container_hbox_), titlebar_left_buttons_vbox_, 286 FALSE, FALSE, 0); 287 if (browser_window_->browser()->profile()->IsOffTheRecord() && 288 browser_window_->browser()->type() == Browser::TYPE_NORMAL) { 289 titlebar_left_spy_frame_ = gtk_alignment_new(0.0, 0.0, 1.0, 1.0); 290 gtk_widget_set_no_show_all(titlebar_left_spy_frame_, TRUE); 291 gtk_alignment_set_padding(GTK_ALIGNMENT(titlebar_left_spy_frame_), 0, 292 kOTRBottomSpacing, kOTRSideSpacing, kOTRSideSpacing); 293 gtk_box_pack_start(GTK_BOX(container_hbox_), titlebar_left_spy_frame_, 294 FALSE, FALSE, 0); 295 296 titlebar_right_spy_frame_ = gtk_alignment_new(0.0, 0.0, 1.0, 1.0); 297 gtk_widget_set_no_show_all(titlebar_right_spy_frame_, TRUE); 298 gtk_alignment_set_padding(GTK_ALIGNMENT(titlebar_right_spy_frame_), 0, 299 kOTRBottomSpacing, kOTRSideSpacing, kOTRSideSpacing); 300 gtk_box_pack_end(GTK_BOX(container_hbox_), titlebar_right_spy_frame_, 301 FALSE, FALSE, 0); 302 } 303 titlebar_right_buttons_vbox_ = gtk_vbox_new(FALSE, 0); 304 gtk_box_pack_end(GTK_BOX(container_hbox_), titlebar_right_buttons_vbox_, 305 FALSE, FALSE, 0); 306 307 #if defined(USE_GCONF) 308 // Either read the gconf database and register for updates (on GNOME), or use 309 // the default value (anywhere else). 310 GConfTitlebarListener::GetInstance()->SetTitlebarButtons(this); 311 #else 312 BuildButtons(kDefaultButtonString); 313 #endif 314 315 // We use an alignment to control the titlebar height. 316 titlebar_alignment_ = gtk_alignment_new(0.0, 0.0, 1.0, 1.0); 317 if (browser_window_->browser()->type() == Browser::TYPE_NORMAL) { 318 gtk_box_pack_start(GTK_BOX(container_hbox_), titlebar_alignment_, TRUE, 319 TRUE, 0); 320 321 // Put the tab strip in the titlebar. 322 gtk_container_add(GTK_CONTAINER(titlebar_alignment_), 323 browser_window_->tabstrip()->widget()); 324 } else { 325 // App mode specific widgets. 326 gtk_box_pack_start(GTK_BOX(container_hbox_), titlebar_alignment_, TRUE, 327 TRUE, 0); 328 GtkWidget* app_mode_hbox = gtk_hbox_new(FALSE, kIconTitleSpacing); 329 gtk_container_add(GTK_CONTAINER(titlebar_alignment_), app_mode_hbox); 330 331 // Put the tab strip in the hbox even though we don't show it. Sometimes 332 // we need the position of the tab strip so make sure it's in our widget 333 // hierarchy. 334 gtk_box_pack_start(GTK_BOX(app_mode_hbox), 335 browser_window_->tabstrip()->widget(), FALSE, FALSE, 0); 336 337 GtkWidget* favicon_event_box = gtk_event_box_new(); 338 gtk_event_box_set_visible_window(GTK_EVENT_BOX(favicon_event_box), FALSE); 339 g_signal_connect(favicon_event_box, "button-press-event", 340 G_CALLBACK(OnButtonPressedThunk), this); 341 gtk_box_pack_start(GTK_BOX(app_mode_hbox), favicon_event_box, FALSE, 342 FALSE, 0); 343 // We use the app logo as a placeholder image so the title doesn't jump 344 // around. 345 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); 346 app_mode_favicon_ = gtk_image_new_from_pixbuf( 347 rb.GetRTLEnabledPixbufNamed(IDR_PRODUCT_LOGO_16)); 348 g_object_set_data(G_OBJECT(app_mode_favicon_), "left-align-popup", 349 reinterpret_cast<void*>(true)); 350 gtk_container_add(GTK_CONTAINER(favicon_event_box), app_mode_favicon_); 351 352 app_mode_title_ = gtk_label_new(NULL); 353 gtk_label_set_ellipsize(GTK_LABEL(app_mode_title_), PANGO_ELLIPSIZE_END); 354 gtk_misc_set_alignment(GTK_MISC(app_mode_title_), 0.0, 0.5); 355 gtk_box_pack_start(GTK_BOX(app_mode_hbox), app_mode_title_, TRUE, TRUE, 356 0); 357 358 // Register with the theme provider to set the |app_mode_title_| label 359 // color. 360 theme_service_ = GtkThemeService::GetFrom( 361 browser_window_->browser()->profile()); 362 registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED, 363 NotificationService::AllSources()); 364 theme_service_->InitThemesFor(this); 365 UpdateTitleAndIcon(); 366 } 367 368 gtk_widget_show_all(container_); 369 370 ui::ActiveWindowWatcherX::AddObserver(this); 371 } 372 373 BrowserTitlebar::~BrowserTitlebar() { 374 ui::ActiveWindowWatcherX::RemoveObserver(this); 375 #if defined(USE_GCONF) 376 GConfTitlebarListener::GetInstance()->RemoveObserver(this); 377 #endif 378 } 379 380 void BrowserTitlebar::BuildButtons(const std::string& button_string) { 381 // Clear out all previous data. 382 close_button_.reset(); 383 restore_button_.reset(); 384 maximize_button_.reset(); 385 minimize_button_.reset(); 386 gtk_util::RemoveAllChildren(titlebar_left_buttons_vbox_); 387 gtk_util::RemoveAllChildren(titlebar_right_buttons_vbox_); 388 titlebar_left_buttons_hbox_ = NULL; 389 titlebar_right_buttons_hbox_ = NULL; 390 top_padding_left_ = NULL; 391 top_padding_right_ = NULL; 392 393 bool left_side = true; 394 StringTokenizer tokenizer(button_string, ":,"); 395 tokenizer.set_options(StringTokenizer::RETURN_DELIMS); 396 int left_count = 0; 397 int right_count = 0; 398 while (tokenizer.GetNext()) { 399 if (tokenizer.token_is_delim()) { 400 if (*tokenizer.token_begin() == ':') 401 left_side = false; 402 } else { 403 base::StringPiece token = tokenizer.token_piece(); 404 if (token == "minimize") { 405 (left_side ? left_count : right_count)++; 406 GtkWidget* parent_box = GetButtonHBox(left_side); 407 minimize_button_.reset( 408 BuildTitlebarButton(IDR_MINIMIZE, IDR_MINIMIZE_P, 409 IDR_MINIMIZE_H, parent_box, 410 IDS_XPFRAME_MINIMIZE_TOOLTIP)); 411 412 gtk_widget_size_request(minimize_button_->widget(), 413 &minimize_button_req_); 414 } else if (token == "maximize") { 415 (left_side ? left_count : right_count)++; 416 GtkWidget* parent_box = GetButtonHBox(left_side); 417 restore_button_.reset( 418 BuildTitlebarButton(IDR_RESTORE, IDR_RESTORE_P, 419 IDR_RESTORE_H, parent_box, 420 IDS_XPFRAME_RESTORE_TOOLTIP)); 421 maximize_button_.reset( 422 BuildTitlebarButton(IDR_MAXIMIZE, IDR_MAXIMIZE_P, 423 IDR_MAXIMIZE_H, parent_box, 424 IDS_XPFRAME_MAXIMIZE_TOOLTIP)); 425 426 gtk_util::SetButtonClickableByMouseButtons(maximize_button_->widget(), 427 true, true, true); 428 gtk_widget_size_request(restore_button_->widget(), 429 &restore_button_req_); 430 } else if (token == "close") { 431 (left_side ? left_count : right_count)++; 432 GtkWidget* parent_box = GetButtonHBox(left_side); 433 close_button_.reset( 434 BuildTitlebarButton(IDR_CLOSE, IDR_CLOSE_P, 435 IDR_CLOSE_H, parent_box, 436 IDS_XPFRAME_CLOSE_TOOLTIP)); 437 close_button_->set_flipped(left_side); 438 439 gtk_widget_size_request(close_button_->widget(), &close_button_req_); 440 } 441 // Ignore any other values like "pin" since we don't have images for 442 // those. 443 } 444 } 445 446 // If we are in incognito mode, add the spy guy to either the end of the left 447 // or the beginning of the right depending on which side has fewer buttons. 448 if (browser_window_->browser()->profile()->IsOffTheRecord() && 449 browser_window_->browser()->type() == Browser::TYPE_NORMAL) { 450 GtkWidget* spy_guy = gtk_image_new_from_pixbuf(GetOTRAvatar()); 451 gtk_misc_set_alignment(GTK_MISC(spy_guy), 0.0, 1.0); 452 gtk_widget_set_size_request(spy_guy, -1, 0); 453 gtk_widget_show(spy_guy); 454 455 // Remove previous state. 456 gtk_util::RemoveAllChildren(titlebar_left_spy_frame_); 457 gtk_util::RemoveAllChildren(titlebar_right_spy_frame_); 458 459 if (right_count > left_count) { 460 gtk_container_add(GTK_CONTAINER(titlebar_left_spy_frame_), spy_guy); 461 gtk_widget_show(titlebar_left_spy_frame_); 462 gtk_widget_hide(titlebar_right_spy_frame_); 463 } else { 464 gtk_container_add(GTK_CONTAINER(titlebar_right_spy_frame_), spy_guy); 465 gtk_widget_show(titlebar_right_spy_frame_); 466 gtk_widget_hide(titlebar_left_spy_frame_); 467 } 468 } 469 470 // Now show the correct widgets in the two hierarchies. 471 if (using_custom_frame_) { 472 gtk_widget_show_all(titlebar_left_buttons_vbox_); 473 gtk_widget_show_all(titlebar_right_buttons_vbox_); 474 } 475 UpdateMaximizeRestoreVisibility(); 476 } 477 478 GtkWidget* BrowserTitlebar::GetButtonHBox(bool left_side) { 479 if (left_side && titlebar_left_buttons_hbox_) 480 return titlebar_left_buttons_hbox_; 481 else if (!left_side && titlebar_right_buttons_hbox_) 482 return titlebar_right_buttons_hbox_; 483 484 // We put the min/max/restore/close buttons in a vbox so they are top aligned 485 // (up to padding) and don't vertically stretch. 486 GtkWidget* vbox = left_side ? titlebar_left_buttons_vbox_ : 487 titlebar_right_buttons_vbox_; 488 489 GtkWidget* top_padding = gtk_fixed_new(); 490 gtk_widget_set_size_request(top_padding, -1, kButtonOuterPadding); 491 gtk_box_pack_start(GTK_BOX(vbox), top_padding, FALSE, FALSE, 0); 492 493 GtkWidget* buttons_hbox = gtk_hbox_new(FALSE, kButtonSpacing); 494 gtk_box_pack_start(GTK_BOX(vbox), buttons_hbox, FALSE, FALSE, 0); 495 496 if (left_side) { 497 titlebar_left_buttons_hbox_ = buttons_hbox; 498 top_padding_left_ = top_padding; 499 } else { 500 titlebar_right_buttons_hbox_ = buttons_hbox; 501 top_padding_right_ = top_padding; 502 } 503 504 return buttons_hbox; 505 } 506 507 CustomDrawButton* BrowserTitlebar::BuildTitlebarButton(int image, 508 int image_pressed, int image_hot, GtkWidget* box, int tooltip) { 509 CustomDrawButton* button = new CustomDrawButton(image, image_pressed, 510 image_hot, 0); 511 gtk_widget_add_events(GTK_WIDGET(button->widget()), GDK_POINTER_MOTION_MASK); 512 g_signal_connect(button->widget(), "clicked", 513 G_CALLBACK(OnButtonClickedThunk), this); 514 g_signal_connect(button->widget(), "motion-notify-event", 515 G_CALLBACK(OnMouseMoveEvent), browser_window_); 516 std::string localized_tooltip = l10n_util::GetStringUTF8(tooltip); 517 gtk_widget_set_tooltip_text(button->widget(), 518 localized_tooltip.c_str()); 519 gtk_box_pack_start(GTK_BOX(box), button->widget(), FALSE, FALSE, 0); 520 return button; 521 } 522 523 void BrowserTitlebar::UpdateCustomFrame(bool use_custom_frame) { 524 using_custom_frame_ = use_custom_frame; 525 if (use_custom_frame) { 526 if (titlebar_left_buttons_vbox_) 527 gtk_widget_show_all(titlebar_left_buttons_vbox_); 528 if (titlebar_right_buttons_vbox_) 529 gtk_widget_show_all(titlebar_right_buttons_vbox_); 530 } else { 531 if (titlebar_left_buttons_vbox_) 532 gtk_widget_hide(titlebar_left_buttons_vbox_); 533 if (titlebar_right_buttons_vbox_) 534 gtk_widget_hide(titlebar_right_buttons_vbox_); 535 } 536 UpdateTitlebarAlignment(); 537 } 538 539 void BrowserTitlebar::UpdateTitleAndIcon() { 540 if (!app_mode_title_) 541 return; 542 543 // Get the page title and elide it to the available space. 544 string16 title = browser_window_->browser()->GetWindowTitleForCurrentTab(); 545 gtk_label_set_text(GTK_LABEL(app_mode_title_), UTF16ToUTF8(title).c_str()); 546 547 // Note: this isn't browser_window_->browser()->type() & Browser::TYPE_APP 548 // because we want to exclude Browser::TYPE_APP_POPUP. 549 if (browser_window_->browser()->type() == Browser::TYPE_APP || 550 browser_window_->browser()->type() == Browser::TYPE_APP_PANEL) { 551 // Update the system app icon. We don't need to update the icon in the top 552 // left of the custom frame, that will get updated when the throbber is 553 // updated. 554 SkBitmap icon = browser_window_->browser()->GetCurrentPageIcon(); 555 if (icon.empty()) { 556 gtk_util::SetWindowIcon(window_); 557 } else { 558 GdkPixbuf* icon_pixbuf = gfx::GdkPixbufFromSkBitmap(&icon); 559 gtk_window_set_icon(window_, icon_pixbuf); 560 g_object_unref(icon_pixbuf); 561 } 562 } 563 } 564 565 void BrowserTitlebar::UpdateThrobber(TabContents* tab_contents) { 566 DCHECK(app_mode_favicon_); 567 568 if (tab_contents && tab_contents->is_loading()) { 569 GdkPixbuf* icon_pixbuf = 570 throbber_.GetNextFrame(tab_contents->waiting_for_response()); 571 gtk_image_set_from_pixbuf(GTK_IMAGE(app_mode_favicon_), icon_pixbuf); 572 } else { 573 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); 574 575 // Note: this isn't browser_window_->browser()->type() & Browser::TYPE_APP 576 // because we want to exclude Browser::TYPE_APP_POPUP. 577 if (browser_window_->browser()->type() == Browser::TYPE_APP || 578 browser_window_->browser()->type() == Browser::TYPE_APP_PANEL) { 579 SkBitmap icon = browser_window_->browser()->GetCurrentPageIcon(); 580 if (icon.empty()) { 581 // Fallback to the Chromium icon if the page has no icon. 582 gtk_image_set_from_pixbuf(GTK_IMAGE(app_mode_favicon_), 583 rb.GetPixbufNamed(IDR_PRODUCT_LOGO_16)); 584 } else { 585 GdkPixbuf* icon_pixbuf = gfx::GdkPixbufFromSkBitmap(&icon); 586 gtk_image_set_from_pixbuf(GTK_IMAGE(app_mode_favicon_), icon_pixbuf); 587 g_object_unref(icon_pixbuf); 588 } 589 } else { 590 gtk_image_set_from_pixbuf(GTK_IMAGE(app_mode_favicon_), 591 rb.GetPixbufNamed(IDR_PRODUCT_LOGO_16)); 592 } 593 throbber_.Reset(); 594 } 595 } 596 597 void BrowserTitlebar::UpdateTitlebarAlignment() { 598 if (browser_window_->browser()->type() == Browser::TYPE_NORMAL) { 599 int top_padding = 0; 600 int side_padding = 0; 601 int vertical_offset = kNormalVerticalOffset; 602 603 if (using_custom_frame_) { 604 if (!browser_window_->IsMaximized()) { 605 top_padding = kTitlebarHeight; 606 } else if (using_custom_frame_ && browser_window_->IsMaximized()) { 607 vertical_offset = 0; 608 side_padding = kMaximizedTabstripPadding; 609 } 610 } 611 612 int right_padding = 0; 613 int left_padding = kTabStripLeftPadding; 614 if (titlebar_right_buttons_hbox_) 615 right_padding = side_padding; 616 if (titlebar_left_buttons_hbox_) 617 left_padding = side_padding; 618 619 gtk_alignment_set_padding(GTK_ALIGNMENT(titlebar_alignment_), 620 top_padding, 0, 621 left_padding, right_padding); 622 browser_window_->tabstrip()->SetVerticalOffset(vertical_offset); 623 } else { 624 if (using_custom_frame_ && !browser_window_->IsFullscreen()) { 625 gtk_alignment_set_padding(GTK_ALIGNMENT(titlebar_alignment_), 626 kAppModePaddingTop, kAppModePaddingBottom, kAppModePaddingLeft, 0); 627 gtk_widget_show(titlebar_alignment_); 628 } else { 629 gtk_widget_hide(titlebar_alignment_); 630 } 631 } 632 633 // Resize the buttons so that the clickable area extends all the way to the 634 // edge of the browser window. 635 GtkRequisition close_button_req = close_button_req_; 636 GtkRequisition minimize_button_req = minimize_button_req_; 637 GtkRequisition restore_button_req = restore_button_req_; 638 if (using_custom_frame_ && browser_window_->IsMaximized()) { 639 close_button_req.width += kButtonOuterPadding; 640 close_button_req.height += kButtonOuterPadding; 641 minimize_button_req.height += kButtonOuterPadding; 642 restore_button_req.height += kButtonOuterPadding; 643 if (top_padding_left_) 644 gtk_widget_hide(top_padding_left_); 645 if (top_padding_right_) 646 gtk_widget_hide(top_padding_right_); 647 } else { 648 if (top_padding_left_) 649 gtk_widget_show(top_padding_left_); 650 if (top_padding_right_) 651 gtk_widget_show(top_padding_right_); 652 } 653 if (close_button_.get()) { 654 gtk_widget_set_size_request(close_button_->widget(), 655 close_button_req.width, 656 close_button_req.height); 657 } 658 if (minimize_button_.get()) { 659 gtk_widget_set_size_request(minimize_button_->widget(), 660 minimize_button_req.width, 661 minimize_button_req.height); 662 } 663 if (maximize_button_.get()) { 664 gtk_widget_set_size_request(restore_button_->widget(), 665 restore_button_req.width, 666 restore_button_req.height); 667 } 668 } 669 670 void BrowserTitlebar::UpdateTextColor() { 671 if (!app_mode_title_) 672 return; 673 674 if (theme_service_ && theme_service_->UseGtkTheme()) { 675 // We don't really have any good options here. 676 // 677 // Colors from window manager themes aren't exposed in GTK; the window 678 // manager is a separate component and when there is information sharing 679 // (in the case of metacity), it's one way where the window manager reads 680 // data from the GTK theme (which allows us to do a decent job with 681 // picking the frame color). 682 // 683 // We probably won't match in the majority of cases, but we can at the 684 // very least make things legible. The default metacity and xfwm themes 685 // on ubuntu have white text hardcoded. Determine whether black or white 686 // has more luminosity contrast and then set that color as the text 687 // color. 688 GdkColor frame_color; 689 if (window_has_focus_) { 690 frame_color = theme_service_->GetGdkColor( 691 ThemeService::COLOR_FRAME); 692 } else { 693 frame_color = theme_service_->GetGdkColor( 694 ThemeService::COLOR_FRAME_INACTIVE); 695 } 696 GdkColor text_color = PickLuminosityContrastingColor( 697 &frame_color, >k_util::kGdkWhite, >k_util::kGdkBlack); 698 gtk_util::SetLabelColor(app_mode_title_, &text_color); 699 } else { 700 gtk_util::SetLabelColor(app_mode_title_, >k_util::kGdkWhite); 701 } 702 } 703 704 void BrowserTitlebar::ShowFaviconMenu(GdkEventButton* event) { 705 if (!favicon_menu_model_.get()) { 706 favicon_menu_model_.reset( 707 new PopupPageMenuModel(this, browser_window_->browser())); 708 709 favicon_menu_.reset(new MenuGtk(NULL, favicon_menu_model_.get())); 710 } 711 712 favicon_menu_->PopupForWidget(app_mode_favicon_, event->button, event->time); 713 } 714 715 void BrowserTitlebar::MaximizeButtonClicked() { 716 GdkEvent* event = gtk_get_current_event(); 717 if (event->button.button == 1) { 718 gtk_window_maximize(window_); 719 } else { 720 GtkWidget* widget = GTK_WIDGET(window_); 721 GdkScreen* screen = gtk_widget_get_screen(widget); 722 gint monitor = gdk_screen_get_monitor_at_window(screen, widget->window); 723 GdkRectangle screen_rect; 724 gdk_screen_get_monitor_geometry(screen, monitor, &screen_rect); 725 726 gint x, y; 727 gtk_window_get_position(window_, &x, &y); 728 gint width = widget->allocation.width; 729 gint height = widget->allocation.height; 730 731 if (event->button.button == 3) { 732 x = 0; 733 width = screen_rect.width; 734 } else if (event->button.button == 2) { 735 y = 0; 736 height = screen_rect.height; 737 } 738 739 browser_window_->SetBounds(gfx::Rect(x, y, width, height)); 740 } 741 gdk_event_free(event); 742 } 743 744 void BrowserTitlebar::UpdateMaximizeRestoreVisibility() { 745 if (maximize_button_.get()) { 746 if (browser_window_->IsMaximized()) { 747 gtk_widget_hide(maximize_button_->widget()); 748 gtk_widget_show(restore_button_->widget()); 749 } else { 750 gtk_widget_hide(restore_button_->widget()); 751 gtk_widget_show(maximize_button_->widget()); 752 } 753 } 754 } 755 756 gboolean BrowserTitlebar::OnWindowStateChanged(GtkWindow* window, 757 GdkEventWindowState* event) { 758 UpdateMaximizeRestoreVisibility(); 759 UpdateTitlebarAlignment(); 760 UpdateTextColor(); 761 return FALSE; 762 } 763 764 gboolean BrowserTitlebar::OnScroll(GtkWidget* widget, GdkEventScroll* event) { 765 TabStripModel* tabstrip_model = browser_window_->browser()->tabstrip_model(); 766 int index = tabstrip_model->active_index(); 767 if (event->direction == GDK_SCROLL_LEFT || 768 event->direction == GDK_SCROLL_UP) { 769 if (index != 0) 770 tabstrip_model->SelectPreviousTab(); 771 } else if (index + 1 < tabstrip_model->count()) { 772 tabstrip_model->SelectNextTab(); 773 } 774 return TRUE; 775 } 776 777 // static 778 void BrowserTitlebar::OnButtonClicked(GtkWidget* button) { 779 if (close_button_.get() && close_button_->widget() == button) { 780 browser_window_->Close(); 781 } else if (restore_button_.get() && restore_button_->widget() == button) { 782 browser_window_->UnMaximize(); 783 } else if (maximize_button_.get() && maximize_button_->widget() == button) { 784 MaximizeButtonClicked(); 785 } else if (minimize_button_.get() && minimize_button_->widget() == button) { 786 gtk_window_iconify(window_); 787 } 788 } 789 790 gboolean BrowserTitlebar::OnButtonPressed(GtkWidget* widget, 791 GdkEventButton* event) { 792 if (event->button != 1) 793 return FALSE; 794 795 ShowFaviconMenu(event); 796 return TRUE; 797 } 798 799 void BrowserTitlebar::ShowContextMenu(GdkEventButton* event) { 800 if (!context_menu_.get()) { 801 context_menu_model_.reset(new ContextMenuModel(this)); 802 context_menu_.reset(new MenuGtk(NULL, context_menu_model_.get())); 803 } 804 805 context_menu_->PopupAsContext(gfx::Point(event->x_root, event->y_root), 806 event->time); 807 } 808 809 bool BrowserTitlebar::IsCommandIdEnabled(int command_id) const { 810 if (command_id == kShowWindowDecorationsCommand) 811 return true; 812 813 return browser_window_->browser()->command_updater()-> 814 IsCommandEnabled(command_id); 815 } 816 817 bool BrowserTitlebar::IsCommandIdChecked(int command_id) const { 818 if (command_id == kShowWindowDecorationsCommand) { 819 PrefService* prefs = browser_window_->browser()->profile()->GetPrefs(); 820 return !prefs->GetBoolean(prefs::kUseCustomChromeFrame); 821 } 822 823 EncodingMenuController controller; 824 if (controller.DoesCommandBelongToEncodingMenu(command_id)) { 825 TabContents* tab_contents = 826 browser_window_->browser()->GetSelectedTabContents(); 827 if (tab_contents) { 828 return controller.IsItemChecked(browser_window_->browser()->profile(), 829 tab_contents->encoding(), 830 command_id); 831 } 832 return false; 833 } 834 835 NOTREACHED(); 836 return false; 837 } 838 839 void BrowserTitlebar::ExecuteCommand(int command_id) { 840 if (command_id == kShowWindowDecorationsCommand) { 841 PrefService* prefs = browser_window_->browser()->profile()->GetPrefs(); 842 prefs->SetBoolean(prefs::kUseCustomChromeFrame, 843 !prefs->GetBoolean(prefs::kUseCustomChromeFrame)); 844 return; 845 } 846 847 browser_window_->browser()->ExecuteCommand(command_id); 848 } 849 850 bool BrowserTitlebar::GetAcceleratorForCommandId( 851 int command_id, ui::Accelerator* accelerator) { 852 const ui::AcceleratorGtk* accelerator_gtk = 853 AcceleratorsGtk::GetInstance()->GetPrimaryAcceleratorForCommand( 854 command_id); 855 if (accelerator_gtk) 856 *accelerator = *accelerator_gtk; 857 return accelerator_gtk; 858 } 859 860 void BrowserTitlebar::Observe(NotificationType type, 861 const NotificationSource& source, 862 const NotificationDetails& details) { 863 switch (type.value) { 864 case NotificationType::BROWSER_THEME_CHANGED: 865 UpdateTextColor(); 866 break; 867 868 default: 869 NOTREACHED(); 870 } 871 } 872 873 void BrowserTitlebar::ActiveWindowChanged(GdkWindow* active_window) { 874 // Can be called during shutdown; BrowserWindowGtk will set our |window_| 875 // to NULL during that time. 876 if (!window_) 877 return; 878 879 window_has_focus_ = GTK_WIDGET(window_)->window == active_window; 880 UpdateTextColor(); 881 } 882 883 /////////////////////////////////////////////////////////////////////////////// 884 // BrowserTitlebar::Throbber implementation 885 // TODO(tc): Handle anti-clockwise spinning when waiting for a connection. 886 887 // We don't bother to clean up these or the pixbufs they contain when we exit. 888 static std::vector<GdkPixbuf*>* g_throbber_frames = NULL; 889 static std::vector<GdkPixbuf*>* g_throbber_waiting_frames = NULL; 890 891 // Load |resource_id| from the ResourceBundle and split it into a series of 892 // square GdkPixbufs that get stored in |frames|. 893 static void MakeThrobberFrames(int resource_id, 894 std::vector<GdkPixbuf*>* frames) { 895 ResourceBundle &rb = ResourceBundle::GetSharedInstance(); 896 SkBitmap* frame_strip = rb.GetBitmapNamed(resource_id); 897 898 // Each frame of the animation is a square, so we use the height as the 899 // frame size. 900 int frame_size = frame_strip->height(); 901 size_t num_frames = frame_strip->width() / frame_size; 902 903 // Make a separate GdkPixbuf for each frame of the animation. 904 for (size_t i = 0; i < num_frames; ++i) { 905 SkBitmap frame = SkBitmapOperations::CreateTiledBitmap(*frame_strip, 906 i * frame_size, 0, frame_size, frame_size); 907 frames->push_back(gfx::GdkPixbufFromSkBitmap(&frame)); 908 } 909 } 910 911 GdkPixbuf* BrowserTitlebar::Throbber::GetNextFrame(bool is_waiting) { 912 Throbber::InitFrames(); 913 if (is_waiting) { 914 return (*g_throbber_waiting_frames)[current_waiting_frame_++ % 915 g_throbber_waiting_frames->size()]; 916 } else { 917 return (*g_throbber_frames)[current_frame_++ % g_throbber_frames->size()]; 918 } 919 } 920 921 void BrowserTitlebar::Throbber::Reset() { 922 current_frame_ = 0; 923 current_waiting_frame_ = 0; 924 } 925 926 // static 927 void BrowserTitlebar::Throbber::InitFrames() { 928 if (g_throbber_frames) 929 return; 930 931 // We load the light version of the throbber since it'll be in the titlebar. 932 g_throbber_frames = new std::vector<GdkPixbuf*>; 933 MakeThrobberFrames(IDR_THROBBER_LIGHT, g_throbber_frames); 934 935 g_throbber_waiting_frames = new std::vector<GdkPixbuf*>; 936 MakeThrobberFrames(IDR_THROBBER_WAITING_LIGHT, g_throbber_waiting_frames); 937 } 938 939 BrowserTitlebar::ContextMenuModel::ContextMenuModel( 940 ui::SimpleMenuModel::Delegate* delegate) 941 : SimpleMenuModel(delegate) { 942 AddItemWithStringId(IDC_NEW_TAB, IDS_TAB_CXMENU_NEWTAB); 943 AddItemWithStringId(IDC_RESTORE_TAB, IDS_RESTORE_TAB); 944 AddSeparator(); 945 AddItemWithStringId(IDC_TASK_MANAGER, IDS_TASK_MANAGER); 946 AddSeparator(); 947 AddCheckItemWithStringId(kShowWindowDecorationsCommand, 948 IDS_SHOW_WINDOW_DECORATIONS_MENU); 949 } 950