Home | History | Annotate | Download | only in frame
      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/frame/app_non_client_frame_view_ash.h"
      6 
      7 #include "ash/shell_delegate.h"
      8 #include "ash/wm/workspace/frame_maximize_button.h"
      9 #include "base/debug/stack_trace.h"
     10 #include "base/i18n/rtl.h"
     11 #include "chrome/browser/ui/ash/chrome_shell_delegate.h"
     12 #include "chrome/browser/ui/views/frame/browser_frame.h"
     13 #include "chrome/browser/ui/views/frame/browser_view.h"
     14 #include "grit/ash_resources.h"
     15 #include "grit/generated_resources.h"  // Accessibility names
     16 #include "grit/theme_resources.h"
     17 #include "ui/aura/window.h"
     18 #include "ui/base/hit_test.h"
     19 #include "ui/base/l10n/l10n_util.h"
     20 #include "ui/base/resource/resource_bundle.h"
     21 #include "ui/base/theme_provider.h"
     22 #include "ui/gfx/canvas.h"
     23 #include "ui/gfx/image/image.h"
     24 #include "ui/gfx/point.h"
     25 #include "ui/gfx/rect.h"
     26 #include "ui/gfx/size.h"
     27 #include "ui/views/controls/button/image_button.h"
     28 #include "ui/views/widget/widget.h"
     29 #include "ui/views/window/non_client_view.h"
     30 
     31 namespace {
     32 // The number of pixels within the shadow to draw the buttons.
     33 const int kShadowStart = 16;
     34 // The size and close buttons are designed to overlap.
     35 const int kButtonOverlap = 1;
     36 
     37 // TODO(pkotwicz): Remove these constants once the IDR_AURA_FULLSCREEN_SHADOW
     38 // resource is updated.
     39 const int kShadowHeightStretch = -1;
     40 }
     41 
     42 class AppNonClientFrameViewAsh::ControlView
     43     : public views::View, public views::ButtonListener {
     44  public:
     45   // TODO(skuhne): If we keep the "always maximized" experiment we might want to
     46   // make this function be able to work with a |restore_button_| which is NULL.
     47   explicit ControlView(AppNonClientFrameViewAsh* owner) :
     48       owner_(owner),
     49       close_button_(new views::ImageButton(this)),
     50       restore_button_(new ash::FrameMaximizeButton(this, owner_))
     51   {
     52     close_button_->SetAccessibleName(
     53         l10n_util::GetStringUTF16(IDS_ACCNAME_CLOSE));
     54     restore_button_->SetAccessibleName(
     55         l10n_util::GetStringUTF16(IDS_ACCNAME_MAXIMIZE));
     56 
     57     ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
     58 
     59     int control_base_resource_id = owner->browser_view()->IsOffTheRecord() ?
     60         IDR_AURA_WINDOW_HEADER_BASE_INCOGNITO_ACTIVE :
     61         IDR_AURA_WINDOW_HEADER_BASE_ACTIVE;
     62     control_base_ = rb.GetImageNamed(control_base_resource_id).ToImageSkia();
     63     shadow_ = rb.GetImageNamed(
     64         base::i18n::IsRTL() ? IDR_AURA_WINDOW_FULLSCREEN_SHADOW_RTL :
     65                               IDR_AURA_WINDOW_FULLSCREEN_SHADOW).ToImageSkia();
     66 
     67     AddChildView(close_button_);
     68     AddChildView(restore_button_);
     69   }
     70 
     71   virtual ~ControlView() {}
     72 
     73   virtual void Layout() OVERRIDE {
     74     if (ash::Shell::IsForcedMaximizeMode()) {
     75       // TODO(skuhne): If this experiment would get persued, it would be better
     76       // to check here the |restore_button_|'s visibility. Furthermore we
     77       // should change |shadow_| to a new bitmap which can host only a single
     78       // button.
     79       gfx::Size size = restore_button_->bounds().size();
     80       if (size.width()) {
     81         size.set_width(0);
     82         restore_button_->SetSize(size);
     83       }
     84     }
     85     restore_button_->SetPosition(gfx::Point(kShadowStart, 0));
     86     close_button_->SetPosition(gfx::Point(kShadowStart +
     87         restore_button_->width() - kButtonOverlap, 0));
     88   }
     89 
     90   virtual void ViewHierarchyChanged(
     91       const ViewHierarchyChangedDetails& details) OVERRIDE {
     92     if (details.is_add && details.child == this) {
     93       SetButtonImages(restore_button_,
     94                       IDR_AURA_WINDOW_FULLSCREEN_RESTORE,
     95                       IDR_AURA_WINDOW_FULLSCREEN_RESTORE_H,
     96                       IDR_AURA_WINDOW_FULLSCREEN_RESTORE_P);
     97       restore_button_->SizeToPreferredSize();
     98 
     99       SetButtonImages(close_button_,
    100                       IDR_AURA_WINDOW_FULLSCREEN_CLOSE,
    101                       IDR_AURA_WINDOW_FULLSCREEN_CLOSE_H,
    102                       IDR_AURA_WINDOW_FULLSCREEN_CLOSE_P);
    103       close_button_->SizeToPreferredSize();
    104     }
    105   }
    106 
    107   virtual gfx::Size GetPreferredSize() OVERRIDE {
    108     int maximize_button_deduction = ash::Shell::IsForcedMaximizeMode() ?
    109         restore_button_->GetPreferredSize().width() : 0;
    110 
    111     return gfx::Size(shadow_->width() - maximize_button_deduction,
    112                      shadow_->height() + kShadowHeightStretch);
    113   }
    114 
    115   virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
    116     canvas->TileImageInt(*control_base_,
    117         base::i18n::IsRTL() ? 0 : restore_button_->x(),
    118         restore_button_->y(),
    119         restore_button_->width() - kButtonOverlap + close_button_->width(),
    120         restore_button_->height());
    121 
    122     views::View::OnPaint(canvas);
    123 
    124     canvas->DrawImageInt(*shadow_, 0, kShadowHeightStretch);
    125   }
    126 
    127   virtual void ButtonPressed(views::Button* sender,
    128                              const ui::Event& event) OVERRIDE {
    129     ash::UserMetricsAction action = ash::UMA_WINDOW_APP_CLOSE_BUTTON_CLICK;
    130     if (sender == close_button_) {
    131       owner_->frame()->Close();
    132     } else if (sender == restore_button_) {
    133       action = ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_RESTORE;
    134       restore_button_->SetState(views::CustomButton::STATE_NORMAL);
    135       owner_->frame()->Restore();
    136     } else {
    137       return;
    138     }
    139     ChromeShellDelegate::instance()->RecordUserMetricsAction(action);
    140   }
    141 
    142   // Returns the insets of the control which are only covered by the shadow.
    143   gfx::Insets GetShadowInsets() {
    144     bool rtl = base::i18n::IsRTL();
    145     return gfx::Insets(
    146         0,
    147         rtl ? 0 : restore_button_->x(),
    148         shadow_->height() - close_button_->height(),
    149         rtl ? restore_button_->x() : 0);
    150   }
    151 
    152  private:
    153   // Sets images whose ids are passed in for each of the respective states
    154   // of |button|.
    155   void SetButtonImages(views::ImageButton* button, int normal_image_id,
    156                        int hot_image_id, int pushed_image_id) {
    157     ui::ThemeProvider* theme_provider = GetThemeProvider();
    158     button->SetImage(views::CustomButton::STATE_NORMAL,
    159                      theme_provider->GetImageSkiaNamed(normal_image_id));
    160     button->SetImage(views::CustomButton::STATE_HOVERED,
    161                      theme_provider->GetImageSkiaNamed(hot_image_id));
    162     button->SetImage(views::CustomButton::STATE_PRESSED,
    163                      theme_provider->GetImageSkiaNamed(pushed_image_id));
    164   }
    165 
    166   AppNonClientFrameViewAsh* owner_;
    167   views::ImageButton* close_button_;
    168   views::ImageButton* restore_button_;
    169   const gfx::ImageSkia* control_base_;
    170   const gfx::ImageSkia* shadow_;
    171 
    172   DISALLOW_COPY_AND_ASSIGN(ControlView);
    173 };
    174 
    175 // Observer to detect when the browser frame widget closes so we can clean
    176 // up our ControlView. Because we can be closed via a keyboard shortcut we
    177 // are not guaranteed to run AppNonClientFrameView's Close() or Restore().
    178 class AppNonClientFrameViewAsh::FrameObserver : public views::WidgetObserver {
    179  public:
    180   explicit FrameObserver(AppNonClientFrameViewAsh* owner) : owner_(owner) {}
    181   virtual ~FrameObserver() {}
    182 
    183   // views::WidgetObserver:
    184   virtual void OnWidgetDestroying(views::Widget* widget) OVERRIDE {
    185     owner_->CloseControlWidget();
    186   }
    187 
    188  private:
    189   AppNonClientFrameViewAsh* owner_;
    190 
    191   DISALLOW_COPY_AND_ASSIGN(FrameObserver);
    192 };
    193 
    194 // static
    195 const char AppNonClientFrameViewAsh::kViewClassName[] =
    196     "AppNonClientFrameViewAsh";
    197 // static
    198 const char AppNonClientFrameViewAsh::kControlWindowName[] =
    199     "AppNonClientFrameViewAshControls";
    200 
    201 AppNonClientFrameViewAsh::AppNonClientFrameViewAsh(
    202     BrowserFrame* frame, BrowserView* browser_view)
    203     : BrowserNonClientFrameView(frame, browser_view),
    204       control_view_(new ControlView(this)),
    205       control_widget_(NULL),
    206       frame_observer_(new FrameObserver(this)) {
    207   // This FrameView is always maximized so we don't want the window to have
    208   // resize borders.
    209   frame->GetNativeView()->set_hit_test_bounds_override_inner(gfx::Insets());
    210   // Watch for frame close so we can clean up the control widget.
    211   frame->AddObserver(frame_observer_.get());
    212   set_background(views::Background::CreateSolidBackground(SK_ColorBLACK));
    213   // Create the controls.
    214   control_widget_ = new views::Widget;
    215   views::Widget::InitParams params(views::Widget::InitParams::TYPE_CONTROL);
    216   params.parent = browser_view->GetNativeWindow();
    217   params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
    218   control_widget_->Init(params);
    219   control_widget_->SetContentsView(control_view_);
    220   aura::Window* window = control_widget_->GetNativeView();
    221   window->SetName(kControlWindowName);
    222   // Need to exclude the shadow from the active control area.
    223   window->SetHitTestBoundsOverrideOuter(control_view_->GetShadowInsets(),
    224                                         control_view_->GetShadowInsets());
    225   gfx::Rect control_bounds = GetControlBounds();
    226   window->SetBounds(control_bounds);
    227   control_widget_->Show();
    228 }
    229 
    230 AppNonClientFrameViewAsh::~AppNonClientFrameViewAsh() {
    231   frame()->RemoveObserver(frame_observer_.get());
    232   // This frame view can be replaced (and deleted) if the window is restored
    233   // via a keyboard shortcut like Alt-[.  Ensure we close the control widget.
    234   CloseControlWidget();
    235 }
    236 
    237 gfx::Rect AppNonClientFrameViewAsh::GetBoundsForClientView() const {
    238   return GetLocalBounds();
    239 }
    240 
    241 gfx::Rect AppNonClientFrameViewAsh::GetWindowBoundsForClientBounds(
    242     const gfx::Rect& client_bounds) const {
    243   return client_bounds;
    244 }
    245 
    246 int AppNonClientFrameViewAsh::NonClientHitTest(
    247     const gfx::Point& point) {
    248   return HTNOWHERE;
    249 }
    250 
    251 void AppNonClientFrameViewAsh::GetWindowMask(const gfx::Size& size,
    252                                              gfx::Path* window_mask) {
    253 }
    254 
    255 void AppNonClientFrameViewAsh::ResetWindowControls() {
    256 }
    257 
    258 void AppNonClientFrameViewAsh::UpdateWindowIcon() {
    259 }
    260 
    261 void AppNonClientFrameViewAsh::UpdateWindowTitle() {
    262 }
    263 
    264 gfx::Rect AppNonClientFrameViewAsh::GetBoundsForTabStrip(
    265     views::View* tabstrip) const {
    266   return gfx::Rect();
    267 }
    268 
    269 BrowserNonClientFrameView::TabStripInsets
    270 AppNonClientFrameViewAsh::GetTabStripInsets(bool restored) const {
    271   return TabStripInsets();
    272 }
    273 
    274 int AppNonClientFrameViewAsh::GetThemeBackgroundXInset() const {
    275   return 0;
    276 }
    277 
    278 void AppNonClientFrameViewAsh::UpdateThrobber(bool running) {
    279 }
    280 
    281 const char* AppNonClientFrameViewAsh::GetClassName() const {
    282   return kViewClassName;
    283 }
    284 
    285 void AppNonClientFrameViewAsh::OnBoundsChanged(
    286     const gfx::Rect& previous_bounds) {
    287   if (control_widget_)
    288     control_widget_->GetNativeView()->SetBounds(GetControlBounds());
    289 }
    290 
    291 gfx::Rect AppNonClientFrameViewAsh::GetControlBounds() const {
    292   if (!control_view_)
    293     return gfx::Rect();
    294   gfx::Size preferred = control_view_->GetPreferredSize();
    295   return gfx::Rect(
    296       base::i18n::IsRTL() ? 0 : (width() - preferred.width()), 0,
    297       preferred.width(), preferred.height());
    298 }
    299 
    300 void AppNonClientFrameViewAsh::CloseControlWidget() {
    301   if (control_widget_) {
    302     control_widget_->Close();
    303     control_widget_ = NULL;
    304   }
    305 }
    306