// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/ui/views/bubble/bubble.h" #include <vector> #include "chrome/browser/ui/views/bubble/border_contents.h" #include "content/common/notification_service.h" #include "ui/base/animation/slide_animation.h" #include "ui/base/keycodes/keyboard_codes.h" #include "ui/gfx/color_utils.h" #include "views/layout/fill_layout.h" #include "views/widget/root_view.h" #include "views/widget/widget.h" #include "views/window/client_view.h" #include "views/window/window.h" #if defined(OS_CHROMEOS) #include "chrome/browser/chromeos/wm_ipc.h" #include "third_party/cros/chromeos_wm_ipc_enums.h" #endif #if defined(OS_WIN) #include "chrome/browser/ui/views/bubble/border_widget_win.h" #endif using std::vector; // How long the fade should last for. static const int kHideFadeDurationMS = 200; // Background color of the bubble. #if defined(OS_WIN) const SkColor Bubble::kBackgroundColor = color_utils::GetSysSkColor(COLOR_WINDOW); #else // TODO(beng): source from theme provider. const SkColor Bubble::kBackgroundColor = SK_ColorWHITE; #endif // BubbleDelegate --------------------------------------------------------- std::wstring BubbleDelegate::accessible_name() { return L""; } // Bubble ----------------------------------------------------------------- // static Bubble* Bubble::Show(views::Widget* parent, const gfx::Rect& position_relative_to, BubbleBorder::ArrowLocation arrow_location, views::View* contents, BubbleDelegate* delegate) { Bubble* bubble = new Bubble; bubble->InitBubble(parent, position_relative_to, arrow_location, contents, delegate); return bubble; } #if defined(OS_CHROMEOS) // static Bubble* Bubble::ShowFocusless( views::Widget* parent, const gfx::Rect& position_relative_to, BubbleBorder::ArrowLocation arrow_location, views::View* contents, BubbleDelegate* delegate, bool show_while_screen_is_locked) { Bubble* bubble = new Bubble(views::WidgetGtk::TYPE_POPUP, show_while_screen_is_locked); bubble->InitBubble(parent, position_relative_to, arrow_location, contents, delegate); return bubble; } #endif void Bubble::Close() { if (show_status_ != kOpen) return; show_status_ = kClosing; if (fade_away_on_close_) FadeOut(); else DoClose(false); } void Bubble::AnimationEnded(const ui::Animation* animation) { if (static_cast<int>(animation_->GetCurrentValue()) == 0) { // When fading out we just need to close the bubble at the end DoClose(false); } else { #if defined(OS_WIN) // When fading in we need to remove the layered window style flag, since // that style prevents some bubble content from working properly. SetWindowLong(GWL_EXSTYLE, GetWindowLong(GWL_EXSTYLE) & ~WS_EX_LAYERED); #endif } } void Bubble::AnimationProgressed(const ui::Animation* animation) { #if defined(OS_WIN) // Set the opacity for the main contents window. unsigned char opacity = static_cast<unsigned char>( animation_->GetCurrentValue() * 255); SetLayeredWindowAttributes(GetNativeView(), 0, static_cast<byte>(opacity), LWA_ALPHA); contents_->SchedulePaint(); // Also fade in/out the bubble border window. border_->SetOpacity(opacity); border_->border_contents()->SchedulePaint(); #else NOTIMPLEMENTED(); #endif } Bubble::Bubble() : #if defined(OS_LINUX) WidgetGtk(TYPE_WINDOW), border_contents_(NULL), #elif defined(OS_WIN) border_(NULL), #endif delegate_(NULL), show_status_(kOpen), fade_away_on_close_(false), #if defined(OS_CHROMEOS) show_while_screen_is_locked_(false), #endif arrow_location_(BubbleBorder::NONE), contents_(NULL) { } #if defined(OS_CHROMEOS) Bubble::Bubble(views::WidgetGtk::Type type, bool show_while_screen_is_locked) : WidgetGtk(type), border_contents_(NULL), delegate_(NULL), show_status_(kOpen), fade_away_on_close_(false), show_while_screen_is_locked_(show_while_screen_is_locked), arrow_location_(BubbleBorder::NONE), contents_(NULL) { } #endif Bubble::~Bubble() { } void Bubble::InitBubble(views::Widget* parent, const gfx::Rect& position_relative_to, BubbleBorder::ArrowLocation arrow_location, views::View* contents, BubbleDelegate* delegate) { delegate_ = delegate; position_relative_to_ = position_relative_to; arrow_location_ = arrow_location; contents_ = contents; // Create the main window. #if defined(OS_WIN) views::Window* parent_window = parent->GetWindow(); if (parent_window) parent_window->DisableInactiveRendering(); set_window_style(WS_POPUP | WS_CLIPCHILDREN); int extended_style = WS_EX_TOOLWINDOW; // During FadeIn we need to turn on the layered window style to deal with // transparency. This flag needs to be reset after fading in is complete. bool fade_in = delegate_ && delegate_->FadeInOnShow(); if (fade_in) extended_style |= WS_EX_LAYERED; set_window_ex_style(extended_style); DCHECK(!border_); border_ = new BorderWidgetWin(); if (fade_in) { border_->SetOpacity(0); SetOpacity(0); } border_->Init(CreateBorderContents(), parent->GetNativeView()); border_->border_contents()->SetBackgroundColor(kBackgroundColor); // We make the BorderWidgetWin the owner of the Bubble HWND, so that the // latter is displayed on top of the former. WidgetWin::Init(border_->GetNativeView(), gfx::Rect()); SetWindowText(GetNativeView(), delegate_->accessible_name().c_str()); #elif defined(OS_LINUX) MakeTransparent(); make_transient_to_parent(); WidgetGtk::InitWithWidget(parent, gfx::Rect()); #if defined(OS_CHROMEOS) { vector<int> params; params.push_back(show_while_screen_is_locked_ ? 1 : 0); chromeos::WmIpc::instance()->SetWindowType( GetNativeView(), chromeos::WM_IPC_WINDOW_CHROME_INFO_BUBBLE, ¶ms); } #endif #endif // Create a View to hold the contents of the main window. views::View* contents_view = new views::View; // We add |contents_view| to ourselves before the AddChildView() call below so // that when |contents| gets added, it will already have a widget, and thus // any NativeButtons it creates in ViewHierarchyChanged() will be functional // (e.g. calling SetChecked() on checkboxes is safe). SetContentsView(contents_view); // Adding |contents| as a child has to be done before we call // contents->GetPreferredSize() below, since some supplied views don't // actually initialize themselves until they're added to a hierarchy. contents_view->AddChildView(contents); // Calculate and set the bounds for all windows and views. gfx::Rect window_bounds; #if defined(OS_WIN) // Initialize and position the border window. window_bounds = border_->SizeAndGetBounds(position_relative_to, arrow_location, contents->GetPreferredSize()); // Make |contents| take up the entire contents view. contents_view->SetLayoutManager(new views::FillLayout); // Paint the background color behind the contents. contents_view->set_background( views::Background::CreateSolidBackground(kBackgroundColor)); #else // Create a view to paint the border and background. border_contents_ = CreateBorderContents(); border_contents_->Init(); border_contents_->SetBackgroundColor(kBackgroundColor); gfx::Rect contents_bounds; border_contents_->SizeAndGetBounds(position_relative_to, arrow_location, false, contents->GetPreferredSize(), &contents_bounds, &window_bounds); // This new view must be added before |contents| so it will paint under it. contents_view->AddChildViewAt(border_contents_, 0); // |contents_view| has no layout manager, so we have to explicitly position // its children. border_contents_->SetBoundsRect( gfx::Rect(gfx::Point(), window_bounds.size())); contents->SetBoundsRect(contents_bounds); #endif SetBounds(window_bounds); // Register the Escape accelerator for closing. GetFocusManager()->RegisterAccelerator( views::Accelerator(ui::VKEY_ESCAPE, false, false, false), this); // Done creating the bubble. NotificationService::current()->Notify(NotificationType::INFO_BUBBLE_CREATED, Source<Bubble>(this), NotificationService::NoDetails()); // Show the window. #if defined(OS_WIN) border_->ShowWindow(SW_SHOW); ShowWindow(SW_SHOW); if (fade_in) FadeIn(); #elif defined(OS_LINUX) views::WidgetGtk::Show(); #endif } BorderContents* Bubble::CreateBorderContents() { return new BorderContents(); } void Bubble::SizeToContents() { gfx::Rect window_bounds; #if defined(OS_WIN) // Initialize and position the border window. window_bounds = border_->SizeAndGetBounds(position_relative_to_, arrow_location_, contents_->GetPreferredSize()); #else gfx::Rect contents_bounds; border_contents_->SizeAndGetBounds(position_relative_to_, arrow_location_, false, contents_->GetPreferredSize(), &contents_bounds, &window_bounds); // |contents_view| has no layout manager, so we have to explicitly position // its children. border_contents_->SetBoundsRect( gfx::Rect(gfx::Point(), window_bounds.size())); contents_->SetBoundsRect(contents_bounds); #endif SetBounds(window_bounds); } #if defined(OS_WIN) void Bubble::OnActivate(UINT action, BOOL minimized, HWND window) { // The popup should close when it is deactivated. if (action == WA_INACTIVE) { Close(); } else if (action == WA_ACTIVE) { DCHECK(GetRootView()->has_children()); GetRootView()->GetChildViewAt(0)->RequestFocus(); } } #elif defined(OS_LINUX) void Bubble::IsActiveChanged() { if (!IsActive()) Close(); } #endif void Bubble::DoClose(bool closed_by_escape) { if (show_status_ == kClosed) return; GetFocusManager()->UnregisterAccelerator( views::Accelerator(ui::VKEY_ESCAPE, false, false, false), this); if (delegate_) delegate_->BubbleClosing(this, closed_by_escape); show_status_ = kClosed; #if defined(OS_WIN) border_->Close(); WidgetWin::Close(); #elif defined(OS_LINUX) WidgetGtk::Close(); #endif } void Bubble::FadeIn() { Fade(true); // |fade_in|. } void Bubble::FadeOut() { #if defined(OS_WIN) // The contents window cannot have the layered flag on by default, since its // content doesn't always work inside a layered window, but when animating it // is ok to set that style on the window for the purpose of fading it out. SetWindowLong(GWL_EXSTYLE, GetWindowLong(GWL_EXSTYLE) | WS_EX_LAYERED); // This must be the very next call, otherwise we can get flicker on close. SetLayeredWindowAttributes(GetNativeView(), 0, static_cast<byte>(255), LWA_ALPHA); #endif Fade(false); // |fade_in|. } void Bubble::Fade(bool fade_in) { animation_.reset(new ui::SlideAnimation(this)); animation_->SetSlideDuration(kHideFadeDurationMS); animation_->SetTweenType(ui::Tween::LINEAR); animation_->Reset(fade_in ? 0.0 : 1.0); if (fade_in) animation_->Show(); else animation_->Hide(); } bool Bubble::AcceleratorPressed(const views::Accelerator& accelerator) { if (!delegate_ || delegate_->CloseOnEscape()) { DoClose(true); return true; } return false; }