summaryrefslogtreecommitdiffstats
path: root/chrome/browser/ui/views/status_bubble_views.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/ui/views/status_bubble_views.cc')
-rw-r--r--chrome/browser/ui/views/status_bubble_views.cc835
1 files changed, 835 insertions, 0 deletions
diff --git a/chrome/browser/ui/views/status_bubble_views.cc b/chrome/browser/ui/views/status_bubble_views.cc
new file mode 100644
index 0000000..234b6f0
--- /dev/null
+++ b/chrome/browser/ui/views/status_bubble_views.cc
@@ -0,0 +1,835 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/views/status_bubble_views.h"
+
+#include <algorithm>
+
+#include "app/linear_animation.h"
+#include "app/resource_bundle.h"
+#include "app/text_elider.h"
+#include "base/i18n/rtl.h"
+#include "base/message_loop.h"
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/themes/browser_theme_provider.h"
+#include "gfx/canvas_skia.h"
+#include "gfx/point.h"
+#include "googleurl/src/gurl.h"
+#include "grit/generated_resources.h"
+#include "grit/theme_resources.h"
+#include "net/base/net_util.h"
+#include "third_party/skia/include/core/SkPaint.h"
+#include "third_party/skia/include/core/SkPath.h"
+#include "third_party/skia/include/core/SkRect.h"
+#include "views/controls/label.h"
+#include "views/controls/scrollbar/native_scroll_bar.h"
+#include "views/screen.h"
+#include "views/widget/root_view.h"
+#include "views/widget/widget.h"
+#include "views/window/window.h"
+
+using views::Widget;
+
+// The alpha and color of the bubble's shadow.
+static const SkColor kShadowColor = SkColorSetARGB(30, 0, 0, 0);
+
+// The roundedness of the edges of our bubble.
+static const int kBubbleCornerRadius = 4;
+
+// How close the mouse can get to the infobubble before it starts sliding
+// off-screen.
+static const int kMousePadding = 20;
+
+// The horizontal offset of the text within the status bubble, not including the
+// outer shadow ring.
+static const int kTextPositionX = 3;
+
+// The minimum horizontal space between the (right) end of the text and the edge
+// of the status bubble, not including the outer shadow ring.
+static const int kTextHorizPadding = 1;
+
+// Delays before we start hiding or showing the bubble after we receive a
+// show or hide request.
+static const int kShowDelay = 80;
+static const int kHideDelay = 250;
+
+// How long each fade should last for.
+static const int kShowFadeDurationMS = 120;
+static const int kHideFadeDurationMS = 200;
+static const int kFramerate = 25;
+
+// How long each expansion step should take.
+static const int kMinExpansionStepDurationMS = 20;
+static const int kMaxExpansionStepDurationMS = 150;
+
+// View -----------------------------------------------------------------------
+// StatusView manages the display of the bubble, applying text changes and
+// fading in or out the bubble as required.
+class StatusBubbleViews::StatusView : public views::Label,
+ public LinearAnimation,
+ public AnimationDelegate {
+ public:
+ StatusView(StatusBubble* status_bubble, views::Widget* popup,
+ ThemeProvider* theme_provider)
+ : ALLOW_THIS_IN_INITIALIZER_LIST(LinearAnimation(kFramerate, this)),
+ stage_(BUBBLE_HIDDEN),
+ style_(STYLE_STANDARD),
+ ALLOW_THIS_IN_INITIALIZER_LIST(timer_factory_(this)),
+ status_bubble_(status_bubble),
+ popup_(popup),
+ opacity_start_(0),
+ opacity_end_(0),
+ theme_provider_(theme_provider) {
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ gfx::Font font(rb.GetFont(ResourceBundle::BaseFont));
+ SetFont(font);
+ }
+
+ virtual ~StatusView() {
+ Stop();
+ CancelTimer();
+ }
+
+ // The bubble can be in one of many stages:
+ enum BubbleStage {
+ BUBBLE_HIDDEN, // Entirely BUBBLE_HIDDEN.
+ BUBBLE_HIDING_FADE, // In a fade-out transition.
+ BUBBLE_HIDING_TIMER, // Waiting before a fade-out.
+ BUBBLE_SHOWING_TIMER, // Waiting before a fade-in.
+ BUBBLE_SHOWING_FADE, // In a fade-in transition.
+ BUBBLE_SHOWN // Fully visible.
+ };
+
+ enum BubbleStyle {
+ STYLE_BOTTOM,
+ STYLE_FLOATING,
+ STYLE_STANDARD,
+ STYLE_STANDARD_RIGHT
+ };
+
+ // Set the bubble text to a certain value, hides the bubble if text is
+ // an empty string. Trigger animation sequence to display if
+ // |should_animate_open|.
+ void SetText(const string16& text, bool should_animate_open);
+
+ BubbleStage GetState() const { return stage_; }
+
+ void SetStyle(BubbleStyle style);
+
+ BubbleStyle GetStyle() const { return style_; }
+
+ // Show the bubble instantly.
+ void Show();
+
+ // Hide the bubble instantly.
+ void Hide();
+
+ // Resets any timers we have. Typically called when the user moves a
+ // mouse.
+ void ResetTimer();
+
+ private:
+ class InitialTimer;
+
+ // Manage the timers that control the delay before a fade begins or ends.
+ void StartTimer(int time);
+ void OnTimer();
+ void CancelTimer();
+ void RestartTimer(int delay);
+
+ // Manage the fades and starting and stopping the animations correctly.
+ void StartFade(double start, double end, int duration);
+ void StartHiding();
+ void StartShowing();
+
+ // Animation functions.
+ double GetCurrentOpacity();
+ void SetOpacity(double opacity);
+ void AnimateToState(double state);
+ void AnimationEnded(const Animation* animation);
+
+ virtual void Paint(gfx::Canvas* canvas);
+
+ BubbleStage stage_;
+ BubbleStyle style_;
+
+ ScopedRunnableMethodFactory<StatusBubbleViews::StatusView> timer_factory_;
+
+ // Manager, owns us.
+ StatusBubble* status_bubble_;
+
+ // Handle to the widget that contains us.
+ views::Widget* popup_;
+
+ // The currently-displayed text.
+ string16 text_;
+
+ // Start and end opacities for the current transition - note that as a
+ // fade-in can easily turn into a fade out, opacity_start_ is sometimes
+ // a value between 0 and 1.
+ double opacity_start_;
+ double opacity_end_;
+
+ // Holds the theme provider of the frame that created us.
+ ThemeProvider* theme_provider_;
+};
+
+void StatusBubbleViews::StatusView::SetText(const string16& text,
+ bool should_animate_open) {
+ if (text.empty()) {
+ // The string was empty.
+ StartHiding();
+ } else {
+ // We want to show the string.
+ text_ = text;
+ if (should_animate_open)
+ StartShowing();
+ }
+
+ SchedulePaint();
+}
+
+void StatusBubbleViews::StatusView::Show() {
+ Stop();
+ CancelTimer();
+ SetOpacity(1.0);
+ popup_->Show();
+ stage_ = BUBBLE_SHOWN;
+ PaintNow();
+}
+
+void StatusBubbleViews::StatusView::Hide() {
+ Stop();
+ CancelTimer();
+ SetOpacity(0.0);
+ text_.clear();
+ popup_->Hide();
+ stage_ = BUBBLE_HIDDEN;
+}
+
+void StatusBubbleViews::StatusView::StartTimer(int time) {
+ if (!timer_factory_.empty())
+ timer_factory_.RevokeAll();
+
+ MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ timer_factory_.NewRunnableMethod(&StatusBubbleViews::StatusView::OnTimer),
+ time);
+}
+
+void StatusBubbleViews::StatusView::OnTimer() {
+ if (stage_ == BUBBLE_HIDING_TIMER) {
+ stage_ = BUBBLE_HIDING_FADE;
+ StartFade(1.0, 0.0, kHideFadeDurationMS);
+ } else if (stage_ == BUBBLE_SHOWING_TIMER) {
+ stage_ = BUBBLE_SHOWING_FADE;
+ StartFade(0.0, 1.0, kShowFadeDurationMS);
+ }
+}
+
+void StatusBubbleViews::StatusView::CancelTimer() {
+ if (!timer_factory_.empty())
+ timer_factory_.RevokeAll();
+}
+
+void StatusBubbleViews::StatusView::RestartTimer(int delay) {
+ CancelTimer();
+ StartTimer(delay);
+}
+
+void StatusBubbleViews::StatusView::ResetTimer() {
+ if (stage_ == BUBBLE_SHOWING_TIMER) {
+ // We hadn't yet begun showing anything when we received a new request
+ // for something to show, so we start from scratch.
+ RestartTimer(kShowDelay);
+ }
+}
+
+void StatusBubbleViews::StatusView::StartFade(double start,
+ double end,
+ int duration) {
+ opacity_start_ = start;
+ opacity_end_ = end;
+
+ // This will also reset the currently-occurring animation.
+ SetDuration(duration);
+ Start();
+}
+
+void StatusBubbleViews::StatusView::StartHiding() {
+ if (stage_ == BUBBLE_SHOWN) {
+ stage_ = BUBBLE_HIDING_TIMER;
+ StartTimer(kHideDelay);
+ } else if (stage_ == BUBBLE_SHOWING_TIMER) {
+ stage_ = BUBBLE_HIDDEN;
+ CancelTimer();
+ } else if (stage_ == BUBBLE_SHOWING_FADE) {
+ stage_ = BUBBLE_HIDING_FADE;
+ // Figure out where we are in the current fade.
+ double current_opacity = GetCurrentOpacity();
+
+ // Start a fade in the opposite direction.
+ StartFade(current_opacity, 0.0,
+ static_cast<int>(kHideFadeDurationMS * current_opacity));
+ }
+}
+
+void StatusBubbleViews::StatusView::StartShowing() {
+ if (stage_ == BUBBLE_HIDDEN) {
+ popup_->Show();
+ stage_ = BUBBLE_SHOWING_TIMER;
+ StartTimer(kShowDelay);
+ } else if (stage_ == BUBBLE_HIDING_TIMER) {
+ stage_ = BUBBLE_SHOWN;
+ CancelTimer();
+ } else if (stage_ == BUBBLE_HIDING_FADE) {
+ // We're partway through a fade.
+ stage_ = BUBBLE_SHOWING_FADE;
+
+ // Figure out where we are in the current fade.
+ double current_opacity = GetCurrentOpacity();
+
+ // Start a fade in the opposite direction.
+ StartFade(current_opacity, 1.0,
+ static_cast<int>(kShowFadeDurationMS * current_opacity));
+ } else if (stage_ == BUBBLE_SHOWING_TIMER) {
+ // We hadn't yet begun showing anything when we received a new request
+ // for something to show, so we start from scratch.
+ ResetTimer();
+ }
+}
+
+// Animation functions.
+double StatusBubbleViews::StatusView::GetCurrentOpacity() {
+ return opacity_start_ + (opacity_end_ - opacity_start_) *
+ LinearAnimation::GetCurrentValue();
+}
+
+void StatusBubbleViews::StatusView::SetOpacity(double opacity) {
+ popup_->SetOpacity(static_cast<unsigned char>(opacity * 255));
+ SchedulePaint();
+}
+
+void StatusBubbleViews::StatusView::AnimateToState(double state) {
+ SetOpacity(GetCurrentOpacity());
+}
+
+void StatusBubbleViews::StatusView::AnimationEnded(
+ const Animation* animation) {
+ SetOpacity(opacity_end_);
+
+ if (stage_ == BUBBLE_HIDING_FADE) {
+ stage_ = BUBBLE_HIDDEN;
+ popup_->Hide();
+ } else if (stage_ == BUBBLE_SHOWING_FADE) {
+ stage_ = BUBBLE_SHOWN;
+ }
+}
+
+void StatusBubbleViews::StatusView::SetStyle(BubbleStyle style) {
+ if (style_ != style) {
+ style_ = style;
+ SchedulePaint();
+ }
+}
+
+void StatusBubbleViews::StatusView::Paint(gfx::Canvas* canvas) {
+ SkPaint paint;
+ paint.setStyle(SkPaint::kFill_Style);
+ paint.setFlags(SkPaint::kAntiAlias_Flag);
+ SkColor toolbar_color =
+ theme_provider_->GetColor(BrowserThemeProvider::COLOR_TOOLBAR);
+ paint.setColor(toolbar_color);
+
+ gfx::Rect popup_bounds;
+ popup_->GetBounds(&popup_bounds, true);
+
+ // Figure out how to round the bubble's four corners.
+ SkScalar rad[8];
+
+ // Top Edges - if the bubble is in its bottom position (sticking downwards),
+ // then we square the top edges. Otherwise, we square the edges based on the
+ // position of the bubble within the window (the bubble is positioned in the
+ // southeast corner in RTL and in the southwest corner in LTR).
+ if (style_ == STYLE_BOTTOM) {
+ // Top Left corner.
+ rad[0] = 0;
+ rad[1] = 0;
+
+ // Top Right corner.
+ rad[2] = 0;
+ rad[3] = 0;
+ } else {
+ if (base::i18n::IsRTL() != (style_ == STYLE_STANDARD_RIGHT)) {
+ // The text is RtL or the bubble is on the right side (but not both).
+
+ // Top Left corner.
+ rad[0] = SkIntToScalar(kBubbleCornerRadius);
+ rad[1] = SkIntToScalar(kBubbleCornerRadius);
+
+ // Top Right corner.
+ rad[2] = 0;
+ rad[3] = 0;
+ } else {
+ // Top Left corner.
+ rad[0] = 0;
+ rad[1] = 0;
+
+ // Top Right corner.
+ rad[2] = SkIntToScalar(kBubbleCornerRadius);
+ rad[3] = SkIntToScalar(kBubbleCornerRadius);
+ }
+ }
+
+ // Bottom edges - square these off if the bubble is in its standard position
+ // (sticking upward).
+ if (style_ == STYLE_STANDARD || style_ == STYLE_STANDARD_RIGHT) {
+ // Bottom Right Corner.
+ rad[4] = 0;
+ rad[5] = 0;
+
+ // Bottom Left Corner.
+ rad[6] = 0;
+ rad[7] = 0;
+ } else {
+ // Bottom Right Corner.
+ rad[4] = SkIntToScalar(kBubbleCornerRadius);
+ rad[5] = SkIntToScalar(kBubbleCornerRadius);
+
+ // Bottom Left Corner.
+ rad[6] = SkIntToScalar(kBubbleCornerRadius);
+ rad[7] = SkIntToScalar(kBubbleCornerRadius);
+ }
+
+ // Draw the bubble's shadow.
+ int width = popup_bounds.width();
+ int height = popup_bounds.height();
+ SkRect rect;
+ rect.set(0, 0,
+ SkIntToScalar(width),
+ SkIntToScalar(height));
+ SkPath shadow_path;
+ shadow_path.addRoundRect(rect, rad, SkPath::kCW_Direction);
+ SkPaint shadow_paint;
+ shadow_paint.setFlags(SkPaint::kAntiAlias_Flag);
+ shadow_paint.setColor(kShadowColor);
+ canvas->AsCanvasSkia()->drawPath(shadow_path, shadow_paint);
+
+ // Draw the bubble.
+ rect.set(SkIntToScalar(kShadowThickness),
+ SkIntToScalar(kShadowThickness),
+ SkIntToScalar(width - kShadowThickness),
+ SkIntToScalar(height - kShadowThickness));
+ SkPath path;
+ path.addRoundRect(rect, rad, SkPath::kCW_Direction);
+ canvas->AsCanvasSkia()->drawPath(path, paint);
+
+ // Draw highlight text and then the text body. In order to make sure the text
+ // is aligned to the right on RTL UIs, we mirror the text bounds if the
+ // locale is RTL.
+ int text_width = std::min(
+ views::Label::font().GetStringWidth(UTF16ToWide(text_)),
+ width - (kShadowThickness * 2) - kTextPositionX - kTextHorizPadding);
+ int text_height = height - (kShadowThickness * 2);
+ gfx::Rect body_bounds(kShadowThickness + kTextPositionX,
+ kShadowThickness,
+ std::max(0, text_width),
+ std::max(0, text_height));
+ body_bounds.set_x(MirroredLeftPointForRect(body_bounds));
+ SkColor text_color =
+ theme_provider_->GetColor(BrowserThemeProvider::COLOR_TAB_TEXT);
+
+ // DrawStringInt doesn't handle alpha, so we'll do the blending ourselves.
+ text_color = SkColorSetARGB(
+ SkColorGetA(text_color),
+ (SkColorGetR(text_color) + SkColorGetR(toolbar_color)) / 2,
+ (SkColorGetG(text_color) + SkColorGetR(toolbar_color)) / 2,
+ (SkColorGetB(text_color) + SkColorGetR(toolbar_color)) / 2);
+ canvas->DrawStringInt(UTF16ToWide(text_),
+ views::Label::font(),
+ text_color,
+ body_bounds.x(),
+ body_bounds.y(),
+ body_bounds.width(),
+ body_bounds.height());
+}
+
+// StatusViewExpander ---------------------------------------------------------
+// Manages the expansion and contraction of the status bubble as it accommodates
+// URLs too long to fit in the standard bubble. Changes are passed through the
+// StatusView to paint.
+class StatusBubbleViews::StatusViewExpander : public LinearAnimation,
+ public AnimationDelegate {
+ public:
+ StatusViewExpander(StatusBubbleViews* status_bubble,
+ StatusView* status_view)
+ : ALLOW_THIS_IN_INITIALIZER_LIST(LinearAnimation(kFramerate, this)),
+ status_bubble_(status_bubble),
+ status_view_(status_view),
+ expansion_start_(0),
+ expansion_end_(0) {
+ }
+
+ // Manage the expansion of the bubble.
+ void StartExpansion(string16 expanded_text, int current_width,
+ int expansion_end);
+
+ // Set width of fully expanded bubble.
+ void SetExpandedWidth(int expanded_width);
+
+ private:
+ // Animation functions.
+ int GetCurrentBubbleWidth();
+ void SetBubbleWidth(int width);
+ void AnimateToState(double state);
+ void AnimationEnded(const Animation* animation);
+
+ // Manager that owns us.
+ StatusBubbleViews* status_bubble_;
+
+ // Change the bounds and text of this view.
+ StatusView* status_view_;
+
+ // Text elided (if needed) to fit maximum status bar width.
+ string16 expanded_text_;
+
+ // Widths at expansion start and end.
+ int expansion_start_;
+ int expansion_end_;
+};
+
+void StatusBubbleViews::StatusViewExpander::AnimateToState(double state) {
+ SetBubbleWidth(GetCurrentBubbleWidth());
+}
+
+void StatusBubbleViews::StatusViewExpander::AnimationEnded(
+ const Animation* animation) {
+ SetBubbleWidth(expansion_end_);
+ status_view_->SetText(expanded_text_, false);
+}
+
+void StatusBubbleViews::StatusViewExpander::StartExpansion(
+ string16 expanded_text, int expansion_start,
+ int expansion_end) {
+ expanded_text_ = expanded_text;
+ expansion_start_ = expansion_start;
+ expansion_end_ = expansion_end;
+ int min_duration = std::max(kMinExpansionStepDurationMS,
+ static_cast<int>(kMaxExpansionStepDurationMS *
+ (expansion_end - expansion_start) / 100.0));
+ SetDuration(std::min(kMaxExpansionStepDurationMS, min_duration));
+ Start();
+}
+
+int StatusBubbleViews::StatusViewExpander::GetCurrentBubbleWidth() {
+ return static_cast<int>(expansion_start_ +
+ (expansion_end_ - expansion_start_) * LinearAnimation::GetCurrentValue());
+}
+
+void StatusBubbleViews::StatusViewExpander::SetBubbleWidth(int width) {
+ status_bubble_->SetBubbleWidth(width);
+ status_view_->SchedulePaint();
+}
+
+// StatusBubble ---------------------------------------------------------------
+
+const int StatusBubbleViews::kShadowThickness = 1;
+
+StatusBubbleViews::StatusBubbleViews(views::View* base_view)
+ : offset_(0),
+ popup_(NULL),
+ opacity_(0),
+ base_view_(base_view),
+ view_(NULL),
+ download_shelf_is_visible_(false),
+ is_expanded_(false),
+ ALLOW_THIS_IN_INITIALIZER_LIST(expand_timer_factory_(this)) {
+ expand_view_.reset();
+}
+
+StatusBubbleViews::~StatusBubbleViews() {
+ CancelExpandTimer();
+ if (popup_.get())
+ popup_->CloseNow();
+}
+
+void StatusBubbleViews::Init() {
+ if (!popup_.get()) {
+ popup_.reset(Widget::CreatePopupWidget(Widget::Transparent,
+ Widget::NotAcceptEvents,
+ Widget::NotDeleteOnDestroy,
+ Widget::MirrorOriginInRTL));
+ views::Widget* frame = base_view_->GetWidget();
+ if (!view_)
+ view_ = new StatusView(this, popup_.get(), frame->GetThemeProvider());
+ if (!expand_view_.get())
+ expand_view_.reset(new StatusViewExpander(this, view_));
+ popup_->SetOpacity(0x00);
+ popup_->Init(frame->GetNativeView(), gfx::Rect());
+ popup_->SetContentsView(view_);
+ Reposition();
+ popup_->Show();
+ }
+}
+
+void StatusBubbleViews::Reposition() {
+ if (popup_.get()) {
+ gfx::Point top_left;
+ views::View::ConvertPointToScreen(base_view_, &top_left);
+
+ popup_->SetBounds(gfx::Rect(top_left.x() + position_.x(),
+ top_left.y() + position_.y(),
+ size_.width(), size_.height()));
+ }
+}
+
+gfx::Size StatusBubbleViews::GetPreferredSize() {
+ return gfx::Size(0, ResourceBundle::GetSharedInstance().GetFont(
+ ResourceBundle::BaseFont).GetHeight() + kTotalVerticalPadding);
+}
+
+void StatusBubbleViews::SetBounds(int x, int y, int w, int h) {
+ original_position_.SetPoint(x, y);
+ position_.SetPoint(base_view_->MirroredXWithWidthInsideView(x, w), y);
+ size_.SetSize(w, h);
+ Reposition();
+}
+
+void StatusBubbleViews::SetStatus(const string16& status_text) {
+ if (size_.IsEmpty())
+ return; // We have no bounds, don't attempt to show the popup.
+
+ if (status_text_ == status_text && !status_text.empty())
+ return;
+
+ if (!IsFrameVisible())
+ return; // Don't show anything if the parent isn't visible.
+
+ Init();
+ status_text_ = status_text;
+ if (!status_text_.empty()) {
+ view_->SetText(status_text, true);
+ view_->Show();
+ } else if (!url_text_.empty()) {
+ view_->SetText(url_text_, true);
+ } else {
+ view_->SetText(string16(), true);
+ }
+}
+
+void StatusBubbleViews::SetURL(const GURL& url, const string16& languages) {
+ languages_ = languages;
+ url_ = url;
+ if (size_.IsEmpty())
+ return; // We have no bounds, don't attempt to show the popup.
+
+ Init();
+
+ // If we want to clear a displayed URL but there is a status still to
+ // display, display that status instead.
+ if (url.is_empty() && !status_text_.empty()) {
+ url_text_ = string16();
+ if (IsFrameVisible())
+ view_->SetText(status_text_, true);
+ return;
+ }
+
+ // Reset expansion state only when bubble is completely hidden.
+ if (view_->GetState() == StatusView::BUBBLE_HIDDEN) {
+ is_expanded_ = false;
+ SetBubbleWidth(GetStandardStatusBubbleWidth());
+ }
+
+ // Set Elided Text corresponding to the GURL object.
+ gfx::Rect popup_bounds;
+ popup_->GetBounds(&popup_bounds, true);
+ int text_width = static_cast<int>(popup_bounds.width() -
+ (kShadowThickness * 2) - kTextPositionX - kTextHorizPadding - 1);
+ url_text_ = gfx::ElideUrl(url, view_->Label::font(),
+ text_width, UTF16ToWideHack(languages));
+
+ std::wstring original_url_text =
+ UTF16ToWideHack(net::FormatUrl(url, UTF16ToUTF8(languages)));
+
+ // An URL is always treated as a left-to-right string. On right-to-left UIs
+ // we need to explicitly mark the URL as LTR to make sure it is displayed
+ // correctly.
+ url_text_ = base::i18n::GetDisplayStringInLTRDirectionality(url_text_);
+
+ if (IsFrameVisible()) {
+ view_->SetText(url_text_, true);
+
+ CancelExpandTimer();
+
+ // If bubble is already in expanded state, shift to adjust to new text
+ // size (shrinking or expanding). Otherwise delay.
+ if (is_expanded_ && !url.is_empty())
+ ExpandBubble();
+ else if (original_url_text.length() > url_text_.length())
+ MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ expand_timer_factory_.NewRunnableMethod(
+ &StatusBubbleViews::ExpandBubble), kExpandHoverDelay);
+ }
+}
+
+void StatusBubbleViews::Hide() {
+ status_text_ = string16();
+ url_text_ = string16();
+ if (view_)
+ view_->Hide();
+}
+
+void StatusBubbleViews::MouseMoved(const gfx::Point& location,
+ bool left_content) {
+ if (left_content)
+ return;
+
+ if (view_) {
+ view_->ResetTimer();
+
+ if (view_->GetState() != StatusView::BUBBLE_HIDDEN &&
+ view_->GetState() != StatusView::BUBBLE_HIDING_FADE &&
+ view_->GetState() != StatusView::BUBBLE_HIDING_TIMER) {
+ AvoidMouse(location);
+ }
+ }
+}
+
+void StatusBubbleViews::UpdateDownloadShelfVisibility(bool visible) {
+ download_shelf_is_visible_ = visible;
+}
+
+void StatusBubbleViews::AvoidMouse(const gfx::Point& location) {
+ // Get the position of the frame.
+ gfx::Point top_left;
+ views::View::ConvertPointToScreen(base_view_, &top_left);
+ // Border included.
+ int window_width = base_view_->GetLocalBounds(true).width();
+
+ // Get the cursor position relative to the popup.
+ gfx::Point relative_location = location;
+ if (base::i18n::IsRTL()) {
+ int top_right_x = top_left.x() + window_width;
+ relative_location.set_x(top_right_x - relative_location.x());
+ } else {
+ relative_location.set_x(
+ relative_location.x() - (top_left.x() + position_.x()));
+ }
+ relative_location.set_y(
+ relative_location.y() - (top_left.y() + position_.y()));
+
+ // If the mouse is in a position where we think it would move the
+ // status bubble, figure out where and how the bubble should be moved.
+ if (relative_location.y() > -kMousePadding &&
+ relative_location.x() < size_.width() + kMousePadding) {
+ int offset = kMousePadding + relative_location.y();
+
+ // Make the movement non-linear.
+ offset = offset * offset / kMousePadding;
+
+ // When the mouse is entering from the right, we want the offset to be
+ // scaled by how horizontally far away the cursor is from the bubble.
+ if (relative_location.x() > size_.width()) {
+ offset = static_cast<int>(static_cast<float>(offset) * (
+ static_cast<float>(kMousePadding -
+ (relative_location.x() - size_.width())) /
+ static_cast<float>(kMousePadding)));
+ }
+
+ // Cap the offset and change the visual presentation of the bubble
+ // depending on where it ends up (so that rounded corners square off
+ // and mate to the edges of the tab content).
+ if (offset >= size_.height() - kShadowThickness * 2) {
+ offset = size_.height() - kShadowThickness * 2;
+ view_->SetStyle(StatusView::STYLE_BOTTOM);
+ } else if (offset > kBubbleCornerRadius / 2 - kShadowThickness) {
+ view_->SetStyle(StatusView::STYLE_FLOATING);
+ } else {
+ view_->SetStyle(StatusView::STYLE_STANDARD);
+ }
+
+ // Check if the bubble sticks out from the monitor or will obscure
+ // download shelf.
+ gfx::NativeView widget = base_view_->GetWidget()->GetNativeView();
+ gfx::Rect monitor_rect =
+ views::Screen::GetMonitorWorkAreaNearestWindow(widget);
+ const int bubble_bottom_y = top_left.y() + position_.y() + size_.height();
+
+ if (bubble_bottom_y + offset > monitor_rect.height() ||
+ (download_shelf_is_visible_ &&
+ (view_->GetStyle() == StatusView::STYLE_FLOATING ||
+ view_->GetStyle() == StatusView::STYLE_BOTTOM))) {
+ // The offset is still too large. Move the bubble to the right and reset
+ // Y offset_ to zero.
+ view_->SetStyle(StatusView::STYLE_STANDARD_RIGHT);
+ offset_ = 0;
+
+ // Subtract border width + bubble width.
+ int right_position_x = window_width - (position_.x() + size_.width());
+ popup_->SetBounds(gfx::Rect(top_left.x() + right_position_x,
+ top_left.y() + position_.y(),
+ size_.width(), size_.height()));
+ } else {
+ offset_ = offset;
+ popup_->SetBounds(gfx::Rect(top_left.x() + position_.x(),
+ top_left.y() + position_.y() + offset_,
+ size_.width(), size_.height()));
+ }
+ } else if (offset_ != 0 ||
+ view_->GetStyle() == StatusView::STYLE_STANDARD_RIGHT) {
+ offset_ = 0;
+ view_->SetStyle(StatusView::STYLE_STANDARD);
+ popup_->SetBounds(gfx::Rect(top_left.x() + position_.x(),
+ top_left.y() + position_.y(),
+ size_.width(), size_.height()));
+ }
+}
+
+bool StatusBubbleViews::IsFrameVisible() {
+ views::Widget* frame = base_view_->GetWidget();
+ if (!frame->IsVisible())
+ return false;
+
+ views::Window* window = frame->GetWindow();
+ return !window || !window->IsMinimized();
+}
+
+void StatusBubbleViews::ExpandBubble() {
+ // Elide URL to maximum possible size, then check actual length (it may
+ // still be too long to fit) before expanding bubble.
+ gfx::Rect popup_bounds;
+ popup_->GetBounds(&popup_bounds, true);
+ int max_status_bubble_width = GetMaxStatusBubbleWidth();
+ url_text_ = gfx::ElideUrl(url_, view_->Label::font(),
+ max_status_bubble_width, UTF16ToWideHack(languages_));
+ int expanded_bubble_width =std::max(GetStandardStatusBubbleWidth(),
+ std::min(view_->Label::font().GetStringWidth(UTF16ToWide(url_text_)) +
+ (kShadowThickness * 2) + kTextPositionX +
+ kTextHorizPadding + 1,
+ max_status_bubble_width));
+ is_expanded_ = true;
+ expand_view_->StartExpansion(url_text_, popup_bounds.width(),
+ expanded_bubble_width);
+}
+
+int StatusBubbleViews::GetStandardStatusBubbleWidth() {
+ return base_view_->bounds().width() / 3;
+}
+
+int StatusBubbleViews::GetMaxStatusBubbleWidth() {
+ return static_cast<int>(std::max(0, base_view_->bounds().width() -
+ (kShadowThickness * 2) - kTextPositionX - kTextHorizPadding - 1 -
+ views::NativeScrollBar::GetVerticalScrollBarWidth()));
+}
+
+void StatusBubbleViews::SetBubbleWidth(int width) {
+ size_.set_width(width);
+ SetBounds(original_position_.x(), original_position_.y(),
+ size_.width(), size_.height());
+}
+
+void StatusBubbleViews::CancelExpandTimer() {
+ if (!expand_timer_factory_.empty())
+ expand_timer_factory_.RevokeAll();
+}