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/avatar_menu_bubble_gtk.h"
7 #include "base/i18n/rtl.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "chrome/browser/browser_process.h"
10 #include "chrome/browser/chrome_notification_types.h"
11 #include "chrome/browser/profiles/avatar_menu.h"
12 #include "chrome/browser/profiles/profile_info_cache.h"
13 #include "chrome/browser/profiles/profile_manager.h"
14 #include "chrome/browser/ui/browser.h"
15 #include "chrome/browser/ui/gtk/avatar_menu_item_gtk.h"
16 #include "chrome/browser/ui/gtk/browser_toolbar_gtk.h"
17 #include "chrome/browser/ui/gtk/browser_window_gtk.h"
18 #include "chrome/browser/ui/gtk/event_utils.h"
19 #include "chrome/browser/ui/gtk/gtk_chrome_link_button.h"
20 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
21 #include "chrome/browser/ui/gtk/location_bar_view_gtk.h"
22 #include "content/public/browser/notification_source.h"
23 #include "grit/generated_resources.h"
24 #include "ui/base/gtk/gtk_hig_constants.h"
25 #include "ui/base/l10n/l10n_util.h"
29 // The minimum width in pixels of the bubble.
30 const int kBubbleMinWidth
= 175;
32 // The number of pixels of padding on the left of the 'New Profile' link at the
33 // bottom of the bubble.
34 const int kNewProfileLinkLeftPadding
= 40;
38 AvatarMenuBubbleGtk::AvatarMenuBubbleGtk(Browser
* browser
,
40 BubbleGtk::FrameStyle arrow
,
41 const gfx::Rect
* rect
)
43 inner_contents_(NULL
),
44 theme_service_(GtkThemeService::GetFrom(browser
->profile())),
45 new_profile_link_(NULL
),
46 minimum_width_(kBubbleMinWidth
),
48 avatar_menu_
.reset(new AvatarMenu(
49 &g_browser_process
->profile_manager()->GetProfileInfoCache(),
51 avatar_menu_
->RebuildMenu();
53 OnAvatarMenuChanged(avatar_menu_
.get());
55 bubble_
= BubbleGtk::Show(anchor
,
59 BubbleGtk::MATCH_SYSTEM_THEME
|
60 BubbleGtk::POPUP_WINDOW
|
61 BubbleGtk::GRAB_INPUT
,
64 g_signal_connect(contents_
, "destroy",
65 G_CALLBACK(&OnDestroyThunk
), this);
68 AvatarMenuBubbleGtk::~AvatarMenuBubbleGtk() {}
70 void AvatarMenuBubbleGtk::OnDestroy(GtkWidget
* widget
) {
71 // We are self deleting, we have a destroy signal setup to catch when we
72 // destroyed (via the BubbleGtk being destroyed), and delete ourself.
73 base::MessageLoop::current()->DeleteSoon(FROM_HERE
, this);
76 void AvatarMenuBubbleGtk::BubbleClosing(BubbleGtk
* bubble
,
77 bool closed_by_escape
) {
81 void AvatarMenuBubbleGtk::OnAvatarMenuChanged(
82 AvatarMenu
* avatar_menu
) {
84 minimum_width_
= kBubbleMinWidth
;
89 void AvatarMenuBubbleGtk::OpenProfile(size_t profile_index
) {
92 GdkModifierType modifier_state
;
93 gtk_get_current_event_state(&modifier_state
);
94 guint modifier_state_uint
= modifier_state
;
95 avatar_menu_
->SwitchToProfile(profile_index
,
96 event_utils::DispositionFromGdkState(modifier_state_uint
) == NEW_WINDOW
,
97 ProfileMetrics::SWITCH_PROFILE_ICON
);
101 void AvatarMenuBubbleGtk::EditProfile(size_t profile_index
) {
104 avatar_menu_
->EditProfile(profile_index
);
108 void AvatarMenuBubbleGtk::OnSizeRequest(GtkWidget
* widget
,
109 GtkRequisition
* req
) {
110 // Always use the maximum width ever requested.
111 if (req
->width
< minimum_width_
)
112 req
->width
= minimum_width_
;
114 minimum_width_
= req
->width
;
117 void AvatarMenuBubbleGtk::OnNewProfileLinkClicked(GtkWidget
* link
) {
120 avatar_menu_
->AddNewProfile(ProfileMetrics::ADD_NEW_USER_ICON
);
124 void AvatarMenuBubbleGtk::OnSwitchProfileLinkClicked(GtkWidget
* link
) {
126 OnAvatarMenuChanged(avatar_menu_
.get());
129 void AvatarMenuBubbleGtk::InitMenuContents() {
130 size_t profile_count
= avatar_menu_
->GetNumberOfItems();
131 GtkWidget
* items_vbox
= gtk_vbox_new(FALSE
, ui::kContentAreaSpacing
);
132 for (size_t i
= 0; i
< profile_count
; ++i
) {
133 AvatarMenu::Item menu_item
= avatar_menu_
->GetItemAt(i
);
134 AvatarMenuItemGtk
* item
= new AvatarMenuItemGtk(
135 this, menu_item
, i
, theme_service_
);
136 items_
.push_back(item
);
138 gtk_box_pack_start(GTK_BOX(items_vbox
), item
->widget(), TRUE
, TRUE
, 0);
139 gtk_widget_set_can_focus(item
->widget(), TRUE
);
140 if (menu_item
.active
)
141 gtk_container_set_focus_child(GTK_CONTAINER(items_vbox
), item
->widget());
143 gtk_box_pack_start(GTK_BOX(inner_contents_
), items_vbox
, TRUE
, TRUE
, 0);
145 if (avatar_menu_
->ShouldShowAddNewProfileLink()) {
146 gtk_box_pack_start(GTK_BOX(inner_contents_
),
147 gtk_hseparator_new(), TRUE
, TRUE
, 0);
149 // The new profile link.
150 new_profile_link_
= theme_service_
->BuildChromeLinkButton(
151 l10n_util::GetStringUTF8(IDS_PROFILES_CREATE_NEW_PROFILE_LINK
));
152 g_signal_connect(new_profile_link_
, "clicked",
153 G_CALLBACK(OnNewProfileLinkClickedThunk
), this);
155 GtkWidget
* link_align
= gtk_alignment_new(0, 0, 0, 0);
156 gtk_alignment_set_padding(GTK_ALIGNMENT(link_align
),
157 0, 0, kNewProfileLinkLeftPadding
, 0);
158 gtk_container_add(GTK_CONTAINER(link_align
), new_profile_link_
);
160 gtk_box_pack_start(GTK_BOX(inner_contents_
), link_align
, FALSE
, FALSE
, 0);
164 void AvatarMenuBubbleGtk::InitManagedUserContents() {
165 int active_index
= avatar_menu_
->GetActiveProfileIndex();
166 AvatarMenu::Item menu_item
=
167 avatar_menu_
->GetItemAt(active_index
);
168 AvatarMenuItemGtk
* item
= new AvatarMenuItemGtk(
169 this, menu_item
, active_index
, theme_service_
);
170 items_
.push_back(item
);
172 gtk_box_pack_start(GTK_BOX(inner_contents_
), item
->widget(), TRUE
, TRUE
, 0);
173 gtk_box_pack_start(GTK_BOX(inner_contents_
),
174 gtk_hseparator_new(), TRUE
, TRUE
, 0);
176 // Add information about managed users within a hbox.
177 GtkWidget
* managed_user_info
= gtk_hbox_new(FALSE
, 5);
178 GdkPixbuf
* limited_user_pixbuf
=
179 avatar_menu_
->GetManagedUserIcon().ToGdkPixbuf();
180 GtkWidget
* limited_user_img
= gtk_image_new_from_pixbuf(limited_user_pixbuf
);
181 GtkWidget
* icon_align
= gtk_alignment_new(0, 0, 0, 0);
182 gtk_container_add(GTK_CONTAINER(icon_align
), limited_user_img
);
183 gtk_box_pack_start(GTK_BOX(managed_user_info
), icon_align
, FALSE
, FALSE
, 0);
184 GtkWidget
* status_label
=
185 theme_service_
->BuildLabel(std::string(), ui::kGdkBlack
);
186 char* markup
= g_markup_printf_escaped(
187 "<span size='small'>%s</span>",
188 base::UTF16ToUTF8(avatar_menu_
->GetManagedUserInformation()).c_str());
189 const int kLabelWidth
= 150;
190 gtk_widget_set_size_request(status_label
, kLabelWidth
, -1);
191 gtk_label_set_markup(GTK_LABEL(status_label
), markup
);
192 gtk_label_set_line_wrap(GTK_LABEL(status_label
), TRUE
);
193 gtk_misc_set_alignment(GTK_MISC(status_label
), 0, 0);
195 gtk_box_pack_start(GTK_BOX(managed_user_info
), status_label
, FALSE
, FALSE
, 0);
197 GTK_BOX(inner_contents_
), managed_user_info
, FALSE
, FALSE
, 0);
200 GTK_BOX(inner_contents_
), gtk_hseparator_new(), TRUE
, TRUE
, 0);
202 // The switch profile link.
203 GtkWidget
* switch_profile_link
= theme_service_
->BuildChromeLinkButton(
204 l10n_util::GetStringUTF8(IDS_PROFILES_SWITCH_PROFILE_LINK
));
205 g_signal_connect(switch_profile_link
, "clicked",
206 G_CALLBACK(OnSwitchProfileLinkClickedThunk
), this);
208 GtkWidget
* link_align
= gtk_alignment_new(0.5, 0, 0, 0);
209 gtk_container_add(GTK_CONTAINER(link_align
), switch_profile_link
);
211 gtk_box_pack_start(GTK_BOX(inner_contents_
), link_align
, FALSE
, FALSE
, 0);
214 void AvatarMenuBubbleGtk::InitContents() {
215 // Destroy the old inner contents to allow replacing it.
217 gtk_widget_destroy(inner_contents_
);
218 inner_contents_
= gtk_vbox_new(FALSE
, ui::kControlSpacing
);
220 contents_
= gtk_vbox_new(FALSE
, 0);
221 gtk_container_set_border_width(GTK_CONTAINER(inner_contents_
),
222 ui::kContentAreaBorder
);
223 g_signal_connect(inner_contents_
, "size-request",
224 G_CALLBACK(OnSizeRequestThunk
), this);
226 if (avatar_menu_
->GetManagedUserInformation().empty() || switching_
)
229 InitManagedUserContents();
230 gtk_box_pack_start(GTK_BOX(contents_
), inner_contents_
, TRUE
, TRUE
, 0);
232 gtk_widget_show_all(contents_
);
235 void AvatarMenuBubbleGtk::CloseBubble() {