Home | History | Annotate | Download | only in views
      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() OVERRIDE;
     52 
     53   views::LabelButton* accept_button() const { return accept_button_; }
     54   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 
     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_NATIVE_TEXTBUTTON);
     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_NATIVE_TEXTBUTTON);
     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 
     81 ButtonView::~ButtonView() {
     82 }
     83 
     84 gfx::Size ButtonView::GetPreferredSize() {
     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 
    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   views::BubbleBorder* bubble_border = new views::BubbleBorder(
    137       views::BubbleBorder::NONE, views::BubbleBorder::BIG_SHADOW,
    138       SK_ColorWHITE);
    139   set_background(new views::BubbleBackground(bubble_border));
    140   set_border(bubble_border);
    141   SetFocusable(false);
    142 
    143   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
    144   message_label_ = new views::Label();
    145   message_label_->SetFont(rb.GetFont(ui::ResourceBundle::MediumFont));
    146 
    147   mouse_lock_exit_instruction_ = new views::Label();
    148   mouse_lock_exit_instruction_->set_collapse_when_hidden(true);
    149   mouse_lock_exit_instruction_->SetText(bubble_->GetInstructionText());
    150   mouse_lock_exit_instruction_->SetFont(
    151       rb.GetFont(ui::ResourceBundle::MediumFont));
    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_->SetFont(rb.GetFont(ui::ResourceBundle::MediumFont));
    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 
    199 FullscreenExitBubbleViews::FullscreenExitView::~FullscreenExitView() {
    200 }
    201 
    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 
    211 void FullscreenExitBubbleViews::FullscreenExitView::LinkClicked(
    212     views::Link* link,
    213     int event_flags) {
    214   bubble_->ToggleFullscreen();
    215 }
    216 
    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           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 
    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.can_activate = false;
    285   params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
    286   params.parent = browser_view_->GetWidget()->GetNativeView();
    287   params.bounds = GetPopupRect(false);
    288   popup_->Init(params);
    289   gfx::Size size = GetPopupRect(true).size();
    290   popup_->SetContentsView(view_);
    291   // We set layout manager to NULL to prevent the widget from sizing its
    292   // contents to the same size as itself. This prevents the widget contents from
    293   // shrinking while we animate the height of the popup to give the impression
    294   // that it is sliding off the top of the screen.
    295   popup_->GetRootView()->SetLayoutManager(NULL);
    296   view_->SetBounds(0, 0, size.width(), size.height());
    297   popup_->Show();  // This does not activate the popup.
    298 
    299   popup_->AddObserver(this);
    300 
    301   registrar_.Add(
    302       this,
    303       chrome::NOTIFICATION_FULLSCREEN_CHANGED,
    304       content::Source<FullscreenController>(
    305           browser_view_->browser()->fullscreen_controller()));
    306 
    307   UpdateForImmersiveState();
    308 }
    309 
    310 FullscreenExitBubbleViews::~FullscreenExitBubbleViews() {
    311   popup_->RemoveObserver(this);
    312 
    313   // This is tricky.  We may be in an ATL message handler stack, in which case
    314   // the popup cannot be deleted yet.  We also can't set the popup's ownership
    315   // model to NATIVE_WIDGET_OWNS_WIDGET because if the user closed the last tab
    316   // while in fullscreen mode, Windows has already destroyed the popup HWND by
    317   // the time we get here, and thus either the popup will already have been
    318   // deleted (if we set this in our constructor) or the popup will never get
    319   // another OnFinalMessage() call (if not, as currently).  So instead, we tell
    320   // the popup to synchronously hide, and then asynchronously close and delete
    321   // itself.
    322   popup_->Close();
    323   base::MessageLoop::current()->DeleteSoon(FROM_HERE, popup_);
    324 }
    325 
    326 void FullscreenExitBubbleViews::UpdateContent(
    327     const GURL& url,
    328     FullscreenExitBubbleType bubble_type) {
    329   DCHECK_NE(FEB_TYPE_NONE, bubble_type);
    330   if (bubble_type_ == bubble_type && url_ == url)
    331     return;
    332 
    333   url_ = url;
    334   bubble_type_ = bubble_type;
    335   view_->UpdateContent(url_, bubble_type_);
    336 
    337   gfx::Size size = GetPopupRect(true).size();
    338   view_->SetSize(size);
    339   popup_->SetBounds(GetPopupRect(false));
    340   Show();
    341 
    342   // Stop watching the mouse even if UpdateMouseWatcher() will start watching
    343   // it again so that the popup with the new content is visible for at least
    344   // |kInitialDelayMs|.
    345   StopWatchingMouse();
    346 
    347   UpdateMouseWatcher();
    348 }
    349 
    350 void FullscreenExitBubbleViews::RepositionIfVisible() {
    351   if (popup_->IsVisible())
    352     UpdateBounds();
    353 }
    354 
    355 void FullscreenExitBubbleViews::UpdateMouseWatcher() {
    356   bool should_watch_mouse = false;
    357   if (popup_->IsVisible())
    358     should_watch_mouse = !fullscreen_bubble::ShowButtonsForType(bubble_type_);
    359   else
    360     should_watch_mouse = CanMouseTriggerSlideIn();
    361 
    362   if (should_watch_mouse == IsWatchingMouse())
    363     return;
    364 
    365   if (should_watch_mouse)
    366     StartWatchingMouse();
    367   else
    368     StopWatchingMouse();
    369 }
    370 
    371 void FullscreenExitBubbleViews::UpdateForImmersiveState() {
    372   AnimatedAttribute expected_animated_attribute =
    373       browser_view_->immersive_mode_controller()->IsEnabled() ?
    374           ANIMATED_ATTRIBUTE_OPACITY : ANIMATED_ATTRIBUTE_BOUNDS;
    375   if (animated_attribute_ != expected_animated_attribute) {
    376     // If an animation is currently in progress, skip to the end because
    377     // switching the animated attribute midway through the animation looks
    378     // weird.
    379     animation_->End();
    380 
    381     animated_attribute_ = expected_animated_attribute;
    382 
    383     // We may have finished hiding |popup_|. However, the bounds animation
    384     // assumes |popup_| has the opacity when it is fully shown and the opacity
    385     // animation assumes |popup_| has the bounds when |popup_| is fully shown.
    386     if (animated_attribute_ == ANIMATED_ATTRIBUTE_BOUNDS)
    387       popup_->SetOpacity(255);
    388     else
    389       UpdateBounds();
    390   }
    391 
    392   UpdateMouseWatcher();
    393 }
    394 
    395 void FullscreenExitBubbleViews::UpdateBounds() {
    396   gfx::Rect popup_rect(GetPopupRect(false));
    397   if (!popup_rect.IsEmpty()) {
    398     popup_->SetBounds(popup_rect);
    399     view_->SetY(popup_rect.height() - view_->height());
    400   }
    401 }
    402 
    403 views::View* FullscreenExitBubbleViews::GetBrowserRootView() const {
    404   return browser_view_->GetWidget()->GetRootView();
    405 }
    406 
    407 void FullscreenExitBubbleViews::AnimationProgressed(
    408     const gfx::Animation* animation) {
    409   if (animated_attribute_ == ANIMATED_ATTRIBUTE_OPACITY) {
    410     int opacity = animation_->CurrentValueBetween(0, 255);
    411     if (opacity == 0) {
    412       popup_->Hide();
    413     } else {
    414       popup_->Show();
    415       popup_->SetOpacity(opacity);
    416     }
    417   } else {
    418     if (GetPopupRect(false).IsEmpty()) {
    419       popup_->Hide();
    420     } else {
    421       UpdateBounds();
    422       popup_->Show();
    423     }
    424   }
    425 }
    426 
    427 void FullscreenExitBubbleViews::AnimationEnded(
    428     const gfx::Animation* animation) {
    429   AnimationProgressed(animation);
    430 }
    431 
    432 gfx::Rect FullscreenExitBubbleViews::GetPopupRect(
    433     bool ignore_animation_state) const {
    434   gfx::Size size(view_->GetPreferredSize());
    435   // NOTE: don't use the bounds of the root_view_. On linux GTK changing window
    436   // size is async. Instead we use the size of the screen.
    437   gfx::Screen* screen =
    438       gfx::Screen::GetScreenFor(browser_view_->GetWidget()->GetNativeView());
    439   gfx::Rect screen_bounds = screen->GetDisplayNearestWindow(
    440       browser_view_->GetWidget()->GetNativeView()).bounds();
    441   int x = screen_bounds.x() + (screen_bounds.width() - size.width()) / 2;
    442 
    443   int top_container_bottom = screen_bounds.y();
    444   if (browser_view_->immersive_mode_controller()->IsEnabled()) {
    445     // Skip querying the top container height in non-immersive fullscreen
    446     // because:
    447     // - The top container height is always zero in non-immersive fullscreen.
    448     // - Querying the top container height may return the height before entering
    449     //   fullscreen because layout is disabled while entering fullscreen.
    450     // A visual glitch due to the delayed layout is avoided in immersive
    451     // fullscreen because entering fullscreen starts with the top container
    452     // revealed. When revealed, the top container has the same height as before
    453     // entering fullscreen.
    454     top_container_bottom =
    455         browser_view_->top_container()->GetBoundsInScreen().bottom();
    456   }
    457   int y = top_container_bottom + kPopupTopPx;
    458 
    459   if (!ignore_animation_state &&
    460       animated_attribute_ == ANIMATED_ATTRIBUTE_BOUNDS) {
    461     int total_height = size.height() + kPopupTopPx;
    462     int popup_bottom = animation_->CurrentValueBetween(total_height, 0);
    463     int y_offset = std::min(popup_bottom, kPopupTopPx);
    464     size.set_height(size.height() - popup_bottom + y_offset);
    465     y -= y_offset;
    466   }
    467   return gfx::Rect(gfx::Point(x, y), size);
    468 }
    469 
    470 gfx::Point FullscreenExitBubbleViews::GetCursorScreenPoint() {
    471   gfx::Point cursor_pos = gfx::Screen::GetScreenFor(
    472       browser_view_->GetWidget()->GetNativeView())->GetCursorScreenPoint();
    473   views::View::ConvertPointFromScreen(GetBrowserRootView(), &cursor_pos);
    474   return cursor_pos;
    475 }
    476 
    477 bool FullscreenExitBubbleViews::WindowContainsPoint(gfx::Point pos) {
    478   return GetBrowserRootView()->HitTestPoint(pos);
    479 }
    480 
    481 bool FullscreenExitBubbleViews::IsWindowActive() {
    482   return browser_view_->GetWidget()->IsActive();
    483 }
    484 
    485 void FullscreenExitBubbleViews::Hide() {
    486   animation_->SetSlideDuration(kSlideOutDurationMs);
    487   animation_->Hide();
    488 }
    489 
    490 void FullscreenExitBubbleViews::Show() {
    491   animation_->SetSlideDuration(kSlideInDurationMs);
    492   animation_->Show();
    493 }
    494 
    495 bool FullscreenExitBubbleViews::IsAnimating() {
    496   return animation_->is_animating();
    497 }
    498 
    499 bool FullscreenExitBubbleViews::CanMouseTriggerSlideIn() const {
    500   return !browser_view_->immersive_mode_controller()->IsEnabled();
    501 }
    502 
    503 void FullscreenExitBubbleViews::Observe(
    504     int type,
    505     const content::NotificationSource& source,
    506     const content::NotificationDetails& details) {
    507   DCHECK_EQ(chrome::NOTIFICATION_FULLSCREEN_CHANGED, type);
    508   UpdateForImmersiveState();
    509 }
    510 
    511 void FullscreenExitBubbleViews::OnWidgetVisibilityChanged(
    512     views::Widget* widget,
    513     bool visible) {
    514   UpdateMouseWatcher();
    515 }
    516