Home | History | Annotate | Download | only in views
      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.
      4 
      5 #include "chrome/browser/ui/views/status_bubble_views.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "base/bind.h"
     10 #include "base/i18n/rtl.h"
     11 #include "base/message_loop/message_loop.h"
     12 #include "base/strings/string_util.h"
     13 #include "base/strings/utf_string_conversions.h"
     14 #include "chrome/browser/themes/theme_properties.h"
     15 #include "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/base/theme_provider.h"
     26 #include "ui/gfx/canvas.h"
     27 #include "ui/gfx/point.h"
     28 #include "ui/gfx/screen.h"
     29 #include "ui/gfx/skia_util.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"
     35 #include "url/gurl.h"
     36 
     37 #if defined(USE_ASH)
     38 #include "ash/wm/property_util.h"
     39 #endif
     40 
     41 // The alpha and color of the bubble's shadow.
     42 static const SkColor kShadowColor = SkColorSetARGB(30, 0, 0, 0);
     43 
     44 // The roundedness of the edges of our bubble.
     45 static const int kBubbleCornerRadius = 4;
     46 
     47 // How close the mouse can get to the infobubble before it starts sliding
     48 // off-screen.
     49 static const int kMousePadding = 20;
     50 
     51 // The horizontal offset of the text within the status bubble, not including the
     52 // outer shadow ring.
     53 static const int kTextPositionX = 3;
     54 
     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;
     58 
     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;
     63 
     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;
     68 
     69 // How long each expansion step should take.
     70 static const int kMinExpansionStepDurationMS = 20;
     71 static const int kMaxExpansionStepDurationMS = 150;
     72 
     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 ui::LinearAnimation,
     78                                       public ui::AnimationDelegate {
     79  public:
     80   StatusView(StatusBubble* status_bubble,
     81              views::Widget* popup,
     82              ui::ThemeProvider* theme_provider)
     83       : ui::LinearAnimation(kFramerate, this),
     84         stage_(BUBBLE_HIDDEN),
     85         style_(STYLE_STANDARD),
     86         timer_factory_(this),
     87         status_bubble_(status_bubble),
     88         popup_(popup),
     89         opacity_start_(0),
     90         opacity_end_(0),
     91         theme_service_(theme_provider) {
     92     ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
     93     SetFont(rb.GetFont(ui::ResourceBundle::BaseFont));
     94   }
     95 
     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.
     99     set_delegate(NULL);
    100     Stop();
    101     CancelTimer();
    102   }
    103 
    104   // The bubble can be in one of many stages:
    105   enum BubbleStage {
    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.
    112   };
    113 
    114   enum BubbleStyle {
    115     STYLE_BOTTOM,
    116     STYLE_FLOATING,
    117     STYLE_STANDARD,
    118     STYLE_STANDARD_RIGHT
    119   };
    120 
    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);
    125 
    126   BubbleStage GetState() const { return stage_; }
    127 
    128   void SetStyle(BubbleStyle style);
    129 
    130   BubbleStyle GetStyle() const { return style_; }
    131 
    132   // Show the bubble instantly.
    133   void Show();
    134 
    135   // Hide the bubble instantly.
    136   void Hide();
    137 
    138   // Resets any timers we have. Typically called when the user moves a
    139   // mouse.
    140   void ResetTimer();
    141 
    142  private:
    143   class InitialTimer;
    144 
    145   // Manage the timers that control the delay before a fade begins or ends.
    146   void StartTimer(base::TimeDelta time);
    147   void OnTimer();
    148   void CancelTimer();
    149   void RestartTimer(base::TimeDelta delay);
    150 
    151   // Manage the fades and starting and stopping the animations correctly.
    152   void StartFade(double start, double end, int duration);
    153   void StartHiding();
    154   void StartShowing();
    155 
    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;
    161 
    162   virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
    163 
    164   BubbleStage stage_;
    165   BubbleStyle style_;
    166 
    167   base::WeakPtrFactory<StatusBubbleViews::StatusView> timer_factory_;
    168 
    169   // Manager, owns us.
    170   StatusBubble* status_bubble_;
    171 
    172   // Handle to the widget that contains us.
    173   views::Widget* popup_;
    174 
    175   // The currently-displayed text.
    176   string16 text_;
    177 
    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_;
    182   double opacity_end_;
    183 
    184   // Holds the theme provider of the frame that created us.
    185   ui::ThemeProvider* theme_service_;
    186 };
    187 
    188 void StatusBubbleViews::StatusView::SetText(const string16& text,
    189                                             bool should_animate_open) {
    190   if (text.empty()) {
    191     // The string was empty.
    192     StartHiding();
    193   } else {
    194     // We want to show the string.
    195     text_ = text;
    196     if (should_animate_open)
    197       StartShowing();
    198   }
    199 
    200   SchedulePaint();
    201 }
    202 
    203 void StatusBubbleViews::StatusView::Show() {
    204   Stop();
    205   CancelTimer();
    206   SetOpacity(1.0);
    207   popup_->Show();
    208   stage_ = BUBBLE_SHOWN;
    209 }
    210 
    211 void StatusBubbleViews::StatusView::Hide() {
    212   Stop();
    213   CancelTimer();
    214   SetOpacity(0.0);
    215   text_.clear();
    216   popup_->Hide();
    217   stage_ = BUBBLE_HIDDEN;
    218 }
    219 
    220 void StatusBubbleViews::StatusView::StartTimer(base::TimeDelta time) {
    221   if (timer_factory_.HasWeakPtrs())
    222     timer_factory_.InvalidateWeakPtrs();
    223 
    224   base::MessageLoop::current()->PostDelayedTask(
    225       FROM_HERE,
    226       base::Bind(&StatusBubbleViews::StatusView::OnTimer,
    227                  timer_factory_.GetWeakPtr()),
    228       time);
    229 }
    230 
    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);
    238   }
    239 }
    240 
    241 void StatusBubbleViews::StatusView::CancelTimer() {
    242   if (timer_factory_.HasWeakPtrs())
    243     timer_factory_.InvalidateWeakPtrs();
    244 }
    245 
    246 void StatusBubbleViews::StatusView::RestartTimer(base::TimeDelta delay) {
    247   CancelTimer();
    248   StartTimer(delay);
    249 }
    250 
    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));
    256   }
    257 }
    258 
    259 void StatusBubbleViews::StatusView::StartFade(double start,
    260                                               double end,
    261                                               int duration) {
    262   opacity_start_ = start;
    263   opacity_end_ = end;
    264 
    265   // This will also reset the currently-occurring animation.
    266   SetDuration(duration);
    267   Start();
    268 }
    269 
    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;
    276     popup_->Hide();
    277     CancelTimer();
    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();
    282 
    283     // Start a fade in the opposite direction.
    284     StartFade(current_opacity, 0.0,
    285               static_cast<int>(kHideFadeDurationMS * current_opacity));
    286   }
    287 }
    288 
    289 void StatusBubbleViews::StatusView::StartShowing() {
    290   if (stage_ == BUBBLE_HIDDEN) {
    291     popup_->Show();
    292     stage_ = BUBBLE_SHOWING_TIMER;
    293     StartTimer(base::TimeDelta::FromMilliseconds(kShowDelay));
    294   } else if (stage_ == BUBBLE_HIDING_TIMER) {
    295     stage_ = BUBBLE_SHOWN;
    296     CancelTimer();
    297   } else if (stage_ == BUBBLE_HIDING_FADE) {
    298     // We're partway through a fade.
    299     stage_ = BUBBLE_SHOWING_FADE;
    300 
    301     // Figure out where we are in the current fade.
    302     double current_opacity = GetCurrentOpacity();
    303 
    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.
    310     ResetTimer();
    311   }
    312 }
    313 
    314 // Animation functions.
    315 double StatusBubbleViews::StatusView::GetCurrentOpacity() {
    316   return opacity_start_ + (opacity_end_ - opacity_start_) *
    317          ui::LinearAnimation::GetCurrentValue();
    318 }
    319 
    320 void StatusBubbleViews::StatusView::SetOpacity(double opacity) {
    321   popup_->SetOpacity(static_cast<unsigned char>(opacity * 255));
    322 }
    323 
    324 void StatusBubbleViews::StatusView::AnimateToState(double state) {
    325   SetOpacity(GetCurrentOpacity());
    326 }
    327 
    328 void StatusBubbleViews::StatusView::AnimationEnded(
    329     const ui::Animation* animation) {
    330   SetOpacity(opacity_end_);
    331 
    332   if (stage_ == BUBBLE_HIDING_FADE) {
    333     stage_ = BUBBLE_HIDDEN;
    334     popup_->Hide();
    335   } else if (stage_ == BUBBLE_SHOWING_FADE) {
    336     stage_ = BUBBLE_SHOWN;
    337   }
    338 }
    339 
    340 void StatusBubbleViews::StatusView::SetStyle(BubbleStyle style) {
    341   if (style_ != style) {
    342     style_ = style;
    343     SchedulePaint();
    344   }
    345 }
    346 
    347 void StatusBubbleViews::StatusView::OnPaint(gfx::Canvas* canvas) {
    348   SkPaint paint;
    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);
    354 
    355   gfx::Rect popup_bounds = popup_->GetWindowBoundsInScreen();
    356 
    357   // Figure out how to round the bubble's four corners.
    358   SkScalar rad[8];
    359 
    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) {
    365     // Top Left corner.
    366     rad[0] = 0;
    367     rad[1] = 0;
    368 
    369     // Top Right corner.
    370     rad[2] = 0;
    371     rad[3] = 0;
    372   } else {
    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).
    375 
    376       // Top Left corner.
    377       rad[0] = SkIntToScalar(kBubbleCornerRadius);
    378       rad[1] = SkIntToScalar(kBubbleCornerRadius);
    379 
    380       // Top Right corner.
    381       rad[2] = 0;
    382       rad[3] = 0;
    383     } else {
    384       // Top Left corner.
    385       rad[0] = 0;
    386       rad[1] = 0;
    387 
    388       // Top Right corner.
    389       rad[2] = SkIntToScalar(kBubbleCornerRadius);
    390       rad[3] = SkIntToScalar(kBubbleCornerRadius);
    391     }
    392   }
    393 
    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.
    398     rad[4] = 0;
    399     rad[5] = 0;
    400 
    401     // Bottom Left Corner.
    402     rad[6] = 0;
    403     rad[7] = 0;
    404   } else {
    405     // Bottom Right Corner.
    406     rad[4] = SkIntToScalar(kBubbleCornerRadius);
    407     rad[5] = SkIntToScalar(kBubbleCornerRadius);
    408 
    409     // Bottom Left Corner.
    410     rad[6] = SkIntToScalar(kBubbleCornerRadius);
    411     rad[7] = SkIntToScalar(kBubbleCornerRadius);
    412   }
    413 
    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())));
    418   SkPath shadow_path;
    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);
    424 
    425   // Draw the bubble.
    426   rect.set(SkIntToScalar(kShadowThickness),
    427            SkIntToScalar(kShadowThickness),
    428            SkIntToScalar(width - kShadowThickness),
    429            SkIntToScalar(height - kShadowThickness));
    430   SkPath path;
    431   path.addRoundRect(rect, rad, SkPath::kCW_Direction);
    432   canvas->DrawPath(path, paint);
    433 
    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
    436   // locale is RTL.
    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,
    442                         kShadowThickness,
    443                         std::max(0, text_width),
    444                         std::max(0, text_height));
    445   body_bounds.set_x(GetMirroredXForRect(body_bounds));
    446   SkColor text_color =
    447       theme_service_->GetColor(ThemeProperties::COLOR_TAB_TEXT);
    448 
    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(),
    457                         text_color,
    458                         body_bounds.x(),
    459                         body_bounds.y(),
    460                         body_bounds.width(),
    461                         body_bounds.height());
    462 }
    463 
    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 ui::LinearAnimation,
    469                                               public ui::AnimationDelegate {
    470  public:
    471   StatusViewExpander(StatusBubbleViews* status_bubble,
    472                      StatusView* status_view)
    473       : ui::LinearAnimation(kFramerate, this),
    474         status_bubble_(status_bubble),
    475         status_view_(status_view),
    476         expansion_start_(0),
    477         expansion_end_(0) {
    478   }
    479 
    480   // Manage the expansion of the bubble.
    481   void StartExpansion(const string16& expanded_text,
    482                       int current_width,
    483                       int expansion_end);
    484 
    485   // Set width of fully expanded bubble.
    486   void SetExpandedWidth(int expanded_width);
    487 
    488  private:
    489   // Animation functions.
    490   int GetCurrentBubbleWidth();
    491   void SetBubbleWidth(int width);
    492   virtual void AnimateToState(double state) OVERRIDE;
    493   virtual void AnimationEnded(const ui::Animation* animation) OVERRIDE;
    494 
    495   // Manager that owns us.
    496   StatusBubbleViews* status_bubble_;
    497 
    498   // Change the bounds and text of this view.
    499   StatusView* status_view_;
    500 
    501   // Text elided (if needed) to fit maximum status bar width.
    502   string16 expanded_text_;
    503 
    504   // Widths at expansion start and end.
    505   int expansion_start_;
    506   int expansion_end_;
    507 };
    508 
    509 void StatusBubbleViews::StatusViewExpander::AnimateToState(double state) {
    510   SetBubbleWidth(GetCurrentBubbleWidth());
    511 }
    512 
    513 void StatusBubbleViews::StatusViewExpander::AnimationEnded(
    514     const ui::Animation* animation) {
    515   SetBubbleWidth(expansion_end_);
    516   status_view_->SetText(expanded_text_, false);
    517 }
    518 
    519 void StatusBubbleViews::StatusViewExpander::StartExpansion(
    520     const string16& expanded_text,
    521     int expansion_start,
    522     int expansion_end) {
    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));
    530   Start();
    531 }
    532 
    533 int StatusBubbleViews::StatusViewExpander::GetCurrentBubbleWidth() {
    534   return static_cast<int>(expansion_start_ +
    535       (expansion_end_ - expansion_start_) *
    536           ui::LinearAnimation::GetCurrentValue());
    537 }
    538 
    539 void StatusBubbleViews::StatusViewExpander::SetBubbleWidth(int width) {
    540   status_bubble_->SetBubbleWidth(width);
    541   status_view_->SchedulePaint();
    542 }
    543 
    544 // StatusBubble ---------------------------------------------------------------
    545 
    546 const int StatusBubbleViews::kShadowThickness = 1;
    547 
    548 StatusBubbleViews::StatusBubbleViews(views::View* base_view)
    549     : contains_mouse_(false),
    550       offset_(0),
    551       opacity_(0),
    552       base_view_(base_view),
    553       view_(NULL),
    554       download_shelf_is_visible_(false),
    555       is_expanded_(false),
    556       expand_timer_factory_(this) {
    557   expand_view_.reset();
    558 }
    559 
    560 StatusBubbleViews::~StatusBubbleViews() {
    561   CancelExpandTimer();
    562   if (popup_.get())
    563     popup_->CloseNow();
    564 }
    565 
    566 void StatusBubbleViews::Init() {
    567   if (!popup_.get()) {
    568     popup_.reset(new views::Widget);
    569     views::Widget* frame = base_view_->GetWidget();
    570     if (!view_)
    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_);
    585 #if defined(USE_ASH)
    586     ash::SetIgnoredByShelf(popup_->GetNativeWindow(), true);
    587 #endif
    588     Reposition();
    589   }
    590 }
    591 
    592 void StatusBubbleViews::Reposition() {
    593   if (popup_.get()) {
    594     gfx::Point top_left;
    595     views::View::ConvertPointToScreen(base_view_, &top_left);
    596 
    597     popup_->SetBounds(gfx::Rect(top_left.x() + position_.x(),
    598                                 top_left.y() + position_.y(),
    599                                 size_.width(), size_.height()));
    600   }
    601 }
    602 
    603 gfx::Size StatusBubbleViews::GetPreferredSize() {
    604   return gfx::Size(0, ui::ResourceBundle::GetSharedInstance().GetFont(
    605       ui::ResourceBundle::BaseFont).GetHeight() + kTotalVerticalPadding);
    606 }
    607 
    608 void StatusBubbleViews::SetBounds(int x, int y, int w, int h) {
    609   original_position_.SetPoint(x, y);
    610   position_.SetPoint(base_view_->GetMirroredXWithWidthInView(x, w), y);
    611   size_.SetSize(w, h);
    612   Reposition();
    613   if (popup_.get() && contains_mouse_)
    614     AvoidMouse(last_mouse_moved_location_);
    615 }
    616 
    617 void StatusBubbleViews::SetStatus(const string16& status_text) {
    618   if (size_.IsEmpty())
    619     return;  // We have no bounds, don't attempt to show the popup.
    620 
    621   if (status_text_ == status_text && !status_text.empty())
    622     return;
    623 
    624   if (!IsFrameVisible())
    625     return;  // Don't show anything if the parent isn't visible.
    626 
    627   Init();
    628   status_text_ = status_text;
    629   if (!status_text_.empty()) {
    630     view_->SetText(status_text, true);
    631     view_->Show();
    632   } else if (!url_text_.empty()) {
    633     view_->SetText(url_text_, true);
    634   } else {
    635     view_->SetText(string16(), true);
    636   }
    637 }
    638 
    639 void StatusBubbleViews::SetURL(const GURL& url, const std::string& languages) {
    640   url_ = url;
    641   languages_ = languages;
    642   if (size_.IsEmpty())
    643     return;  // We have no bounds, don't attempt to show the popup.
    644 
    645   Init();
    646 
    647   // If we want to clear a displayed URL but there is a status still to
    648   // display, display that status instead.
    649   if (url.is_empty() && !status_text_.empty()) {
    650     url_text_ = string16();
    651     if (IsFrameVisible())
    652       view_->SetText(status_text_, true);
    653     return;
    654   }
    655 
    656   // Reset expansion state only when bubble is completely hidden.
    657   if (view_->GetState() == StatusView::BUBBLE_HIDDEN) {
    658     is_expanded_ = false;
    659     SetBubbleWidth(GetStandardStatusBubbleWidth());
    660   }
    661 
    662   // Set Elided Text corresponding to the GURL object.
    663   gfx::Rect popup_bounds = popup_->GetWindowBoundsInScreen();
    664   int text_width = static_cast<int>(popup_bounds.width() -
    665       (kShadowThickness * 2) - kTextPositionX - kTextHorizPadding - 1);
    666   url_text_ = ui::ElideUrl(url, view_->Label::font(), text_width, languages);
    667 
    668   // An URL is always treated as a left-to-right string. On right-to-left UIs
    669   // we need to explicitly mark the URL as LTR to make sure it is displayed
    670   // correctly.
    671   url_text_ = base::i18n::GetDisplayStringInLTRDirectionality(url_text_);
    672 
    673   if (IsFrameVisible()) {
    674     view_->SetText(url_text_, true);
    675 
    676     CancelExpandTimer();
    677 
    678     // If bubble is already in expanded state, shift to adjust to new text
    679     // size (shrinking or expanding). Otherwise delay.
    680     if (is_expanded_ && !url.is_empty()) {
    681       ExpandBubble();
    682     } else if (net::FormatUrl(url, languages).length() > url_text_.length()) {
    683       base::MessageLoop::current()->PostDelayedTask(
    684           FROM_HERE,
    685           base::Bind(&StatusBubbleViews::ExpandBubble,
    686                      expand_timer_factory_.GetWeakPtr()),
    687           base::TimeDelta::FromMilliseconds(kExpandHoverDelay));
    688     }
    689   }
    690 }
    691 
    692 void StatusBubbleViews::Hide() {
    693   status_text_ = string16();
    694   url_text_ = string16();
    695   if (view_)
    696     view_->Hide();
    697 }
    698 
    699 void StatusBubbleViews::MouseMoved(const gfx::Point& location,
    700                                    bool left_content) {
    701   contains_mouse_ = !left_content;
    702   if (left_content) {
    703     Reposition();
    704     return;
    705   }
    706   last_mouse_moved_location_ = location;
    707 
    708   if (view_) {
    709     view_->ResetTimer();
    710 
    711     if (view_->GetState() != StatusView::BUBBLE_HIDDEN &&
    712         view_->GetState() != StatusView::BUBBLE_HIDING_FADE &&
    713         view_->GetState() != StatusView::BUBBLE_HIDING_TIMER) {
    714       AvoidMouse(location);
    715     }
    716   }
    717 }
    718 
    719 void StatusBubbleViews::UpdateDownloadShelfVisibility(bool visible) {
    720   download_shelf_is_visible_ = visible;
    721 }
    722 
    723 void StatusBubbleViews::AvoidMouse(const gfx::Point& location) {
    724   // Get the position of the frame.
    725   gfx::Point top_left;
    726   views::View::ConvertPointToScreen(base_view_, &top_left);
    727   // Border included.
    728   int window_width = base_view_->GetLocalBounds().width();
    729 
    730   // Get the cursor position relative to the popup.
    731   gfx::Point relative_location = location;
    732   if (base::i18n::IsRTL()) {
    733     int top_right_x = top_left.x() + window_width;
    734     relative_location.set_x(top_right_x - relative_location.x());
    735   } else {
    736     relative_location.set_x(
    737         relative_location.x() - (top_left.x() + position_.x()));
    738   }
    739   relative_location.set_y(
    740       relative_location.y() - (top_left.y() + position_.y()));
    741 
    742   // If the mouse is in a position where we think it would move the
    743   // status bubble, figure out where and how the bubble should be moved.
    744   if (relative_location.y() > -kMousePadding &&
    745       relative_location.x() < size_.width() + kMousePadding) {
    746     int offset = kMousePadding + relative_location.y();
    747 
    748     // Make the movement non-linear.
    749     offset = offset * offset / kMousePadding;
    750 
    751     // When the mouse is entering from the right, we want the offset to be
    752     // scaled by how horizontally far away the cursor is from the bubble.
    753     if (relative_location.x() > size_.width()) {
    754       offset = static_cast<int>(static_cast<float>(offset) * (
    755           static_cast<float>(kMousePadding -
    756               (relative_location.x() - size_.width())) /
    757           static_cast<float>(kMousePadding)));
    758     }
    759 
    760     // Cap the offset and change the visual presentation of the bubble
    761     // depending on where it ends up (so that rounded corners square off
    762     // and mate to the edges of the tab content).
    763     if (offset >= size_.height() - kShadowThickness * 2) {
    764       offset = size_.height() - kShadowThickness * 2;
    765       view_->SetStyle(StatusView::STYLE_BOTTOM);
    766     } else if (offset > kBubbleCornerRadius / 2 - kShadowThickness) {
    767       view_->SetStyle(StatusView::STYLE_FLOATING);
    768     } else {
    769       view_->SetStyle(StatusView::STYLE_STANDARD);
    770     }
    771 
    772     // Check if the bubble sticks out from the monitor or will obscure
    773     // download shelf.
    774     gfx::NativeView window = base_view_->GetWidget()->GetNativeView();
    775     gfx::Rect monitor_rect = gfx::Screen::GetScreenFor(window)->
    776         GetDisplayNearestWindow(window).work_area();
    777     const int bubble_bottom_y = top_left.y() + position_.y() + size_.height();
    778 
    779     if (bubble_bottom_y + offset > monitor_rect.height() ||
    780         (download_shelf_is_visible_ &&
    781          (view_->GetStyle() == StatusView::STYLE_FLOATING ||
    782           view_->GetStyle() == StatusView::STYLE_BOTTOM))) {
    783       // The offset is still too large. Move the bubble to the right and reset
    784       // Y offset_ to zero.
    785       view_->SetStyle(StatusView::STYLE_STANDARD_RIGHT);
    786       offset_ = 0;
    787 
    788       // Subtract border width + bubble width.
    789       int right_position_x = window_width - (position_.x() + size_.width());
    790       popup_->SetBounds(gfx::Rect(top_left.x() + right_position_x,
    791                                   top_left.y() + position_.y(),
    792                                   size_.width(), size_.height()));
    793     } else {
    794       offset_ = offset;
    795       popup_->SetBounds(gfx::Rect(top_left.x() + position_.x(),
    796                                   top_left.y() + position_.y() + offset_,
    797                                   size_.width(), size_.height()));
    798     }
    799   } else if (offset_ != 0 ||
    800       view_->GetStyle() == StatusView::STYLE_STANDARD_RIGHT) {
    801     offset_ = 0;
    802     view_->SetStyle(StatusView::STYLE_STANDARD);
    803     popup_->SetBounds(gfx::Rect(top_left.x() + position_.x(),
    804                                 top_left.y() + position_.y(),
    805                                 size_.width(), size_.height()));
    806   }
    807 }
    808 
    809 bool StatusBubbleViews::IsFrameVisible() {
    810   views::Widget* frame = base_view_->GetWidget();
    811   if (!frame->IsVisible())
    812     return false;
    813 
    814   views::Widget* window = frame->GetTopLevelWidget();
    815   return !window || !window->IsMinimized();
    816 }
    817 
    818 void StatusBubbleViews::ExpandBubble() {
    819   // Elide URL to maximum possible size, then check actual length (it may
    820   // still be too long to fit) before expanding bubble.
    821   gfx::Rect popup_bounds = popup_->GetWindowBoundsInScreen();
    822   int max_status_bubble_width = GetMaxStatusBubbleWidth();
    823   url_text_ = ui::ElideUrl(url_, view_->Label::font(),
    824       max_status_bubble_width, languages_);
    825   int expanded_bubble_width =std::max(GetStandardStatusBubbleWidth(),
    826       std::min(view_->Label::font().GetStringWidth(url_text_) +
    827                    (kShadowThickness * 2) + kTextPositionX +
    828                    kTextHorizPadding + 1,
    829                max_status_bubble_width));
    830   is_expanded_ = true;
    831   expand_view_->StartExpansion(url_text_, popup_bounds.width(),
    832                                expanded_bubble_width);
    833 }
    834 
    835 int StatusBubbleViews::GetStandardStatusBubbleWidth() {
    836   return base_view_->bounds().width() / 3;
    837 }
    838 
    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)));
    844 }
    845 
    846 void StatusBubbleViews::SetBubbleWidth(int width) {
    847   size_.set_width(width);
    848   SetBounds(original_position_.x(), original_position_.y(),
    849             size_.width(), size_.height());
    850 }
    851 
    852 void StatusBubbleViews::CancelExpandTimer() {
    853   if (expand_timer_factory_.HasWeakPtrs())
    854     expand_timer_factory_.InvalidateWeakPtrs();
    855 }
    856