root/chrome/browser/ui/gtk/browser_titlebar.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. OnMouseMoveEvent
  2. GdkColorToHSL
  3. PickLuminosityContrastingColor
  4. browser_
  5. Build
  6. theme_service_
  7. Init
  8. BuildButtons
  9. BuildButton
  10. GetButtonHBox
  11. CreateTitlebarButton
  12. GetButtonResources
  13. UpdateButtonBackground
  14. UpdateCustomFrame
  15. UpdateTitleAndIcon
  16. UpdateThrobber
  17. UpdateTitlebarAlignment
  18. UpdateTextColor
  19. UpdateAvatarLabel
  20. UpdateAvatar
  21. MaximizeButtonClicked
  22. UpdateMaximizeRestoreVisibility
  23. OnWindowStateChanged
  24. OnScroll
  25. OnButtonClicked
  26. OnFaviconMenuButtonPressed
  27. OnAvatarLabelButtonPressed
  28. ShowContextMenu
  29. IsCommandIdEnabled
  30. IsCommandIdChecked
  31. ExecuteCommand
  32. GetAcceleratorForCommandId
  33. Observe
  34. ActiveWindowChanged
  35. ShouldDisplayAvatar
  36. IsOffTheRecord

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ui/gtk/browser_titlebar.h"

#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>

#include <string>
#include <vector>

#include "base/command_line.h"
#include "base/i18n/rtl.h"
#include "base/memory/singleton.h"
#include "base/prefs/pref_service.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_tokenizer.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/profiles/avatar_menu.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_info_cache.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/themes/theme_properties.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/gtk/accelerators_gtk.h"
#include "chrome/browser/ui/gtk/avatar_menu_bubble_gtk.h"
#include "chrome/browser/ui/gtk/avatar_menu_button_gtk.h"
#include "chrome/browser/ui/gtk/browser_window_gtk.h"
#include "chrome/browser/ui/gtk/custom_button.h"
#if defined(USE_GCONF)
#include "chrome/browser/ui/gtk/gconf_titlebar_listener.h"
#endif
#include "chrome/browser/ui/gtk/gtk_theme_service.h"
#include "chrome/browser/ui/gtk/gtk_util.h"
#include "chrome/browser/ui/gtk/menu_gtk.h"
#include "chrome/browser/ui/gtk/nine_box.h"
#include "chrome/browser/ui/gtk/tabs/tab_strip_gtk.h"
#include "chrome/browser/ui/gtk/unity_service.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/toolbar/encoding_menu_controller.h"
#include "chrome/browser/ui/toolbar/wrench_menu_model.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/web_contents.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.h"
#include "grit/ui_resources.h"
#include "ui/base/gtk/gtk_hig_constants.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/x/active_window_watcher_x.h"
#include "ui/gfx/image/image.h"

using content::WebContents;

namespace {

// The space above the titlebars.
const int kTitlebarHeight = 14;

// The thickness in pixels of the tab border.
const int kTabOuterThickness = 1;

// Amount to offset the tab images relative to the background.
const int kNormalVerticalOffset = kTitlebarHeight + kTabOuterThickness;

// A linux specific menu item for toggling window decorations.
const int kShowWindowDecorationsCommand = 200;

const int kAvatarBottomSpacing = 1;
// There are 2 px on each side of the avatar (between the frame border and
// it on the left, and between it and the tabstrip on the right).
const int kAvatarSideSpacing = 2;

// There is a 4px gap between the icon and the title text.
const int kIconTitleSpacing = 4;

// Padding around the icon when in app mode or popup mode.
const int kAppModePaddingTop = 5;
const int kAppModePaddingBottom = 4;
const int kAppModePaddingLeft = 2;

// The left padding of the tab strip.  In Views, the tab strip has a left
// margin of FrameBorderThickness + kClientEdgeThickness.  This offset is to
// account for kClientEdgeThickness.
const int kTabStripLeftPadding = 1;

// Spacing between buttons of the titlebar.
const int kButtonSpacing = 2;

// Spacing around outside of titlebar buttons.
const int kButtonOuterPadding = 2;

// Spacing between tabstrip and window control buttons (when the window is
// maximized).
const int kMaximizedTabstripPadding = 16;

gboolean OnMouseMoveEvent(GtkWidget* widget, GdkEventMotion* event,
                          BrowserWindowGtk* browser_window) {
  // Reset to the default mouse cursor.
  browser_window->ResetCustomFrameCursor();
  return TRUE;
}

// Converts a GdkColor to a color_utils::HSL.
color_utils::HSL GdkColorToHSL(const GdkColor* color) {
  color_utils::HSL hsl;
  color_utils::SkColorToHSL(SkColorSetRGB(color->red >> 8,
                                          color->green >> 8,
                                          color->blue >> 8), &hsl);
  return hsl;
}

// Returns either |one| or |two| based on which has a greater difference in
// luminosity.
GdkColor PickLuminosityContrastingColor(const GdkColor* base,
                                        const GdkColor* one,
                                        const GdkColor* two) {
  // Convert all GdkColors to color_utils::HSLs.
  color_utils::HSL baseHSL = GdkColorToHSL(base);
  color_utils::HSL oneHSL = GdkColorToHSL(one);
  color_utils::HSL twoHSL = GdkColorToHSL(two);
  double one_difference = fabs(baseHSL.l - oneHSL.l);
  double two_difference = fabs(baseHSL.l - twoHSL.l);

  // Be biased towards the first color presented.
  if (two_difference > one_difference + 0.1)
    return *two;
  else
    return *one;
}

}  // namespace

////////////////////////////////////////////////////////////////////////////////
// PopupPageMenuModel

// A menu model that builds the contents of the menu shown for popups (when the
// user clicks on the favicon) and all of its submenus.
class PopupPageMenuModel : public ui::SimpleMenuModel {
 public:
  PopupPageMenuModel(ui::SimpleMenuModel::Delegate* delegate, Browser* browser);
  virtual ~PopupPageMenuModel() { }

 private:
  void Build();

  // Models for submenus referenced by this model. SimpleMenuModel only uses
  // weak references so these must be kept for the lifetime of the top-level
  // model.
  scoped_ptr<ZoomMenuModel> zoom_menu_model_;
  scoped_ptr<EncodingMenuModel> encoding_menu_model_;
  Browser* browser_;  // weak

  DISALLOW_COPY_AND_ASSIGN(PopupPageMenuModel);
};

PopupPageMenuModel::PopupPageMenuModel(
    ui::SimpleMenuModel::Delegate* delegate,
    Browser* browser)
    : ui::SimpleMenuModel(delegate), browser_(browser) {
  Build();
}

void PopupPageMenuModel::Build() {
  AddItemWithStringId(IDC_BACK, IDS_CONTENT_CONTEXT_BACK);
  AddItemWithStringId(IDC_FORWARD, IDS_CONTENT_CONTEXT_FORWARD);
  AddItemWithStringId(IDC_RELOAD, IDS_APP_MENU_RELOAD);
  AddSeparator(ui::NORMAL_SEPARATOR);
  AddItemWithStringId(IDC_SHOW_AS_TAB, IDS_SHOW_AS_TAB);
  AddSeparator(ui::NORMAL_SEPARATOR);
  AddItemWithStringId(IDC_CUT, IDS_CUT);
  AddItemWithStringId(IDC_COPY, IDS_COPY);
  AddItemWithStringId(IDC_PASTE, IDS_PASTE);
  AddSeparator(ui::NORMAL_SEPARATOR);
  AddItemWithStringId(IDC_FIND, IDS_FIND);
  AddItemWithStringId(IDC_PRINT, IDS_PRINT);
  zoom_menu_model_.reset(new ZoomMenuModel(delegate()));
  AddSubMenuWithStringId(IDC_ZOOM_MENU, IDS_ZOOM_MENU, zoom_menu_model_.get());

  encoding_menu_model_.reset(new EncodingMenuModel(browser_));
  AddSubMenuWithStringId(IDC_ENCODING_MENU, IDS_ENCODING_MENU,
                         encoding_menu_model_.get());

  AddSeparator(ui::NORMAL_SEPARATOR);
  AddItemWithStringId(IDC_CLOSE_WINDOW, IDS_CLOSE);
}

////////////////////////////////////////////////////////////////////////////////
// BrowserTitlebar

// static
const char BrowserTitlebar::kDefaultButtonString[] = ":minimize,maximize,close";

BrowserTitlebar::BrowserTitlebar(BrowserWindowGtk* browser_window,
                                 GtkWindow* window)
    : browser_window_(browser_window),
      window_(window),
      container_(NULL),
      container_hbox_(NULL),
      titlebar_left_buttons_vbox_(NULL),
      titlebar_right_buttons_vbox_(NULL),
      titlebar_left_buttons_hbox_(NULL),
      titlebar_right_buttons_hbox_(NULL),
      titlebar_left_avatar_frame_(NULL),
      titlebar_right_avatar_frame_(NULL),
      titlebar_left_label_frame_(NULL),
      titlebar_right_label_frame_(NULL),
      avatar_(NULL),
      avatar_label_(NULL),
      avatar_label_bg_(NULL),
      top_padding_left_(NULL),
      top_padding_right_(NULL),
      titlebar_alignment_(NULL),
      app_mode_favicon_(NULL),
      app_mode_title_(NULL),
      using_custom_frame_(false),
      window_has_focus_(false),
      display_avatar_on_left_(false),
      theme_service_(NULL) {
}

void BrowserTitlebar::Init() {
  // The widget hierarchy is shown below.
  //
  // +- EventBox (container_) ------------------------------------------------+
  // +- HBox (container_hbox_) -----------------------------------------------+
  // |+ VBox ---++- Algn. -++- Alignment --------------++- Algn. -++ VBox ---+|
  // || titlebar||titlebar ||   (titlebar_alignment_)  ||titlebar || titlebar||
  // || left    ||left     ||                          ||right    || right   ||
  // || button  ||spy      ||                          ||spy      || button  ||
  // || vbox    ||frame    ||+- TabStripGtk  ---------+||frame    || vbox    ||
  // ||         ||         || tab   tab   tabclose    |||         ||         ||
  // ||         ||         ||+------------------------+||         ||         ||
  // |+---------++---------++--------------------------++---------++---------+|
  // +------------------------------------------------------------------------+
  //
  // There are two vboxes on either side of |container_hbox_| because when the
  // desktop is GNOME, the button placement is configurable based on a metacity
  // gconf key. We can't just have one vbox and move it back and forth because
  // the gconf language allows you to place buttons on both sides of the
  // window.  This being Linux, I'm sure there's a bunch of people who have
  // already configured their window manager to do so and we don't want to get
  // confused when that happens. The actual contents of these vboxes are lazily
  // generated so they don't interfere with alignment when everything is
  // stuffed in the other box.
  //
  // Each vbox has the following hierarchy if it contains buttons:
  //
  // +- VBox (titlebar_{l,r}_buttons_vbox_) ---------+
  // |+- Fixed (top_padding_{l,r}_) ----------------+|
  // ||+- HBox (titlebar_{l,r}_buttons_hbox_ ------+||
  // ||| (buttons of a configurable layout)        |||
  // ||+-------------------------------------------+||
  // |+---------------------------------------------+|
  // +-----------------------------------------------+
  //
  // The two spy alignments are only allocated if this window is an incognito
  // window. Only one of them holds the spy image.
  //
  // If we're a popup window or in app mode, we don't display the spy guy or
  // the tab strip.  Instead, put an hbox in titlebar_alignment_ in place of
  // the tab strip.
  // +- Alignment (titlebar_alignment_) -----------------------------------+
  // |+ HBox -------------------------------------------------------------+|
  // ||+- TabStripGtk -++- Image ----------------++- Label --------------+||
  // ||| hidden        ++    (app_mode_favicon_) ||    (app_mode_title_) |||
  // |||               ||  favicon               ||  page title          |||
  // ||+---------------++------------------------++----------------------+||
  // |+-------------------------------------------------------------------+|
  // +---------------------------------------------------------------------+
  container_hbox_ = gtk_hbox_new(FALSE, 0);

  container_ = gtk_event_box_new();
  gtk_widget_set_name(container_, "chrome-browser-titlebar");
  gtk_event_box_set_visible_window(GTK_EVENT_BOX(container_), FALSE);
  gtk_container_add(GTK_CONTAINER(container_), container_hbox_);

  g_signal_connect(container_, "scroll-event", G_CALLBACK(OnScrollThunk), this);

  g_signal_connect(window_, "window-state-event",
                   G_CALLBACK(OnWindowStateChangedThunk), this);

  // Allocate the two button boxes on the left and right parts of the bar. These
  // are always allocated, but only displayed in incognito mode or when using
  // multiple profiles.
  titlebar_left_buttons_vbox_ = gtk_vbox_new(FALSE, 0);
  gtk_box_pack_start(GTK_BOX(container_hbox_), titlebar_left_buttons_vbox_,
                     FALSE, FALSE, 0);
  titlebar_left_avatar_frame_ = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
  gtk_widget_set_no_show_all(titlebar_left_avatar_frame_, TRUE);
  gtk_alignment_set_padding(GTK_ALIGNMENT(titlebar_left_avatar_frame_), 0,
      kAvatarBottomSpacing, kAvatarSideSpacing, kAvatarSideSpacing);
  gtk_box_pack_start(GTK_BOX(container_hbox_), titlebar_left_avatar_frame_,
                     FALSE, FALSE, 0);

  titlebar_left_label_frame_ = gtk_alignment_new(0.0, 0.5, 0.0, 0.0);
  gtk_widget_set_no_show_all(titlebar_left_label_frame_, TRUE);
  gtk_alignment_set_padding(GTK_ALIGNMENT(titlebar_left_label_frame_), 0,
      kAvatarBottomSpacing, kAvatarSideSpacing, kAvatarSideSpacing);
  gtk_box_pack_start(GTK_BOX(container_hbox_), titlebar_left_label_frame_,
                   FALSE, FALSE, 0);

  titlebar_right_avatar_frame_ = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
  gtk_widget_set_no_show_all(titlebar_right_avatar_frame_, TRUE);
  gtk_alignment_set_padding(GTK_ALIGNMENT(titlebar_right_avatar_frame_), 0,
      kAvatarBottomSpacing, kAvatarSideSpacing, kAvatarSideSpacing);
  gtk_box_pack_end(GTK_BOX(container_hbox_), titlebar_right_avatar_frame_,
                   FALSE, FALSE, 0);

  titlebar_right_label_frame_ = gtk_alignment_new(0.0, 0.5, 0.0, 0.0);
  gtk_widget_set_no_show_all(titlebar_right_label_frame_, TRUE);
  gtk_alignment_set_padding(GTK_ALIGNMENT(titlebar_right_label_frame_), 0,
      kAvatarBottomSpacing, kAvatarSideSpacing, kAvatarSideSpacing);
  gtk_box_pack_end(GTK_BOX(container_hbox_), titlebar_right_label_frame_,
                   FALSE, FALSE, 0);

  titlebar_right_buttons_vbox_ = gtk_vbox_new(FALSE, 0);
  gtk_box_pack_end(GTK_BOX(container_hbox_), titlebar_right_buttons_vbox_,
                   FALSE, FALSE, 0);

  // Create the Avatar button and listen for notifications. It must always be
  // created because a new profile can be added at any time.
  avatar_button_.reset(new AvatarMenuButtonGtk(browser_window_->browser()));

  registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED,
                 content::NotificationService::AllSources());

#if defined(USE_GCONF)
  // Either read the gconf database and register for updates (on GNOME), or use
  // the default value (anywhere else).
  GConfTitlebarListener::GetInstance()->SetTitlebarButtons(this);
#else
  BuildButtons(kDefaultButtonString);
#endif

  UpdateAvatar();

  // We use an alignment to control the titlebar height.
  titlebar_alignment_ = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
  if (browser_window_->browser()->is_type_tabbed()) {
    gtk_box_pack_start(GTK_BOX(container_hbox_), titlebar_alignment_, TRUE,
                       TRUE, 0);

    // Put the tab strip in the titlebar.
    gtk_container_add(GTK_CONTAINER(titlebar_alignment_),
                      browser_window_->tabstrip()->widget());
  } else {
    // App mode specific widgets.
    gtk_box_pack_start(GTK_BOX(container_hbox_), titlebar_alignment_, TRUE,
                       TRUE, 0);
    GtkWidget* app_mode_hbox = gtk_hbox_new(FALSE, kIconTitleSpacing);
    gtk_container_add(GTK_CONTAINER(titlebar_alignment_), app_mode_hbox);

    // Put the tab strip in the hbox even though we don't show it.  Sometimes
    // we need the position of the tab strip so make sure it's in our widget
    // hierarchy.
    gtk_box_pack_start(GTK_BOX(app_mode_hbox),
        browser_window_->tabstrip()->widget(), FALSE, FALSE, 0);

    GtkWidget* favicon_event_box = gtk_event_box_new();
    gtk_event_box_set_visible_window(GTK_EVENT_BOX(favicon_event_box), FALSE);
    g_signal_connect(favicon_event_box, "button-press-event",
                     G_CALLBACK(OnFaviconMenuButtonPressedThunk), this);
    gtk_box_pack_start(GTK_BOX(app_mode_hbox), favicon_event_box, FALSE,
                       FALSE, 0);
    // We use the app logo as a placeholder image so the title doesn't jump
    // around.
    ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
    app_mode_favicon_ = gtk_image_new_from_pixbuf(rb.GetNativeImageNamed(
        IDR_PRODUCT_LOGO_16, ui::ResourceBundle::RTL_ENABLED).ToGdkPixbuf());
    g_object_set_data(G_OBJECT(app_mode_favicon_), "left-align-popup",
                      reinterpret_cast<void*>(true));
    gtk_container_add(GTK_CONTAINER(favicon_event_box), app_mode_favicon_);

    app_mode_title_ = gtk_label_new(NULL);
    gtk_label_set_ellipsize(GTK_LABEL(app_mode_title_), PANGO_ELLIPSIZE_END);
    gtk_misc_set_alignment(GTK_MISC(app_mode_title_), 0.0, 0.5);
    gtk_box_pack_start(GTK_BOX(app_mode_hbox), app_mode_title_, TRUE, TRUE,
                       0);

    UpdateTitleAndIcon();
  }

  theme_service_ = GtkThemeService::GetFrom(
      browser_window_->browser()->profile());
  registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
                 content::Source<ThemeService>(theme_service_));
  theme_service_->InitThemesFor(this);

  gtk_widget_show_all(container_);

  ui::ActiveWindowWatcherX::AddObserver(this);
}

BrowserTitlebar::~BrowserTitlebar() {
  ui::ActiveWindowWatcherX::RemoveObserver(this);
#if defined(USE_GCONF)
  GConfTitlebarListener::GetInstance()->RemoveObserver(this);
#endif
}

void BrowserTitlebar::BuildButtons(const std::string& button_string) {
  // Clear out all previous data.
  close_button_.reset();
  restore_button_.reset();
  maximize_button_.reset();
  minimize_button_.reset();
  gtk_util::RemoveAllChildren(titlebar_left_buttons_vbox_);
  gtk_util::RemoveAllChildren(titlebar_right_buttons_vbox_);
  titlebar_left_buttons_hbox_ = NULL;
  titlebar_right_buttons_hbox_ = NULL;
  top_padding_left_ = NULL;
  top_padding_right_ = NULL;

  bool left_side = true;
  base::StringTokenizer tokenizer(button_string, ":,");
  tokenizer.set_options(base::StringTokenizer::RETURN_DELIMS);
  int left_count = 0;
  int right_count = 0;
  while (tokenizer.GetNext()) {
    if (tokenizer.token_is_delim()) {
      if (*tokenizer.token_begin() == ':')
        left_side = false;
    } else {
      base::StringPiece token = tokenizer.token_piece();
      if (BuildButton(token.as_string(), left_side))
        (left_side ? left_count : right_count)++;
    }
  }

  // If we are in incognito mode, add the spy guy to either the end of the left
  // or the beginning of the right depending on which side has fewer buttons.
  display_avatar_on_left_ = right_count > left_count;

  // Now show the correct widgets in the two hierarchies.
  if (using_custom_frame_) {
    gtk_widget_show_all(titlebar_left_buttons_vbox_);
    gtk_widget_show_all(titlebar_right_buttons_vbox_);
  }
  UpdateMaximizeRestoreVisibility();
}

bool BrowserTitlebar::BuildButton(const std::string& button_token,
                                  bool left_side) {
  if (button_token == "minimize") {
    minimize_button_.reset(CreateTitlebarButton("minimize", left_side));

    gtk_widget_size_request(minimize_button_->widget(),
                            &minimize_button_req_);
    return true;
  } else if (button_token == "maximize") {
    restore_button_.reset(CreateTitlebarButton("unmaximize", left_side));
    maximize_button_.reset(CreateTitlebarButton("maximize", left_side));

    gtk_util::SetButtonClickableByMouseButtons(maximize_button_->widget(),
                                               true, true, true);
    gtk_widget_size_request(restore_button_->widget(),
                            &restore_button_req_);
    return true;
  } else if (button_token == "close") {
    close_button_.reset(CreateTitlebarButton("close", left_side));
    close_button_->set_flipped(left_side);

    gtk_widget_size_request(close_button_->widget(), &close_button_req_);
    return true;
  }
  // Ignore any other values like "pin" since we don't have images for
  // those.
  return false;
}

GtkWidget* BrowserTitlebar::GetButtonHBox(bool left_side) {
  if (left_side && titlebar_left_buttons_hbox_)
    return titlebar_left_buttons_hbox_;
  else if (!left_side && titlebar_right_buttons_hbox_)
    return titlebar_right_buttons_hbox_;

  // We put the min/max/restore/close buttons in a vbox so they are top aligned
  // (up to padding) and don't vertically stretch.
  GtkWidget* vbox = left_side ? titlebar_left_buttons_vbox_ :
                    titlebar_right_buttons_vbox_;

  GtkWidget* top_padding = gtk_fixed_new();
  gtk_widget_set_size_request(top_padding, -1, kButtonOuterPadding);
  gtk_box_pack_start(GTK_BOX(vbox), top_padding, FALSE, FALSE, 0);

  GtkWidget* buttons_hbox = gtk_hbox_new(FALSE, kButtonSpacing);
  gtk_box_pack_start(GTK_BOX(vbox), buttons_hbox, FALSE, FALSE, 0);

  if (left_side) {
    titlebar_left_buttons_hbox_ = buttons_hbox;
    top_padding_left_ = top_padding;
  } else {
    titlebar_right_buttons_hbox_ = buttons_hbox;
    top_padding_right_ = top_padding;
  }

  return buttons_hbox;
}

CustomDrawButton* BrowserTitlebar::CreateTitlebarButton(
    const std::string& button_name, bool left_side) {
  int normal_image_id;
  int pressed_image_id;
  int hover_image_id;
  int tooltip_id;
  GetButtonResources(button_name, &normal_image_id, &pressed_image_id,
                     &hover_image_id, &tooltip_id);

  CustomDrawButton* button = new CustomDrawButton(normal_image_id,
                                                  pressed_image_id,
                                                  hover_image_id,
                                                  0);
  gtk_widget_add_events(GTK_WIDGET(button->widget()), GDK_POINTER_MOTION_MASK);
  g_signal_connect(button->widget(), "clicked",
                   G_CALLBACK(OnButtonClickedThunk), this);
  g_signal_connect(button->widget(), "motion-notify-event",
                   G_CALLBACK(OnMouseMoveEvent), browser_window_);

  std::string localized_tooltip = l10n_util::GetStringUTF8(tooltip_id);
  gtk_widget_set_tooltip_text(button->widget(),
                              localized_tooltip.c_str());

  GtkWidget* box = GetButtonHBox(left_side);
  gtk_box_pack_start(GTK_BOX(box), button->widget(), FALSE, FALSE, 0);
  return button;
}

void BrowserTitlebar::GetButtonResources(const std::string& button_name,
                                         int* normal_image_id,
                                         int* pressed_image_id,
                                         int* hover_image_id,
                                         int* tooltip_id) const {
  if (button_name == "close") {
    *normal_image_id = IDR_CLOSE;
    *pressed_image_id = IDR_CLOSE_P;
    *hover_image_id = IDR_CLOSE_H;
    *tooltip_id = IDS_XPFRAME_CLOSE_TOOLTIP;
  } else if (button_name == "minimize") {
    *normal_image_id = IDR_MINIMIZE;
    *pressed_image_id = IDR_MINIMIZE_P;
    *hover_image_id = IDR_MINIMIZE_H;
    *tooltip_id = IDS_XPFRAME_MINIMIZE_TOOLTIP;
  } else if (button_name == "maximize") {
    *normal_image_id = IDR_MAXIMIZE;
    *pressed_image_id = IDR_MAXIMIZE_P;
    *hover_image_id = IDR_MAXIMIZE_H;
    *tooltip_id = IDS_XPFRAME_MAXIMIZE_TOOLTIP;
  } else if (button_name == "unmaximize") {
    *normal_image_id = IDR_RESTORE;
    *pressed_image_id = IDR_RESTORE_P;
    *hover_image_id = IDR_RESTORE_H;
    *tooltip_id = IDS_XPFRAME_RESTORE_TOOLTIP;
  }
}

void BrowserTitlebar::UpdateButtonBackground(CustomDrawButton* button) {
  SkColor color = theme_service_->GetColor(
      ThemeProperties::COLOR_BUTTON_BACKGROUND);
  SkBitmap background = theme_service_->GetImageNamed(
      IDR_THEME_WINDOW_CONTROL_BACKGROUND).AsBitmap();

  // TODO(erg): For now, we just use a completely black mask and we can get
  // away with this in the short term because our buttons are rectangles. We
  // should get Glen to make properly hinted masks that match our min/max/close
  // buttons (which have some odd alpha blending around the
  // edges). http://crbug.com/103661
  SkBitmap mask;
  mask.setConfig(SkBitmap::kARGB_8888_Config,
                 button->SurfaceWidth(), button->SurfaceHeight(), 0);
  mask.allocPixels();
  mask.eraseColor(SK_ColorBLACK);

  button->SetBackground(color, background, mask);
}

void BrowserTitlebar::UpdateCustomFrame(bool use_custom_frame) {
  using_custom_frame_ = use_custom_frame;
  if (!use_custom_frame ||
      (browser_window_->IsMaximized() && unity::IsRunning())) {
    if (titlebar_left_buttons_vbox_)
      gtk_widget_hide(titlebar_left_buttons_vbox_);
    if (titlebar_right_buttons_vbox_)
      gtk_widget_hide(titlebar_right_buttons_vbox_);
  } else {
    if (titlebar_left_buttons_vbox_)
      gtk_widget_show_all(titlebar_left_buttons_vbox_);
    if (titlebar_right_buttons_vbox_)
      gtk_widget_show_all(titlebar_right_buttons_vbox_);
  }
  UpdateTitlebarAlignment();
  UpdateMaximizeRestoreVisibility();
}

void BrowserTitlebar::UpdateTitleAndIcon() {
  if (!app_mode_title_)
    return;

  // Get the page title and elide it to the available space.
  base::string16 title =
      browser_window_->browser()->GetWindowTitleForCurrentTab();
  gtk_label_set_text(GTK_LABEL(app_mode_title_),
                     base::UTF16ToUTF8(title).c_str());

  if (browser_window_->browser()->is_app()) {
    switch (browser_window_->browser()->type()) {
      case Browser::TYPE_POPUP: {
        // Update the system app icon.  We don't need to update the icon in the
        // top left of the custom frame, that will get updated when the
        // throbber is updated.
        Profile* profile = browser_window_->browser()->profile();
        gfx::Image icon = browser_window_->browser()->GetCurrentPageIcon();
        if (icon.IsEmpty()) {
          gtk_util::SetWindowIcon(window_, profile);
        } else {
          gtk_util::SetWindowIcon(window_, profile, icon.ToGdkPixbuf());
        }
        break;
      }
      case Browser::TYPE_TABBED: {
        NOTREACHED() << "We should never have a tabbed app window.";
        break;
      }
    }
  }
}

void BrowserTitlebar::UpdateThrobber(WebContents* web_contents) {
  DCHECK(app_mode_favicon_);

  if (web_contents && web_contents->IsLoading()) {
    GdkPixbuf* icon_pixbuf =
        throbber_.GetNextFrame(web_contents->IsWaitingForResponse());
    gtk_image_set_from_pixbuf(GTK_IMAGE(app_mode_favicon_), icon_pixbuf);
  } else {
    ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();

    // Note: we want to exclude the application popup/panel window.
    if ((browser_window_->browser()->is_app() &&
        !browser_window_->browser()->is_type_tabbed()) ||
        browser_window_->browser()->is_type_popup()) {
      gfx::Image icon = browser_window_->browser()->GetCurrentPageIcon();
      if (icon.IsEmpty()) {
        // Fallback to the Chromium icon if the page has no icon.
        gtk_image_set_from_pixbuf(GTK_IMAGE(app_mode_favicon_),
            rb.GetNativeImageNamed(IDR_PRODUCT_LOGO_16).ToGdkPixbuf());
      } else {
        gtk_image_set_from_pixbuf(GTK_IMAGE(app_mode_favicon_),
                                  icon.ToGdkPixbuf());
      }
    } else {
      gtk_image_set_from_pixbuf(GTK_IMAGE(app_mode_favicon_),
          rb.GetNativeImageNamed(IDR_PRODUCT_LOGO_16).ToGdkPixbuf());
    }
    throbber_.Reset();
  }
}

void BrowserTitlebar::UpdateTitlebarAlignment() {
  if (browser_window_->browser()->is_type_tabbed()) {
    int top_padding = 0;
    int side_padding = 0;
    int vertical_offset = kNormalVerticalOffset;

    if (using_custom_frame_) {
      if (!browser_window_->IsMaximized()) {
        top_padding = kTitlebarHeight;
      } else if (using_custom_frame_ && browser_window_->IsMaximized()) {
        vertical_offset = 0;
        if (!unity::IsRunning())
          side_padding = kMaximizedTabstripPadding;
      }
    }

    int right_padding = 0;
    int left_padding = kTabStripLeftPadding;
    if (titlebar_right_buttons_hbox_)
      right_padding = side_padding;
    if (titlebar_left_buttons_hbox_)
      left_padding = side_padding;

    gtk_alignment_set_padding(GTK_ALIGNMENT(titlebar_alignment_),
                              top_padding, 0,
                              left_padding, right_padding);
    browser_window_->tabstrip()->SetVerticalOffset(vertical_offset);
  } else {
    if (using_custom_frame_ && !browser_window_->IsFullscreen()) {
      gtk_alignment_set_padding(GTK_ALIGNMENT(titlebar_alignment_),
          kAppModePaddingTop, kAppModePaddingBottom, kAppModePaddingLeft, 0);
      gtk_widget_show(titlebar_alignment_);
    } else {
      gtk_widget_hide(titlebar_alignment_);
    }
  }

  // Resize the buttons so that the clickable area extends all the way to the
  // edge of the browser window.
  GtkRequisition close_button_req = close_button_req_;
  GtkRequisition minimize_button_req = minimize_button_req_;
  GtkRequisition restore_button_req = restore_button_req_;
  if (using_custom_frame_ && browser_window_->IsMaximized()) {
    close_button_req.width += kButtonOuterPadding;
    close_button_req.height += kButtonOuterPadding;
    minimize_button_req.height += kButtonOuterPadding;
    restore_button_req.height += kButtonOuterPadding;
    if (top_padding_left_)
      gtk_widget_hide(top_padding_left_);
    if (top_padding_right_)
      gtk_widget_hide(top_padding_right_);
  } else {
    if (top_padding_left_)
      gtk_widget_show(top_padding_left_);
    if (top_padding_right_)
      gtk_widget_show(top_padding_right_);
  }
  if (close_button_.get()) {
    gtk_widget_set_size_request(close_button_->widget(),
                                close_button_req.width,
                                close_button_req.height);
  }
  if (minimize_button_.get()) {
    gtk_widget_set_size_request(minimize_button_->widget(),
                                minimize_button_req.width,
                                minimize_button_req.height);
  }
  if (maximize_button_.get()) {
    gtk_widget_set_size_request(restore_button_->widget(),
                                restore_button_req.width,
                                restore_button_req.height);
  }
}

void BrowserTitlebar::UpdateTextColor() {
  if (!app_mode_title_)
    return;

  if (theme_service_ && theme_service_->UsingNativeTheme()) {
    // We don't really have any good options here.
    //
    // Colors from window manager themes aren't exposed in GTK; the window
    // manager is a separate component and when there is information sharing
    // (in the case of metacity), it's one way where the window manager reads
    // data from the GTK theme (which allows us to do a decent job with
    // picking the frame color).
    //
    // We probably won't match in the majority of cases, but we can at the
    // very least make things legible. The default metacity and xfwm themes
    // on ubuntu have white text hardcoded. Determine whether black or white
    // has more luminosity contrast and then set that color as the text
    // color.
    GdkColor frame_color;
    if (window_has_focus_) {
      frame_color = theme_service_->GetGdkColor(
          ThemeProperties::COLOR_FRAME);
    } else {
      frame_color = theme_service_->GetGdkColor(
          ThemeProperties::COLOR_FRAME_INACTIVE);
    }
    GdkColor text_color = PickLuminosityContrastingColor(
        &frame_color, &ui::kGdkWhite, &ui::kGdkBlack);
    gtk_util::SetLabelColor(app_mode_title_, &text_color);
  } else {
    gtk_util::SetLabelColor(app_mode_title_, &ui::kGdkWhite);
  }
}

void BrowserTitlebar::UpdateAvatarLabel() {
  if (theme_service_ && avatar_label_) {
    GdkColor text_color =
        theme_service_->GetGdkColor(ThemeProperties::COLOR_MANAGED_USER_LABEL);
    GdkColor label_background = theme_service_->GetGdkColor(
        ThemeProperties::COLOR_MANAGED_USER_LABEL_BACKGROUND);
    gtk_util::SetLabelColor(avatar_label_, &text_color);
    gtk_widget_modify_bg(
        GTK_WIDGET(avatar_label_bg_), GTK_STATE_NORMAL, &label_background);
    char* markup = g_markup_printf_escaped(
        "<span size='small'>%s</span>",
        l10n_util::GetStringUTF8(IDS_MANAGED_USER_AVATAR_LABEL).c_str());
    gtk_label_set_markup(GTK_LABEL(avatar_label_), markup);
    g_free(markup);
  }
}

void BrowserTitlebar::UpdateAvatar() {
  // Remove previous state.
  gtk_util::RemoveAllChildren(titlebar_left_avatar_frame_);
  gtk_util::RemoveAllChildren(titlebar_right_avatar_frame_);
  gtk_util::RemoveAllChildren(titlebar_left_label_frame_);
  gtk_util::RemoveAllChildren(titlebar_right_label_frame_);

  if (!ShouldDisplayAvatar())
    return;

  if (!avatar_) {
    if (IsOffTheRecord()) {
      ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
      gfx::Image avatar_image =
          rb.GetNativeImageNamed(IDR_OTR_ICON, ui::ResourceBundle::RTL_ENABLED);
      avatar_ = gtk_image_new_from_pixbuf(avatar_image.ToGdkPixbuf());
      gtk_misc_set_alignment(GTK_MISC(avatar_), 0.0, 1.0);
      gtk_widget_set_size_request(avatar_, -1, 0);
    } else {
      // Use a clickable avatar.
      avatar_ = avatar_button_->widget();
    }
  }

  gtk_widget_show_all(avatar_);

  Profile* profile = browser_window_->browser()->profile();
  if (profile->IsManaged()) {
    avatar_label_ = gtk_label_new(NULL);
    gtk_misc_set_padding(GTK_MISC(avatar_label_), 10, 2);
    avatar_label_bg_ = gtk_event_box_new();
    gtk_container_add(GTK_CONTAINER(avatar_label_bg_), avatar_label_);
    g_signal_connect(avatar_label_bg_, "button-press-event",
                     G_CALLBACK(OnAvatarLabelButtonPressedThunk), this);
    UpdateAvatarLabel();
    gtk_widget_show(avatar_label_bg_);
    gtk_widget_show(avatar_label_);
    if (display_avatar_on_left_) {
      gtk_container_add(GTK_CONTAINER(titlebar_left_label_frame_),
                        avatar_label_bg_);
      gtk_widget_show(titlebar_left_label_frame_);
      gtk_widget_hide(titlebar_right_label_frame_);
    } else {
      gtk_container_add(GTK_CONTAINER(titlebar_right_label_frame_),
                        avatar_label_bg_);
      gtk_widget_show(titlebar_right_label_frame_);
      gtk_widget_hide(titlebar_left_label_frame_);
    }
  }

  if (display_avatar_on_left_) {
    gtk_container_add(GTK_CONTAINER(titlebar_left_avatar_frame_), avatar_);
    gtk_widget_show(titlebar_left_avatar_frame_);
    gtk_widget_hide(titlebar_right_avatar_frame_);
  } else {
    gtk_container_add(GTK_CONTAINER(titlebar_right_avatar_frame_), avatar_);
    gtk_widget_show(titlebar_right_avatar_frame_);
    gtk_widget_hide(titlebar_left_avatar_frame_);
  }

  if (IsOffTheRecord())
    return;

  bool is_gaia_picture = false;
  gfx::Image avatar;
  ProfileInfoCache& cache =
      g_browser_process->profile_manager()->GetProfileInfoCache();
  size_t index = cache.GetIndexOfProfileWithPath(profile->GetPath());
  if (index == std::string::npos)
    return;

  is_gaia_picture =
      cache.IsUsingGAIAPictureOfProfileAtIndex(index) &&
      cache.GetGAIAPictureOfProfileAtIndex(index);
  avatar = cache.GetAvatarIconOfProfileAtIndex(index);
  avatar_button_->SetIcon(avatar, is_gaia_picture);
  avatar_button_->set_menu_frame_style(display_avatar_on_left_ ?
      BubbleGtk::ANCHOR_TOP_LEFT : BubbleGtk::ANCHOR_TOP_RIGHT);
}

void BrowserTitlebar::MaximizeButtonClicked() {
  GdkEvent* event = gtk_get_current_event();
  if (event->button.button == 1) {
    gtk_window_maximize(window_);
  } else {
    GtkWidget* widget = GTK_WIDGET(window_);
    GdkScreen* screen = gtk_widget_get_screen(widget);
    gint monitor = gdk_screen_get_monitor_at_window(
        screen, gtk_widget_get_window(widget));
    GdkRectangle screen_rect;
    gdk_screen_get_monitor_geometry(screen, monitor, &screen_rect);

    gint x, y;
    gtk_window_get_position(window_, &x, &y);

    GtkAllocation allocation;
    gtk_widget_get_allocation(widget, &allocation);
    gint width = allocation.width;
    gint height = allocation.height;

    if (event->button.button == 3) {
      x = 0;
      width = screen_rect.width;
    } else if (event->button.button == 2) {
      y = 0;
      height = screen_rect.height;
    }

    browser_window_->SetBounds(gfx::Rect(x, y, width, height));
  }
  gdk_event_free(event);
}

void BrowserTitlebar::UpdateMaximizeRestoreVisibility() {
  if (maximize_button_.get()) {
    if (browser_window_->IsMaximized()) {
      gtk_widget_hide(maximize_button_->widget());
      gtk_widget_show(restore_button_->widget());
    } else {
      gtk_widget_hide(restore_button_->widget());
      gtk_widget_show(maximize_button_->widget());
    }
  }
}

gboolean BrowserTitlebar::OnWindowStateChanged(GtkWindow* window,
                                               GdkEventWindowState* event) {
  UpdateMaximizeRestoreVisibility();
  UpdateTitlebarAlignment();
  UpdateTextColor();
  return FALSE;
}

gboolean BrowserTitlebar::OnScroll(GtkWidget* widget, GdkEventScroll* event) {
  Browser* browser = browser_window_->browser();
  int index = browser->tab_strip_model()->active_index();
  if (event->direction == GDK_SCROLL_LEFT ||
      event->direction == GDK_SCROLL_UP) {
    if (index != 0)
      chrome::SelectPreviousTab(browser);
  } else if (index + 1 < browser->tab_strip_model()->count()) {
    chrome::SelectNextTab(browser);
  }
  return TRUE;
}

void BrowserTitlebar::OnButtonClicked(GtkWidget* button) {
  if (close_button_.get() && close_button_->widget() == button) {
    browser_window_->Close();
  } else if (restore_button_.get() && restore_button_->widget() == button) {
    browser_window_->UnMaximize();
  } else if (maximize_button_.get() && maximize_button_->widget() == button) {
    MaximizeButtonClicked();
  } else if (minimize_button_.get() && minimize_button_->widget() == button) {
    gtk_window_iconify(window_);
  }
}

gboolean BrowserTitlebar::OnFaviconMenuButtonPressed(GtkWidget* widget,
                                                     GdkEventButton* event) {
  if (event->button != 1)
    return FALSE;

  if (!favicon_menu_model_.get()) {
    favicon_menu_model_.reset(
        new PopupPageMenuModel(this, browser_window_->browser()));

    favicon_menu_.reset(new MenuGtk(NULL, favicon_menu_model_.get()));
  }

  favicon_menu_->PopupForWidget(app_mode_favicon_, event->button, event->time);

  return TRUE;
}

gboolean BrowserTitlebar::OnAvatarLabelButtonPressed(GtkWidget* widget,
                                                     GdkEventButton* event) {
  if (event->button != 1)
    return FALSE;

  // Show the avatar menu bubble with the upward arrow at the x position where
  // the user has clicked.
  gfx::Rect rect = gtk_util::WidgetBounds(widget);
  rect.set_x(event->x);
  rect.set_width(0);
  new AvatarMenuBubbleGtk(
      browser_window_->browser(), widget, BubbleGtk::ANCHOR_TOP_RIGHT, &rect);
  return TRUE;
}

void BrowserTitlebar::ShowContextMenu(GdkEventButton* event) {
  if (!context_menu_.get()) {
    context_menu_model_.reset(new ContextMenuModel(this));
    context_menu_.reset(new MenuGtk(NULL, context_menu_model_.get()));
  }

  context_menu_->PopupAsContext(gfx::Point(event->x_root, event->y_root),
                                event->time);
}

bool BrowserTitlebar::IsCommandIdEnabled(int command_id) const {
  if (command_id == kShowWindowDecorationsCommand)
    return true;

  return chrome::IsCommandEnabled(browser_window_->browser(), command_id);
}

bool BrowserTitlebar::IsCommandIdChecked(int command_id) const {
  if (command_id == kShowWindowDecorationsCommand) {
    PrefService* prefs = browser_window_->browser()->profile()->GetPrefs();
    return !prefs->GetBoolean(prefs::kUseCustomChromeFrame);
  }

  EncodingMenuController controller;
  if (controller.DoesCommandBelongToEncodingMenu(command_id)) {
    WebContents* web_contents =
        browser_window_->browser()->tab_strip_model()->GetActiveWebContents();
    if (web_contents) {
      return controller.IsItemChecked(browser_window_->browser()->profile(),
                                      web_contents->GetEncoding(),
                                      command_id);
    }
    return false;
  }

  NOTREACHED();
  return false;
}

void BrowserTitlebar::ExecuteCommand(int command_id, int event_flags) {
  if (command_id == kShowWindowDecorationsCommand) {
    PrefService* prefs = browser_window_->browser()->profile()->GetPrefs();
    prefs->SetBoolean(prefs::kUseCustomChromeFrame,
                  !prefs->GetBoolean(prefs::kUseCustomChromeFrame));
    return;
  }

  chrome::ExecuteCommand(browser_window_->browser(), command_id);
}

bool BrowserTitlebar::GetAcceleratorForCommandId(
    int command_id,
    ui::Accelerator* out_accelerator) {
  const ui::Accelerator* accelerator =
      AcceleratorsGtk::GetInstance()->GetPrimaryAcceleratorForCommand(
          command_id);
  if (!accelerator)
    return false;
  *out_accelerator = *accelerator;
  return true;
}

void BrowserTitlebar::Observe(int type,
                              const content::NotificationSource& source,
                              const content::NotificationDetails& details) {
  switch (type) {
    case chrome::NOTIFICATION_BROWSER_THEME_CHANGED: {
      UpdateTextColor();
      UpdateAvatarLabel();

      if (minimize_button_.get())
        UpdateButtonBackground(minimize_button_.get());
      if (maximize_button_.get())
        UpdateButtonBackground(maximize_button_.get());
      if (restore_button_.get())
        UpdateButtonBackground(restore_button_.get());
      if (close_button_.get())
        UpdateButtonBackground(close_button_.get());
      break;
    }

    case chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED:
      if (!IsOffTheRecord())
        UpdateAvatar();
      break;

    default:
      NOTREACHED();
  }
}

void BrowserTitlebar::ActiveWindowChanged(GdkWindow* active_window) {
  // Can be called during shutdown; BrowserWindowGtk will set our |window_|
  // to NULL during that time.
  if (!window_)
    return;

  window_has_focus_ =
      gtk_widget_get_window(GTK_WIDGET(window_)) == active_window;
  UpdateTextColor();
}

bool BrowserTitlebar::ShouldDisplayAvatar() {
  if (IsOffTheRecord())
    return true;

  if (!browser_window_->browser()->is_type_tabbed())
    return false;

  return AvatarMenu::ShouldShowAvatarMenu();
}

bool BrowserTitlebar::IsOffTheRecord() {
  return browser_window_->browser()->profile()->IsOffTheRecord();
}

BrowserTitlebar::ContextMenuModel::ContextMenuModel(
    ui::SimpleMenuModel::Delegate* delegate)
    : SimpleMenuModel(delegate) {
  AddItemWithStringId(IDC_NEW_TAB, IDS_TAB_CXMENU_NEWTAB);
  AddItemWithStringId(IDC_RESTORE_TAB, IDS_RESTORE_TAB);
  AddSeparator(ui::NORMAL_SEPARATOR);
  AddItemWithStringId(IDC_TASK_MANAGER, IDS_TASK_MANAGER);
  AddSeparator(ui::NORMAL_SEPARATOR);
  AddCheckItemWithStringId(kShowWindowDecorationsCommand,
                           IDS_SHOW_WINDOW_DECORATIONS_MENU);
}

/* [<][>][^][v][top][bottom][index][help] */