diff options
-rw-r--r-- | chrome/browser/ui/views/autofill/autofill_dialog_views.cc | 162 | ||||
-rw-r--r-- | chrome/browser/ui/views/autofill/autofill_dialog_views.h | 47 | ||||
-rw-r--r-- | ui/views/bubble/bubble_delegate.cc | 9 | ||||
-rw-r--r-- | ui/views/bubble/bubble_delegate.h | 3 |
4 files changed, 139 insertions, 82 deletions
diff --git a/chrome/browser/ui/views/autofill/autofill_dialog_views.cc b/chrome/browser/ui/views/autofill/autofill_dialog_views.cc index 78a551f..823f8f9 100644 --- a/chrome/browser/ui/views/autofill/autofill_dialog_views.cc +++ b/chrome/browser/ui/views/autofill/autofill_dialog_views.cc @@ -100,6 +100,13 @@ const int kDialogEdgePadding = 20; // The vertical padding between rows of manual inputs (in pixels). const int kManualInputRowPadding = 10; +// The margin between the content of the error bubble and its border. +const int kErrorBubbleHorizontalMargin = 14; +const int kErrorBubbleVerticalMargin = 12; + +// The visible width of bubble borders (differs from the actual width) in px. +const int kBubbleBorderVisibleWidth = 1; + // Slight shading for mouse hover and legal document background. SkColor kShadingColor = SkColorSetARGB(7, 0, 0, 0); @@ -124,6 +131,7 @@ const SkColor kGreyTextColor = SkColorSetRGB(102, 102, 102); const char kNotificationAreaClassName[] = "autofill/NotificationArea"; const char kOverlayViewClassName[] = "autofill/OverlayView"; +const char kSectionContainerClassName[] = "autofill/SectionContainer"; const char kSuggestedButtonClassName[] = "autofill/SuggestedButton"; // Draws an arrow at the top of |canvas| pointing to |tip_x|. @@ -217,37 +225,6 @@ class SectionRowView : public views::View { DISALLOW_COPY_AND_ASSIGN(SectionRowView); }; -// This view is used for the contents of the error bubble widget. -class ErrorBubbleContents : public views::View { - public: - explicit ErrorBubbleContents(const base::string16& message) - : color_(kWarningColor) { - set_border(views::Border::CreateEmptyBorder(kArrowHeight - 3, 0, 0, 0)); - - views::Label* label = new views::Label(); - label->SetText(message); - label->SetAutoColorReadabilityEnabled(false); - label->SetEnabledColor(SK_ColorWHITE); - label->set_border( - views::Border::CreateSolidSidedBorder(5, 10, 5, 10, color_)); - label->set_background( - views::Background::CreateSolidBackground(color_)); - SetLayoutManager(new views::FillLayout()); - AddChildView(label); - } - virtual ~ErrorBubbleContents() {} - - virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE { - views::View::OnPaint(canvas); - DrawArrow(canvas, width() / 2.0f, color_, SK_ColorTRANSPARENT); - } - - private: - SkColor color_; - - DISALLOW_COPY_AND_ASSIGN(ErrorBubbleContents); -}; - // A view that propagates visibility and preferred size changes. class LayoutPropagationView : public views::View { public: @@ -505,30 +482,39 @@ class LoadingAnimationView : public views::View, // AutofillDialogViews::ErrorBubble -------------------------------------------- AutofillDialogViews::ErrorBubble::ErrorBubble(views::View* anchor, + views::View* anchor_container, const base::string16& message) : anchor_(anchor), - contents_(new ErrorBubbleContents(message)), - observer_(this) { - widget_ = new views::Widget; - views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP); - params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; - views::Widget* anchor_widget = anchor->GetWidget(); - DCHECK(anchor_widget); - params.parent = anchor_widget->GetNativeView(); - - widget_->Init(params); - widget_->SetContentsView(contents_); + anchor_container_(anchor_container) { + DCHECK(anchor_container_->Contains(anchor)); + + SetAnchorView(anchor_); + set_arrow(ShouldArrowGoOnTheRight() ? views::BubbleBorder::TOP_RIGHT : + views::BubbleBorder::TOP_LEFT); + set_margins(gfx::Insets(kErrorBubbleVerticalMargin, + kErrorBubbleHorizontalMargin, + kErrorBubbleVerticalMargin, + kErrorBubbleHorizontalMargin)); + set_use_focusless(true); + + SetLayoutManager(new views::FillLayout); + views::Label* label = new views::Label(message); + label->SetHorizontalAlignment(gfx::ALIGN_LEFT); + label->SetMultiLine(true); + AddChildView(label); + + widget_ = views::BubbleDelegateView::CreateBubble(this); UpdatePosition(); - observer_.Add(widget_); } AutofillDialogViews::ErrorBubble::~ErrorBubble() { - if (widget_) - widget_->Close(); + DCHECK(!widget_); } -bool AutofillDialogViews::ErrorBubble::IsShowing() { - return widget_ && widget_->IsVisible(); +void AutofillDialogViews::ErrorBubble::Hide() { + views::Widget* widget = GetWidget(); + if (widget && !widget->IsClosed()) + widget->Close(); } void AutofillDialogViews::ErrorBubble::UpdatePosition() { @@ -536,7 +522,7 @@ void AutofillDialogViews::ErrorBubble::UpdatePosition() { return; if (!anchor_->GetVisibleBounds().IsEmpty()) { - widget_->SetBounds(GetBoundsForWidget()); + SizeToContents(); widget_->SetVisibilityChangedAnimationsEnabled(true); widget_->Show(); } else { @@ -545,22 +531,51 @@ void AutofillDialogViews::ErrorBubble::UpdatePosition() { } } -void AutofillDialogViews::ErrorBubble::OnWidgetClosing(views::Widget* widget) { - DCHECK_EQ(widget_, widget); - observer_.Remove(widget_); - widget_ = NULL; +gfx::Size AutofillDialogViews::ErrorBubble::GetPreferredSize() { + int pref_width = GetPreferredBubbleWidth(); + pref_width -= GetBubbleFrameView()->GetInsets().width(); + pref_width -= 2 * kBubbleBorderVisibleWidth; + return gfx::Size(pref_width, GetHeightForWidth(pref_width)); } -gfx::Rect AutofillDialogViews::ErrorBubble::GetBoundsForWidget() { +gfx::Rect AutofillDialogViews::ErrorBubble::GetBubbleBounds() { + gfx::Rect bounds = views::BubbleDelegateView::GetBubbleBounds(); gfx::Rect anchor_bounds = anchor_->GetBoundsInScreen(); - gfx::Rect bubble_bounds; - bubble_bounds.set_size(contents_->GetPreferredSize()); - bubble_bounds.set_x(anchor_bounds.right() - - (anchor_bounds.width() + bubble_bounds.width()) / 2); - const int kErrorBubbleOverlap = 3; - bubble_bounds.set_y(anchor_bounds.bottom() - kErrorBubbleOverlap); + anchor_bounds.Inset(-GetBubbleFrameView()->bubble_border()->GetInsets()); + bounds.set_x(ShouldArrowGoOnTheRight() ? + anchor_bounds.right() - bounds.width() - kBubbleBorderVisibleWidth : + anchor_bounds.x() + kBubbleBorderVisibleWidth); + return bounds; +} - return bubble_bounds; +void AutofillDialogViews::ErrorBubble::OnWidgetClosing(views::Widget* widget) { + if (widget == widget_) + widget_ = NULL; +} + +bool AutofillDialogViews::ErrorBubble::ShouldFlipArrowForRtl() const { + return false; +} + +int AutofillDialogViews::ErrorBubble::GetContainerWidth() { + return anchor_container_->width() - anchor_container_->GetInsets().width(); +} + +int AutofillDialogViews::ErrorBubble::GetPreferredBubbleWidth() { + return (GetContainerWidth() - views::kRelatedControlHorizontalSpacing) / 2; +} + +bool AutofillDialogViews::ErrorBubble::ShouldArrowGoOnTheRight() { + gfx::Point anchor_offset; + views::View::ConvertPointToTarget(anchor_, anchor_container_, &anchor_offset); + anchor_offset.Offset(-anchor_container_->GetInsets().left(), 0); + + if (base::i18n::IsRTL()) { + int anchor_right_x = anchor_offset.x() + anchor_->width(); + return anchor_right_x >= GetPreferredBubbleWidth(); + } + + return anchor_offset.x() + GetPreferredBubbleWidth() > GetContainerWidth(); } // AutofillDialogViews::AccountChooser ----------------------------------------- @@ -862,7 +877,7 @@ void AutofillDialogViews::OnWidgetBoundsChanged(views::Widget* widget, base::TimeDelta::FromMilliseconds(100), base::Bind(&AutofillDialogViews::ContentsPreferredSizeChanged, base::Unretained(this))); - error_bubble_.reset(); + HideErrorBubble(); } bool AutofillDialogViews::NotificationArea::HasArrow() { @@ -936,6 +951,10 @@ void AutofillDialogViews::SectionContainer::SetForwardMouseEvents( set_background(NULL); } +const char* AutofillDialogViews::SectionContainer::GetClassName() const { + return kSectionContainerClassName; +} + void AutofillDialogViews::SectionContainer::OnMouseMoved( const ui::MouseEvent& event) { SetActive(ShouldForwardEvent(event)); @@ -1245,6 +1264,7 @@ AutofillDialogViews::AutofillDialogViews(AutofillDialogViewDelegate* delegate) footnote_view_(NULL), legal_document_view_(NULL), focus_manager_(NULL), + error_bubble_(NULL), observer_(this) { DCHECK(delegate); detail_groups_.insert(std::make_pair(SECTION_CC, @@ -1258,6 +1278,7 @@ AutofillDialogViews::AutofillDialogViews(AutofillDialogViewDelegate* delegate) } AutofillDialogViews::~AutofillDialogViews() { + HideErrorBubble(); DCHECK(!window_); } @@ -1731,7 +1752,7 @@ void AutofillDialogViews::OnWillChangeFocus( views::View* focused_before, views::View* focused_now) { delegate_->FocusMoved(); - error_bubble_.reset(); + HideErrorBubble(); } void AutofillDialogViews::OnDidChangeFocus( @@ -2134,7 +2155,7 @@ void AutofillDialogViews::SetValidityForInput( if (error_bubble_ && error_bubble_->anchor() == input) { validity_map_.erase(input); - error_bubble_.reset(); + HideErrorBubble(); } } } @@ -2148,8 +2169,19 @@ void AutofillDialogViews::ShowErrorBubbleForViewIfNecessary(views::View* view) { if (error_message != validity_map_.end()) { view->ScrollRectToVisible(view->GetLocalBounds()); - if (!error_bubble_ || error_bubble_->anchor() != view) - error_bubble_.reset(new ErrorBubble(view, error_message->second)); + if (!error_bubble_ || error_bubble_->anchor() != view) { + HideErrorBubble(); + views::View* section = + view->GetAncestorWithClassName(kSectionContainerClassName); + error_bubble_ = new ErrorBubble(view, section, error_message->second); + } + } +} + +void AutofillDialogViews::HideErrorBubble() { + if (error_bubble_) { + error_bubble_->Hide(); + error_bubble_ = NULL; } } diff --git a/chrome/browser/ui/views/autofill/autofill_dialog_views.h b/chrome/browser/ui/views/autofill/autofill_dialog_views.h index 65c025c..cdb8d80 100644 --- a/chrome/browser/ui/views/autofill/autofill_dialog_views.h +++ b/chrome/browser/ui/views/autofill/autofill_dialog_views.h @@ -16,6 +16,7 @@ #include "chrome/browser/ui/autofill/autofill_dialog_view.h" #include "chrome/browser/ui/autofill/autofill_dialog_view_delegate.h" #include "chrome/browser/ui/autofill/testable_autofill_dialog_view.h" +#include "ui/views/bubble/bubble_delegate.h" #include "ui/views/controls/button/button.h" #include "ui/views/controls/button/menu_button.h" #include "ui/views/controls/button/menu_button_listener.h" @@ -167,31 +168,43 @@ class AutofillDialogViews : public AutofillDialogView, private: // A class that creates and manages a widget for error messages. - class ErrorBubble : public views::WidgetObserver { + class ErrorBubble : public views::BubbleDelegateView { public: - ErrorBubble(views::View* anchor, const base::string16& message); + ErrorBubble(views::View* anchor, + views::View* anchor_container, + const base::string16& message); virtual ~ErrorBubble(); - bool IsShowing(); - - // Re-positions the bubble over |anchor_|. If |anchor_| is not visible, - // the bubble will hide. + // Updates the position of the bubble. void UpdatePosition(); + // Hides and closes the bubble. + void Hide(); + + // views::BubbleDelegateView: + virtual gfx::Size GetPreferredSize() OVERRIDE; + virtual gfx::Rect GetBubbleBounds() OVERRIDE; virtual void OnWidgetClosing(views::Widget* widget) OVERRIDE; + virtual bool ShouldFlipArrowForRtl() const OVERRIDE; - views::View* anchor() { return anchor_; } + const views::View* anchor() const { return anchor_; } private: - // Calculates and returns the bounds of |widget_|, depending on |anchor_| - // and |contents_|. - gfx::Rect GetBoundsForWidget(); + // Calculate the effective container width (ignores edge padding). + int GetContainerWidth(); + + // Returns the desired bubble width (total). + int GetPreferredBubbleWidth(); + + // Whether the bubble should stick to the right edge of |anchor_|. + bool ShouldArrowGoOnTheRight(); views::Widget* widget_; // Weak, may be NULL. - views::View* anchor_; // Weak. - // The contents view of |widget_|. - views::View* contents_; // Weak. - ScopedObserver<views::Widget, ErrorBubble> observer_; + views::View* const anchor_; // Weak. + + // Used to determine the width of the bubble and whether to stick to the + // right edge of |anchor_|. Must contain |anchor_|. + views::View* const anchor_container_; // Weak. DISALLOW_COPY_AND_ASSIGN(ErrorBubble); }; @@ -331,6 +344,7 @@ class AutofillDialogViews : public AutofillDialogView, void SetForwardMouseEvents(bool forward); // views::View implementation. + virtual const char* GetClassName() const OVERRIDE; virtual void OnMouseMoved(const ui::MouseEvent& event) OVERRIDE; virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE; virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE; @@ -548,6 +562,9 @@ class AutofillDialogViews : public AutofillDialogView, // |validity_map_|. void ShowErrorBubbleForViewIfNecessary(views::View* view); + // Hides |error_bubble_| (if it exists). + void HideErrorBubble(); + // Updates validity of the inputs in |section| with new |validity_messages|. // Fields are only updated with unsure messages if |overwrite_valid| is true. void MarkInputsInvalid(DialogSection section, @@ -671,7 +688,7 @@ class AutofillDialogViews : public AutofillDialogView, views::FocusManager* focus_manager_; // The object that manages the error bubble widget. - scoped_ptr<ErrorBubble> error_bubble_; + ErrorBubble* error_bubble_; // Weak; owns itself. // Map from input view (textfield or combobox) to error string. std::map<views::View*, base::string16> validity_map_; diff --git a/ui/views/bubble/bubble_delegate.cc b/ui/views/bubble/bubble_delegate.cc index 426ea12..0ab575d 100644 --- a/ui/views/bubble/bubble_delegate.cc +++ b/ui/views/bubble/bubble_delegate.cc @@ -219,8 +219,9 @@ View* BubbleDelegateView::GetContentsView() { NonClientFrameView* BubbleDelegateView::CreateNonClientFrameView( Widget* widget) { BubbleFrameView* frame = new BubbleFrameView(margins()); - const BubbleBorder::Arrow adjusted_arrow = base::i18n::IsRTL() ? - BubbleBorder::horizontal_mirror(arrow()) : arrow(); + BubbleBorder::Arrow adjusted_arrow = arrow(); + if (ShouldFlipArrowForRtl() && base::i18n::IsRTL()) + adjusted_arrow = BubbleBorder::horizontal_mirror(adjusted_arrow); frame->SetBubbleBorder(new BubbleBorder(adjusted_arrow, shadow(), color())); return frame; } @@ -367,6 +368,10 @@ void BubbleDelegateView::AnimationProgressed(const gfx::Animation* animation) { void BubbleDelegateView::Init() {} +bool BubbleDelegateView::ShouldFlipArrowForRtl() const { + return true; +} + void BubbleDelegateView::SetAnchorView(View* anchor_view) { // When the anchor view gets set the associated anchor widget might // change as well. diff --git a/ui/views/bubble/bubble_delegate.h b/ui/views/bubble/bubble_delegate.h index 2cb7749..c6ac3eb 100644 --- a/ui/views/bubble/bubble_delegate.h +++ b/ui/views/bubble/bubble_delegate.h @@ -141,6 +141,9 @@ class VIEWS_EXPORT BubbleDelegateView : public WidgetDelegateView, // Perform view initialization on the contents for bubble sizing. virtual void Init(); + // Whether |arrow()| should automatically flip while in RTL. + virtual bool ShouldFlipArrowForRtl() const; + // Set the anchor view or rect; set these before CreateBubble or Show. Note // that if a valid view gets passed, the anchor rect will get ignored. If the // view gets deleted, but no new view gets set, the last known anchor postion |