OLD | NEW |
(Empty) | |
| 1 // Copyright 2014 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 "ash/system/user/user_view.h" |
| 6 |
| 7 #include <algorithm> |
| 8 |
| 9 #include "ash/multi_profile_uma.h" |
| 10 #include "ash/popup_message.h" |
| 11 #include "ash/session_state_delegate.h" |
| 12 #include "ash/shell.h" |
| 13 #include "ash/shell_delegate.h" |
| 14 #include "ash/system/tray/system_tray.h" |
| 15 #include "ash/system/tray/system_tray_delegate.h" |
| 16 #include "ash/system/tray/tray_popup_label_button.h" |
| 17 #include "ash/system/tray/tray_popup_label_button_border.h" |
| 18 #include "ash/system/user/button_from_view.h" |
| 19 #include "ash/system/user/config.h" |
| 20 #include "ash/system/user/rounded_image_view.h" |
| 21 #include "ash/system/user/user_card_view.h" |
| 22 #include "grit/ash_resources.h" |
| 23 #include "grit/ash_strings.h" |
| 24 #include "ui/base/l10n/l10n_util.h" |
| 25 #include "ui/base/resource/resource_bundle.h" |
| 26 #include "ui/views/layout/fill_layout.h" |
| 27 #include "ui/views/painter.h" |
| 28 #include "ui/wm/core/shadow_types.h" |
| 29 |
| 30 namespace ash { |
| 31 namespace tray { |
| 32 |
| 33 namespace { |
| 34 |
| 35 const int kPublicAccountLogoutButtonBorderImagesNormal[] = { |
| 36 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER, |
| 37 IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND, |
| 38 IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND, |
| 39 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER, |
| 40 IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND, |
| 41 IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND, |
| 42 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER, |
| 43 IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND, |
| 44 IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND, |
| 45 }; |
| 46 |
| 47 const int kPublicAccountLogoutButtonBorderImagesHovered[] = { |
| 48 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER, |
| 49 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER, |
| 50 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER, |
| 51 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER, |
| 52 IDR_AURA_TRAY_POPUP_LABEL_BUTTON_HOVER_BACKGROUND, |
| 53 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER, |
| 54 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER, |
| 55 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER, |
| 56 IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER, |
| 57 }; |
| 58 |
| 59 // When a hover border is used, it is starting this many pixels before the icon |
| 60 // position. |
| 61 const int kTrayUserTileHoverBorderInset = 10; |
| 62 |
| 63 // Offsetting the popup message relative to the tray menu. |
| 64 const int kPopupMessageOffset = 25; |
| 65 |
| 66 // Switch to a user with the given |user_index|. |
| 67 void SwitchUser(ash::MultiProfileIndex user_index) { |
| 68 // Do not switch users when the log screen is presented. |
| 69 if (ash::Shell::GetInstance() |
| 70 ->session_state_delegate() |
| 71 ->IsUserSessionBlocked()) |
| 72 return; |
| 73 |
| 74 DCHECK(user_index > 0); |
| 75 ash::SessionStateDelegate* delegate = |
| 76 ash::Shell::GetInstance()->session_state_delegate(); |
| 77 ash::MultiProfileUMA::RecordSwitchActiveUser( |
| 78 ash::MultiProfileUMA::SWITCH_ACTIVE_USER_BY_TRAY); |
| 79 delegate->SwitchActiveUser(delegate->GetUserID(user_index)); |
| 80 } |
| 81 |
| 82 class LogoutButton : public TrayPopupLabelButton { |
| 83 public: |
| 84 // If |placeholder| is true, button is used as placeholder. That means that |
| 85 // button is inactive and is not painted, but consume the same ammount of |
| 86 // space, as if it was painted. |
| 87 LogoutButton(views::ButtonListener* listener, |
| 88 const base::string16& text, |
| 89 bool placeholder) |
| 90 : TrayPopupLabelButton(listener, text), placeholder_(placeholder) { |
| 91 SetEnabled(!placeholder_); |
| 92 } |
| 93 |
| 94 virtual ~LogoutButton() {} |
| 95 |
| 96 private: |
| 97 virtual void Paint(gfx::Canvas* canvas) OVERRIDE { |
| 98 // Just skip paint if this button used as a placeholder. |
| 99 if (!placeholder_) |
| 100 TrayPopupLabelButton::Paint(canvas); |
| 101 } |
| 102 |
| 103 bool placeholder_; |
| 104 DISALLOW_COPY_AND_ASSIGN(LogoutButton); |
| 105 }; |
| 106 |
| 107 class UserViewMouseWatcherHost : public views::MouseWatcherHost { |
| 108 public: |
| 109 explicit UserViewMouseWatcherHost(const gfx::Rect& screen_area) |
| 110 : screen_area_(screen_area) {} |
| 111 virtual ~UserViewMouseWatcherHost() {} |
| 112 |
| 113 // Implementation of MouseWatcherHost. |
| 114 virtual bool Contains(const gfx::Point& screen_point, |
| 115 views::MouseWatcherHost::MouseEventType type) OVERRIDE { |
| 116 return screen_area_.Contains(screen_point); |
| 117 } |
| 118 |
| 119 private: |
| 120 gfx::Rect screen_area_; |
| 121 |
| 122 DISALLOW_COPY_AND_ASSIGN(UserViewMouseWatcherHost); |
| 123 }; |
| 124 |
| 125 // The menu item view which gets shown when the user clicks in multi profile |
| 126 // mode onto the user item. |
| 127 class AddUserView : public views::View { |
| 128 public: |
| 129 // The |owner| is the view for which this view gets created. |
| 130 AddUserView(ButtonFromView* owner); |
| 131 virtual ~AddUserView(); |
| 132 |
| 133 // Get the anchor view for a message. |
| 134 views::View* anchor() { return anchor_; } |
| 135 |
| 136 private: |
| 137 // Overridden from views::View. |
| 138 virtual gfx::Size GetPreferredSize() OVERRIDE; |
| 139 |
| 140 // Create the additional client content for this item. |
| 141 void AddContent(); |
| 142 |
| 143 // This is the content we create and show. |
| 144 views::View* add_user_; |
| 145 |
| 146 // This is the owner view of this item. |
| 147 ButtonFromView* owner_; |
| 148 |
| 149 // The anchor view for targetted bubble messages. |
| 150 views::View* anchor_; |
| 151 |
| 152 DISALLOW_COPY_AND_ASSIGN(AddUserView); |
| 153 }; |
| 154 |
| 155 AddUserView::AddUserView(ButtonFromView* owner) |
| 156 : add_user_(NULL), owner_(owner), anchor_(NULL) { |
| 157 AddContent(); |
| 158 owner_->ForceBorderVisible(true); |
| 159 } |
| 160 |
| 161 AddUserView::~AddUserView() { |
| 162 owner_->ForceBorderVisible(false); |
| 163 } |
| 164 |
| 165 gfx::Size AddUserView::GetPreferredSize() { |
| 166 return owner_->bounds().size(); |
| 167 } |
| 168 |
| 169 void AddUserView::AddContent() { |
| 170 SetLayoutManager(new views::FillLayout()); |
| 171 set_background(views::Background::CreateSolidBackground(kBackgroundColor)); |
| 172 |
| 173 add_user_ = new views::View; |
| 174 add_user_->SetBorder(views::Border::CreateEmptyBorder( |
| 175 kTrayPopupUserCardVerticalPadding, |
| 176 kTrayPopupPaddingHorizontal - kTrayUserTileHoverBorderInset, |
| 177 kTrayPopupUserCardVerticalPadding, |
| 178 kTrayPopupPaddingHorizontal - kTrayUserTileHoverBorderInset)); |
| 179 |
| 180 add_user_->SetLayoutManager(new views::BoxLayout( |
| 181 views::BoxLayout::kHorizontal, 0, 0, kTrayPopupPaddingBetweenItems)); |
| 182 AddChildViewAt(add_user_, 0); |
| 183 |
| 184 // Add the [+] icon which is also the anchor for messages. |
| 185 RoundedImageView* icon = new RoundedImageView(kTrayAvatarCornerRadius, true); |
| 186 anchor_ = icon; |
| 187 icon->SetImage(*ui::ResourceBundle::GetSharedInstance() |
| 188 .GetImageNamed(IDR_AURA_UBER_TRAY_ADD_MULTIPROFILE_USER) |
| 189 .ToImageSkia(), |
| 190 gfx::Size(kTrayAvatarSize, kTrayAvatarSize)); |
| 191 add_user_->AddChildView(icon); |
| 192 |
| 193 // Add the command text. |
| 194 views::Label* command_label = new views::Label( |
| 195 l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_SIGN_IN_ANOTHER_ACCOUNT)); |
| 196 command_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| 197 add_user_->AddChildView(command_label); |
| 198 } |
| 199 |
| 200 } // namespace |
| 201 |
| 202 UserView::UserView(SystemTrayItem* owner, |
| 203 user::LoginStatus login, |
| 204 MultiProfileIndex index, |
| 205 bool for_detailed_view) |
| 206 : multiprofile_index_(index), |
| 207 user_card_view_(NULL), |
| 208 owner_(owner), |
| 209 is_user_card_button_(false), |
| 210 logout_button_(NULL), |
| 211 add_user_disabled_(false), |
| 212 for_detailed_view_(for_detailed_view) { |
| 213 CHECK_NE(user::LOGGED_IN_NONE, login); |
| 214 if (!index) { |
| 215 // Only the logged in user will have a background. All other users will have |
| 216 // to allow the TrayPopupContainer highlighting the menu line. |
| 217 set_background(views::Background::CreateSolidBackground( |
| 218 login == user::LOGGED_IN_PUBLIC ? kPublicAccountBackgroundColor |
| 219 : kBackgroundColor)); |
| 220 } |
| 221 SetLayoutManager(new views::BoxLayout( |
| 222 views::BoxLayout::kHorizontal, 0, 0, kTrayPopupPaddingBetweenItems)); |
| 223 // The logout button must be added before the user card so that the user card |
| 224 // can correctly calculate the remaining available width. |
| 225 // Note that only the current multiprofile user gets a button. |
| 226 if (!multiprofile_index_) |
| 227 AddLogoutButton(login); |
| 228 AddUserCard(login); |
| 229 } |
| 230 |
| 231 UserView::~UserView() {} |
| 232 |
| 233 void UserView::MouseMovedOutOfHost() { |
| 234 popup_message_.reset(); |
| 235 mouse_watcher_.reset(); |
| 236 add_menu_option_.reset(); |
| 237 } |
| 238 |
| 239 TrayUser::TestState UserView::GetStateForTest() const { |
| 240 if (add_menu_option_.get()) { |
| 241 return add_user_disabled_ ? TrayUser::ACTIVE_BUT_DISABLED |
| 242 : TrayUser::ACTIVE; |
| 243 } |
| 244 |
| 245 if (!is_user_card_button_) |
| 246 return TrayUser::SHOWN; |
| 247 |
| 248 return static_cast<ButtonFromView*>(user_card_view_)->is_hovered_for_test() |
| 249 ? TrayUser::HOVERED |
| 250 : TrayUser::SHOWN; |
| 251 } |
| 252 |
| 253 gfx::Rect UserView::GetBoundsInScreenOfUserButtonForTest() { |
| 254 DCHECK(user_card_view_); |
| 255 return user_card_view_->GetBoundsInScreen(); |
| 256 } |
| 257 |
| 258 gfx::Size UserView::GetPreferredSize() { |
| 259 gfx::Size size = views::View::GetPreferredSize(); |
| 260 // Only the active user panel will be forced to a certain height. |
| 261 if (!multiprofile_index_) { |
| 262 size.set_height( |
| 263 std::max(size.height(), kTrayPopupItemHeight + GetInsets().height())); |
| 264 } |
| 265 return size; |
| 266 } |
| 267 |
| 268 int UserView::GetHeightForWidth(int width) { |
| 269 return GetPreferredSize().height(); |
| 270 } |
| 271 |
| 272 void UserView::Layout() { |
| 273 gfx::Rect contents_area(GetContentsBounds()); |
| 274 if (user_card_view_ && logout_button_) { |
| 275 // Give the logout button the space it requests. |
| 276 gfx::Rect logout_area = contents_area; |
| 277 logout_area.ClampToCenteredSize(logout_button_->GetPreferredSize()); |
| 278 logout_area.set_x(contents_area.right() - logout_area.width()); |
| 279 |
| 280 // Give the remaining space to the user card. |
| 281 gfx::Rect user_card_area = contents_area; |
| 282 int remaining_width = contents_area.width() - logout_area.width(); |
| 283 if (IsMultiProfileSupportedAndUserActive() || |
| 284 IsMultiAccountSupportedAndUserActive()) { |
| 285 // In multiprofile case |user_card_view_| and |logout_button_| have to |
| 286 // have the same height. |
| 287 int y = std::min(user_card_area.y(), logout_area.y()); |
| 288 int height = std::max(user_card_area.height(), logout_area.height()); |
| 289 logout_area.set_y(y); |
| 290 logout_area.set_height(height); |
| 291 user_card_area.set_y(y); |
| 292 user_card_area.set_height(height); |
| 293 |
| 294 // In multiprofile mode we have also to increase the size of the card by |
| 295 // the size of the border to make it overlap with the logout button. |
| 296 user_card_area.set_width(std::max(0, remaining_width + 1)); |
| 297 |
| 298 // To make the logout button symmetrical with the user card we also make |
| 299 // the button longer by the same size the hover area in front of the icon |
| 300 // got inset. |
| 301 logout_area.set_width(logout_area.width() + |
| 302 kTrayUserTileHoverBorderInset); |
| 303 } else { |
| 304 // In all other modes we have to make sure that there is enough spacing |
| 305 // between the two. |
| 306 remaining_width -= kTrayPopupPaddingBetweenItems; |
| 307 } |
| 308 user_card_area.set_width(remaining_width); |
| 309 user_card_view_->SetBoundsRect(user_card_area); |
| 310 logout_button_->SetBoundsRect(logout_area); |
| 311 } else if (user_card_view_) { |
| 312 user_card_view_->SetBoundsRect(contents_area); |
| 313 } else if (logout_button_) { |
| 314 logout_button_->SetBoundsRect(contents_area); |
| 315 } |
| 316 } |
| 317 |
| 318 void UserView::ButtonPressed(views::Button* sender, const ui::Event& event) { |
| 319 if (sender == logout_button_) { |
| 320 Shell::GetInstance()->metrics()->RecordUserMetricsAction( |
| 321 ash::UMA_STATUS_AREA_SIGN_OUT); |
| 322 Shell::GetInstance()->system_tray_delegate()->SignOut(); |
| 323 } else if (sender == user_card_view_ && !multiprofile_index_ && |
| 324 IsMultiAccountSupportedAndUserActive()) { |
| 325 owner_->TransitionDetailedView(); |
| 326 } else if (sender == user_card_view_ && |
| 327 IsMultiProfileSupportedAndUserActive()) { |
| 328 if (!multiprofile_index_) { |
| 329 ToggleAddUserMenuOption(); |
| 330 } else { |
| 331 SwitchUser(multiprofile_index_); |
| 332 // Since the user list is about to change the system menu should get |
| 333 // closed. |
| 334 owner_->system_tray()->CloseSystemBubble(); |
| 335 } |
| 336 } else if (add_menu_option_.get() && |
| 337 sender == add_menu_option_->GetContentsView()) { |
| 338 // Let the user add another account to the session. |
| 339 MultiProfileUMA::RecordSigninUser(MultiProfileUMA::SIGNIN_USER_BY_TRAY); |
| 340 Shell::GetInstance()->system_tray_delegate()->ShowUserLogin(); |
| 341 owner_->system_tray()->CloseSystemBubble(); |
| 342 } else { |
| 343 NOTREACHED(); |
| 344 } |
| 345 } |
| 346 |
| 347 void UserView::AddLogoutButton(user::LoginStatus login) { |
| 348 const base::string16 title = |
| 349 user::GetLocalizedSignOutStringForStatus(login, true); |
| 350 TrayPopupLabelButton* logout_button = |
| 351 new LogoutButton(this, title, for_detailed_view_); |
| 352 logout_button->SetAccessibleName(title); |
| 353 logout_button_ = logout_button; |
| 354 // In public account mode, the logout button border has a custom color. |
| 355 if (login == user::LOGGED_IN_PUBLIC) { |
| 356 scoped_ptr<TrayPopupLabelButtonBorder> border( |
| 357 new TrayPopupLabelButtonBorder()); |
| 358 border->SetPainter(false, |
| 359 views::Button::STATE_NORMAL, |
| 360 views::Painter::CreateImageGridPainter( |
| 361 kPublicAccountLogoutButtonBorderImagesNormal)); |
| 362 border->SetPainter(false, |
| 363 views::Button::STATE_HOVERED, |
| 364 views::Painter::CreateImageGridPainter( |
| 365 kPublicAccountLogoutButtonBorderImagesHovered)); |
| 366 border->SetPainter(false, |
| 367 views::Button::STATE_PRESSED, |
| 368 views::Painter::CreateImageGridPainter( |
| 369 kPublicAccountLogoutButtonBorderImagesHovered)); |
| 370 logout_button_->SetBorder(border.PassAs<views::Border>()); |
| 371 } |
| 372 AddChildView(logout_button_); |
| 373 } |
| 374 |
| 375 void UserView::AddUserCard(user::LoginStatus login) { |
| 376 // Add padding around the panel. |
| 377 SetBorder(views::Border::CreateEmptyBorder(kTrayPopupUserCardVerticalPadding, |
| 378 kTrayPopupPaddingHorizontal, |
| 379 kTrayPopupUserCardVerticalPadding, |
| 380 kTrayPopupPaddingHorizontal)); |
| 381 |
| 382 views::TrayBubbleView* bubble_view = |
| 383 owner_->system_tray()->GetSystemBubble()->bubble_view(); |
| 384 int max_card_width = |
| 385 bubble_view->GetMaximumSize().width() - |
| 386 (2 * kTrayPopupPaddingHorizontal + kTrayPopupPaddingBetweenItems); |
| 387 if (logout_button_) |
| 388 max_card_width -= logout_button_->GetPreferredSize().width(); |
| 389 user_card_view_ = |
| 390 new UserCardView(login, max_card_width, multiprofile_index_); |
| 391 bool clickable = IsMultiProfileSupportedAndUserActive() || |
| 392 IsMultiAccountSupportedAndUserActive(); |
| 393 if (clickable) { |
| 394 // To allow the border to start before the icon, reduce the size before and |
| 395 // add an inset to the icon to get the spacing. |
| 396 if (!multiprofile_index_) { |
| 397 SetBorder(views::Border::CreateEmptyBorder( |
| 398 kTrayPopupUserCardVerticalPadding, |
| 399 kTrayPopupPaddingHorizontal - kTrayUserTileHoverBorderInset, |
| 400 kTrayPopupUserCardVerticalPadding, |
| 401 kTrayPopupPaddingHorizontal)); |
| 402 user_card_view_->SetBorder(views::Border::CreateEmptyBorder( |
| 403 0, kTrayUserTileHoverBorderInset, 0, 0)); |
| 404 } |
| 405 if (!for_detailed_view_) { |
| 406 user_card_view_ = |
| 407 new ButtonFromView(user_card_view_, this, !multiprofile_index_); |
| 408 } else { |
| 409 // We want user card for detailed view to have exactly the same look |
| 410 // as user card for default view. That's why we wrap it in a button |
| 411 // without click listener and special hover behaviour. |
| 412 user_card_view_ = new ButtonFromView(user_card_view_, NULL, false); |
| 413 } |
| 414 is_user_card_button_ = true; |
| 415 } |
| 416 AddChildViewAt(user_card_view_, 0); |
| 417 // Card for locally managed user can consume more space than currently |
| 418 // available. In that case we should increase system bubble's width. |
| 419 if (login == user::LOGGED_IN_PUBLIC) |
| 420 bubble_view->SetWidth(GetPreferredSize().width()); |
| 421 } |
| 422 |
| 423 void UserView::ToggleAddUserMenuOption() { |
| 424 if (add_menu_option_.get()) { |
| 425 popup_message_.reset(); |
| 426 mouse_watcher_.reset(); |
| 427 add_menu_option_.reset(); |
| 428 return; |
| 429 } |
| 430 |
| 431 // Note: We do not need to install a global event handler to delete this |
| 432 // item since it will destroyed automatically before the menu / user menu item |
| 433 // gets destroyed.. |
| 434 add_menu_option_.reset(new views::Widget); |
| 435 views::Widget::InitParams params; |
| 436 params.type = views::Widget::InitParams::TYPE_TOOLTIP; |
| 437 params.keep_on_top = true; |
| 438 params.context = this->GetWidget()->GetNativeWindow(); |
| 439 params.accept_events = true; |
| 440 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; |
| 441 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; |
| 442 add_menu_option_->Init(params); |
| 443 add_menu_option_->SetOpacity(0xFF); |
| 444 add_menu_option_->GetNativeWindow()->set_owned_by_parent(false); |
| 445 SetShadowType(add_menu_option_->GetNativeView(), wm::SHADOW_TYPE_NONE); |
| 446 |
| 447 // Position it below our user card. |
| 448 gfx::Rect bounds = user_card_view_->GetBoundsInScreen(); |
| 449 bounds.set_y(bounds.y() + bounds.height()); |
| 450 add_menu_option_->SetBounds(bounds); |
| 451 |
| 452 // Show the content. |
| 453 add_menu_option_->SetAlwaysOnTop(true); |
| 454 add_menu_option_->Show(); |
| 455 |
| 456 AddUserView* add_user_view = |
| 457 new AddUserView(static_cast<ButtonFromView*>(user_card_view_)); |
| 458 |
| 459 const SessionStateDelegate* delegate = |
| 460 Shell::GetInstance()->session_state_delegate(); |
| 461 add_user_disabled_ = delegate->NumberOfLoggedInUsers() >= |
| 462 delegate->GetMaximumNumberOfLoggedInUsers(); |
| 463 ButtonFromView* button = add_user_disabled_ |
| 464 ? new ButtonFromView(add_user_view, NULL, false) |
| 465 : new ButtonFromView(add_user_view, this, true); |
| 466 button->ForceBorderVisible(true); |
| 467 add_menu_option_->SetContentsView(button); |
| 468 |
| 469 if (add_user_disabled_) { |
| 470 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); |
| 471 popup_message_.reset(new PopupMessage( |
| 472 bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_CAPTION_CANNOT_ADD_USER), |
| 473 bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_MESSAGE_CANNOT_ADD_USER), |
| 474 PopupMessage::ICON_WARNING, |
| 475 add_user_view->anchor(), |
| 476 views::BubbleBorder::TOP_LEFT, |
| 477 gfx::Size(parent()->bounds().width() - kPopupMessageOffset, 0), |
| 478 2 * kPopupMessageOffset)); |
| 479 } |
| 480 // Find the screen area which encloses both elements and sets then a mouse |
| 481 // watcher which will close the "menu". |
| 482 gfx::Rect area = user_card_view_->GetBoundsInScreen(); |
| 483 area.set_height(2 * area.height()); |
| 484 mouse_watcher_.reset( |
| 485 new views::MouseWatcher(new UserViewMouseWatcherHost(area), this)); |
| 486 mouse_watcher_->Start(); |
| 487 } |
| 488 |
| 489 } // namespace tray |
| 490 } // namespace ash |
OLD | NEW |