1 // Copyright (c) 2012 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.
5 #include "chrome/browser/ui/gtk/browser_titlebar.h"
7 #include <gdk/gdkkeysyms.h>
13 #include "base/command_line.h"
14 #include "base/i18n/rtl.h"
15 #include "base/memory/singleton.h"
16 #include "base/prefs/pref_service.h"
17 #include "base/strings/string_piece.h"
18 #include "base/strings/string_tokenizer.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "chrome/app/chrome_command_ids.h"
21 #include "chrome/browser/browser_process.h"
22 #include "chrome/browser/chrome_notification_types.h"
23 #include "chrome/browser/profiles/avatar_menu.h"
24 #include "chrome/browser/profiles/profile.h"
25 #include "chrome/browser/profiles/profile_info_cache.h"
26 #include "chrome/browser/profiles/profile_manager.h"
27 #include "chrome/browser/themes/theme_properties.h"
28 #include "chrome/browser/ui/browser.h"
29 #include "chrome/browser/ui/browser_commands.h"
30 #include "chrome/browser/ui/gtk/accelerators_gtk.h"
31 #include "chrome/browser/ui/gtk/avatar_menu_bubble_gtk.h"
32 #include "chrome/browser/ui/gtk/avatar_menu_button_gtk.h"
33 #include "chrome/browser/ui/gtk/browser_window_gtk.h"
34 #include "chrome/browser/ui/gtk/custom_button.h"
35 #if defined(USE_GCONF)
36 #include "chrome/browser/ui/gtk/gconf_titlebar_listener.h"
38 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
39 #include "chrome/browser/ui/gtk/gtk_util.h"
40 #include "chrome/browser/ui/gtk/menu_gtk.h"
41 #include "chrome/browser/ui/gtk/nine_box.h"
42 #include "chrome/browser/ui/gtk/tabs/tab_strip_gtk.h"
43 #include "chrome/browser/ui/gtk/unity_service.h"
44 #include "chrome/browser/ui/tabs/tab_strip_model.h"
45 #include "chrome/browser/ui/toolbar/encoding_menu_controller.h"
46 #include "chrome/browser/ui/toolbar/wrench_menu_model.h"
47 #include "chrome/common/chrome_switches.h"
48 #include "chrome/common/pref_names.h"
49 #include "content/public/browser/notification_service.h"
50 #include "content/public/browser/web_contents.h"
51 #include "grit/generated_resources.h"
52 #include "grit/theme_resources.h"
53 #include "grit/ui_resources.h"
54 #include "ui/base/gtk/gtk_hig_constants.h"
55 #include "ui/base/l10n/l10n_util.h"
56 #include "ui/base/resource/resource_bundle.h"
57 #include "ui/base/x/active_window_watcher_x.h"
58 #include "ui/gfx/image/image.h"
60 using content::WebContents
;
64 // The space above the titlebars.
65 const int kTitlebarHeight
= 14;
67 // The thickness in pixels of the tab border.
68 const int kTabOuterThickness
= 1;
70 // Amount to offset the tab images relative to the background.
71 const int kNormalVerticalOffset
= kTitlebarHeight
+ kTabOuterThickness
;
73 // A linux specific menu item for toggling window decorations.
74 const int kShowWindowDecorationsCommand
= 200;
76 const int kAvatarBottomSpacing
= 1;
77 // There are 2 px on each side of the avatar (between the frame border and
78 // it on the left, and between it and the tabstrip on the right).
79 const int kAvatarSideSpacing
= 2;
81 // There is a 4px gap between the icon and the title text.
82 const int kIconTitleSpacing
= 4;
84 // Padding around the icon when in app mode or popup mode.
85 const int kAppModePaddingTop
= 5;
86 const int kAppModePaddingBottom
= 4;
87 const int kAppModePaddingLeft
= 2;
89 // The left padding of the tab strip. In Views, the tab strip has a left
90 // margin of FrameBorderThickness + kClientEdgeThickness. This offset is to
91 // account for kClientEdgeThickness.
92 const int kTabStripLeftPadding
= 1;
94 // Spacing between buttons of the titlebar.
95 const int kButtonSpacing
= 2;
97 // Spacing around outside of titlebar buttons.
98 const int kButtonOuterPadding
= 2;
100 // Spacing between tabstrip and window control buttons (when the window is
102 const int kMaximizedTabstripPadding
= 16;
104 gboolean
OnMouseMoveEvent(GtkWidget
* widget
, GdkEventMotion
* event
,
105 BrowserWindowGtk
* browser_window
) {
106 // Reset to the default mouse cursor.
107 browser_window
->ResetCustomFrameCursor();
111 // Converts a GdkColor to a color_utils::HSL.
112 color_utils::HSL
GdkColorToHSL(const GdkColor
* color
) {
113 color_utils::HSL hsl
;
114 color_utils::SkColorToHSL(SkColorSetRGB(color
->red
>> 8,
116 color
->blue
>> 8), &hsl
);
120 // Returns either |one| or |two| based on which has a greater difference in
122 GdkColor
PickLuminosityContrastingColor(const GdkColor
* base
,
124 const GdkColor
* two
) {
125 // Convert all GdkColors to color_utils::HSLs.
126 color_utils::HSL baseHSL
= GdkColorToHSL(base
);
127 color_utils::HSL oneHSL
= GdkColorToHSL(one
);
128 color_utils::HSL twoHSL
= GdkColorToHSL(two
);
129 double one_difference
= fabs(baseHSL
.l
- oneHSL
.l
);
130 double two_difference
= fabs(baseHSL
.l
- twoHSL
.l
);
132 // Be biased towards the first color presented.
133 if (two_difference
> one_difference
+ 0.1)
141 ////////////////////////////////////////////////////////////////////////////////
142 // PopupPageMenuModel
144 // A menu model that builds the contents of the menu shown for popups (when the
145 // user clicks on the favicon) and all of its submenus.
146 class PopupPageMenuModel
: public ui::SimpleMenuModel
{
148 PopupPageMenuModel(ui::SimpleMenuModel::Delegate
* delegate
, Browser
* browser
);
149 virtual ~PopupPageMenuModel() { }
154 // Models for submenus referenced by this model. SimpleMenuModel only uses
155 // weak references so these must be kept for the lifetime of the top-level
157 scoped_ptr
<ZoomMenuModel
> zoom_menu_model_
;
158 scoped_ptr
<EncodingMenuModel
> encoding_menu_model_
;
159 Browser
* browser_
; // weak
161 DISALLOW_COPY_AND_ASSIGN(PopupPageMenuModel
);
164 PopupPageMenuModel::PopupPageMenuModel(
165 ui::SimpleMenuModel::Delegate
* delegate
,
167 : ui::SimpleMenuModel(delegate
), browser_(browser
) {
171 void PopupPageMenuModel::Build() {
172 AddItemWithStringId(IDC_BACK
, IDS_CONTENT_CONTEXT_BACK
);
173 AddItemWithStringId(IDC_FORWARD
, IDS_CONTENT_CONTEXT_FORWARD
);
174 AddItemWithStringId(IDC_RELOAD
, IDS_APP_MENU_RELOAD
);
175 AddSeparator(ui::NORMAL_SEPARATOR
);
176 AddItemWithStringId(IDC_SHOW_AS_TAB
, IDS_SHOW_AS_TAB
);
177 AddSeparator(ui::NORMAL_SEPARATOR
);
178 AddItemWithStringId(IDC_CUT
, IDS_CUT
);
179 AddItemWithStringId(IDC_COPY
, IDS_COPY
);
180 AddItemWithStringId(IDC_PASTE
, IDS_PASTE
);
181 AddSeparator(ui::NORMAL_SEPARATOR
);
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());
187 encoding_menu_model_
.reset(new EncodingMenuModel(browser_
));
188 AddSubMenuWithStringId(IDC_ENCODING_MENU
, IDS_ENCODING_MENU
,
189 encoding_menu_model_
.get());
191 AddSeparator(ui::NORMAL_SEPARATOR
);
192 AddItemWithStringId(IDC_CLOSE_WINDOW
, IDS_CLOSE
);
195 ////////////////////////////////////////////////////////////////////////////////
199 const char BrowserTitlebar::kDefaultButtonString
[] = ":minimize,maximize,close";
201 BrowserTitlebar::BrowserTitlebar(BrowserWindowGtk
* browser_window
,
203 : browser_window_(browser_window
),
206 container_hbox_(NULL
),
207 titlebar_left_buttons_vbox_(NULL
),
208 titlebar_right_buttons_vbox_(NULL
),
209 titlebar_left_buttons_hbox_(NULL
),
210 titlebar_right_buttons_hbox_(NULL
),
211 titlebar_left_avatar_frame_(NULL
),
212 titlebar_right_avatar_frame_(NULL
),
213 titlebar_left_label_frame_(NULL
),
214 titlebar_right_label_frame_(NULL
),
217 avatar_label_bg_(NULL
),
218 top_padding_left_(NULL
),
219 top_padding_right_(NULL
),
220 titlebar_alignment_(NULL
),
221 app_mode_favicon_(NULL
),
222 app_mode_title_(NULL
),
223 using_custom_frame_(false),
224 window_has_focus_(false),
225 display_avatar_on_left_(false),
226 theme_service_(NULL
) {
229 void BrowserTitlebar::Init() {
230 // The widget hierarchy is shown below.
232 // +- EventBox (container_) ------------------------------------------------+
233 // +- HBox (container_hbox_) -----------------------------------------------+
234 // |+ VBox ---++- Algn. -++- Alignment --------------++- Algn. -++ VBox ---+|
235 // || titlebar||titlebar || (titlebar_alignment_) ||titlebar || titlebar||
236 // || left ||left || ||right || right ||
237 // || button ||spy || ||spy || button ||
238 // || vbox ||frame ||+- TabStripGtk ---------+||frame || vbox ||
239 // || || || tab tab tabclose ||| || ||
240 // || || ||+------------------------+|| || ||
241 // |+---------++---------++--------------------------++---------++---------+|
242 // +------------------------------------------------------------------------+
244 // There are two vboxes on either side of |container_hbox_| because when the
245 // desktop is GNOME, the button placement is configurable based on a metacity
246 // gconf key. We can't just have one vbox and move it back and forth because
247 // the gconf language allows you to place buttons on both sides of the
248 // window. This being Linux, I'm sure there's a bunch of people who have
249 // already configured their window manager to do so and we don't want to get
250 // confused when that happens. The actual contents of these vboxes are lazily
251 // generated so they don't interfere with alignment when everything is
252 // stuffed in the other box.
254 // Each vbox has the following hierarchy if it contains buttons:
256 // +- VBox (titlebar_{l,r}_buttons_vbox_) ---------+
257 // |+- Fixed (top_padding_{l,r}_) ----------------+|
258 // ||+- HBox (titlebar_{l,r}_buttons_hbox_ ------+||
259 // ||| (buttons of a configurable layout) |||
260 // ||+-------------------------------------------+||
261 // |+---------------------------------------------+|
262 // +-----------------------------------------------+
264 // The two spy alignments are only allocated if this window is an incognito
265 // window. Only one of them holds the spy image.
267 // If we're a popup window or in app mode, we don't display the spy guy or
268 // the tab strip. Instead, put an hbox in titlebar_alignment_ in place of
270 // +- Alignment (titlebar_alignment_) -----------------------------------+
271 // |+ HBox -------------------------------------------------------------+|
272 // ||+- TabStripGtk -++- Image ----------------++- Label --------------+||
273 // ||| hidden ++ (app_mode_favicon_) || (app_mode_title_) |||
274 // ||| || favicon || page title |||
275 // ||+---------------++------------------------++----------------------+||
276 // |+-------------------------------------------------------------------+|
277 // +---------------------------------------------------------------------+
278 container_hbox_
= gtk_hbox_new(FALSE
, 0);
280 container_
= gtk_event_box_new();
281 gtk_widget_set_name(container_
, "chrome-browser-titlebar");
282 gtk_event_box_set_visible_window(GTK_EVENT_BOX(container_
), FALSE
);
283 gtk_container_add(GTK_CONTAINER(container_
), container_hbox_
);
285 g_signal_connect(container_
, "scroll-event", G_CALLBACK(OnScrollThunk
), this);
287 g_signal_connect(window_
, "window-state-event",
288 G_CALLBACK(OnWindowStateChangedThunk
), this);
290 // Allocate the two button boxes on the left and right parts of the bar. These
291 // are always allocated, but only displayed in incognito mode or when using
292 // multiple profiles.
293 titlebar_left_buttons_vbox_
= gtk_vbox_new(FALSE
, 0);
294 gtk_box_pack_start(GTK_BOX(container_hbox_
), titlebar_left_buttons_vbox_
,
296 titlebar_left_avatar_frame_
= gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
297 gtk_widget_set_no_show_all(titlebar_left_avatar_frame_
, TRUE
);
298 gtk_alignment_set_padding(GTK_ALIGNMENT(titlebar_left_avatar_frame_
), 0,
299 kAvatarBottomSpacing
, kAvatarSideSpacing
, kAvatarSideSpacing
);
300 gtk_box_pack_start(GTK_BOX(container_hbox_
), titlebar_left_avatar_frame_
,
303 titlebar_left_label_frame_
= gtk_alignment_new(0.0, 0.5, 0.0, 0.0);
304 gtk_widget_set_no_show_all(titlebar_left_label_frame_
, TRUE
);
305 gtk_alignment_set_padding(GTK_ALIGNMENT(titlebar_left_label_frame_
), 0,
306 kAvatarBottomSpacing
, kAvatarSideSpacing
, kAvatarSideSpacing
);
307 gtk_box_pack_start(GTK_BOX(container_hbox_
), titlebar_left_label_frame_
,
310 titlebar_right_avatar_frame_
= gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
311 gtk_widget_set_no_show_all(titlebar_right_avatar_frame_
, TRUE
);
312 gtk_alignment_set_padding(GTK_ALIGNMENT(titlebar_right_avatar_frame_
), 0,
313 kAvatarBottomSpacing
, kAvatarSideSpacing
, kAvatarSideSpacing
);
314 gtk_box_pack_end(GTK_BOX(container_hbox_
), titlebar_right_avatar_frame_
,
317 titlebar_right_label_frame_
= gtk_alignment_new(0.0, 0.5, 0.0, 0.0);
318 gtk_widget_set_no_show_all(titlebar_right_label_frame_
, TRUE
);
319 gtk_alignment_set_padding(GTK_ALIGNMENT(titlebar_right_label_frame_
), 0,
320 kAvatarBottomSpacing
, kAvatarSideSpacing
, kAvatarSideSpacing
);
321 gtk_box_pack_end(GTK_BOX(container_hbox_
), titlebar_right_label_frame_
,
324 titlebar_right_buttons_vbox_
= gtk_vbox_new(FALSE
, 0);
325 gtk_box_pack_end(GTK_BOX(container_hbox_
), titlebar_right_buttons_vbox_
,
328 // Create the Avatar button and listen for notifications. It must always be
329 // created because a new profile can be added at any time.
330 avatar_button_
.reset(new AvatarMenuButtonGtk(browser_window_
->browser()));
332 registrar_
.Add(this, chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED
,
333 content::NotificationService::AllSources());
335 #if defined(USE_GCONF)
336 // Either read the gconf database and register for updates (on GNOME), or use
337 // the default value (anywhere else).
338 GConfTitlebarListener::GetInstance()->SetTitlebarButtons(this);
340 BuildButtons(kDefaultButtonString
);
345 // We use an alignment to control the titlebar height.
346 titlebar_alignment_
= gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
347 if (browser_window_
->browser()->is_type_tabbed()) {
348 gtk_box_pack_start(GTK_BOX(container_hbox_
), titlebar_alignment_
, TRUE
,
351 // Put the tab strip in the titlebar.
352 gtk_container_add(GTK_CONTAINER(titlebar_alignment_
),
353 browser_window_
->tabstrip()->widget());
355 // App mode specific widgets.
356 gtk_box_pack_start(GTK_BOX(container_hbox_
), titlebar_alignment_
, TRUE
,
358 GtkWidget
* app_mode_hbox
= gtk_hbox_new(FALSE
, kIconTitleSpacing
);
359 gtk_container_add(GTK_CONTAINER(titlebar_alignment_
), app_mode_hbox
);
361 // Put the tab strip in the hbox even though we don't show it. Sometimes
362 // we need the position of the tab strip so make sure it's in our widget
364 gtk_box_pack_start(GTK_BOX(app_mode_hbox
),
365 browser_window_
->tabstrip()->widget(), FALSE
, FALSE
, 0);
367 GtkWidget
* favicon_event_box
= gtk_event_box_new();
368 gtk_event_box_set_visible_window(GTK_EVENT_BOX(favicon_event_box
), FALSE
);
369 g_signal_connect(favicon_event_box
, "button-press-event",
370 G_CALLBACK(OnFaviconMenuButtonPressedThunk
), this);
371 gtk_box_pack_start(GTK_BOX(app_mode_hbox
), favicon_event_box
, FALSE
,
373 // We use the app logo as a placeholder image so the title doesn't jump
375 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
376 app_mode_favicon_
= gtk_image_new_from_pixbuf(rb
.GetNativeImageNamed(
377 IDR_PRODUCT_LOGO_16
, ui::ResourceBundle::RTL_ENABLED
).ToGdkPixbuf());
378 g_object_set_data(G_OBJECT(app_mode_favicon_
), "left-align-popup",
379 reinterpret_cast<void*>(true));
380 gtk_container_add(GTK_CONTAINER(favicon_event_box
), app_mode_favicon_
);
382 app_mode_title_
= gtk_label_new(NULL
);
383 gtk_label_set_ellipsize(GTK_LABEL(app_mode_title_
), PANGO_ELLIPSIZE_END
);
384 gtk_misc_set_alignment(GTK_MISC(app_mode_title_
), 0.0, 0.5);
385 gtk_box_pack_start(GTK_BOX(app_mode_hbox
), app_mode_title_
, TRUE
, TRUE
,
388 UpdateTitleAndIcon();
391 theme_service_
= GtkThemeService::GetFrom(
392 browser_window_
->browser()->profile());
393 registrar_
.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED
,
394 content::Source
<ThemeService
>(theme_service_
));
395 theme_service_
->InitThemesFor(this);
397 gtk_widget_show_all(container_
);
399 ui::ActiveWindowWatcherX::AddObserver(this);
402 BrowserTitlebar::~BrowserTitlebar() {
403 ui::ActiveWindowWatcherX::RemoveObserver(this);
404 #if defined(USE_GCONF)
405 GConfTitlebarListener::GetInstance()->RemoveObserver(this);
409 void BrowserTitlebar::BuildButtons(const std::string
& button_string
) {
410 // Clear out all previous data.
411 close_button_
.reset();
412 restore_button_
.reset();
413 maximize_button_
.reset();
414 minimize_button_
.reset();
415 gtk_util::RemoveAllChildren(titlebar_left_buttons_vbox_
);
416 gtk_util::RemoveAllChildren(titlebar_right_buttons_vbox_
);
417 titlebar_left_buttons_hbox_
= NULL
;
418 titlebar_right_buttons_hbox_
= NULL
;
419 top_padding_left_
= NULL
;
420 top_padding_right_
= NULL
;
422 bool left_side
= true;
423 base::StringTokenizer
tokenizer(button_string
, ":,");
424 tokenizer
.set_options(base::StringTokenizer::RETURN_DELIMS
);
427 while (tokenizer
.GetNext()) {
428 if (tokenizer
.token_is_delim()) {
429 if (*tokenizer
.token_begin() == ':')
432 base::StringPiece token
= tokenizer
.token_piece();
433 if (BuildButton(token
.as_string(), left_side
))
434 (left_side
? left_count
: right_count
)++;
438 // If we are in incognito mode, add the spy guy to either the end of the left
439 // or the beginning of the right depending on which side has fewer buttons.
440 display_avatar_on_left_
= right_count
> left_count
;
442 // Now show the correct widgets in the two hierarchies.
443 if (using_custom_frame_
) {
444 gtk_widget_show_all(titlebar_left_buttons_vbox_
);
445 gtk_widget_show_all(titlebar_right_buttons_vbox_
);
447 UpdateMaximizeRestoreVisibility();
450 bool BrowserTitlebar::BuildButton(const std::string
& button_token
,
452 if (button_token
== "minimize") {
453 minimize_button_
.reset(CreateTitlebarButton("minimize", left_side
));
455 gtk_widget_size_request(minimize_button_
->widget(),
456 &minimize_button_req_
);
458 } else if (button_token
== "maximize") {
459 restore_button_
.reset(CreateTitlebarButton("unmaximize", left_side
));
460 maximize_button_
.reset(CreateTitlebarButton("maximize", left_side
));
462 gtk_util::SetButtonClickableByMouseButtons(maximize_button_
->widget(),
464 gtk_widget_size_request(restore_button_
->widget(),
465 &restore_button_req_
);
467 } else if (button_token
== "close") {
468 close_button_
.reset(CreateTitlebarButton("close", left_side
));
469 close_button_
->set_flipped(left_side
);
471 gtk_widget_size_request(close_button_
->widget(), &close_button_req_
);
474 // Ignore any other values like "pin" since we don't have images for
479 GtkWidget
* BrowserTitlebar::GetButtonHBox(bool left_side
) {
480 if (left_side
&& titlebar_left_buttons_hbox_
)
481 return titlebar_left_buttons_hbox_
;
482 else if (!left_side
&& titlebar_right_buttons_hbox_
)
483 return titlebar_right_buttons_hbox_
;
485 // We put the min/max/restore/close buttons in a vbox so they are top aligned
486 // (up to padding) and don't vertically stretch.
487 GtkWidget
* vbox
= left_side
? titlebar_left_buttons_vbox_
:
488 titlebar_right_buttons_vbox_
;
490 GtkWidget
* top_padding
= gtk_fixed_new();
491 gtk_widget_set_size_request(top_padding
, -1, kButtonOuterPadding
);
492 gtk_box_pack_start(GTK_BOX(vbox
), top_padding
, FALSE
, FALSE
, 0);
494 GtkWidget
* buttons_hbox
= gtk_hbox_new(FALSE
, kButtonSpacing
);
495 gtk_box_pack_start(GTK_BOX(vbox
), buttons_hbox
, FALSE
, FALSE
, 0);
498 titlebar_left_buttons_hbox_
= buttons_hbox
;
499 top_padding_left_
= top_padding
;
501 titlebar_right_buttons_hbox_
= buttons_hbox
;
502 top_padding_right_
= top_padding
;
508 CustomDrawButton
* BrowserTitlebar::CreateTitlebarButton(
509 const std::string
& button_name
, bool left_side
) {
511 int pressed_image_id
;
514 GetButtonResources(button_name
, &normal_image_id
, &pressed_image_id
,
515 &hover_image_id
, &tooltip_id
);
517 CustomDrawButton
* button
= new CustomDrawButton(normal_image_id
,
521 gtk_widget_add_events(GTK_WIDGET(button
->widget()), GDK_POINTER_MOTION_MASK
);
522 g_signal_connect(button
->widget(), "clicked",
523 G_CALLBACK(OnButtonClickedThunk
), this);
524 g_signal_connect(button
->widget(), "motion-notify-event",
525 G_CALLBACK(OnMouseMoveEvent
), browser_window_
);
527 std::string localized_tooltip
= l10n_util::GetStringUTF8(tooltip_id
);
528 gtk_widget_set_tooltip_text(button
->widget(),
529 localized_tooltip
.c_str());
531 GtkWidget
* box
= GetButtonHBox(left_side
);
532 gtk_box_pack_start(GTK_BOX(box
), button
->widget(), FALSE
, FALSE
, 0);
536 void BrowserTitlebar::GetButtonResources(const std::string
& button_name
,
537 int* normal_image_id
,
538 int* pressed_image_id
,
540 int* tooltip_id
) const {
541 if (button_name
== "close") {
542 *normal_image_id
= IDR_CLOSE
;
543 *pressed_image_id
= IDR_CLOSE_P
;
544 *hover_image_id
= IDR_CLOSE_H
;
545 *tooltip_id
= IDS_XPFRAME_CLOSE_TOOLTIP
;
546 } else if (button_name
== "minimize") {
547 *normal_image_id
= IDR_MINIMIZE
;
548 *pressed_image_id
= IDR_MINIMIZE_P
;
549 *hover_image_id
= IDR_MINIMIZE_H
;
550 *tooltip_id
= IDS_XPFRAME_MINIMIZE_TOOLTIP
;
551 } else if (button_name
== "maximize") {
552 *normal_image_id
= IDR_MAXIMIZE
;
553 *pressed_image_id
= IDR_MAXIMIZE_P
;
554 *hover_image_id
= IDR_MAXIMIZE_H
;
555 *tooltip_id
= IDS_XPFRAME_MAXIMIZE_TOOLTIP
;
556 } else if (button_name
== "unmaximize") {
557 *normal_image_id
= IDR_RESTORE
;
558 *pressed_image_id
= IDR_RESTORE_P
;
559 *hover_image_id
= IDR_RESTORE_H
;
560 *tooltip_id
= IDS_XPFRAME_RESTORE_TOOLTIP
;
564 void BrowserTitlebar::UpdateButtonBackground(CustomDrawButton
* button
) {
565 SkColor color
= theme_service_
->GetColor(
566 ThemeProperties::COLOR_BUTTON_BACKGROUND
);
567 SkBitmap background
= theme_service_
->GetImageNamed(
568 IDR_THEME_WINDOW_CONTROL_BACKGROUND
).AsBitmap();
570 // TODO(erg): For now, we just use a completely black mask and we can get
571 // away with this in the short term because our buttons are rectangles. We
572 // should get Glen to make properly hinted masks that match our min/max/close
573 // buttons (which have some odd alpha blending around the
574 // edges). http://crbug.com/103661
576 mask
.setConfig(SkBitmap::kARGB_8888_Config
,
577 button
->SurfaceWidth(), button
->SurfaceHeight(), 0);
579 mask
.eraseColor(SK_ColorBLACK
);
581 button
->SetBackground(color
, background
, mask
);
584 void BrowserTitlebar::UpdateCustomFrame(bool use_custom_frame
) {
585 using_custom_frame_
= use_custom_frame
;
586 if (!use_custom_frame
||
587 (browser_window_
->IsMaximized() && unity::IsRunning())) {
588 if (titlebar_left_buttons_vbox_
)
589 gtk_widget_hide(titlebar_left_buttons_vbox_
);
590 if (titlebar_right_buttons_vbox_
)
591 gtk_widget_hide(titlebar_right_buttons_vbox_
);
593 if (titlebar_left_buttons_vbox_
)
594 gtk_widget_show_all(titlebar_left_buttons_vbox_
);
595 if (titlebar_right_buttons_vbox_
)
596 gtk_widget_show_all(titlebar_right_buttons_vbox_
);
598 UpdateTitlebarAlignment();
599 UpdateMaximizeRestoreVisibility();
602 void BrowserTitlebar::UpdateTitleAndIcon() {
603 if (!app_mode_title_
)
606 // Get the page title and elide it to the available space.
607 base::string16 title
=
608 browser_window_
->browser()->GetWindowTitleForCurrentTab();
609 gtk_label_set_text(GTK_LABEL(app_mode_title_
),
610 base::UTF16ToUTF8(title
).c_str());
612 if (browser_window_
->browser()->is_app()) {
613 switch (browser_window_
->browser()->type()) {
614 case Browser::TYPE_POPUP
: {
615 // Update the system app icon. We don't need to update the icon in the
616 // top left of the custom frame, that will get updated when the
617 // throbber is updated.
618 Profile
* profile
= browser_window_
->browser()->profile();
619 gfx::Image icon
= browser_window_
->browser()->GetCurrentPageIcon();
620 if (icon
.IsEmpty()) {
621 gtk_util::SetWindowIcon(window_
, profile
);
623 gtk_util::SetWindowIcon(window_
, profile
, icon
.ToGdkPixbuf());
627 case Browser::TYPE_TABBED
: {
628 NOTREACHED() << "We should never have a tabbed app window.";
635 void BrowserTitlebar::UpdateThrobber(WebContents
* web_contents
) {
636 DCHECK(app_mode_favicon_
);
638 if (web_contents
&& web_contents
->IsLoading()) {
639 GdkPixbuf
* icon_pixbuf
=
640 throbber_
.GetNextFrame(web_contents
->IsWaitingForResponse());
641 gtk_image_set_from_pixbuf(GTK_IMAGE(app_mode_favicon_
), icon_pixbuf
);
643 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
645 // Note: we want to exclude the application popup/panel window.
646 if ((browser_window_
->browser()->is_app() &&
647 !browser_window_
->browser()->is_type_tabbed()) ||
648 browser_window_
->browser()->is_type_popup()) {
649 gfx::Image icon
= browser_window_
->browser()->GetCurrentPageIcon();
650 if (icon
.IsEmpty()) {
651 // Fallback to the Chromium icon if the page has no icon.
652 gtk_image_set_from_pixbuf(GTK_IMAGE(app_mode_favicon_
),
653 rb
.GetNativeImageNamed(IDR_PRODUCT_LOGO_16
).ToGdkPixbuf());
655 gtk_image_set_from_pixbuf(GTK_IMAGE(app_mode_favicon_
),
659 gtk_image_set_from_pixbuf(GTK_IMAGE(app_mode_favicon_
),
660 rb
.GetNativeImageNamed(IDR_PRODUCT_LOGO_16
).ToGdkPixbuf());
666 void BrowserTitlebar::UpdateTitlebarAlignment() {
667 if (browser_window_
->browser()->is_type_tabbed()) {
669 int side_padding
= 0;
670 int vertical_offset
= kNormalVerticalOffset
;
672 if (using_custom_frame_
) {
673 if (!browser_window_
->IsMaximized()) {
674 top_padding
= kTitlebarHeight
;
675 } else if (using_custom_frame_
&& browser_window_
->IsMaximized()) {
677 if (!unity::IsRunning())
678 side_padding
= kMaximizedTabstripPadding
;
682 int right_padding
= 0;
683 int left_padding
= kTabStripLeftPadding
;
684 if (titlebar_right_buttons_hbox_
)
685 right_padding
= side_padding
;
686 if (titlebar_left_buttons_hbox_
)
687 left_padding
= side_padding
;
689 gtk_alignment_set_padding(GTK_ALIGNMENT(titlebar_alignment_
),
691 left_padding
, right_padding
);
692 browser_window_
->tabstrip()->SetVerticalOffset(vertical_offset
);
694 if (using_custom_frame_
&& !browser_window_
->IsFullscreen()) {
695 gtk_alignment_set_padding(GTK_ALIGNMENT(titlebar_alignment_
),
696 kAppModePaddingTop
, kAppModePaddingBottom
, kAppModePaddingLeft
, 0);
697 gtk_widget_show(titlebar_alignment_
);
699 gtk_widget_hide(titlebar_alignment_
);
703 // Resize the buttons so that the clickable area extends all the way to the
704 // edge of the browser window.
705 GtkRequisition close_button_req
= close_button_req_
;
706 GtkRequisition minimize_button_req
= minimize_button_req_
;
707 GtkRequisition restore_button_req
= restore_button_req_
;
708 if (using_custom_frame_
&& browser_window_
->IsMaximized()) {
709 close_button_req
.width
+= kButtonOuterPadding
;
710 close_button_req
.height
+= kButtonOuterPadding
;
711 minimize_button_req
.height
+= kButtonOuterPadding
;
712 restore_button_req
.height
+= kButtonOuterPadding
;
713 if (top_padding_left_
)
714 gtk_widget_hide(top_padding_left_
);
715 if (top_padding_right_
)
716 gtk_widget_hide(top_padding_right_
);
718 if (top_padding_left_
)
719 gtk_widget_show(top_padding_left_
);
720 if (top_padding_right_
)
721 gtk_widget_show(top_padding_right_
);
723 if (close_button_
.get()) {
724 gtk_widget_set_size_request(close_button_
->widget(),
725 close_button_req
.width
,
726 close_button_req
.height
);
728 if (minimize_button_
.get()) {
729 gtk_widget_set_size_request(minimize_button_
->widget(),
730 minimize_button_req
.width
,
731 minimize_button_req
.height
);
733 if (maximize_button_
.get()) {
734 gtk_widget_set_size_request(restore_button_
->widget(),
735 restore_button_req
.width
,
736 restore_button_req
.height
);
740 void BrowserTitlebar::UpdateTextColor() {
741 if (!app_mode_title_
)
744 if (theme_service_
&& theme_service_
->UsingNativeTheme()) {
745 // We don't really have any good options here.
747 // Colors from window manager themes aren't exposed in GTK; the window
748 // manager is a separate component and when there is information sharing
749 // (in the case of metacity), it's one way where the window manager reads
750 // data from the GTK theme (which allows us to do a decent job with
751 // picking the frame color).
753 // We probably won't match in the majority of cases, but we can at the
754 // very least make things legible. The default metacity and xfwm themes
755 // on ubuntu have white text hardcoded. Determine whether black or white
756 // has more luminosity contrast and then set that color as the text
758 GdkColor frame_color
;
759 if (window_has_focus_
) {
760 frame_color
= theme_service_
->GetGdkColor(
761 ThemeProperties::COLOR_FRAME
);
763 frame_color
= theme_service_
->GetGdkColor(
764 ThemeProperties::COLOR_FRAME_INACTIVE
);
766 GdkColor text_color
= PickLuminosityContrastingColor(
767 &frame_color
, &ui::kGdkWhite
, &ui::kGdkBlack
);
768 gtk_util::SetLabelColor(app_mode_title_
, &text_color
);
770 gtk_util::SetLabelColor(app_mode_title_
, &ui::kGdkWhite
);
774 void BrowserTitlebar::UpdateAvatarLabel() {
775 if (theme_service_
&& avatar_label_
) {
776 GdkColor text_color
=
777 theme_service_
->GetGdkColor(ThemeProperties::COLOR_MANAGED_USER_LABEL
);
778 GdkColor label_background
= theme_service_
->GetGdkColor(
779 ThemeProperties::COLOR_MANAGED_USER_LABEL_BACKGROUND
);
780 gtk_util::SetLabelColor(avatar_label_
, &text_color
);
781 gtk_widget_modify_bg(
782 GTK_WIDGET(avatar_label_bg_
), GTK_STATE_NORMAL
, &label_background
);
783 char* markup
= g_markup_printf_escaped(
784 "<span size='small'>%s</span>",
785 l10n_util::GetStringUTF8(IDS_MANAGED_USER_AVATAR_LABEL
).c_str());
786 gtk_label_set_markup(GTK_LABEL(avatar_label_
), markup
);
791 void BrowserTitlebar::UpdateAvatar() {
792 // Remove previous state.
793 gtk_util::RemoveAllChildren(titlebar_left_avatar_frame_
);
794 gtk_util::RemoveAllChildren(titlebar_right_avatar_frame_
);
795 gtk_util::RemoveAllChildren(titlebar_left_label_frame_
);
796 gtk_util::RemoveAllChildren(titlebar_right_label_frame_
);
798 if (!ShouldDisplayAvatar())
802 if (IsOffTheRecord()) {
803 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
804 gfx::Image avatar_image
=
805 rb
.GetNativeImageNamed(IDR_OTR_ICON
, ui::ResourceBundle::RTL_ENABLED
);
806 avatar_
= gtk_image_new_from_pixbuf(avatar_image
.ToGdkPixbuf());
807 gtk_misc_set_alignment(GTK_MISC(avatar_
), 0.0, 1.0);
808 gtk_widget_set_size_request(avatar_
, -1, 0);
810 // Use a clickable avatar.
811 avatar_
= avatar_button_
->widget();
815 gtk_widget_show_all(avatar_
);
817 Profile
* profile
= browser_window_
->browser()->profile();
818 if (profile
->IsManaged()) {
819 avatar_label_
= gtk_label_new(NULL
);
820 gtk_misc_set_padding(GTK_MISC(avatar_label_
), 10, 2);
821 avatar_label_bg_
= gtk_event_box_new();
822 gtk_container_add(GTK_CONTAINER(avatar_label_bg_
), avatar_label_
);
823 g_signal_connect(avatar_label_bg_
, "button-press-event",
824 G_CALLBACK(OnAvatarLabelButtonPressedThunk
), this);
826 gtk_widget_show(avatar_label_bg_
);
827 gtk_widget_show(avatar_label_
);
828 if (display_avatar_on_left_
) {
829 gtk_container_add(GTK_CONTAINER(titlebar_left_label_frame_
),
831 gtk_widget_show(titlebar_left_label_frame_
);
832 gtk_widget_hide(titlebar_right_label_frame_
);
834 gtk_container_add(GTK_CONTAINER(titlebar_right_label_frame_
),
836 gtk_widget_show(titlebar_right_label_frame_
);
837 gtk_widget_hide(titlebar_left_label_frame_
);
841 if (display_avatar_on_left_
) {
842 gtk_container_add(GTK_CONTAINER(titlebar_left_avatar_frame_
), avatar_
);
843 gtk_widget_show(titlebar_left_avatar_frame_
);
844 gtk_widget_hide(titlebar_right_avatar_frame_
);
846 gtk_container_add(GTK_CONTAINER(titlebar_right_avatar_frame_
), avatar_
);
847 gtk_widget_show(titlebar_right_avatar_frame_
);
848 gtk_widget_hide(titlebar_left_avatar_frame_
);
851 if (IsOffTheRecord())
854 bool is_gaia_picture
= false;
856 ProfileInfoCache
& cache
=
857 g_browser_process
->profile_manager()->GetProfileInfoCache();
858 size_t index
= cache
.GetIndexOfProfileWithPath(profile
->GetPath());
859 if (index
== std::string::npos
)
863 cache
.IsUsingGAIAPictureOfProfileAtIndex(index
) &&
864 cache
.GetGAIAPictureOfProfileAtIndex(index
);
865 avatar
= cache
.GetAvatarIconOfProfileAtIndex(index
);
866 avatar_button_
->SetIcon(avatar
, is_gaia_picture
);
867 avatar_button_
->set_menu_frame_style(display_avatar_on_left_
?
868 BubbleGtk::ANCHOR_TOP_LEFT
: BubbleGtk::ANCHOR_TOP_RIGHT
);
871 void BrowserTitlebar::MaximizeButtonClicked() {
872 GdkEvent
* event
= gtk_get_current_event();
873 if (event
->button
.button
== 1) {
874 gtk_window_maximize(window_
);
876 GtkWidget
* widget
= GTK_WIDGET(window_
);
877 GdkScreen
* screen
= gtk_widget_get_screen(widget
);
878 gint monitor
= gdk_screen_get_monitor_at_window(
879 screen
, gtk_widget_get_window(widget
));
880 GdkRectangle screen_rect
;
881 gdk_screen_get_monitor_geometry(screen
, monitor
, &screen_rect
);
884 gtk_window_get_position(window_
, &x
, &y
);
886 GtkAllocation allocation
;
887 gtk_widget_get_allocation(widget
, &allocation
);
888 gint width
= allocation
.width
;
889 gint height
= allocation
.height
;
891 if (event
->button
.button
== 3) {
893 width
= screen_rect
.width
;
894 } else if (event
->button
.button
== 2) {
896 height
= screen_rect
.height
;
899 browser_window_
->SetBounds(gfx::Rect(x
, y
, width
, height
));
901 gdk_event_free(event
);
904 void BrowserTitlebar::UpdateMaximizeRestoreVisibility() {
905 if (maximize_button_
.get()) {
906 if (browser_window_
->IsMaximized()) {
907 gtk_widget_hide(maximize_button_
->widget());
908 gtk_widget_show(restore_button_
->widget());
910 gtk_widget_hide(restore_button_
->widget());
911 gtk_widget_show(maximize_button_
->widget());
916 gboolean
BrowserTitlebar::OnWindowStateChanged(GtkWindow
* window
,
917 GdkEventWindowState
* event
) {
918 UpdateMaximizeRestoreVisibility();
919 UpdateTitlebarAlignment();
924 gboolean
BrowserTitlebar::OnScroll(GtkWidget
* widget
, GdkEventScroll
* event
) {
925 Browser
* browser
= browser_window_
->browser();
926 int index
= browser
->tab_strip_model()->active_index();
927 if (event
->direction
== GDK_SCROLL_LEFT
||
928 event
->direction
== GDK_SCROLL_UP
) {
930 chrome::SelectPreviousTab(browser
);
931 } else if (index
+ 1 < browser
->tab_strip_model()->count()) {
932 chrome::SelectNextTab(browser
);
937 void BrowserTitlebar::OnButtonClicked(GtkWidget
* button
) {
938 if (close_button_
.get() && close_button_
->widget() == button
) {
939 browser_window_
->Close();
940 } else if (restore_button_
.get() && restore_button_
->widget() == button
) {
941 browser_window_
->UnMaximize();
942 } else if (maximize_button_
.get() && maximize_button_
->widget() == button
) {
943 MaximizeButtonClicked();
944 } else if (minimize_button_
.get() && minimize_button_
->widget() == button
) {
945 gtk_window_iconify(window_
);
949 gboolean
BrowserTitlebar::OnFaviconMenuButtonPressed(GtkWidget
* widget
,
950 GdkEventButton
* event
) {
951 if (event
->button
!= 1)
954 if (!favicon_menu_model_
.get()) {
955 favicon_menu_model_
.reset(
956 new PopupPageMenuModel(this, browser_window_
->browser()));
958 favicon_menu_
.reset(new MenuGtk(NULL
, favicon_menu_model_
.get()));
961 favicon_menu_
->PopupForWidget(app_mode_favicon_
, event
->button
, event
->time
);
966 gboolean
BrowserTitlebar::OnAvatarLabelButtonPressed(GtkWidget
* widget
,
967 GdkEventButton
* event
) {
968 if (event
->button
!= 1)
971 // Show the avatar menu bubble with the upward arrow at the x position where
972 // the user has clicked.
973 gfx::Rect rect
= gtk_util::WidgetBounds(widget
);
974 rect
.set_x(event
->x
);
976 new AvatarMenuBubbleGtk(
977 browser_window_
->browser(), widget
, BubbleGtk::ANCHOR_TOP_RIGHT
, &rect
);
981 void BrowserTitlebar::ShowContextMenu(GdkEventButton
* event
) {
982 if (!context_menu_
.get()) {
983 context_menu_model_
.reset(new ContextMenuModel(this));
984 context_menu_
.reset(new MenuGtk(NULL
, context_menu_model_
.get()));
987 context_menu_
->PopupAsContext(gfx::Point(event
->x_root
, event
->y_root
),
991 bool BrowserTitlebar::IsCommandIdEnabled(int command_id
) const {
992 if (command_id
== kShowWindowDecorationsCommand
)
995 return chrome::IsCommandEnabled(browser_window_
->browser(), command_id
);
998 bool BrowserTitlebar::IsCommandIdChecked(int command_id
) const {
999 if (command_id
== kShowWindowDecorationsCommand
) {
1000 PrefService
* prefs
= browser_window_
->browser()->profile()->GetPrefs();
1001 return !prefs
->GetBoolean(prefs::kUseCustomChromeFrame
);
1004 EncodingMenuController controller
;
1005 if (controller
.DoesCommandBelongToEncodingMenu(command_id
)) {
1006 WebContents
* web_contents
=
1007 browser_window_
->browser()->tab_strip_model()->GetActiveWebContents();
1009 return controller
.IsItemChecked(browser_window_
->browser()->profile(),
1010 web_contents
->GetEncoding(),
1020 void BrowserTitlebar::ExecuteCommand(int command_id
, int event_flags
) {
1021 if (command_id
== kShowWindowDecorationsCommand
) {
1022 PrefService
* prefs
= browser_window_
->browser()->profile()->GetPrefs();
1023 prefs
->SetBoolean(prefs::kUseCustomChromeFrame
,
1024 !prefs
->GetBoolean(prefs::kUseCustomChromeFrame
));
1028 chrome::ExecuteCommand(browser_window_
->browser(), command_id
);
1031 bool BrowserTitlebar::GetAcceleratorForCommandId(
1033 ui::Accelerator
* out_accelerator
) {
1034 const ui::Accelerator
* accelerator
=
1035 AcceleratorsGtk::GetInstance()->GetPrimaryAcceleratorForCommand(
1039 *out_accelerator
= *accelerator
;
1043 void BrowserTitlebar::Observe(int type
,
1044 const content::NotificationSource
& source
,
1045 const content::NotificationDetails
& details
) {
1047 case chrome::NOTIFICATION_BROWSER_THEME_CHANGED
: {
1049 UpdateAvatarLabel();
1051 if (minimize_button_
.get())
1052 UpdateButtonBackground(minimize_button_
.get());
1053 if (maximize_button_
.get())
1054 UpdateButtonBackground(maximize_button_
.get());
1055 if (restore_button_
.get())
1056 UpdateButtonBackground(restore_button_
.get());
1057 if (close_button_
.get())
1058 UpdateButtonBackground(close_button_
.get());
1062 case chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED
:
1063 if (!IsOffTheRecord())
1072 void BrowserTitlebar::ActiveWindowChanged(GdkWindow
* active_window
) {
1073 // Can be called during shutdown; BrowserWindowGtk will set our |window_|
1074 // to NULL during that time.
1079 gtk_widget_get_window(GTK_WIDGET(window_
)) == active_window
;
1083 bool BrowserTitlebar::ShouldDisplayAvatar() {
1084 if (IsOffTheRecord())
1087 if (!browser_window_
->browser()->is_type_tabbed())
1090 return AvatarMenu::ShouldShowAvatarMenu();
1093 bool BrowserTitlebar::IsOffTheRecord() {
1094 return browser_window_
->browser()->profile()->IsOffTheRecord();
1097 BrowserTitlebar::ContextMenuModel::ContextMenuModel(
1098 ui::SimpleMenuModel::Delegate
* delegate
)
1099 : SimpleMenuModel(delegate
) {
1100 AddItemWithStringId(IDC_NEW_TAB
, IDS_TAB_CXMENU_NEWTAB
);
1101 AddItemWithStringId(IDC_RESTORE_TAB
, IDS_RESTORE_TAB
);
1102 AddSeparator(ui::NORMAL_SEPARATOR
);
1103 AddItemWithStringId(IDC_TASK_MANAGER
, IDS_TASK_MANAGER
);
1104 AddSeparator(ui::NORMAL_SEPARATOR
);
1105 AddCheckItemWithStringId(kShowWindowDecorationsCommand
,
1106 IDS_SHOW_WINDOW_DECORATIONS_MENU
);