blob: 71c8571988fe344df95ae9cf13f552c122f18433 [file] [log] [blame]
// Copyright (c) 2010 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/gtk/browser_titlebar.h"
#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>
#include <string>
#include <vector>
#include "app/l10n_util.h"
#include "app/resource_bundle.h"
#include "base/command_line.h"
#include "base/singleton.h"
#include "base/string_piece.h"
#include "base/string_tokenizer.h"
#include "base/utf_string_conversions.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/encoding_menu_controller.h"
#include "chrome/browser/gtk/accelerators_gtk.h"
#include "chrome/browser/gtk/browser_window_gtk.h"
#include "chrome/browser/gtk/custom_button.h"
#if defined(USE_GCONF)
#include "chrome/browser/gtk/gconf_titlebar_listener.h"
#endif
#include "chrome/browser/gtk/gtk_theme_provider.h"
#include "chrome/browser/gtk/gtk_util.h"
#include "chrome/browser/gtk/menu_gtk.h"
#include "chrome/browser/gtk/nine_box.h"
#include "chrome/browser/gtk/tabs/tab_strip_gtk.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/profile.h"
#include "chrome/browser/tab_contents/tab_contents.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/wrench_menu_model.h"
#include "chrome/common/notification_service.h"
#include "chrome/common/pref_names.h"
#include "gfx/gtk_util.h"
#include "gfx/skbitmap_operations.h"
#include "grit/app_resources.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.h"
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 kOTRBottomSpacing = 1;
// There are 2 px on each side of the OTR avatar (between the frame border and
// it on the left, and between it and the tabstrip on the right).
const int kOTRSideSpacing = 2;
// The thickness of the custom frame border; we need it here to enlarge the
// close button whent the custom frame border isn't showing but the custom
// titlebar is showing.
const int kFrameBorderThickness = 4;
// 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;
}
GdkPixbuf* GetOTRAvatar() {
static GdkPixbuf* otr_avatar = NULL;
if (!otr_avatar) {
ResourceBundle& rb = ResourceBundle::GetSharedInstance();
otr_avatar = rb.GetRTLEnabledPixbufNamed(IDR_OTR_ICON);
}
return otr_avatar;
}
// 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 menus::SimpleMenuModel {
public:
explicit PopupPageMenuModel(menus::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(
menus::SimpleMenuModel::Delegate* delegate,
Browser* browser)
: menus::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();
AddItemWithStringId(IDC_SHOW_AS_TAB, IDS_SHOW_AS_TAB);
AddItemWithStringId(IDC_COPY_URL, IDS_APP_MENU_COPY_URL);
AddSeparator();
AddItemWithStringId(IDC_CUT, IDS_CUT);
AddItemWithStringId(IDC_COPY, IDS_COPY);
AddItemWithStringId(IDC_PASTE, IDS_PASTE);
AddSeparator();
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();
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),
titlebar_left_buttons_vbox_(NULL),
titlebar_right_buttons_vbox_(NULL),
titlebar_left_buttons_hbox_(NULL),
titlebar_right_buttons_hbox_(NULL),
titlebar_left_spy_frame_(NULL),
titlebar_right_spy_frame_(NULL),
top_padding_left_(NULL),
top_padding_right_(NULL),
app_mode_favicon_(NULL),
app_mode_title_(NULL),
using_custom_frame_(false),
window_has_focus_(false),
theme_provider_(NULL) {
Init();
}
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 contains 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, and
// spyguy frames in case of incognito mode.
titlebar_left_buttons_vbox_ = gtk_vbox_new(FALSE, 0);
gtk_box_pack_start(GTK_BOX(container_hbox_), titlebar_left_buttons_vbox_,
FALSE, FALSE, 0);
if (browser_window_->browser()->profile()->IsOffTheRecord() &&
browser_window_->browser()->type() == Browser::TYPE_NORMAL) {
titlebar_left_spy_frame_ = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
gtk_widget_set_no_show_all(titlebar_left_spy_frame_, TRUE);
gtk_alignment_set_padding(GTK_ALIGNMENT(titlebar_left_spy_frame_), 0,
kOTRBottomSpacing, kOTRSideSpacing, kOTRSideSpacing);
gtk_box_pack_start(GTK_BOX(container_hbox_), titlebar_left_spy_frame_,
FALSE, FALSE, 0);
titlebar_right_spy_frame_ = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
gtk_widget_set_no_show_all(titlebar_right_spy_frame_, TRUE);
gtk_alignment_set_padding(GTK_ALIGNMENT(titlebar_right_spy_frame_), 0,
kOTRBottomSpacing, kOTRSideSpacing, kOTRSideSpacing);
gtk_box_pack_end(GTK_BOX(container_hbox_), titlebar_right_spy_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);
#if defined(USE_GCONF)
// Either read the gconf database and register for updates (on GNOME), or use
// the default value (anywhere else).
Singleton<GConfTitlebarListener>()->SetTitlebarButtons(this);
#else
BuildButtons(kDefaultButtonString);
#endif
// 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()->type() == Browser::TYPE_NORMAL) {
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(OnButtonPressedThunk), 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.
ResourceBundle& rb = ResourceBundle::GetSharedInstance();
app_mode_favicon_ = gtk_image_new_from_pixbuf(
rb.GetRTLEnabledPixbufNamed(IDR_PRODUCT_LOGO_16));
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);
// Register with the theme provider to set the |app_mode_title_| label
// color.
theme_provider_ = GtkThemeProvider::GetFrom(
browser_window_->browser()->profile());
registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED,
NotificationService::AllSources());
theme_provider_->InitThemesFor(this);
UpdateTitleAndIcon();
}
gtk_widget_show_all(container_);
ActiveWindowWatcherX::AddObserver(this);
}
BrowserTitlebar::~BrowserTitlebar() {
ActiveWindowWatcherX::RemoveObserver(this);
#if defined(USE_GCONF)
Singleton<GConfTitlebarListener>()->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;
StringTokenizer tokenizer(button_string, ":,");
tokenizer.set_options(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 (token == "minimize") {
(left_side ? left_count : right_count)++;
GtkWidget* parent_box = GetButtonHBox(left_side);
minimize_button_.reset(
BuildTitlebarButton(IDR_MINIMIZE, IDR_MINIMIZE_P,
IDR_MINIMIZE_H, parent_box,
IDS_XPFRAME_MINIMIZE_TOOLTIP));
gtk_widget_size_request(minimize_button_->widget(),
&minimize_button_req_);
} else if (token == "maximize") {
(left_side ? left_count : right_count)++;
GtkWidget* parent_box = GetButtonHBox(left_side);
restore_button_.reset(
BuildTitlebarButton(IDR_RESTORE, IDR_RESTORE_P,
IDR_RESTORE_H, parent_box,
IDS_XPFRAME_RESTORE_TOOLTIP));
maximize_button_.reset(
BuildTitlebarButton(IDR_MAXIMIZE, IDR_MAXIMIZE_P,
IDR_MAXIMIZE_H, parent_box,
IDS_XPFRAME_MAXIMIZE_TOOLTIP));
gtk_util::SetButtonClickableByMouseButtons(maximize_button_->widget(),
true, true, true);
gtk_widget_size_request(restore_button_->widget(),
&restore_button_req_);
} else if (token == "close") {
(left_side ? left_count : right_count)++;
GtkWidget* parent_box = GetButtonHBox(left_side);
close_button_.reset(
BuildTitlebarButton(IDR_CLOSE, IDR_CLOSE_P,
IDR_CLOSE_H, parent_box,
IDS_XPFRAME_CLOSE_TOOLTIP));
close_button_->set_flipped(left_side);
gtk_widget_size_request(close_button_->widget(), &close_button_req_);
}
// Ignore any other values like "pin" since we don't have images for
// those.
}
}
// 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.
if (browser_window_->browser()->profile()->IsOffTheRecord() &&
browser_window_->browser()->type() == Browser::TYPE_NORMAL) {
GtkWidget* spy_guy = gtk_image_new_from_pixbuf(GetOTRAvatar());
gtk_misc_set_alignment(GTK_MISC(spy_guy), 0.0, 1.0);
gtk_widget_set_size_request(spy_guy, -1, 0);
gtk_widget_show(spy_guy);
// Remove previous state.
gtk_util::RemoveAllChildren(titlebar_left_spy_frame_);
gtk_util::RemoveAllChildren(titlebar_right_spy_frame_);
if (right_count > left_count) {
gtk_container_add(GTK_CONTAINER(titlebar_left_spy_frame_), spy_guy);
gtk_widget_show(titlebar_left_spy_frame_);
gtk_widget_hide(titlebar_right_spy_frame_);
} else {
gtk_container_add(GTK_CONTAINER(titlebar_right_spy_frame_), spy_guy);
gtk_widget_show(titlebar_right_spy_frame_);
gtk_widget_hide(titlebar_left_spy_frame_);
}
}
// 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();
}
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::BuildTitlebarButton(int image,
int image_pressed, int image_hot, GtkWidget* box, int tooltip) {
CustomDrawButton* button = new CustomDrawButton(image, image_pressed,
image_hot, 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);
gtk_widget_set_tooltip_text(button->widget(),
localized_tooltip.c_str());
gtk_box_pack_start(GTK_BOX(box), button->widget(), FALSE, FALSE, 0);
return button;
}
void BrowserTitlebar::UpdateCustomFrame(bool use_custom_frame) {
using_custom_frame_ = use_custom_frame;
if (use_custom_frame) {
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_);
} else {
if (titlebar_left_buttons_vbox_)
gtk_widget_hide(titlebar_left_buttons_vbox_);
if (titlebar_right_buttons_vbox_)
gtk_widget_hide(titlebar_right_buttons_vbox_);
}
UpdateTitlebarAlignment();
}
void BrowserTitlebar::UpdateTitleAndIcon() {
if (!app_mode_title_)
return;
// Get the page title and elide it to the available space.
string16 title = browser_window_->browser()->GetWindowTitleForCurrentTab();
gtk_label_set_text(GTK_LABEL(app_mode_title_), UTF16ToUTF8(title).c_str());
// Note: this isn't browser_window_->browser()->type() & Browser::TYPE_APP
// because we want to exclude Browser::TYPE_APP_POPUP.
if (browser_window_->browser()->type() == Browser::TYPE_APP ||
browser_window_->browser()->type() == Browser::TYPE_APP_PANEL) {
// 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.
SkBitmap icon = browser_window_->browser()->GetCurrentPageIcon();
if (icon.empty()) {
gtk_util::SetWindowIcon(window_);
} else {
GdkPixbuf* icon_pixbuf = gfx::GdkPixbufFromSkBitmap(&icon);
gtk_window_set_icon(window_, icon_pixbuf);
g_object_unref(icon_pixbuf);
}
}
}
void BrowserTitlebar::UpdateThrobber(TabContents* tab_contents) {
DCHECK(app_mode_favicon_);
if (tab_contents && tab_contents->is_loading()) {
GdkPixbuf* icon_pixbuf =
throbber_.GetNextFrame(tab_contents->waiting_for_response());
gtk_image_set_from_pixbuf(GTK_IMAGE(app_mode_favicon_), icon_pixbuf);
} else {
ResourceBundle& rb = ResourceBundle::GetSharedInstance();
// Note: this isn't browser_window_->browser()->type() & Browser::TYPE_APP
// because we want to exclude Browser::TYPE_APP_POPUP.
if (browser_window_->browser()->type() == Browser::TYPE_APP ||
browser_window_->browser()->type() == Browser::TYPE_APP_PANEL) {
SkBitmap icon = browser_window_->browser()->GetCurrentPageIcon();
if (icon.empty()) {
// Fallback to the Chromium icon if the page has no icon.
gtk_image_set_from_pixbuf(GTK_IMAGE(app_mode_favicon_),
rb.GetPixbufNamed(IDR_PRODUCT_LOGO_16));
} else {
GdkPixbuf* icon_pixbuf = gfx::GdkPixbufFromSkBitmap(&icon);
gtk_image_set_from_pixbuf(GTK_IMAGE(app_mode_favicon_), icon_pixbuf);
g_object_unref(icon_pixbuf);
}
} else {
gtk_image_set_from_pixbuf(GTK_IMAGE(app_mode_favicon_),
rb.GetPixbufNamed(IDR_PRODUCT_LOGO_16));
}
throbber_.Reset();
}
}
void BrowserTitlebar::UpdateTitlebarAlignment() {
if (browser_window_->browser()->type() == Browser::TYPE_NORMAL) {
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;
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_provider_ && theme_provider_->UseGtkTheme()) {
// 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_provider_->GetGdkColor(
BrowserThemeProvider::COLOR_FRAME);
} else {
frame_color = theme_provider_->GetGdkColor(
BrowserThemeProvider::COLOR_FRAME_INACTIVE);
}
GdkColor text_color = PickLuminosityContrastingColor(
&frame_color, &gtk_util::kGdkWhite, &gtk_util::kGdkBlack);
gtk_util::SetLabelColor(app_mode_title_, &text_color);
} else {
gtk_util::SetLabelColor(app_mode_title_, &gtk_util::kGdkWhite);
}
}
void BrowserTitlebar::ShowFaviconMenu(GdkEventButton* event) {
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_->Popup(app_mode_favicon_, reinterpret_cast<GdkEvent*>(event));
}
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, widget->window);
GdkRectangle screen_rect;
gdk_screen_get_monitor_geometry(screen, monitor, &screen_rect);
gint x, y;
gtk_window_get_position(window_, &x, &y);
gint width = widget->allocation.width;
gint height = widget->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) {
TabStripModel* tabstrip_model = browser_window_->browser()->tabstrip_model();
int index = tabstrip_model->selected_index();
if (event->direction == GDK_SCROLL_LEFT ||
event->direction == GDK_SCROLL_UP) {
if (index != 0)
tabstrip_model->SelectPreviousTab();
} else if (index + 1 < tabstrip_model->count()) {
tabstrip_model->SelectNextTab();
}
return TRUE;
}
// static
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::OnButtonPressed(GtkWidget* widget,
GdkEventButton* event) {
if (event->button != 1)
return FALSE;
ShowFaviconMenu(event);
return TRUE;
}
void BrowserTitlebar::ShowContextMenu() {
if (!context_menu_.get()) {
context_menu_model_.reset(new ContextMenuModel(this));
context_menu_.reset(new MenuGtk(NULL, context_menu_model_.get()));
}
context_menu_->PopupAsContext(gtk_get_current_event_time());
}
bool BrowserTitlebar::IsCommandIdEnabled(int command_id) const {
if (command_id == kShowWindowDecorationsCommand)
return true;
return browser_window_->browser()->command_updater()->
IsCommandEnabled(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)) {
TabContents* tab_contents =
browser_window_->browser()->GetSelectedTabContents();
if (tab_contents) {
return controller.IsItemChecked(browser_window_->browser()->profile(),
tab_contents->encoding(),
command_id);
}
return false;
}
NOTREACHED();
return false;
}
void BrowserTitlebar::ExecuteCommand(int command_id) {
if (command_id == kShowWindowDecorationsCommand) {
PrefService* prefs = browser_window_->browser()->profile()->GetPrefs();
prefs->SetBoolean(prefs::kUseCustomChromeFrame,
!prefs->GetBoolean(prefs::kUseCustomChromeFrame));
return;
}
browser_window_->browser()->ExecuteCommand(command_id);
}
bool BrowserTitlebar::GetAcceleratorForCommandId(
int command_id, menus::Accelerator* accelerator) {
const menus::AcceleratorGtk* accelerator_gtk =
Singleton<AcceleratorsGtk>()->GetPrimaryAcceleratorForCommand(
command_id);
if (accelerator_gtk)
*accelerator = *accelerator_gtk;
return accelerator_gtk;
}
void BrowserTitlebar::Observe(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details) {
switch (type.value) {
case NotificationType::BROWSER_THEME_CHANGED:
UpdateTextColor();
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(window_)->window == active_window;
UpdateTextColor();
}
///////////////////////////////////////////////////////////////////////////////
// BrowserTitlebar::Throbber implementation
// TODO(tc): Handle anti-clockwise spinning when waiting for a connection.
// We don't bother to clean up these or the pixbufs they contain when we exit.
static std::vector<GdkPixbuf*>* g_throbber_frames = NULL;
static std::vector<GdkPixbuf*>* g_throbber_waiting_frames = NULL;
// Load |resource_id| from the ResourceBundle and split it into a series of
// square GdkPixbufs that get stored in |frames|.
static void MakeThrobberFrames(int resource_id,
std::vector<GdkPixbuf*>* frames) {
ResourceBundle &rb = ResourceBundle::GetSharedInstance();
SkBitmap* frame_strip = rb.GetBitmapNamed(resource_id);
// Each frame of the animation is a square, so we use the height as the
// frame size.
int frame_size = frame_strip->height();
size_t num_frames = frame_strip->width() / frame_size;
// Make a separate GdkPixbuf for each frame of the animation.
for (size_t i = 0; i < num_frames; ++i) {
SkBitmap frame = SkBitmapOperations::CreateTiledBitmap(*frame_strip,
i * frame_size, 0, frame_size, frame_size);
frames->push_back(gfx::GdkPixbufFromSkBitmap(&frame));
}
}
GdkPixbuf* BrowserTitlebar::Throbber::GetNextFrame(bool is_waiting) {
Throbber::InitFrames();
if (is_waiting) {
return (*g_throbber_waiting_frames)[current_waiting_frame_++ %
g_throbber_waiting_frames->size()];
} else {
return (*g_throbber_frames)[current_frame_++ % g_throbber_frames->size()];
}
}
void BrowserTitlebar::Throbber::Reset() {
current_frame_ = 0;
current_waiting_frame_ = 0;
}
// static
void BrowserTitlebar::Throbber::InitFrames() {
if (g_throbber_frames)
return;
// We load the light version of the throbber since it'll be in the titlebar.
g_throbber_frames = new std::vector<GdkPixbuf*>;
MakeThrobberFrames(IDR_THROBBER_LIGHT, g_throbber_frames);
g_throbber_waiting_frames = new std::vector<GdkPixbuf*>;
MakeThrobberFrames(IDR_THROBBER_WAITING_LIGHT, g_throbber_waiting_frames);
}
BrowserTitlebar::ContextMenuModel::ContextMenuModel(
menus::SimpleMenuModel::Delegate* delegate)
: SimpleMenuModel(delegate) {
AddItemWithStringId(IDC_NEW_TAB, IDS_TAB_CXMENU_NEWTAB);
AddItemWithStringId(IDC_RESTORE_TAB, IDS_RESTORE_TAB);
AddSeparator();
AddItemWithStringId(IDC_TASK_MANAGER, IDS_TASK_MANAGER);
AddSeparator();
AddCheckItemWithStringId(kShowWindowDecorationsCommand,
IDS_SHOW_WINDOW_DECORATIONS_MENU);
}