Index: chrome/browser/views/status_bubble_views.cc |
=================================================================== |
--- chrome/browser/views/status_bubble_views.cc (revision 19035) |
+++ chrome/browser/views/status_bubble_views.cc (working copy) |
@@ -28,6 +28,7 @@ |
#include "views/widget/root_view.h" |
#include "views/widget/widget.h" |
#if defined(OS_WIN) |
+#include "views/controls/scrollbar/native_scroll_bar.h" |
#include "views/widget/widget_win.h" |
#endif |
@@ -53,7 +54,7 @@ |
// The minimum horizontal space between the (right) end of the text and the edge |
// of the status bubble, not including the outer shadow ring, or a 1 px gap we |
-// leave so we can shit all the text by 1 px to produce a "highlight" effect. |
+// leave so we can shift all the text by 1 px to produce a "highlight" effect. |
static const int kTextHorizPadding = 1; |
// Delays before we start hiding or showing the bubble after we receive a |
@@ -66,6 +67,9 @@ |
static const int kHideFadeDurationMS = 200; |
static const int kFramerate = 25; |
+// How long each expansion step should take. |
+static const int kExpansionStepDurationMS = 150; |
+ |
// View ----------------------------------------------------------------------- |
// StatusView manages the display of the bubble, applying text changes and |
// fading in or out the bubble as required. |
@@ -113,6 +117,11 @@ |
// Set the bubble text to a certain value, hides the bubble if text is |
// an empty string. |
+ void SetTextAndAnimate(const std::wstring& text); |
+ |
+ // Set the bubble text to a certain value without triggering animation |
+ // sequence. Called by the StatusViewExpander after bubble has been |
+ // fully expanded. |
void SetText(const std::wstring& text); |
BubbleStage GetState() const { return stage_; } |
@@ -178,6 +187,12 @@ |
}; |
void StatusBubbleViews::StatusView::SetText(const std::wstring& text) { |
+ text_ = text; |
+ SchedulePaint(); |
+} |
+ |
+void StatusBubbleViews::StatusView::SetTextAndAnimate( |
+ const std::wstring& text) { |
if (text.empty()) { |
// The string was empty. |
StartHiding(); |
@@ -317,7 +332,6 @@ |
void StatusBubbleViews::StatusView::AnimationEnded( |
const Animation* animation) { |
SetOpacity(opacity_end_); |
- |
if (stage_ == BUBBLE_HIDING_FADE) { |
stage_ = BUBBLE_HIDDEN; |
popup_->Hide(); |
@@ -453,6 +467,83 @@ |
body_bounds.height()); |
} |
+ |
+// StatusViewExpander --------------------------------------------------------- |
+// StatusViewExpander manages the expansion and contraction of the status |
+// bubble as it accommodates URL's too long to fit in the standard bubble. |
+// Changes are passed through to the StatusView to paint. |
+class StatusBubbleViews::StatusViewExpander : public Animation, |
+ public AnimationDelegate { |
+ public: |
+ StatusViewExpander(StatusBubble* status_bubble, StatusView* status_view) |
+ : Animation(kFramerate, this), |
+ status_bubble_(status_bubble), |
+ status_view_(status_view), |
+ expansion_start_(0), |
+ expansion_end_(0) { |
+ } |
+ |
+ // Manage the expansion of the bubble. |
+ void StartExpansion(std::wstring expanded_text, int current_width, |
+ int expansion_end); |
+ |
+ // Set width of fully expanded bubble. |
+ void SetExpandedWidth(int expanded_width); |
+ |
+ private: |
+ // Animation functions. |
+ int GetCurrentBubbleWidth(); |
+ void SetBubbleWidth(int width); |
+ void AnimateToState(double state); |
+ void AnimationEnded(const Animation* animation); |
+ |
+ // We are changing the bounds and text of this view. |
+ StatusView* status_view_; |
+ |
+ // Manager that owns us. |
+ StatusBubble* status_bubble_; |
+ |
+ // The currently displayed text. |
+ std::wstring text_; |
+ |
+ // Text elided to fit maximum possible status bar width. |
+ std::wstring expanded_text_; |
+ |
+ // Widths at expansion start and end. |
+ int expansion_start_; |
+ int expansion_end_; |
+}; |
+ |
+void StatusBubbleViews::StatusViewExpander::AnimateToState(double state) { |
+ SetBubbleWidth(GetCurrentBubbleWidth()); |
+} |
+ |
+void StatusBubbleViews::StatusViewExpander::AnimationEnded( |
+ const Animation* animation) { |
+ SetBubbleWidth(expansion_end_); |
+ status_view_->SetText(expanded_text_); |
+} |
+ |
+void StatusBubbleViews::StatusViewExpander::StartExpansion( |
+ std::wstring expanded_text, int expansion_start, |
+ int expansion_end) { |
+ expanded_text_ = expanded_text; |
+ expansion_start_ = expansion_start; |
+ expansion_end_ = expansion_end; |
+ SetDuration(kExpansionStepDurationMS); |
+ Start(); |
+} |
+ |
+int StatusBubbleViews::StatusViewExpander::GetCurrentBubbleWidth() { |
+ return static_cast<int>(expansion_start_ + |
+ (expansion_end_ - expansion_start_) * Animation::GetCurrentValue()); |
+} |
+ |
+void StatusBubbleViews::StatusViewExpander::SetBubbleWidth(int width) { |
+ status_bubble_->SetBubbleWidth(width); |
+ status_view_->SchedulePaint(); |
+} |
+ |
// StatusBubble --------------------------------------------------------------- |
const int StatusBubbleViews::kShadowThickness = 1; |
@@ -463,10 +554,13 @@ |
opacity_(0), |
frame_(frame), |
view_(NULL), |
- download_shelf_is_visible_(false) { |
+ download_shelf_is_visible_(false), |
+ is_expanded_(false), |
+ expand_timer_factory_(this) { |
} |
StatusBubbleViews::~StatusBubbleViews() { |
+ CancelExpandTimer(); |
if (popup_.get()) |
popup_->CloseNow(); |
} |
@@ -479,6 +573,8 @@ |
if (!view_) |
view_ = new StatusView(this, popup, frame_->GetThemeProvider()); |
+ if (!expand_view_) |
+ expand_view_ = new StatusViewExpander(this, view_); |
popup->set_window_style(WS_POPUP); |
popup->set_window_ex_style(WS_EX_LAYERED | WS_EX_TOOLWINDOW | |
@@ -535,26 +631,34 @@ |
Init(); |
status_text_ = status_text; |
if (!status_text_.empty()) { |
- view_->SetText(status_text); |
+ view_->SetTextAndAnimate(status_text); |
view_->Show(); |
} else if (!url_text_.empty()) { |
- view_->SetText(url_text_); |
+ view_->SetTextAndAnimate(url_text_); |
} else { |
- view_->SetText(std::wstring()); |
+ view_->SetTextAndAnimate(std::wstring()); |
} |
} |
void StatusBubbleViews::SetURL(const GURL& url, const std::wstring& languages) { |
+ languages_ = languages; |
+ url_ = url; |
Init(); |
// If we want to clear a displayed URL but there is a status still to |
// display, display that status instead. |
if (url.is_empty() && !status_text_.empty()) { |
url_text_ = std::wstring(); |
- view_->SetText(status_text_); |
+ view_->SetTextAndAnimate(status_text_); |
return; |
} |
+ // Reset expansion state only when bubble is completely hidden. |
+ if (view_->GetState() == StatusView::BUBBLE_HIDDEN) { |
+ is_expanded_ = false; |
+ SetBubbleWidth(GetStandardStatusBubbleWidth()); |
+ } |
+ |
// Set Elided Text corresponding to the GURL object. |
gfx::Rect popup_bounds; |
popup_->GetBounds(&popup_bounds, true); |
@@ -563,13 +667,30 @@ |
url_text_ = gfx::ElideUrl(url, view_->Label::GetFont(), text_width, |
languages); |
+ url_parse::Parsed parsed; |
+ std::wstring original_url_text_ = |
+ net::FormatUrl(url, languages, true, true, &parsed, NULL); |
+ |
// An URL is always treated as a left-to-right string. On right-to-left UIs |
// we need to explicitly mark the URL as LTR to make sure it is displayed |
// correctly. |
if (l10n_util::GetTextDirection() == l10n_util::RIGHT_TO_LEFT && |
!url_text_.empty()) |
l10n_util::WrapStringWithLTRFormatting(&url_text_); |
- view_->SetText(url_text_); |
+ |
+ view_->SetTextAndAnimate(url_text_); |
+ |
+ CancelExpandTimer(); |
+ |
+ // If bubble is already in expanded state, shift immediately to adjust to |
+ // new text size (shrinking or expanding). Otherwise delay for |
+ // kExpandHoverDelay ms. |
+ if (is_expanded_ && !url.is_empty()) |
+ ExpandBubble(); |
+ else if (original_url_text_.length() > url_text_.length()) |
+ MessageLoop::current()->PostDelayedTask(FROM_HERE, |
+ expand_timer_factory_.NewRunnableMethod( |
+ &StatusBubbleViews::ExpandBubble), kExpandHoverDelay); |
} |
void StatusBubbleViews::Hide() { |
@@ -675,7 +796,7 @@ |
view_->SetStyle(StatusView::STYLE_STANDARD_RIGHT); |
offset_ = 0; |
- // Substract border width + bubble width. |
+ // Subtract border width + bubble width. |
int right_position_x = window_width - (position_.x() + size_.width()); |
popup_->SetBounds(gfx::Rect(top_left.x() + right_position_x, |
top_left.y() + position_.y(), |
@@ -695,3 +816,46 @@ |
size_.width(), size_.height())); |
} |
} |
+ |
+void StatusBubbleViews::ExpandBubble() { |
+ // Elide url to maximum possible size, then check actual length (it may |
+ // still be too long to fit) before expanding bubble. |
+ gfx::Rect popup_bounds; |
+ popup_->GetBounds(&popup_bounds, true); |
+ int max_status_bubble_width = GetMaxStatusBubbleWidth(); |
+ url_text_ = gfx::ElideUrl(url_, view_->Label::GetFont(), |
+ max_status_bubble_width, languages_); |
+ int expanded_bubble_width = |
+ std::max(GetStandardStatusBubbleWidth(), |
+ std::min(view_->Label::GetFont().GetStringWidth(url_text_) + |
+ (kShadowThickness * 2) + kTextPositionX + kTextHorizPadding + 1, |
+ max_status_bubble_width)); |
+ is_expanded_ = true; |
+ expand_view_->StartExpansion(url_text_, popup_bounds.width(), |
+ expanded_bubble_width); |
+} |
+ |
+int StatusBubbleViews::GetStandardStatusBubbleWidth() { |
+ gfx::Rect frame_bounds; |
+ frame_->GetBounds(&frame_bounds, false); |
+ return frame_bounds.width() / 3; |
+} |
+ |
+int StatusBubbleViews::GetMaxStatusBubbleWidth() { |
+ gfx::Rect frame_bounds; |
+ frame_->GetBounds(&frame_bounds, false); |
+ return static_cast<int>(frame_bounds.width() - (kShadowThickness * 2) - |
+ kTextPositionX - kTextHorizPadding - 1 - |
+ views::NativeScrollBar::GetVerticalScrollBarWidth()); |
+} |
+ |
+void StatusBubbleViews::SetBubbleWidth(int width) { |
+ size_.set_width(width); |
+ SetBounds(position_.x(), position_.y(), size_.width(), size_.height()); |
+} |
+ |
+void StatusBubbleViews::CancelExpandTimer() { |
+ if (!expand_timer_factory_.empty()) |
+ expand_timer_factory_.RevokeAll(); |
+} |
+ |