NaCl: Update revision in DEPS, r12770 -> r12773
[chromium-blink-merge.git] / chrome / browser / ui / views / status_bubble_views.cc
blobc2717e60e1a780c3fb302fca5c998c2e199cf104
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/views/status_bubble_views.h"
7 #include <algorithm>
9 #include "base/bind.h"
10 #include "base/i18n/rtl.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "chrome/browser/themes/theme_properties.h"
15 #include "chrome/browser/ui/elide_url.h"
16 #include "net/base/net_util.h"
17 #include "third_party/skia/include/core/SkPaint.h"
18 #include "third_party/skia/include/core/SkPath.h"
19 #include "third_party/skia/include/core/SkRect.h"
20 #include "ui/base/theme_provider.h"
21 #include "ui/gfx/animation/animation_delegate.h"
22 #include "ui/gfx/animation/linear_animation.h"
23 #include "ui/gfx/canvas.h"
24 #include "ui/gfx/font_list.h"
25 #include "ui/gfx/point.h"
26 #include "ui/gfx/rect.h"
27 #include "ui/gfx/screen.h"
28 #include "ui/gfx/skia_util.h"
29 #include "ui/gfx/text_elider.h"
30 #include "ui/gfx/text_utils.h"
31 #include "ui/native_theme/native_theme.h"
32 #include "ui/views/controls/scrollbar/native_scroll_bar.h"
33 #include "ui/views/widget/root_view.h"
34 #include "ui/views/widget/widget.h"
35 #include "url/gurl.h"
37 #if defined(USE_AURA)
38 #include "ui/aura/window.h"
39 #endif
41 #if defined(USE_ASH)
42 #include "ash/wm/window_state.h"
43 #endif
45 // The alpha and color of the bubble's shadow.
46 static const SkColor kShadowColor = SkColorSetARGB(30, 0, 0, 0);
48 // The roundedness of the edges of our bubble.
49 static const int kBubbleCornerRadius = 4;
51 // How close the mouse can get to the infobubble before it starts sliding
52 // off-screen.
53 static const int kMousePadding = 20;
55 // The horizontal offset of the text within the status bubble, not including the
56 // outer shadow ring.
57 static const int kTextPositionX = 3;
59 // The minimum horizontal space between the (right) end of the text and the edge
60 // of the status bubble, not including the outer shadow ring.
61 static const int kTextHorizPadding = 1;
63 // Delays before we start hiding or showing the bubble after we receive a
64 // show or hide request.
65 static const int kShowDelay = 80;
66 static const int kHideDelay = 250;
68 // How long each fade should last for.
69 static const int kShowFadeDurationMS = 120;
70 static const int kHideFadeDurationMS = 200;
71 static const int kFramerate = 25;
73 // How long each expansion step should take.
74 static const int kMinExpansionStepDurationMS = 20;
75 static const int kMaxExpansionStepDurationMS = 150;
78 // StatusBubbleViews::StatusViewAnimation --------------------------------------
79 class StatusBubbleViews::StatusViewAnimation : public gfx::LinearAnimation,
80 public gfx::AnimationDelegate {
81 public:
82 StatusViewAnimation(StatusView* status_view,
83 double opacity_start,
84 double opacity_end);
85 virtual ~StatusViewAnimation();
87 double GetCurrentOpacity();
89 private:
90 // gfx::LinearAnimation:
91 virtual void AnimateToState(double state) OVERRIDE;
93 // gfx::AnimationDelegate:
94 virtual void AnimationEnded(const Animation* animation) OVERRIDE;
96 StatusView* status_view_;
98 // Start and end opacities for the current transition - note that as a
99 // fade-in can easily turn into a fade out, opacity_start_ is sometimes
100 // a value between 0 and 1.
101 double opacity_start_;
102 double opacity_end_;
104 DISALLOW_COPY_AND_ASSIGN(StatusViewAnimation);
108 // StatusBubbleViews::StatusView -----------------------------------------------
110 // StatusView manages the display of the bubble, applying text changes and
111 // fading in or out the bubble as required.
112 class StatusBubbleViews::StatusView : public views::View {
113 public:
114 // The bubble can be in one of many states:
115 enum BubbleState {
116 BUBBLE_HIDDEN, // Entirely BUBBLE_HIDDEN.
117 BUBBLE_HIDING_FADE, // In a fade-out transition.
118 BUBBLE_HIDING_TIMER, // Waiting before a fade-out.
119 BUBBLE_SHOWING_TIMER, // Waiting before a fade-in.
120 BUBBLE_SHOWING_FADE, // In a fade-in transition.
121 BUBBLE_SHOWN // Fully visible.
124 enum BubbleStyle {
125 STYLE_BOTTOM,
126 STYLE_FLOATING,
127 STYLE_STANDARD,
128 STYLE_STANDARD_RIGHT
131 StatusView(StatusBubble* status_bubble,
132 views::Widget* popup,
133 ui::ThemeProvider* theme_provider);
134 virtual ~StatusView();
136 // Set the bubble text to a certain value, hides the bubble if text is
137 // an empty string. Trigger animation sequence to display if
138 // |should_animate_open|.
139 void SetText(const base::string16& text, bool should_animate_open);
141 BubbleState state() const { return state_; }
142 BubbleStyle style() const { return style_; }
143 void SetStyle(BubbleStyle style);
145 // Show the bubble instantly.
146 void Show();
148 // Hide the bubble instantly.
149 void Hide();
151 // Resets any timers we have. Typically called when the user moves a
152 // mouse.
153 void ResetTimer();
155 // This call backs the StatusView in order to fade the bubble in and out.
156 void SetOpacity(double opacity);
158 // Depending on the state of the bubble this will either hide the popup or
159 // not.
160 void OnAnimationEnded();
162 private:
163 class InitialTimer;
165 // Manage the timers that control the delay before a fade begins or ends.
166 void StartTimer(base::TimeDelta time);
167 void OnTimer();
168 void CancelTimer();
169 void RestartTimer(base::TimeDelta delay);
171 // Manage the fades and starting and stopping the animations correctly.
172 void StartFade(double start, double end, int duration);
173 void StartHiding();
174 void StartShowing();
176 // views::View:
177 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
179 BubbleState state_;
180 BubbleStyle style_;
182 base::WeakPtrFactory<StatusBubbleViews::StatusView> timer_factory_;
184 scoped_ptr<StatusViewAnimation> animation_;
186 // Manager, owns us.
187 StatusBubble* status_bubble_;
189 // Handle to the widget that contains us.
190 views::Widget* popup_;
192 // The currently-displayed text.
193 base::string16 text_;
195 // Holds the theme provider of the frame that created us.
196 ui::ThemeProvider* theme_service_;
198 DISALLOW_COPY_AND_ASSIGN(StatusView);
201 StatusBubbleViews::StatusView::StatusView(StatusBubble* status_bubble,
202 views::Widget* popup,
203 ui::ThemeProvider* theme_provider)
204 : state_(BUBBLE_HIDDEN),
205 style_(STYLE_STANDARD),
206 timer_factory_(this),
207 animation_(new StatusViewAnimation(this, 0, 0)),
208 status_bubble_(status_bubble),
209 popup_(popup),
210 theme_service_(theme_provider) {
213 StatusBubbleViews::StatusView::~StatusView() {
214 animation_->Stop();
215 CancelTimer();
218 void StatusBubbleViews::StatusView::SetText(const base::string16& text,
219 bool should_animate_open) {
220 if (text.empty()) {
221 // The string was empty.
222 StartHiding();
223 } else {
224 // We want to show the string.
225 if (text != text_) {
226 text_ = text;
227 SchedulePaint();
229 if (should_animate_open)
230 StartShowing();
234 void StatusBubbleViews::StatusView::Show() {
235 animation_->Stop();
236 CancelTimer();
237 SetOpacity(1.0);
238 popup_->ShowInactive();
239 state_ = BUBBLE_SHOWN;
242 void StatusBubbleViews::StatusView::Hide() {
243 animation_->Stop();
244 CancelTimer();
245 SetOpacity(0.0);
246 text_.clear();
247 popup_->Hide();
248 state_ = BUBBLE_HIDDEN;
251 void StatusBubbleViews::StatusView::StartTimer(base::TimeDelta time) {
252 if (timer_factory_.HasWeakPtrs())
253 timer_factory_.InvalidateWeakPtrs();
255 base::MessageLoop::current()->PostDelayedTask(
256 FROM_HERE,
257 base::Bind(&StatusBubbleViews::StatusView::OnTimer,
258 timer_factory_.GetWeakPtr()),
259 time);
262 void StatusBubbleViews::StatusView::OnTimer() {
263 if (state_ == BUBBLE_HIDING_TIMER) {
264 state_ = BUBBLE_HIDING_FADE;
265 StartFade(1.0, 0.0, kHideFadeDurationMS);
266 } else if (state_ == BUBBLE_SHOWING_TIMER) {
267 state_ = BUBBLE_SHOWING_FADE;
268 StartFade(0.0, 1.0, kShowFadeDurationMS);
272 void StatusBubbleViews::StatusView::CancelTimer() {
273 if (timer_factory_.HasWeakPtrs())
274 timer_factory_.InvalidateWeakPtrs();
277 void StatusBubbleViews::StatusView::RestartTimer(base::TimeDelta delay) {
278 CancelTimer();
279 StartTimer(delay);
282 void StatusBubbleViews::StatusView::ResetTimer() {
283 if (state_ == BUBBLE_SHOWING_TIMER) {
284 // We hadn't yet begun showing anything when we received a new request
285 // for something to show, so we start from scratch.
286 RestartTimer(base::TimeDelta::FromMilliseconds(kShowDelay));
290 void StatusBubbleViews::StatusView::StartFade(double start,
291 double end,
292 int duration) {
293 animation_.reset(new StatusViewAnimation(this, start, end));
295 // This will also reset the currently-occurring animation.
296 animation_->SetDuration(duration);
297 animation_->Start();
300 void StatusBubbleViews::StatusView::StartHiding() {
301 if (state_ == BUBBLE_SHOWN) {
302 state_ = BUBBLE_HIDING_TIMER;
303 StartTimer(base::TimeDelta::FromMilliseconds(kHideDelay));
304 } else if (state_ == BUBBLE_SHOWING_TIMER) {
305 state_ = BUBBLE_HIDDEN;
306 popup_->Hide();
307 CancelTimer();
308 } else if (state_ == BUBBLE_SHOWING_FADE) {
309 state_ = BUBBLE_HIDING_FADE;
310 // Figure out where we are in the current fade.
311 double current_opacity = animation_->GetCurrentOpacity();
313 // Start a fade in the opposite direction.
314 StartFade(current_opacity, 0.0,
315 static_cast<int>(kHideFadeDurationMS * current_opacity));
319 void StatusBubbleViews::StatusView::StartShowing() {
320 if (state_ == BUBBLE_HIDDEN) {
321 popup_->ShowInactive();
322 state_ = BUBBLE_SHOWING_TIMER;
323 StartTimer(base::TimeDelta::FromMilliseconds(kShowDelay));
324 } else if (state_ == BUBBLE_HIDING_TIMER) {
325 state_ = BUBBLE_SHOWN;
326 CancelTimer();
327 } else if (state_ == BUBBLE_HIDING_FADE) {
328 // We're partway through a fade.
329 state_ = BUBBLE_SHOWING_FADE;
331 // Figure out where we are in the current fade.
332 double current_opacity = animation_->GetCurrentOpacity();
334 // Start a fade in the opposite direction.
335 StartFade(current_opacity, 1.0,
336 static_cast<int>(kShowFadeDurationMS * current_opacity));
337 } else if (state_ == BUBBLE_SHOWING_TIMER) {
338 // We hadn't yet begun showing anything when we received a new request
339 // for something to show, so we start from scratch.
340 ResetTimer();
344 void StatusBubbleViews::StatusView::SetOpacity(double opacity) {
345 popup_->SetOpacity(static_cast<unsigned char>(opacity * 255));
348 void StatusBubbleViews::StatusView::SetStyle(BubbleStyle style) {
349 if (style_ != style) {
350 style_ = style;
351 SchedulePaint();
355 void StatusBubbleViews::StatusView::OnAnimationEnded() {
356 if (state_ == BUBBLE_HIDING_FADE) {
357 state_ = BUBBLE_HIDDEN;
358 popup_->Hide();
359 } else if (state_ == BUBBLE_SHOWING_FADE) {
360 state_ = BUBBLE_SHOWN;
364 void StatusBubbleViews::StatusView::OnPaint(gfx::Canvas* canvas) {
365 SkPaint paint;
366 paint.setStyle(SkPaint::kFill_Style);
367 paint.setAntiAlias(true);
368 SkColor toolbar_color = theme_service_->GetColor(
369 ThemeProperties::COLOR_TOOLBAR);
370 paint.setColor(toolbar_color);
372 gfx::Rect popup_bounds = popup_->GetWindowBoundsInScreen();
374 // Figure out how to round the bubble's four corners.
375 SkScalar rad[8];
377 // Top Edges - if the bubble is in its bottom position (sticking downwards),
378 // then we square the top edges. Otherwise, we square the edges based on the
379 // position of the bubble within the window (the bubble is positioned in the
380 // southeast corner in RTL and in the southwest corner in LTR).
381 if (style_ == STYLE_BOTTOM) {
382 // Top Left corner.
383 rad[0] = 0;
384 rad[1] = 0;
386 // Top Right corner.
387 rad[2] = 0;
388 rad[3] = 0;
389 } else {
390 if (base::i18n::IsRTL() != (style_ == STYLE_STANDARD_RIGHT)) {
391 // The text is RtL or the bubble is on the right side (but not both).
393 // Top Left corner.
394 rad[0] = SkIntToScalar(kBubbleCornerRadius);
395 rad[1] = SkIntToScalar(kBubbleCornerRadius);
397 // Top Right corner.
398 rad[2] = 0;
399 rad[3] = 0;
400 } else {
401 // Top Left corner.
402 rad[0] = 0;
403 rad[1] = 0;
405 // Top Right corner.
406 rad[2] = SkIntToScalar(kBubbleCornerRadius);
407 rad[3] = SkIntToScalar(kBubbleCornerRadius);
411 // Bottom edges - square these off if the bubble is in its standard position
412 // (sticking upward).
413 if (style_ == STYLE_STANDARD || style_ == STYLE_STANDARD_RIGHT) {
414 // Bottom Right Corner.
415 rad[4] = 0;
416 rad[5] = 0;
418 // Bottom Left Corner.
419 rad[6] = 0;
420 rad[7] = 0;
421 } else {
422 // Bottom Right Corner.
423 rad[4] = SkIntToScalar(kBubbleCornerRadius);
424 rad[5] = SkIntToScalar(kBubbleCornerRadius);
426 // Bottom Left Corner.
427 rad[6] = SkIntToScalar(kBubbleCornerRadius);
428 rad[7] = SkIntToScalar(kBubbleCornerRadius);
431 // Draw the bubble's shadow.
432 int width = popup_bounds.width();
433 int height = popup_bounds.height();
434 SkRect rect(gfx::RectToSkRect(gfx::Rect(popup_bounds.size())));
435 SkPath shadow_path;
436 shadow_path.addRoundRect(rect, rad, SkPath::kCW_Direction);
437 SkPaint shadow_paint;
438 shadow_paint.setAntiAlias(true);
439 shadow_paint.setColor(kShadowColor);
440 canvas->DrawPath(shadow_path, shadow_paint);
442 // Draw the bubble.
443 rect.set(SkIntToScalar(kShadowThickness),
444 SkIntToScalar(kShadowThickness),
445 SkIntToScalar(width - kShadowThickness),
446 SkIntToScalar(height - kShadowThickness));
447 SkPath path;
448 path.addRoundRect(rect, rad, SkPath::kCW_Direction);
449 canvas->DrawPath(path, paint);
451 // Draw highlight text and then the text body. In order to make sure the text
452 // is aligned to the right on RTL UIs, we mirror the text bounds if the
453 // locale is RTL.
454 const gfx::FontList font_list;
455 int text_width = std::min(
456 gfx::GetStringWidth(text_, font_list),
457 width - (kShadowThickness * 2) - kTextPositionX - kTextHorizPadding);
458 int text_height = height - (kShadowThickness * 2);
459 gfx::Rect body_bounds(kShadowThickness + kTextPositionX,
460 kShadowThickness,
461 std::max(0, text_width),
462 std::max(0, text_height));
463 body_bounds.set_x(GetMirroredXForRect(body_bounds));
464 SkColor text_color =
465 theme_service_->GetColor(ThemeProperties::COLOR_STATUS_BAR_TEXT);
466 canvas->DrawStringRect(text_, font_list, text_color, body_bounds);
470 // StatusBubbleViews::StatusViewAnimation --------------------------------------
472 StatusBubbleViews::StatusViewAnimation::StatusViewAnimation(
473 StatusView* status_view,
474 double opacity_start,
475 double opacity_end)
476 : gfx::LinearAnimation(kFramerate, this),
477 status_view_(status_view),
478 opacity_start_(opacity_start),
479 opacity_end_(opacity_end) {
482 StatusBubbleViews::StatusViewAnimation::~StatusViewAnimation() {
483 // Remove ourself as a delegate so that we don't get notified when
484 // animations end as a result of destruction.
485 set_delegate(NULL);
488 double StatusBubbleViews::StatusViewAnimation::GetCurrentOpacity() {
489 return opacity_start_ + (opacity_end_ - opacity_start_) *
490 gfx::LinearAnimation::GetCurrentValue();
493 void StatusBubbleViews::StatusViewAnimation::AnimateToState(double state) {
494 status_view_->SetOpacity(GetCurrentOpacity());
497 void StatusBubbleViews::StatusViewAnimation::AnimationEnded(
498 const gfx::Animation* animation) {
499 status_view_->SetOpacity(opacity_end_);
500 status_view_->OnAnimationEnded();
503 // StatusBubbleViews::StatusViewExpander ---------------------------------------
505 // Manages the expansion and contraction of the status bubble as it accommodates
506 // URLs too long to fit in the standard bubble. Changes are passed through the
507 // StatusView to paint.
508 class StatusBubbleViews::StatusViewExpander : public gfx::LinearAnimation,
509 public gfx::AnimationDelegate {
510 public:
511 StatusViewExpander(StatusBubbleViews* status_bubble,
512 StatusView* status_view)
513 : gfx::LinearAnimation(kFramerate, this),
514 status_bubble_(status_bubble),
515 status_view_(status_view),
516 expansion_start_(0),
517 expansion_end_(0) {
520 // Manage the expansion of the bubble.
521 void StartExpansion(const base::string16& expanded_text,
522 int current_width,
523 int expansion_end);
525 // Set width of fully expanded bubble.
526 void SetExpandedWidth(int expanded_width);
528 private:
529 // Animation functions.
530 int GetCurrentBubbleWidth();
531 void SetBubbleWidth(int width);
532 virtual void AnimateToState(double state) OVERRIDE;
533 virtual void AnimationEnded(const gfx::Animation* animation) OVERRIDE;
535 // Manager that owns us.
536 StatusBubbleViews* status_bubble_;
538 // Change the bounds and text of this view.
539 StatusView* status_view_;
541 // Text elided (if needed) to fit maximum status bar width.
542 base::string16 expanded_text_;
544 // Widths at expansion start and end.
545 int expansion_start_;
546 int expansion_end_;
549 void StatusBubbleViews::StatusViewExpander::AnimateToState(double state) {
550 SetBubbleWidth(GetCurrentBubbleWidth());
553 void StatusBubbleViews::StatusViewExpander::AnimationEnded(
554 const gfx::Animation* animation) {
555 SetBubbleWidth(expansion_end_);
556 status_view_->SetText(expanded_text_, false);
559 void StatusBubbleViews::StatusViewExpander::StartExpansion(
560 const base::string16& expanded_text,
561 int expansion_start,
562 int expansion_end) {
563 expanded_text_ = expanded_text;
564 expansion_start_ = expansion_start;
565 expansion_end_ = expansion_end;
566 int min_duration = std::max(kMinExpansionStepDurationMS,
567 static_cast<int>(kMaxExpansionStepDurationMS *
568 (expansion_end - expansion_start) / 100.0));
569 SetDuration(std::min(kMaxExpansionStepDurationMS, min_duration));
570 Start();
573 int StatusBubbleViews::StatusViewExpander::GetCurrentBubbleWidth() {
574 return static_cast<int>(expansion_start_ +
575 (expansion_end_ - expansion_start_) *
576 gfx::LinearAnimation::GetCurrentValue());
579 void StatusBubbleViews::StatusViewExpander::SetBubbleWidth(int width) {
580 status_bubble_->SetBubbleWidth(width);
581 status_view_->SchedulePaint();
585 // StatusBubbleViews -----------------------------------------------------------
587 const int StatusBubbleViews::kShadowThickness = 1;
589 StatusBubbleViews::StatusBubbleViews(views::View* base_view)
590 : contains_mouse_(false),
591 offset_(0),
592 opacity_(0),
593 base_view_(base_view),
594 view_(NULL),
595 download_shelf_is_visible_(false),
596 is_expanded_(false),
597 expand_timer_factory_(this) {
598 expand_view_.reset();
601 StatusBubbleViews::~StatusBubbleViews() {
602 CancelExpandTimer();
603 if (popup_.get())
604 popup_->CloseNow();
607 void StatusBubbleViews::Init() {
608 if (!popup_.get()) {
609 popup_.reset(new views::Widget);
610 views::Widget* frame = base_view_->GetWidget();
611 if (!view_)
612 view_ = new StatusView(this, popup_.get(), frame->GetThemeProvider());
613 if (!expand_view_.get())
614 expand_view_.reset(new StatusViewExpander(this, view_));
615 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
616 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
617 params.accept_events = false;
618 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
619 params.parent = frame->GetNativeView();
620 params.context = frame->GetNativeView();
621 popup_->Init(params);
622 #if defined(USE_AURA)
623 popup_->GetNativeView()->SetName("StatusBubbleViews");
624 #endif
625 // We do our own animation and don't want any from the system.
626 popup_->SetVisibilityChangedAnimationsEnabled(false);
627 popup_->SetOpacity(0x00);
628 popup_->SetContentsView(view_);
629 #if defined(USE_ASH)
630 ash::wm::GetWindowState(popup_->GetNativeWindow())->
631 set_ignored_by_shelf(true);
632 #endif
633 RepositionPopup();
637 void StatusBubbleViews::Reposition() {
638 // In restored mode, the client area has a client edge between it and the
639 // frame.
640 int overlap = kShadowThickness;
641 // The extra pixels defined by kClientEdgeThickness is only drawn in frame
642 // content border on windows for non-aura build.
643 #if !defined(USE_ASH)
644 overlap +=
645 IsFrameMaximized() ? 0 : views::NonClientFrameView::kClientEdgeThickness;
646 #endif
647 int height = GetPreferredSize().height();
648 int base_view_height = base_view()->bounds().height();
649 gfx::Point origin(-overlap, base_view_height - height + overlap);
650 SetBounds(origin.x(), origin.y(), base_view()->bounds().width() / 3, height);
653 void StatusBubbleViews::RepositionPopup() {
654 if (popup_.get()) {
655 gfx::Point top_left;
656 views::View::ConvertPointToScreen(base_view_, &top_left);
658 popup_->SetBounds(gfx::Rect(top_left.x() + position_.x(),
659 top_left.y() + position_.y(),
660 size_.width(), size_.height()));
664 gfx::Size StatusBubbleViews::GetPreferredSize() {
665 return gfx::Size(0, gfx::FontList().GetHeight() + kTotalVerticalPadding);
668 void StatusBubbleViews::SetBounds(int x, int y, int w, int h) {
669 original_position_.SetPoint(x, y);
670 position_.SetPoint(base_view_->GetMirroredXWithWidthInView(x, w), y);
671 size_.SetSize(w, h);
672 RepositionPopup();
673 if (popup_.get() && contains_mouse_)
674 AvoidMouse(last_mouse_moved_location_);
677 void StatusBubbleViews::SetStatus(const base::string16& status_text) {
678 if (size_.IsEmpty())
679 return; // We have no bounds, don't attempt to show the popup.
681 if (status_text_ == status_text && !status_text.empty())
682 return;
684 if (!IsFrameVisible())
685 return; // Don't show anything if the parent isn't visible.
687 Init();
688 status_text_ = status_text;
689 if (!status_text_.empty()) {
690 view_->SetText(status_text, true);
691 view_->Show();
692 } else if (!url_text_.empty()) {
693 view_->SetText(url_text_, true);
694 } else {
695 view_->SetText(base::string16(), true);
699 void StatusBubbleViews::SetURL(const GURL& url, const std::string& languages) {
700 url_ = url;
701 languages_ = languages;
702 if (size_.IsEmpty())
703 return; // We have no bounds, don't attempt to show the popup.
705 Init();
707 // If we want to clear a displayed URL but there is a status still to
708 // display, display that status instead.
709 if (url.is_empty() && !status_text_.empty()) {
710 url_text_ = base::string16();
711 if (IsFrameVisible())
712 view_->SetText(status_text_, true);
713 return;
716 // Reset expansion state only when bubble is completely hidden.
717 if (view_->state() == StatusView::BUBBLE_HIDDEN) {
718 is_expanded_ = false;
719 SetBubbleWidth(GetStandardStatusBubbleWidth());
722 // Set Elided Text corresponding to the GURL object.
723 gfx::Rect popup_bounds = popup_->GetWindowBoundsInScreen();
724 int text_width = static_cast<int>(popup_bounds.width() -
725 (kShadowThickness * 2) - kTextPositionX - kTextHorizPadding - 1);
726 url_text_ = ElideUrl(url, gfx::FontList(), text_width, languages);
728 // An URL is always treated as a left-to-right string. On right-to-left UIs
729 // we need to explicitly mark the URL as LTR to make sure it is displayed
730 // correctly.
731 url_text_ = base::i18n::GetDisplayStringInLTRDirectionality(url_text_);
733 if (IsFrameVisible()) {
734 view_->SetText(url_text_, true);
736 CancelExpandTimer();
738 // If bubble is already in expanded state, shift to adjust to new text
739 // size (shrinking or expanding). Otherwise delay.
740 if (is_expanded_ && !url.is_empty()) {
741 ExpandBubble();
742 } else if (net::FormatUrl(url, languages).length() > url_text_.length()) {
743 base::MessageLoop::current()->PostDelayedTask(
744 FROM_HERE,
745 base::Bind(&StatusBubbleViews::ExpandBubble,
746 expand_timer_factory_.GetWeakPtr()),
747 base::TimeDelta::FromMilliseconds(kExpandHoverDelayMS));
752 void StatusBubbleViews::Hide() {
753 status_text_ = base::string16();
754 url_text_ = base::string16();
755 if (view_)
756 view_->Hide();
759 void StatusBubbleViews::MouseMoved(const gfx::Point& location,
760 bool left_content) {
761 contains_mouse_ = !left_content;
762 if (left_content) {
763 RepositionPopup();
764 return;
766 last_mouse_moved_location_ = location;
768 if (view_) {
769 view_->ResetTimer();
771 if (view_->state() != StatusView::BUBBLE_HIDDEN &&
772 view_->state() != StatusView::BUBBLE_HIDING_FADE &&
773 view_->state() != StatusView::BUBBLE_HIDING_TIMER) {
774 AvoidMouse(location);
779 void StatusBubbleViews::UpdateDownloadShelfVisibility(bool visible) {
780 download_shelf_is_visible_ = visible;
783 void StatusBubbleViews::AvoidMouse(const gfx::Point& location) {
784 // Get the position of the frame.
785 gfx::Point top_left;
786 views::View::ConvertPointToScreen(base_view_, &top_left);
787 // Border included.
788 int window_width = base_view_->GetLocalBounds().width();
790 // Get the cursor position relative to the popup.
791 gfx::Point relative_location = location;
792 if (base::i18n::IsRTL()) {
793 int top_right_x = top_left.x() + window_width;
794 relative_location.set_x(top_right_x - relative_location.x());
795 } else {
796 relative_location.set_x(
797 relative_location.x() - (top_left.x() + position_.x()));
799 relative_location.set_y(
800 relative_location.y() - (top_left.y() + position_.y()));
802 // If the mouse is in a position where we think it would move the
803 // status bubble, figure out where and how the bubble should be moved.
804 if (relative_location.y() > -kMousePadding &&
805 relative_location.x() < size_.width() + kMousePadding) {
806 int offset = kMousePadding + relative_location.y();
808 // Make the movement non-linear.
809 offset = offset * offset / kMousePadding;
811 // When the mouse is entering from the right, we want the offset to be
812 // scaled by how horizontally far away the cursor is from the bubble.
813 if (relative_location.x() > size_.width()) {
814 offset = static_cast<int>(static_cast<float>(offset) * (
815 static_cast<float>(kMousePadding -
816 (relative_location.x() - size_.width())) /
817 static_cast<float>(kMousePadding)));
820 // Cap the offset and change the visual presentation of the bubble
821 // depending on where it ends up (so that rounded corners square off
822 // and mate to the edges of the tab content).
823 if (offset >= size_.height() - kShadowThickness * 2) {
824 offset = size_.height() - kShadowThickness * 2;
825 view_->SetStyle(StatusView::STYLE_BOTTOM);
826 } else if (offset > kBubbleCornerRadius / 2 - kShadowThickness) {
827 view_->SetStyle(StatusView::STYLE_FLOATING);
828 } else {
829 view_->SetStyle(StatusView::STYLE_STANDARD);
832 // Check if the bubble sticks out from the monitor or will obscure
833 // download shelf.
834 gfx::NativeView window = base_view_->GetWidget()->GetNativeView();
835 gfx::Rect monitor_rect = gfx::Screen::GetScreenFor(window)->
836 GetDisplayNearestWindow(window).work_area();
837 const int bubble_bottom_y = top_left.y() + position_.y() + size_.height();
839 if (bubble_bottom_y + offset > monitor_rect.height() ||
840 (download_shelf_is_visible_ &&
841 (view_->style() == StatusView::STYLE_FLOATING ||
842 view_->style() == StatusView::STYLE_BOTTOM))) {
843 // The offset is still too large. Move the bubble to the right and reset
844 // Y offset_ to zero.
845 view_->SetStyle(StatusView::STYLE_STANDARD_RIGHT);
846 offset_ = 0;
848 // Subtract border width + bubble width.
849 int right_position_x = window_width - (position_.x() + size_.width());
850 popup_->SetBounds(gfx::Rect(top_left.x() + right_position_x,
851 top_left.y() + position_.y(),
852 size_.width(), size_.height()));
853 } else {
854 offset_ = offset;
855 popup_->SetBounds(gfx::Rect(top_left.x() + position_.x(),
856 top_left.y() + position_.y() + offset_,
857 size_.width(), size_.height()));
859 } else if (offset_ != 0 ||
860 view_->style() == StatusView::STYLE_STANDARD_RIGHT) {
861 offset_ = 0;
862 view_->SetStyle(StatusView::STYLE_STANDARD);
863 popup_->SetBounds(gfx::Rect(top_left.x() + position_.x(),
864 top_left.y() + position_.y(),
865 size_.width(), size_.height()));
869 bool StatusBubbleViews::IsFrameVisible() {
870 views::Widget* frame = base_view_->GetWidget();
871 if (!frame->IsVisible())
872 return false;
874 views::Widget* window = frame->GetTopLevelWidget();
875 return !window || !window->IsMinimized();
878 bool StatusBubbleViews::IsFrameMaximized() {
879 views::Widget* frame = base_view_->GetWidget();
880 views::Widget* window = frame->GetTopLevelWidget();
881 return window && window->IsMaximized();
884 void StatusBubbleViews::ExpandBubble() {
885 // Elide URL to maximum possible size, then check actual length (it may
886 // still be too long to fit) before expanding bubble.
887 gfx::Rect popup_bounds = popup_->GetWindowBoundsInScreen();
888 int max_status_bubble_width = GetMaxStatusBubbleWidth();
889 const gfx::FontList font_list;
890 url_text_ = ElideUrl(url_, font_list, max_status_bubble_width, languages_);
891 int expanded_bubble_width =
892 std::max(GetStandardStatusBubbleWidth(),
893 std::min(gfx::GetStringWidth(url_text_, font_list) +
894 (kShadowThickness * 2) + kTextPositionX +
895 kTextHorizPadding + 1,
896 max_status_bubble_width));
897 is_expanded_ = true;
898 expand_view_->StartExpansion(url_text_, popup_bounds.width(),
899 expanded_bubble_width);
902 int StatusBubbleViews::GetStandardStatusBubbleWidth() {
903 return base_view_->bounds().width() / 3;
906 int StatusBubbleViews::GetMaxStatusBubbleWidth() {
907 const ui::NativeTheme* theme = base_view_->GetNativeTheme();
908 return static_cast<int>(std::max(0, base_view_->bounds().width() -
909 (kShadowThickness * 2) - kTextPositionX - kTextHorizPadding - 1 -
910 views::NativeScrollBar::GetVerticalScrollBarWidth(theme)));
913 void StatusBubbleViews::SetBubbleWidth(int width) {
914 size_.set_width(width);
915 SetBounds(original_position_.x(), original_position_.y(),
916 size_.width(), size_.height());
919 void StatusBubbleViews::CancelExpandTimer() {
920 if (expand_timer_factory_.HasWeakPtrs())
921 expand_timer_factory_.InvalidateWeakPtrs();