Revert of Add button to add new FSP services to Files app. (patchset #8 id:140001...
[chromium-blink-merge.git] / chrome / browser / ui / views / status_bubble_views.cc
bloba6ca0372db3d154b63f7f68737fd351a4dcb0f30
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/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/geometry/point.h"
25 #include "ui/gfx/geometry/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_ASH)
37 #include "ash/wm/window_state.h"
38 #endif
40 // The alpha and color of the bubble's shadow.
41 static const SkColor kShadowColor = SkColorSetARGB(30, 0, 0, 0);
43 // The roundedness of the edges of our bubble.
44 static const int kBubbleCornerRadius = 4;
46 // How close the mouse can get to the infobubble before it starts sliding
47 // off-screen.
48 static const int kMousePadding = 20;
50 // The horizontal offset of the text within the status bubble, not including the
51 // outer shadow ring.
52 static const int kTextPositionX = 3;
54 // The minimum horizontal space between the (right) end of the text and the edge
55 // of the status bubble, not including the outer shadow ring.
56 static const int kTextHorizPadding = 1;
58 // Delays before we start hiding or showing the bubble after we receive a
59 // show or hide request.
60 static const int kShowDelay = 80;
61 static const int kHideDelay = 250;
63 // How long each fade should last for.
64 static const int kShowFadeDurationMS = 120;
65 static const int kHideFadeDurationMS = 200;
66 static const int kFramerate = 25;
68 // How long each expansion step should take.
69 static const int kMinExpansionStepDurationMS = 20;
70 static const int kMaxExpansionStepDurationMS = 150;
73 // StatusBubbleViews::StatusViewAnimation --------------------------------------
74 class StatusBubbleViews::StatusViewAnimation : public gfx::LinearAnimation,
75 public gfx::AnimationDelegate {
76 public:
77 StatusViewAnimation(StatusView* status_view,
78 double opacity_start,
79 double opacity_end);
80 ~StatusViewAnimation() override;
82 double GetCurrentOpacity();
84 private:
85 // gfx::LinearAnimation:
86 void AnimateToState(double state) override;
88 // gfx::AnimationDelegate:
89 void AnimationEnded(const Animation* animation) override;
91 StatusView* status_view_;
93 // Start and end opacities for the current transition - note that as a
94 // fade-in can easily turn into a fade out, opacity_start_ is sometimes
95 // a value between 0 and 1.
96 double opacity_start_;
97 double opacity_end_;
99 DISALLOW_COPY_AND_ASSIGN(StatusViewAnimation);
103 // StatusBubbleViews::StatusView -----------------------------------------------
105 // StatusView manages the display of the bubble, applying text changes and
106 // fading in or out the bubble as required.
107 class StatusBubbleViews::StatusView : public views::View {
108 public:
109 // The bubble can be in one of many states:
110 enum BubbleState {
111 BUBBLE_HIDDEN, // Entirely BUBBLE_HIDDEN.
112 BUBBLE_HIDING_FADE, // In a fade-out transition.
113 BUBBLE_HIDING_TIMER, // Waiting before a fade-out.
114 BUBBLE_SHOWING_TIMER, // Waiting before a fade-in.
115 BUBBLE_SHOWING_FADE, // In a fade-in transition.
116 BUBBLE_SHOWN // Fully visible.
119 enum BubbleStyle {
120 STYLE_BOTTOM,
121 STYLE_FLOATING,
122 STYLE_STANDARD,
123 STYLE_STANDARD_RIGHT
126 StatusView(views::Widget* popup,
127 ui::ThemeProvider* theme_provider);
128 ~StatusView() override;
130 // Set the bubble text to a certain value, hides the bubble if text is
131 // an empty string. Trigger animation sequence to display if
132 // |should_animate_open|.
133 void SetText(const base::string16& text, bool should_animate_open);
135 BubbleState state() const { return state_; }
136 BubbleStyle style() const { return style_; }
137 void SetStyle(BubbleStyle style);
139 // Show the bubble instantly.
140 void Show();
142 // Hide the bubble instantly.
143 void Hide();
145 // Resets any timers we have. Typically called when the user moves a
146 // mouse.
147 void ResetTimer();
149 // This call backs the StatusView in order to fade the bubble in and out.
150 void SetOpacity(double opacity);
152 // Depending on the state of the bubble this will either hide the popup or
153 // not.
154 void OnAnimationEnded();
156 private:
157 class InitialTimer;
159 // Manage the timers that control the delay before a fade begins or ends.
160 void StartTimer(base::TimeDelta time);
161 void OnTimer();
162 void CancelTimer();
163 void RestartTimer(base::TimeDelta delay);
165 // Manage the fades and starting and stopping the animations correctly.
166 void StartFade(double start, double end, int duration);
167 void StartHiding();
168 void StartShowing();
170 // views::View:
171 const char* GetClassName() const override;
172 void OnPaint(gfx::Canvas* canvas) override;
174 BubbleState state_;
175 BubbleStyle style_;
177 scoped_ptr<StatusViewAnimation> animation_;
179 // Handle to the widget that contains us.
180 views::Widget* popup_;
182 // The currently-displayed text.
183 base::string16 text_;
185 // Holds the theme provider of the frame that created us.
186 ui::ThemeProvider* theme_service_;
188 base::WeakPtrFactory<StatusBubbleViews::StatusView> timer_factory_;
190 DISALLOW_COPY_AND_ASSIGN(StatusView);
193 StatusBubbleViews::StatusView::StatusView(views::Widget* popup,
194 ui::ThemeProvider* theme_provider)
195 : state_(BUBBLE_HIDDEN),
196 style_(STYLE_STANDARD),
197 animation_(new StatusViewAnimation(this, 0, 0)),
198 popup_(popup),
199 theme_service_(theme_provider),
200 timer_factory_(this) {
203 StatusBubbleViews::StatusView::~StatusView() {
204 animation_->Stop();
205 CancelTimer();
208 void StatusBubbleViews::StatusView::SetText(const base::string16& text,
209 bool should_animate_open) {
210 if (text.empty()) {
211 // The string was empty.
212 StartHiding();
213 } else {
214 // We want to show the string.
215 if (text != text_) {
216 text_ = text;
217 SchedulePaint();
219 if (should_animate_open)
220 StartShowing();
224 void StatusBubbleViews::StatusView::Show() {
225 animation_->Stop();
226 CancelTimer();
227 SetOpacity(1.0);
228 popup_->ShowInactive();
229 state_ = BUBBLE_SHOWN;
232 void StatusBubbleViews::StatusView::Hide() {
233 animation_->Stop();
234 CancelTimer();
235 SetOpacity(0.0);
236 text_.clear();
237 popup_->Hide();
238 state_ = BUBBLE_HIDDEN;
241 void StatusBubbleViews::StatusView::StartTimer(base::TimeDelta time) {
242 if (timer_factory_.HasWeakPtrs())
243 timer_factory_.InvalidateWeakPtrs();
245 base::MessageLoop::current()->PostDelayedTask(
246 FROM_HERE,
247 base::Bind(&StatusBubbleViews::StatusView::OnTimer,
248 timer_factory_.GetWeakPtr()),
249 time);
252 void StatusBubbleViews::StatusView::OnTimer() {
253 if (state_ == BUBBLE_HIDING_TIMER) {
254 state_ = BUBBLE_HIDING_FADE;
255 StartFade(1.0, 0.0, kHideFadeDurationMS);
256 } else if (state_ == BUBBLE_SHOWING_TIMER) {
257 state_ = BUBBLE_SHOWING_FADE;
258 StartFade(0.0, 1.0, kShowFadeDurationMS);
262 void StatusBubbleViews::StatusView::CancelTimer() {
263 if (timer_factory_.HasWeakPtrs())
264 timer_factory_.InvalidateWeakPtrs();
267 void StatusBubbleViews::StatusView::RestartTimer(base::TimeDelta delay) {
268 CancelTimer();
269 StartTimer(delay);
272 void StatusBubbleViews::StatusView::ResetTimer() {
273 if (state_ == BUBBLE_SHOWING_TIMER) {
274 // We hadn't yet begun showing anything when we received a new request
275 // for something to show, so we start from scratch.
276 RestartTimer(base::TimeDelta::FromMilliseconds(kShowDelay));
280 void StatusBubbleViews::StatusView::StartFade(double start,
281 double end,
282 int duration) {
283 animation_.reset(new StatusViewAnimation(this, start, end));
285 // This will also reset the currently-occurring animation.
286 animation_->SetDuration(duration);
287 animation_->Start();
290 void StatusBubbleViews::StatusView::StartHiding() {
291 if (state_ == BUBBLE_SHOWN) {
292 state_ = BUBBLE_HIDING_TIMER;
293 StartTimer(base::TimeDelta::FromMilliseconds(kHideDelay));
294 } else if (state_ == BUBBLE_SHOWING_TIMER) {
295 state_ = BUBBLE_HIDDEN;
296 popup_->Hide();
297 CancelTimer();
298 } else if (state_ == BUBBLE_SHOWING_FADE) {
299 state_ = BUBBLE_HIDING_FADE;
300 // Figure out where we are in the current fade.
301 double current_opacity = animation_->GetCurrentOpacity();
303 // Start a fade in the opposite direction.
304 StartFade(current_opacity, 0.0,
305 static_cast<int>(kHideFadeDurationMS * current_opacity));
309 void StatusBubbleViews::StatusView::StartShowing() {
310 if (state_ == BUBBLE_HIDDEN) {
311 popup_->ShowInactive();
312 state_ = BUBBLE_SHOWING_TIMER;
313 StartTimer(base::TimeDelta::FromMilliseconds(kShowDelay));
314 } else if (state_ == BUBBLE_HIDING_TIMER) {
315 state_ = BUBBLE_SHOWN;
316 CancelTimer();
317 } else if (state_ == BUBBLE_HIDING_FADE) {
318 // We're partway through a fade.
319 state_ = BUBBLE_SHOWING_FADE;
321 // Figure out where we are in the current fade.
322 double current_opacity = animation_->GetCurrentOpacity();
324 // Start a fade in the opposite direction.
325 StartFade(current_opacity, 1.0,
326 static_cast<int>(kShowFadeDurationMS * current_opacity));
327 } else if (state_ == BUBBLE_SHOWING_TIMER) {
328 // We hadn't yet begun showing anything when we received a new request
329 // for something to show, so we start from scratch.
330 ResetTimer();
334 void StatusBubbleViews::StatusView::SetOpacity(double opacity) {
335 popup_->SetOpacity(static_cast<unsigned char>(opacity * 255));
338 void StatusBubbleViews::StatusView::SetStyle(BubbleStyle style) {
339 if (style_ != style) {
340 style_ = style;
341 SchedulePaint();
345 void StatusBubbleViews::StatusView::OnAnimationEnded() {
346 if (state_ == BUBBLE_HIDING_FADE) {
347 state_ = BUBBLE_HIDDEN;
348 popup_->Hide();
349 } else if (state_ == BUBBLE_SHOWING_FADE) {
350 state_ = BUBBLE_SHOWN;
354 const char* StatusBubbleViews::StatusView::GetClassName() const {
355 return "StatusBubbleViews::StatusView";
358 void StatusBubbleViews::StatusView::OnPaint(gfx::Canvas* canvas) {
359 SkPaint paint;
360 paint.setStyle(SkPaint::kFill_Style);
361 paint.setAntiAlias(true);
362 SkColor toolbar_color = theme_service_->GetColor(
363 ThemeProperties::COLOR_TOOLBAR);
364 paint.setColor(toolbar_color);
366 gfx::Rect popup_bounds = popup_->GetWindowBoundsInScreen();
368 SkScalar rad[8] = {};
370 // Top Edges - if the bubble is in its bottom position (sticking downwards),
371 // then we square the top edges. Otherwise, we square the edges based on the
372 // position of the bubble within the window (the bubble is positioned in the
373 // southeast corner in RTL and in the southwest corner in LTR).
374 if (style_ != STYLE_BOTTOM) {
375 if (base::i18n::IsRTL() != (style_ == STYLE_STANDARD_RIGHT)) {
376 // The text is RtL or the bubble is on the right side (but not both).
378 // Top Left corner.
379 rad[0] = kBubbleCornerRadius;
380 rad[1] = kBubbleCornerRadius;
381 } else {
382 // Top Right corner.
383 rad[2] = kBubbleCornerRadius;
384 rad[3] = kBubbleCornerRadius;
388 // Bottom edges - Keep these squared off if the bubble is in its standard
389 // position (sticking upward).
390 if (style_ != STYLE_STANDARD && style_ != STYLE_STANDARD_RIGHT) {
391 // Bottom Right Corner.
392 rad[4] = kBubbleCornerRadius;
393 rad[5] = kBubbleCornerRadius;
395 // Bottom Left Corner.
396 rad[6] = kBubbleCornerRadius;
397 rad[7] = kBubbleCornerRadius;
400 // Draw the bubble's shadow.
401 int width = popup_bounds.width();
402 int height = popup_bounds.height();
403 gfx::Rect rect(gfx::Rect(popup_bounds.size()));
404 SkPaint shadow_paint;
405 shadow_paint.setAntiAlias(true);
406 shadow_paint.setColor(kShadowColor);
408 SkRRect rrect;
409 rrect.setRectRadii(RectToSkRect(rect), (const SkVector*)rad);
410 canvas->sk_canvas()->drawRRect(rrect, shadow_paint);
412 const int shadow_size = 2 * kShadowThickness;
413 // Draw the bubble.
414 rect.SetRect(SkIntToScalar(kShadowThickness), SkIntToScalar(kShadowThickness),
415 SkIntToScalar(width - shadow_size),
416 SkIntToScalar(height - shadow_size));
417 rrect.setRectRadii(RectToSkRect(rect), (const SkVector*)rad);
418 canvas->sk_canvas()->drawRRect(rrect, paint);
420 // Draw highlight text and then the text body. In order to make sure the text
421 // is aligned to the right on RTL UIs, we mirror the text bounds if the
422 // locale is RTL.
423 const gfx::FontList font_list;
424 int text_width =
425 std::min(gfx::GetStringWidth(text_, font_list),
426 width - shadow_size - kTextPositionX - kTextHorizPadding);
427 int text_height = height - shadow_size;
428 gfx::Rect body_bounds(kShadowThickness + kTextPositionX,
429 kShadowThickness,
430 std::max(0, text_width),
431 std::max(0, text_height));
432 body_bounds.set_x(GetMirroredXForRect(body_bounds));
433 SkColor text_color =
434 theme_service_->GetColor(ThemeProperties::COLOR_STATUS_BAR_TEXT);
435 canvas->DrawStringRect(text_, font_list, text_color, body_bounds);
439 // StatusBubbleViews::StatusViewAnimation --------------------------------------
441 StatusBubbleViews::StatusViewAnimation::StatusViewAnimation(
442 StatusView* status_view,
443 double opacity_start,
444 double opacity_end)
445 : gfx::LinearAnimation(kFramerate, this),
446 status_view_(status_view),
447 opacity_start_(opacity_start),
448 opacity_end_(opacity_end) {
451 StatusBubbleViews::StatusViewAnimation::~StatusViewAnimation() {
452 // Remove ourself as a delegate so that we don't get notified when
453 // animations end as a result of destruction.
454 set_delegate(NULL);
457 double StatusBubbleViews::StatusViewAnimation::GetCurrentOpacity() {
458 return opacity_start_ + (opacity_end_ - opacity_start_) *
459 gfx::LinearAnimation::GetCurrentValue();
462 void StatusBubbleViews::StatusViewAnimation::AnimateToState(double state) {
463 status_view_->SetOpacity(GetCurrentOpacity());
466 void StatusBubbleViews::StatusViewAnimation::AnimationEnded(
467 const gfx::Animation* animation) {
468 status_view_->SetOpacity(opacity_end_);
469 status_view_->OnAnimationEnded();
472 // StatusBubbleViews::StatusViewExpander ---------------------------------------
474 // Manages the expansion and contraction of the status bubble as it accommodates
475 // URLs too long to fit in the standard bubble. Changes are passed through the
476 // StatusView to paint.
477 class StatusBubbleViews::StatusViewExpander : public gfx::LinearAnimation,
478 public gfx::AnimationDelegate {
479 public:
480 StatusViewExpander(StatusBubbleViews* status_bubble,
481 StatusView* status_view)
482 : gfx::LinearAnimation(kFramerate, this),
483 status_bubble_(status_bubble),
484 status_view_(status_view),
485 expansion_start_(0),
486 expansion_end_(0) {
489 // Manage the expansion of the bubble.
490 void StartExpansion(const base::string16& expanded_text,
491 int current_width,
492 int expansion_end);
494 // Set width of fully expanded bubble.
495 void SetExpandedWidth(int expanded_width);
497 private:
498 // Animation functions.
499 int GetCurrentBubbleWidth();
500 void SetBubbleWidth(int width);
501 void AnimateToState(double state) override;
502 void AnimationEnded(const gfx::Animation* animation) override;
504 // Manager that owns us.
505 StatusBubbleViews* status_bubble_;
507 // Change the bounds and text of this view.
508 StatusView* status_view_;
510 // Text elided (if needed) to fit maximum status bar width.
511 base::string16 expanded_text_;
513 // Widths at expansion start and end.
514 int expansion_start_;
515 int expansion_end_;
518 void StatusBubbleViews::StatusViewExpander::AnimateToState(double state) {
519 SetBubbleWidth(GetCurrentBubbleWidth());
522 void StatusBubbleViews::StatusViewExpander::AnimationEnded(
523 const gfx::Animation* animation) {
524 SetBubbleWidth(expansion_end_);
525 status_view_->SetText(expanded_text_, false);
528 void StatusBubbleViews::StatusViewExpander::StartExpansion(
529 const base::string16& expanded_text,
530 int expansion_start,
531 int expansion_end) {
532 expanded_text_ = expanded_text;
533 expansion_start_ = expansion_start;
534 expansion_end_ = expansion_end;
535 int min_duration = std::max(kMinExpansionStepDurationMS,
536 static_cast<int>(kMaxExpansionStepDurationMS *
537 (expansion_end - expansion_start) / 100.0));
538 SetDuration(std::min(kMaxExpansionStepDurationMS, min_duration));
539 Start();
542 int StatusBubbleViews::StatusViewExpander::GetCurrentBubbleWidth() {
543 return static_cast<int>(expansion_start_ +
544 (expansion_end_ - expansion_start_) *
545 gfx::LinearAnimation::GetCurrentValue());
548 void StatusBubbleViews::StatusViewExpander::SetBubbleWidth(int width) {
549 status_bubble_->SetBubbleWidth(width);
550 status_view_->SchedulePaint();
554 // StatusBubbleViews -----------------------------------------------------------
556 const int StatusBubbleViews::kShadowThickness = 1;
558 StatusBubbleViews::StatusBubbleViews(views::View* base_view)
559 : contains_mouse_(false),
560 offset_(0),
561 base_view_(base_view),
562 view_(NULL),
563 download_shelf_is_visible_(false),
564 is_expanded_(false),
565 expand_timer_factory_(this) {
566 expand_view_.reset();
569 StatusBubbleViews::~StatusBubbleViews() {
570 CancelExpandTimer();
571 if (popup_.get())
572 popup_->CloseNow();
575 void StatusBubbleViews::Init() {
576 if (!popup_.get()) {
577 popup_.reset(new views::Widget);
578 views::Widget* frame = base_view_->GetWidget();
579 if (!view_)
580 view_ = new StatusView(popup_.get(), frame->GetThemeProvider());
581 if (!expand_view_.get())
582 expand_view_.reset(new StatusViewExpander(this, view_));
583 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
584 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
585 params.accept_events = false;
586 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
587 params.parent = frame->GetNativeView();
588 params.context = frame->GetNativeWindow();
589 popup_->Init(params);
590 // We do our own animation and don't want any from the system.
591 popup_->SetVisibilityChangedAnimationsEnabled(false);
592 popup_->SetOpacity(0x00);
593 popup_->SetContentsView(view_);
594 #if defined(USE_ASH)
595 ash::wm::GetWindowState(popup_->GetNativeWindow())->
596 set_ignored_by_shelf(true);
597 #endif
598 RepositionPopup();
602 void StatusBubbleViews::Reposition() {
603 // In restored mode, the client area has a client edge between it and the
604 // frame.
605 int overlap = kShadowThickness;
606 int height = GetPreferredSize().height();
607 int base_view_height = base_view()->bounds().height();
608 gfx::Point origin(-overlap, base_view_height - height + overlap);
609 SetBounds(origin.x(), origin.y(), base_view()->bounds().width() / 3, height);
612 void StatusBubbleViews::RepositionPopup() {
613 if (popup_.get()) {
614 gfx::Point top_left;
615 views::View::ConvertPointToScreen(base_view_, &top_left);
617 popup_->SetBounds(gfx::Rect(top_left.x() + position_.x(),
618 top_left.y() + position_.y(),
619 size_.width(), size_.height()));
623 gfx::Size StatusBubbleViews::GetPreferredSize() {
624 return gfx::Size(0, gfx::FontList().GetHeight() + kTotalVerticalPadding);
627 void StatusBubbleViews::SetBounds(int x, int y, int w, int h) {
628 original_position_.SetPoint(x, y);
629 position_.SetPoint(base_view_->GetMirroredXWithWidthInView(x, w), y);
630 size_.SetSize(w, h);
631 RepositionPopup();
632 if (popup_.get() && contains_mouse_)
633 AvoidMouse(last_mouse_moved_location_);
636 void StatusBubbleViews::SetStatus(const base::string16& status_text) {
637 if (size_.IsEmpty())
638 return; // We have no bounds, don't attempt to show the popup.
640 if (status_text_ == status_text && !status_text.empty())
641 return;
643 if (!IsFrameVisible())
644 return; // Don't show anything if the parent isn't visible.
646 Init();
647 status_text_ = status_text;
648 if (!status_text_.empty()) {
649 view_->SetText(status_text, true);
650 view_->Show();
651 } else if (!url_text_.empty()) {
652 view_->SetText(url_text_, true);
653 } else {
654 view_->SetText(base::string16(), true);
658 void StatusBubbleViews::SetURL(const GURL& url, const std::string& languages) {
659 url_ = url;
660 languages_ = languages;
661 if (size_.IsEmpty())
662 return; // We have no bounds, don't attempt to show the popup.
664 Init();
666 // If we want to clear a displayed URL but there is a status still to
667 // display, display that status instead.
668 if (url.is_empty() && !status_text_.empty()) {
669 url_text_ = base::string16();
670 if (IsFrameVisible())
671 view_->SetText(status_text_, true);
672 return;
675 // Reset expansion state only when bubble is completely hidden.
676 if (view_->state() == StatusView::BUBBLE_HIDDEN) {
677 is_expanded_ = false;
678 SetBubbleWidth(GetStandardStatusBubbleWidth());
681 // Set Elided Text corresponding to the GURL object.
682 gfx::Rect popup_bounds = popup_->GetWindowBoundsInScreen();
683 int text_width = static_cast<int>(popup_bounds.width() -
684 (kShadowThickness * 2) - kTextPositionX - kTextHorizPadding - 1);
685 url_text_ = ElideUrl(url, gfx::FontList(), text_width, languages);
687 // An URL is always treated as a left-to-right string. On right-to-left UIs
688 // we need to explicitly mark the URL as LTR to make sure it is displayed
689 // correctly.
690 url_text_ = base::i18n::GetDisplayStringInLTRDirectionality(url_text_);
692 if (IsFrameVisible()) {
693 view_->SetText(url_text_, true);
695 CancelExpandTimer();
697 // If bubble is already in expanded state, shift to adjust to new text
698 // size (shrinking or expanding). Otherwise delay.
699 if (is_expanded_ && !url.is_empty()) {
700 ExpandBubble();
701 } else if (net::FormatUrl(url, languages).length() > url_text_.length()) {
702 base::MessageLoop::current()->PostDelayedTask(
703 FROM_HERE,
704 base::Bind(&StatusBubbleViews::ExpandBubble,
705 expand_timer_factory_.GetWeakPtr()),
706 base::TimeDelta::FromMilliseconds(kExpandHoverDelayMS));
711 void StatusBubbleViews::Hide() {
712 status_text_ = base::string16();
713 url_text_ = base::string16();
714 if (view_)
715 view_->Hide();
718 void StatusBubbleViews::MouseMoved(const gfx::Point& location,
719 bool left_content) {
720 contains_mouse_ = !left_content;
721 if (left_content) {
722 RepositionPopup();
723 return;
725 last_mouse_moved_location_ = location;
727 if (view_) {
728 view_->ResetTimer();
730 if (view_->state() != StatusView::BUBBLE_HIDDEN &&
731 view_->state() != StatusView::BUBBLE_HIDING_FADE &&
732 view_->state() != StatusView::BUBBLE_HIDING_TIMER) {
733 AvoidMouse(location);
738 void StatusBubbleViews::UpdateDownloadShelfVisibility(bool visible) {
739 download_shelf_is_visible_ = visible;
742 void StatusBubbleViews::AvoidMouse(const gfx::Point& location) {
743 // Get the position of the frame.
744 gfx::Point top_left;
745 views::View::ConvertPointToScreen(base_view_, &top_left);
746 // Border included.
747 int window_width = base_view_->GetLocalBounds().width();
749 // Get the cursor position relative to the popup.
750 gfx::Point relative_location = location;
751 if (base::i18n::IsRTL()) {
752 int top_right_x = top_left.x() + window_width;
753 relative_location.set_x(top_right_x - relative_location.x());
754 } else {
755 relative_location.set_x(
756 relative_location.x() - (top_left.x() + position_.x()));
758 relative_location.set_y(
759 relative_location.y() - (top_left.y() + position_.y()));
761 // If the mouse is in a position where we think it would move the
762 // status bubble, figure out where and how the bubble should be moved.
763 if (relative_location.y() > -kMousePadding &&
764 relative_location.x() < size_.width() + kMousePadding) {
765 int offset = kMousePadding + relative_location.y();
767 // Make the movement non-linear.
768 offset = offset * offset / kMousePadding;
770 // When the mouse is entering from the right, we want the offset to be
771 // scaled by how horizontally far away the cursor is from the bubble.
772 if (relative_location.x() > size_.width()) {
773 offset = static_cast<int>(static_cast<float>(offset) * (
774 static_cast<float>(kMousePadding -
775 (relative_location.x() - size_.width())) /
776 static_cast<float>(kMousePadding)));
779 // Cap the offset and change the visual presentation of the bubble
780 // depending on where it ends up (so that rounded corners square off
781 // and mate to the edges of the tab content).
782 if (offset >= size_.height() - kShadowThickness * 2) {
783 offset = size_.height() - kShadowThickness * 2;
784 view_->SetStyle(StatusView::STYLE_BOTTOM);
785 } else if (offset > kBubbleCornerRadius / 2 - kShadowThickness) {
786 view_->SetStyle(StatusView::STYLE_FLOATING);
787 } else {
788 view_->SetStyle(StatusView::STYLE_STANDARD);
791 // Check if the bubble sticks out from the monitor or will obscure
792 // download shelf.
793 gfx::NativeView window = base_view_->GetWidget()->GetNativeView();
794 gfx::Rect monitor_rect = gfx::Screen::GetScreenFor(window)->
795 GetDisplayNearestWindow(window).work_area();
796 const int bubble_bottom_y = top_left.y() + position_.y() + size_.height();
798 if (bubble_bottom_y + offset > monitor_rect.height() ||
799 (download_shelf_is_visible_ &&
800 (view_->style() == StatusView::STYLE_FLOATING ||
801 view_->style() == StatusView::STYLE_BOTTOM))) {
802 // The offset is still too large. Move the bubble to the right and reset
803 // Y offset_ to zero.
804 view_->SetStyle(StatusView::STYLE_STANDARD_RIGHT);
805 offset_ = 0;
807 // Subtract border width + bubble width.
808 int right_position_x = window_width - (position_.x() + size_.width());
809 popup_->SetBounds(gfx::Rect(top_left.x() + right_position_x,
810 top_left.y() + position_.y(),
811 size_.width(), size_.height()));
812 } else {
813 offset_ = offset;
814 popup_->SetBounds(gfx::Rect(top_left.x() + position_.x(),
815 top_left.y() + position_.y() + offset_,
816 size_.width(), size_.height()));
818 } else if (offset_ != 0 ||
819 view_->style() == StatusView::STYLE_STANDARD_RIGHT) {
820 offset_ = 0;
821 view_->SetStyle(StatusView::STYLE_STANDARD);
822 popup_->SetBounds(gfx::Rect(top_left.x() + position_.x(),
823 top_left.y() + position_.y(),
824 size_.width(), size_.height()));
828 bool StatusBubbleViews::IsFrameVisible() {
829 views::Widget* frame = base_view_->GetWidget();
830 if (!frame->IsVisible())
831 return false;
833 views::Widget* window = frame->GetTopLevelWidget();
834 return !window || !window->IsMinimized();
837 bool StatusBubbleViews::IsFrameMaximized() {
838 views::Widget* frame = base_view_->GetWidget();
839 views::Widget* window = frame->GetTopLevelWidget();
840 return window && window->IsMaximized();
843 void StatusBubbleViews::ExpandBubble() {
844 // Elide URL to maximum possible size, then check actual length (it may
845 // still be too long to fit) before expanding bubble.
846 gfx::Rect popup_bounds = popup_->GetWindowBoundsInScreen();
847 int max_status_bubble_width = GetMaxStatusBubbleWidth();
848 const gfx::FontList font_list;
849 url_text_ = ElideUrl(url_, font_list, max_status_bubble_width, languages_);
850 int expanded_bubble_width =
851 std::max(GetStandardStatusBubbleWidth(),
852 std::min(gfx::GetStringWidth(url_text_, font_list) +
853 (kShadowThickness * 2) + kTextPositionX +
854 kTextHorizPadding + 1,
855 max_status_bubble_width));
856 is_expanded_ = true;
857 expand_view_->StartExpansion(url_text_, popup_bounds.width(),
858 expanded_bubble_width);
861 int StatusBubbleViews::GetStandardStatusBubbleWidth() {
862 return base_view_->bounds().width() / 3;
865 int StatusBubbleViews::GetMaxStatusBubbleWidth() {
866 const ui::NativeTheme* theme = base_view_->GetNativeTheme();
867 return static_cast<int>(std::max(0, base_view_->bounds().width() -
868 (kShadowThickness * 2) - kTextPositionX - kTextHorizPadding - 1 -
869 views::NativeScrollBar::GetVerticalScrollBarWidth(theme)));
872 void StatusBubbleViews::SetBubbleWidth(int width) {
873 size_.set_width(width);
874 SetBounds(original_position_.x(), original_position_.y(),
875 size_.width(), size_.height());
878 void StatusBubbleViews::CancelExpandTimer() {
879 if (expand_timer_factory_.HasWeakPtrs())
880 expand_timer_factory_.InvalidateWeakPtrs();