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