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"
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"
37 #include "ui/aura/window.h"
41 #include "ash/wm/window_state.h"
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
52 static const int kMousePadding
= 20;
54 // The horizontal offset of the text within the status bubble, not including the
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
{
83 // The bubble can be in one of many states:
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.
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.
117 // Hide the bubble instantly.
120 // Resets any timers we have. Typically called when the user moves a
127 // Manage the timers that control the delay before a fade begins or ends.
128 void StartTimer(base::TimeDelta time
);
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
);
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
;
149 base::WeakPtrFactory
<StatusBubbleViews::StatusView
> timer_factory_
;
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_
;
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
),
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.
194 void StatusBubbleViews::StatusView::SetText(const base::string16
& text
,
195 bool should_animate_open
) {
197 // The string was empty.
200 // We want to show the string.
205 if (should_animate_open
)
210 void StatusBubbleViews::StatusView::Show() {
214 popup_
->ShowInactive();
215 state_
= BUBBLE_SHOWN
;
218 void StatusBubbleViews::StatusView::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(
233 base::Bind(&StatusBubbleViews::StatusView::OnTimer
,
234 timer_factory_
.GetWeakPtr()),
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
) {
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
,
269 opacity_start_
= start
;
272 // This will also reset the currently-occurring animation.
273 SetDuration(duration
);
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
;
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
;
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.
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
;
342 } else if (state_
== BUBBLE_SHOWING_FADE
) {
343 state_
= BUBBLE_SHOWN
;
347 void StatusBubbleViews::StatusView::SetStyle(BubbleStyle style
) {
348 if (style_
!= style
) {
354 void StatusBubbleViews::StatusView::OnPaint(gfx::Canvas
* canvas
) {
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.
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
) {
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).
384 rad
[0] = SkIntToScalar(kBubbleCornerRadius
);
385 rad
[1] = SkIntToScalar(kBubbleCornerRadius
);
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.
408 // Bottom Left Corner.
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())));
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
);
433 rect
.set(SkIntToScalar(kShadowThickness
),
434 SkIntToScalar(kShadowThickness
),
435 SkIntToScalar(width
- kShadowThickness
),
436 SkIntToScalar(height
- kShadowThickness
));
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
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
,
451 std::max(0, text_width
),
452 std::max(0, text_height
));
453 body_bounds
.set_x(GetMirroredXForRect(body_bounds
));
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
{
466 StatusViewExpander(StatusBubbleViews
* status_bubble
,
467 StatusView
* status_view
)
468 : gfx::LinearAnimation(kFramerate
, this),
469 status_bubble_(status_bubble
),
470 status_view_(status_view
),
475 // Manage the expansion of the bubble.
476 void StartExpansion(const base::string16
& expanded_text
,
480 // Set width of fully expanded bubble.
481 void SetExpandedWidth(int expanded_width
);
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_
;
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
,
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
));
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),
547 base_view_(base_view
),
549 download_shelf_is_visible_(false),
551 expand_timer_factory_(this) {
552 expand_view_
.reset();
555 StatusBubbleViews::~StatusBubbleViews() {
561 void StatusBubbleViews::Init() {
563 popup_
.reset(new views::Widget
);
564 views::Widget
* frame
= base_view_
->GetWidget();
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");
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_
);
584 ash::wm::GetWindowState(popup_
->GetNativeWindow())->
585 set_ignored_by_shelf(true);
591 void StatusBubbleViews::Reposition() {
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
);
611 if (popup_
.get() && contains_mouse_
)
612 AvoidMouse(last_mouse_moved_location_
);
615 void StatusBubbleViews::SetStatus(const base::string16
& status_text
) {
617 return; // We have no bounds, don't attempt to show the popup.
619 if (status_text_
== status_text
&& !status_text
.empty())
622 if (!IsFrameVisible())
623 return; // Don't show anything if the parent isn't visible.
626 status_text_
= status_text
;
627 if (!status_text_
.empty()) {
628 view_
->SetText(status_text
, true);
630 } else if (!url_text_
.empty()) {
631 view_
->SetText(url_text_
, true);
633 view_
->SetText(base::string16(), true);
637 void StatusBubbleViews::SetURL(const GURL
& url
, const std::string
& languages
) {
639 languages_
= languages
;
641 return; // We have no bounds, don't attempt to show the popup.
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);
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
669 url_text_
= base::i18n::GetDisplayStringInLTRDirectionality(url_text_
);
671 if (IsFrameVisible()) {
672 view_
->SetText(url_text_
, true);
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()) {
680 } else if (net::FormatUrl(url
, languages
).length() > url_text_
.length()) {
681 base::MessageLoop::current()->PostDelayedTask(
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();
697 void StatusBubbleViews::MouseMoved(const gfx::Point
& location
,
699 contains_mouse_
= !left_content
;
704 last_mouse_moved_location_
= location
;
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.
724 views::View::ConvertPointToScreen(base_view_
, &top_left
);
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());
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
);
767 view_
->SetStyle(StatusView::STYLE_STANDARD
);
770 // Check if the bubble sticks out from the monitor or will obscure
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
);
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()));
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
) {
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())
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
));
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();