- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / views / status_bubble_views.cc
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/resource/resource_bundle.h"
22 #include "ui/base/theme_provider.h"
23 #include "ui/gfx/animation/animation_delegate.h"
24 #include "ui/gfx/animation/linear_animation.h"
25 #include "ui/gfx/canvas.h"
26 #include "ui/gfx/point.h"
27 #include "ui/gfx/screen.h"
28 #include "ui/gfx/skia_util.h"
29 #include "ui/gfx/text_elider.h"
30 #include "ui/native_theme/native_theme.h"
31 #include "ui/views/controls/label.h"
32 #include "ui/views/controls/scrollbar/native_scroll_bar.h"
33 #include "ui/views/widget/root_view.h"
34 #include "ui/views/widget/widget.h"
35 #include "url/gurl.h"
36
37 #if defined(USE_ASH)
38 #include "ash/wm/window_state.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 gfx::LinearAnimation,
78                                       public gfx::AnimationDelegate {
79  public:
80   StatusView(StatusBubble* status_bubble,
81              views::Widget* popup,
82              ui::ThemeProvider* theme_provider)
83       : gfx::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          gfx::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 gfx::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 gfx::LinearAnimation,
469                                               public gfx::AnimationDelegate {
470  public:
471   StatusViewExpander(StatusBubbleViews* status_bubble,
472                      StatusView* status_view)
473       : gfx::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 gfx::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 gfx::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           gfx::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::wm::GetWindowState(popup_->GetNativeWindow())->
587         set_ignored_by_shelf(true);
588 #endif
589     Reposition();
590   }
591 }
592
593 void StatusBubbleViews::Reposition() {
594   if (popup_.get()) {
595     gfx::Point top_left;
596     views::View::ConvertPointToScreen(base_view_, &top_left);
597
598     popup_->SetBounds(gfx::Rect(top_left.x() + position_.x(),
599                                 top_left.y() + position_.y(),
600                                 size_.width(), size_.height()));
601   }
602 }
603
604 gfx::Size StatusBubbleViews::GetPreferredSize() {
605   return gfx::Size(0, ui::ResourceBundle::GetSharedInstance().GetFont(
606       ui::ResourceBundle::BaseFont).GetHeight() + kTotalVerticalPadding);
607 }
608
609 void StatusBubbleViews::SetBounds(int x, int y, int w, int h) {
610   original_position_.SetPoint(x, y);
611   position_.SetPoint(base_view_->GetMirroredXWithWidthInView(x, w), y);
612   size_.SetSize(w, h);
613   Reposition();
614   if (popup_.get() && contains_mouse_)
615     AvoidMouse(last_mouse_moved_location_);
616 }
617
618 void StatusBubbleViews::SetStatus(const string16& status_text) {
619   if (size_.IsEmpty())
620     return;  // We have no bounds, don't attempt to show the popup.
621
622   if (status_text_ == status_text && !status_text.empty())
623     return;
624
625   if (!IsFrameVisible())
626     return;  // Don't show anything if the parent isn't visible.
627
628   Init();
629   status_text_ = status_text;
630   if (!status_text_.empty()) {
631     view_->SetText(status_text, true);
632     view_->Show();
633   } else if (!url_text_.empty()) {
634     view_->SetText(url_text_, true);
635   } else {
636     view_->SetText(string16(), true);
637   }
638 }
639
640 void StatusBubbleViews::SetURL(const GURL& url, const std::string& languages) {
641   url_ = url;
642   languages_ = languages;
643   if (size_.IsEmpty())
644     return;  // We have no bounds, don't attempt to show the popup.
645
646   Init();
647
648   // If we want to clear a displayed URL but there is a status still to
649   // display, display that status instead.
650   if (url.is_empty() && !status_text_.empty()) {
651     url_text_ = string16();
652     if (IsFrameVisible())
653       view_->SetText(status_text_, true);
654     return;
655   }
656
657   // Reset expansion state only when bubble is completely hidden.
658   if (view_->GetState() == StatusView::BUBBLE_HIDDEN) {
659     is_expanded_ = false;
660     SetBubbleWidth(GetStandardStatusBubbleWidth());
661   }
662
663   // Set Elided Text corresponding to the GURL object.
664   gfx::Rect popup_bounds = popup_->GetWindowBoundsInScreen();
665   int text_width = static_cast<int>(popup_bounds.width() -
666       (kShadowThickness * 2) - kTextPositionX - kTextHorizPadding - 1);
667   url_text_ = gfx::ElideUrl(url, view_->Label::font(), text_width, languages);
668
669   // An URL is always treated as a left-to-right string. On right-to-left UIs
670   // we need to explicitly mark the URL as LTR to make sure it is displayed
671   // correctly.
672   url_text_ = base::i18n::GetDisplayStringInLTRDirectionality(url_text_);
673
674   if (IsFrameVisible()) {
675     view_->SetText(url_text_, true);
676
677     CancelExpandTimer();
678
679     // If bubble is already in expanded state, shift to adjust to new text
680     // size (shrinking or expanding). Otherwise delay.
681     if (is_expanded_ && !url.is_empty()) {
682       ExpandBubble();
683     } else if (net::FormatUrl(url, languages).length() > url_text_.length()) {
684       base::MessageLoop::current()->PostDelayedTask(
685           FROM_HERE,
686           base::Bind(&StatusBubbleViews::ExpandBubble,
687                      expand_timer_factory_.GetWeakPtr()),
688           base::TimeDelta::FromMilliseconds(kExpandHoverDelay));
689     }
690   }
691 }
692
693 void StatusBubbleViews::Hide() {
694   status_text_ = string16();
695   url_text_ = string16();
696   if (view_)
697     view_->Hide();
698 }
699
700 void StatusBubbleViews::MouseMoved(const gfx::Point& location,
701                                    bool left_content) {
702   contains_mouse_ = !left_content;
703   if (left_content) {
704     Reposition();
705     return;
706   }
707   last_mouse_moved_location_ = location;
708
709   if (view_) {
710     view_->ResetTimer();
711
712     if (view_->GetState() != StatusView::BUBBLE_HIDDEN &&
713         view_->GetState() != StatusView::BUBBLE_HIDING_FADE &&
714         view_->GetState() != StatusView::BUBBLE_HIDING_TIMER) {
715       AvoidMouse(location);
716     }
717   }
718 }
719
720 void StatusBubbleViews::UpdateDownloadShelfVisibility(bool visible) {
721   download_shelf_is_visible_ = visible;
722 }
723
724 void StatusBubbleViews::AvoidMouse(const gfx::Point& location) {
725   // Get the position of the frame.
726   gfx::Point top_left;
727   views::View::ConvertPointToScreen(base_view_, &top_left);
728   // Border included.
729   int window_width = base_view_->GetLocalBounds().width();
730
731   // Get the cursor position relative to the popup.
732   gfx::Point relative_location = location;
733   if (base::i18n::IsRTL()) {
734     int top_right_x = top_left.x() + window_width;
735     relative_location.set_x(top_right_x - relative_location.x());
736   } else {
737     relative_location.set_x(
738         relative_location.x() - (top_left.x() + position_.x()));
739   }
740   relative_location.set_y(
741       relative_location.y() - (top_left.y() + position_.y()));
742
743   // If the mouse is in a position where we think it would move the
744   // status bubble, figure out where and how the bubble should be moved.
745   if (relative_location.y() > -kMousePadding &&
746       relative_location.x() < size_.width() + kMousePadding) {
747     int offset = kMousePadding + relative_location.y();
748
749     // Make the movement non-linear.
750     offset = offset * offset / kMousePadding;
751
752     // When the mouse is entering from the right, we want the offset to be
753     // scaled by how horizontally far away the cursor is from the bubble.
754     if (relative_location.x() > size_.width()) {
755       offset = static_cast<int>(static_cast<float>(offset) * (
756           static_cast<float>(kMousePadding -
757               (relative_location.x() - size_.width())) /
758           static_cast<float>(kMousePadding)));
759     }
760
761     // Cap the offset and change the visual presentation of the bubble
762     // depending on where it ends up (so that rounded corners square off
763     // and mate to the edges of the tab content).
764     if (offset >= size_.height() - kShadowThickness * 2) {
765       offset = size_.height() - kShadowThickness * 2;
766       view_->SetStyle(StatusView::STYLE_BOTTOM);
767     } else if (offset > kBubbleCornerRadius / 2 - kShadowThickness) {
768       view_->SetStyle(StatusView::STYLE_FLOATING);
769     } else {
770       view_->SetStyle(StatusView::STYLE_STANDARD);
771     }
772
773     // Check if the bubble sticks out from the monitor or will obscure
774     // download shelf.
775     gfx::NativeView window = base_view_->GetWidget()->GetNativeView();
776     gfx::Rect monitor_rect = gfx::Screen::GetScreenFor(window)->
777         GetDisplayNearestWindow(window).work_area();
778     const int bubble_bottom_y = top_left.y() + position_.y() + size_.height();
779
780     if (bubble_bottom_y + offset > monitor_rect.height() ||
781         (download_shelf_is_visible_ &&
782          (view_->GetStyle() == StatusView::STYLE_FLOATING ||
783           view_->GetStyle() == StatusView::STYLE_BOTTOM))) {
784       // The offset is still too large. Move the bubble to the right and reset
785       // Y offset_ to zero.
786       view_->SetStyle(StatusView::STYLE_STANDARD_RIGHT);
787       offset_ = 0;
788
789       // Subtract border width + bubble width.
790       int right_position_x = window_width - (position_.x() + size_.width());
791       popup_->SetBounds(gfx::Rect(top_left.x() + right_position_x,
792                                   top_left.y() + position_.y(),
793                                   size_.width(), size_.height()));
794     } else {
795       offset_ = offset;
796       popup_->SetBounds(gfx::Rect(top_left.x() + position_.x(),
797                                   top_left.y() + position_.y() + offset_,
798                                   size_.width(), size_.height()));
799     }
800   } else if (offset_ != 0 ||
801       view_->GetStyle() == StatusView::STYLE_STANDARD_RIGHT) {
802     offset_ = 0;
803     view_->SetStyle(StatusView::STYLE_STANDARD);
804     popup_->SetBounds(gfx::Rect(top_left.x() + position_.x(),
805                                 top_left.y() + position_.y(),
806                                 size_.width(), size_.height()));
807   }
808 }
809
810 bool StatusBubbleViews::IsFrameVisible() {
811   views::Widget* frame = base_view_->GetWidget();
812   if (!frame->IsVisible())
813     return false;
814
815   views::Widget* window = frame->GetTopLevelWidget();
816   return !window || !window->IsMinimized();
817 }
818
819 void StatusBubbleViews::ExpandBubble() {
820   // Elide URL to maximum possible size, then check actual length (it may
821   // still be too long to fit) before expanding bubble.
822   gfx::Rect popup_bounds = popup_->GetWindowBoundsInScreen();
823   int max_status_bubble_width = GetMaxStatusBubbleWidth();
824   url_text_ = gfx::ElideUrl(url_, view_->Label::font(),
825       max_status_bubble_width, languages_);
826   int expanded_bubble_width =std::max(GetStandardStatusBubbleWidth(),
827       std::min(view_->Label::font().GetStringWidth(url_text_) +
828                    (kShadowThickness * 2) + kTextPositionX +
829                    kTextHorizPadding + 1,
830                max_status_bubble_width));
831   is_expanded_ = true;
832   expand_view_->StartExpansion(url_text_, popup_bounds.width(),
833                                expanded_bubble_width);
834 }
835
836 int StatusBubbleViews::GetStandardStatusBubbleWidth() {
837   return base_view_->bounds().width() / 3;
838 }
839
840 int StatusBubbleViews::GetMaxStatusBubbleWidth() {
841   const ui::NativeTheme* theme = base_view_->GetNativeTheme();
842   return static_cast<int>(std::max(0, base_view_->bounds().width() -
843       (kShadowThickness * 2) - kTextPositionX - kTextHorizPadding - 1 -
844       views::NativeScrollBar::GetVerticalScrollBarWidth(theme)));
845 }
846
847 void StatusBubbleViews::SetBubbleWidth(int width) {
848   size_.set_width(width);
849   SetBounds(original_position_.x(), original_position_.y(),
850             size_.width(), size_.height());
851 }
852
853 void StatusBubbleViews::CancelExpandTimer() {
854   if (expand_timer_factory_.HasWeakPtrs())
855     expand_timer_factory_.InvalidateWeakPtrs();
856 }