info_bubble.cc revision a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7
1// Copyright 2013 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/autofill/info_bubble.h"
6
7#include "base/i18n/rtl.h"
8#include "ui/gfx/point.h"
9#include "ui/gfx/rect.h"
10#include "ui/gfx/size.h"
11#include "ui/gfx/text_constants.h"
12#include "ui/views/bubble/bubble_border.h"
13#include "ui/views/bubble/bubble_frame_view.h"
14#include "ui/views/controls/combobox/combobox.h"
15#include "ui/views/controls/label.h"
16#include "ui/views/layout/fill_layout.h"
17#include "ui/views/layout/layout_constants.h"
18#include "ui/views/widget/widget.h"
19
20namespace autofill {
21
22namespace {
23
24// The visible width of bubble borders (differs from the actual width) in px.
25const int kBubbleBorderVisibleWidth = 1;
26
27// The margin between the content of the error bubble and its border.
28const int kInfoBubbleHorizontalMargin = 14;
29const int kInfoBubbleVerticalMargin = 12;
30
31}  // namespace
32
33InfoBubble::InfoBubble(views::View* anchor,
34                       const base::string16& message)
35    : anchor_(anchor),
36      align_to_anchor_edge_(false),
37      preferred_width_(233),
38      show_above_anchor_(false) {
39  DCHECK(anchor_);
40  SetAnchorView(anchor_);
41
42  set_margins(gfx::Insets(kInfoBubbleVerticalMargin,
43                          kInfoBubbleHorizontalMargin,
44                          kInfoBubbleVerticalMargin,
45                          kInfoBubbleHorizontalMargin));
46  set_use_focusless(true);
47
48  SetLayoutManager(new views::FillLayout);
49  views::Label* label = new views::Label(message);
50  label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
51  label->SetMultiLine(true);
52  AddChildView(label);
53}
54
55InfoBubble::~InfoBubble() {}
56
57void InfoBubble::Show() {
58  // TODO(dbeam): currently we assume that combobox menus always show downward
59  // (which isn't true). If the invalid combobox is low enough on the screen,
60  // its menu will actually show upward and obscure the bubble. Figure out when
61  // this might happen and adjust |show_above_anchor_| accordingly. This is not
62  // that big of deal because it rarely happens in practice.
63  if (show_above_anchor_) {
64    set_arrow(ShouldArrowGoOnTheRight() ? views::BubbleBorder::BOTTOM_RIGHT :
65                                          views::BubbleBorder::BOTTOM_LEFT);
66  } else {
67    set_arrow(ShouldArrowGoOnTheRight() ? views::BubbleBorder::TOP_RIGHT :
68                                          views::BubbleBorder::TOP_LEFT);
69  }
70
71  widget_ = views::BubbleDelegateView::CreateBubble(this);
72  UpdatePosition();
73}
74
75void InfoBubble::Hide() {
76  views::Widget* widget = GetWidget();
77  if (widget && !widget->IsClosed())
78    widget->Close();
79}
80
81void InfoBubble::UpdatePosition() {
82  if (!widget_)
83    return;
84
85  if (!anchor_->GetVisibleBounds().IsEmpty()) {
86    SizeToContents();
87    widget_->SetVisibilityChangedAnimationsEnabled(true);
88    widget_->ShowInactive();
89  } else {
90    widget_->SetVisibilityChangedAnimationsEnabled(false);
91    widget_->Hide();
92  }
93}
94
95gfx::Size InfoBubble::GetPreferredSize() {
96  int pref_width = preferred_width_;
97  pref_width -= GetBubbleFrameView()->GetInsets().width();
98  pref_width -= 2 * kBubbleBorderVisibleWidth;
99  return gfx::Size(pref_width, GetHeightForWidth(pref_width));
100}
101
102gfx::Rect InfoBubble::GetBubbleBounds() {
103  gfx::Rect bounds = views::BubbleDelegateView::GetBubbleBounds();
104  gfx::Rect anchor_rect = GetAnchorRect();
105
106  if (show_above_anchor_)
107    bounds.set_y(anchor_rect.y() - GetBubbleFrameView()->height());
108  else
109    bounds.set_y(anchor_rect.bottom());
110
111  if (align_to_anchor_edge_) {
112    anchor_rect.Inset(-GetBubbleFrameView()->bubble_border()->GetInsets());
113    bounds.set_x(ShouldArrowGoOnTheRight() ?
114        anchor_rect.right() - bounds.width() - kBubbleBorderVisibleWidth :
115        anchor_rect.x() + kBubbleBorderVisibleWidth);
116  }
117
118  return bounds;
119}
120
121void InfoBubble::OnWidgetClosing(views::Widget* widget) {
122  if (widget == widget_)
123    widget_ = NULL;
124}
125
126bool InfoBubble::ShouldFlipArrowForRtl() const {
127  return false;
128}
129
130bool InfoBubble::ShouldArrowGoOnTheRight() {
131  views::View* container = anchor_->GetWidget()->non_client_view();
132  DCHECK(container->Contains(anchor_));
133
134  gfx::Point anchor_offset;
135  views::View::ConvertPointToTarget(anchor_, container, &anchor_offset);
136  anchor_offset.Offset(-container_insets_.left(), 0);
137
138  if (base::i18n::IsRTL()) {
139    int anchor_right_x = anchor_offset.x() + anchor_->width();
140    return anchor_right_x >= preferred_width_;
141  }
142
143  int container_width = container->width() - container_insets_.width();
144  return anchor_offset.x() + preferred_width_ > container_width;
145}
146
147}  // namespace autofill
148