[Metrics] Make MetricsStateManager take a callback param to check if UMA is enabled.
[chromium-blink-merge.git] / chrome / browser / ui / views / status_bubble_views.cc
blobc566ea7e12e29768f1ca6e1be3e6023edc2e0e46
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/aura/window.h"
21 #include "ui/base/theme_provider.h"
22 #include "ui/gfx/animation/animation_delegate.h"
23 #include "ui/gfx/animation/linear_animation.h"
24 #include "ui/gfx/canvas.h"
25 #include "ui/gfx/font_list.h"
26 #include "ui/gfx/point.h"
27 #include "ui/gfx/rect.h"
28 #include "ui/gfx/screen.h"
29 #include "ui/gfx/skia_util.h"
30 #include "ui/gfx/text_elider.h"
31 #include "ui/gfx/text_utils.h"
32 #include "ui/native_theme/native_theme.h"
33 #include "ui/views/controls/scrollbar/native_scroll_bar.h"
34 #include "ui/views/widget/root_view.h"
35 #include "ui/views/widget/widget.h"
36 #include "url/gurl.h"
38 #if defined(USE_ASH)
39 #include "ash/wm/window_state.h"
40 #endif
42 // The alpha and color of the bubble's shadow.
43 static const SkColor kShadowColor = SkColorSetARGB(30, 0, 0, 0);
45 // The roundedness of the edges of our bubble.
46 static const int kBubbleCornerRadius = 4;
48 // How close the mouse can get to the infobubble before it starts sliding
49 // off-screen.
50 static const int kMousePadding = 20;
52 // The horizontal offset of the text within the status bubble, not including the
53 // outer shadow ring.
54 static const int kTextPositionX = 3;
56 // The minimum horizontal space between the (right) end of the text and the edge
57 // of the status bubble, not including the outer shadow ring.
58 static const int kTextHorizPadding = 1;
60 // Delays before we start hiding or showing the bubble after we receive a
61 // show or hide request.
62 static const int kShowDelay = 80;
63 static const int kHideDelay = 250;
65 // How long each fade should last for.
66 static const int kShowFadeDurationMS = 120;
67 static const int kHideFadeDurationMS = 200;
68 static const int kFramerate = 25;
70 // How long each expansion step should take.
71 static const int kMinExpansionStepDurationMS = 20;
72 static const int kMaxExpansionStepDurationMS = 150;
75 // StatusBubbleViews::StatusViewAnimation --------------------------------------
76 class StatusBubbleViews::StatusViewAnimation : public gfx::LinearAnimation,
77 public gfx::AnimationDelegate {
78 public:
79 StatusViewAnimation(StatusView* status_view,
80 double opacity_start,
81 double opacity_end);
82 virtual ~StatusViewAnimation();
84 double GetCurrentOpacity();
86 private:
87 // gfx::LinearAnimation:
88 virtual void AnimateToState(double state) OVERRIDE;
90 // gfx::AnimationDelegate:
91 virtual void AnimationEnded(const Animation* animation) OVERRIDE;
93 StatusView* status_view_;
95 // Start and end opacities for the current transition - note that as a
96 // fade-in can easily turn into a fade out, opacity_start_ is sometimes
97 // a value between 0 and 1.
98 double opacity_start_;
99 double opacity_end_;
101 DISALLOW_COPY_AND_ASSIGN(StatusViewAnimation);
105 // StatusBubbleViews::StatusView -----------------------------------------------
107 // StatusView manages the display of the bubble, applying text changes and
108 // fading in or out the bubble as required.
109 class StatusBubbleViews::StatusView : public views::View {
110 public:
111 // The bubble can be in one of many states:
112 enum BubbleState {
113 BUBBLE_HIDDEN, // Entirely BUBBLE_HIDDEN.
114 BUBBLE_HIDING_FADE, // In a fade-out transition.
115 BUBBLE_HIDING_TIMER, // Waiting before a fade-out.
116 BUBBLE_SHOWING_TIMER, // Waiting before a fade-in.
117 BUBBLE_SHOWING_FADE, // In a fade-in transition.
118 BUBBLE_SHOWN // Fully visible.
121 enum BubbleStyle {
122 STYLE_BOTTOM,
123 STYLE_FLOATING,
124 STYLE_STANDARD,
125 STYLE_STANDARD_RIGHT
128 StatusView(views::Widget* popup,
129 ui::ThemeProvider* theme_provider);
130 virtual ~StatusView();
132 // Set the bubble text to a certain value, hides the bubble if text is
133 // an empty string. Trigger animation sequence to display if
134 // |should_animate_open|.
135 void SetText(const base::string16& text, bool should_animate_open);
137 BubbleState state() const { return state_; }
138 BubbleStyle style() const { return style_; }
139 void SetStyle(BubbleStyle style);
141 // Show the bubble instantly.
142 void Show();
144 // Hide the bubble instantly.
145 void Hide();
147 // Resets any timers we have. Typically called when the user moves a
148 // mouse.
149 void ResetTimer();
151 // This call backs the StatusView in order to fade the bubble in and out.
152 void SetOpacity(double opacity);
154 // Depending on the state of the bubble this will either hide the popup or
155 // not.
156 void OnAnimationEnded();
158 private:
159 class InitialTimer;
161 // Manage the timers that control the delay before a fade begins or ends.
162 void StartTimer(base::TimeDelta time);
163 void OnTimer();
164 void CancelTimer();
165 void RestartTimer(base::TimeDelta delay);
167 // Manage the fades and starting and stopping the animations correctly.
168 void StartFade(double start, double end, int duration);
169 void StartHiding();
170 void StartShowing();
172 // views::View:
173 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
175 BubbleState state_;
176 BubbleStyle style_;
178 base::WeakPtrFactory<StatusBubbleViews::StatusView> timer_factory_;
180 scoped_ptr<StatusViewAnimation> animation_;
182 // Handle to the widget that contains us.
183 views::Widget* popup_;
185 // The currently-displayed text.
186 base::string16 text_;
188 // Holds the theme provider of the frame that created us.
189 ui::ThemeProvider* theme_service_;
191 DISALLOW_COPY_AND_ASSIGN(StatusView);
194 StatusBubbleViews::StatusView::StatusView(views::Widget* popup,
195 ui::ThemeProvider* theme_provider)
196 : state_(BUBBLE_HIDDEN),
197 style_(STYLE_STANDARD),
198 timer_factory_(this),
199 animation_(new StatusViewAnimation(this, 0, 0)),
200 popup_(popup),
201 theme_service_(theme_provider) {
204 StatusBubbleViews::StatusView::~StatusView() {
205 animation_->Stop();
206 CancelTimer();
209 void StatusBubbleViews::StatusView::SetText(const base::string16& text,
210 bool should_animate_open) {
211 if (text.empty()) {
212 // The string was empty.
213 StartHiding();
214 } else {
215 // We want to show the string.
216 if (text != text_) {
217 text_ = text;
218 SchedulePaint();
220 if (should_animate_open)
221 StartShowing();
225 void StatusBubbleViews::StatusView::Show() {
226 animation_->Stop();
227 CancelTimer();
228 SetOpacity(1.0);
229 popup_->ShowInactive();
230 state_ = BUBBLE_SHOWN;
233 void StatusBubbleViews::StatusView::Hide() {
234 animation_->Stop();
235 CancelTimer();
236 SetOpacity(0.0);
237 text_.clear();
238 popup_->Hide();
239 state_ = BUBBLE_HIDDEN;
242 void StatusBubbleViews::StatusView::StartTimer(base::TimeDelta time) {
243 if (timer_factory_.HasWeakPtrs())
244 timer_factory_.InvalidateWeakPtrs();
246 base::MessageLoop::current()->PostDelayedTask(
247 FROM_HERE,
248 base::Bind(&StatusBubbleViews::StatusView::OnTimer,
249 timer_factory_.GetWeakPtr()),
250 time);
253 void StatusBubbleViews::StatusView::OnTimer() {
254 if (state_ == BUBBLE_HIDING_TIMER) {
255 state_ = BUBBLE_HIDING_FADE;
256 StartFade(1.0, 0.0, kHideFadeDurationMS);
257 } else if (state_ == BUBBLE_SHOWING_TIMER) {
258 state_ = BUBBLE_SHOWING_FADE;
259 StartFade(0.0, 1.0, kShowFadeDurationMS);
263 void StatusBubbleViews::StatusView::CancelTimer() {
264 if (timer_factory_.HasWeakPtrs())
265 timer_factory_.InvalidateWeakPtrs();
268 void StatusBubbleViews::StatusView::RestartTimer(base::TimeDelta delay) {
269 CancelTimer();
270 StartTimer(delay);
273 void StatusBubbleViews::StatusView::ResetTimer() {
274 if (state_ == BUBBLE_SHOWING_TIMER) {
275 // We hadn't yet begun showing anything when we received a new request
276 // for something to show, so we start from scratch.
277 RestartTimer(base::TimeDelta::FromMilliseconds(kShowDelay));
281 void StatusBubbleViews::StatusView::StartFade(double start,
282 double end,
283 int duration) {
284 animation_.reset(new StatusViewAnimation(this, start, end));
286 // This will also reset the currently-occurring animation.
287 animation_->SetDuration(duration);
288 animation_->Start();
291 void StatusBubbleViews::StatusView::StartHiding() {
292 if (state_ == BUBBLE_SHOWN) {
293 state_ = BUBBLE_HIDING_TIMER;
294 StartTimer(base::TimeDelta::FromMilliseconds(kHideDelay));
295 } else if (state_ == BUBBLE_SHOWING_TIMER) {
296 state_ = BUBBLE_HIDDEN;
297 popup_->Hide();
298 CancelTimer();
299 } else if (state_ == BUBBLE_SHOWING_FADE) {
300 state_ = BUBBLE_HIDING_FADE;
301 // Figure out where we are in the current fade.
302 double current_opacity = animation_->GetCurrentOpacity();
304 // Start a fade in the opposite direction.
305 StartFade(current_opacity, 0.0,
306 static_cast<int>(kHideFadeDurationMS * current_opacity));
310 void StatusBubbleViews::StatusView::StartShowing() {
311 if (state_ == BUBBLE_HIDDEN) {
312 popup_->ShowInactive();
313 state_ = BUBBLE_SHOWING_TIMER;
314 StartTimer(base::TimeDelta::FromMilliseconds(kShowDelay));
315 } else if (state_ == BUBBLE_HIDING_TIMER) {
316 state_ = BUBBLE_SHOWN;
317 CancelTimer();
318 } else if (state_ == BUBBLE_HIDING_FADE) {
319 // We're partway through a fade.
320 state_ = BUBBLE_SHOWING_FADE;
322 // Figure out where we are in the current fade.
323 double current_opacity = animation_->GetCurrentOpacity();
325 // Start a fade in the opposite direction.
326 StartFade(current_opacity, 1.0,
327 static_cast<int>(kShowFadeDurationMS * current_opacity));
328 } else if (state_ == BUBBLE_SHOWING_TIMER) {
329 // We hadn't yet begun showing anything when we received a new request
330 // for something to show, so we start from scratch.
331 ResetTimer();
335 void StatusBubbleViews::StatusView::SetOpacity(double opacity) {
336 popup_->SetOpacity(static_cast<unsigned char>(opacity * 255));
339 void StatusBubbleViews::StatusView::SetStyle(BubbleStyle style) {
340 if (style_ != style) {
341 style_ = style;
342 SchedulePaint();
346 void StatusBubbleViews::StatusView::OnAnimationEnded() {
347 if (state_ == BUBBLE_HIDING_FADE) {
348 state_ = BUBBLE_HIDDEN;
349 popup_->Hide();
350 } else if (state_ == BUBBLE_SHOWING_FADE) {
351 state_ = BUBBLE_SHOWN;
355 void StatusBubbleViews::StatusView::OnPaint(gfx::Canvas* canvas) {
356 SkPaint paint;
357 paint.setStyle(SkPaint::kFill_Style);
358 paint.setAntiAlias(true);
359 SkColor toolbar_color = theme_service_->GetColor(
360 ThemeProperties::COLOR_TOOLBAR);
361 paint.setColor(toolbar_color);
363 gfx::Rect popup_bounds = popup_->GetWindowBoundsInScreen();
365 // Figure out how to round the bubble's four corners.
366 SkScalar rad[8];
368 // Top Edges - if the bubble is in its bottom position (sticking downwards),
369 // then we square the top edges. Otherwise, we square the edges based on the
370 // position of the bubble within the window (the bubble is positioned in the
371 // southeast corner in RTL and in the southwest corner in LTR).
372 if (style_ == STYLE_BOTTOM) {
373 // Top Left corner.
374 rad[0] = 0;
375 rad[1] = 0;
377 // Top Right corner.
378 rad[2] = 0;
379 rad[3] = 0;
380 } else {
381 if (base::i18n::IsRTL() != (style_ == STYLE_STANDARD_RIGHT)) {
382 // The text is RtL or the bubble is on the right side (but not both).
384 // Top Left corner.
385 rad[0] = SkIntToScalar(kBubbleCornerRadius);
386 rad[1] = SkIntToScalar(kBubbleCornerRadius);
388 // Top Right corner.
389 rad[2] = 0;
390 rad[3] = 0;
391 } else {
392 // Top Left corner.
393 rad[0] = 0;
394 rad[1] = 0;
396 // Top Right corner.
397 rad[2] = SkIntToScalar(kBubbleCornerRadius);
398 rad[3] = SkIntToScalar(kBubbleCornerRadius);
402 // Bottom edges - square these off if the bubble is in its standard position
403 // (sticking upward).
404 if (style_ == STYLE_STANDARD || style_ == STYLE_STANDARD_RIGHT) {
405 // Bottom Right Corner.
406 rad[4] = 0;
407 rad[5] = 0;
409 // Bottom Left Corner.
410 rad[6] = 0;
411 rad[7] = 0;
412 } else {
413 // Bottom Right Corner.
414 rad[4] = SkIntToScalar(kBubbleCornerRadius);
415 rad[5] = SkIntToScalar(kBubbleCornerRadius);
417 // Bottom Left Corner.
418 rad[6] = SkIntToScalar(kBubbleCornerRadius);
419 rad[7] = SkIntToScalar(kBubbleCornerRadius);
422 // Draw the bubble's shadow.
423 int width = popup_bounds.width();
424 int height = popup_bounds.height();
425 SkRect rect(gfx::RectToSkRect(gfx::Rect(popup_bounds.size())));
426 SkPath shadow_path;
427 shadow_path.addRoundRect(rect, rad, SkPath::kCW_Direction);
428 SkPaint shadow_paint;
429 shadow_paint.setAntiAlias(true);
430 shadow_paint.setColor(kShadowColor);
431 canvas->DrawPath(shadow_path, shadow_paint);
433 // Draw the bubble.
434 rect.set(SkIntToScalar(kShadowThickness),
435 SkIntToScalar(kShadowThickness),
436 SkIntToScalar(width - kShadowThickness),
437 SkIntToScalar(height - kShadowThickness));
438 SkPath path;
439 path.addRoundRect(rect, rad, SkPath::kCW_Direction);
440 canvas->DrawPath(path, paint);
442 // Draw highlight text and then the text body. In order to make sure the text
443 // is aligned to the right on RTL UIs, we mirror the text bounds if the
444 // locale is RTL.
445 const gfx::FontList font_list;
446 int text_width = std::min(
447 gfx::GetStringWidth(text_, font_list),
448 width - (kShadowThickness * 2) - kTextPositionX - kTextHorizPadding);
449 int text_height = height - (kShadowThickness * 2);
450 gfx::Rect body_bounds(kShadowThickness + kTextPositionX,
451 kShadowThickness,
452 std::max(0, text_width),
453 std::max(0, text_height));
454 body_bounds.set_x(GetMirroredXForRect(body_bounds));
455 SkColor text_color =
456 theme_service_->GetColor(ThemeProperties::COLOR_STATUS_BAR_TEXT);
457 canvas->DrawStringRect(text_, font_list, text_color, body_bounds);
461 // StatusBubbleViews::StatusViewAnimation --------------------------------------
463 StatusBubbleViews::StatusViewAnimation::StatusViewAnimation(
464 StatusView* status_view,
465 double opacity_start,
466 double opacity_end)
467 : gfx::LinearAnimation(kFramerate, this),
468 status_view_(status_view),
469 opacity_start_(opacity_start),
470 opacity_end_(opacity_end) {
473 StatusBubbleViews::StatusViewAnimation::~StatusViewAnimation() {
474 // Remove ourself as a delegate so that we don't get notified when
475 // animations end as a result of destruction.
476 set_delegate(NULL);
479 double StatusBubbleViews::StatusViewAnimation::GetCurrentOpacity() {
480 return opacity_start_ + (opacity_end_ - opacity_start_) *
481 gfx::LinearAnimation::GetCurrentValue();
484 void StatusBubbleViews::StatusViewAnimation::AnimateToState(double state) {
485 status_view_->SetOpacity(GetCurrentOpacity());
488 void StatusBubbleViews::StatusViewAnimation::AnimationEnded(
489 const gfx::Animation* animation) {
490 status_view_->SetOpacity(opacity_end_);
491 status_view_->OnAnimationEnded();
494 // StatusBubbleViews::StatusViewExpander ---------------------------------------
496 // Manages the expansion and contraction of the status bubble as it accommodates
497 // URLs too long to fit in the standard bubble. Changes are passed through the
498 // StatusView to paint.
499 class StatusBubbleViews::StatusViewExpander : public gfx::LinearAnimation,
500 public gfx::AnimationDelegate {
501 public:
502 StatusViewExpander(StatusBubbleViews* status_bubble,
503 StatusView* status_view)
504 : gfx::LinearAnimation(kFramerate, this),
505 status_bubble_(status_bubble),
506 status_view_(status_view),
507 expansion_start_(0),
508 expansion_end_(0) {
511 // Manage the expansion of the bubble.
512 void StartExpansion(const base::string16& expanded_text,
513 int current_width,
514 int expansion_end);
516 // Set width of fully expanded bubble.
517 void SetExpandedWidth(int expanded_width);
519 private:
520 // Animation functions.
521 int GetCurrentBubbleWidth();
522 void SetBubbleWidth(int width);
523 virtual void AnimateToState(double state) OVERRIDE;
524 virtual void AnimationEnded(const gfx::Animation* animation) OVERRIDE;
526 // Manager that owns us.
527 StatusBubbleViews* status_bubble_;
529 // Change the bounds and text of this view.
530 StatusView* status_view_;
532 // Text elided (if needed) to fit maximum status bar width.
533 base::string16 expanded_text_;
535 // Widths at expansion start and end.
536 int expansion_start_;
537 int expansion_end_;
540 void StatusBubbleViews::StatusViewExpander::AnimateToState(double state) {
541 SetBubbleWidth(GetCurrentBubbleWidth());
544 void StatusBubbleViews::StatusViewExpander::AnimationEnded(
545 const gfx::Animation* animation) {
546 SetBubbleWidth(expansion_end_);
547 status_view_->SetText(expanded_text_, false);
550 void StatusBubbleViews::StatusViewExpander::StartExpansion(
551 const base::string16& expanded_text,
552 int expansion_start,
553 int expansion_end) {
554 expanded_text_ = expanded_text;
555 expansion_start_ = expansion_start;
556 expansion_end_ = expansion_end;
557 int min_duration = std::max(kMinExpansionStepDurationMS,
558 static_cast<int>(kMaxExpansionStepDurationMS *
559 (expansion_end - expansion_start) / 100.0));
560 SetDuration(std::min(kMaxExpansionStepDurationMS, min_duration));
561 Start();
564 int StatusBubbleViews::StatusViewExpander::GetCurrentBubbleWidth() {
565 return static_cast<int>(expansion_start_ +
566 (expansion_end_ - expansion_start_) *
567 gfx::LinearAnimation::GetCurrentValue());
570 void StatusBubbleViews::StatusViewExpander::SetBubbleWidth(int width) {
571 status_bubble_->SetBubbleWidth(width);
572 status_view_->SchedulePaint();
576 // StatusBubbleViews -----------------------------------------------------------
578 const int StatusBubbleViews::kShadowThickness = 1;
580 StatusBubbleViews::StatusBubbleViews(views::View* base_view)
581 : contains_mouse_(false),
582 offset_(0),
583 base_view_(base_view),
584 view_(NULL),
585 download_shelf_is_visible_(false),
586 is_expanded_(false),
587 expand_timer_factory_(this) {
588 expand_view_.reset();
591 StatusBubbleViews::~StatusBubbleViews() {
592 CancelExpandTimer();
593 if (popup_.get())
594 popup_->CloseNow();
597 void StatusBubbleViews::Init() {
598 if (!popup_.get()) {
599 popup_.reset(new views::Widget);
600 views::Widget* frame = base_view_->GetWidget();
601 if (!view_)
602 view_ = new StatusView(popup_.get(), frame->GetThemeProvider());
603 if (!expand_view_.get())
604 expand_view_.reset(new StatusViewExpander(this, view_));
605 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
606 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
607 params.accept_events = false;
608 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
609 params.parent = frame->GetNativeView();
610 params.context = frame->GetNativeView();
611 popup_->Init(params);
612 popup_->GetNativeView()->SetName("StatusBubbleViews");
613 // We do our own animation and don't want any from the system.
614 popup_->SetVisibilityChangedAnimationsEnabled(false);
615 popup_->SetOpacity(0x00);
616 popup_->SetContentsView(view_);
617 #if defined(USE_ASH)
618 ash::wm::GetWindowState(popup_->GetNativeWindow())->
619 set_ignored_by_shelf(true);
620 #endif
621 RepositionPopup();
625 void StatusBubbleViews::Reposition() {
626 // In restored mode, the client area has a client edge between it and the
627 // frame.
628 int overlap = kShadowThickness;
629 // The extra pixels defined by kClientEdgeThickness is only drawn in frame
630 // content border on windows for non-aura build.
631 #if !defined(USE_ASH)
632 overlap +=
633 IsFrameMaximized() ? 0 : views::NonClientFrameView::kClientEdgeThickness;
634 #endif
635 int height = GetPreferredSize().height();
636 int base_view_height = base_view()->bounds().height();
637 gfx::Point origin(-overlap, base_view_height - height + overlap);
638 SetBounds(origin.x(), origin.y(), base_view()->bounds().width() / 3, height);
641 void StatusBubbleViews::RepositionPopup() {
642 if (popup_.get()) {
643 gfx::Point top_left;
644 views::View::ConvertPointToScreen(base_view_, &top_left);
646 popup_->SetBounds(gfx::Rect(top_left.x() + position_.x(),
647 top_left.y() + position_.y(),
648 size_.width(), size_.height()));
652 gfx::Size StatusBubbleViews::GetPreferredSize() {
653 return gfx::Size(0, gfx::FontList().GetHeight() + kTotalVerticalPadding);
656 void StatusBubbleViews::SetBounds(int x, int y, int w, int h) {
657 original_position_.SetPoint(x, y);
658 position_.SetPoint(base_view_->GetMirroredXWithWidthInView(x, w), y);
659 size_.SetSize(w, h);
660 RepositionPopup();
661 if (popup_.get() && contains_mouse_)
662 AvoidMouse(last_mouse_moved_location_);
665 void StatusBubbleViews::SetStatus(const base::string16& status_text) {
666 if (size_.IsEmpty())
667 return; // We have no bounds, don't attempt to show the popup.
669 if (status_text_ == status_text && !status_text.empty())
670 return;
672 if (!IsFrameVisible())
673 return; // Don't show anything if the parent isn't visible.
675 Init();
676 status_text_ = status_text;
677 if (!status_text_.empty()) {
678 view_->SetText(status_text, true);
679 view_->Show();
680 } else if (!url_text_.empty()) {
681 view_->SetText(url_text_, true);
682 } else {
683 view_->SetText(base::string16(), true);
687 void StatusBubbleViews::SetURL(const GURL& url, const std::string& languages) {
688 url_ = url;
689 languages_ = languages;
690 if (size_.IsEmpty())
691 return; // We have no bounds, don't attempt to show the popup.
693 Init();
695 // If we want to clear a displayed URL but there is a status still to
696 // display, display that status instead.
697 if (url.is_empty() && !status_text_.empty()) {
698 url_text_ = base::string16();
699 if (IsFrameVisible())
700 view_->SetText(status_text_, true);
701 return;
704 // Reset expansion state only when bubble is completely hidden.
705 if (view_->state() == StatusView::BUBBLE_HIDDEN) {
706 is_expanded_ = false;
707 SetBubbleWidth(GetStandardStatusBubbleWidth());
710 // Set Elided Text corresponding to the GURL object.
711 gfx::Rect popup_bounds = popup_->GetWindowBoundsInScreen();
712 int text_width = static_cast<int>(popup_bounds.width() -
713 (kShadowThickness * 2) - kTextPositionX - kTextHorizPadding - 1);
714 url_text_ = ElideUrl(url, gfx::FontList(), text_width, languages);
716 // An URL is always treated as a left-to-right string. On right-to-left UIs
717 // we need to explicitly mark the URL as LTR to make sure it is displayed
718 // correctly.
719 url_text_ = base::i18n::GetDisplayStringInLTRDirectionality(url_text_);
721 if (IsFrameVisible()) {
722 view_->SetText(url_text_, true);
724 CancelExpandTimer();
726 // If bubble is already in expanded state, shift to adjust to new text
727 // size (shrinking or expanding). Otherwise delay.
728 if (is_expanded_ && !url.is_empty()) {
729 ExpandBubble();
730 } else if (net::FormatUrl(url, languages).length() > url_text_.length()) {
731 base::MessageLoop::current()->PostDelayedTask(
732 FROM_HERE,
733 base::Bind(&StatusBubbleViews::ExpandBubble,
734 expand_timer_factory_.GetWeakPtr()),
735 base::TimeDelta::FromMilliseconds(kExpandHoverDelayMS));
740 void StatusBubbleViews::Hide() {
741 status_text_ = base::string16();
742 url_text_ = base::string16();
743 if (view_)
744 view_->Hide();
747 void StatusBubbleViews::MouseMoved(const gfx::Point& location,
748 bool left_content) {
749 contains_mouse_ = !left_content;
750 if (left_content) {
751 RepositionPopup();
752 return;
754 last_mouse_moved_location_ = location;
756 if (view_) {
757 view_->ResetTimer();
759 if (view_->state() != StatusView::BUBBLE_HIDDEN &&
760 view_->state() != StatusView::BUBBLE_HIDING_FADE &&
761 view_->state() != StatusView::BUBBLE_HIDING_TIMER) {
762 AvoidMouse(location);
767 void StatusBubbleViews::UpdateDownloadShelfVisibility(bool visible) {
768 download_shelf_is_visible_ = visible;
771 void StatusBubbleViews::AvoidMouse(const gfx::Point& location) {
772 // Get the position of the frame.
773 gfx::Point top_left;
774 views::View::ConvertPointToScreen(base_view_, &top_left);
775 // Border included.
776 int window_width = base_view_->GetLocalBounds().width();
778 // Get the cursor position relative to the popup.
779 gfx::Point relative_location = location;
780 if (base::i18n::IsRTL()) {
781 int top_right_x = top_left.x() + window_width;
782 relative_location.set_x(top_right_x - relative_location.x());
783 } else {
784 relative_location.set_x(
785 relative_location.x() - (top_left.x() + position_.x()));
787 relative_location.set_y(
788 relative_location.y() - (top_left.y() + position_.y()));
790 // If the mouse is in a position where we think it would move the
791 // status bubble, figure out where and how the bubble should be moved.
792 if (relative_location.y() > -kMousePadding &&
793 relative_location.x() < size_.width() + kMousePadding) {
794 int offset = kMousePadding + relative_location.y();
796 // Make the movement non-linear.
797 offset = offset * offset / kMousePadding;
799 // When the mouse is entering from the right, we want the offset to be
800 // scaled by how horizontally far away the cursor is from the bubble.
801 if (relative_location.x() > size_.width()) {
802 offset = static_cast<int>(static_cast<float>(offset) * (
803 static_cast<float>(kMousePadding -
804 (relative_location.x() - size_.width())) /
805 static_cast<float>(kMousePadding)));
808 // Cap the offset and change the visual presentation of the bubble
809 // depending on where it ends up (so that rounded corners square off
810 // and mate to the edges of the tab content).
811 if (offset >= size_.height() - kShadowThickness * 2) {
812 offset = size_.height() - kShadowThickness * 2;
813 view_->SetStyle(StatusView::STYLE_BOTTOM);
814 } else if (offset > kBubbleCornerRadius / 2 - kShadowThickness) {
815 view_->SetStyle(StatusView::STYLE_FLOATING);
816 } else {
817 view_->SetStyle(StatusView::STYLE_STANDARD);
820 // Check if the bubble sticks out from the monitor or will obscure
821 // download shelf.
822 gfx::NativeView window = base_view_->GetWidget()->GetNativeView();
823 gfx::Rect monitor_rect = gfx::Screen::GetScreenFor(window)->
824 GetDisplayNearestWindow(window).work_area();
825 const int bubble_bottom_y = top_left.y() + position_.y() + size_.height();
827 if (bubble_bottom_y + offset > monitor_rect.height() ||
828 (download_shelf_is_visible_ &&
829 (view_->style() == StatusView::STYLE_FLOATING ||
830 view_->style() == StatusView::STYLE_BOTTOM))) {
831 // The offset is still too large. Move the bubble to the right and reset
832 // Y offset_ to zero.
833 view_->SetStyle(StatusView::STYLE_STANDARD_RIGHT);
834 offset_ = 0;
836 // Subtract border width + bubble width.
837 int right_position_x = window_width - (position_.x() + size_.width());
838 popup_->SetBounds(gfx::Rect(top_left.x() + right_position_x,
839 top_left.y() + position_.y(),
840 size_.width(), size_.height()));
841 } else {
842 offset_ = offset;
843 popup_->SetBounds(gfx::Rect(top_left.x() + position_.x(),
844 top_left.y() + position_.y() + offset_,
845 size_.width(), size_.height()));
847 } else if (offset_ != 0 ||
848 view_->style() == StatusView::STYLE_STANDARD_RIGHT) {
849 offset_ = 0;
850 view_->SetStyle(StatusView::STYLE_STANDARD);
851 popup_->SetBounds(gfx::Rect(top_left.x() + position_.x(),
852 top_left.y() + position_.y(),
853 size_.width(), size_.height()));
857 bool StatusBubbleViews::IsFrameVisible() {
858 views::Widget* frame = base_view_->GetWidget();
859 if (!frame->IsVisible())
860 return false;
862 views::Widget* window = frame->GetTopLevelWidget();
863 return !window || !window->IsMinimized();
866 bool StatusBubbleViews::IsFrameMaximized() {
867 views::Widget* frame = base_view_->GetWidget();
868 views::Widget* window = frame->GetTopLevelWidget();
869 return window && window->IsMaximized();
872 void StatusBubbleViews::ExpandBubble() {
873 // Elide URL to maximum possible size, then check actual length (it may
874 // still be too long to fit) before expanding bubble.
875 gfx::Rect popup_bounds = popup_->GetWindowBoundsInScreen();
876 int max_status_bubble_width = GetMaxStatusBubbleWidth();
877 const gfx::FontList font_list;
878 url_text_ = ElideUrl(url_, font_list, max_status_bubble_width, languages_);
879 int expanded_bubble_width =
880 std::max(GetStandardStatusBubbleWidth(),
881 std::min(gfx::GetStringWidth(url_text_, font_list) +
882 (kShadowThickness * 2) + kTextPositionX +
883 kTextHorizPadding + 1,
884 max_status_bubble_width));
885 is_expanded_ = true;
886 expand_view_->StartExpansion(url_text_, popup_bounds.width(),
887 expanded_bubble_width);
890 int StatusBubbleViews::GetStandardStatusBubbleWidth() {
891 return base_view_->bounds().width() / 3;
894 int StatusBubbleViews::GetMaxStatusBubbleWidth() {
895 const ui::NativeTheme* theme = base_view_->GetNativeTheme();
896 return static_cast<int>(std::max(0, base_view_->bounds().width() -
897 (kShadowThickness * 2) - kTextPositionX - kTextHorizPadding - 1 -
898 views::NativeScrollBar::GetVerticalScrollBarWidth(theme)));
901 void StatusBubbleViews::SetBubbleWidth(int width) {
902 size_.set_width(width);
903 SetBounds(original_position_.x(), original_position_.y(),
904 size_.width(), size_.height());
907 void StatusBubbleViews::CancelExpandTimer() {
908 if (expand_timer_factory_.HasWeakPtrs())
909 expand_timer_factory_.InvalidateWeakPtrs();