OLD | NEW |
| (Empty) |
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/views/avatar_menu_bubble_view.h" | |
6 | |
7 #include <algorithm> | |
8 | |
9 #include "base/strings/string16.h" | |
10 #include "base/strings/utf_string_conversions.h" | |
11 #include "chrome/app/chrome_command_ids.h" | |
12 #include "chrome/browser/browser_process.h" | |
13 #include "chrome/browser/profiles/avatar_menu.h" | |
14 #include "chrome/browser/profiles/profile_info_cache.h" | |
15 #include "chrome/browser/profiles/profile_info_util.h" | |
16 #include "chrome/browser/profiles/profile_manager.h" | |
17 #include "chrome/browser/profiles/profile_window.h" | |
18 #include "chrome/browser/signin/signin_manager_factory.h" | |
19 #include "chrome/browser/ui/browser.h" | |
20 #include "chrome/browser/ui/browser_commands.h" | |
21 #include "chrome/browser/ui/browser_list.h" | |
22 #include "chrome/browser/ui/browser_window.h" | |
23 #include "chrome/browser/ui/chrome_pages.h" | |
24 #include "chrome/common/profile_management_switches.h" | |
25 #include "chrome/common/url_constants.h" | |
26 #include "components/signin/core/browser/signin_manager.h" | |
27 #include "content/public/browser/page_navigator.h" | |
28 #include "content/public/browser/web_contents.h" | |
29 #include "grit/generated_resources.h" | |
30 #include "grit/theme_resources.h" | |
31 #include "ui/base/l10n/l10n_util.h" | |
32 #include "ui/base/resource/resource_bundle.h" | |
33 #include "ui/gfx/canvas.h" | |
34 #include "ui/gfx/image/canvas_image_source.h" | |
35 #include "ui/gfx/image/image.h" | |
36 #include "ui/views/controls/button/custom_button.h" | |
37 #include "ui/views/controls/button/image_button.h" | |
38 #include "ui/views/controls/button/label_button.h" | |
39 #include "ui/views/controls/image_view.h" | |
40 #include "ui/views/controls/label.h" | |
41 #include "ui/views/controls/link.h" | |
42 #include "ui/views/controls/separator.h" | |
43 #include "ui/views/layout/grid_layout.h" | |
44 #include "ui/views/layout/layout_constants.h" | |
45 #include "ui/views/widget/widget.h" | |
46 | |
47 namespace { | |
48 | |
49 const int kItemHeight = 44; | |
50 const int kItemMarginY = 4; | |
51 const int kIconMarginX = 6; | |
52 const int kSeparatorPaddingY = 5; | |
53 const int kMaxItemTextWidth = 200; | |
54 const SkColor kHighlightColor = 0xFFE3EDF6; | |
55 | |
56 inline int Round(double x) { | |
57 return static_cast<int>(x + 0.5); | |
58 } | |
59 | |
60 gfx::Rect GetCenteredAndScaledRect(int src_width, int src_height, | |
61 int dst_x, int dst_y, | |
62 int dst_width, int dst_height) { | |
63 int scaled_width; | |
64 int scaled_height; | |
65 if (src_width > src_height) { | |
66 scaled_width = std::min(src_width, dst_width); | |
67 float scale = static_cast<float>(scaled_width) / | |
68 static_cast<float>(src_width); | |
69 scaled_height = Round(src_height * scale); | |
70 } else { | |
71 scaled_height = std::min(src_height, dst_height); | |
72 float scale = static_cast<float>(scaled_height) / | |
73 static_cast<float>(src_height); | |
74 scaled_width = Round(src_width * scale); | |
75 } | |
76 int x = dst_x + (dst_width - scaled_width) / 2; | |
77 int y = dst_y + (dst_height - scaled_height) / 2; | |
78 return gfx::Rect(x, y, scaled_width, scaled_height); | |
79 } | |
80 | |
81 // BadgeImageSource ----------------------------------------------------------- | |
82 class BadgeImageSource: public gfx::CanvasImageSource { | |
83 public: | |
84 BadgeImageSource(const gfx::ImageSkia& icon, | |
85 const gfx::Size& icon_size, | |
86 const gfx::ImageSkia& badge); | |
87 | |
88 virtual ~BadgeImageSource(); | |
89 | |
90 // Overridden from CanvasImageSource: | |
91 virtual void Draw(gfx::Canvas* canvas) OVERRIDE; | |
92 | |
93 private: | |
94 gfx::Size ComputeSize(const gfx::ImageSkia& icon, | |
95 const gfx::Size& size, | |
96 const gfx::ImageSkia& badge); | |
97 | |
98 const gfx::ImageSkia icon_; | |
99 gfx::Size icon_size_; | |
100 const gfx::ImageSkia badge_; | |
101 | |
102 DISALLOW_COPY_AND_ASSIGN(BadgeImageSource); | |
103 }; | |
104 | |
105 BadgeImageSource::BadgeImageSource(const gfx::ImageSkia& icon, | |
106 const gfx::Size& icon_size, | |
107 const gfx::ImageSkia& badge) | |
108 : gfx::CanvasImageSource(ComputeSize(icon, icon_size, badge), false), | |
109 icon_(icon), | |
110 icon_size_(icon_size), | |
111 badge_(badge) { | |
112 } | |
113 | |
114 BadgeImageSource::~BadgeImageSource() { | |
115 } | |
116 | |
117 void BadgeImageSource::Draw(gfx::Canvas* canvas) { | |
118 canvas->DrawImageInt(icon_, 0, 0, icon_.width(), icon_.height(), 0, 0, | |
119 icon_size_.width(), icon_size_.height(), true); | |
120 canvas->DrawImageInt(badge_, size().width() - badge_.width(), | |
121 size().height() - badge_.height()); | |
122 } | |
123 | |
124 gfx::Size BadgeImageSource::ComputeSize(const gfx::ImageSkia& icon, | |
125 const gfx::Size& icon_size, | |
126 const gfx::ImageSkia& badge) { | |
127 const float kBadgeOverlapRatioX = 1.0f / 5.0f; | |
128 int width = icon_size.width() + badge.width() * kBadgeOverlapRatioX; | |
129 const float kBadgeOverlapRatioY = 1.0f / 3.0f; | |
130 int height = icon_size.height() + badge.height() * kBadgeOverlapRatioY; | |
131 return gfx::Size(width, height); | |
132 } | |
133 | |
134 // HighlightDelegate ---------------------------------------------------------- | |
135 | |
136 // Delegate to callback when the highlight state of a control changes. | |
137 class HighlightDelegate { | |
138 public: | |
139 virtual ~HighlightDelegate() {} | |
140 virtual void OnHighlightStateChanged() = 0; | |
141 virtual void OnFocusStateChanged(bool has_focus) = 0; | |
142 }; | |
143 | |
144 | |
145 // EditProfileLink ------------------------------------------------------------ | |
146 | |
147 // A custom Link control that forwards highlight state changes. We need to do | |
148 // this to make sure that the ProfileItemView looks highlighted even when | |
149 // the mouse is over this link. | |
150 class EditProfileLink : public views::Link { | |
151 public: | |
152 explicit EditProfileLink(const base::string16& title, | |
153 HighlightDelegate* delegate); | |
154 | |
155 virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE; | |
156 virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE; | |
157 virtual void OnFocus() OVERRIDE; | |
158 virtual void OnBlur() OVERRIDE; | |
159 | |
160 views::CustomButton::ButtonState state() { return state_; } | |
161 | |
162 private: | |
163 HighlightDelegate* delegate_; | |
164 views::CustomButton::ButtonState state_; | |
165 }; | |
166 | |
167 EditProfileLink::EditProfileLink(const base::string16& title, | |
168 HighlightDelegate* delegate) | |
169 : views::Link(title), | |
170 delegate_(delegate), | |
171 state_(views::CustomButton::STATE_NORMAL) { | |
172 } | |
173 | |
174 void EditProfileLink::OnMouseEntered(const ui::MouseEvent& event) { | |
175 views::Link::OnMouseEntered(event); | |
176 state_ = views::CustomButton::STATE_HOVERED; | |
177 delegate_->OnHighlightStateChanged(); | |
178 } | |
179 | |
180 void EditProfileLink::OnMouseExited(const ui::MouseEvent& event) { | |
181 views::Link::OnMouseExited(event); | |
182 state_ = views::CustomButton::STATE_NORMAL; | |
183 delegate_->OnHighlightStateChanged(); | |
184 } | |
185 | |
186 void EditProfileLink::OnFocus() { | |
187 views::Link::OnFocus(); | |
188 delegate_->OnFocusStateChanged(true); | |
189 } | |
190 | |
191 void EditProfileLink::OnBlur() { | |
192 views::Link::OnBlur(); | |
193 state_ = views::CustomButton::STATE_NORMAL; | |
194 delegate_->OnFocusStateChanged(false); | |
195 } | |
196 | |
197 | |
198 // ProfileImageView ----------------------------------------------------------- | |
199 | |
200 // A custom image view that ignores mouse events so that the parent can receive | |
201 // them instead. | |
202 class ProfileImageView : public views::ImageView { | |
203 public: | |
204 virtual bool HitTestRect(const gfx::Rect& rect) const OVERRIDE; | |
205 }; | |
206 | |
207 bool ProfileImageView::HitTestRect(const gfx::Rect& rect) const { | |
208 return false; | |
209 } | |
210 | |
211 } // namespace | |
212 | |
213 // ProfileItemView ------------------------------------------------------------ | |
214 | |
215 // Control that shows information about a single profile. | |
216 class ProfileItemView : public views::CustomButton, | |
217 public HighlightDelegate { | |
218 public: | |
219 ProfileItemView(const AvatarMenu::Item& item, | |
220 AvatarMenuBubbleView* parent, | |
221 AvatarMenu* menu); | |
222 | |
223 virtual gfx::Size GetPreferredSize() OVERRIDE; | |
224 virtual void Layout() OVERRIDE; | |
225 virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE; | |
226 virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE; | |
227 virtual void OnFocus() OVERRIDE; | |
228 virtual void OnBlur() OVERRIDE; | |
229 | |
230 virtual void OnHighlightStateChanged() OVERRIDE; | |
231 virtual void OnFocusStateChanged(bool has_focus) OVERRIDE; | |
232 | |
233 const AvatarMenu::Item& item() const { return item_; } | |
234 EditProfileLink* edit_link() { return edit_link_; } | |
235 | |
236 private: | |
237 gfx::ImageSkia GetBadgedIcon(const gfx::ImageSkia& icon); | |
238 | |
239 bool IsHighlighted(); | |
240 | |
241 AvatarMenu::Item item_; | |
242 AvatarMenuBubbleView* parent_; | |
243 AvatarMenu* menu_; | |
244 views::ImageView* image_view_; | |
245 views::Label* name_label_; | |
246 views::Label* sync_state_label_; | |
247 EditProfileLink* edit_link_; | |
248 | |
249 DISALLOW_COPY_AND_ASSIGN(ProfileItemView); | |
250 }; | |
251 | |
252 ProfileItemView::ProfileItemView(const AvatarMenu::Item& item, | |
253 AvatarMenuBubbleView* parent, | |
254 AvatarMenu* menu) | |
255 : views::CustomButton(parent), | |
256 item_(item), | |
257 parent_(parent), | |
258 menu_(menu) { | |
259 set_notify_enter_exit_on_child(true); | |
260 | |
261 image_view_ = new ProfileImageView(); | |
262 gfx::ImageSkia profile_icon = *item_.icon.ToImageSkia(); | |
263 if (item_.active || item_.signin_required) | |
264 image_view_->SetImage(GetBadgedIcon(profile_icon)); | |
265 else | |
266 image_view_->SetImage(profile_icon); | |
267 AddChildView(image_view_); | |
268 | |
269 // Add a label to show the profile name. | |
270 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance(); | |
271 name_label_ = new views::Label(item_.name, | |
272 rb->GetFontList(item_.active ? | |
273 ui::ResourceBundle::BoldFont : | |
274 ui::ResourceBundle::BaseFont)); | |
275 name_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); | |
276 AddChildView(name_label_); | |
277 | |
278 // Add a label to show the sync state. | |
279 sync_state_label_ = new views::Label(item_.sync_state); | |
280 if (item_.signed_in) | |
281 sync_state_label_->SetElideBehavior(views::Label::ELIDE_AS_EMAIL); | |
282 sync_state_label_->SetFontList( | |
283 rb->GetFontList(ui::ResourceBundle::SmallFont)); | |
284 sync_state_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); | |
285 sync_state_label_->SetEnabled(false); | |
286 AddChildView(sync_state_label_); | |
287 | |
288 // Add an edit profile link. | |
289 edit_link_ = new EditProfileLink( | |
290 l10n_util::GetStringUTF16(IDS_PROFILES_EDIT_PROFILE_LINK), this); | |
291 edit_link_->set_listener(parent); | |
292 edit_link_->SetHorizontalAlignment(gfx::ALIGN_LEFT); | |
293 AddChildView(edit_link_); | |
294 | |
295 OnHighlightStateChanged(); | |
296 } | |
297 | |
298 gfx::Size ProfileItemView::GetPreferredSize() { | |
299 int text_width = std::max(name_label_->GetPreferredSize().width(), | |
300 sync_state_label_->GetPreferredSize().width()); | |
301 text_width = std::max(edit_link_->GetPreferredSize().width(), text_width); | |
302 text_width = std::min(kMaxItemTextWidth, text_width); | |
303 return gfx::Size(profiles::kAvatarIconWidth + kIconMarginX + text_width, | |
304 kItemHeight); | |
305 } | |
306 | |
307 void ProfileItemView::Layout() { | |
308 // Profile icon. | |
309 gfx::Rect icon_rect; | |
310 if (item_.active) { | |
311 // If this is the active item then the icon is already scaled and so | |
312 // just use the preferred size. | |
313 icon_rect.set_size(image_view_->GetPreferredSize()); | |
314 icon_rect.set_y((height() - icon_rect.height()) / 2); | |
315 } else { | |
316 const gfx::ImageSkia& icon = image_view_->GetImage(); | |
317 icon_rect = GetCenteredAndScaledRect(icon.width(), icon.height(), 0, 0, | |
318 profiles::kAvatarIconWidth, height()); | |
319 } | |
320 image_view_->SetBoundsRect(icon_rect); | |
321 | |
322 int label_x = profiles::kAvatarIconWidth + kIconMarginX; | |
323 int max_label_width = std::max(width() - label_x, 0); | |
324 gfx::Size name_size = name_label_->GetPreferredSize(); | |
325 name_size.set_width(std::min(name_size.width(), max_label_width)); | |
326 gfx::Size state_size = sync_state_label_->GetPreferredSize(); | |
327 state_size.set_width(std::min(state_size.width(), max_label_width)); | |
328 gfx::Size edit_size = edit_link_->GetPreferredSize(); | |
329 edit_size.set_width(std::min(edit_size.width(), max_label_width)); | |
330 | |
331 const int kNameStatePaddingY = 2; | |
332 int labels_height = name_size.height() + kNameStatePaddingY + | |
333 std::max(state_size.height(), edit_size.height()); | |
334 int y = (height() - labels_height) / 2; | |
335 name_label_->SetBounds(label_x, y, name_size.width(), name_size.height()); | |
336 | |
337 int bottom = y + labels_height; | |
338 sync_state_label_->SetBounds(label_x, bottom - state_size.height(), | |
339 state_size.width(), state_size.height()); | |
340 // The edit link overlaps the sync state label. | |
341 edit_link_->SetBounds(label_x, bottom - edit_size.height(), | |
342 edit_size.width(), edit_size.height()); | |
343 } | |
344 | |
345 void ProfileItemView::OnMouseEntered(const ui::MouseEvent& event) { | |
346 views::CustomButton::OnMouseEntered(event); | |
347 OnHighlightStateChanged(); | |
348 } | |
349 | |
350 void ProfileItemView::OnMouseExited(const ui::MouseEvent& event) { | |
351 views::CustomButton::OnMouseExited(event); | |
352 OnHighlightStateChanged(); | |
353 } | |
354 | |
355 void ProfileItemView::OnFocus() { | |
356 views::CustomButton::OnFocus(); | |
357 OnFocusStateChanged(true); | |
358 } | |
359 | |
360 void ProfileItemView::OnBlur() { | |
361 views::CustomButton::OnBlur(); | |
362 OnFocusStateChanged(false); | |
363 } | |
364 | |
365 void ProfileItemView::OnHighlightStateChanged() { | |
366 const SkColor color = IsHighlighted() ? kHighlightColor : parent_->color(); | |
367 set_background(views::Background::CreateSolidBackground(color)); | |
368 name_label_->SetBackgroundColor(color); | |
369 sync_state_label_->SetBackgroundColor(color); | |
370 edit_link_->SetBackgroundColor(color); | |
371 | |
372 bool show_edit = IsHighlighted() && item_.active && | |
373 menu_->ShouldShowEditProfileLink(); | |
374 sync_state_label_->SetVisible(!show_edit); | |
375 edit_link_->SetVisible(show_edit); | |
376 SchedulePaint(); | |
377 } | |
378 | |
379 void ProfileItemView::OnFocusStateChanged(bool has_focus) { | |
380 if (!has_focus && state() != views::CustomButton::STATE_DISABLED) | |
381 SetState(views::CustomButton::STATE_NORMAL); | |
382 OnHighlightStateChanged(); | |
383 } | |
384 | |
385 // static | |
386 gfx::ImageSkia ProfileItemView::GetBadgedIcon(const gfx::ImageSkia& icon) { | |
387 ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance(); | |
388 const gfx::ImageSkia* badge = NULL; | |
389 | |
390 if (item_.active) | |
391 badge = rb->GetImageSkiaNamed(IDR_PROFILE_SELECTED); | |
392 else if (item_.signin_required) // TODO(bcwhite): create new icon | |
393 badge = rb->GetImageSkiaNamed(IDR_OMNIBOX_HTTPS_VALID); | |
394 else | |
395 NOTREACHED(); // function should only be called if one of above is true | |
396 | |
397 gfx::Size icon_size = GetCenteredAndScaledRect(icon.width(), icon.height(), | |
398 0, 0, profiles::kAvatarIconWidth, kItemHeight).size(); | |
399 gfx::CanvasImageSource* source = | |
400 new BadgeImageSource(icon, icon_size, *badge); | |
401 // ImageSkia takes ownership of |source|. | |
402 return gfx::ImageSkia(source, source->size()); | |
403 } | |
404 | |
405 bool ProfileItemView::IsHighlighted() { | |
406 return state() == views::CustomButton::STATE_PRESSED || | |
407 state() == views::CustomButton::STATE_HOVERED || | |
408 edit_link_->state() == views::CustomButton::STATE_PRESSED || | |
409 edit_link_->state() == views::CustomButton::STATE_HOVERED || | |
410 HasFocus() || | |
411 edit_link_->HasFocus(); | |
412 } | |
413 | |
414 | |
415 // ActionButtonView ----------------------------------------------------------- | |
416 | |
417 // A custom view that manages the "action" buttons at the bottom of the list | |
418 // of profiles. | |
419 class ActionButtonView : public views::View { | |
420 public: | |
421 ActionButtonView(views::ButtonListener* listener, Profile* profile); | |
422 | |
423 private: | |
424 views::LabelButton* manage_button_; | |
425 views::LabelButton* signout_button_; | |
426 | |
427 DISALLOW_COPY_AND_ASSIGN(ActionButtonView); | |
428 }; | |
429 | |
430 | |
431 ActionButtonView::ActionButtonView(views::ButtonListener* listener, | |
432 Profile* profile) | |
433 : manage_button_(NULL), | |
434 signout_button_(NULL) { | |
435 std::string username; | |
436 SigninManagerBase* signin = | |
437 SigninManagerFactory::GetForProfile(profile); | |
438 if (signin != NULL) | |
439 username = signin->GetAuthenticatedUsername(); | |
440 | |
441 manage_button_ = new views::LabelButton( | |
442 listener, l10n_util::GetStringUTF16(IDS_PROFILES_MANAGE_PROFILES_BUTTON)); | |
443 manage_button_->SetStyle(views::Button::STYLE_BUTTON); | |
444 manage_button_->SetTooltipText( | |
445 l10n_util::GetStringUTF16(IDS_PROFILES_MANAGE_PROFILES_BUTTON_TIP)); | |
446 manage_button_->set_tag(IDS_PROFILES_MANAGE_PROFILES_BUTTON); | |
447 | |
448 signout_button_ = new views::LabelButton( | |
449 listener, l10n_util::GetStringUTF16(IDS_PROFILES_PROFILE_SIGNOUT_BUTTON)); | |
450 signout_button_->SetStyle(views::Button::STYLE_BUTTON); | |
451 if (username.empty()) { | |
452 signout_button_->SetTooltipText( | |
453 l10n_util::GetStringUTF16( | |
454 IDS_PROFILES_PROFILE_SIGNOUT_BUTTON_TIP_UNAVAILABLE)); | |
455 signout_button_->SetEnabled(false); | |
456 } else { | |
457 signout_button_->SetTooltipText( | |
458 l10n_util::GetStringFUTF16(IDS_PROFILES_PROFILE_SIGNOUT_BUTTON_TIP, | |
459 base::UTF8ToUTF16(username))); | |
460 } | |
461 signout_button_->set_tag(IDS_PROFILES_PROFILE_SIGNOUT_BUTTON); | |
462 | |
463 views::GridLayout* layout = new views::GridLayout(this); | |
464 views::ColumnSet* columns = layout->AddColumnSet(0); | |
465 columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL, 1, | |
466 views::GridLayout::USE_PREF, 0, 0); | |
467 columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL, 1, | |
468 views::GridLayout::USE_PREF, 0, 0); | |
469 layout->StartRow(0, 0); | |
470 layout->AddView(signout_button_); | |
471 layout->AddView(manage_button_); | |
472 SetLayoutManager(layout); | |
473 } | |
474 | |
475 | |
476 // AvatarMenuBubbleView ------------------------------------------------------- | |
477 | |
478 // static | |
479 AvatarMenuBubbleView* AvatarMenuBubbleView::avatar_bubble_ = NULL; | |
480 bool AvatarMenuBubbleView::close_on_deactivate_for_testing_ = true; | |
481 | |
482 // static | |
483 void AvatarMenuBubbleView::ShowBubble( | |
484 views::View* anchor_view, | |
485 views::BubbleBorder::Arrow arrow, | |
486 views::BubbleBorder::BubbleAlignment border_alignment, | |
487 const gfx::Rect& anchor_rect, | |
488 Browser* browser) { | |
489 if (IsShowing()) | |
490 return; | |
491 | |
492 DCHECK(chrome::IsCommandEnabled(browser, IDC_SHOW_AVATAR_MENU)); | |
493 avatar_bubble_ = new AvatarMenuBubbleView( | |
494 anchor_view, arrow, anchor_rect, browser); | |
495 views::BubbleDelegateView::CreateBubble(avatar_bubble_); | |
496 avatar_bubble_->set_close_on_deactivate(close_on_deactivate_for_testing_); | |
497 avatar_bubble_->SetBackgroundColors(); | |
498 avatar_bubble_->SetAlignment(border_alignment); | |
499 avatar_bubble_->GetWidget()->Show(); | |
500 } | |
501 | |
502 // static | |
503 bool AvatarMenuBubbleView::IsShowing() { | |
504 return avatar_bubble_ != NULL; | |
505 } | |
506 | |
507 // static | |
508 void AvatarMenuBubbleView::Hide() { | |
509 if (IsShowing()) | |
510 avatar_bubble_->GetWidget()->Close(); | |
511 } | |
512 | |
513 AvatarMenuBubbleView::AvatarMenuBubbleView( | |
514 views::View* anchor_view, | |
515 views::BubbleBorder::Arrow arrow, | |
516 const gfx::Rect& anchor_rect, | |
517 Browser* browser) | |
518 : BubbleDelegateView(anchor_view, arrow), | |
519 anchor_rect_(anchor_rect), | |
520 browser_(browser), | |
521 separator_(NULL), | |
522 buttons_view_(NULL), | |
523 managed_user_info_(NULL), | |
524 separator_switch_users_(NULL), | |
525 expanded_(false) { | |
526 avatar_menu_.reset(new AvatarMenu( | |
527 &g_browser_process->profile_manager()->GetProfileInfoCache(), | |
528 this, | |
529 browser_)); | |
530 avatar_menu_->RebuildMenu(); | |
531 } | |
532 | |
533 AvatarMenuBubbleView::~AvatarMenuBubbleView() { | |
534 } | |
535 | |
536 gfx::Size AvatarMenuBubbleView::GetPreferredSize() { | |
537 const int kBubbleViewMinWidth = 175; | |
538 gfx::Size preferred_size(kBubbleViewMinWidth, 0); | |
539 for (size_t i = 0; i < item_views_.size(); ++i) { | |
540 gfx::Size size = item_views_[i]->GetPreferredSize(); | |
541 preferred_size.Enlarge(0, size.height() + kItemMarginY); | |
542 preferred_size.SetToMax(size); | |
543 } | |
544 | |
545 if (buttons_view_) { | |
546 preferred_size.Enlarge( | |
547 0, kSeparatorPaddingY * 2 + separator_->GetPreferredSize().height()); | |
548 | |
549 gfx::Size buttons_size = buttons_view_->GetPreferredSize(); | |
550 preferred_size.Enlarge(0, buttons_size.height()); | |
551 preferred_size.SetToMax(buttons_size); | |
552 } | |
553 | |
554 | |
555 if (managed_user_info_) { | |
556 // First handle the switch profile link because it can still affect the | |
557 // preferred width. | |
558 gfx::Size size = switch_profile_link_->GetPreferredSize(); | |
559 preferred_size.Enlarge(0, size.height()); | |
560 preferred_size.SetToMax(size); | |
561 | |
562 // Add the height of the two separators. | |
563 preferred_size.Enlarge( | |
564 0, | |
565 kSeparatorPaddingY * 4 + separator_->GetPreferredSize().height() * 2); | |
566 } | |
567 | |
568 const int kBubbleViewMaxWidth = 800; | |
569 preferred_size.SetToMin( | |
570 gfx::Size(kBubbleViewMaxWidth, preferred_size.height())); | |
571 | |
572 // We have to do this after the final width is calculated, since the label | |
573 // will wrap based on the width. | |
574 if (managed_user_info_) { | |
575 int remaining_width = | |
576 preferred_size.width() - icon_view_->GetPreferredSize().width() - | |
577 views::kRelatedControlSmallHorizontalSpacing; | |
578 preferred_size.Enlarge( | |
579 0, | |
580 managed_user_info_->GetHeightForWidth(remaining_width) + kItemMarginY); | |
581 } | |
582 | |
583 return preferred_size; | |
584 } | |
585 | |
586 void AvatarMenuBubbleView::Layout() { | |
587 int y = 0; | |
588 for (size_t i = 0; i < item_views_.size(); ++i) { | |
589 views::CustomButton* item_view = item_views_[i]; | |
590 int item_height = item_view->GetPreferredSize().height(); | |
591 int item_width = width(); | |
592 item_view->SetBounds(0, y, item_width, item_height); | |
593 y += item_height + kItemMarginY; | |
594 } | |
595 | |
596 int separator_height; | |
597 if (buttons_view_ || managed_user_info_) { | |
598 separator_height = separator_->GetPreferredSize().height(); | |
599 y += kSeparatorPaddingY; | |
600 separator_->SetBounds(0, y, width(), separator_height); | |
601 y += kSeparatorPaddingY + separator_height; | |
602 } | |
603 | |
604 if (buttons_view_) { | |
605 buttons_view_->SetBounds(0, y, | |
606 width(), buttons_view_->GetPreferredSize().height()); | |
607 } else if (managed_user_info_) { | |
608 gfx::Size icon_size = icon_view_->GetPreferredSize(); | |
609 gfx::Rect icon_bounds(0, y, icon_size.width(), icon_size.height()); | |
610 icon_view_->SetBoundsRect(icon_bounds); | |
611 int info_width = width() - icon_bounds.right() - | |
612 views::kRelatedControlSmallHorizontalSpacing; | |
613 int height = managed_user_info_->GetHeightForWidth(info_width); | |
614 managed_user_info_->SetBounds( | |
615 icon_bounds.right() + views::kRelatedControlSmallHorizontalSpacing, | |
616 y, info_width, height); | |
617 y += height + kItemMarginY + kSeparatorPaddingY; | |
618 separator_switch_users_->SetBounds(0, y, width(), separator_height); | |
619 y += separator_height + kSeparatorPaddingY; | |
620 int link_height = switch_profile_link_->GetPreferredSize().height(); | |
621 switch_profile_link_->SetBounds(0, y, width(), link_height); | |
622 } | |
623 } | |
624 | |
625 bool AvatarMenuBubbleView::AcceleratorPressed( | |
626 const ui::Accelerator& accelerator) { | |
627 if (accelerator.key_code() != ui::VKEY_DOWN && | |
628 accelerator.key_code() != ui::VKEY_UP) | |
629 return BubbleDelegateView::AcceleratorPressed(accelerator); | |
630 | |
631 if (item_views_.empty()) | |
632 return true; | |
633 | |
634 // Find the currently focused item. Note that if there is no focused item, the | |
635 // code below correctly handles a |focus_index| of -1. | |
636 int focus_index = -1; | |
637 for (size_t i = 0; i < item_views_.size(); ++i) { | |
638 if (item_views_[i]->HasFocus()) { | |
639 focus_index = i; | |
640 break; | |
641 } | |
642 } | |
643 | |
644 // Moved the focus up or down by 1. | |
645 if (accelerator.key_code() == ui::VKEY_DOWN) | |
646 focus_index = (focus_index + 1) % item_views_.size(); | |
647 else | |
648 focus_index = ((focus_index > 0) ? focus_index : item_views_.size()) - 1; | |
649 item_views_[focus_index]->RequestFocus(); | |
650 | |
651 return true; | |
652 } | |
653 | |
654 void AvatarMenuBubbleView::ButtonPressed(views::Button* sender, | |
655 const ui::Event& event) { | |
656 if (sender->tag() == IDS_PROFILES_MANAGE_PROFILES_BUTTON) { | |
657 std::string subpage = chrome::kSearchUsersSubPage; | |
658 chrome::ShowSettingsSubPage(browser_, subpage); | |
659 return; | |
660 } else if (sender->tag() == IDS_PROFILES_PROFILE_SIGNOUT_BUTTON) { | |
661 profiles::LockProfile(browser_->profile()); | |
662 return; | |
663 } | |
664 | |
665 for (size_t i = 0; i < item_views_.size(); ++i) { | |
666 ProfileItemView* item_view = item_views_[i]; | |
667 if (sender == item_view) { | |
668 // Clicking on the active profile shouldn't do anything. | |
669 if (!item_view->item().active) { | |
670 avatar_menu_->SwitchToProfile( | |
671 i, ui::DispositionFromEventFlags(event.flags()) == NEW_WINDOW, | |
672 ProfileMetrics::SWITCH_PROFILE_ICON); | |
673 } | |
674 break; | |
675 } | |
676 } | |
677 } | |
678 | |
679 void AvatarMenuBubbleView::LinkClicked(views::Link* source, int event_flags) { | |
680 if (source == buttons_view_) { | |
681 avatar_menu_->AddNewProfile(ProfileMetrics::ADD_NEW_USER_ICON); | |
682 return; | |
683 } | |
684 if (source == switch_profile_link_) { | |
685 expanded_ = true; | |
686 OnAvatarMenuChanged(avatar_menu_.get()); | |
687 return; | |
688 } | |
689 | |
690 for (size_t i = 0; i < item_views_.size(); ++i) { | |
691 ProfileItemView* item_view = item_views_[i]; | |
692 if (source == item_view->edit_link()) { | |
693 avatar_menu_->EditProfile(i); | |
694 return; | |
695 } | |
696 } | |
697 } | |
698 | |
699 gfx::Rect AvatarMenuBubbleView::GetAnchorRect() { | |
700 return anchor_rect_; | |
701 } | |
702 | |
703 void AvatarMenuBubbleView::Init() { | |
704 // Build the menu for the first time. | |
705 OnAvatarMenuChanged(avatar_menu_.get()); | |
706 AddAccelerator(ui::Accelerator(ui::VKEY_DOWN, ui::EF_NONE)); | |
707 AddAccelerator(ui::Accelerator(ui::VKEY_UP, ui::EF_NONE)); | |
708 } | |
709 | |
710 void AvatarMenuBubbleView::WindowClosing() { | |
711 DCHECK_EQ(avatar_bubble_, this); | |
712 avatar_bubble_ = NULL; | |
713 } | |
714 | |
715 void AvatarMenuBubbleView::InitMenuContents( | |
716 AvatarMenu* avatar_menu) { | |
717 for (size_t i = 0; i < avatar_menu->GetNumberOfItems(); ++i) { | |
718 const AvatarMenu::Item& item = avatar_menu->GetItemAt(i); | |
719 ProfileItemView* item_view = new ProfileItemView(item, | |
720 this, | |
721 avatar_menu_.get()); | |
722 item_view->SetAccessibleName(l10n_util::GetStringFUTF16( | |
723 IDS_PROFILES_SWITCH_TO_PROFILE_ACCESSIBLE_NAME, item.name)); | |
724 item_view->SetFocusable(true); | |
725 AddChildView(item_view); | |
726 item_views_.push_back(item_view); | |
727 } | |
728 | |
729 if (switches::IsNewProfileManagement()) { | |
730 separator_ = new views::Separator(views::Separator::HORIZONTAL); | |
731 AddChildView(separator_); | |
732 buttons_view_ = new ActionButtonView(this, browser_->profile()); | |
733 AddChildView(buttons_view_); | |
734 } else if (avatar_menu_->ShouldShowAddNewProfileLink()) { | |
735 views::Link* add_profile_link = new views::Link( | |
736 l10n_util::GetStringUTF16(IDS_PROFILES_CREATE_NEW_PROFILE_LINK)); | |
737 add_profile_link->set_listener(this); | |
738 add_profile_link->SetHorizontalAlignment(gfx::ALIGN_CENTER); | |
739 add_profile_link->SetBackgroundColor(color()); | |
740 separator_ = new views::Separator(views::Separator::HORIZONTAL); | |
741 AddChildView(separator_); | |
742 buttons_view_ = add_profile_link; | |
743 AddChildView(buttons_view_); | |
744 } | |
745 } | |
746 | |
747 void AvatarMenuBubbleView::InitManagedUserContents( | |
748 AvatarMenu* avatar_menu) { | |
749 // Show the profile of the managed user. | |
750 size_t active_index = avatar_menu->GetActiveProfileIndex(); | |
751 const AvatarMenu::Item& item = | |
752 avatar_menu->GetItemAt(active_index); | |
753 ProfileItemView* item_view = new ProfileItemView(item, | |
754 this, | |
755 avatar_menu_.get()); | |
756 item_view->SetAccessibleName(l10n_util::GetStringFUTF16( | |
757 IDS_PROFILES_SWITCH_TO_PROFILE_ACCESSIBLE_NAME, item.name)); | |
758 item_views_.push_back(item_view); | |
759 AddChildView(item_view); | |
760 separator_ = new views::Separator(views::Separator::HORIZONTAL); | |
761 AddChildView(separator_); | |
762 | |
763 // Add information about managed users. | |
764 managed_user_info_ = | |
765 new views::Label(avatar_menu_->GetManagedUserInformation(), | |
766 ui::ResourceBundle::GetSharedInstance().GetFontList( | |
767 ui::ResourceBundle::SmallFont)); | |
768 managed_user_info_->SetMultiLine(true); | |
769 managed_user_info_->SetHorizontalAlignment(gfx::ALIGN_LEFT); | |
770 managed_user_info_->SetBackgroundColor(color()); | |
771 AddChildView(managed_user_info_); | |
772 | |
773 // Add the managed user icon. | |
774 icon_view_ = new views::ImageView(); | |
775 icon_view_->SetImage(avatar_menu_->GetManagedUserIcon().ToImageSkia()); | |
776 AddChildView(icon_view_); | |
777 | |
778 // Add a link for switching profiles. | |
779 separator_switch_users_ = new views::Separator(views::Separator::HORIZONTAL); | |
780 AddChildView(separator_switch_users_); | |
781 switch_profile_link_ = new views::Link( | |
782 l10n_util::GetStringUTF16(IDS_PROFILES_SWITCH_PROFILE_LINK)); | |
783 switch_profile_link_->set_listener(this); | |
784 switch_profile_link_->SetHorizontalAlignment(gfx::ALIGN_CENTER); | |
785 switch_profile_link_->SetBackgroundColor(color()); | |
786 AddChildView(switch_profile_link_); | |
787 } | |
788 | |
789 void AvatarMenuBubbleView::OnAvatarMenuChanged( | |
790 AvatarMenu* avatar_menu) { | |
791 // Unset all our child view references and call RemoveAllChildViews() which | |
792 // will actually delete them. | |
793 buttons_view_ = NULL; | |
794 managed_user_info_ = NULL; | |
795 item_views_.clear(); | |
796 RemoveAllChildViews(true); | |
797 | |
798 if (avatar_menu_->GetManagedUserInformation().empty() || expanded_) | |
799 InitMenuContents(avatar_menu); | |
800 else | |
801 InitManagedUserContents(avatar_menu); | |
802 | |
803 // If the bubble has already been shown then resize and reposition the bubble. | |
804 Layout(); | |
805 if (GetBubbleFrameView()) | |
806 SizeToContents(); | |
807 } | |
808 | |
809 void AvatarMenuBubbleView::SetBackgroundColors() { | |
810 for (size_t i = 0; i < item_views_.size(); ++i) { | |
811 item_views_[i]->OnHighlightStateChanged(); | |
812 } | |
813 } | |
OLD | NEW |