• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "chrome/browser/ui/views/fullscreen_exit_bubble_views.h"
6 
7 #include "base/message_loop/message_loop.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "chrome/app/chrome_command_ids.h"
10 #include "chrome/browser/chrome_notification_types.h"
11 #include "chrome/browser/ui/fullscreen/fullscreen_controller.h"
12 #include "chrome/browser/ui/views/frame/browser_view.h"
13 #include "chrome/browser/ui/views/frame/immersive_mode_controller.h"
14 #include "chrome/browser/ui/views/frame/top_container_view.h"
15 #include "content/public/browser/notification_service.h"
16 #include "grit/generated_resources.h"
17 #include "grit/ui_strings.h"
18 #include "ui/base/l10n/l10n_util.h"
19 #include "ui/base/resource/resource_bundle.h"
20 #include "ui/events/keycodes/keyboard_codes.h"
21 #include "ui/gfx/animation/slide_animation.h"
22 #include "ui/gfx/canvas.h"
23 #include "ui/gfx/screen.h"
24 #include "ui/views/bubble/bubble_border.h"
25 #include "ui/views/controls/button/label_button.h"
26 #include "ui/views/controls/link.h"
27 #include "ui/views/controls/link_listener.h"
28 #include "ui/views/layout/box_layout.h"
29 #include "ui/views/layout/grid_layout.h"
30 #include "ui/views/view.h"
31 #include "ui/views/widget/widget.h"
32 #include "url/gurl.h"
33 
34 #if defined(OS_WIN)
35 #include "ui/base/l10n/l10n_util_win.h"
36 #endif
37 
38 // FullscreenExitView ----------------------------------------------------------
39 
40 namespace {
41 
42 // Space between the site info label and the buttons / link.
43 const int kMiddlePaddingPx = 30;
44 
45 class ButtonView : public views::View {
46  public:
47   ButtonView(views::ButtonListener* listener, int between_button_spacing);
48   virtual ~ButtonView();
49 
50   // Returns an empty size when the view is not visible.
51   virtual gfx::Size GetPreferredSize() const OVERRIDE;
52 
accept_button() const53   views::LabelButton* accept_button() const { return accept_button_; }
deny_button() const54   views::LabelButton* deny_button() const { return deny_button_; }
55 
56  private:
57   views::LabelButton* accept_button_;
58   views::LabelButton* deny_button_;
59 
60   DISALLOW_COPY_AND_ASSIGN(ButtonView);
61 };
62 
ButtonView(views::ButtonListener * listener,int between_button_spacing)63 ButtonView::ButtonView(views::ButtonListener* listener,
64                        int between_button_spacing)
65     : accept_button_(NULL),
66       deny_button_(NULL) {
67   accept_button_ = new views::LabelButton(listener, base::string16());
68   accept_button_->SetStyle(views::Button::STYLE_BUTTON);
69   accept_button_->SetFocusable(false);
70   AddChildView(accept_button_);
71 
72   deny_button_ = new views::LabelButton(listener, base::string16());
73   deny_button_->SetStyle(views::Button::STYLE_BUTTON);
74   deny_button_->SetFocusable(false);
75   AddChildView(deny_button_);
76 
77   SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0,
78                                         between_button_spacing));
79 }
80 
~ButtonView()81 ButtonView::~ButtonView() {
82 }
83 
GetPreferredSize() const84 gfx::Size ButtonView::GetPreferredSize() const {
85   return visible() ? views::View::GetPreferredSize() : gfx::Size();
86 }
87 
88 }  // namespace
89 
90 class FullscreenExitBubbleViews::FullscreenExitView
91     : public views::View,
92       public views::ButtonListener,
93       public views::LinkListener {
94  public:
95   FullscreenExitView(FullscreenExitBubbleViews* bubble,
96                      const base::string16& accelerator,
97                      const GURL& url,
98                      FullscreenExitBubbleType bubble_type);
99   virtual ~FullscreenExitView();
100 
101   // views::ButtonListener
102   virtual void ButtonPressed(views::Button* sender,
103                              const ui::Event& event) OVERRIDE;
104 
105   // views::LinkListener
106   virtual void LinkClicked(views::Link* source, int event_flags) OVERRIDE;
107 
108   void UpdateContent(const GURL& url, FullscreenExitBubbleType bubble_type);
109 
110  private:
111   FullscreenExitBubbleViews* bubble_;
112 
113   // Clickable hint text for exiting fullscreen mode.
114   views::Link* link_;
115   // Instruction for exiting mouse lock.
116   views::Label* mouse_lock_exit_instruction_;
117   // Informational label: 'www.foo.com has gone fullscreen'.
118   views::Label* message_label_;
119   ButtonView* button_view_;
120   const base::string16 browser_fullscreen_exit_accelerator_;
121 
122   DISALLOW_COPY_AND_ASSIGN(FullscreenExitView);
123 };
124 
FullscreenExitView(FullscreenExitBubbleViews * bubble,const base::string16 & accelerator,const GURL & url,FullscreenExitBubbleType bubble_type)125 FullscreenExitBubbleViews::FullscreenExitView::FullscreenExitView(
126     FullscreenExitBubbleViews* bubble,
127     const base::string16& accelerator,
128     const GURL& url,
129     FullscreenExitBubbleType bubble_type)
130     : bubble_(bubble),
131       link_(NULL),
132       mouse_lock_exit_instruction_(NULL),
133       message_label_(NULL),
134       button_view_(NULL),
135       browser_fullscreen_exit_accelerator_(accelerator) {
136   scoped_ptr<views::BubbleBorder> bubble_border(
137       new views::BubbleBorder(views::BubbleBorder::NONE,
138                               views::BubbleBorder::BIG_SHADOW,
139                               SK_ColorWHITE));
140   set_background(new views::BubbleBackground(bubble_border.get()));
141   SetBorder(bubble_border.PassAs<views::Border>());
142   SetFocusable(false);
143 
144   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
145   const gfx::FontList& medium_font_list =
146       rb.GetFontList(ui::ResourceBundle::MediumFont);
147   message_label_ = new views::Label(base::string16(), medium_font_list);
148 
149   mouse_lock_exit_instruction_ =
150       new views::Label(bubble_->GetInstructionText(), medium_font_list);
151   mouse_lock_exit_instruction_->set_collapse_when_hidden(true);
152 
153   link_ = new views::Link();
154   link_->set_collapse_when_hidden(true);
155   link_->SetFocusable(false);
156 #if defined(OS_CHROMEOS)
157   // On CrOS, the link text doesn't change, since it doesn't show the shortcut.
158   link_->SetText(l10n_util::GetStringUTF16(IDS_EXIT_FULLSCREEN_MODE));
159 #endif
160   link_->set_listener(this);
161   link_->SetFontList(medium_font_list);
162   link_->SetPressedColor(message_label_->enabled_color());
163   link_->SetEnabledColor(message_label_->enabled_color());
164   link_->SetVisible(false);
165 
166   link_->SetBackgroundColor(background()->get_color());
167   message_label_->SetBackgroundColor(background()->get_color());
168   mouse_lock_exit_instruction_->SetBackgroundColor(background()->get_color());
169 
170   button_view_ = new ButtonView(this, kPaddingPx);
171   button_view_->accept_button()->SetText(bubble->GetAllowButtonText());
172 
173   views::GridLayout* layout = new views::GridLayout(this);
174   views::ColumnSet* columns = layout->AddColumnSet(0);
175   columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::CENTER, 0,
176                      views::GridLayout::USE_PREF, 0, 0);
177   columns->AddPaddingColumn(1, kMiddlePaddingPx);
178   columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::CENTER, 0,
179                      views::GridLayout::USE_PREF, 0, 0);
180   columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::CENTER, 0,
181                      views::GridLayout::USE_PREF, 0, 0);
182   columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::CENTER, 0,
183                      views::GridLayout::USE_PREF, 0, 0);
184 
185   layout->StartRow(0, 0);
186   layout->AddView(message_label_);
187   layout->AddView(button_view_);
188   layout->AddView(mouse_lock_exit_instruction_);
189   layout->AddView(link_);
190 
191   gfx::Insets padding(kPaddingPx, kPaddingPx, kPaddingPx, kPaddingPx);
192   padding += GetInsets();
193   layout->SetInsets(padding);
194   SetLayoutManager(layout);
195 
196   UpdateContent(url, bubble_type);
197 }
198 
~FullscreenExitView()199 FullscreenExitBubbleViews::FullscreenExitView::~FullscreenExitView() {
200 }
201 
ButtonPressed(views::Button * sender,const ui::Event & event)202 void FullscreenExitBubbleViews::FullscreenExitView::ButtonPressed(
203     views::Button* sender,
204     const ui::Event& event) {
205   if (sender == button_view_->accept_button())
206     bubble_->Accept();
207   else
208     bubble_->Cancel();
209 }
210 
LinkClicked(views::Link * link,int event_flags)211 void FullscreenExitBubbleViews::FullscreenExitView::LinkClicked(
212     views::Link* link,
213     int event_flags) {
214   bubble_->ToggleFullscreen();
215 }
216 
UpdateContent(const GURL & url,FullscreenExitBubbleType bubble_type)217 void FullscreenExitBubbleViews::FullscreenExitView::UpdateContent(
218     const GURL& url,
219     FullscreenExitBubbleType bubble_type) {
220   DCHECK_NE(FEB_TYPE_NONE, bubble_type);
221 
222   message_label_->SetText(bubble_->GetCurrentMessageText());
223   if (fullscreen_bubble::ShowButtonsForType(bubble_type)) {
224     link_->SetVisible(false);
225     mouse_lock_exit_instruction_->SetVisible(false);
226     button_view_->SetVisible(true);
227     button_view_->deny_button()->SetText(bubble_->GetCurrentDenyButtonText());
228     button_view_->deny_button()->set_min_size(gfx::Size());
229   } else {
230     bool link_visible = true;
231     base::string16 accelerator;
232     if (bubble_type == FEB_TYPE_BROWSER_FULLSCREEN_EXIT_INSTRUCTION ||
233         bubble_type == FEB_TYPE_BROWSER_EXTENSION_FULLSCREEN_EXIT_INSTRUCTION) {
234       accelerator = browser_fullscreen_exit_accelerator_;
235     } else if (bubble_type == FEB_TYPE_FULLSCREEN_EXIT_INSTRUCTION) {
236       accelerator = l10n_util::GetStringUTF16(IDS_APP_ESC_KEY);
237     } else {
238       link_visible = false;
239     }
240 #if !defined(OS_CHROMEOS)
241     if (link_visible) {
242       link_->SetText(
243           l10n_util::GetStringUTF16(IDS_EXIT_FULLSCREEN_MODE) +
244           base::UTF8ToUTF16(" ") +
245           l10n_util::GetStringFUTF16(IDS_EXIT_FULLSCREEN_MODE_ACCELERATOR,
246               accelerator));
247     }
248 #endif
249     link_->SetVisible(link_visible);
250     mouse_lock_exit_instruction_->SetVisible(!link_visible);
251     button_view_->SetVisible(false);
252   }
253 }
254 
255 
256 // FullscreenExitBubbleViews ---------------------------------------------------
257 
FullscreenExitBubbleViews(BrowserView * browser_view,const GURL & url,FullscreenExitBubbleType bubble_type)258 FullscreenExitBubbleViews::FullscreenExitBubbleViews(
259     BrowserView* browser_view,
260     const GURL& url,
261     FullscreenExitBubbleType bubble_type)
262     : FullscreenExitBubble(browser_view->browser(), url, bubble_type),
263       browser_view_(browser_view),
264       popup_(NULL),
265       animation_(new gfx::SlideAnimation(this)),
266       animated_attribute_(ANIMATED_ATTRIBUTE_BOUNDS) {
267   animation_->Reset(1);
268 
269   // Create the contents view.
270   ui::Accelerator accelerator(ui::VKEY_UNKNOWN, ui::EF_NONE);
271   bool got_accelerator = browser_view_->GetWidget()->GetAccelerator(
272       IDC_FULLSCREEN, &accelerator);
273   DCHECK(got_accelerator);
274   view_ = new FullscreenExitView(
275       this, accelerator.GetShortcutText(), url, bubble_type_);
276 
277   // TODO(yzshen): Change to use the new views bubble, BubbleDelegateView.
278   // TODO(pkotwicz): When this becomes a views bubble, make sure that this
279   // bubble is ignored by ImmersiveModeControllerAsh::BubbleManager.
280   // Initialize the popup.
281   popup_ = new views::Widget;
282   views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
283   params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
284   params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
285   params.parent = browser_view_->GetWidget()->GetNativeView();
286   params.bounds = GetPopupRect(false);
287   popup_->Init(params);
288   gfx::Size size = GetPopupRect(true).size();
289   popup_->SetContentsView(view_);
290   // We set layout manager to NULL to prevent the widget from sizing its
291   // contents to the same size as itself. This prevents the widget contents from
292   // shrinking while we animate the height of the popup to give the impression
293   // that it is sliding off the top of the screen.
294   popup_->GetRootView()->SetLayoutManager(NULL);
295   view_->SetBounds(0, 0, size.width(), size.height());
296   popup_->ShowInactive();  // This does not activate the popup.
297 
298   popup_->AddObserver(this);
299 
300   registrar_.Add(
301       this,
302       chrome::NOTIFICATION_FULLSCREEN_CHANGED,
303       content::Source<FullscreenController>(
304           browser_view_->browser()->fullscreen_controller()));
305 
306   UpdateForImmersiveState();
307 }
308 
~FullscreenExitBubbleViews()309 FullscreenExitBubbleViews::~FullscreenExitBubbleViews() {
310   popup_->RemoveObserver(this);
311 
312   // This is tricky.  We may be in an ATL message handler stack, in which case
313   // the popup cannot be deleted yet.  We also can't set the popup's ownership
314   // model to NATIVE_WIDGET_OWNS_WIDGET because if the user closed the last tab
315   // while in fullscreen mode, Windows has already destroyed the popup HWND by
316   // the time we get here, and thus either the popup will already have been
317   // deleted (if we set this in our constructor) or the popup will never get
318   // another OnFinalMessage() call (if not, as currently).  So instead, we tell
319   // the popup to synchronously hide, and then asynchronously close and delete
320   // itself.
321   popup_->Close();
322   base::MessageLoop::current()->DeleteSoon(FROM_HERE, popup_);
323 }
324 
UpdateContent(const GURL & url,FullscreenExitBubbleType bubble_type)325 void FullscreenExitBubbleViews::UpdateContent(
326     const GURL& url,
327     FullscreenExitBubbleType bubble_type) {
328   DCHECK_NE(FEB_TYPE_NONE, bubble_type);
329   if (bubble_type_ == bubble_type && url_ == url)
330     return;
331 
332   url_ = url;
333   bubble_type_ = bubble_type;
334   view_->UpdateContent(url_, bubble_type_);
335 
336   gfx::Size size = GetPopupRect(true).size();
337   view_->SetSize(size);
338   popup_->SetBounds(GetPopupRect(false));
339   Show();
340 
341   // Stop watching the mouse even if UpdateMouseWatcher() will start watching
342   // it again so that the popup with the new content is visible for at least
343   // |kInitialDelayMs|.
344   StopWatchingMouse();
345 
346   UpdateMouseWatcher();
347 }
348 
RepositionIfVisible()349 void FullscreenExitBubbleViews::RepositionIfVisible() {
350   if (popup_->IsVisible())
351     UpdateBounds();
352 }
353 
UpdateMouseWatcher()354 void FullscreenExitBubbleViews::UpdateMouseWatcher() {
355   bool should_watch_mouse = false;
356   if (popup_->IsVisible())
357     should_watch_mouse = !fullscreen_bubble::ShowButtonsForType(bubble_type_);
358   else
359     should_watch_mouse = CanMouseTriggerSlideIn();
360 
361   if (should_watch_mouse == IsWatchingMouse())
362     return;
363 
364   if (should_watch_mouse)
365     StartWatchingMouse();
366   else
367     StopWatchingMouse();
368 }
369 
UpdateForImmersiveState()370 void FullscreenExitBubbleViews::UpdateForImmersiveState() {
371   AnimatedAttribute expected_animated_attribute =
372       browser_view_->immersive_mode_controller()->IsEnabled() ?
373           ANIMATED_ATTRIBUTE_OPACITY : ANIMATED_ATTRIBUTE_BOUNDS;
374   if (animated_attribute_ != expected_animated_attribute) {
375     // If an animation is currently in progress, skip to the end because
376     // switching the animated attribute midway through the animation looks
377     // weird.
378     animation_->End();
379 
380     animated_attribute_ = expected_animated_attribute;
381 
382     // We may have finished hiding |popup_|. However, the bounds animation
383     // assumes |popup_| has the opacity when it is fully shown and the opacity
384     // animation assumes |popup_| has the bounds when |popup_| is fully shown.
385     if (animated_attribute_ == ANIMATED_ATTRIBUTE_BOUNDS)
386       popup_->SetOpacity(255);
387     else
388       UpdateBounds();
389   }
390 
391   UpdateMouseWatcher();
392 }
393 
UpdateBounds()394 void FullscreenExitBubbleViews::UpdateBounds() {
395   gfx::Rect popup_rect(GetPopupRect(false));
396   if (!popup_rect.IsEmpty()) {
397     popup_->SetBounds(popup_rect);
398     view_->SetY(popup_rect.height() - view_->height());
399   }
400 }
401 
GetBrowserRootView() const402 views::View* FullscreenExitBubbleViews::GetBrowserRootView() const {
403   return browser_view_->GetWidget()->GetRootView();
404 }
405 
AnimationProgressed(const gfx::Animation * animation)406 void FullscreenExitBubbleViews::AnimationProgressed(
407     const gfx::Animation* animation) {
408   if (animated_attribute_ == ANIMATED_ATTRIBUTE_OPACITY) {
409     int opacity = animation_->CurrentValueBetween(0, 255);
410     if (opacity == 0) {
411       popup_->Hide();
412     } else {
413       popup_->Show();
414       popup_->SetOpacity(opacity);
415     }
416   } else {
417     if (GetPopupRect(false).IsEmpty()) {
418       popup_->Hide();
419     } else {
420       UpdateBounds();
421       popup_->Show();
422     }
423   }
424 }
425 
AnimationEnded(const gfx::Animation * animation)426 void FullscreenExitBubbleViews::AnimationEnded(
427     const gfx::Animation* animation) {
428   AnimationProgressed(animation);
429 }
430 
GetPopupRect(bool ignore_animation_state) const431 gfx::Rect FullscreenExitBubbleViews::GetPopupRect(
432     bool ignore_animation_state) const {
433   gfx::Size size(view_->GetPreferredSize());
434   // NOTE: don't use the bounds of the root_view_. On linux GTK changing window
435   // size is async. Instead we use the size of the screen.
436   gfx::Screen* screen =
437       gfx::Screen::GetScreenFor(browser_view_->GetWidget()->GetNativeView());
438   gfx::Rect screen_bounds = screen->GetDisplayNearestWindow(
439       browser_view_->GetWidget()->GetNativeView()).bounds();
440   int x = screen_bounds.x() + (screen_bounds.width() - size.width()) / 2;
441 
442   int top_container_bottom = screen_bounds.y();
443   if (browser_view_->immersive_mode_controller()->IsEnabled()) {
444     // Skip querying the top container height in non-immersive fullscreen
445     // because:
446     // - The top container height is always zero in non-immersive fullscreen.
447     // - Querying the top container height may return the height before entering
448     //   fullscreen because layout is disabled while entering fullscreen.
449     // A visual glitch due to the delayed layout is avoided in immersive
450     // fullscreen because entering fullscreen starts with the top container
451     // revealed. When revealed, the top container has the same height as before
452     // entering fullscreen.
453     top_container_bottom =
454         browser_view_->top_container()->GetBoundsInScreen().bottom();
455   }
456   int y = top_container_bottom + kPopupTopPx;
457 
458   if (!ignore_animation_state &&
459       animated_attribute_ == ANIMATED_ATTRIBUTE_BOUNDS) {
460     int total_height = size.height() + kPopupTopPx;
461     int popup_bottom = animation_->CurrentValueBetween(total_height, 0);
462     int y_offset = std::min(popup_bottom, kPopupTopPx);
463     size.set_height(size.height() - popup_bottom + y_offset);
464     y -= y_offset;
465   }
466   return gfx::Rect(gfx::Point(x, y), size);
467 }
468 
GetCursorScreenPoint()469 gfx::Point FullscreenExitBubbleViews::GetCursorScreenPoint() {
470   gfx::Point cursor_pos = gfx::Screen::GetScreenFor(
471       browser_view_->GetWidget()->GetNativeView())->GetCursorScreenPoint();
472   views::View::ConvertPointFromScreen(GetBrowserRootView(), &cursor_pos);
473   return cursor_pos;
474 }
475 
WindowContainsPoint(gfx::Point pos)476 bool FullscreenExitBubbleViews::WindowContainsPoint(gfx::Point pos) {
477   return GetBrowserRootView()->HitTestPoint(pos);
478 }
479 
IsWindowActive()480 bool FullscreenExitBubbleViews::IsWindowActive() {
481   return browser_view_->GetWidget()->IsActive();
482 }
483 
Hide()484 void FullscreenExitBubbleViews::Hide() {
485   animation_->SetSlideDuration(kSlideOutDurationMs);
486   animation_->Hide();
487 }
488 
Show()489 void FullscreenExitBubbleViews::Show() {
490   animation_->SetSlideDuration(kSlideInDurationMs);
491   animation_->Show();
492 }
493 
IsAnimating()494 bool FullscreenExitBubbleViews::IsAnimating() {
495   return animation_->is_animating();
496 }
497 
CanMouseTriggerSlideIn() const498 bool FullscreenExitBubbleViews::CanMouseTriggerSlideIn() const {
499   return !browser_view_->immersive_mode_controller()->IsEnabled();
500 }
501 
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)502 void FullscreenExitBubbleViews::Observe(
503     int type,
504     const content::NotificationSource& source,
505     const content::NotificationDetails& details) {
506   DCHECK_EQ(chrome::NOTIFICATION_FULLSCREEN_CHANGED, type);
507   UpdateForImmersiveState();
508 }
509 
OnWidgetVisibilityChanged(views::Widget * widget,bool visible)510 void FullscreenExitBubbleViews::OnWidgetVisibilityChanged(
511     views::Widget* widget,
512     bool visible) {
513   UpdateMouseWatcher();
514 }
515