summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/ui/views/autofill/autofill_dialog_views.cc162
-rw-r--r--chrome/browser/ui/views/autofill/autofill_dialog_views.h47
-rw-r--r--ui/views/bubble/bubble_delegate.cc9
-rw-r--r--ui/views/bubble/bubble_delegate.h3
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