1 // Copyright (c) 2011 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/bubble/bubble.h" 6 7 #include <vector> 8 9 #include "chrome/browser/ui/views/bubble/border_contents.h" 10 #include "content/common/notification_service.h" 11 #include "ui/base/animation/slide_animation.h" 12 #include "ui/base/keycodes/keyboard_codes.h" 13 #include "ui/gfx/color_utils.h" 14 #include "views/layout/fill_layout.h" 15 #include "views/widget/root_view.h" 16 #include "views/widget/widget.h" 17 #include "views/window/client_view.h" 18 #include "views/window/window.h" 19 20 #if defined(OS_CHROMEOS) 21 #include "chrome/browser/chromeos/wm_ipc.h" 22 #include "third_party/cros/chromeos_wm_ipc_enums.h" 23 #endif 24 25 #if defined(OS_WIN) 26 #include "chrome/browser/ui/views/bubble/border_widget_win.h" 27 #endif 28 29 using std::vector; 30 31 // How long the fade should last for. 32 static const int kHideFadeDurationMS = 200; 33 34 // Background color of the bubble. 35 #if defined(OS_WIN) 36 const SkColor Bubble::kBackgroundColor = 37 color_utils::GetSysSkColor(COLOR_WINDOW); 38 #else 39 // TODO(beng): source from theme provider. 40 const SkColor Bubble::kBackgroundColor = SK_ColorWHITE; 41 #endif 42 43 // BubbleDelegate --------------------------------------------------------- 44 45 std::wstring BubbleDelegate::accessible_name() { 46 return L""; 47 } 48 49 // Bubble ----------------------------------------------------------------- 50 51 // static 52 Bubble* Bubble::Show(views::Widget* parent, 53 const gfx::Rect& position_relative_to, 54 BubbleBorder::ArrowLocation arrow_location, 55 views::View* contents, 56 BubbleDelegate* delegate) { 57 Bubble* bubble = new Bubble; 58 bubble->InitBubble(parent, position_relative_to, arrow_location, 59 contents, delegate); 60 return bubble; 61 } 62 63 #if defined(OS_CHROMEOS) 64 // static 65 Bubble* Bubble::ShowFocusless( 66 views::Widget* parent, 67 const gfx::Rect& position_relative_to, 68 BubbleBorder::ArrowLocation arrow_location, 69 views::View* contents, 70 BubbleDelegate* delegate, 71 bool show_while_screen_is_locked) { 72 Bubble* bubble = new Bubble(views::WidgetGtk::TYPE_POPUP, 73 show_while_screen_is_locked); 74 bubble->InitBubble(parent, position_relative_to, arrow_location, 75 contents, delegate); 76 return bubble; 77 } 78 #endif 79 80 void Bubble::Close() { 81 if (show_status_ != kOpen) 82 return; 83 84 show_status_ = kClosing; 85 86 if (fade_away_on_close_) 87 FadeOut(); 88 else 89 DoClose(false); 90 } 91 92 void Bubble::AnimationEnded(const ui::Animation* animation) { 93 if (static_cast<int>(animation_->GetCurrentValue()) == 0) { 94 // When fading out we just need to close the bubble at the end 95 DoClose(false); 96 } else { 97 #if defined(OS_WIN) 98 // When fading in we need to remove the layered window style flag, since 99 // that style prevents some bubble content from working properly. 100 SetWindowLong(GWL_EXSTYLE, GetWindowLong(GWL_EXSTYLE) & ~WS_EX_LAYERED); 101 #endif 102 } 103 } 104 105 void Bubble::AnimationProgressed(const ui::Animation* animation) { 106 #if defined(OS_WIN) 107 // Set the opacity for the main contents window. 108 unsigned char opacity = static_cast<unsigned char>( 109 animation_->GetCurrentValue() * 255); 110 SetLayeredWindowAttributes(GetNativeView(), 0, 111 static_cast<byte>(opacity), LWA_ALPHA); 112 contents_->SchedulePaint(); 113 114 // Also fade in/out the bubble border window. 115 border_->SetOpacity(opacity); 116 border_->border_contents()->SchedulePaint(); 117 #else 118 NOTIMPLEMENTED(); 119 #endif 120 } 121 122 Bubble::Bubble() 123 : 124 #if defined(OS_LINUX) 125 WidgetGtk(TYPE_WINDOW), 126 border_contents_(NULL), 127 #elif defined(OS_WIN) 128 border_(NULL), 129 #endif 130 delegate_(NULL), 131 show_status_(kOpen), 132 fade_away_on_close_(false), 133 #if defined(OS_CHROMEOS) 134 show_while_screen_is_locked_(false), 135 #endif 136 arrow_location_(BubbleBorder::NONE), 137 contents_(NULL) { 138 } 139 140 #if defined(OS_CHROMEOS) 141 Bubble::Bubble(views::WidgetGtk::Type type, bool show_while_screen_is_locked) 142 : WidgetGtk(type), 143 border_contents_(NULL), 144 delegate_(NULL), 145 show_status_(kOpen), 146 fade_away_on_close_(false), 147 show_while_screen_is_locked_(show_while_screen_is_locked), 148 arrow_location_(BubbleBorder::NONE), 149 contents_(NULL) { 150 } 151 #endif 152 153 Bubble::~Bubble() { 154 } 155 156 void Bubble::InitBubble(views::Widget* parent, 157 const gfx::Rect& position_relative_to, 158 BubbleBorder::ArrowLocation arrow_location, 159 views::View* contents, 160 BubbleDelegate* delegate) { 161 delegate_ = delegate; 162 position_relative_to_ = position_relative_to; 163 arrow_location_ = arrow_location; 164 contents_ = contents; 165 166 // Create the main window. 167 #if defined(OS_WIN) 168 views::Window* parent_window = parent->GetWindow(); 169 if (parent_window) 170 parent_window->DisableInactiveRendering(); 171 set_window_style(WS_POPUP | WS_CLIPCHILDREN); 172 int extended_style = WS_EX_TOOLWINDOW; 173 // During FadeIn we need to turn on the layered window style to deal with 174 // transparency. This flag needs to be reset after fading in is complete. 175 bool fade_in = delegate_ && delegate_->FadeInOnShow(); 176 if (fade_in) 177 extended_style |= WS_EX_LAYERED; 178 set_window_ex_style(extended_style); 179 180 DCHECK(!border_); 181 border_ = new BorderWidgetWin(); 182 183 if (fade_in) { 184 border_->SetOpacity(0); 185 SetOpacity(0); 186 } 187 188 border_->Init(CreateBorderContents(), parent->GetNativeView()); 189 border_->border_contents()->SetBackgroundColor(kBackgroundColor); 190 191 // We make the BorderWidgetWin the owner of the Bubble HWND, so that the 192 // latter is displayed on top of the former. 193 WidgetWin::Init(border_->GetNativeView(), gfx::Rect()); 194 195 SetWindowText(GetNativeView(), delegate_->accessible_name().c_str()); 196 #elif defined(OS_LINUX) 197 MakeTransparent(); 198 make_transient_to_parent(); 199 WidgetGtk::InitWithWidget(parent, gfx::Rect()); 200 #if defined(OS_CHROMEOS) 201 { 202 vector<int> params; 203 params.push_back(show_while_screen_is_locked_ ? 1 : 0); 204 chromeos::WmIpc::instance()->SetWindowType( 205 GetNativeView(), 206 chromeos::WM_IPC_WINDOW_CHROME_INFO_BUBBLE, 207 ¶ms); 208 } 209 #endif 210 #endif 211 212 // Create a View to hold the contents of the main window. 213 views::View* contents_view = new views::View; 214 // We add |contents_view| to ourselves before the AddChildView() call below so 215 // that when |contents| gets added, it will already have a widget, and thus 216 // any NativeButtons it creates in ViewHierarchyChanged() will be functional 217 // (e.g. calling SetChecked() on checkboxes is safe). 218 SetContentsView(contents_view); 219 // Adding |contents| as a child has to be done before we call 220 // contents->GetPreferredSize() below, since some supplied views don't 221 // actually initialize themselves until they're added to a hierarchy. 222 contents_view->AddChildView(contents); 223 224 // Calculate and set the bounds for all windows and views. 225 gfx::Rect window_bounds; 226 227 #if defined(OS_WIN) 228 // Initialize and position the border window. 229 window_bounds = border_->SizeAndGetBounds(position_relative_to, 230 arrow_location, 231 contents->GetPreferredSize()); 232 233 // Make |contents| take up the entire contents view. 234 contents_view->SetLayoutManager(new views::FillLayout); 235 236 // Paint the background color behind the contents. 237 contents_view->set_background( 238 views::Background::CreateSolidBackground(kBackgroundColor)); 239 #else 240 // Create a view to paint the border and background. 241 border_contents_ = CreateBorderContents(); 242 border_contents_->Init(); 243 border_contents_->SetBackgroundColor(kBackgroundColor); 244 gfx::Rect contents_bounds; 245 border_contents_->SizeAndGetBounds(position_relative_to, 246 arrow_location, false, contents->GetPreferredSize(), 247 &contents_bounds, &window_bounds); 248 // This new view must be added before |contents| so it will paint under it. 249 contents_view->AddChildViewAt(border_contents_, 0); 250 251 // |contents_view| has no layout manager, so we have to explicitly position 252 // its children. 253 border_contents_->SetBoundsRect( 254 gfx::Rect(gfx::Point(), window_bounds.size())); 255 contents->SetBoundsRect(contents_bounds); 256 #endif 257 SetBounds(window_bounds); 258 259 // Register the Escape accelerator for closing. 260 GetFocusManager()->RegisterAccelerator( 261 views::Accelerator(ui::VKEY_ESCAPE, false, false, false), this); 262 263 // Done creating the bubble. 264 NotificationService::current()->Notify(NotificationType::INFO_BUBBLE_CREATED, 265 Source<Bubble>(this), 266 NotificationService::NoDetails()); 267 268 // Show the window. 269 #if defined(OS_WIN) 270 border_->ShowWindow(SW_SHOW); 271 ShowWindow(SW_SHOW); 272 if (fade_in) 273 FadeIn(); 274 #elif defined(OS_LINUX) 275 views::WidgetGtk::Show(); 276 #endif 277 } 278 279 BorderContents* Bubble::CreateBorderContents() { 280 return new BorderContents(); 281 } 282 283 void Bubble::SizeToContents() { 284 gfx::Rect window_bounds; 285 286 #if defined(OS_WIN) 287 // Initialize and position the border window. 288 window_bounds = border_->SizeAndGetBounds(position_relative_to_, 289 arrow_location_, 290 contents_->GetPreferredSize()); 291 #else 292 gfx::Rect contents_bounds; 293 border_contents_->SizeAndGetBounds(position_relative_to_, 294 arrow_location_, false, contents_->GetPreferredSize(), 295 &contents_bounds, &window_bounds); 296 // |contents_view| has no layout manager, so we have to explicitly position 297 // its children. 298 border_contents_->SetBoundsRect( 299 gfx::Rect(gfx::Point(), window_bounds.size())); 300 contents_->SetBoundsRect(contents_bounds); 301 #endif 302 SetBounds(window_bounds); 303 } 304 305 #if defined(OS_WIN) 306 void Bubble::OnActivate(UINT action, BOOL minimized, HWND window) { 307 // The popup should close when it is deactivated. 308 if (action == WA_INACTIVE) { 309 Close(); 310 } else if (action == WA_ACTIVE) { 311 DCHECK(GetRootView()->has_children()); 312 GetRootView()->GetChildViewAt(0)->RequestFocus(); 313 } 314 } 315 #elif defined(OS_LINUX) 316 void Bubble::IsActiveChanged() { 317 if (!IsActive()) 318 Close(); 319 } 320 #endif 321 322 void Bubble::DoClose(bool closed_by_escape) { 323 if (show_status_ == kClosed) 324 return; 325 326 GetFocusManager()->UnregisterAccelerator( 327 views::Accelerator(ui::VKEY_ESCAPE, false, false, false), this); 328 if (delegate_) 329 delegate_->BubbleClosing(this, closed_by_escape); 330 show_status_ = kClosed; 331 #if defined(OS_WIN) 332 border_->Close(); 333 WidgetWin::Close(); 334 #elif defined(OS_LINUX) 335 WidgetGtk::Close(); 336 #endif 337 } 338 339 void Bubble::FadeIn() { 340 Fade(true); // |fade_in|. 341 } 342 343 void Bubble::FadeOut() { 344 #if defined(OS_WIN) 345 // The contents window cannot have the layered flag on by default, since its 346 // content doesn't always work inside a layered window, but when animating it 347 // is ok to set that style on the window for the purpose of fading it out. 348 SetWindowLong(GWL_EXSTYLE, GetWindowLong(GWL_EXSTYLE) | WS_EX_LAYERED); 349 // This must be the very next call, otherwise we can get flicker on close. 350 SetLayeredWindowAttributes(GetNativeView(), 0, 351 static_cast<byte>(255), LWA_ALPHA); 352 #endif 353 354 Fade(false); // |fade_in|. 355 } 356 357 void Bubble::Fade(bool fade_in) { 358 animation_.reset(new ui::SlideAnimation(this)); 359 animation_->SetSlideDuration(kHideFadeDurationMS); 360 animation_->SetTweenType(ui::Tween::LINEAR); 361 362 animation_->Reset(fade_in ? 0.0 : 1.0); 363 if (fade_in) 364 animation_->Show(); 365 else 366 animation_->Hide(); 367 } 368 369 bool Bubble::AcceleratorPressed(const views::Accelerator& accelerator) { 370 if (!delegate_ || delegate_->CloseOnEscape()) { 371 DoClose(true); 372 return true; 373 } 374 return false; 375 } 376