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_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/i18n/rtl.h" 15 #include "base/memory/singleton.h" 16 #include "base/prefs/pref_service.h" 17 #include "base/strings/string_piece.h" 18 #include "base/strings/string_tokenizer.h" 19 #include "base/strings/utf_string_conversions.h" 20 #include "chrome/app/chrome_command_ids.h" 21 #include "chrome/browser/browser_process.h" 22 #include "chrome/browser/chrome_notification_types.h" 23 #include "chrome/browser/profiles/avatar_menu_model.h" 24 #include "chrome/browser/profiles/profile.h" 25 #include "chrome/browser/profiles/profile_info_cache.h" 26 #include "chrome/browser/profiles/profile_manager.h" 27 #include "chrome/browser/themes/theme_properties.h" 28 #include "chrome/browser/ui/browser.h" 29 #include "chrome/browser/ui/browser_commands.h" 30 #include "chrome/browser/ui/gtk/accelerators_gtk.h" 31 #include "chrome/browser/ui/gtk/avatar_menu_bubble_gtk.h" 32 #include "chrome/browser/ui/gtk/avatar_menu_button_gtk.h" 33 #include "chrome/browser/ui/gtk/browser_window_gtk.h" 34 #include "chrome/browser/ui/gtk/custom_button.h" 35 #if defined(USE_GCONF) 36 #include "chrome/browser/ui/gtk/gconf_titlebar_listener.h" 37 #endif 38 #include "chrome/browser/ui/gtk/gtk_theme_service.h" 39 #include "chrome/browser/ui/gtk/gtk_util.h" 40 #include "chrome/browser/ui/gtk/menu_gtk.h" 41 #include "chrome/browser/ui/gtk/nine_box.h" 42 #include "chrome/browser/ui/gtk/tabs/tab_strip_gtk.h" 43 #include "chrome/browser/ui/gtk/unity_service.h" 44 #include "chrome/browser/ui/tabs/tab_strip_model.h" 45 #include "chrome/browser/ui/toolbar/encoding_menu_controller.h" 46 #include "chrome/browser/ui/toolbar/wrench_menu_model.h" 47 #include "chrome/common/chrome_switches.h" 48 #include "chrome/common/pref_names.h" 49 #include "content/public/browser/notification_service.h" 50 #include "content/public/browser/web_contents.h" 51 #include "grit/generated_resources.h" 52 #include "grit/theme_resources.h" 53 #include "grit/ui_resources.h" 54 #include "ui/base/gtk/gtk_hig_constants.h" 55 #include "ui/base/l10n/l10n_util.h" 56 #include "ui/base/resource/resource_bundle.h" 57 #include "ui/base/x/active_window_watcher_x.h" 58 #include "ui/gfx/image/image.h" 59 60 using content::WebContents; 61 62 namespace { 63 64 // The space above the titlebars. 65 const int kTitlebarHeight = 14; 66 67 // The thickness in pixels of the tab border. 68 const int kTabOuterThickness = 1; 69 70 // Amount to offset the tab images relative to the background. 71 const int kNormalVerticalOffset = kTitlebarHeight + kTabOuterThickness; 72 73 // A linux specific menu item for toggling window decorations. 74 const int kShowWindowDecorationsCommand = 200; 75 76 const int kAvatarBottomSpacing = 1; 77 // There are 2 px on each side of the avatar (between the frame border and 78 // it on the left, and between it and the tabstrip on the right). 79 const int kAvatarSideSpacing = 2; 80 81 // The thickness of the custom frame border; we need it here to enlarge the 82 // close button whent the custom frame border isn't showing but the custom 83 // titlebar is showing. 84 const int kFrameBorderThickness = 4; 85 86 // There is a 4px gap between the icon and the title text. 87 const int kIconTitleSpacing = 4; 88 89 // Padding around the icon when in app mode or popup mode. 90 const int kAppModePaddingTop = 5; 91 const int kAppModePaddingBottom = 4; 92 const int kAppModePaddingLeft = 2; 93 94 // The left padding of the tab strip. In Views, the tab strip has a left 95 // margin of FrameBorderThickness + kClientEdgeThickness. This offset is to 96 // account for kClientEdgeThickness. 97 const int kTabStripLeftPadding = 1; 98 99 // Spacing between buttons of the titlebar. 100 const int kButtonSpacing = 2; 101 102 // Spacing around outside of titlebar buttons. 103 const int kButtonOuterPadding = 2; 104 105 // Spacing between tabstrip and window control buttons (when the window is 106 // maximized). 107 const int kMaximizedTabstripPadding = 16; 108 109 gboolean OnMouseMoveEvent(GtkWidget* widget, GdkEventMotion* event, 110 BrowserWindowGtk* browser_window) { 111 // Reset to the default mouse cursor. 112 browser_window->ResetCustomFrameCursor(); 113 return TRUE; 114 } 115 116 // Converts a GdkColor to a color_utils::HSL. 117 color_utils::HSL GdkColorToHSL(const GdkColor* color) { 118 color_utils::HSL hsl; 119 color_utils::SkColorToHSL(SkColorSetRGB(color->red >> 8, 120 color->green >> 8, 121 color->blue >> 8), &hsl); 122 return hsl; 123 } 124 125 // Returns either |one| or |two| based on which has a greater difference in 126 // luminosity. 127 GdkColor PickLuminosityContrastingColor(const GdkColor* base, 128 const GdkColor* one, 129 const GdkColor* two) { 130 // Convert all GdkColors to color_utils::HSLs. 131 color_utils::HSL baseHSL = GdkColorToHSL(base); 132 color_utils::HSL oneHSL = GdkColorToHSL(one); 133 color_utils::HSL twoHSL = GdkColorToHSL(two); 134 double one_difference = fabs(baseHSL.l - oneHSL.l); 135 double two_difference = fabs(baseHSL.l - twoHSL.l); 136 137 // Be biased towards the first color presented. 138 if (two_difference > one_difference + 0.1) 139 return *two; 140 else 141 return *one; 142 } 143 144 } // namespace 145 146 //////////////////////////////////////////////////////////////////////////////// 147 // PopupPageMenuModel 148 149 // A menu model that builds the contents of the menu shown for popups (when the 150 // user clicks on the favicon) and all of its submenus. 151 class PopupPageMenuModel : public ui::SimpleMenuModel { 152 public: 153 PopupPageMenuModel(ui::SimpleMenuModel::Delegate* delegate, Browser* browser); 154 virtual ~PopupPageMenuModel() { } 155 156 private: 157 void Build(); 158 159 // Models for submenus referenced by this model. SimpleMenuModel only uses 160 // weak references so these must be kept for the lifetime of the top-level 161 // model. 162 scoped_ptr<ZoomMenuModel> zoom_menu_model_; 163 scoped_ptr<EncodingMenuModel> encoding_menu_model_; 164 Browser* browser_; // weak 165 166 DISALLOW_COPY_AND_ASSIGN(PopupPageMenuModel); 167 }; 168 169 PopupPageMenuModel::PopupPageMenuModel( 170 ui::SimpleMenuModel::Delegate* delegate, 171 Browser* browser) 172 : ui::SimpleMenuModel(delegate), browser_(browser) { 173 Build(); 174 } 175 176 void PopupPageMenuModel::Build() { 177 AddItemWithStringId(IDC_BACK, IDS_CONTENT_CONTEXT_BACK); 178 AddItemWithStringId(IDC_FORWARD, IDS_CONTENT_CONTEXT_FORWARD); 179 AddItemWithStringId(IDC_RELOAD, IDS_APP_MENU_RELOAD); 180 AddSeparator(ui::NORMAL_SEPARATOR); 181 AddItemWithStringId(IDC_SHOW_AS_TAB, IDS_SHOW_AS_TAB); 182 AddSeparator(ui::NORMAL_SEPARATOR); 183 AddItemWithStringId(IDC_CUT, IDS_CUT); 184 AddItemWithStringId(IDC_COPY, IDS_COPY); 185 AddItemWithStringId(IDC_PASTE, IDS_PASTE); 186 AddSeparator(ui::NORMAL_SEPARATOR); 187 AddItemWithStringId(IDC_FIND, IDS_FIND); 188 AddItemWithStringId(IDC_PRINT, IDS_PRINT); 189 zoom_menu_model_.reset(new ZoomMenuModel(delegate())); 190 AddSubMenuWithStringId(IDC_ZOOM_MENU, IDS_ZOOM_MENU, zoom_menu_model_.get()); 191 192 encoding_menu_model_.reset(new EncodingMenuModel(browser_)); 193 AddSubMenuWithStringId(IDC_ENCODING_MENU, IDS_ENCODING_MENU, 194 encoding_menu_model_.get()); 195 196 AddSeparator(ui::NORMAL_SEPARATOR); 197 AddItemWithStringId(IDC_CLOSE_WINDOW, IDS_CLOSE); 198 } 199 200 //////////////////////////////////////////////////////////////////////////////// 201 // BrowserTitlebar 202 203 // static 204 const char BrowserTitlebar::kDefaultButtonString[] = ":minimize,maximize,close"; 205 206 BrowserTitlebar::BrowserTitlebar(BrowserWindowGtk* browser_window, 207 GtkWindow* window) 208 : browser_window_(browser_window), 209 window_(window), 210 container_(NULL), 211 container_hbox_(NULL), 212 titlebar_left_buttons_vbox_(NULL), 213 titlebar_right_buttons_vbox_(NULL), 214 titlebar_left_buttons_hbox_(NULL), 215 titlebar_right_buttons_hbox_(NULL), 216 titlebar_left_avatar_frame_(NULL), 217 titlebar_right_avatar_frame_(NULL), 218 titlebar_left_label_frame_(NULL), 219 titlebar_right_label_frame_(NULL), 220 avatar_(NULL), 221 avatar_label_(NULL), 222 avatar_label_bg_(NULL), 223 top_padding_left_(NULL), 224 top_padding_right_(NULL), 225 titlebar_alignment_(NULL), 226 app_mode_favicon_(NULL), 227 app_mode_title_(NULL), 228 using_custom_frame_(false), 229 window_has_focus_(false), 230 display_avatar_on_left_(false), 231 theme_service_(NULL) { 232 } 233 234 void BrowserTitlebar::Init() { 235 // The widget hierarchy is shown below. 236 // 237 // +- EventBox (container_) ------------------------------------------------+ 238 // +- HBox (container_hbox_) -----------------------------------------------+ 239 // |+ VBox ---++- Algn. -++- Alignment --------------++- Algn. -++ VBox ---+| 240 // || titlebar||titlebar || (titlebar_alignment_) ||titlebar || titlebar|| 241 // || left ||left || ||right || right || 242 // || button ||spy || ||spy || button || 243 // || vbox ||frame ||+- TabStripGtk ---------+||frame || vbox || 244 // || || || tab tab tabclose ||| || || 245 // || || ||+------------------------+|| || || 246 // |+---------++---------++--------------------------++---------++---------+| 247 // +------------------------------------------------------------------------+ 248 // 249 // There are two vboxes on either side of |container_hbox_| because when the 250 // desktop is GNOME, the button placement is configurable based on a metacity 251 // gconf key. We can't just have one vbox and move it back and forth because 252 // the gconf language allows you to place buttons on both sides of the 253 // window. This being Linux, I'm sure there's a bunch of people who have 254 // already configured their window manager to do so and we don't want to get 255 // confused when that happens. The actual contents of these vboxes are lazily 256 // generated so they don't interfere with alignment when everything is 257 // stuffed in the other box. 258 // 259 // Each vbox has the following hierarchy if it contains buttons: 260 // 261 // +- VBox (titlebar_{l,r}_buttons_vbox_) ---------+ 262 // |+- Fixed (top_padding_{l,r}_) ----------------+| 263 // ||+- HBox (titlebar_{l,r}_buttons_hbox_ ------+|| 264 // ||| (buttons of a configurable layout) ||| 265 // ||+-------------------------------------------+|| 266 // |+---------------------------------------------+| 267 // +-----------------------------------------------+ 268 // 269 // The two spy alignments are only allocated if this window is an incognito 270 // window. Only one of them holds the spy image. 271 // 272 // If we're a popup window or in app mode, we don't display the spy guy or 273 // the tab strip. Instead, put an hbox in titlebar_alignment_ in place of 274 // the tab strip. 275 // +- Alignment (titlebar_alignment_) -----------------------------------+ 276 // |+ HBox -------------------------------------------------------------+| 277 // ||+- TabStripGtk -++- Image ----------------++- Label --------------+|| 278 // ||| hidden ++ (app_mode_favicon_) || (app_mode_title_) ||| 279 // ||| || favicon || page title ||| 280 // ||+---------------++------------------------++----------------------+|| 281 // |+-------------------------------------------------------------------+| 282 // +---------------------------------------------------------------------+ 283 container_hbox_ = gtk_hbox_new(FALSE, 0); 284 285 container_ = gtk_event_box_new(); 286 gtk_widget_set_name(container_, "chrome-browser-titlebar"); 287 gtk_event_box_set_visible_window(GTK_EVENT_BOX(container_), FALSE); 288 gtk_container_add(GTK_CONTAINER(container_), container_hbox_); 289 290 g_signal_connect(container_, "scroll-event", G_CALLBACK(OnScrollThunk), this); 291 292 g_signal_connect(window_, "window-state-event", 293 G_CALLBACK(OnWindowStateChangedThunk), this); 294 295 // Allocate the two button boxes on the left and right parts of the bar. These 296 // are always allocated, but only displayed in incognito mode or when using 297 // multiple profiles. 298 titlebar_left_buttons_vbox_ = gtk_vbox_new(FALSE, 0); 299 gtk_box_pack_start(GTK_BOX(container_hbox_), titlebar_left_buttons_vbox_, 300 FALSE, FALSE, 0); 301 titlebar_left_avatar_frame_ = gtk_alignment_new(0.0, 0.0, 1.0, 1.0); 302 gtk_widget_set_no_show_all(titlebar_left_avatar_frame_, TRUE); 303 gtk_alignment_set_padding(GTK_ALIGNMENT(titlebar_left_avatar_frame_), 0, 304 kAvatarBottomSpacing, kAvatarSideSpacing, kAvatarSideSpacing); 305 gtk_box_pack_start(GTK_BOX(container_hbox_), titlebar_left_avatar_frame_, 306 FALSE, FALSE, 0); 307 308 titlebar_left_label_frame_ = gtk_alignment_new(0.0, 0.5, 0.0, 0.0); 309 gtk_widget_set_no_show_all(titlebar_left_label_frame_, TRUE); 310 gtk_alignment_set_padding(GTK_ALIGNMENT(titlebar_left_label_frame_), 0, 311 kAvatarBottomSpacing, kAvatarSideSpacing, kAvatarSideSpacing); 312 gtk_box_pack_start(GTK_BOX(container_hbox_), titlebar_left_label_frame_, 313 FALSE, FALSE, 0); 314 315 titlebar_right_avatar_frame_ = gtk_alignment_new(0.0, 0.0, 1.0, 1.0); 316 gtk_widget_set_no_show_all(titlebar_right_avatar_frame_, TRUE); 317 gtk_alignment_set_padding(GTK_ALIGNMENT(titlebar_right_avatar_frame_), 0, 318 kAvatarBottomSpacing, kAvatarSideSpacing, kAvatarSideSpacing); 319 gtk_box_pack_end(GTK_BOX(container_hbox_), titlebar_right_avatar_frame_, 320 FALSE, FALSE, 0); 321 322 titlebar_right_label_frame_ = gtk_alignment_new(0.0, 0.5, 0.0, 0.0); 323 gtk_widget_set_no_show_all(titlebar_right_label_frame_, TRUE); 324 gtk_alignment_set_padding(GTK_ALIGNMENT(titlebar_right_label_frame_), 0, 325 kAvatarBottomSpacing, kAvatarSideSpacing, kAvatarSideSpacing); 326 gtk_box_pack_end(GTK_BOX(container_hbox_), titlebar_right_label_frame_, 327 FALSE, FALSE, 0); 328 329 titlebar_right_buttons_vbox_ = gtk_vbox_new(FALSE, 0); 330 gtk_box_pack_end(GTK_BOX(container_hbox_), titlebar_right_buttons_vbox_, 331 FALSE, FALSE, 0); 332 333 // Create the Avatar button and listen for notifications. It must always be 334 // created because a new profile can be added at any time. 335 avatar_button_.reset(new AvatarMenuButtonGtk(browser_window_->browser())); 336 337 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED, 338 content::NotificationService::AllSources()); 339 340 #if defined(USE_GCONF) 341 // Either read the gconf database and register for updates (on GNOME), or use 342 // the default value (anywhere else). 343 GConfTitlebarListener::GetInstance()->SetTitlebarButtons(this); 344 #else 345 BuildButtons(kDefaultButtonString); 346 #endif 347 348 UpdateAvatar(); 349 350 // We use an alignment to control the titlebar height. 351 titlebar_alignment_ = gtk_alignment_new(0.0, 0.0, 1.0, 1.0); 352 if (browser_window_->browser()->is_type_tabbed()) { 353 gtk_box_pack_start(GTK_BOX(container_hbox_), titlebar_alignment_, TRUE, 354 TRUE, 0); 355 356 // Put the tab strip in the titlebar. 357 gtk_container_add(GTK_CONTAINER(titlebar_alignment_), 358 browser_window_->tabstrip()->widget()); 359 } else { 360 // App mode specific widgets. 361 gtk_box_pack_start(GTK_BOX(container_hbox_), titlebar_alignment_, TRUE, 362 TRUE, 0); 363 GtkWidget* app_mode_hbox = gtk_hbox_new(FALSE, kIconTitleSpacing); 364 gtk_container_add(GTK_CONTAINER(titlebar_alignment_), app_mode_hbox); 365 366 // Put the tab strip in the hbox even though we don't show it. Sometimes 367 // we need the position of the tab strip so make sure it's in our widget 368 // hierarchy. 369 gtk_box_pack_start(GTK_BOX(app_mode_hbox), 370 browser_window_->tabstrip()->widget(), FALSE, FALSE, 0); 371 372 GtkWidget* favicon_event_box = gtk_event_box_new(); 373 gtk_event_box_set_visible_window(GTK_EVENT_BOX(favicon_event_box), FALSE); 374 g_signal_connect(favicon_event_box, "button-press-event", 375 G_CALLBACK(OnFaviconMenuButtonPressedThunk), this); 376 gtk_box_pack_start(GTK_BOX(app_mode_hbox), favicon_event_box, FALSE, 377 FALSE, 0); 378 // We use the app logo as a placeholder image so the title doesn't jump 379 // around. 380 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 381 app_mode_favicon_ = gtk_image_new_from_pixbuf(rb.GetNativeImageNamed( 382 IDR_PRODUCT_LOGO_16, ui::ResourceBundle::RTL_ENABLED).ToGdkPixbuf()); 383 g_object_set_data(G_OBJECT(app_mode_favicon_), "left-align-popup", 384 reinterpret_cast<void*>(true)); 385 gtk_container_add(GTK_CONTAINER(favicon_event_box), app_mode_favicon_); 386 387 app_mode_title_ = gtk_label_new(NULL); 388 gtk_label_set_ellipsize(GTK_LABEL(app_mode_title_), PANGO_ELLIPSIZE_END); 389 gtk_misc_set_alignment(GTK_MISC(app_mode_title_), 0.0, 0.5); 390 gtk_box_pack_start(GTK_BOX(app_mode_hbox), app_mode_title_, TRUE, TRUE, 391 0); 392 393 UpdateTitleAndIcon(); 394 } 395 396 theme_service_ = GtkThemeService::GetFrom( 397 browser_window_->browser()->profile()); 398 registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED, 399 content::Source<ThemeService>(theme_service_)); 400 theme_service_->InitThemesFor(this); 401 402 gtk_widget_show_all(container_); 403 404 ui::ActiveWindowWatcherX::AddObserver(this); 405 } 406 407 BrowserTitlebar::~BrowserTitlebar() { 408 ui::ActiveWindowWatcherX::RemoveObserver(this); 409 #if defined(USE_GCONF) 410 GConfTitlebarListener::GetInstance()->RemoveObserver(this); 411 #endif 412 } 413 414 void BrowserTitlebar::BuildButtons(const std::string& button_string) { 415 // Clear out all previous data. 416 close_button_.reset(); 417 restore_button_.reset(); 418 maximize_button_.reset(); 419 minimize_button_.reset(); 420 gtk_util::RemoveAllChildren(titlebar_left_buttons_vbox_); 421 gtk_util::RemoveAllChildren(titlebar_right_buttons_vbox_); 422 titlebar_left_buttons_hbox_ = NULL; 423 titlebar_right_buttons_hbox_ = NULL; 424 top_padding_left_ = NULL; 425 top_padding_right_ = NULL; 426 427 bool left_side = true; 428 base::StringTokenizer tokenizer(button_string, ":,"); 429 tokenizer.set_options(base::StringTokenizer::RETURN_DELIMS); 430 int left_count = 0; 431 int right_count = 0; 432 while (tokenizer.GetNext()) { 433 if (tokenizer.token_is_delim()) { 434 if (*tokenizer.token_begin() == ':') 435 left_side = false; 436 } else { 437 base::StringPiece token = tokenizer.token_piece(); 438 if (BuildButton(token.as_string(), left_side)) 439 (left_side ? left_count : right_count)++; 440 } 441 } 442 443 // If we are in incognito mode, add the spy guy to either the end of the left 444 // or the beginning of the right depending on which side has fewer buttons. 445 display_avatar_on_left_ = right_count > left_count; 446 447 // Now show the correct widgets in the two hierarchies. 448 if (using_custom_frame_) { 449 gtk_widget_show_all(titlebar_left_buttons_vbox_); 450 gtk_widget_show_all(titlebar_right_buttons_vbox_); 451 } 452 UpdateMaximizeRestoreVisibility(); 453 } 454 455 bool BrowserTitlebar::BuildButton(const std::string& button_token, 456 bool left_side) { 457 if (button_token == "minimize") { 458 minimize_button_.reset(CreateTitlebarButton("minimize", left_side)); 459 460 gtk_widget_size_request(minimize_button_->widget(), 461 &minimize_button_req_); 462 return true; 463 } else if (button_token == "maximize") { 464 restore_button_.reset(CreateTitlebarButton("unmaximize", left_side)); 465 maximize_button_.reset(CreateTitlebarButton("maximize", left_side)); 466 467 gtk_util::SetButtonClickableByMouseButtons(maximize_button_->widget(), 468 true, true, true); 469 gtk_widget_size_request(restore_button_->widget(), 470 &restore_button_req_); 471 return true; 472 } else if (button_token == "close") { 473 close_button_.reset(CreateTitlebarButton("close", left_side)); 474 close_button_->set_flipped(left_side); 475 476 gtk_widget_size_request(close_button_->widget(), &close_button_req_); 477 return true; 478 } 479 // Ignore any other values like "pin" since we don't have images for 480 // those. 481 return false; 482 } 483 484 GtkWidget* BrowserTitlebar::GetButtonHBox(bool left_side) { 485 if (left_side && titlebar_left_buttons_hbox_) 486 return titlebar_left_buttons_hbox_; 487 else if (!left_side && titlebar_right_buttons_hbox_) 488 return titlebar_right_buttons_hbox_; 489 490 // We put the min/max/restore/close buttons in a vbox so they are top aligned 491 // (up to padding) and don't vertically stretch. 492 GtkWidget* vbox = left_side ? titlebar_left_buttons_vbox_ : 493 titlebar_right_buttons_vbox_; 494 495 GtkWidget* top_padding = gtk_fixed_new(); 496 gtk_widget_set_size_request(top_padding, -1, kButtonOuterPadding); 497 gtk_box_pack_start(GTK_BOX(vbox), top_padding, FALSE, FALSE, 0); 498 499 GtkWidget* buttons_hbox = gtk_hbox_new(FALSE, kButtonSpacing); 500 gtk_box_pack_start(GTK_BOX(vbox), buttons_hbox, FALSE, FALSE, 0); 501 502 if (left_side) { 503 titlebar_left_buttons_hbox_ = buttons_hbox; 504 top_padding_left_ = top_padding; 505 } else { 506 titlebar_right_buttons_hbox_ = buttons_hbox; 507 top_padding_right_ = top_padding; 508 } 509 510 return buttons_hbox; 511 } 512 513 CustomDrawButton* BrowserTitlebar::CreateTitlebarButton( 514 const std::string& button_name, bool left_side) { 515 int normal_image_id; 516 int pressed_image_id; 517 int hover_image_id; 518 int tooltip_id; 519 GetButtonResources(button_name, &normal_image_id, &pressed_image_id, 520 &hover_image_id, &tooltip_id); 521 522 CustomDrawButton* button = new CustomDrawButton(normal_image_id, 523 pressed_image_id, 524 hover_image_id, 525 0); 526 gtk_widget_add_events(GTK_WIDGET(button->widget()), GDK_POINTER_MOTION_MASK); 527 g_signal_connect(button->widget(), "clicked", 528 G_CALLBACK(OnButtonClickedThunk), this); 529 g_signal_connect(button->widget(), "motion-notify-event", 530 G_CALLBACK(OnMouseMoveEvent), browser_window_); 531 532 std::string localized_tooltip = l10n_util::GetStringUTF8(tooltip_id); 533 gtk_widget_set_tooltip_text(button->widget(), 534 localized_tooltip.c_str()); 535 536 GtkWidget* box = GetButtonHBox(left_side); 537 gtk_box_pack_start(GTK_BOX(box), button->widget(), FALSE, FALSE, 0); 538 return button; 539 } 540 541 void BrowserTitlebar::GetButtonResources(const std::string& button_name, 542 int* normal_image_id, 543 int* pressed_image_id, 544 int* hover_image_id, 545 int* tooltip_id) const { 546 if (button_name == "close") { 547 *normal_image_id = IDR_CLOSE; 548 *pressed_image_id = IDR_CLOSE_P; 549 *hover_image_id = IDR_CLOSE_H; 550 *tooltip_id = IDS_XPFRAME_CLOSE_TOOLTIP; 551 } else if (button_name == "minimize") { 552 *normal_image_id = IDR_MINIMIZE; 553 *pressed_image_id = IDR_MINIMIZE_P; 554 *hover_image_id = IDR_MINIMIZE_H; 555 *tooltip_id = IDS_XPFRAME_MINIMIZE_TOOLTIP; 556 } else if (button_name == "maximize") { 557 *normal_image_id = IDR_MAXIMIZE; 558 *pressed_image_id = IDR_MAXIMIZE_P; 559 *hover_image_id = IDR_MAXIMIZE_H; 560 *tooltip_id = IDS_XPFRAME_MAXIMIZE_TOOLTIP; 561 } else if (button_name == "unmaximize") { 562 *normal_image_id = IDR_RESTORE; 563 *pressed_image_id = IDR_RESTORE_P; 564 *hover_image_id = IDR_RESTORE_H; 565 *tooltip_id = IDS_XPFRAME_RESTORE_TOOLTIP; 566 } 567 } 568 569 void BrowserTitlebar::UpdateButtonBackground(CustomDrawButton* button) { 570 SkColor color = theme_service_->GetColor( 571 ThemeProperties::COLOR_BUTTON_BACKGROUND); 572 SkBitmap background = theme_service_->GetImageNamed( 573 IDR_THEME_WINDOW_CONTROL_BACKGROUND).AsBitmap(); 574 575 // TODO(erg): For now, we just use a completely black mask and we can get 576 // away with this in the short term because our buttons are rectangles. We 577 // should get Glen to make properly hinted masks that match our min/max/close 578 // buttons (which have some odd alpha blending around the 579 // edges). http://crbug.com/103661 580 SkBitmap mask; 581 mask.setConfig(SkBitmap::kARGB_8888_Config, 582 button->SurfaceWidth(), button->SurfaceHeight(), 0); 583 mask.allocPixels(); 584 mask.eraseColor(SK_ColorBLACK); 585 586 button->SetBackground(color, background, mask); 587 } 588 589 void BrowserTitlebar::UpdateCustomFrame(bool use_custom_frame) { 590 using_custom_frame_ = use_custom_frame; 591 if (!use_custom_frame || 592 (browser_window_->IsMaximized() && unity::IsRunning())) { 593 if (titlebar_left_buttons_vbox_) 594 gtk_widget_hide(titlebar_left_buttons_vbox_); 595 if (titlebar_right_buttons_vbox_) 596 gtk_widget_hide(titlebar_right_buttons_vbox_); 597 } else { 598 if (titlebar_left_buttons_vbox_) 599 gtk_widget_show_all(titlebar_left_buttons_vbox_); 600 if (titlebar_right_buttons_vbox_) 601 gtk_widget_show_all(titlebar_right_buttons_vbox_); 602 } 603 UpdateTitlebarAlignment(); 604 UpdateMaximizeRestoreVisibility(); 605 } 606 607 void BrowserTitlebar::UpdateTitleAndIcon() { 608 if (!app_mode_title_) 609 return; 610 611 // Get the page title and elide it to the available space. 612 string16 title = browser_window_->browser()->GetWindowTitleForCurrentTab(); 613 gtk_label_set_text(GTK_LABEL(app_mode_title_), UTF16ToUTF8(title).c_str()); 614 615 if (browser_window_->browser()->is_app()) { 616 switch (browser_window_->browser()->type()) { 617 case Browser::TYPE_POPUP: { 618 // Update the system app icon. We don't need to update the icon in the 619 // top left of the custom frame, that will get updated when the 620 // throbber is updated. 621 Profile* profile = browser_window_->browser()->profile(); 622 gfx::Image icon = browser_window_->browser()->GetCurrentPageIcon(); 623 if (icon.IsEmpty()) { 624 gtk_util::SetWindowIcon(window_, profile); 625 } else { 626 gtk_util::SetWindowIcon(window_, profile, icon.ToGdkPixbuf()); 627 } 628 break; 629 } 630 case Browser::TYPE_TABBED: { 631 NOTREACHED() << "We should never have a tabbed app window."; 632 break; 633 } 634 } 635 } 636 } 637 638 void BrowserTitlebar::UpdateThrobber(WebContents* web_contents) { 639 DCHECK(app_mode_favicon_); 640 641 if (web_contents && web_contents->IsLoading()) { 642 GdkPixbuf* icon_pixbuf = 643 throbber_.GetNextFrame(web_contents->IsWaitingForResponse()); 644 gtk_image_set_from_pixbuf(GTK_IMAGE(app_mode_favicon_), icon_pixbuf); 645 } else { 646 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 647 648 // Note: we want to exclude the application popup/panel window. 649 if ((browser_window_->browser()->is_app() && 650 !browser_window_->browser()->is_type_tabbed())) { 651 gfx::Image icon = browser_window_->browser()->GetCurrentPageIcon(); 652 if (icon.IsEmpty()) { 653 // Fallback to the Chromium icon if the page has no icon. 654 gtk_image_set_from_pixbuf(GTK_IMAGE(app_mode_favicon_), 655 rb.GetNativeImageNamed(IDR_PRODUCT_LOGO_16).ToGdkPixbuf()); 656 } else { 657 gtk_image_set_from_pixbuf(GTK_IMAGE(app_mode_favicon_), 658 icon.ToGdkPixbuf()); 659 } 660 } else { 661 gtk_image_set_from_pixbuf(GTK_IMAGE(app_mode_favicon_), 662 rb.GetNativeImageNamed(IDR_PRODUCT_LOGO_16).ToGdkPixbuf()); 663 } 664 throbber_.Reset(); 665 } 666 } 667 668 void BrowserTitlebar::UpdateTitlebarAlignment() { 669 if (browser_window_->browser()->is_type_tabbed()) { 670 int top_padding = 0; 671 int side_padding = 0; 672 int vertical_offset = kNormalVerticalOffset; 673 674 if (using_custom_frame_) { 675 if (!browser_window_->IsMaximized()) { 676 top_padding = kTitlebarHeight; 677 } else if (using_custom_frame_ && browser_window_->IsMaximized()) { 678 vertical_offset = 0; 679 if (!unity::IsRunning()) 680 side_padding = kMaximizedTabstripPadding; 681 } 682 } 683 684 int right_padding = 0; 685 int left_padding = kTabStripLeftPadding; 686 if (titlebar_right_buttons_hbox_) 687 right_padding = side_padding; 688 if (titlebar_left_buttons_hbox_) 689 left_padding = side_padding; 690 691 gtk_alignment_set_padding(GTK_ALIGNMENT(titlebar_alignment_), 692 top_padding, 0, 693 left_padding, right_padding); 694 browser_window_->tabstrip()->SetVerticalOffset(vertical_offset); 695 } else { 696 if (using_custom_frame_ && !browser_window_->IsFullscreen()) { 697 gtk_alignment_set_padding(GTK_ALIGNMENT(titlebar_alignment_), 698 kAppModePaddingTop, kAppModePaddingBottom, kAppModePaddingLeft, 0); 699 gtk_widget_show(titlebar_alignment_); 700 } else { 701 gtk_widget_hide(titlebar_alignment_); 702 } 703 } 704 705 // Resize the buttons so that the clickable area extends all the way to the 706 // edge of the browser window. 707 GtkRequisition close_button_req = close_button_req_; 708 GtkRequisition minimize_button_req = minimize_button_req_; 709 GtkRequisition restore_button_req = restore_button_req_; 710 if (using_custom_frame_ && browser_window_->IsMaximized()) { 711 close_button_req.width += kButtonOuterPadding; 712 close_button_req.height += kButtonOuterPadding; 713 minimize_button_req.height += kButtonOuterPadding; 714 restore_button_req.height += kButtonOuterPadding; 715 if (top_padding_left_) 716 gtk_widget_hide(top_padding_left_); 717 if (top_padding_right_) 718 gtk_widget_hide(top_padding_right_); 719 } else { 720 if (top_padding_left_) 721 gtk_widget_show(top_padding_left_); 722 if (top_padding_right_) 723 gtk_widget_show(top_padding_right_); 724 } 725 if (close_button_.get()) { 726 gtk_widget_set_size_request(close_button_->widget(), 727 close_button_req.width, 728 close_button_req.height); 729 } 730 if (minimize_button_.get()) { 731 gtk_widget_set_size_request(minimize_button_->widget(), 732 minimize_button_req.width, 733 minimize_button_req.height); 734 } 735 if (maximize_button_.get()) { 736 gtk_widget_set_size_request(restore_button_->widget(), 737 restore_button_req.width, 738 restore_button_req.height); 739 } 740 } 741 742 void BrowserTitlebar::UpdateTextColor() { 743 if (!app_mode_title_) 744 return; 745 746 if (theme_service_ && theme_service_->UsingNativeTheme()) { 747 // We don't really have any good options here. 748 // 749 // Colors from window manager themes aren't exposed in GTK; the window 750 // manager is a separate component and when there is information sharing 751 // (in the case of metacity), it's one way where the window manager reads 752 // data from the GTK theme (which allows us to do a decent job with 753 // picking the frame color). 754 // 755 // We probably won't match in the majority of cases, but we can at the 756 // very least make things legible. The default metacity and xfwm themes 757 // on ubuntu have white text hardcoded. Determine whether black or white 758 // has more luminosity contrast and then set that color as the text 759 // color. 760 GdkColor frame_color; 761 if (window_has_focus_) { 762 frame_color = theme_service_->GetGdkColor( 763 ThemeProperties::COLOR_FRAME); 764 } else { 765 frame_color = theme_service_->GetGdkColor( 766 ThemeProperties::COLOR_FRAME_INACTIVE); 767 } 768 GdkColor text_color = PickLuminosityContrastingColor( 769 &frame_color, &ui::kGdkWhite, &ui::kGdkBlack); 770 gtk_util::SetLabelColor(app_mode_title_, &text_color); 771 } else { 772 gtk_util::SetLabelColor(app_mode_title_, &ui::kGdkWhite); 773 } 774 } 775 776 void BrowserTitlebar::UpdateAvatarLabel() { 777 if (theme_service_ && avatar_label_) { 778 GdkColor text_color = 779 theme_service_->GetGdkColor(ThemeProperties::COLOR_MANAGED_USER_LABEL); 780 GdkColor label_background = theme_service_->GetGdkColor( 781 ThemeProperties::COLOR_MANAGED_USER_LABEL_BACKGROUND); 782 gtk_util::SetLabelColor(avatar_label_, &text_color); 783 gtk_widget_modify_bg( 784 GTK_WIDGET(avatar_label_bg_), GTK_STATE_NORMAL, &label_background); 785 char* markup = g_markup_printf_escaped( 786 "<span size='small'>%s</span>", 787 l10n_util::GetStringUTF8(IDS_MANAGED_USER_AVATAR_LABEL).c_str()); 788 gtk_label_set_markup(GTK_LABEL(avatar_label_), markup); 789 g_free(markup); 790 } 791 } 792 793 void BrowserTitlebar::UpdateAvatar() { 794 // Remove previous state. 795 gtk_util::RemoveAllChildren(titlebar_left_avatar_frame_); 796 gtk_util::RemoveAllChildren(titlebar_right_avatar_frame_); 797 gtk_util::RemoveAllChildren(titlebar_left_label_frame_); 798 gtk_util::RemoveAllChildren(titlebar_right_label_frame_); 799 800 if (!ShouldDisplayAvatar()) 801 return; 802 803 if (!avatar_) { 804 if (IsOffTheRecord()) { 805 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 806 gfx::Image avatar_image = 807 rb.GetNativeImageNamed(IDR_OTR_ICON, ui::ResourceBundle::RTL_ENABLED); 808 avatar_ = gtk_image_new_from_pixbuf(avatar_image.ToGdkPixbuf()); 809 gtk_misc_set_alignment(GTK_MISC(avatar_), 0.0, 1.0); 810 gtk_widget_set_size_request(avatar_, -1, 0); 811 } else { 812 // Use a clickable avatar. 813 avatar_ = avatar_button_->widget(); 814 } 815 } 816 817 gtk_widget_show_all(avatar_); 818 819 Profile* profile = browser_window_->browser()->profile(); 820 if (profile->IsManaged()) { 821 avatar_label_ = gtk_label_new(NULL); 822 gtk_misc_set_padding(GTK_MISC(avatar_label_), 10, 2); 823 avatar_label_bg_ = gtk_event_box_new(); 824 gtk_container_add(GTK_CONTAINER(avatar_label_bg_), avatar_label_); 825 g_signal_connect(avatar_label_bg_, "button-press-event", 826 G_CALLBACK(OnAvatarLabelButtonPressedThunk), this); 827 UpdateAvatarLabel(); 828 gtk_widget_show(avatar_label_bg_); 829 gtk_widget_show(avatar_label_); 830 if (display_avatar_on_left_) { 831 gtk_container_add(GTK_CONTAINER(titlebar_left_label_frame_), 832 avatar_label_bg_); 833 gtk_widget_show(titlebar_left_label_frame_); 834 gtk_widget_hide(titlebar_right_label_frame_); 835 } else { 836 gtk_container_add(GTK_CONTAINER(titlebar_right_label_frame_), 837 avatar_label_bg_); 838 gtk_widget_show(titlebar_right_label_frame_); 839 gtk_widget_hide(titlebar_left_label_frame_); 840 } 841 } 842 843 if (display_avatar_on_left_) { 844 gtk_container_add(GTK_CONTAINER(titlebar_left_avatar_frame_), avatar_); 845 gtk_widget_show(titlebar_left_avatar_frame_); 846 gtk_widget_hide(titlebar_right_avatar_frame_); 847 } else { 848 gtk_container_add(GTK_CONTAINER(titlebar_right_avatar_frame_), avatar_); 849 gtk_widget_show(titlebar_right_avatar_frame_); 850 gtk_widget_hide(titlebar_left_avatar_frame_); 851 } 852 853 if (IsOffTheRecord()) 854 return; 855 856 bool is_gaia_picture = false; 857 gfx::Image avatar; 858 ProfileInfoCache& cache = 859 g_browser_process->profile_manager()->GetProfileInfoCache(); 860 size_t index = cache.GetIndexOfProfileWithPath(profile->GetPath()); 861 if (index == std::string::npos) 862 return; 863 864 is_gaia_picture = 865 cache.IsUsingGAIAPictureOfProfileAtIndex(index) && 866 cache.GetGAIAPictureOfProfileAtIndex(index); 867 avatar = cache.GetAvatarIconOfProfileAtIndex(index); 868 avatar_button_->SetIcon(avatar, is_gaia_picture); 869 avatar_button_->set_menu_frame_style(display_avatar_on_left_ ? 870 BubbleGtk::ANCHOR_TOP_LEFT : BubbleGtk::ANCHOR_TOP_RIGHT); 871 } 872 873 void BrowserTitlebar::MaximizeButtonClicked() { 874 GdkEvent* event = gtk_get_current_event(); 875 if (event->button.button == 1) { 876 gtk_window_maximize(window_); 877 } else { 878 GtkWidget* widget = GTK_WIDGET(window_); 879 GdkScreen* screen = gtk_widget_get_screen(widget); 880 gint monitor = gdk_screen_get_monitor_at_window( 881 screen, gtk_widget_get_window(widget)); 882 GdkRectangle screen_rect; 883 gdk_screen_get_monitor_geometry(screen, monitor, &screen_rect); 884 885 gint x, y; 886 gtk_window_get_position(window_, &x, &y); 887 888 GtkAllocation allocation; 889 gtk_widget_get_allocation(widget, &allocation); 890 gint width = allocation.width; 891 gint height = allocation.height; 892 893 if (event->button.button == 3) { 894 x = 0; 895 width = screen_rect.width; 896 } else if (event->button.button == 2) { 897 y = 0; 898 height = screen_rect.height; 899 } 900 901 browser_window_->SetBounds(gfx::Rect(x, y, width, height)); 902 } 903 gdk_event_free(event); 904 } 905 906 void BrowserTitlebar::UpdateMaximizeRestoreVisibility() { 907 if (maximize_button_.get()) { 908 if (browser_window_->IsMaximized()) { 909 gtk_widget_hide(maximize_button_->widget()); 910 gtk_widget_show(restore_button_->widget()); 911 } else { 912 gtk_widget_hide(restore_button_->widget()); 913 gtk_widget_show(maximize_button_->widget()); 914 } 915 } 916 } 917 918 gboolean BrowserTitlebar::OnWindowStateChanged(GtkWindow* window, 919 GdkEventWindowState* event) { 920 UpdateMaximizeRestoreVisibility(); 921 UpdateTitlebarAlignment(); 922 UpdateTextColor(); 923 return FALSE; 924 } 925 926 gboolean BrowserTitlebar::OnScroll(GtkWidget* widget, GdkEventScroll* event) { 927 Browser* browser = browser_window_->browser(); 928 int index = browser->tab_strip_model()->active_index(); 929 if (event->direction == GDK_SCROLL_LEFT || 930 event->direction == GDK_SCROLL_UP) { 931 if (index != 0) 932 chrome::SelectPreviousTab(browser); 933 } else if (index + 1 < browser->tab_strip_model()->count()) { 934 chrome::SelectNextTab(browser); 935 } 936 return TRUE; 937 } 938 939 void BrowserTitlebar::OnButtonClicked(GtkWidget* button) { 940 if (close_button_.get() && close_button_->widget() == button) { 941 browser_window_->Close(); 942 } else if (restore_button_.get() && restore_button_->widget() == button) { 943 browser_window_->UnMaximize(); 944 } else if (maximize_button_.get() && maximize_button_->widget() == button) { 945 MaximizeButtonClicked(); 946 } else if (minimize_button_.get() && minimize_button_->widget() == button) { 947 gtk_window_iconify(window_); 948 } 949 } 950 951 gboolean BrowserTitlebar::OnFaviconMenuButtonPressed(GtkWidget* widget, 952 GdkEventButton* event) { 953 if (event->button != 1) 954 return FALSE; 955 956 if (!favicon_menu_model_.get()) { 957 favicon_menu_model_.reset( 958 new PopupPageMenuModel(this, browser_window_->browser())); 959 960 favicon_menu_.reset(new MenuGtk(NULL, favicon_menu_model_.get())); 961 } 962 963 favicon_menu_->PopupForWidget(app_mode_favicon_, event->button, event->time); 964 965 return TRUE; 966 } 967 968 gboolean BrowserTitlebar::OnAvatarLabelButtonPressed(GtkWidget* widget, 969 GdkEventButton* event) { 970 if (event->button != 1) 971 return FALSE; 972 973 // Show the avatar menu bubble with the upward arrow at the x position where 974 // the user has clicked. 975 gfx::Rect rect = gtk_util::WidgetBounds(widget); 976 rect.set_x(event->x); 977 rect.set_width(0); 978 new AvatarMenuBubbleGtk( 979 browser_window_->browser(), widget, BubbleGtk::ANCHOR_TOP_RIGHT, &rect); 980 return TRUE; 981 } 982 983 void BrowserTitlebar::ShowContextMenu(GdkEventButton* event) { 984 if (!context_menu_.get()) { 985 context_menu_model_.reset(new ContextMenuModel(this)); 986 context_menu_.reset(new MenuGtk(NULL, context_menu_model_.get())); 987 } 988 989 context_menu_->PopupAsContext(gfx::Point(event->x_root, event->y_root), 990 event->time); 991 } 992 993 bool BrowserTitlebar::IsCommandIdEnabled(int command_id) const { 994 if (command_id == kShowWindowDecorationsCommand) 995 return true; 996 997 return chrome::IsCommandEnabled(browser_window_->browser(), command_id); 998 } 999 1000 bool BrowserTitlebar::IsCommandIdChecked(int command_id) const { 1001 if (command_id == kShowWindowDecorationsCommand) { 1002 PrefService* prefs = browser_window_->browser()->profile()->GetPrefs(); 1003 return !prefs->GetBoolean(prefs::kUseCustomChromeFrame); 1004 } 1005 1006 EncodingMenuController controller; 1007 if (controller.DoesCommandBelongToEncodingMenu(command_id)) { 1008 WebContents* web_contents = 1009 browser_window_->browser()->tab_strip_model()->GetActiveWebContents(); 1010 if (web_contents) { 1011 return controller.IsItemChecked(browser_window_->browser()->profile(), 1012 web_contents->GetEncoding(), 1013 command_id); 1014 } 1015 return false; 1016 } 1017 1018 NOTREACHED(); 1019 return false; 1020 } 1021 1022 void BrowserTitlebar::ExecuteCommand(int command_id, int event_flags) { 1023 if (command_id == kShowWindowDecorationsCommand) { 1024 PrefService* prefs = browser_window_->browser()->profile()->GetPrefs(); 1025 prefs->SetBoolean(prefs::kUseCustomChromeFrame, 1026 !prefs->GetBoolean(prefs::kUseCustomChromeFrame)); 1027 return; 1028 } 1029 1030 chrome::ExecuteCommand(browser_window_->browser(), command_id); 1031 } 1032 1033 bool BrowserTitlebar::GetAcceleratorForCommandId( 1034 int command_id, 1035 ui::Accelerator* out_accelerator) { 1036 const ui::Accelerator* accelerator = 1037 AcceleratorsGtk::GetInstance()->GetPrimaryAcceleratorForCommand( 1038 command_id); 1039 if (!accelerator) 1040 return false; 1041 *out_accelerator = *accelerator; 1042 return true; 1043 } 1044 1045 void BrowserTitlebar::Observe(int type, 1046 const content::NotificationSource& source, 1047 const content::NotificationDetails& details) { 1048 switch (type) { 1049 case chrome::NOTIFICATION_BROWSER_THEME_CHANGED: { 1050 UpdateTextColor(); 1051 UpdateAvatarLabel(); 1052 1053 if (minimize_button_.get()) 1054 UpdateButtonBackground(minimize_button_.get()); 1055 if (maximize_button_.get()) 1056 UpdateButtonBackground(maximize_button_.get()); 1057 if (restore_button_.get()) 1058 UpdateButtonBackground(restore_button_.get()); 1059 if (close_button_.get()) 1060 UpdateButtonBackground(close_button_.get()); 1061 break; 1062 } 1063 1064 case chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED: 1065 if (!IsOffTheRecord()) 1066 UpdateAvatar(); 1067 break; 1068 1069 default: 1070 NOTREACHED(); 1071 } 1072 } 1073 1074 void BrowserTitlebar::ActiveWindowChanged(GdkWindow* active_window) { 1075 // Can be called during shutdown; BrowserWindowGtk will set our |window_| 1076 // to NULL during that time. 1077 if (!window_) 1078 return; 1079 1080 window_has_focus_ = 1081 gtk_widget_get_window(GTK_WIDGET(window_)) == active_window; 1082 UpdateTextColor(); 1083 } 1084 1085 bool BrowserTitlebar::ShouldDisplayAvatar() { 1086 if (IsOffTheRecord()) 1087 return true; 1088 1089 if (!browser_window_->browser()->is_type_tabbed()) 1090 return false; 1091 1092 return AvatarMenuModel::ShouldShowAvatarMenu(); 1093 } 1094 1095 bool BrowserTitlebar::IsOffTheRecord() { 1096 return browser_window_->browser()->profile()->IsOffTheRecord(); 1097 } 1098 1099 BrowserTitlebar::ContextMenuModel::ContextMenuModel( 1100 ui::SimpleMenuModel::Delegate* delegate) 1101 : SimpleMenuModel(delegate) { 1102 AddItemWithStringId(IDC_NEW_TAB, IDS_TAB_CXMENU_NEWTAB); 1103 AddItemWithStringId(IDC_RESTORE_TAB, IDS_RESTORE_TAB); 1104 AddSeparator(ui::NORMAL_SEPARATOR); 1105 AddItemWithStringId(IDC_TASK_MANAGER, IDS_TASK_MANAGER); 1106 AddSeparator(ui::NORMAL_SEPARATOR); 1107 AddCheckItemWithStringId(kShowWindowDecorationsCommand, 1108 IDS_SHOW_WINDOW_DECORATIONS_MENU); 1109 } 1110