• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
OnMouseMoveEvent(GtkWidget * widget,GdkEventMotion * event,BrowserWindowGtk * browser_window)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 
GetOTRAvatar()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.
GdkColorToHSL(const GdkColor * color)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.
PickLuminosityContrastingColor(const GdkColor * base,const GdkColor * one,const GdkColor * two)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);
~PopupPageMenuModel()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 
PopupPageMenuModel(ui::SimpleMenuModel::Delegate * delegate,Browser * browser)163 PopupPageMenuModel::PopupPageMenuModel(
164     ui::SimpleMenuModel::Delegate* delegate,
165     Browser* browser)
166     : ui::SimpleMenuModel(delegate), browser_(browser) {
167   Build();
168 }
169 
Build()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 
BrowserTitlebar(BrowserWindowGtk * browser_window,GtkWindow * window)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 
Init()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 
~BrowserTitlebar()373 BrowserTitlebar::~BrowserTitlebar() {
374   ui::ActiveWindowWatcherX::RemoveObserver(this);
375 #if defined(USE_GCONF)
376   GConfTitlebarListener::GetInstance()->RemoveObserver(this);
377 #endif
378 }
379 
BuildButtons(const std::string & button_string)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 
GetButtonHBox(bool left_side)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 
BuildTitlebarButton(int image,int image_pressed,int image_hot,GtkWidget * box,int tooltip)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 
UpdateCustomFrame(bool use_custom_frame)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 
UpdateTitleAndIcon()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 
UpdateThrobber(TabContents * tab_contents)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 
UpdateTitlebarAlignment()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 
UpdateTextColor()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 
ShowFaviconMenu(GdkEventButton * event)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 
MaximizeButtonClicked()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 
UpdateMaximizeRestoreVisibility()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 
OnWindowStateChanged(GtkWindow * window,GdkEventWindowState * event)756 gboolean BrowserTitlebar::OnWindowStateChanged(GtkWindow* window,
757                                                GdkEventWindowState* event) {
758   UpdateMaximizeRestoreVisibility();
759   UpdateTitlebarAlignment();
760   UpdateTextColor();
761   return FALSE;
762 }
763 
OnScroll(GtkWidget * widget,GdkEventScroll * event)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
OnButtonClicked(GtkWidget * button)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 
OnButtonPressed(GtkWidget * widget,GdkEventButton * event)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 
ShowContextMenu(GdkEventButton * event)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 
IsCommandIdEnabled(int command_id) const809 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 
IsCommandIdChecked(int command_id) const817 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 
ExecuteCommand(int command_id)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 
GetAcceleratorForCommandId(int command_id,ui::Accelerator * accelerator)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 
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)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 
ActiveWindowChanged(GdkWindow * active_window)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|.
MakeThrobberFrames(int resource_id,std::vector<GdkPixbuf * > * 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 
GetNextFrame(bool is_waiting)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 
Reset()921 void BrowserTitlebar::Throbber::Reset() {
922   current_frame_ = 0;
923   current_waiting_frame_ = 0;
924 }
925 
926 // static
InitFrames()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 
ContextMenuModel(ui::SimpleMenuModel::Delegate * delegate)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