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. 4 5 #include "chrome/browser/ui/gtk/avatar_menu_bubble_gtk.h" 6 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_model.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" 26 27 namespace { 28 29 // The minimum width in pixels of the bubble. 30 const int kBubbleMinWidth = 175; 31 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; 35 36 } // namespace 37 38 AvatarMenuBubbleGtk::AvatarMenuBubbleGtk(Browser* browser, 39 GtkWidget* anchor, 40 BubbleGtk::FrameStyle arrow, 41 const gfx::Rect* rect) 42 : contents_(NULL), 43 inner_contents_(NULL), 44 theme_service_(GtkThemeService::GetFrom(browser->profile())), 45 new_profile_link_(NULL), 46 minimum_width_(kBubbleMinWidth), 47 switching_(false) { 48 avatar_menu_model_.reset(new AvatarMenuModel( 49 &g_browser_process->profile_manager()->GetProfileInfoCache(), 50 this, browser)); 51 52 OnAvatarMenuModelChanged(avatar_menu_model_.get()); 53 54 bubble_ = BubbleGtk::Show(anchor, 55 rect, 56 contents_, 57 arrow, 58 BubbleGtk::MATCH_SYSTEM_THEME | 59 BubbleGtk::POPUP_WINDOW | 60 BubbleGtk::GRAB_INPUT, 61 theme_service_, 62 this); // |delegate| 63 g_signal_connect(contents_, "destroy", 64 G_CALLBACK(&OnDestroyThunk), this); 65 } 66 67 AvatarMenuBubbleGtk::~AvatarMenuBubbleGtk() {} 68 69 void AvatarMenuBubbleGtk::OnDestroy(GtkWidget* widget) { 70 // We are self deleting, we have a destroy signal setup to catch when we 71 // destroyed (via the BubbleGtk being destroyed), and delete ourself. 72 base::MessageLoop::current()->DeleteSoon(FROM_HERE, this); 73 } 74 75 void AvatarMenuBubbleGtk::BubbleClosing(BubbleGtk* bubble, 76 bool closed_by_escape) { 77 bubble_ = NULL; 78 } 79 80 void AvatarMenuBubbleGtk::OnAvatarMenuModelChanged( 81 AvatarMenuModel* avatar_menu_model) { 82 items_.clear(); 83 minimum_width_ = kBubbleMinWidth; 84 85 InitContents(); 86 } 87 88 void AvatarMenuBubbleGtk::OpenProfile(size_t profile_index) { 89 if (!bubble_) 90 return; 91 GdkModifierType modifier_state; 92 gtk_get_current_event_state(&modifier_state); 93 guint modifier_state_uint = modifier_state; 94 avatar_menu_model_->SwitchToProfile(profile_index, 95 event_utils::DispositionFromGdkState(modifier_state_uint) == NEW_WINDOW); 96 CloseBubble(); 97 } 98 99 void AvatarMenuBubbleGtk::EditProfile(size_t profile_index) { 100 if (!bubble_) 101 return; 102 avatar_menu_model_->EditProfile(profile_index); 103 CloseBubble(); 104 } 105 106 void AvatarMenuBubbleGtk::OnSizeRequest(GtkWidget* widget, 107 GtkRequisition* req) { 108 // Always use the maximum width ever requested. 109 if (req->width < minimum_width_) 110 req->width = minimum_width_; 111 else 112 minimum_width_ = req->width; 113 } 114 115 void AvatarMenuBubbleGtk::OnNewProfileLinkClicked(GtkWidget* link) { 116 if (!bubble_) 117 return; 118 avatar_menu_model_->AddNewProfile(ProfileMetrics::ADD_NEW_USER_ICON); 119 CloseBubble(); 120 } 121 122 void AvatarMenuBubbleGtk::OnSwitchProfileLinkClicked(GtkWidget* link) { 123 switching_ = true; 124 OnAvatarMenuModelChanged(avatar_menu_model_.get()); 125 } 126 127 void AvatarMenuBubbleGtk::InitMenuContents() { 128 size_t profile_count = avatar_menu_model_->GetNumberOfItems(); 129 GtkWidget* items_vbox = gtk_vbox_new(FALSE, ui::kContentAreaSpacing); 130 for (size_t i = 0; i < profile_count; ++i) { 131 AvatarMenuModel::Item menu_item = avatar_menu_model_->GetItemAt(i); 132 AvatarMenuItemGtk* item = new AvatarMenuItemGtk( 133 this, menu_item, i, theme_service_); 134 items_.push_back(item); 135 136 gtk_box_pack_start(GTK_BOX(items_vbox), item->widget(), TRUE, TRUE, 0); 137 gtk_widget_set_can_focus(item->widget(), TRUE); 138 if (menu_item.active) 139 gtk_container_set_focus_child(GTK_CONTAINER(items_vbox), item->widget()); 140 } 141 gtk_box_pack_start(GTK_BOX(inner_contents_), items_vbox, TRUE, TRUE, 0); 142 143 if (avatar_menu_model_->ShouldShowAddNewProfileLink()) { 144 gtk_box_pack_start(GTK_BOX(inner_contents_), 145 gtk_hseparator_new(), TRUE, TRUE, 0); 146 147 // The new profile link. 148 new_profile_link_ = theme_service_->BuildChromeLinkButton( 149 l10n_util::GetStringUTF8(IDS_PROFILES_CREATE_NEW_PROFILE_LINK)); 150 g_signal_connect(new_profile_link_, "clicked", 151 G_CALLBACK(OnNewProfileLinkClickedThunk), this); 152 153 GtkWidget* link_align = gtk_alignment_new(0, 0, 0, 0); 154 gtk_alignment_set_padding(GTK_ALIGNMENT(link_align), 155 0, 0, kNewProfileLinkLeftPadding, 0); 156 gtk_container_add(GTK_CONTAINER(link_align), new_profile_link_); 157 158 gtk_box_pack_start(GTK_BOX(inner_contents_), link_align, FALSE, FALSE, 0); 159 } 160 161 } 162 163 void AvatarMenuBubbleGtk::InitManagedUserContents() { 164 int active_index = avatar_menu_model_->GetActiveProfileIndex(); 165 AvatarMenuModel::Item menu_item = 166 avatar_menu_model_->GetItemAt(active_index); 167 AvatarMenuItemGtk* item = new AvatarMenuItemGtk( 168 this, menu_item, active_index, theme_service_); 169 items_.push_back(item); 170 171 gtk_box_pack_start(GTK_BOX(inner_contents_), item->widget(), TRUE, TRUE, 0); 172 gtk_box_pack_start(GTK_BOX(inner_contents_), 173 gtk_hseparator_new(), TRUE, TRUE, 0); 174 175 // Add information about managed users within a hbox. 176 GtkWidget* managed_user_info = gtk_hbox_new(FALSE, 5); 177 GdkPixbuf* limited_user_pixbuf = 178 avatar_menu_model_->GetManagedUserIcon().ToGdkPixbuf(); 179 GtkWidget* limited_user_img = gtk_image_new_from_pixbuf(limited_user_pixbuf); 180 GtkWidget* icon_align = gtk_alignment_new(0, 0, 0, 0); 181 gtk_container_add(GTK_CONTAINER(icon_align), limited_user_img); 182 gtk_box_pack_start(GTK_BOX(managed_user_info), icon_align, FALSE, FALSE, 0); 183 GtkWidget* status_label = 184 theme_service_->BuildLabel(std::string(), ui::kGdkBlack); 185 char* markup = g_markup_printf_escaped( 186 "<span size='small'>%s</span>", 187 UTF16ToUTF8(avatar_menu_model_->GetManagedUserInformation()).c_str()); 188 const int kLabelWidth = 150; 189 gtk_widget_set_size_request(status_label, kLabelWidth, -1); 190 gtk_label_set_markup(GTK_LABEL(status_label), markup); 191 gtk_label_set_line_wrap(GTK_LABEL(status_label), TRUE); 192 gtk_misc_set_alignment(GTK_MISC(status_label), 0, 0); 193 g_free(markup); 194 gtk_box_pack_start(GTK_BOX(managed_user_info), status_label, FALSE, FALSE, 0); 195 gtk_box_pack_start( 196 GTK_BOX(inner_contents_), managed_user_info, FALSE, FALSE, 0); 197 198 gtk_box_pack_start( 199 GTK_BOX(inner_contents_), gtk_hseparator_new(), TRUE, TRUE, 0); 200 201 // The switch profile link. 202 GtkWidget* switch_profile_link = theme_service_->BuildChromeLinkButton( 203 l10n_util::GetStringUTF8(IDS_PROFILES_SWITCH_PROFILE_LINK)); 204 g_signal_connect(switch_profile_link, "clicked", 205 G_CALLBACK(OnSwitchProfileLinkClickedThunk), this); 206 207 GtkWidget* link_align = gtk_alignment_new(0.5, 0, 0, 0); 208 gtk_container_add(GTK_CONTAINER(link_align), switch_profile_link); 209 210 gtk_box_pack_start(GTK_BOX(inner_contents_), link_align, FALSE, FALSE, 0); 211 } 212 213 void AvatarMenuBubbleGtk::InitContents() { 214 // Destroy the old inner contents to allow replacing it. 215 if (inner_contents_) 216 gtk_widget_destroy(inner_contents_); 217 inner_contents_ = gtk_vbox_new(FALSE, ui::kControlSpacing); 218 if (!contents_) 219 contents_ = gtk_vbox_new(FALSE, 0); 220 gtk_container_set_border_width(GTK_CONTAINER(inner_contents_), 221 ui::kContentAreaBorder); 222 g_signal_connect(inner_contents_, "size-request", 223 G_CALLBACK(OnSizeRequestThunk), this); 224 225 if (avatar_menu_model_->GetManagedUserInformation().empty() || switching_) 226 InitMenuContents(); 227 else 228 InitManagedUserContents(); 229 gtk_box_pack_start(GTK_BOX(contents_), inner_contents_, TRUE, TRUE, 0); 230 if (bubble_) 231 gtk_widget_show_all(contents_); 232 } 233 234 void AvatarMenuBubbleGtk::CloseBubble() { 235 if (bubble_) { 236 bubble_->Close(); 237 bubble_ = NULL; 238 } 239 } 240