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 "grit/generated_resources.h"
16 #include "grit/theme_resources.h"
17 #include "net/base/net_util.h"
18 #include "third_party/skia/include/core/SkPaint.h"
19 #include "third_party/skia/include/core/SkPath.h"
20 #include "third_party/skia/include/core/SkRect.h"
21 #include "ui/base/resource/resource_bundle.h"
22 #include "ui/base/theme_provider.h"
23 #include "ui/gfx/animation/animation_delegate.h"
24 #include "ui/gfx/animation/linear_animation.h"
25 #include "ui/gfx/canvas.h"
26 #include "ui/gfx/point.h"
27 #include "ui/gfx/screen.h"
28 #include "ui/gfx/skia_util.h"
29 #include "ui/gfx/text_elider.h"
30 #include "ui/native_theme/native_theme.h"
31 #include "ui/views/controls/label.h"
32 #include "ui/views/controls/scrollbar/native_scroll_bar.h"
33 #include "ui/views/widget/root_view.h"
34 #include "ui/views/widget/widget.h"
38 #include "ash/wm/window_state.h"
41 // The alpha and color of the bubble's shadow.
42 static const SkColor kShadowColor = SkColorSetARGB(30, 0, 0, 0);
44 // The roundedness of the edges of our bubble.
45 static const int kBubbleCornerRadius = 4;
47 // How close the mouse can get to the infobubble before it starts sliding
49 static const int kMousePadding = 20;
51 // The horizontal offset of the text within the status bubble, not including the
53 static const int kTextPositionX = 3;
55 // The minimum horizontal space between the (right) end of the text and the edge
56 // of the status bubble, not including the outer shadow ring.
57 static const int kTextHorizPadding = 1;
59 // Delays before we start hiding or showing the bubble after we receive a
60 // show or hide request.
61 static const int kShowDelay = 80;
62 static const int kHideDelay = 250;
64 // How long each fade should last for.
65 static const int kShowFadeDurationMS = 120;
66 static const int kHideFadeDurationMS = 200;
67 static const int kFramerate = 25;
69 // How long each expansion step should take.
70 static const int kMinExpansionStepDurationMS = 20;
71 static const int kMaxExpansionStepDurationMS = 150;
73 // View -----------------------------------------------------------------------
74 // StatusView manages the display of the bubble, applying text changes and
75 // fading in or out the bubble as required.
76 class StatusBubbleViews::StatusView : public views::Label,
77 public gfx::LinearAnimation,
78 public gfx::AnimationDelegate {
80 StatusView(StatusBubble* status_bubble,
82 ui::ThemeProvider* theme_provider)
83 : gfx::LinearAnimation(kFramerate, this),
84 stage_(BUBBLE_HIDDEN),
85 style_(STYLE_STANDARD),
87 status_bubble_(status_bubble),
91 theme_service_(theme_provider) {
92 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
93 SetFont(rb.GetFont(ui::ResourceBundle::BaseFont));
96 virtual ~StatusView() {
97 // Remove ourself as a delegate so that we don't get notified when
98 // animations end as a result of destruction.
104 // The bubble can be in one of many stages:
106 BUBBLE_HIDDEN, // Entirely BUBBLE_HIDDEN.
107 BUBBLE_HIDING_FADE, // In a fade-out transition.
108 BUBBLE_HIDING_TIMER, // Waiting before a fade-out.
109 BUBBLE_SHOWING_TIMER, // Waiting before a fade-in.
110 BUBBLE_SHOWING_FADE, // In a fade-in transition.
111 BUBBLE_SHOWN // Fully visible.
121 // Set the bubble text to a certain value, hides the bubble if text is
122 // an empty string. Trigger animation sequence to display if
123 // |should_animate_open|.
124 void SetText(const string16& text, bool should_animate_open);
126 BubbleStage GetState() const { return stage_; }
128 void SetStyle(BubbleStyle style);
130 BubbleStyle GetStyle() const { return style_; }
132 // Show the bubble instantly.
135 // Hide the bubble instantly.
138 // Resets any timers we have. Typically called when the user moves a
145 // Manage the timers that control the delay before a fade begins or ends.
146 void StartTimer(base::TimeDelta time);
149 void RestartTimer(base::TimeDelta delay);
151 // Manage the fades and starting and stopping the animations correctly.
152 void StartFade(double start, double end, int duration);
156 // Animation functions.
157 double GetCurrentOpacity();
158 void SetOpacity(double opacity);
159 virtual void AnimateToState(double state) OVERRIDE;
160 virtual void AnimationEnded(const Animation* animation) OVERRIDE;
162 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
167 base::WeakPtrFactory<StatusBubbleViews::StatusView> timer_factory_;
170 StatusBubble* status_bubble_;
172 // Handle to the widget that contains us.
173 views::Widget* popup_;
175 // The currently-displayed text.
178 // Start and end opacities for the current transition - note that as a
179 // fade-in can easily turn into a fade out, opacity_start_ is sometimes
180 // a value between 0 and 1.
181 double opacity_start_;
184 // Holds the theme provider of the frame that created us.
185 ui::ThemeProvider* theme_service_;
188 void StatusBubbleViews::StatusView::SetText(const string16& text,
189 bool should_animate_open) {
191 // The string was empty.
194 // We want to show the string.
196 if (should_animate_open)
203 void StatusBubbleViews::StatusView::Show() {
208 stage_ = BUBBLE_SHOWN;
211 void StatusBubbleViews::StatusView::Hide() {
217 stage_ = BUBBLE_HIDDEN;
220 void StatusBubbleViews::StatusView::StartTimer(base::TimeDelta time) {
221 if (timer_factory_.HasWeakPtrs())
222 timer_factory_.InvalidateWeakPtrs();
224 base::MessageLoop::current()->PostDelayedTask(
226 base::Bind(&StatusBubbleViews::StatusView::OnTimer,
227 timer_factory_.GetWeakPtr()),
231 void StatusBubbleViews::StatusView::OnTimer() {
232 if (stage_ == BUBBLE_HIDING_TIMER) {
233 stage_ = BUBBLE_HIDING_FADE;
234 StartFade(1.0, 0.0, kHideFadeDurationMS);
235 } else if (stage_ == BUBBLE_SHOWING_TIMER) {
236 stage_ = BUBBLE_SHOWING_FADE;
237 StartFade(0.0, 1.0, kShowFadeDurationMS);
241 void StatusBubbleViews::StatusView::CancelTimer() {
242 if (timer_factory_.HasWeakPtrs())
243 timer_factory_.InvalidateWeakPtrs();
246 void StatusBubbleViews::StatusView::RestartTimer(base::TimeDelta delay) {
251 void StatusBubbleViews::StatusView::ResetTimer() {
252 if (stage_ == BUBBLE_SHOWING_TIMER) {
253 // We hadn't yet begun showing anything when we received a new request
254 // for something to show, so we start from scratch.
255 RestartTimer(base::TimeDelta::FromMilliseconds(kShowDelay));
259 void StatusBubbleViews::StatusView::StartFade(double start,
262 opacity_start_ = start;
265 // This will also reset the currently-occurring animation.
266 SetDuration(duration);
270 void StatusBubbleViews::StatusView::StartHiding() {
271 if (stage_ == BUBBLE_SHOWN) {
272 stage_ = BUBBLE_HIDING_TIMER;
273 StartTimer(base::TimeDelta::FromMilliseconds(kHideDelay));
274 } else if (stage_ == BUBBLE_SHOWING_TIMER) {
275 stage_ = BUBBLE_HIDDEN;
278 } else if (stage_ == BUBBLE_SHOWING_FADE) {
279 stage_ = BUBBLE_HIDING_FADE;
280 // Figure out where we are in the current fade.
281 double current_opacity = GetCurrentOpacity();
283 // Start a fade in the opposite direction.
284 StartFade(current_opacity, 0.0,
285 static_cast<int>(kHideFadeDurationMS * current_opacity));
289 void StatusBubbleViews::StatusView::StartShowing() {
290 if (stage_ == BUBBLE_HIDDEN) {
292 stage_ = BUBBLE_SHOWING_TIMER;
293 StartTimer(base::TimeDelta::FromMilliseconds(kShowDelay));
294 } else if (stage_ == BUBBLE_HIDING_TIMER) {
295 stage_ = BUBBLE_SHOWN;
297 } else if (stage_ == BUBBLE_HIDING_FADE) {
298 // We're partway through a fade.
299 stage_ = BUBBLE_SHOWING_FADE;
301 // Figure out where we are in the current fade.
302 double current_opacity = GetCurrentOpacity();
304 // Start a fade in the opposite direction.
305 StartFade(current_opacity, 1.0,
306 static_cast<int>(kShowFadeDurationMS * current_opacity));
307 } else if (stage_ == BUBBLE_SHOWING_TIMER) {
308 // We hadn't yet begun showing anything when we received a new request
309 // for something to show, so we start from scratch.
314 // Animation functions.
315 double StatusBubbleViews::StatusView::GetCurrentOpacity() {
316 return opacity_start_ + (opacity_end_ - opacity_start_) *
317 gfx::LinearAnimation::GetCurrentValue();
320 void StatusBubbleViews::StatusView::SetOpacity(double opacity) {
321 popup_->SetOpacity(static_cast<unsigned char>(opacity * 255));
324 void StatusBubbleViews::StatusView::AnimateToState(double state) {
325 SetOpacity(GetCurrentOpacity());
328 void StatusBubbleViews::StatusView::AnimationEnded(
329 const gfx::Animation* animation) {
330 SetOpacity(opacity_end_);
332 if (stage_ == BUBBLE_HIDING_FADE) {
333 stage_ = BUBBLE_HIDDEN;
335 } else if (stage_ == BUBBLE_SHOWING_FADE) {
336 stage_ = BUBBLE_SHOWN;
340 void StatusBubbleViews::StatusView::SetStyle(BubbleStyle style) {
341 if (style_ != style) {
347 void StatusBubbleViews::StatusView::OnPaint(gfx::Canvas* canvas) {
349 paint.setStyle(SkPaint::kFill_Style);
350 paint.setAntiAlias(true);
351 SkColor toolbar_color = theme_service_->GetColor(
352 ThemeProperties::COLOR_TOOLBAR);
353 paint.setColor(toolbar_color);
355 gfx::Rect popup_bounds = popup_->GetWindowBoundsInScreen();
357 // Figure out how to round the bubble's four corners.
360 // Top Edges - if the bubble is in its bottom position (sticking downwards),
361 // then we square the top edges. Otherwise, we square the edges based on the
362 // position of the bubble within the window (the bubble is positioned in the
363 // southeast corner in RTL and in the southwest corner in LTR).
364 if (style_ == STYLE_BOTTOM) {
373 if (base::i18n::IsRTL() != (style_ == STYLE_STANDARD_RIGHT)) {
374 // The text is RtL or the bubble is on the right side (but not both).
377 rad[0] = SkIntToScalar(kBubbleCornerRadius);
378 rad[1] = SkIntToScalar(kBubbleCornerRadius);
389 rad[2] = SkIntToScalar(kBubbleCornerRadius);
390 rad[3] = SkIntToScalar(kBubbleCornerRadius);
394 // Bottom edges - square these off if the bubble is in its standard position
395 // (sticking upward).
396 if (style_ == STYLE_STANDARD || style_ == STYLE_STANDARD_RIGHT) {
397 // Bottom Right Corner.
401 // Bottom Left Corner.
405 // Bottom Right Corner.
406 rad[4] = SkIntToScalar(kBubbleCornerRadius);
407 rad[5] = SkIntToScalar(kBubbleCornerRadius);
409 // Bottom Left Corner.
410 rad[6] = SkIntToScalar(kBubbleCornerRadius);
411 rad[7] = SkIntToScalar(kBubbleCornerRadius);
414 // Draw the bubble's shadow.
415 int width = popup_bounds.width();
416 int height = popup_bounds.height();
417 SkRect rect(gfx::RectToSkRect(gfx::Rect(popup_bounds.size())));
419 shadow_path.addRoundRect(rect, rad, SkPath::kCW_Direction);
420 SkPaint shadow_paint;
421 shadow_paint.setAntiAlias(true);
422 shadow_paint.setColor(kShadowColor);
423 canvas->DrawPath(shadow_path, shadow_paint);
426 rect.set(SkIntToScalar(kShadowThickness),
427 SkIntToScalar(kShadowThickness),
428 SkIntToScalar(width - kShadowThickness),
429 SkIntToScalar(height - kShadowThickness));
431 path.addRoundRect(rect, rad, SkPath::kCW_Direction);
432 canvas->DrawPath(path, paint);
434 // Draw highlight text and then the text body. In order to make sure the text
435 // is aligned to the right on RTL UIs, we mirror the text bounds if the
437 int text_width = std::min(
438 views::Label::font().GetStringWidth(text_),
439 width - (kShadowThickness * 2) - kTextPositionX - kTextHorizPadding);
440 int text_height = height - (kShadowThickness * 2);
441 gfx::Rect body_bounds(kShadowThickness + kTextPositionX,
443 std::max(0, text_width),
444 std::max(0, text_height));
445 body_bounds.set_x(GetMirroredXForRect(body_bounds));
447 theme_service_->GetColor(ThemeProperties::COLOR_TAB_TEXT);
449 // DrawStringInt doesn't handle alpha, so we'll do the blending ourselves.
450 text_color = SkColorSetARGB(
451 SkColorGetA(text_color),
452 (SkColorGetR(text_color) + SkColorGetR(toolbar_color)) / 2,
453 (SkColorGetG(text_color) + SkColorGetR(toolbar_color)) / 2,
454 (SkColorGetB(text_color) + SkColorGetR(toolbar_color)) / 2);
455 canvas->DrawStringInt(text_,
456 views::Label::font(),
461 body_bounds.height());
464 // StatusViewExpander ---------------------------------------------------------
465 // Manages the expansion and contraction of the status bubble as it accommodates
466 // URLs too long to fit in the standard bubble. Changes are passed through the
467 // StatusView to paint.
468 class StatusBubbleViews::StatusViewExpander : public gfx::LinearAnimation,
469 public gfx::AnimationDelegate {
471 StatusViewExpander(StatusBubbleViews* status_bubble,
472 StatusView* status_view)
473 : gfx::LinearAnimation(kFramerate, this),
474 status_bubble_(status_bubble),
475 status_view_(status_view),
480 // Manage the expansion of the bubble.
481 void StartExpansion(const string16& expanded_text,
485 // Set width of fully expanded bubble.
486 void SetExpandedWidth(int expanded_width);
489 // Animation functions.
490 int GetCurrentBubbleWidth();
491 void SetBubbleWidth(int width);
492 virtual void AnimateToState(double state) OVERRIDE;
493 virtual void AnimationEnded(const gfx::Animation* animation) OVERRIDE;
495 // Manager that owns us.
496 StatusBubbleViews* status_bubble_;
498 // Change the bounds and text of this view.
499 StatusView* status_view_;
501 // Text elided (if needed) to fit maximum status bar width.
502 string16 expanded_text_;
504 // Widths at expansion start and end.
505 int expansion_start_;
509 void StatusBubbleViews::StatusViewExpander::AnimateToState(double state) {
510 SetBubbleWidth(GetCurrentBubbleWidth());
513 void StatusBubbleViews::StatusViewExpander::AnimationEnded(
514 const gfx::Animation* animation) {
515 SetBubbleWidth(expansion_end_);
516 status_view_->SetText(expanded_text_, false);
519 void StatusBubbleViews::StatusViewExpander::StartExpansion(
520 const string16& expanded_text,
523 expanded_text_ = expanded_text;
524 expansion_start_ = expansion_start;
525 expansion_end_ = expansion_end;
526 int min_duration = std::max(kMinExpansionStepDurationMS,
527 static_cast<int>(kMaxExpansionStepDurationMS *
528 (expansion_end - expansion_start) / 100.0));
529 SetDuration(std::min(kMaxExpansionStepDurationMS, min_duration));
533 int StatusBubbleViews::StatusViewExpander::GetCurrentBubbleWidth() {
534 return static_cast<int>(expansion_start_ +
535 (expansion_end_ - expansion_start_) *
536 gfx::LinearAnimation::GetCurrentValue());
539 void StatusBubbleViews::StatusViewExpander::SetBubbleWidth(int width) {
540 status_bubble_->SetBubbleWidth(width);
541 status_view_->SchedulePaint();
544 // StatusBubble ---------------------------------------------------------------
546 const int StatusBubbleViews::kShadowThickness = 1;
548 StatusBubbleViews::StatusBubbleViews(views::View* base_view)
549 : contains_mouse_(false),
552 base_view_(base_view),
554 download_shelf_is_visible_(false),
556 expand_timer_factory_(this) {
557 expand_view_.reset();
560 StatusBubbleViews::~StatusBubbleViews() {
566 void StatusBubbleViews::Init() {
568 popup_.reset(new views::Widget);
569 views::Widget* frame = base_view_->GetWidget();
571 view_ = new StatusView(this, popup_.get(), frame->GetThemeProvider());
572 if (!expand_view_.get())
573 expand_view_.reset(new StatusViewExpander(this, view_));
574 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
575 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
576 params.accept_events = false;
577 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
578 params.parent = frame->GetNativeView();
579 params.context = frame->GetNativeView();
580 popup_->Init(params);
581 // We do our own animation and don't want any from the system.
582 popup_->SetVisibilityChangedAnimationsEnabled(false);
583 popup_->SetOpacity(0x00);
584 popup_->SetContentsView(view_);
586 ash::wm::GetWindowState(popup_->GetNativeWindow())->
587 set_ignored_by_shelf(true);
593 void StatusBubbleViews::Reposition() {
596 views::View::ConvertPointToScreen(base_view_, &top_left);
598 popup_->SetBounds(gfx::Rect(top_left.x() + position_.x(),
599 top_left.y() + position_.y(),
600 size_.width(), size_.height()));
604 gfx::Size StatusBubbleViews::GetPreferredSize() {
605 return gfx::Size(0, ui::ResourceBundle::GetSharedInstance().GetFont(
606 ui::ResourceBundle::BaseFont).GetHeight() + kTotalVerticalPadding);
609 void StatusBubbleViews::SetBounds(int x, int y, int w, int h) {
610 original_position_.SetPoint(x, y);
611 position_.SetPoint(base_view_->GetMirroredXWithWidthInView(x, w), y);
614 if (popup_.get() && contains_mouse_)
615 AvoidMouse(last_mouse_moved_location_);
618 void StatusBubbleViews::SetStatus(const string16& status_text) {
620 return; // We have no bounds, don't attempt to show the popup.
622 if (status_text_ == status_text && !status_text.empty())
625 if (!IsFrameVisible())
626 return; // Don't show anything if the parent isn't visible.
629 status_text_ = status_text;
630 if (!status_text_.empty()) {
631 view_->SetText(status_text, true);
633 } else if (!url_text_.empty()) {
634 view_->SetText(url_text_, true);
636 view_->SetText(string16(), true);
640 void StatusBubbleViews::SetURL(const GURL& url, const std::string& languages) {
642 languages_ = languages;
644 return; // We have no bounds, don't attempt to show the popup.
648 // If we want to clear a displayed URL but there is a status still to
649 // display, display that status instead.
650 if (url.is_empty() && !status_text_.empty()) {
651 url_text_ = string16();
652 if (IsFrameVisible())
653 view_->SetText(status_text_, true);
657 // Reset expansion state only when bubble is completely hidden.
658 if (view_->GetState() == StatusView::BUBBLE_HIDDEN) {
659 is_expanded_ = false;
660 SetBubbleWidth(GetStandardStatusBubbleWidth());
663 // Set Elided Text corresponding to the GURL object.
664 gfx::Rect popup_bounds = popup_->GetWindowBoundsInScreen();
665 int text_width = static_cast<int>(popup_bounds.width() -
666 (kShadowThickness * 2) - kTextPositionX - kTextHorizPadding - 1);
667 url_text_ = gfx::ElideUrl(url, view_->Label::font(), text_width, languages);
669 // An URL is always treated as a left-to-right string. On right-to-left UIs
670 // we need to explicitly mark the URL as LTR to make sure it is displayed
672 url_text_ = base::i18n::GetDisplayStringInLTRDirectionality(url_text_);
674 if (IsFrameVisible()) {
675 view_->SetText(url_text_, true);
679 // If bubble is already in expanded state, shift to adjust to new text
680 // size (shrinking or expanding). Otherwise delay.
681 if (is_expanded_ && !url.is_empty()) {
683 } else if (net::FormatUrl(url, languages).length() > url_text_.length()) {
684 base::MessageLoop::current()->PostDelayedTask(
686 base::Bind(&StatusBubbleViews::ExpandBubble,
687 expand_timer_factory_.GetWeakPtr()),
688 base::TimeDelta::FromMilliseconds(kExpandHoverDelay));
693 void StatusBubbleViews::Hide() {
694 status_text_ = string16();
695 url_text_ = string16();
700 void StatusBubbleViews::MouseMoved(const gfx::Point& location,
702 contains_mouse_ = !left_content;
707 last_mouse_moved_location_ = location;
712 if (view_->GetState() != StatusView::BUBBLE_HIDDEN &&
713 view_->GetState() != StatusView::BUBBLE_HIDING_FADE &&
714 view_->GetState() != StatusView::BUBBLE_HIDING_TIMER) {
715 AvoidMouse(location);
720 void StatusBubbleViews::UpdateDownloadShelfVisibility(bool visible) {
721 download_shelf_is_visible_ = visible;
724 void StatusBubbleViews::AvoidMouse(const gfx::Point& location) {
725 // Get the position of the frame.
727 views::View::ConvertPointToScreen(base_view_, &top_left);
729 int window_width = base_view_->GetLocalBounds().width();
731 // Get the cursor position relative to the popup.
732 gfx::Point relative_location = location;
733 if (base::i18n::IsRTL()) {
734 int top_right_x = top_left.x() + window_width;
735 relative_location.set_x(top_right_x - relative_location.x());
737 relative_location.set_x(
738 relative_location.x() - (top_left.x() + position_.x()));
740 relative_location.set_y(
741 relative_location.y() - (top_left.y() + position_.y()));
743 // If the mouse is in a position where we think it would move the
744 // status bubble, figure out where and how the bubble should be moved.
745 if (relative_location.y() > -kMousePadding &&
746 relative_location.x() < size_.width() + kMousePadding) {
747 int offset = kMousePadding + relative_location.y();
749 // Make the movement non-linear.
750 offset = offset * offset / kMousePadding;
752 // When the mouse is entering from the right, we want the offset to be
753 // scaled by how horizontally far away the cursor is from the bubble.
754 if (relative_location.x() > size_.width()) {
755 offset = static_cast<int>(static_cast<float>(offset) * (
756 static_cast<float>(kMousePadding -
757 (relative_location.x() - size_.width())) /
758 static_cast<float>(kMousePadding)));
761 // Cap the offset and change the visual presentation of the bubble
762 // depending on where it ends up (so that rounded corners square off
763 // and mate to the edges of the tab content).
764 if (offset >= size_.height() - kShadowThickness * 2) {
765 offset = size_.height() - kShadowThickness * 2;
766 view_->SetStyle(StatusView::STYLE_BOTTOM);
767 } else if (offset > kBubbleCornerRadius / 2 - kShadowThickness) {
768 view_->SetStyle(StatusView::STYLE_FLOATING);
770 view_->SetStyle(StatusView::STYLE_STANDARD);
773 // Check if the bubble sticks out from the monitor or will obscure
775 gfx::NativeView window = base_view_->GetWidget()->GetNativeView();
776 gfx::Rect monitor_rect = gfx::Screen::GetScreenFor(window)->
777 GetDisplayNearestWindow(window).work_area();
778 const int bubble_bottom_y = top_left.y() + position_.y() + size_.height();
780 if (bubble_bottom_y + offset > monitor_rect.height() ||
781 (download_shelf_is_visible_ &&
782 (view_->GetStyle() == StatusView::STYLE_FLOATING ||
783 view_->GetStyle() == StatusView::STYLE_BOTTOM))) {
784 // The offset is still too large. Move the bubble to the right and reset
785 // Y offset_ to zero.
786 view_->SetStyle(StatusView::STYLE_STANDARD_RIGHT);
789 // Subtract border width + bubble width.
790 int right_position_x = window_width - (position_.x() + size_.width());
791 popup_->SetBounds(gfx::Rect(top_left.x() + right_position_x,
792 top_left.y() + position_.y(),
793 size_.width(), size_.height()));
796 popup_->SetBounds(gfx::Rect(top_left.x() + position_.x(),
797 top_left.y() + position_.y() + offset_,
798 size_.width(), size_.height()));
800 } else if (offset_ != 0 ||
801 view_->GetStyle() == StatusView::STYLE_STANDARD_RIGHT) {
803 view_->SetStyle(StatusView::STYLE_STANDARD);
804 popup_->SetBounds(gfx::Rect(top_left.x() + position_.x(),
805 top_left.y() + position_.y(),
806 size_.width(), size_.height()));
810 bool StatusBubbleViews::IsFrameVisible() {
811 views::Widget* frame = base_view_->GetWidget();
812 if (!frame->IsVisible())
815 views::Widget* window = frame->GetTopLevelWidget();
816 return !window || !window->IsMinimized();
819 void StatusBubbleViews::ExpandBubble() {
820 // Elide URL to maximum possible size, then check actual length (it may
821 // still be too long to fit) before expanding bubble.
822 gfx::Rect popup_bounds = popup_->GetWindowBoundsInScreen();
823 int max_status_bubble_width = GetMaxStatusBubbleWidth();
824 url_text_ = gfx::ElideUrl(url_, view_->Label::font(),
825 max_status_bubble_width, languages_);
826 int expanded_bubble_width =std::max(GetStandardStatusBubbleWidth(),
827 std::min(view_->Label::font().GetStringWidth(url_text_) +
828 (kShadowThickness * 2) + kTextPositionX +
829 kTextHorizPadding + 1,
830 max_status_bubble_width));
832 expand_view_->StartExpansion(url_text_, popup_bounds.width(),
833 expanded_bubble_width);
836 int StatusBubbleViews::GetStandardStatusBubbleWidth() {
837 return base_view_->bounds().width() / 3;
840 int StatusBubbleViews::GetMaxStatusBubbleWidth() {
841 const ui::NativeTheme* theme = base_view_->GetNativeTheme();
842 return static_cast<int>(std::max(0, base_view_->bounds().width() -
843 (kShadowThickness * 2) - kTextPositionX - kTextHorizPadding - 1 -
844 views::NativeScrollBar::GetVerticalScrollBarWidth(theme)));
847 void StatusBubbleViews::SetBubbleWidth(int width) {
848 size_.set_width(width);
849 SetBounds(original_position_.x(), original_position_.y(),
850 size_.width(), size_.height());
853 void StatusBubbleViews::CancelExpandTimer() {
854 if (expand_timer_factory_.HasWeakPtrs())
855 expand_timer_factory_.InvalidateWeakPtrs();