Home | History | Annotate | Download | only in views
      1 // Copyright (c) 2011 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.
      4 
      5 #include "chrome/browser/ui/views/status_bubble_views.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "base/i18n/rtl.h"
     10 #include "base/message_loop.h"
     11 #include "base/string_util.h"
     12 #include "base/utf_string_conversions.h"
     13 #include "chrome/browser/themes/theme_service.h"
     14 #include "googleurl/src/gurl.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/animation/animation_delegate.h"
     22 #include "ui/base/animation/linear_animation.h"
     23 #include "ui/base/resource/resource_bundle.h"
     24 #include "ui/base/text/text_elider.h"
     25 #include "ui/gfx/canvas_skia.h"
     26 #include "ui/gfx/point.h"
     27 #include "views/controls/label.h"
     28 #include "views/controls/scrollbar/native_scroll_bar.h"
     29 #include "views/screen.h"
     30 #include "views/widget/root_view.h"
     31 #include "views/widget/widget.h"
     32 #include "views/window/window.h"
     33 
     34 using views::Widget;
     35 
     36 // The alpha and color of the bubble's shadow.
     37 static const SkColor kShadowColor = SkColorSetARGB(30, 0, 0, 0);
     38 
     39 // The roundedness of the edges of our bubble.
     40 static const int kBubbleCornerRadius = 4;
     41 
     42 // How close the mouse can get to the infobubble before it starts sliding
     43 // off-screen.
     44 static const int kMousePadding = 20;
     45 
     46 // The horizontal offset of the text within the status bubble, not including the
     47 // outer shadow ring.
     48 static const int kTextPositionX = 3;
     49 
     50 // The minimum horizontal space between the (right) end of the text and the edge
     51 // of the status bubble, not including the outer shadow ring.
     52 static const int kTextHorizPadding = 1;
     53 
     54 // Delays before we start hiding or showing the bubble after we receive a
     55 // show or hide request.
     56 static const int kShowDelay = 80;
     57 static const int kHideDelay = 250;
     58 
     59 // How long each fade should last for.
     60 static const int kShowFadeDurationMS = 120;
     61 static const int kHideFadeDurationMS = 200;
     62 static const int kFramerate = 25;
     63 
     64 // How long each expansion step should take.
     65 static const int kMinExpansionStepDurationMS = 20;
     66 static const int kMaxExpansionStepDurationMS = 150;
     67 
     68 // View -----------------------------------------------------------------------
     69 // StatusView manages the display of the bubble, applying text changes and
     70 // fading in or out the bubble as required.
     71 class StatusBubbleViews::StatusView : public views::Label,
     72                                       public ui::LinearAnimation,
     73                                       public ui::AnimationDelegate {
     74  public:
     75   StatusView(StatusBubble* status_bubble, views::Widget* popup,
     76              ui::ThemeProvider* theme_provider)
     77       : ALLOW_THIS_IN_INITIALIZER_LIST(ui::LinearAnimation(kFramerate, this)),
     78         stage_(BUBBLE_HIDDEN),
     79         style_(STYLE_STANDARD),
     80         ALLOW_THIS_IN_INITIALIZER_LIST(timer_factory_(this)),
     81         status_bubble_(status_bubble),
     82         popup_(popup),
     83         opacity_start_(0),
     84         opacity_end_(0),
     85         theme_service_(theme_provider) {
     86     ResourceBundle& rb = ResourceBundle::GetSharedInstance();
     87     gfx::Font font(rb.GetFont(ResourceBundle::BaseFont));
     88     SetFont(font);
     89   }
     90 
     91   virtual ~StatusView() {
     92     Stop();
     93     CancelTimer();
     94   }
     95 
     96   // The bubble can be in one of many stages:
     97   enum BubbleStage {
     98     BUBBLE_HIDDEN,         // Entirely BUBBLE_HIDDEN.
     99     BUBBLE_HIDING_FADE,    // In a fade-out transition.
    100     BUBBLE_HIDING_TIMER,   // Waiting before a fade-out.
    101     BUBBLE_SHOWING_TIMER,  // Waiting before a fade-in.
    102     BUBBLE_SHOWING_FADE,   // In a fade-in transition.
    103     BUBBLE_SHOWN           // Fully visible.
    104   };
    105 
    106   enum BubbleStyle {
    107     STYLE_BOTTOM,
    108     STYLE_FLOATING,
    109     STYLE_STANDARD,
    110     STYLE_STANDARD_RIGHT
    111   };
    112 
    113   // Set the bubble text to a certain value, hides the bubble if text is
    114   // an empty string.  Trigger animation sequence to display if
    115   // |should_animate_open|.
    116   void SetText(const string16& text, bool should_animate_open);
    117 
    118   BubbleStage GetState() const { return stage_; }
    119 
    120   void SetStyle(BubbleStyle style);
    121 
    122   BubbleStyle GetStyle() const { return style_; }
    123 
    124   // Show the bubble instantly.
    125   void Show();
    126 
    127   // Hide the bubble instantly.
    128   void Hide();
    129 
    130   // Resets any timers we have. Typically called when the user moves a
    131   // mouse.
    132   void ResetTimer();
    133 
    134  private:
    135   class InitialTimer;
    136 
    137   // Manage the timers that control the delay before a fade begins or ends.
    138   void StartTimer(int time);
    139   void OnTimer();
    140   void CancelTimer();
    141   void RestartTimer(int delay);
    142 
    143   // Manage the fades and starting and stopping the animations correctly.
    144   void StartFade(double start, double end, int duration);
    145   void StartHiding();
    146   void StartShowing();
    147 
    148   // Animation functions.
    149   double GetCurrentOpacity();
    150   void SetOpacity(double opacity);
    151   void AnimateToState(double state);
    152   void AnimationEnded(const Animation* animation);
    153 
    154   virtual void OnPaint(gfx::Canvas* canvas);
    155 
    156   BubbleStage stage_;
    157   BubbleStyle style_;
    158 
    159   ScopedRunnableMethodFactory<StatusBubbleViews::StatusView> timer_factory_;
    160 
    161   // Manager, owns us.
    162   StatusBubble* status_bubble_;
    163 
    164   // Handle to the widget that contains us.
    165   views::Widget* popup_;
    166 
    167   // The currently-displayed text.
    168   string16 text_;
    169 
    170   // Start and end opacities for the current transition - note that as a
    171   // fade-in can easily turn into a fade out, opacity_start_ is sometimes
    172   // a value between 0 and 1.
    173   double opacity_start_;
    174   double opacity_end_;
    175 
    176   // Holds the theme provider of the frame that created us.
    177   ui::ThemeProvider* theme_service_;
    178 };
    179 
    180 void StatusBubbleViews::StatusView::SetText(const string16& text,
    181                                             bool should_animate_open) {
    182   if (text.empty()) {
    183     // The string was empty.
    184     StartHiding();
    185   } else {
    186     // We want to show the string.
    187     text_ = text;
    188     if (should_animate_open)
    189       StartShowing();
    190   }
    191 
    192   SchedulePaint();
    193 }
    194 
    195 void StatusBubbleViews::StatusView::Show() {
    196   Stop();
    197   CancelTimer();
    198   SetOpacity(1.0);
    199   popup_->Show();
    200   stage_ = BUBBLE_SHOWN;
    201   PaintNow();
    202 }
    203 
    204 void StatusBubbleViews::StatusView::Hide() {
    205   Stop();
    206   CancelTimer();
    207   SetOpacity(0.0);
    208   text_.clear();
    209   popup_->Hide();
    210   stage_ = BUBBLE_HIDDEN;
    211 }
    212 
    213 void StatusBubbleViews::StatusView::StartTimer(int time) {
    214   if (!timer_factory_.empty())
    215     timer_factory_.RevokeAll();
    216 
    217   MessageLoop::current()->PostDelayedTask(FROM_HERE,
    218       timer_factory_.NewRunnableMethod(&StatusBubbleViews::StatusView::OnTimer),
    219       time);
    220 }
    221 
    222 void StatusBubbleViews::StatusView::OnTimer() {
    223   if (stage_ == BUBBLE_HIDING_TIMER) {
    224     stage_ = BUBBLE_HIDING_FADE;
    225     StartFade(1.0, 0.0, kHideFadeDurationMS);
    226   } else if (stage_ == BUBBLE_SHOWING_TIMER) {
    227     stage_ = BUBBLE_SHOWING_FADE;
    228     StartFade(0.0, 1.0, kShowFadeDurationMS);
    229   }
    230 }
    231 
    232 void StatusBubbleViews::StatusView::CancelTimer() {
    233   if (!timer_factory_.empty())
    234     timer_factory_.RevokeAll();
    235 }
    236 
    237 void StatusBubbleViews::StatusView::RestartTimer(int delay) {
    238   CancelTimer();
    239   StartTimer(delay);
    240 }
    241 
    242 void StatusBubbleViews::StatusView::ResetTimer() {
    243   if (stage_ == BUBBLE_SHOWING_TIMER) {
    244     // We hadn't yet begun showing anything when we received a new request
    245     // for something to show, so we start from scratch.
    246     RestartTimer(kShowDelay);
    247   }
    248 }
    249 
    250 void StatusBubbleViews::StatusView::StartFade(double start,
    251                                               double end,
    252                                               int duration) {
    253   opacity_start_ = start;
    254   opacity_end_ = end;
    255 
    256   // This will also reset the currently-occurring animation.
    257   SetDuration(duration);
    258   Start();
    259 }
    260 
    261 void StatusBubbleViews::StatusView::StartHiding() {
    262   if (stage_ == BUBBLE_SHOWN) {
    263     stage_ = BUBBLE_HIDING_TIMER;
    264     StartTimer(kHideDelay);
    265   } else if (stage_ == BUBBLE_SHOWING_TIMER) {
    266     stage_ = BUBBLE_HIDDEN;
    267     CancelTimer();
    268   } else if (stage_ == BUBBLE_SHOWING_FADE) {
    269     stage_ = BUBBLE_HIDING_FADE;
    270     // Figure out where we are in the current fade.
    271     double current_opacity = GetCurrentOpacity();
    272 
    273     // Start a fade in the opposite direction.
    274     StartFade(current_opacity, 0.0,
    275               static_cast<int>(kHideFadeDurationMS * current_opacity));
    276   }
    277 }
    278 
    279 void StatusBubbleViews::StatusView::StartShowing() {
    280   if (stage_ == BUBBLE_HIDDEN) {
    281     popup_->Show();
    282     stage_ = BUBBLE_SHOWING_TIMER;
    283     StartTimer(kShowDelay);
    284   } else if (stage_ == BUBBLE_HIDING_TIMER) {
    285     stage_ = BUBBLE_SHOWN;
    286     CancelTimer();
    287   } else if (stage_ == BUBBLE_HIDING_FADE) {
    288     // We're partway through a fade.
    289     stage_ = BUBBLE_SHOWING_FADE;
    290 
    291     // Figure out where we are in the current fade.
    292     double current_opacity = GetCurrentOpacity();
    293 
    294     // Start a fade in the opposite direction.
    295     StartFade(current_opacity, 1.0,
    296               static_cast<int>(kShowFadeDurationMS * current_opacity));
    297   } else if (stage_ == BUBBLE_SHOWING_TIMER) {
    298     // We hadn't yet begun showing anything when we received a new request
    299     // for something to show, so we start from scratch.
    300     ResetTimer();
    301   }
    302 }
    303 
    304 // Animation functions.
    305 double StatusBubbleViews::StatusView::GetCurrentOpacity() {
    306   return opacity_start_ + (opacity_end_ - opacity_start_) *
    307          ui::LinearAnimation::GetCurrentValue();
    308 }
    309 
    310 void StatusBubbleViews::StatusView::SetOpacity(double opacity) {
    311   popup_->SetOpacity(static_cast<unsigned char>(opacity * 255));
    312   SchedulePaint();
    313 }
    314 
    315 void StatusBubbleViews::StatusView::AnimateToState(double state) {
    316   SetOpacity(GetCurrentOpacity());
    317 }
    318 
    319 void StatusBubbleViews::StatusView::AnimationEnded(
    320     const ui::Animation* animation) {
    321   SetOpacity(opacity_end_);
    322 
    323   if (stage_ == BUBBLE_HIDING_FADE) {
    324     stage_ = BUBBLE_HIDDEN;
    325     popup_->Hide();
    326   } else if (stage_ == BUBBLE_SHOWING_FADE) {
    327     stage_ = BUBBLE_SHOWN;
    328   }
    329 }
    330 
    331 void StatusBubbleViews::StatusView::SetStyle(BubbleStyle style) {
    332   if (style_ != style) {
    333     style_ = style;
    334     SchedulePaint();
    335   }
    336 }
    337 
    338 void StatusBubbleViews::StatusView::OnPaint(gfx::Canvas* canvas) {
    339   SkPaint paint;
    340   paint.setStyle(SkPaint::kFill_Style);
    341   paint.setFlags(SkPaint::kAntiAlias_Flag);
    342   SkColor toolbar_color =
    343       theme_service_->GetColor(ThemeService::COLOR_TOOLBAR);
    344   paint.setColor(toolbar_color);
    345 
    346   gfx::Rect popup_bounds = popup_->GetWindowScreenBounds();
    347 
    348   // Figure out how to round the bubble's four corners.
    349   SkScalar rad[8];
    350 
    351   // Top Edges - if the bubble is in its bottom position (sticking downwards),
    352   // then we square the top edges. Otherwise, we square the edges based on the
    353   // position of the bubble within the window (the bubble is positioned in the
    354   // southeast corner in RTL and in the southwest corner in LTR).
    355   if (style_ == STYLE_BOTTOM) {
    356     // Top Left corner.
    357     rad[0] = 0;
    358     rad[1] = 0;
    359 
    360     // Top Right corner.
    361     rad[2] = 0;
    362     rad[3] = 0;
    363   } else {
    364     if (base::i18n::IsRTL() != (style_ == STYLE_STANDARD_RIGHT)) {
    365       // The text is RtL or the bubble is on the right side (but not both).
    366 
    367       // Top Left corner.
    368       rad[0] = SkIntToScalar(kBubbleCornerRadius);
    369       rad[1] = SkIntToScalar(kBubbleCornerRadius);
    370 
    371       // Top Right corner.
    372       rad[2] = 0;
    373       rad[3] = 0;
    374     } else {
    375       // Top Left corner.
    376       rad[0] = 0;
    377       rad[1] = 0;
    378 
    379       // Top Right corner.
    380       rad[2] = SkIntToScalar(kBubbleCornerRadius);
    381       rad[3] = SkIntToScalar(kBubbleCornerRadius);
    382     }
    383   }
    384 
    385   // Bottom edges - square these off if the bubble is in its standard position
    386   // (sticking upward).
    387   if (style_ == STYLE_STANDARD || style_ == STYLE_STANDARD_RIGHT) {
    388     // Bottom Right Corner.
    389     rad[4] = 0;
    390     rad[5] = 0;
    391 
    392     // Bottom Left Corner.
    393     rad[6] = 0;
    394     rad[7] = 0;
    395   } else {
    396     // Bottom Right Corner.
    397     rad[4] = SkIntToScalar(kBubbleCornerRadius);
    398     rad[5] = SkIntToScalar(kBubbleCornerRadius);
    399 
    400     // Bottom Left Corner.
    401     rad[6] = SkIntToScalar(kBubbleCornerRadius);
    402     rad[7] = SkIntToScalar(kBubbleCornerRadius);
    403   }
    404 
    405   // Draw the bubble's shadow.
    406   int width = popup_bounds.width();
    407   int height = popup_bounds.height();
    408   SkRect rect;
    409   rect.set(0, 0,
    410            SkIntToScalar(width),
    411            SkIntToScalar(height));
    412   SkPath shadow_path;
    413   shadow_path.addRoundRect(rect, rad, SkPath::kCW_Direction);
    414   SkPaint shadow_paint;
    415   shadow_paint.setFlags(SkPaint::kAntiAlias_Flag);
    416   shadow_paint.setColor(kShadowColor);
    417   canvas->AsCanvasSkia()->drawPath(shadow_path, shadow_paint);
    418 
    419   // Draw the bubble.
    420   rect.set(SkIntToScalar(kShadowThickness),
    421            SkIntToScalar(kShadowThickness),
    422            SkIntToScalar(width - kShadowThickness),
    423            SkIntToScalar(height - kShadowThickness));
    424   SkPath path;
    425   path.addRoundRect(rect, rad, SkPath::kCW_Direction);
    426   canvas->AsCanvasSkia()->drawPath(path, paint);
    427 
    428   // Draw highlight text and then the text body. In order to make sure the text
    429   // is aligned to the right on RTL UIs, we mirror the text bounds if the
    430   // locale is RTL.
    431   int text_width = std::min(
    432       views::Label::font().GetStringWidth(text_),
    433       width - (kShadowThickness * 2) - kTextPositionX - kTextHorizPadding);
    434   int text_height = height - (kShadowThickness * 2);
    435   gfx::Rect body_bounds(kShadowThickness + kTextPositionX,
    436                         kShadowThickness,
    437                         std::max(0, text_width),
    438                         std::max(0, text_height));
    439   body_bounds.set_x(GetMirroredXForRect(body_bounds));
    440   SkColor text_color =
    441       theme_service_->GetColor(ThemeService::COLOR_TAB_TEXT);
    442 
    443   // DrawStringInt doesn't handle alpha, so we'll do the blending ourselves.
    444   text_color = SkColorSetARGB(
    445       SkColorGetA(text_color),
    446       (SkColorGetR(text_color) + SkColorGetR(toolbar_color)) / 2,
    447       (SkColorGetG(text_color) + SkColorGetR(toolbar_color)) / 2,
    448       (SkColorGetB(text_color) + SkColorGetR(toolbar_color)) / 2);
    449   canvas->DrawStringInt(text_,
    450                         views::Label::font(),
    451                         text_color,
    452                         body_bounds.x(),
    453                         body_bounds.y(),
    454                         body_bounds.width(),
    455                         body_bounds.height());
    456 }
    457 
    458 // StatusViewExpander ---------------------------------------------------------
    459 // Manages the expansion and contraction of the status bubble as it accommodates
    460 // URLs too long to fit in the standard bubble. Changes are passed through the
    461 // StatusView to paint.
    462 class StatusBubbleViews::StatusViewExpander : public ui::LinearAnimation,
    463                                               public ui::AnimationDelegate {
    464  public:
    465   StatusViewExpander(StatusBubbleViews* status_bubble,
    466                      StatusView* status_view)
    467       : ALLOW_THIS_IN_INITIALIZER_LIST(ui::LinearAnimation(kFramerate, this)),
    468         status_bubble_(status_bubble),
    469         status_view_(status_view),
    470         expansion_start_(0),
    471         expansion_end_(0) {
    472   }
    473 
    474   // Manage the expansion of the bubble.
    475   void StartExpansion(const string16& expanded_text,
    476                       int current_width,
    477                       int expansion_end);
    478 
    479   // Set width of fully expanded bubble.
    480   void SetExpandedWidth(int expanded_width);
    481 
    482  private:
    483   // Animation functions.
    484   int GetCurrentBubbleWidth();
    485   void SetBubbleWidth(int width);
    486   void AnimateToState(double state);
    487   void AnimationEnded(const ui::Animation* animation);
    488 
    489   // Manager that owns us.
    490   StatusBubbleViews* status_bubble_;
    491 
    492   // Change the bounds and text of this view.
    493   StatusView* status_view_;
    494 
    495   // Text elided (if needed) to fit maximum status bar width.
    496   string16 expanded_text_;
    497 
    498   // Widths at expansion start and end.
    499   int expansion_start_;
    500   int expansion_end_;
    501 };
    502 
    503 void StatusBubbleViews::StatusViewExpander::AnimateToState(double state) {
    504   SetBubbleWidth(GetCurrentBubbleWidth());
    505 }
    506 
    507 void StatusBubbleViews::StatusViewExpander::AnimationEnded(
    508     const ui::Animation* animation) {
    509   SetBubbleWidth(expansion_end_);
    510   status_view_->SetText(expanded_text_, false);
    511 }
    512 
    513 void StatusBubbleViews::StatusViewExpander::StartExpansion(
    514     const string16& expanded_text,
    515     int expansion_start,
    516     int expansion_end) {
    517   expanded_text_ = expanded_text;
    518   expansion_start_ = expansion_start;
    519   expansion_end_ = expansion_end;
    520   int min_duration = std::max(kMinExpansionStepDurationMS,
    521       static_cast<int>(kMaxExpansionStepDurationMS *
    522           (expansion_end - expansion_start) / 100.0));
    523   SetDuration(std::min(kMaxExpansionStepDurationMS, min_duration));
    524   Start();
    525 }
    526 
    527 int StatusBubbleViews::StatusViewExpander::GetCurrentBubbleWidth() {
    528   return static_cast<int>(expansion_start_ +
    529       (expansion_end_ - expansion_start_) *
    530           ui::LinearAnimation::GetCurrentValue());
    531 }
    532 
    533 void StatusBubbleViews::StatusViewExpander::SetBubbleWidth(int width) {
    534   status_bubble_->SetBubbleWidth(width);
    535   status_view_->SchedulePaint();
    536 }
    537 
    538 // StatusBubble ---------------------------------------------------------------
    539 
    540 const int StatusBubbleViews::kShadowThickness = 1;
    541 
    542 StatusBubbleViews::StatusBubbleViews(views::View* base_view)
    543     : offset_(0),
    544       popup_(NULL),
    545       opacity_(0),
    546       base_view_(base_view),
    547       view_(NULL),
    548       download_shelf_is_visible_(false),
    549       is_expanded_(false),
    550       ALLOW_THIS_IN_INITIALIZER_LIST(expand_timer_factory_(this)) {
    551   expand_view_.reset();
    552 }
    553 
    554 StatusBubbleViews::~StatusBubbleViews() {
    555   CancelExpandTimer();
    556   if (popup_.get())
    557     popup_->CloseNow();
    558 }
    559 
    560 void StatusBubbleViews::Init() {
    561   if (!popup_.get()) {
    562     Widget::CreateParams params(Widget::CreateParams::TYPE_POPUP);
    563     params.transparent = true;
    564     params.accept_events = false;
    565     params.delete_on_destroy = false;
    566     popup_.reset(Widget::CreateWidget(params));
    567     views::Widget* frame = base_view_->GetWidget();
    568     if (!view_)
    569       view_ = new StatusView(this, popup_.get(), frame->GetThemeProvider());
    570     if (!expand_view_.get())
    571       expand_view_.reset(new StatusViewExpander(this, view_));
    572     popup_->SetOpacity(0x00);
    573     popup_->Init(frame->GetNativeView(), gfx::Rect());
    574     popup_->SetContentsView(view_);
    575     Reposition();
    576     popup_->Show();
    577   }
    578 }
    579 
    580 void StatusBubbleViews::Reposition() {
    581   if (popup_.get()) {
    582     gfx::Point top_left;
    583     views::View::ConvertPointToScreen(base_view_, &top_left);
    584 
    585     popup_->SetBounds(gfx::Rect(top_left.x() + position_.x(),
    586                                 top_left.y() + position_.y(),
    587                                 size_.width(), size_.height()));
    588   }
    589 }
    590 
    591 gfx::Size StatusBubbleViews::GetPreferredSize() {
    592   return gfx::Size(0, ResourceBundle::GetSharedInstance().GetFont(
    593       ResourceBundle::BaseFont).GetHeight() + kTotalVerticalPadding);
    594 }
    595 
    596 void StatusBubbleViews::SetBounds(int x, int y, int w, int h) {
    597   original_position_.SetPoint(x, y);
    598   position_.SetPoint(base_view_->GetMirroredXWithWidthInView(x, w), y);
    599   size_.SetSize(w, h);
    600   Reposition();
    601 }
    602 
    603 void StatusBubbleViews::SetStatus(const string16& status_text) {
    604   if (size_.IsEmpty())
    605     return;  // We have no bounds, don't attempt to show the popup.
    606 
    607   if (status_text_ == status_text && !status_text.empty())
    608     return;
    609 
    610   if (!IsFrameVisible())
    611     return;  // Don't show anything if the parent isn't visible.
    612 
    613   Init();
    614   status_text_ = status_text;
    615   if (!status_text_.empty()) {
    616     view_->SetText(status_text, true);
    617     view_->Show();
    618   } else if (!url_text_.empty()) {
    619     view_->SetText(url_text_, true);
    620   } else {
    621     view_->SetText(string16(), true);
    622   }
    623 }
    624 
    625 void StatusBubbleViews::SetURL(const GURL& url, const string16& languages) {
    626   languages_ = languages;
    627   url_ = url;
    628   if (size_.IsEmpty())
    629     return;  // We have no bounds, don't attempt to show the popup.
    630 
    631   Init();
    632 
    633   // If we want to clear a displayed URL but there is a status still to
    634   // display, display that status instead.
    635   if (url.is_empty() && !status_text_.empty()) {
    636     url_text_ = string16();
    637     if (IsFrameVisible())
    638       view_->SetText(status_text_, true);
    639     return;
    640   }
    641 
    642   // Reset expansion state only when bubble is completely hidden.
    643   if (view_->GetState() == StatusView::BUBBLE_HIDDEN) {
    644     is_expanded_ = false;
    645     SetBubbleWidth(GetStandardStatusBubbleWidth());
    646   }
    647 
    648   // Set Elided Text corresponding to the GURL object.
    649   gfx::Rect popup_bounds = popup_->GetWindowScreenBounds();
    650   int text_width = static_cast<int>(popup_bounds.width() -
    651       (kShadowThickness * 2) - kTextPositionX - kTextHorizPadding - 1);
    652   url_text_ = ui::ElideUrl(url, view_->Label::font(),
    653       text_width, UTF16ToUTF8(languages));
    654 
    655   std::wstring original_url_text =
    656       UTF16ToWideHack(net::FormatUrl(url, UTF16ToUTF8(languages)));
    657 
    658   // An URL is always treated as a left-to-right string. On right-to-left UIs
    659   // we need to explicitly mark the URL as LTR to make sure it is displayed
    660   // correctly.
    661   url_text_ = base::i18n::GetDisplayStringInLTRDirectionality(url_text_);
    662 
    663   if (IsFrameVisible()) {
    664     view_->SetText(url_text_, true);
    665 
    666     CancelExpandTimer();
    667 
    668     // If bubble is already in expanded state, shift to adjust to new text
    669     // size (shrinking or expanding). Otherwise delay.
    670     if (is_expanded_ && !url.is_empty())
    671       ExpandBubble();
    672     else if (original_url_text.length() > url_text_.length())
    673       MessageLoop::current()->PostDelayedTask(FROM_HERE,
    674           expand_timer_factory_.NewRunnableMethod(
    675           &StatusBubbleViews::ExpandBubble), kExpandHoverDelay);
    676   }
    677 }
    678 
    679 void StatusBubbleViews::Hide() {
    680   status_text_ = string16();
    681   url_text_ = string16();
    682   if (view_)
    683     view_->Hide();
    684 }
    685 
    686 void StatusBubbleViews::MouseMoved(const gfx::Point& location,
    687                                    bool left_content) {
    688   if (left_content)
    689     return;
    690 
    691   if (view_) {
    692     view_->ResetTimer();
    693 
    694     if (view_->GetState() != StatusView::BUBBLE_HIDDEN &&
    695         view_->GetState() != StatusView::BUBBLE_HIDING_FADE &&
    696         view_->GetState() != StatusView::BUBBLE_HIDING_TIMER) {
    697       AvoidMouse(location);
    698     }
    699   }
    700 }
    701 
    702 void StatusBubbleViews::UpdateDownloadShelfVisibility(bool visible) {
    703   download_shelf_is_visible_ = visible;
    704 }
    705 
    706 void StatusBubbleViews::AvoidMouse(const gfx::Point& location) {
    707   // Get the position of the frame.
    708   gfx::Point top_left;
    709   views::View::ConvertPointToScreen(base_view_, &top_left);
    710   // Border included.
    711   int window_width = base_view_->GetLocalBounds().width();
    712 
    713   // Get the cursor position relative to the popup.
    714   gfx::Point relative_location = location;
    715   if (base::i18n::IsRTL()) {
    716     int top_right_x = top_left.x() + window_width;
    717     relative_location.set_x(top_right_x - relative_location.x());
    718   } else {
    719     relative_location.set_x(
    720         relative_location.x() - (top_left.x() + position_.x()));
    721   }
    722   relative_location.set_y(
    723       relative_location.y() - (top_left.y() + position_.y()));
    724 
    725   // If the mouse is in a position where we think it would move the
    726   // status bubble, figure out where and how the bubble should be moved.
    727   if (relative_location.y() > -kMousePadding &&
    728       relative_location.x() < size_.width() + kMousePadding) {
    729     int offset = kMousePadding + relative_location.y();
    730 
    731     // Make the movement non-linear.
    732     offset = offset * offset / kMousePadding;
    733 
    734     // When the mouse is entering from the right, we want the offset to be
    735     // scaled by how horizontally far away the cursor is from the bubble.
    736     if (relative_location.x() > size_.width()) {
    737       offset = static_cast<int>(static_cast<float>(offset) * (
    738           static_cast<float>(kMousePadding -
    739               (relative_location.x() - size_.width())) /
    740           static_cast<float>(kMousePadding)));
    741     }
    742 
    743     // Cap the offset and change the visual presentation of the bubble
    744     // depending on where it ends up (so that rounded corners square off
    745     // and mate to the edges of the tab content).
    746     if (offset >= size_.height() - kShadowThickness * 2) {
    747       offset = size_.height() - kShadowThickness * 2;
    748       view_->SetStyle(StatusView::STYLE_BOTTOM);
    749     } else if (offset > kBubbleCornerRadius / 2 - kShadowThickness) {
    750       view_->SetStyle(StatusView::STYLE_FLOATING);
    751     } else {
    752       view_->SetStyle(StatusView::STYLE_STANDARD);
    753     }
    754 
    755     // Check if the bubble sticks out from the monitor or will obscure
    756     // download shelf.
    757     gfx::NativeView widget = base_view_->GetWidget()->GetNativeView();
    758     gfx::Rect monitor_rect =
    759         views::Screen::GetMonitorWorkAreaNearestWindow(widget);
    760     const int bubble_bottom_y = top_left.y() + position_.y() + size_.height();
    761 
    762     if (bubble_bottom_y + offset > monitor_rect.height() ||
    763         (download_shelf_is_visible_ &&
    764          (view_->GetStyle() == StatusView::STYLE_FLOATING ||
    765           view_->GetStyle() == StatusView::STYLE_BOTTOM))) {
    766       // The offset is still too large. Move the bubble to the right and reset
    767       // Y offset_ to zero.
    768       view_->SetStyle(StatusView::STYLE_STANDARD_RIGHT);
    769       offset_ = 0;
    770 
    771       // Subtract border width + bubble width.
    772       int right_position_x = window_width - (position_.x() + size_.width());
    773       popup_->SetBounds(gfx::Rect(top_left.x() + right_position_x,
    774                                   top_left.y() + position_.y(),
    775                                   size_.width(), size_.height()));
    776     } else {
    777       offset_ = offset;
    778       popup_->SetBounds(gfx::Rect(top_left.x() + position_.x(),
    779                                   top_left.y() + position_.y() + offset_,
    780                                   size_.width(), size_.height()));
    781     }
    782   } else if (offset_ != 0 ||
    783       view_->GetStyle() == StatusView::STYLE_STANDARD_RIGHT) {
    784     offset_ = 0;
    785     view_->SetStyle(StatusView::STYLE_STANDARD);
    786     popup_->SetBounds(gfx::Rect(top_left.x() + position_.x(),
    787                                 top_left.y() + position_.y(),
    788                                 size_.width(), size_.height()));
    789   }
    790 }
    791 
    792 bool StatusBubbleViews::IsFrameVisible() {
    793   views::Widget* frame = base_view_->GetWidget();
    794   if (!frame->IsVisible())
    795     return false;
    796 
    797   views::Window* window = frame->GetWindow();
    798   return !window || !window->IsMinimized();
    799 }
    800 
    801 void StatusBubbleViews::ExpandBubble() {
    802   // Elide URL to maximum possible size, then check actual length (it may
    803   // still be too long to fit) before expanding bubble.
    804   gfx::Rect popup_bounds = popup_->GetWindowScreenBounds();
    805   int max_status_bubble_width = GetMaxStatusBubbleWidth();
    806   url_text_ = ui::ElideUrl(url_, view_->Label::font(),
    807       max_status_bubble_width, UTF16ToUTF8(languages_));
    808   int expanded_bubble_width =std::max(GetStandardStatusBubbleWidth(),
    809       std::min(view_->Label::font().GetStringWidth(url_text_) +
    810                    (kShadowThickness * 2) + kTextPositionX +
    811                    kTextHorizPadding + 1,
    812                max_status_bubble_width));
    813   is_expanded_ = true;
    814   expand_view_->StartExpansion(url_text_, popup_bounds.width(),
    815                                expanded_bubble_width);
    816 }
    817 
    818 int StatusBubbleViews::GetStandardStatusBubbleWidth() {
    819   return base_view_->bounds().width() / 3;
    820 }
    821 
    822 int StatusBubbleViews::GetMaxStatusBubbleWidth() {
    823   return static_cast<int>(std::max(0, base_view_->bounds().width() -
    824       (kShadowThickness * 2) - kTextPositionX - kTextHorizPadding - 1 -
    825       views::NativeScrollBar::GetVerticalScrollBarWidth()));
    826 }
    827 
    828 void StatusBubbleViews::SetBubbleWidth(int width) {
    829   size_.set_width(width);
    830   SetBounds(original_position_.x(), original_position_.y(),
    831             size_.width(), size_.height());
    832 }
    833 
    834 void StatusBubbleViews::CancelExpandTimer() {
    835   if (!expand_timer_factory_.empty())
    836     expand_timer_factory_.RevokeAll();
    837 }
    838