Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / ui / views / status_bubble_views.cc
blobf9a7d56529ba3025bac769941c74c15b5ccb4d82
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 "net/base/net_util.h"
16 #include "third_party/skia/include/core/SkPaint.h"
17 #include "third_party/skia/include/core/SkPath.h"
18 #include "third_party/skia/include/core/SkRect.h"
19 #include "ui/base/theme_provider.h"
20 #include "ui/gfx/animation/animation_delegate.h"
21 #include "ui/gfx/animation/linear_animation.h"
22 #include "ui/gfx/canvas.h"
23 #include "ui/gfx/font_list.h"
24 #include "ui/gfx/point.h"
25 #include "ui/gfx/rect.h"
26 #include "ui/gfx/screen.h"
27 #include "ui/gfx/skia_util.h"
28 #include "ui/gfx/text_elider.h"
29 #include "ui/gfx/text_utils.h"
30 #include "ui/native_theme/native_theme.h"
31 #include "ui/views/controls/scrollbar/native_scroll_bar.h"
32 #include "ui/views/widget/root_view.h"
33 #include "ui/views/widget/widget.h"
34 #include "url/gurl.h"
36 #if defined(USE_AURA)
37 #include "ui/aura/window.h"
38 #endif
40 #if defined(USE_ASH)
41 #include "ash/wm/window_state.h"
42 #endif
44 // The alpha and color of the bubble's shadow.
45 static const SkColor kShadowColor = SkColorSetARGB(30, 0, 0, 0);
47 // The roundedness of the edges of our bubble.
48 static const int kBubbleCornerRadius = 4;
50 // How close the mouse can get to the infobubble before it starts sliding
51 // off-screen.
52 static const int kMousePadding = 20;
54 // The horizontal offset of the text within the status bubble, not including the
55 // outer shadow ring.
56 static const int kTextPositionX = 3;
58 // The minimum horizontal space between the (right) end of the text and the edge
59 // of the status bubble, not including the outer shadow ring.
60 static const int kTextHorizPadding = 1;
62 // Delays before we start hiding or showing the bubble after we receive a
63 // show or hide request.
64 static const int kShowDelay = 80;
65 static const int kHideDelay = 250;
67 // How long each fade should last for.
68 static const int kShowFadeDurationMS = 120;
69 static const int kHideFadeDurationMS = 200;
70 static const int kFramerate = 25;
72 // How long each expansion step should take.
73 static const int kMinExpansionStepDurationMS = 20;
74 static const int kMaxExpansionStepDurationMS = 150;
76 // View -----------------------------------------------------------------------
77 // StatusView manages the display of the bubble, applying text changes and
78 // fading in or out the bubble as required.
79 class StatusBubbleViews::StatusView : public views::View,
80 public gfx::LinearAnimation,
81 public gfx::AnimationDelegate {
82 public:
83 // The bubble can be in one of many states:
84 enum BubbleState {
85 BUBBLE_HIDDEN, // Entirely BUBBLE_HIDDEN.
86 BUBBLE_HIDING_FADE, // In a fade-out transition.
87 BUBBLE_HIDING_TIMER, // Waiting before a fade-out.
88 BUBBLE_SHOWING_TIMER, // Waiting before a fade-in.
89 BUBBLE_SHOWING_FADE, // In a fade-in transition.
90 BUBBLE_SHOWN // Fully visible.
93 enum BubbleStyle {
94 STYLE_BOTTOM,
95 STYLE_FLOATING,
96 STYLE_STANDARD,
97 STYLE_STANDARD_RIGHT
100 StatusView(StatusBubble* status_bubble,
101 views::Widget* popup,
102 ui::ThemeProvider* theme_provider);
103 virtual ~StatusView();
105 // Set the bubble text to a certain value, hides the bubble if text is
106 // an empty string. Trigger animation sequence to display if
107 // |should_animate_open|.
108 void SetText(const base::string16& text, bool should_animate_open);
110 BubbleState state() const { return state_; }
111 BubbleStyle style() const { return style_; }
112 void SetStyle(BubbleStyle style);
114 // Show the bubble instantly.
115 void Show();
117 // Hide the bubble instantly.
118 void Hide();
120 // Resets any timers we have. Typically called when the user moves a
121 // mouse.
122 void ResetTimer();
124 private:
125 class InitialTimer;
127 // Manage the timers that control the delay before a fade begins or ends.
128 void StartTimer(base::TimeDelta time);
129 void OnTimer();
130 void CancelTimer();
131 void RestartTimer(base::TimeDelta delay);
133 // Manage the fades and starting and stopping the animations correctly.
134 void StartFade(double start, double end, int duration);
135 void StartHiding();
136 void StartShowing();
138 // Animation functions.
139 double GetCurrentOpacity();
140 void SetOpacity(double opacity);
141 virtual void AnimateToState(double state) OVERRIDE;
142 virtual void AnimationEnded(const Animation* animation) OVERRIDE;
144 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
146 BubbleState state_;
147 BubbleStyle style_;
149 base::WeakPtrFactory<StatusBubbleViews::StatusView> timer_factory_;
151 // Manager, owns us.
152 StatusBubble* status_bubble_;
154 // Handle to the widget that contains us.
155 views::Widget* popup_;
157 // The currently-displayed text.
158 base::string16 text_;
160 // Start and end opacities for the current transition - note that as a
161 // fade-in can easily turn into a fade out, opacity_start_ is sometimes
162 // a value between 0 and 1.
163 double opacity_start_;
164 double opacity_end_;
166 // Holds the theme provider of the frame that created us.
167 ui::ThemeProvider* theme_service_;
169 DISALLOW_COPY_AND_ASSIGN(StatusView);
172 StatusBubbleViews::StatusView::StatusView(StatusBubble* status_bubble,
173 views::Widget* popup,
174 ui::ThemeProvider* theme_provider)
175 : gfx::LinearAnimation(kFramerate, this),
176 state_(BUBBLE_HIDDEN),
177 style_(STYLE_STANDARD),
178 timer_factory_(this),
179 status_bubble_(status_bubble),
180 popup_(popup),
181 opacity_start_(0),
182 opacity_end_(0),
183 theme_service_(theme_provider) {
186 StatusBubbleViews::StatusView::~StatusView() {
187 // Remove ourself as a delegate so that we don't get notified when
188 // animations end as a result of destruction.
189 set_delegate(NULL);
190 Stop();
191 CancelTimer();
194 void StatusBubbleViews::StatusView::SetText(const base::string16& text,
195 bool should_animate_open) {
196 if (text.empty()) {
197 // The string was empty.
198 StartHiding();
199 } else {
200 // We want to show the string.
201 if (text != text_) {
202 text_ = text;
203 SchedulePaint();
205 if (should_animate_open)
206 StartShowing();
210 void StatusBubbleViews::StatusView::Show() {
211 Stop();
212 CancelTimer();
213 SetOpacity(1.0);
214 popup_->ShowInactive();
215 state_ = BUBBLE_SHOWN;
218 void StatusBubbleViews::StatusView::Hide() {
219 Stop();
220 CancelTimer();
221 SetOpacity(0.0);
222 text_.clear();
223 popup_->Hide();
224 state_ = BUBBLE_HIDDEN;
227 void StatusBubbleViews::StatusView::StartTimer(base::TimeDelta time) {
228 if (timer_factory_.HasWeakPtrs())
229 timer_factory_.InvalidateWeakPtrs();
231 base::MessageLoop::current()->PostDelayedTask(
232 FROM_HERE,
233 base::Bind(&StatusBubbleViews::StatusView::OnTimer,
234 timer_factory_.GetWeakPtr()),
235 time);
238 void StatusBubbleViews::StatusView::OnTimer() {
239 if (state_ == BUBBLE_HIDING_TIMER) {
240 state_ = BUBBLE_HIDING_FADE;
241 StartFade(1.0, 0.0, kHideFadeDurationMS);
242 } else if (state_ == BUBBLE_SHOWING_TIMER) {
243 state_ = BUBBLE_SHOWING_FADE;
244 StartFade(0.0, 1.0, kShowFadeDurationMS);
248 void StatusBubbleViews::StatusView::CancelTimer() {
249 if (timer_factory_.HasWeakPtrs())
250 timer_factory_.InvalidateWeakPtrs();
253 void StatusBubbleViews::StatusView::RestartTimer(base::TimeDelta delay) {
254 CancelTimer();
255 StartTimer(delay);
258 void StatusBubbleViews::StatusView::ResetTimer() {
259 if (state_ == BUBBLE_SHOWING_TIMER) {
260 // We hadn't yet begun showing anything when we received a new request
261 // for something to show, so we start from scratch.
262 RestartTimer(base::TimeDelta::FromMilliseconds(kShowDelay));
266 void StatusBubbleViews::StatusView::StartFade(double start,
267 double end,
268 int duration) {
269 opacity_start_ = start;
270 opacity_end_ = end;
272 // This will also reset the currently-occurring animation.
273 SetDuration(duration);
274 Start();
277 void StatusBubbleViews::StatusView::StartHiding() {
278 if (state_ == BUBBLE_SHOWN) {
279 state_ = BUBBLE_HIDING_TIMER;
280 StartTimer(base::TimeDelta::FromMilliseconds(kHideDelay));
281 } else if (state_ == BUBBLE_SHOWING_TIMER) {
282 state_ = BUBBLE_HIDDEN;
283 popup_->Hide();
284 CancelTimer();
285 } else if (state_ == BUBBLE_SHOWING_FADE) {
286 state_ = BUBBLE_HIDING_FADE;
287 // Figure out where we are in the current fade.
288 double current_opacity = GetCurrentOpacity();
290 // Start a fade in the opposite direction.
291 StartFade(current_opacity, 0.0,
292 static_cast<int>(kHideFadeDurationMS * current_opacity));
296 void StatusBubbleViews::StatusView::StartShowing() {
297 if (state_ == BUBBLE_HIDDEN) {
298 popup_->ShowInactive();
299 state_ = BUBBLE_SHOWING_TIMER;
300 StartTimer(base::TimeDelta::FromMilliseconds(kShowDelay));
301 } else if (state_ == BUBBLE_HIDING_TIMER) {
302 state_ = BUBBLE_SHOWN;
303 CancelTimer();
304 } else if (state_ == BUBBLE_HIDING_FADE) {
305 // We're partway through a fade.
306 state_ = BUBBLE_SHOWING_FADE;
308 // Figure out where we are in the current fade.
309 double current_opacity = GetCurrentOpacity();
311 // Start a fade in the opposite direction.
312 StartFade(current_opacity, 1.0,
313 static_cast<int>(kShowFadeDurationMS * current_opacity));
314 } else if (state_ == BUBBLE_SHOWING_TIMER) {
315 // We hadn't yet begun showing anything when we received a new request
316 // for something to show, so we start from scratch.
317 ResetTimer();
321 // Animation functions.
322 double StatusBubbleViews::StatusView::GetCurrentOpacity() {
323 return opacity_start_ + (opacity_end_ - opacity_start_) *
324 gfx::LinearAnimation::GetCurrentValue();
327 void StatusBubbleViews::StatusView::SetOpacity(double opacity) {
328 popup_->SetOpacity(static_cast<unsigned char>(opacity * 255));
331 void StatusBubbleViews::StatusView::AnimateToState(double state) {
332 SetOpacity(GetCurrentOpacity());
335 void StatusBubbleViews::StatusView::AnimationEnded(
336 const gfx::Animation* animation) {
337 SetOpacity(opacity_end_);
339 if (state_ == BUBBLE_HIDING_FADE) {
340 state_ = BUBBLE_HIDDEN;
341 popup_->Hide();
342 } else if (state_ == BUBBLE_SHOWING_FADE) {
343 state_ = BUBBLE_SHOWN;
347 void StatusBubbleViews::StatusView::SetStyle(BubbleStyle style) {
348 if (style_ != style) {
349 style_ = style;
350 SchedulePaint();
354 void StatusBubbleViews::StatusView::OnPaint(gfx::Canvas* canvas) {
355 SkPaint paint;
356 paint.setStyle(SkPaint::kFill_Style);
357 paint.setAntiAlias(true);
358 SkColor toolbar_color = theme_service_->GetColor(
359 ThemeProperties::COLOR_TOOLBAR);
360 paint.setColor(toolbar_color);
362 gfx::Rect popup_bounds = popup_->GetWindowBoundsInScreen();
364 // Figure out how to round the bubble's four corners.
365 SkScalar rad[8];
367 // Top Edges - if the bubble is in its bottom position (sticking downwards),
368 // then we square the top edges. Otherwise, we square the edges based on the
369 // position of the bubble within the window (the bubble is positioned in the
370 // southeast corner in RTL and in the southwest corner in LTR).
371 if (style_ == STYLE_BOTTOM) {
372 // Top Left corner.
373 rad[0] = 0;
374 rad[1] = 0;
376 // Top Right corner.
377 rad[2] = 0;
378 rad[3] = 0;
379 } else {
380 if (base::i18n::IsRTL() != (style_ == STYLE_STANDARD_RIGHT)) {
381 // The text is RtL or the bubble is on the right side (but not both).
383 // Top Left corner.
384 rad[0] = SkIntToScalar(kBubbleCornerRadius);
385 rad[1] = SkIntToScalar(kBubbleCornerRadius);
387 // Top Right corner.
388 rad[2] = 0;
389 rad[3] = 0;
390 } else {
391 // Top Left corner.
392 rad[0] = 0;
393 rad[1] = 0;
395 // Top Right corner.
396 rad[2] = SkIntToScalar(kBubbleCornerRadius);
397 rad[3] = SkIntToScalar(kBubbleCornerRadius);
401 // Bottom edges - square these off if the bubble is in its standard position
402 // (sticking upward).
403 if (style_ == STYLE_STANDARD || style_ == STYLE_STANDARD_RIGHT) {
404 // Bottom Right Corner.
405 rad[4] = 0;
406 rad[5] = 0;
408 // Bottom Left Corner.
409 rad[6] = 0;
410 rad[7] = 0;
411 } else {
412 // Bottom Right Corner.
413 rad[4] = SkIntToScalar(kBubbleCornerRadius);
414 rad[5] = SkIntToScalar(kBubbleCornerRadius);
416 // Bottom Left Corner.
417 rad[6] = SkIntToScalar(kBubbleCornerRadius);
418 rad[7] = SkIntToScalar(kBubbleCornerRadius);
421 // Draw the bubble's shadow.
422 int width = popup_bounds.width();
423 int height = popup_bounds.height();
424 SkRect rect(gfx::RectToSkRect(gfx::Rect(popup_bounds.size())));
425 SkPath shadow_path;
426 shadow_path.addRoundRect(rect, rad, SkPath::kCW_Direction);
427 SkPaint shadow_paint;
428 shadow_paint.setAntiAlias(true);
429 shadow_paint.setColor(kShadowColor);
430 canvas->DrawPath(shadow_path, shadow_paint);
432 // Draw the bubble.
433 rect.set(SkIntToScalar(kShadowThickness),
434 SkIntToScalar(kShadowThickness),
435 SkIntToScalar(width - kShadowThickness),
436 SkIntToScalar(height - kShadowThickness));
437 SkPath path;
438 path.addRoundRect(rect, rad, SkPath::kCW_Direction);
439 canvas->DrawPath(path, paint);
441 // Draw highlight text and then the text body. In order to make sure the text
442 // is aligned to the right on RTL UIs, we mirror the text bounds if the
443 // locale is RTL.
444 const gfx::FontList font_list;
445 int text_width = std::min(
446 gfx::GetStringWidth(text_, font_list),
447 width - (kShadowThickness * 2) - kTextPositionX - kTextHorizPadding);
448 int text_height = height - (kShadowThickness * 2);
449 gfx::Rect body_bounds(kShadowThickness + kTextPositionX,
450 kShadowThickness,
451 std::max(0, text_width),
452 std::max(0, text_height));
453 body_bounds.set_x(GetMirroredXForRect(body_bounds));
454 SkColor text_color =
455 theme_service_->GetColor(ThemeProperties::COLOR_STATUS_BAR_TEXT);
456 canvas->DrawStringRect(text_, font_list, text_color, body_bounds);
459 // StatusViewExpander ---------------------------------------------------------
460 // Manages the expansion and contraction of the status bubble as it accommodates
461 // URLs too long to fit in the standard bubble. Changes are passed through the
462 // StatusView to paint.
463 class StatusBubbleViews::StatusViewExpander : public gfx::LinearAnimation,
464 public gfx::AnimationDelegate {
465 public:
466 StatusViewExpander(StatusBubbleViews* status_bubble,
467 StatusView* status_view)
468 : gfx::LinearAnimation(kFramerate, this),
469 status_bubble_(status_bubble),
470 status_view_(status_view),
471 expansion_start_(0),
472 expansion_end_(0) {
475 // Manage the expansion of the bubble.
476 void StartExpansion(const base::string16& expanded_text,
477 int current_width,
478 int expansion_end);
480 // Set width of fully expanded bubble.
481 void SetExpandedWidth(int expanded_width);
483 private:
484 // Animation functions.
485 int GetCurrentBubbleWidth();
486 void SetBubbleWidth(int width);
487 virtual void AnimateToState(double state) OVERRIDE;
488 virtual void AnimationEnded(const gfx::Animation* animation) OVERRIDE;
490 // Manager that owns us.
491 StatusBubbleViews* status_bubble_;
493 // Change the bounds and text of this view.
494 StatusView* status_view_;
496 // Text elided (if needed) to fit maximum status bar width.
497 base::string16 expanded_text_;
499 // Widths at expansion start and end.
500 int expansion_start_;
501 int expansion_end_;
504 void StatusBubbleViews::StatusViewExpander::AnimateToState(double state) {
505 SetBubbleWidth(GetCurrentBubbleWidth());
508 void StatusBubbleViews::StatusViewExpander::AnimationEnded(
509 const gfx::Animation* animation) {
510 SetBubbleWidth(expansion_end_);
511 status_view_->SetText(expanded_text_, false);
514 void StatusBubbleViews::StatusViewExpander::StartExpansion(
515 const base::string16& expanded_text,
516 int expansion_start,
517 int expansion_end) {
518 expanded_text_ = expanded_text;
519 expansion_start_ = expansion_start;
520 expansion_end_ = expansion_end;
521 int min_duration = std::max(kMinExpansionStepDurationMS,
522 static_cast<int>(kMaxExpansionStepDurationMS *
523 (expansion_end - expansion_start) / 100.0));
524 SetDuration(std::min(kMaxExpansionStepDurationMS, min_duration));
525 Start();
528 int StatusBubbleViews::StatusViewExpander::GetCurrentBubbleWidth() {
529 return static_cast<int>(expansion_start_ +
530 (expansion_end_ - expansion_start_) *
531 gfx::LinearAnimation::GetCurrentValue());
534 void StatusBubbleViews::StatusViewExpander::SetBubbleWidth(int width) {
535 status_bubble_->SetBubbleWidth(width);
536 status_view_->SchedulePaint();
539 // StatusBubble ---------------------------------------------------------------
541 const int StatusBubbleViews::kShadowThickness = 1;
543 StatusBubbleViews::StatusBubbleViews(views::View* base_view)
544 : contains_mouse_(false),
545 offset_(0),
546 opacity_(0),
547 base_view_(base_view),
548 view_(NULL),
549 download_shelf_is_visible_(false),
550 is_expanded_(false),
551 expand_timer_factory_(this) {
552 expand_view_.reset();
555 StatusBubbleViews::~StatusBubbleViews() {
556 CancelExpandTimer();
557 if (popup_.get())
558 popup_->CloseNow();
561 void StatusBubbleViews::Init() {
562 if (!popup_.get()) {
563 popup_.reset(new views::Widget);
564 views::Widget* frame = base_view_->GetWidget();
565 if (!view_)
566 view_ = new StatusView(this, popup_.get(), frame->GetThemeProvider());
567 if (!expand_view_.get())
568 expand_view_.reset(new StatusViewExpander(this, view_));
569 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
570 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
571 params.accept_events = false;
572 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
573 params.parent = frame->GetNativeView();
574 params.context = frame->GetNativeView();
575 popup_->Init(params);
576 #if defined(USE_AURA)
577 popup_->GetNativeView()->SetName("StatusBubbleViews");
578 #endif
579 // We do our own animation and don't want any from the system.
580 popup_->SetVisibilityChangedAnimationsEnabled(false);
581 popup_->SetOpacity(0x00);
582 popup_->SetContentsView(view_);
583 #if defined(USE_ASH)
584 ash::wm::GetWindowState(popup_->GetNativeWindow())->
585 set_ignored_by_shelf(true);
586 #endif
587 Reposition();
591 void StatusBubbleViews::Reposition() {
592 if (popup_.get()) {
593 gfx::Point top_left;
594 views::View::ConvertPointToScreen(base_view_, &top_left);
596 popup_->SetBounds(gfx::Rect(top_left.x() + position_.x(),
597 top_left.y() + position_.y(),
598 size_.width(), size_.height()));
602 gfx::Size StatusBubbleViews::GetPreferredSize() {
603 return gfx::Size(0, gfx::FontList().GetHeight() + kTotalVerticalPadding);
606 void StatusBubbleViews::SetBounds(int x, int y, int w, int h) {
607 original_position_.SetPoint(x, y);
608 position_.SetPoint(base_view_->GetMirroredXWithWidthInView(x, w), y);
609 size_.SetSize(w, h);
610 Reposition();
611 if (popup_.get() && contains_mouse_)
612 AvoidMouse(last_mouse_moved_location_);
615 void StatusBubbleViews::SetStatus(const base::string16& status_text) {
616 if (size_.IsEmpty())
617 return; // We have no bounds, don't attempt to show the popup.
619 if (status_text_ == status_text && !status_text.empty())
620 return;
622 if (!IsFrameVisible())
623 return; // Don't show anything if the parent isn't visible.
625 Init();
626 status_text_ = status_text;
627 if (!status_text_.empty()) {
628 view_->SetText(status_text, true);
629 view_->Show();
630 } else if (!url_text_.empty()) {
631 view_->SetText(url_text_, true);
632 } else {
633 view_->SetText(base::string16(), true);
637 void StatusBubbleViews::SetURL(const GURL& url, const std::string& languages) {
638 url_ = url;
639 languages_ = languages;
640 if (size_.IsEmpty())
641 return; // We have no bounds, don't attempt to show the popup.
643 Init();
645 // If we want to clear a displayed URL but there is a status still to
646 // display, display that status instead.
647 if (url.is_empty() && !status_text_.empty()) {
648 url_text_ = base::string16();
649 if (IsFrameVisible())
650 view_->SetText(status_text_, true);
651 return;
654 // Reset expansion state only when bubble is completely hidden.
655 if (view_->state() == StatusView::BUBBLE_HIDDEN) {
656 is_expanded_ = false;
657 SetBubbleWidth(GetStandardStatusBubbleWidth());
660 // Set Elided Text corresponding to the GURL object.
661 gfx::Rect popup_bounds = popup_->GetWindowBoundsInScreen();
662 int text_width = static_cast<int>(popup_bounds.width() -
663 (kShadowThickness * 2) - kTextPositionX - kTextHorizPadding - 1);
664 url_text_ = gfx::ElideUrl(url, gfx::FontList(), text_width, languages);
666 // An URL is always treated as a left-to-right string. On right-to-left UIs
667 // we need to explicitly mark the URL as LTR to make sure it is displayed
668 // correctly.
669 url_text_ = base::i18n::GetDisplayStringInLTRDirectionality(url_text_);
671 if (IsFrameVisible()) {
672 view_->SetText(url_text_, true);
674 CancelExpandTimer();
676 // If bubble is already in expanded state, shift to adjust to new text
677 // size (shrinking or expanding). Otherwise delay.
678 if (is_expanded_ && !url.is_empty()) {
679 ExpandBubble();
680 } else if (net::FormatUrl(url, languages).length() > url_text_.length()) {
681 base::MessageLoop::current()->PostDelayedTask(
682 FROM_HERE,
683 base::Bind(&StatusBubbleViews::ExpandBubble,
684 expand_timer_factory_.GetWeakPtr()),
685 base::TimeDelta::FromMilliseconds(kExpandHoverDelay));
690 void StatusBubbleViews::Hide() {
691 status_text_ = base::string16();
692 url_text_ = base::string16();
693 if (view_)
694 view_->Hide();
697 void StatusBubbleViews::MouseMoved(const gfx::Point& location,
698 bool left_content) {
699 contains_mouse_ = !left_content;
700 if (left_content) {
701 Reposition();
702 return;
704 last_mouse_moved_location_ = location;
706 if (view_) {
707 view_->ResetTimer();
709 if (view_->state() != StatusView::BUBBLE_HIDDEN &&
710 view_->state() != StatusView::BUBBLE_HIDING_FADE &&
711 view_->state() != StatusView::BUBBLE_HIDING_TIMER) {
712 AvoidMouse(location);
717 void StatusBubbleViews::UpdateDownloadShelfVisibility(bool visible) {
718 download_shelf_is_visible_ = visible;
721 void StatusBubbleViews::AvoidMouse(const gfx::Point& location) {
722 // Get the position of the frame.
723 gfx::Point top_left;
724 views::View::ConvertPointToScreen(base_view_, &top_left);
725 // Border included.
726 int window_width = base_view_->GetLocalBounds().width();
728 // Get the cursor position relative to the popup.
729 gfx::Point relative_location = location;
730 if (base::i18n::IsRTL()) {
731 int top_right_x = top_left.x() + window_width;
732 relative_location.set_x(top_right_x - relative_location.x());
733 } else {
734 relative_location.set_x(
735 relative_location.x() - (top_left.x() + position_.x()));
737 relative_location.set_y(
738 relative_location.y() - (top_left.y() + position_.y()));
740 // If the mouse is in a position where we think it would move the
741 // status bubble, figure out where and how the bubble should be moved.
742 if (relative_location.y() > -kMousePadding &&
743 relative_location.x() < size_.width() + kMousePadding) {
744 int offset = kMousePadding + relative_location.y();
746 // Make the movement non-linear.
747 offset = offset * offset / kMousePadding;
749 // When the mouse is entering from the right, we want the offset to be
750 // scaled by how horizontally far away the cursor is from the bubble.
751 if (relative_location.x() > size_.width()) {
752 offset = static_cast<int>(static_cast<float>(offset) * (
753 static_cast<float>(kMousePadding -
754 (relative_location.x() - size_.width())) /
755 static_cast<float>(kMousePadding)));
758 // Cap the offset and change the visual presentation of the bubble
759 // depending on where it ends up (so that rounded corners square off
760 // and mate to the edges of the tab content).
761 if (offset >= size_.height() - kShadowThickness * 2) {
762 offset = size_.height() - kShadowThickness * 2;
763 view_->SetStyle(StatusView::STYLE_BOTTOM);
764 } else if (offset > kBubbleCornerRadius / 2 - kShadowThickness) {
765 view_->SetStyle(StatusView::STYLE_FLOATING);
766 } else {
767 view_->SetStyle(StatusView::STYLE_STANDARD);
770 // Check if the bubble sticks out from the monitor or will obscure
771 // download shelf.
772 gfx::NativeView window = base_view_->GetWidget()->GetNativeView();
773 gfx::Rect monitor_rect = gfx::Screen::GetScreenFor(window)->
774 GetDisplayNearestWindow(window).work_area();
775 const int bubble_bottom_y = top_left.y() + position_.y() + size_.height();
777 if (bubble_bottom_y + offset > monitor_rect.height() ||
778 (download_shelf_is_visible_ &&
779 (view_->style() == StatusView::STYLE_FLOATING ||
780 view_->style() == StatusView::STYLE_BOTTOM))) {
781 // The offset is still too large. Move the bubble to the right and reset
782 // Y offset_ to zero.
783 view_->SetStyle(StatusView::STYLE_STANDARD_RIGHT);
784 offset_ = 0;
786 // Subtract border width + bubble width.
787 int right_position_x = window_width - (position_.x() + size_.width());
788 popup_->SetBounds(gfx::Rect(top_left.x() + right_position_x,
789 top_left.y() + position_.y(),
790 size_.width(), size_.height()));
791 } else {
792 offset_ = offset;
793 popup_->SetBounds(gfx::Rect(top_left.x() + position_.x(),
794 top_left.y() + position_.y() + offset_,
795 size_.width(), size_.height()));
797 } else if (offset_ != 0 ||
798 view_->style() == StatusView::STYLE_STANDARD_RIGHT) {
799 offset_ = 0;
800 view_->SetStyle(StatusView::STYLE_STANDARD);
801 popup_->SetBounds(gfx::Rect(top_left.x() + position_.x(),
802 top_left.y() + position_.y(),
803 size_.width(), size_.height()));
807 bool StatusBubbleViews::IsFrameVisible() {
808 views::Widget* frame = base_view_->GetWidget();
809 if (!frame->IsVisible())
810 return false;
812 views::Widget* window = frame->GetTopLevelWidget();
813 return !window || !window->IsMinimized();
816 void StatusBubbleViews::ExpandBubble() {
817 // Elide URL to maximum possible size, then check actual length (it may
818 // still be too long to fit) before expanding bubble.
819 gfx::Rect popup_bounds = popup_->GetWindowBoundsInScreen();
820 int max_status_bubble_width = GetMaxStatusBubbleWidth();
821 const gfx::FontList font_list;
822 url_text_ = gfx::ElideUrl(url_, font_list,
823 max_status_bubble_width, languages_);
824 int expanded_bubble_width =
825 std::max(GetStandardStatusBubbleWidth(),
826 std::min(gfx::GetStringWidth(url_text_, font_list) +
827 (kShadowThickness * 2) + kTextPositionX +
828 kTextHorizPadding + 1,
829 max_status_bubble_width));
830 is_expanded_ = true;
831 expand_view_->StartExpansion(url_text_, popup_bounds.width(),
832 expanded_bubble_width);
835 int StatusBubbleViews::GetStandardStatusBubbleWidth() {
836 return base_view_->bounds().width() / 3;
839 int StatusBubbleViews::GetMaxStatusBubbleWidth() {
840 const ui::NativeTheme* theme = base_view_->GetNativeTheme();
841 return static_cast<int>(std::max(0, base_view_->bounds().width() -
842 (kShadowThickness * 2) - kTextPositionX - kTextHorizPadding - 1 -
843 views::NativeScrollBar::GetVerticalScrollBarWidth(theme)));
846 void StatusBubbleViews::SetBubbleWidth(int width) {
847 size_.set_width(width);
848 SetBounds(original_position_.x(), original_position_.y(),
849 size_.width(), size_.height());
852 void StatusBubbleViews::CancelExpandTimer() {
853 if (expand_timer_factory_.HasWeakPtrs())
854 expand_timer_factory_.InvalidateWeakPtrs();