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