Home | History | Annotate | Download | only in location_bar
      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/location_bar/zoom_bubble_view.h"
      6 
      7 #include "base/i18n/rtl.h"
      8 #include "chrome/browser/chrome_notification_types.h"
      9 #include "chrome/browser/chrome_page_zoom.h"
     10 #include "chrome/browser/ui/browser.h"
     11 #include "chrome/browser/ui/browser_finder.h"
     12 #include "chrome/browser/ui/browser_window.h"
     13 #include "chrome/browser/ui/views/frame/browser_view.h"
     14 #include "chrome/browser/ui/views/location_bar/location_bar_view.h"
     15 #include "chrome/browser/ui/views/location_bar/zoom_view.h"
     16 #include "chrome/browser/ui/zoom/zoom_controller.h"
     17 #include "content/public/browser/notification_source.h"
     18 #include "content/public/browser/web_contents_view.h"
     19 #include "grit/generated_resources.h"
     20 #include "ui/base/l10n/l10n_util.h"
     21 #include "ui/base/resource/resource_bundle.h"
     22 #include "ui/views/controls/button/label_button.h"
     23 #include "ui/views/controls/separator.h"
     24 #include "ui/views/layout/box_layout.h"
     25 #include "ui/views/layout/layout_constants.h"
     26 #include "ui/views/widget/widget.h"
     27 
     28 namespace {
     29 
     30 // The number of milliseconds the bubble should stay on the screen if it will
     31 // close automatically.
     32 const int kBubbleCloseDelay = 1500;
     33 
     34 // The bubble's padding from the screen edge, used in fullscreen.
     35 const int kFullscreenPaddingEnd = 20;
     36 
     37 }  // namespace
     38 
     39 // static
     40 ZoomBubbleView* ZoomBubbleView::zoom_bubble_ = NULL;
     41 
     42 // static
     43 void ZoomBubbleView::ShowBubble(content::WebContents* web_contents,
     44                                 bool auto_close) {
     45   Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
     46   DCHECK(browser && browser->window() && browser->fullscreen_controller());
     47 
     48   BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
     49   bool is_fullscreen = browser_view->IsFullscreen();
     50   bool anchor_to_view = !is_fullscreen ||
     51       browser_view->immersive_mode_controller()->IsRevealed();
     52   views::View* anchor_view = anchor_to_view ?
     53       browser_view->GetLocationBarView()->zoom_view() : NULL;
     54 
     55   // If the bubble is already showing in this window and its |auto_close_| value
     56   // is equal to |auto_close|, the bubble can be reused and only the label text
     57   // needs to be updated.
     58   if (zoom_bubble_ &&
     59       zoom_bubble_->anchor_view() == anchor_view &&
     60       zoom_bubble_->auto_close_ == auto_close) {
     61     zoom_bubble_->Refresh();
     62   } else {
     63     // If the bubble is already showing but its |auto_close_| value is not equal
     64     // to |auto_close|, the bubble's focus properties must change, so the
     65     // current bubble must be closed and a new one created.
     66     CloseBubble();
     67 
     68     zoom_bubble_ = new ZoomBubbleView(anchor_view,
     69                                       web_contents,
     70                                       auto_close,
     71                                       browser_view->immersive_mode_controller(),
     72                                       browser->fullscreen_controller());
     73 
     74     // If we do not have an anchor view, parent the bubble to the content area.
     75     if (!anchor_to_view) {
     76       zoom_bubble_->set_parent_window(
     77           web_contents->GetView()->GetTopLevelNativeWindow());
     78     }
     79 
     80     views::BubbleDelegateView::CreateBubble(zoom_bubble_);
     81 
     82     // Adjust for fullscreen after creation as it relies on the content size.
     83     if (is_fullscreen)
     84       zoom_bubble_->AdjustForFullscreen(browser_view->GetBoundsInScreen());
     85 
     86     zoom_bubble_->GetWidget()->Show();
     87   }
     88 }
     89 
     90 // static
     91 void ZoomBubbleView::CloseBubble() {
     92   if (zoom_bubble_)
     93     zoom_bubble_->Close();
     94 }
     95 
     96 // static
     97 bool ZoomBubbleView::IsShowing() {
     98   // The bubble may be in the process of closing.
     99   return zoom_bubble_ != NULL && zoom_bubble_->GetWidget()->IsVisible();
    100 }
    101 
    102 // static
    103 const ZoomBubbleView* ZoomBubbleView::GetZoomBubbleForTest() {
    104   return zoom_bubble_;
    105 }
    106 
    107 ZoomBubbleView::ZoomBubbleView(
    108     views::View* anchor_view,
    109     content::WebContents* web_contents,
    110     bool auto_close,
    111     ImmersiveModeController* immersive_mode_controller,
    112     FullscreenController* fullscreen_controller)
    113     : BubbleDelegateView(anchor_view, anchor_view ?
    114           views::BubbleBorder::TOP_RIGHT : views::BubbleBorder::NONE),
    115       label_(NULL),
    116       web_contents_(web_contents),
    117       auto_close_(auto_close),
    118       immersive_mode_controller_(immersive_mode_controller) {
    119   // Compensate for built-in vertical padding in the anchor view's image.
    120   set_anchor_view_insets(gfx::Insets(5, 0, 5, 0));
    121   set_use_focusless(auto_close);
    122   set_notify_enter_exit_on_child(true);
    123 
    124   // Add observers to close the bubble if the fullscreen state or immersive
    125   // fullscreen revealed state changes.
    126   registrar_.Add(this,
    127                  chrome::NOTIFICATION_FULLSCREEN_CHANGED,
    128                  content::Source<FullscreenController>(fullscreen_controller));
    129   immersive_mode_controller_->AddObserver(this);
    130 }
    131 
    132 ZoomBubbleView::~ZoomBubbleView() {
    133   if (immersive_mode_controller_)
    134     immersive_mode_controller_->RemoveObserver(this);
    135 }
    136 
    137 void ZoomBubbleView::AdjustForFullscreen(const gfx::Rect& screen_bounds) {
    138   if (anchor_view())
    139     return;
    140 
    141   // TODO(dbeam): should RTL logic be done in views::BubbleDelegateView?
    142   const size_t bubble_half_width = width() / 2;
    143   const int x_pos = base::i18n::IsRTL() ?
    144       screen_bounds.x() + bubble_half_width + kFullscreenPaddingEnd :
    145       screen_bounds.right() - bubble_half_width - kFullscreenPaddingEnd;
    146   set_anchor_rect(gfx::Rect(x_pos, screen_bounds.y(), 0, 0));
    147 
    148   // Used to update |views::BubbleDelegate::anchor_rect_| in a semi-hacky way.
    149   // TODO(dbeam): update only the bounds of this view or its border or frame.
    150   SizeToContents();
    151 }
    152 
    153 void ZoomBubbleView::Refresh() {
    154   ZoomController* zoom_controller =
    155       ZoomController::FromWebContents(web_contents_);
    156   int zoom_percent = zoom_controller->zoom_percent();
    157   label_->SetText(
    158       l10n_util::GetStringFUTF16Int(IDS_TOOLTIP_ZOOM, zoom_percent));
    159   StartTimerIfNecessary();
    160 }
    161 
    162 void ZoomBubbleView::Close() {
    163   GetWidget()->Close();
    164 }
    165 
    166 void ZoomBubbleView::StartTimerIfNecessary() {
    167   if (auto_close_) {
    168     if (timer_.IsRunning()) {
    169       timer_.Reset();
    170     } else {
    171       timer_.Start(
    172           FROM_HERE,
    173           base::TimeDelta::FromMilliseconds(kBubbleCloseDelay),
    174           this,
    175           &ZoomBubbleView::Close);
    176     }
    177   }
    178 }
    179 
    180 void ZoomBubbleView::StopTimer() {
    181   timer_.Stop();
    182 }
    183 
    184 void ZoomBubbleView::OnMouseEntered(const ui::MouseEvent& event) {
    185   StopTimer();
    186 }
    187 
    188 void ZoomBubbleView::OnMouseExited(const ui::MouseEvent& event) {
    189   StartTimerIfNecessary();
    190 }
    191 
    192 void ZoomBubbleView::OnGestureEvent(ui::GestureEvent* event) {
    193   if (!zoom_bubble_ || !zoom_bubble_->auto_close_ ||
    194       event->type() != ui::ET_GESTURE_TAP) {
    195     return;
    196   }
    197 
    198   // If an auto-closing bubble was tapped, show a non-auto-closing bubble in
    199   // its place.
    200   ShowBubble(zoom_bubble_->web_contents_, false);
    201   event->SetHandled();
    202 }
    203 
    204 void ZoomBubbleView::ButtonPressed(views::Button* sender,
    205                                    const ui::Event& event) {
    206   chrome_page_zoom::Zoom(web_contents_, content::PAGE_ZOOM_RESET);
    207 }
    208 
    209 void ZoomBubbleView::Init() {
    210   SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical,
    211       0, 0, views::kRelatedControlVerticalSpacing));
    212 
    213   ZoomController* zoom_controller =
    214       ZoomController::FromWebContents(web_contents_);
    215   int zoom_percent = zoom_controller->zoom_percent();
    216   label_ = new views::Label(
    217       l10n_util::GetStringFUTF16Int(IDS_TOOLTIP_ZOOM, zoom_percent));
    218   label_->SetFont(
    219       ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::MediumFont));
    220   AddChildView(label_);
    221 
    222   views::LabelButton* set_default_button = new views::LabelButton(
    223       this, l10n_util::GetStringUTF16(IDS_ZOOM_SET_DEFAULT));
    224   set_default_button->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
    225   set_default_button->SetHorizontalAlignment(gfx::ALIGN_CENTER);
    226   AddChildView(set_default_button);
    227 
    228   StartTimerIfNecessary();
    229 }
    230 
    231 void ZoomBubbleView::Observe(int type,
    232                              const content::NotificationSource& source,
    233                              const content::NotificationDetails& details) {
    234   DCHECK_EQ(type, chrome::NOTIFICATION_FULLSCREEN_CHANGED);
    235   CloseBubble();
    236 }
    237 
    238 void ZoomBubbleView::OnImmersiveRevealStarted() {
    239   CloseBubble();
    240 }
    241 
    242 void ZoomBubbleView::OnImmersiveModeControllerDestroyed() {
    243   immersive_mode_controller_ = NULL;
    244 }
    245 
    246 void ZoomBubbleView::WindowClosing() {
    247   // |zoom_bubble_| can be a new bubble by this point (as Close(); doesn't
    248   // call this right away). Only set to NULL when it's this bubble.
    249   if (zoom_bubble_ == this)
    250     zoom_bubble_ = NULL;
    251 }
    252