Home | History | Annotate | Download | only in gtk
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/ui/gtk/browser_titlebar.h"
      6 
      7 #include <gdk/gdkkeysyms.h>
      8 #include <gtk/gtk.h>
      9 
     10 #include <string>
     11 #include <vector>
     12 
     13 #include "base/command_line.h"
     14 #include "base/memory/singleton.h"
     15 #include "base/string_piece.h"
     16 #include "base/string_tokenizer.h"
     17 #include "base/utf_string_conversions.h"
     18 #include "chrome/app/chrome_command_ids.h"
     19 #include "chrome/browser/prefs/pref_service.h"
     20 #include "chrome/browser/profiles/profile.h"
     21 #include "chrome/browser/ui/browser.h"
     22 #include "chrome/browser/ui/gtk/accelerators_gtk.h"
     23 #include "chrome/browser/ui/gtk/browser_window_gtk.h"
     24 #include "chrome/browser/ui/gtk/custom_button.h"
     25 #if defined(USE_GCONF)
     26 #include "chrome/browser/ui/gtk/gconf_titlebar_listener.h"
     27 #endif
     28 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
     29 #include "chrome/browser/ui/gtk/gtk_util.h"
     30 #include "chrome/browser/ui/gtk/menu_gtk.h"
     31 #include "chrome/browser/ui/gtk/nine_box.h"
     32 #include "chrome/browser/ui/gtk/tabs/tab_strip_gtk.h"
     33 #include "chrome/browser/ui/toolbar/encoding_menu_controller.h"
     34 #include "chrome/browser/ui/toolbar/wrench_menu_model.h"
     35 #include "chrome/common/pref_names.h"
     36 #include "content/browser/tab_contents/tab_contents.h"
     37 #include "content/common/notification_service.h"
     38 #include "grit/app_resources.h"
     39 #include "grit/generated_resources.h"
     40 #include "grit/theme_resources.h"
     41 #include "ui/base/l10n/l10n_util.h"
     42 #include "ui/base/resource/resource_bundle.h"
     43 #include "ui/gfx/gtk_util.h"
     44 #include "ui/gfx/skbitmap_operations.h"
     45 
     46 namespace {
     47 
     48 // The space above the titlebars.
     49 const int kTitlebarHeight = 14;
     50 
     51 // The thickness in pixels of the tab border.
     52 const int kTabOuterThickness = 1;
     53 
     54 // Amount to offset the tab images relative to the background.
     55 const int kNormalVerticalOffset = kTitlebarHeight + kTabOuterThickness;
     56 
     57 // A linux specific menu item for toggling window decorations.
     58 const int kShowWindowDecorationsCommand = 200;
     59 
     60 const int kOTRBottomSpacing = 1;
     61 // There are 2 px on each side of the OTR avatar (between the frame border and
     62 // it on the left, and between it and the tabstrip on the right).
     63 const int kOTRSideSpacing = 2;
     64 
     65 // The thickness of the custom frame border; we need it here to enlarge the
     66 // close button whent the custom frame border isn't showing but the custom
     67 // titlebar is showing.
     68 const int kFrameBorderThickness = 4;
     69 
     70 // There is a 4px gap between the icon and the title text.
     71 const int kIconTitleSpacing = 4;
     72 
     73 // Padding around the icon when in app mode or popup mode.
     74 const int kAppModePaddingTop = 5;
     75 const int kAppModePaddingBottom = 4;
     76 const int kAppModePaddingLeft = 2;
     77 
     78 // The left padding of the tab strip.  In Views, the tab strip has a left
     79 // margin of FrameBorderThickness + kClientEdgeThickness.  This offset is to
     80 // account for kClientEdgeThickness.
     81 const int kTabStripLeftPadding = 1;
     82 
     83 // Spacing between buttons of the titlebar.
     84 const int kButtonSpacing = 2;
     85 
     86 // Spacing around outside of titlebar buttons.
     87 const int kButtonOuterPadding = 2;
     88 
     89 // Spacing between tabstrip and window control buttons (when the window is
     90 // maximized).
     91 const int kMaximizedTabstripPadding = 16;
     92 
     93 gboolean OnMouseMoveEvent(GtkWidget* widget, GdkEventMotion* event,
     94                           BrowserWindowGtk* browser_window) {
     95   // Reset to the default mouse cursor.
     96   browser_window->ResetCustomFrameCursor();
     97   return TRUE;
     98 }
     99 
    100 GdkPixbuf* GetOTRAvatar() {
    101   static GdkPixbuf* otr_avatar = NULL;
    102   if (!otr_avatar) {
    103     ResourceBundle& rb = ResourceBundle::GetSharedInstance();
    104     otr_avatar = rb.GetRTLEnabledPixbufNamed(IDR_OTR_ICON);
    105   }
    106   return otr_avatar;
    107 }
    108 
    109 // Converts a GdkColor to a color_utils::HSL.
    110 color_utils::HSL GdkColorToHSL(const GdkColor* color) {
    111   color_utils::HSL hsl;
    112   color_utils::SkColorToHSL(SkColorSetRGB(color->red >> 8,
    113                                           color->green >> 8,
    114                                           color->blue >> 8), &hsl);
    115   return hsl;
    116 }
    117 
    118 // Returns either |one| or |two| based on which has a greater difference in
    119 // luminosity.
    120 GdkColor PickLuminosityContrastingColor(const GdkColor* base,
    121                                         const GdkColor* one,
    122                                         const GdkColor* two) {
    123   // Convert all GdkColors to color_utils::HSLs.
    124   color_utils::HSL baseHSL = GdkColorToHSL(base);
    125   color_utils::HSL oneHSL = GdkColorToHSL(one);
    126   color_utils::HSL twoHSL = GdkColorToHSL(two);
    127   double one_difference = fabs(baseHSL.l - oneHSL.l);
    128   double two_difference = fabs(baseHSL.l - twoHSL.l);
    129 
    130   // Be biased towards the first color presented.
    131   if (two_difference > one_difference + 0.1)
    132     return *two;
    133   else
    134     return *one;
    135 }
    136 
    137 }  // namespace
    138 
    139 ////////////////////////////////////////////////////////////////////////////////
    140 // PopupPageMenuModel
    141 
    142 // A menu model that builds the contents of the menu shown for popups (when the
    143 // user clicks on the favicon) and all of its submenus.
    144 class PopupPageMenuModel : public ui::SimpleMenuModel {
    145  public:
    146   explicit PopupPageMenuModel(ui::SimpleMenuModel::Delegate* delegate,
    147                               Browser* browser);
    148   virtual ~PopupPageMenuModel() { }
    149 
    150  private:
    151   void Build();
    152 
    153   // Models for submenus referenced by this model. SimpleMenuModel only uses
    154   // weak references so these must be kept for the lifetime of the top-level
    155   // model.
    156   scoped_ptr<ZoomMenuModel> zoom_menu_model_;
    157   scoped_ptr<EncodingMenuModel> encoding_menu_model_;
    158   Browser* browser_;  // weak
    159 
    160   DISALLOW_COPY_AND_ASSIGN(PopupPageMenuModel);
    161 };
    162 
    163 PopupPageMenuModel::PopupPageMenuModel(
    164     ui::SimpleMenuModel::Delegate* delegate,
    165     Browser* browser)
    166     : ui::SimpleMenuModel(delegate), browser_(browser) {
    167   Build();
    168 }
    169 
    170 void PopupPageMenuModel::Build() {
    171   AddItemWithStringId(IDC_BACK, IDS_CONTENT_CONTEXT_BACK);
    172   AddItemWithStringId(IDC_FORWARD, IDS_CONTENT_CONTEXT_FORWARD);
    173   AddItemWithStringId(IDC_RELOAD, IDS_APP_MENU_RELOAD);
    174   AddSeparator();
    175   AddItemWithStringId(IDC_SHOW_AS_TAB, IDS_SHOW_AS_TAB);
    176   AddItemWithStringId(IDC_COPY_URL, IDS_APP_MENU_COPY_URL);
    177   AddSeparator();
    178   AddItemWithStringId(IDC_CUT, IDS_CUT);
    179   AddItemWithStringId(IDC_COPY, IDS_COPY);
    180   AddItemWithStringId(IDC_PASTE, IDS_PASTE);
    181   AddSeparator();
    182   AddItemWithStringId(IDC_FIND, IDS_FIND);
    183   AddItemWithStringId(IDC_PRINT, IDS_PRINT);
    184   zoom_menu_model_.reset(new ZoomMenuModel(delegate()));
    185   AddSubMenuWithStringId(IDC_ZOOM_MENU, IDS_ZOOM_MENU, zoom_menu_model_.get());
    186 
    187   encoding_menu_model_.reset(new EncodingMenuModel(browser_));
    188   AddSubMenuWithStringId(IDC_ENCODING_MENU, IDS_ENCODING_MENU,
    189                          encoding_menu_model_.get());
    190 
    191   AddSeparator();
    192   AddItemWithStringId(IDC_CLOSE_WINDOW, IDS_CLOSE);
    193 }
    194 
    195 ////////////////////////////////////////////////////////////////////////////////
    196 // BrowserTitlebar
    197 
    198 // static
    199 const char BrowserTitlebar::kDefaultButtonString[] = ":minimize,maximize,close";
    200 
    201 BrowserTitlebar::BrowserTitlebar(BrowserWindowGtk* browser_window,
    202                                  GtkWindow* window)
    203     : browser_window_(browser_window),
    204       window_(window),
    205       titlebar_left_buttons_vbox_(NULL),
    206       titlebar_right_buttons_vbox_(NULL),
    207       titlebar_left_buttons_hbox_(NULL),
    208       titlebar_right_buttons_hbox_(NULL),
    209       titlebar_left_spy_frame_(NULL),
    210       titlebar_right_spy_frame_(NULL),
    211       top_padding_left_(NULL),
    212       top_padding_right_(NULL),
    213       app_mode_favicon_(NULL),
    214       app_mode_title_(NULL),
    215       using_custom_frame_(false),
    216       window_has_focus_(false),
    217       theme_service_(NULL) {
    218   Init();
    219 }
    220 
    221 void BrowserTitlebar::Init() {
    222   // The widget hierarchy is shown below.
    223   //
    224   // +- EventBox (container_) ------------------------------------------------+
    225   // +- HBox (container_hbox_) -----------------------------------------------+
    226   // |+ VBox ---++- Algn. -++- Alignment --------------++- Algn. -++ VBox ---+|
    227   // || titlebar||titlebar ||   (titlebar_alignment_)  ||titlebar || titlebar||
    228   // || left    ||left     ||                          ||right    || right   ||
    229   // || button  ||spy      ||                          ||spy      || button  ||
    230   // || vbox    ||frame    ||+- TabStripGtk  ---------+||frame    || vbox    ||
    231   // ||         ||         || tab   tab   tabclose    |||         ||         ||
    232   // ||         ||         ||+------------------------+||         ||         ||
    233   // |+---------++---------++--------------------------++---------++---------+|
    234   // +------------------------------------------------------------------------+
    235   //
    236   // There are two vboxes on either side of |container_hbox_| because when the
    237   // desktop is GNOME, the button placement is configurable based on a metacity
    238   // gconf key. We can't just have one vbox and move it back and forth because
    239   // the gconf language allows you to place buttons on both sides of the
    240   // window.  This being Linux, I'm sure there's a bunch of people who have
    241   // already configured their window manager to do so and we don't want to get
    242   // confused when that happens. The actual contents of these vboxes are lazily
    243   // generated so they don't interfere with alignment when everything is
    244   // stuffed in the other box.
    245   //
    246   // Each vbox has the contains the following hierarchy if it contains buttons:
    247   //
    248   // +- VBox (titlebar_{l,r}_buttons_vbox_) ---------+
    249   // |+- Fixed (top_padding_{l,r}_) ----------------+|
    250   // ||+- HBox (titlebar_{l,r}_buttons_hbox_ ------+||
    251   // ||| (buttons of a configurable layout)        |||
    252   // ||+-------------------------------------------+||
    253   // |+---------------------------------------------+|
    254   // +-----------------------------------------------+
    255   //
    256   // The two spy alignments are only allocated if this window is an incognito
    257   // window. Only one of them holds the spy image.
    258   //
    259   // If we're a popup window or in app mode, we don't display the spy guy or
    260   // the tab strip.  Instead, put an hbox in titlebar_alignment_ in place of
    261   // the tab strip.
    262   // +- Alignment (titlebar_alignment_) -----------------------------------+
    263   // |+ HBox -------------------------------------------------------------+|
    264   // ||+- TabStripGtk -++- Image ----------------++- Label --------------+||
    265   // ||| hidden        ++    (app_mode_favicon_) ||    (app_mode_title_) |||
    266   // |||               ||  favicon               ||  page title          |||
    267   // ||+---------------++------------------------++----------------------+||
    268   // |+-------------------------------------------------------------------+|
    269   // +---------------------------------------------------------------------+
    270   container_hbox_ = gtk_hbox_new(FALSE, 0);
    271 
    272   container_ = gtk_event_box_new();
    273   gtk_widget_set_name(container_, "chrome-browser-titlebar");
    274   gtk_event_box_set_visible_window(GTK_EVENT_BOX(container_), FALSE);
    275   gtk_container_add(GTK_CONTAINER(container_), container_hbox_);
    276 
    277   g_signal_connect(container_, "scroll-event", G_CALLBACK(OnScrollThunk), this);
    278 
    279   g_signal_connect(window_, "window-state-event",
    280                    G_CALLBACK(OnWindowStateChangedThunk), this);
    281 
    282   // Allocate the two button boxes on the left and right parts of the bar, and
    283   // spyguy frames in case of incognito mode.
    284   titlebar_left_buttons_vbox_ = gtk_vbox_new(FALSE, 0);
    285   gtk_box_pack_start(GTK_BOX(container_hbox_), titlebar_left_buttons_vbox_,
    286                      FALSE, FALSE, 0);
    287   if (browser_window_->browser()->profile()->IsOffTheRecord() &&
    288       browser_window_->browser()->type() == Browser::TYPE_NORMAL) {
    289     titlebar_left_spy_frame_ = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
    290     gtk_widget_set_no_show_all(titlebar_left_spy_frame_, TRUE);
    291     gtk_alignment_set_padding(GTK_ALIGNMENT(titlebar_left_spy_frame_), 0,
    292         kOTRBottomSpacing, kOTRSideSpacing, kOTRSideSpacing);
    293     gtk_box_pack_start(GTK_BOX(container_hbox_), titlebar_left_spy_frame_,
    294                        FALSE, FALSE, 0);
    295 
    296     titlebar_right_spy_frame_ = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
    297     gtk_widget_set_no_show_all(titlebar_right_spy_frame_, TRUE);
    298     gtk_alignment_set_padding(GTK_ALIGNMENT(titlebar_right_spy_frame_), 0,
    299         kOTRBottomSpacing, kOTRSideSpacing, kOTRSideSpacing);
    300     gtk_box_pack_end(GTK_BOX(container_hbox_), titlebar_right_spy_frame_,
    301                      FALSE, FALSE, 0);
    302   }
    303   titlebar_right_buttons_vbox_ = gtk_vbox_new(FALSE, 0);
    304   gtk_box_pack_end(GTK_BOX(container_hbox_), titlebar_right_buttons_vbox_,
    305                    FALSE, FALSE, 0);
    306 
    307 #if defined(USE_GCONF)
    308   // Either read the gconf database and register for updates (on GNOME), or use
    309   // the default value (anywhere else).
    310   GConfTitlebarListener::GetInstance()->SetTitlebarButtons(this);
    311 #else
    312   BuildButtons(kDefaultButtonString);
    313 #endif
    314 
    315   // We use an alignment to control the titlebar height.
    316   titlebar_alignment_ = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
    317   if (browser_window_->browser()->type() == Browser::TYPE_NORMAL) {
    318     gtk_box_pack_start(GTK_BOX(container_hbox_), titlebar_alignment_, TRUE,
    319                        TRUE, 0);
    320 
    321     // Put the tab strip in the titlebar.
    322     gtk_container_add(GTK_CONTAINER(titlebar_alignment_),
    323                       browser_window_->tabstrip()->widget());
    324   } else {
    325     // App mode specific widgets.
    326     gtk_box_pack_start(GTK_BOX(container_hbox_), titlebar_alignment_, TRUE,
    327                        TRUE, 0);
    328     GtkWidget* app_mode_hbox = gtk_hbox_new(FALSE, kIconTitleSpacing);
    329     gtk_container_add(GTK_CONTAINER(titlebar_alignment_), app_mode_hbox);
    330 
    331     // Put the tab strip in the hbox even though we don't show it.  Sometimes
    332     // we need the position of the tab strip so make sure it's in our widget
    333     // hierarchy.
    334     gtk_box_pack_start(GTK_BOX(app_mode_hbox),
    335         browser_window_->tabstrip()->widget(), FALSE, FALSE, 0);
    336 
    337     GtkWidget* favicon_event_box = gtk_event_box_new();
    338     gtk_event_box_set_visible_window(GTK_EVENT_BOX(favicon_event_box), FALSE);
    339     g_signal_connect(favicon_event_box, "button-press-event",
    340                      G_CALLBACK(OnButtonPressedThunk), this);
    341     gtk_box_pack_start(GTK_BOX(app_mode_hbox), favicon_event_box, FALSE,
    342                        FALSE, 0);
    343     // We use the app logo as a placeholder image so the title doesn't jump
    344     // around.
    345     ResourceBundle& rb = ResourceBundle::GetSharedInstance();
    346     app_mode_favicon_ = gtk_image_new_from_pixbuf(
    347         rb.GetRTLEnabledPixbufNamed(IDR_PRODUCT_LOGO_16));
    348     g_object_set_data(G_OBJECT(app_mode_favicon_), "left-align-popup",
    349                       reinterpret_cast<void*>(true));
    350     gtk_container_add(GTK_CONTAINER(favicon_event_box), app_mode_favicon_);
    351 
    352     app_mode_title_ = gtk_label_new(NULL);
    353     gtk_label_set_ellipsize(GTK_LABEL(app_mode_title_), PANGO_ELLIPSIZE_END);
    354     gtk_misc_set_alignment(GTK_MISC(app_mode_title_), 0.0, 0.5);
    355     gtk_box_pack_start(GTK_BOX(app_mode_hbox), app_mode_title_, TRUE, TRUE,
    356                        0);
    357 
    358     // Register with the theme provider to set the |app_mode_title_| label
    359     // color.
    360     theme_service_ = GtkThemeService::GetFrom(
    361         browser_window_->browser()->profile());
    362     registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED,
    363                    NotificationService::AllSources());
    364     theme_service_->InitThemesFor(this);
    365     UpdateTitleAndIcon();
    366   }
    367 
    368   gtk_widget_show_all(container_);
    369 
    370   ui::ActiveWindowWatcherX::AddObserver(this);
    371 }
    372 
    373 BrowserTitlebar::~BrowserTitlebar() {
    374   ui::ActiveWindowWatcherX::RemoveObserver(this);
    375 #if defined(USE_GCONF)
    376   GConfTitlebarListener::GetInstance()->RemoveObserver(this);
    377 #endif
    378 }
    379 
    380 void BrowserTitlebar::BuildButtons(const std::string& button_string) {
    381   // Clear out all previous data.
    382   close_button_.reset();
    383   restore_button_.reset();
    384   maximize_button_.reset();
    385   minimize_button_.reset();
    386   gtk_util::RemoveAllChildren(titlebar_left_buttons_vbox_);
    387   gtk_util::RemoveAllChildren(titlebar_right_buttons_vbox_);
    388   titlebar_left_buttons_hbox_ = NULL;
    389   titlebar_right_buttons_hbox_ = NULL;
    390   top_padding_left_ = NULL;
    391   top_padding_right_ = NULL;
    392 
    393   bool left_side = true;
    394   StringTokenizer tokenizer(button_string, ":,");
    395   tokenizer.set_options(StringTokenizer::RETURN_DELIMS);
    396   int left_count = 0;
    397   int right_count = 0;
    398   while (tokenizer.GetNext()) {
    399     if (tokenizer.token_is_delim()) {
    400       if (*tokenizer.token_begin() == ':')
    401         left_side = false;
    402     } else {
    403       base::StringPiece token = tokenizer.token_piece();
    404       if (token == "minimize") {
    405         (left_side ? left_count : right_count)++;
    406         GtkWidget* parent_box = GetButtonHBox(left_side);
    407         minimize_button_.reset(
    408             BuildTitlebarButton(IDR_MINIMIZE, IDR_MINIMIZE_P,
    409                                 IDR_MINIMIZE_H, parent_box,
    410                                 IDS_XPFRAME_MINIMIZE_TOOLTIP));
    411 
    412         gtk_widget_size_request(minimize_button_->widget(),
    413                                 &minimize_button_req_);
    414       } else if (token == "maximize") {
    415         (left_side ? left_count : right_count)++;
    416         GtkWidget* parent_box = GetButtonHBox(left_side);
    417         restore_button_.reset(
    418             BuildTitlebarButton(IDR_RESTORE, IDR_RESTORE_P,
    419                                 IDR_RESTORE_H, parent_box,
    420                                 IDS_XPFRAME_RESTORE_TOOLTIP));
    421         maximize_button_.reset(
    422             BuildTitlebarButton(IDR_MAXIMIZE, IDR_MAXIMIZE_P,
    423                                 IDR_MAXIMIZE_H, parent_box,
    424                                 IDS_XPFRAME_MAXIMIZE_TOOLTIP));
    425 
    426         gtk_util::SetButtonClickableByMouseButtons(maximize_button_->widget(),
    427                                                    true, true, true);
    428         gtk_widget_size_request(restore_button_->widget(),
    429                                 &restore_button_req_);
    430       } else if (token == "close") {
    431         (left_side ? left_count : right_count)++;
    432         GtkWidget* parent_box = GetButtonHBox(left_side);
    433         close_button_.reset(
    434             BuildTitlebarButton(IDR_CLOSE, IDR_CLOSE_P,
    435                                 IDR_CLOSE_H, parent_box,
    436                                 IDS_XPFRAME_CLOSE_TOOLTIP));
    437         close_button_->set_flipped(left_side);
    438 
    439         gtk_widget_size_request(close_button_->widget(), &close_button_req_);
    440       }
    441       // Ignore any other values like "pin" since we don't have images for
    442       // those.
    443     }
    444   }
    445 
    446   // If we are in incognito mode, add the spy guy to either the end of the left
    447   // or the beginning of the right depending on which side has fewer buttons.
    448   if (browser_window_->browser()->profile()->IsOffTheRecord() &&
    449       browser_window_->browser()->type() == Browser::TYPE_NORMAL) {
    450     GtkWidget* spy_guy = gtk_image_new_from_pixbuf(GetOTRAvatar());
    451     gtk_misc_set_alignment(GTK_MISC(spy_guy), 0.0, 1.0);
    452     gtk_widget_set_size_request(spy_guy, -1, 0);
    453     gtk_widget_show(spy_guy);
    454 
    455     // Remove previous state.
    456     gtk_util::RemoveAllChildren(titlebar_left_spy_frame_);
    457     gtk_util::RemoveAllChildren(titlebar_right_spy_frame_);
    458 
    459     if (right_count > left_count) {
    460       gtk_container_add(GTK_CONTAINER(titlebar_left_spy_frame_), spy_guy);
    461       gtk_widget_show(titlebar_left_spy_frame_);
    462       gtk_widget_hide(titlebar_right_spy_frame_);
    463     } else {
    464       gtk_container_add(GTK_CONTAINER(titlebar_right_spy_frame_), spy_guy);
    465       gtk_widget_show(titlebar_right_spy_frame_);
    466       gtk_widget_hide(titlebar_left_spy_frame_);
    467     }
    468   }
    469 
    470   // Now show the correct widgets in the two hierarchies.
    471   if (using_custom_frame_) {
    472     gtk_widget_show_all(titlebar_left_buttons_vbox_);
    473     gtk_widget_show_all(titlebar_right_buttons_vbox_);
    474   }
    475   UpdateMaximizeRestoreVisibility();
    476 }
    477 
    478 GtkWidget* BrowserTitlebar::GetButtonHBox(bool left_side) {
    479   if (left_side && titlebar_left_buttons_hbox_)
    480     return titlebar_left_buttons_hbox_;
    481   else if (!left_side && titlebar_right_buttons_hbox_)
    482     return titlebar_right_buttons_hbox_;
    483 
    484   // We put the min/max/restore/close buttons in a vbox so they are top aligned
    485   // (up to padding) and don't vertically stretch.
    486   GtkWidget* vbox = left_side ? titlebar_left_buttons_vbox_ :
    487                     titlebar_right_buttons_vbox_;
    488 
    489   GtkWidget* top_padding = gtk_fixed_new();
    490   gtk_widget_set_size_request(top_padding, -1, kButtonOuterPadding);
    491   gtk_box_pack_start(GTK_BOX(vbox), top_padding, FALSE, FALSE, 0);
    492 
    493   GtkWidget* buttons_hbox = gtk_hbox_new(FALSE, kButtonSpacing);
    494   gtk_box_pack_start(GTK_BOX(vbox), buttons_hbox, FALSE, FALSE, 0);
    495 
    496   if (left_side) {
    497     titlebar_left_buttons_hbox_ = buttons_hbox;
    498     top_padding_left_ = top_padding;
    499   } else {
    500     titlebar_right_buttons_hbox_ = buttons_hbox;
    501     top_padding_right_ = top_padding;
    502   }
    503 
    504   return buttons_hbox;
    505 }
    506 
    507 CustomDrawButton* BrowserTitlebar::BuildTitlebarButton(int image,
    508     int image_pressed, int image_hot, GtkWidget* box, int tooltip) {
    509   CustomDrawButton* button = new CustomDrawButton(image, image_pressed,
    510                                                   image_hot, 0);
    511   gtk_widget_add_events(GTK_WIDGET(button->widget()), GDK_POINTER_MOTION_MASK);
    512   g_signal_connect(button->widget(), "clicked",
    513                    G_CALLBACK(OnButtonClickedThunk), this);
    514   g_signal_connect(button->widget(), "motion-notify-event",
    515                    G_CALLBACK(OnMouseMoveEvent), browser_window_);
    516   std::string localized_tooltip = l10n_util::GetStringUTF8(tooltip);
    517   gtk_widget_set_tooltip_text(button->widget(),
    518                               localized_tooltip.c_str());
    519   gtk_box_pack_start(GTK_BOX(box), button->widget(), FALSE, FALSE, 0);
    520   return button;
    521 }
    522 
    523 void BrowserTitlebar::UpdateCustomFrame(bool use_custom_frame) {
    524   using_custom_frame_ = use_custom_frame;
    525   if (use_custom_frame) {
    526     if (titlebar_left_buttons_vbox_)
    527       gtk_widget_show_all(titlebar_left_buttons_vbox_);
    528     if (titlebar_right_buttons_vbox_)
    529       gtk_widget_show_all(titlebar_right_buttons_vbox_);
    530   } else {
    531     if (titlebar_left_buttons_vbox_)
    532       gtk_widget_hide(titlebar_left_buttons_vbox_);
    533     if (titlebar_right_buttons_vbox_)
    534       gtk_widget_hide(titlebar_right_buttons_vbox_);
    535   }
    536   UpdateTitlebarAlignment();
    537 }
    538 
    539 void BrowserTitlebar::UpdateTitleAndIcon() {
    540   if (!app_mode_title_)
    541     return;
    542 
    543   // Get the page title and elide it to the available space.
    544   string16 title = browser_window_->browser()->GetWindowTitleForCurrentTab();
    545   gtk_label_set_text(GTK_LABEL(app_mode_title_), UTF16ToUTF8(title).c_str());
    546 
    547   // Note: this isn't browser_window_->browser()->type() & Browser::TYPE_APP
    548   // because we want to exclude Browser::TYPE_APP_POPUP.
    549   if (browser_window_->browser()->type() == Browser::TYPE_APP ||
    550       browser_window_->browser()->type() == Browser::TYPE_APP_PANEL) {
    551     // Update the system app icon.  We don't need to update the icon in the top
    552     // left of the custom frame, that will get updated when the throbber is
    553     // updated.
    554     SkBitmap icon = browser_window_->browser()->GetCurrentPageIcon();
    555     if (icon.empty()) {
    556       gtk_util::SetWindowIcon(window_);
    557     } else {
    558       GdkPixbuf* icon_pixbuf = gfx::GdkPixbufFromSkBitmap(&icon);
    559       gtk_window_set_icon(window_, icon_pixbuf);
    560       g_object_unref(icon_pixbuf);
    561     }
    562   }
    563 }
    564 
    565 void BrowserTitlebar::UpdateThrobber(TabContents* tab_contents) {
    566   DCHECK(app_mode_favicon_);
    567 
    568   if (tab_contents && tab_contents->is_loading()) {
    569     GdkPixbuf* icon_pixbuf =
    570         throbber_.GetNextFrame(tab_contents->waiting_for_response());
    571     gtk_image_set_from_pixbuf(GTK_IMAGE(app_mode_favicon_), icon_pixbuf);
    572   } else {
    573     ResourceBundle& rb = ResourceBundle::GetSharedInstance();
    574 
    575     // Note: this isn't browser_window_->browser()->type() & Browser::TYPE_APP
    576     // because we want to exclude Browser::TYPE_APP_POPUP.
    577     if (browser_window_->browser()->type() == Browser::TYPE_APP ||
    578         browser_window_->browser()->type() == Browser::TYPE_APP_PANEL) {
    579       SkBitmap icon = browser_window_->browser()->GetCurrentPageIcon();
    580       if (icon.empty()) {
    581         // Fallback to the Chromium icon if the page has no icon.
    582         gtk_image_set_from_pixbuf(GTK_IMAGE(app_mode_favicon_),
    583             rb.GetPixbufNamed(IDR_PRODUCT_LOGO_16));
    584       } else {
    585         GdkPixbuf* icon_pixbuf = gfx::GdkPixbufFromSkBitmap(&icon);
    586         gtk_image_set_from_pixbuf(GTK_IMAGE(app_mode_favicon_), icon_pixbuf);
    587         g_object_unref(icon_pixbuf);
    588       }
    589     } else {
    590       gtk_image_set_from_pixbuf(GTK_IMAGE(app_mode_favicon_),
    591           rb.GetPixbufNamed(IDR_PRODUCT_LOGO_16));
    592     }
    593     throbber_.Reset();
    594   }
    595 }
    596 
    597 void BrowserTitlebar::UpdateTitlebarAlignment() {
    598   if (browser_window_->browser()->type() == Browser::TYPE_NORMAL) {
    599     int top_padding = 0;
    600     int side_padding = 0;
    601     int vertical_offset = kNormalVerticalOffset;
    602 
    603     if (using_custom_frame_) {
    604       if (!browser_window_->IsMaximized()) {
    605         top_padding = kTitlebarHeight;
    606       } else if (using_custom_frame_ && browser_window_->IsMaximized()) {
    607         vertical_offset = 0;
    608         side_padding = kMaximizedTabstripPadding;
    609       }
    610     }
    611 
    612     int right_padding = 0;
    613     int left_padding = kTabStripLeftPadding;
    614     if (titlebar_right_buttons_hbox_)
    615       right_padding = side_padding;
    616     if (titlebar_left_buttons_hbox_)
    617       left_padding = side_padding;
    618 
    619     gtk_alignment_set_padding(GTK_ALIGNMENT(titlebar_alignment_),
    620                               top_padding, 0,
    621                               left_padding, right_padding);
    622     browser_window_->tabstrip()->SetVerticalOffset(vertical_offset);
    623   } else {
    624     if (using_custom_frame_ && !browser_window_->IsFullscreen()) {
    625       gtk_alignment_set_padding(GTK_ALIGNMENT(titlebar_alignment_),
    626           kAppModePaddingTop, kAppModePaddingBottom, kAppModePaddingLeft, 0);
    627       gtk_widget_show(titlebar_alignment_);
    628     } else {
    629       gtk_widget_hide(titlebar_alignment_);
    630     }
    631   }
    632 
    633   // Resize the buttons so that the clickable area extends all the way to the
    634   // edge of the browser window.
    635   GtkRequisition close_button_req = close_button_req_;
    636   GtkRequisition minimize_button_req = minimize_button_req_;
    637   GtkRequisition restore_button_req = restore_button_req_;
    638   if (using_custom_frame_ && browser_window_->IsMaximized()) {
    639     close_button_req.width += kButtonOuterPadding;
    640     close_button_req.height += kButtonOuterPadding;
    641     minimize_button_req.height += kButtonOuterPadding;
    642     restore_button_req.height += kButtonOuterPadding;
    643     if (top_padding_left_)
    644       gtk_widget_hide(top_padding_left_);
    645     if (top_padding_right_)
    646       gtk_widget_hide(top_padding_right_);
    647   } else {
    648     if (top_padding_left_)
    649       gtk_widget_show(top_padding_left_);
    650     if (top_padding_right_)
    651       gtk_widget_show(top_padding_right_);
    652   }
    653   if (close_button_.get()) {
    654     gtk_widget_set_size_request(close_button_->widget(),
    655                                 close_button_req.width,
    656                                 close_button_req.height);
    657   }
    658   if (minimize_button_.get()) {
    659     gtk_widget_set_size_request(minimize_button_->widget(),
    660                                 minimize_button_req.width,
    661                                 minimize_button_req.height);
    662   }
    663   if (maximize_button_.get()) {
    664     gtk_widget_set_size_request(restore_button_->widget(),
    665                                 restore_button_req.width,
    666                                 restore_button_req.height);
    667   }
    668 }
    669 
    670 void BrowserTitlebar::UpdateTextColor() {
    671   if (!app_mode_title_)
    672     return;
    673 
    674   if (theme_service_ && theme_service_->UseGtkTheme()) {
    675     // We don't really have any good options here.
    676     //
    677     // Colors from window manager themes aren't exposed in GTK; the window
    678     // manager is a separate component and when there is information sharing
    679     // (in the case of metacity), it's one way where the window manager reads
    680     // data from the GTK theme (which allows us to do a decent job with
    681     // picking the frame color).
    682     //
    683     // We probably won't match in the majority of cases, but we can at the
    684     // very least make things legible. The default metacity and xfwm themes
    685     // on ubuntu have white text hardcoded. Determine whether black or white
    686     // has more luminosity contrast and then set that color as the text
    687     // color.
    688     GdkColor frame_color;
    689     if (window_has_focus_) {
    690       frame_color = theme_service_->GetGdkColor(
    691           ThemeService::COLOR_FRAME);
    692     } else {
    693       frame_color = theme_service_->GetGdkColor(
    694           ThemeService::COLOR_FRAME_INACTIVE);
    695     }
    696     GdkColor text_color = PickLuminosityContrastingColor(
    697         &frame_color, &gtk_util::kGdkWhite, &gtk_util::kGdkBlack);
    698     gtk_util::SetLabelColor(app_mode_title_, &text_color);
    699   } else {
    700     gtk_util::SetLabelColor(app_mode_title_, &gtk_util::kGdkWhite);
    701   }
    702 }
    703 
    704 void BrowserTitlebar::ShowFaviconMenu(GdkEventButton* event) {
    705   if (!favicon_menu_model_.get()) {
    706     favicon_menu_model_.reset(
    707         new PopupPageMenuModel(this, browser_window_->browser()));
    708 
    709     favicon_menu_.reset(new MenuGtk(NULL, favicon_menu_model_.get()));
    710   }
    711 
    712   favicon_menu_->PopupForWidget(app_mode_favicon_, event->button, event->time);
    713 }
    714 
    715 void BrowserTitlebar::MaximizeButtonClicked() {
    716   GdkEvent* event = gtk_get_current_event();
    717   if (event->button.button == 1) {
    718     gtk_window_maximize(window_);
    719   } else {
    720     GtkWidget* widget = GTK_WIDGET(window_);
    721     GdkScreen* screen = gtk_widget_get_screen(widget);
    722     gint monitor = gdk_screen_get_monitor_at_window(screen, widget->window);
    723     GdkRectangle screen_rect;
    724     gdk_screen_get_monitor_geometry(screen, monitor, &screen_rect);
    725 
    726     gint x, y;
    727     gtk_window_get_position(window_, &x, &y);
    728     gint width = widget->allocation.width;
    729     gint height = widget->allocation.height;
    730 
    731     if (event->button.button == 3) {
    732       x = 0;
    733       width = screen_rect.width;
    734     } else if (event->button.button == 2) {
    735       y = 0;
    736       height = screen_rect.height;
    737     }
    738 
    739     browser_window_->SetBounds(gfx::Rect(x, y, width, height));
    740   }
    741   gdk_event_free(event);
    742 }
    743 
    744 void BrowserTitlebar::UpdateMaximizeRestoreVisibility() {
    745   if (maximize_button_.get()) {
    746     if (browser_window_->IsMaximized()) {
    747       gtk_widget_hide(maximize_button_->widget());
    748       gtk_widget_show(restore_button_->widget());
    749     } else {
    750       gtk_widget_hide(restore_button_->widget());
    751       gtk_widget_show(maximize_button_->widget());
    752     }
    753   }
    754 }
    755 
    756 gboolean BrowserTitlebar::OnWindowStateChanged(GtkWindow* window,
    757                                                GdkEventWindowState* event) {
    758   UpdateMaximizeRestoreVisibility();
    759   UpdateTitlebarAlignment();
    760   UpdateTextColor();
    761   return FALSE;
    762 }
    763 
    764 gboolean BrowserTitlebar::OnScroll(GtkWidget* widget, GdkEventScroll* event) {
    765   TabStripModel* tabstrip_model = browser_window_->browser()->tabstrip_model();
    766   int index = tabstrip_model->active_index();
    767   if (event->direction == GDK_SCROLL_LEFT ||
    768       event->direction == GDK_SCROLL_UP) {
    769     if (index != 0)
    770       tabstrip_model->SelectPreviousTab();
    771   } else if (index + 1 < tabstrip_model->count()) {
    772     tabstrip_model->SelectNextTab();
    773   }
    774   return TRUE;
    775 }
    776 
    777 // static
    778 void BrowserTitlebar::OnButtonClicked(GtkWidget* button) {
    779   if (close_button_.get() && close_button_->widget() == button) {
    780     browser_window_->Close();
    781   } else if (restore_button_.get() && restore_button_->widget() == button) {
    782     browser_window_->UnMaximize();
    783   } else if (maximize_button_.get() && maximize_button_->widget() == button) {
    784     MaximizeButtonClicked();
    785   } else if (minimize_button_.get() && minimize_button_->widget() == button) {
    786     gtk_window_iconify(window_);
    787   }
    788 }
    789 
    790 gboolean BrowserTitlebar::OnButtonPressed(GtkWidget* widget,
    791                                           GdkEventButton* event) {
    792   if (event->button != 1)
    793     return FALSE;
    794 
    795   ShowFaviconMenu(event);
    796   return TRUE;
    797 }
    798 
    799 void BrowserTitlebar::ShowContextMenu(GdkEventButton* event) {
    800   if (!context_menu_.get()) {
    801     context_menu_model_.reset(new ContextMenuModel(this));
    802     context_menu_.reset(new MenuGtk(NULL, context_menu_model_.get()));
    803   }
    804 
    805   context_menu_->PopupAsContext(gfx::Point(event->x_root, event->y_root),
    806                                 event->time);
    807 }
    808 
    809 bool BrowserTitlebar::IsCommandIdEnabled(int command_id) const {
    810   if (command_id == kShowWindowDecorationsCommand)
    811     return true;
    812 
    813   return browser_window_->browser()->command_updater()->
    814       IsCommandEnabled(command_id);
    815 }
    816 
    817 bool BrowserTitlebar::IsCommandIdChecked(int command_id) const {
    818   if (command_id == kShowWindowDecorationsCommand) {
    819     PrefService* prefs = browser_window_->browser()->profile()->GetPrefs();
    820     return !prefs->GetBoolean(prefs::kUseCustomChromeFrame);
    821   }
    822 
    823   EncodingMenuController controller;
    824   if (controller.DoesCommandBelongToEncodingMenu(command_id)) {
    825     TabContents* tab_contents =
    826         browser_window_->browser()->GetSelectedTabContents();
    827     if (tab_contents) {
    828       return controller.IsItemChecked(browser_window_->browser()->profile(),
    829                                       tab_contents->encoding(),
    830                                       command_id);
    831     }
    832     return false;
    833   }
    834 
    835   NOTREACHED();
    836   return false;
    837 }
    838 
    839 void BrowserTitlebar::ExecuteCommand(int command_id) {
    840   if (command_id == kShowWindowDecorationsCommand) {
    841     PrefService* prefs = browser_window_->browser()->profile()->GetPrefs();
    842     prefs->SetBoolean(prefs::kUseCustomChromeFrame,
    843                   !prefs->GetBoolean(prefs::kUseCustomChromeFrame));
    844     return;
    845   }
    846 
    847   browser_window_->browser()->ExecuteCommand(command_id);
    848 }
    849 
    850 bool BrowserTitlebar::GetAcceleratorForCommandId(
    851     int command_id, ui::Accelerator* accelerator) {
    852   const ui::AcceleratorGtk* accelerator_gtk =
    853       AcceleratorsGtk::GetInstance()->GetPrimaryAcceleratorForCommand(
    854           command_id);
    855   if (accelerator_gtk)
    856     *accelerator = *accelerator_gtk;
    857   return accelerator_gtk;
    858 }
    859 
    860 void BrowserTitlebar::Observe(NotificationType type,
    861                               const NotificationSource& source,
    862                               const NotificationDetails& details) {
    863   switch (type.value) {
    864     case NotificationType::BROWSER_THEME_CHANGED:
    865       UpdateTextColor();
    866       break;
    867 
    868     default:
    869       NOTREACHED();
    870   }
    871 }
    872 
    873 void BrowserTitlebar::ActiveWindowChanged(GdkWindow* active_window) {
    874   // Can be called during shutdown; BrowserWindowGtk will set our |window_|
    875   // to NULL during that time.
    876   if (!window_)
    877     return;
    878 
    879   window_has_focus_ = GTK_WIDGET(window_)->window == active_window;
    880   UpdateTextColor();
    881 }
    882 
    883 ///////////////////////////////////////////////////////////////////////////////
    884 // BrowserTitlebar::Throbber implementation
    885 // TODO(tc): Handle anti-clockwise spinning when waiting for a connection.
    886 
    887 // We don't bother to clean up these or the pixbufs they contain when we exit.
    888 static std::vector<GdkPixbuf*>* g_throbber_frames = NULL;
    889 static std::vector<GdkPixbuf*>* g_throbber_waiting_frames = NULL;
    890 
    891 // Load |resource_id| from the ResourceBundle and split it into a series of
    892 // square GdkPixbufs that get stored in |frames|.
    893 static void MakeThrobberFrames(int resource_id,
    894                                std::vector<GdkPixbuf*>* frames) {
    895   ResourceBundle &rb = ResourceBundle::GetSharedInstance();
    896   SkBitmap* frame_strip = rb.GetBitmapNamed(resource_id);
    897 
    898   // Each frame of the animation is a square, so we use the height as the
    899   // frame size.
    900   int frame_size = frame_strip->height();
    901   size_t num_frames = frame_strip->width() / frame_size;
    902 
    903   // Make a separate GdkPixbuf for each frame of the animation.
    904   for (size_t i = 0; i < num_frames; ++i) {
    905     SkBitmap frame = SkBitmapOperations::CreateTiledBitmap(*frame_strip,
    906         i * frame_size, 0, frame_size, frame_size);
    907     frames->push_back(gfx::GdkPixbufFromSkBitmap(&frame));
    908   }
    909 }
    910 
    911 GdkPixbuf* BrowserTitlebar::Throbber::GetNextFrame(bool is_waiting) {
    912   Throbber::InitFrames();
    913   if (is_waiting) {
    914     return (*g_throbber_waiting_frames)[current_waiting_frame_++ %
    915         g_throbber_waiting_frames->size()];
    916   } else {
    917     return (*g_throbber_frames)[current_frame_++ % g_throbber_frames->size()];
    918   }
    919 }
    920 
    921 void BrowserTitlebar::Throbber::Reset() {
    922   current_frame_ = 0;
    923   current_waiting_frame_ = 0;
    924 }
    925 
    926 // static
    927 void BrowserTitlebar::Throbber::InitFrames() {
    928   if (g_throbber_frames)
    929     return;
    930 
    931   // We load the light version of the throbber since it'll be in the titlebar.
    932   g_throbber_frames = new std::vector<GdkPixbuf*>;
    933   MakeThrobberFrames(IDR_THROBBER_LIGHT, g_throbber_frames);
    934 
    935   g_throbber_waiting_frames = new std::vector<GdkPixbuf*>;
    936   MakeThrobberFrames(IDR_THROBBER_WAITING_LIGHT, g_throbber_waiting_frames);
    937 }
    938 
    939 BrowserTitlebar::ContextMenuModel::ContextMenuModel(
    940     ui::SimpleMenuModel::Delegate* delegate)
    941     : SimpleMenuModel(delegate) {
    942   AddItemWithStringId(IDC_NEW_TAB, IDS_TAB_CXMENU_NEWTAB);
    943   AddItemWithStringId(IDC_RESTORE_TAB, IDS_RESTORE_TAB);
    944   AddSeparator();
    945   AddItemWithStringId(IDC_TASK_MANAGER, IDS_TASK_MANAGER);
    946   AddSeparator();
    947   AddCheckItemWithStringId(kShowWindowDecorationsCommand,
    948                            IDS_SHOW_WINDOW_DECORATIONS_MENU);
    949 }
    950