普通文本  |  1107行  |  41.55 KB

// 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_), 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);
}