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 20 namespace autofill { 21 22 namespace { 23 24 // The visible width of bubble borders (differs from the actual width) in px. 25 const int kBubbleBorderVisibleWidth = 1; 26 27 // The margin between the content of the error bubble and its border. 28 const int kInfoBubbleHorizontalMargin = 14; 29 const int kInfoBubbleVerticalMargin = 12; 30 31 } // namespace 32 33 class InfoBubbleFrame : public views::BubbleFrameView { 34 public: 35 explicit InfoBubbleFrame(const gfx::Insets& content_margins) 36 : views::BubbleFrameView(content_margins) {} 37 virtual ~InfoBubbleFrame() {} 38 39 virtual gfx::Rect GetAvailableScreenBounds(const gfx::Rect& rect) OVERRIDE { 40 return available_bounds_; 41 } 42 43 void set_available_bounds(const gfx::Rect& available_bounds) { 44 available_bounds_ = available_bounds; 45 } 46 47 private: 48 // Bounds that this frame should try to keep bubbles within (screen coords). 49 gfx::Rect available_bounds_; 50 51 DISALLOW_COPY_AND_ASSIGN(InfoBubbleFrame); 52 }; 53 54 InfoBubble::InfoBubble(views::View* anchor, 55 const base::string16& message) 56 : anchor_(anchor), 57 frame_(NULL), 58 align_to_anchor_edge_(false), 59 preferred_width_(233), 60 show_above_anchor_(false) { 61 DCHECK(anchor_); 62 SetAnchorView(anchor_); 63 64 set_margins(gfx::Insets(kInfoBubbleVerticalMargin, 65 kInfoBubbleHorizontalMargin, 66 kInfoBubbleVerticalMargin, 67 kInfoBubbleHorizontalMargin)); 68 set_use_focusless(true); 69 70 SetLayoutManager(new views::FillLayout); 71 views::Label* label = new views::Label(message); 72 label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 73 label->SetMultiLine(true); 74 AddChildView(label); 75 } 76 77 InfoBubble::~InfoBubble() {} 78 79 void InfoBubble::Show() { 80 // TODO(dbeam): currently we assume that combobox menus always show downward 81 // (which isn't true). If the invalid combobox is low enough on the screen, 82 // its menu will actually show upward and obscure the bubble. Figure out when 83 // this might happen and adjust |show_above_anchor_| accordingly. This is not 84 // that big of deal because it rarely happens in practice. 85 if (show_above_anchor_) 86 set_arrow(views::BubbleBorder::vertical_mirror(arrow())); 87 88 widget_ = views::BubbleDelegateView::CreateBubble(this); 89 90 if (align_to_anchor_edge_) { 91 // The frame adjusts its arrow before the bubble's alignment can be changed. 92 // Set the created bubble border back to the original arrow and re-adjust. 93 frame_->bubble_border()->set_arrow(arrow()); 94 SetAlignment(views::BubbleBorder::ALIGN_EDGE_TO_ANCHOR_EDGE); 95 } 96 97 UpdatePosition(); 98 } 99 100 void InfoBubble::Hide() { 101 views::Widget* widget = GetWidget(); 102 if (widget && !widget->IsClosed()) 103 widget->Close(); 104 } 105 106 void InfoBubble::UpdatePosition() { 107 if (!widget_) 108 return; 109 110 if (!anchor_->GetVisibleBounds().IsEmpty()) { 111 SizeToContents(); 112 widget_->SetVisibilityChangedAnimationsEnabled(true); 113 widget_->ShowInactive(); 114 } else { 115 widget_->SetVisibilityChangedAnimationsEnabled(false); 116 widget_->Hide(); 117 } 118 } 119 120 views::NonClientFrameView* InfoBubble::CreateNonClientFrameView( 121 views::Widget* widget) { 122 DCHECK(!frame_); 123 frame_ = new InfoBubbleFrame(margins()); 124 frame_->set_available_bounds(anchor_widget()->GetWindowBoundsInScreen()); 125 frame_->SetBubbleBorder(new views::BubbleBorder(arrow(), shadow(), color())); 126 return frame_; 127 } 128 129 gfx::Size InfoBubble::GetPreferredSize() { 130 int pref_width = preferred_width_; 131 pref_width -= frame_->GetInsets().width(); 132 pref_width -= 2 * kBubbleBorderVisibleWidth; 133 return gfx::Size(pref_width, GetHeightForWidth(pref_width)); 134 } 135 136 void InfoBubble::OnWidgetDestroyed(views::Widget* widget) { 137 if (widget == widget_) 138 widget_ = NULL; 139 } 140 141 void InfoBubble::OnWidgetBoundsChanged(views::Widget* widget, 142 const gfx::Rect& new_bounds) { 143 views::BubbleDelegateView::OnWidgetBoundsChanged(widget, new_bounds); 144 if (anchor_widget() == widget) 145 frame_->set_available_bounds(widget->GetWindowBoundsInScreen()); 146 } 147 148 } // namespace autofill 149