| // 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, >k_util::kGdkWhite, >k_util::kGdkBlack); |
| gtk_util::SetLabelColor(app_mode_title_, &text_color); |
| } else { |
| gtk_util::SetLabelColor(app_mode_title_, >k_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); |
| } |