1 // Copyright (c) 2012 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 "ui/views/bubble/bubble_delegate.h" 6 7 #include "ui/accessibility/ax_view_state.h" 8 #include "ui/base/resource/resource_bundle.h" 9 #include "ui/gfx/color_utils.h" 10 #include "ui/gfx/rect.h" 11 #include "ui/native_theme/native_theme.h" 12 #include "ui/views/bubble/bubble_frame_view.h" 13 #include "ui/views/focus/view_storage.h" 14 #include "ui/views/widget/widget.h" 15 #include "ui/views/widget/widget_observer.h" 16 17 #if defined(OS_WIN) 18 #include "ui/base/win/shell.h" 19 #endif 20 21 // The defaut margin between the content and the inside border, in pixels. 22 static const int kDefaultMargin = 6; 23 24 namespace views { 25 26 namespace { 27 28 // Create a widget to host the bubble. 29 Widget* CreateBubbleWidget(BubbleDelegateView* bubble) { 30 Widget* bubble_widget = new Widget(); 31 Widget::InitParams bubble_params(Widget::InitParams::TYPE_BUBBLE); 32 bubble_params.delegate = bubble; 33 bubble_params.opacity = Widget::InitParams::TRANSLUCENT_WINDOW; 34 bubble_params.accept_events = bubble->accept_events(); 35 if (bubble->parent_window()) 36 bubble_params.parent = bubble->parent_window(); 37 else if (bubble->anchor_widget()) 38 bubble_params.parent = bubble->anchor_widget()->GetNativeView(); 39 bubble_params.activatable = bubble->CanActivate() ? 40 Widget::InitParams::ACTIVATABLE_YES : Widget::InitParams::ACTIVATABLE_NO; 41 bubble->OnBeforeBubbleWidgetInit(&bubble_params, bubble_widget); 42 bubble_widget->Init(bubble_params); 43 return bubble_widget; 44 } 45 46 } // namespace 47 48 BubbleDelegateView::BubbleDelegateView() 49 : close_on_esc_(true), 50 close_on_deactivate_(true), 51 anchor_view_storage_id_(ViewStorage::GetInstance()->CreateStorageID()), 52 anchor_widget_(NULL), 53 arrow_(BubbleBorder::TOP_LEFT), 54 shadow_(BubbleBorder::SMALL_SHADOW), 55 color_explicitly_set_(false), 56 margins_(kDefaultMargin, kDefaultMargin, kDefaultMargin, kDefaultMargin), 57 accept_events_(true), 58 border_accepts_events_(true), 59 adjust_if_offscreen_(true), 60 parent_window_(NULL) { 61 AddAccelerator(ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE)); 62 UpdateColorsFromTheme(GetNativeTheme()); 63 } 64 65 BubbleDelegateView::BubbleDelegateView( 66 View* anchor_view, 67 BubbleBorder::Arrow arrow) 68 : close_on_esc_(true), 69 close_on_deactivate_(true), 70 anchor_view_storage_id_(ViewStorage::GetInstance()->CreateStorageID()), 71 anchor_widget_(NULL), 72 arrow_(arrow), 73 shadow_(BubbleBorder::SMALL_SHADOW), 74 color_explicitly_set_(false), 75 margins_(kDefaultMargin, kDefaultMargin, kDefaultMargin, kDefaultMargin), 76 accept_events_(true), 77 border_accepts_events_(true), 78 adjust_if_offscreen_(true), 79 parent_window_(NULL) { 80 SetAnchorView(anchor_view); 81 AddAccelerator(ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE)); 82 UpdateColorsFromTheme(GetNativeTheme()); 83 } 84 85 BubbleDelegateView::~BubbleDelegateView() { 86 if (GetWidget()) 87 GetWidget()->RemoveObserver(this); 88 SetLayoutManager(NULL); 89 SetAnchorView(NULL); 90 } 91 92 // static 93 Widget* BubbleDelegateView::CreateBubble(BubbleDelegateView* bubble_delegate) { 94 bubble_delegate->Init(); 95 // Get the latest anchor widget from the anchor view at bubble creation time. 96 bubble_delegate->SetAnchorView(bubble_delegate->GetAnchorView()); 97 Widget* bubble_widget = CreateBubbleWidget(bubble_delegate); 98 99 #if defined(OS_WIN) 100 // If glass is enabled, the bubble is allowed to extend outside the bounds of 101 // the parent frame and let DWM handle compositing. If not, then we don't 102 // want to allow the bubble to extend the frame because it will be clipped. 103 bubble_delegate->set_adjust_if_offscreen(ui::win::IsAeroGlassEnabled()); 104 #elif defined(OS_LINUX) && !defined(OS_CHROMEOS) 105 // Linux clips bubble windows that extend outside their parent window bounds. 106 bubble_delegate->set_adjust_if_offscreen(false); 107 #endif 108 109 bubble_delegate->SizeToContents(); 110 bubble_widget->AddObserver(bubble_delegate); 111 return bubble_widget; 112 } 113 114 BubbleDelegateView* BubbleDelegateView::AsBubbleDelegate() { 115 return this; 116 } 117 118 bool BubbleDelegateView::ShouldShowCloseButton() const { 119 return false; 120 } 121 122 View* BubbleDelegateView::GetContentsView() { 123 return this; 124 } 125 126 NonClientFrameView* BubbleDelegateView::CreateNonClientFrameView( 127 Widget* widget) { 128 BubbleFrameView* frame = new BubbleFrameView(margins()); 129 // Note: In CreateBubble, the call to SizeToContents() will cause 130 // the relayout that this call requires. 131 frame->SetTitleFontList(GetTitleFontList()); 132 BubbleBorder::Arrow adjusted_arrow = arrow(); 133 if (base::i18n::IsRTL()) 134 adjusted_arrow = BubbleBorder::horizontal_mirror(adjusted_arrow); 135 frame->SetBubbleBorder(scoped_ptr<BubbleBorder>( 136 new BubbleBorder(adjusted_arrow, shadow(), color()))); 137 return frame; 138 } 139 140 void BubbleDelegateView::GetAccessibleState(ui::AXViewState* state) { 141 state->role = ui::AX_ROLE_DIALOG; 142 } 143 144 void BubbleDelegateView::OnWidgetDestroying(Widget* widget) { 145 if (anchor_widget() == widget) 146 SetAnchorView(NULL); 147 } 148 149 void BubbleDelegateView::OnWidgetVisibilityChanging(Widget* widget, 150 bool visible) { 151 #if defined(OS_WIN) 152 // On Windows we need to handle this before the bubble is visible or hidden. 153 // Please see the comment on the OnWidgetVisibilityChanging function. On 154 // other platforms it is fine to handle it after the bubble is shown/hidden. 155 HandleVisibilityChanged(widget, visible); 156 #endif 157 } 158 159 void BubbleDelegateView::OnWidgetVisibilityChanged(Widget* widget, 160 bool visible) { 161 #if !defined(OS_WIN) 162 HandleVisibilityChanged(widget, visible); 163 #endif 164 } 165 166 void BubbleDelegateView::OnWidgetActivationChanged(Widget* widget, 167 bool active) { 168 if (close_on_deactivate() && widget == GetWidget() && !active) 169 GetWidget()->Close(); 170 } 171 172 void BubbleDelegateView::OnWidgetBoundsChanged(Widget* widget, 173 const gfx::Rect& new_bounds) { 174 if (anchor_widget() == widget) 175 SizeToContents(); 176 } 177 178 View* BubbleDelegateView::GetAnchorView() const { 179 return ViewStorage::GetInstance()->RetrieveView(anchor_view_storage_id_); 180 } 181 182 gfx::Rect BubbleDelegateView::GetAnchorRect() const { 183 if (!GetAnchorView()) 184 return anchor_rect_; 185 186 anchor_rect_ = GetAnchorView()->GetBoundsInScreen(); 187 anchor_rect_.Inset(anchor_view_insets_); 188 return anchor_rect_; 189 } 190 191 void BubbleDelegateView::OnBeforeBubbleWidgetInit(Widget::InitParams* params, 192 Widget* widget) const { 193 } 194 195 void BubbleDelegateView::SetAlignment(BubbleBorder::BubbleAlignment alignment) { 196 GetBubbleFrameView()->bubble_border()->set_alignment(alignment); 197 SizeToContents(); 198 } 199 200 void BubbleDelegateView::SetArrowPaintType( 201 BubbleBorder::ArrowPaintType paint_type) { 202 GetBubbleFrameView()->bubble_border()->set_paint_arrow(paint_type); 203 SizeToContents(); 204 } 205 206 void BubbleDelegateView::OnAnchorBoundsChanged() { 207 SizeToContents(); 208 } 209 210 bool BubbleDelegateView::AcceleratorPressed( 211 const ui::Accelerator& accelerator) { 212 if (!close_on_esc() || accelerator.key_code() != ui::VKEY_ESCAPE) 213 return false; 214 GetWidget()->Close(); 215 return true; 216 } 217 218 void BubbleDelegateView::OnNativeThemeChanged(const ui::NativeTheme* theme) { 219 UpdateColorsFromTheme(theme); 220 } 221 222 void BubbleDelegateView::Init() {} 223 224 void BubbleDelegateView::SetAnchorView(View* anchor_view) { 225 // When the anchor view gets set the associated anchor widget might 226 // change as well. 227 if (!anchor_view || anchor_widget() != anchor_view->GetWidget()) { 228 if (anchor_widget()) { 229 anchor_widget_->RemoveObserver(this); 230 anchor_widget_ = NULL; 231 } 232 if (anchor_view) { 233 anchor_widget_ = anchor_view->GetWidget(); 234 if (anchor_widget_) 235 anchor_widget_->AddObserver(this); 236 } 237 } 238 239 // Remove the old storage item and set the new (if there is one). 240 ViewStorage* view_storage = ViewStorage::GetInstance(); 241 if (view_storage->RetrieveView(anchor_view_storage_id_)) 242 view_storage->RemoveView(anchor_view_storage_id_); 243 if (anchor_view) 244 view_storage->StoreView(anchor_view_storage_id_, anchor_view); 245 246 // Do not update anchoring for NULL views; this could indicate that our 247 // NativeWindow is being destroyed, so it would be dangerous for us to update 248 // our anchor bounds at that point. (It's safe to skip this, since if we were 249 // to update the bounds when |anchor_view| is NULL, the bubble won't move.) 250 if (anchor_view && GetWidget()) 251 OnAnchorBoundsChanged(); 252 } 253 254 void BubbleDelegateView::SetAnchorRect(const gfx::Rect& rect) { 255 anchor_rect_ = rect; 256 if (GetWidget()) 257 OnAnchorBoundsChanged(); 258 } 259 260 void BubbleDelegateView::SizeToContents() { 261 GetWidget()->SetBounds(GetBubbleBounds()); 262 } 263 264 BubbleFrameView* BubbleDelegateView::GetBubbleFrameView() const { 265 const NonClientView* view = 266 GetWidget() ? GetWidget()->non_client_view() : NULL; 267 return view ? static_cast<BubbleFrameView*>(view->frame_view()) : NULL; 268 } 269 270 gfx::Rect BubbleDelegateView::GetBubbleBounds() { 271 // The argument rect has its origin at the bubble's arrow anchor point; 272 // its size is the preferred size of the bubble's client view (this view). 273 bool anchor_minimized = anchor_widget() && anchor_widget()->IsMinimized(); 274 return GetBubbleFrameView()->GetUpdatedWindowBounds(GetAnchorRect(), 275 GetPreferredSize(), adjust_if_offscreen_ && !anchor_minimized); 276 } 277 278 const gfx::FontList& BubbleDelegateView::GetTitleFontList() const { 279 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 280 return rb.GetFontList(ui::ResourceBundle::MediumFont); 281 } 282 283 284 void BubbleDelegateView::UpdateColorsFromTheme(const ui::NativeTheme* theme) { 285 if (!color_explicitly_set_) 286 color_ = theme->GetSystemColor(ui::NativeTheme::kColorId_DialogBackground); 287 set_background(Background::CreateSolidBackground(color())); 288 BubbleFrameView* frame_view = GetBubbleFrameView(); 289 if (frame_view) 290 frame_view->bubble_border()->set_background_color(color()); 291 } 292 293 void BubbleDelegateView::HandleVisibilityChanged(Widget* widget, bool visible) { 294 if (widget == GetWidget() && anchor_widget() && 295 anchor_widget()->GetTopLevelWidget()) { 296 if (visible) 297 anchor_widget()->GetTopLevelWidget()->DisableInactiveRendering(); 298 else 299 anchor_widget()->GetTopLevelWidget()->EnableInactiveRendering(); 300 } 301 } 302 303 } // namespace views 304