Home | History | Annotate | Download | only in caption_buttons
      1 // Copyright 2013 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 "ash/wm/caption_buttons/frame_caption_button_container_view.h"
      6 
      7 #include "ash/ash_switches.h"
      8 #include "ash/metrics/user_metrics_recorder.h"
      9 #include "ash/shell.h"
     10 #include "ash/wm/caption_buttons/alternate_frame_size_button.h"
     11 #include "ash/wm/caption_buttons/frame_caption_button.h"
     12 #include "ash/wm/caption_buttons/frame_maximize_button.h"
     13 #include "grit/ash_resources.h"
     14 #include "grit/ui_strings.h"  // Accessibility names
     15 #include "ui/base/hit_test.h"
     16 #include "ui/base/l10n/l10n_util.h"
     17 #include "ui/base/resource/resource_bundle.h"
     18 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
     19 #include "ui/gfx/canvas.h"
     20 #include "ui/gfx/insets.h"
     21 #include "ui/gfx/point.h"
     22 #include "ui/views/controls/button/image_button.h"
     23 #include "ui/views/widget/widget.h"
     24 #include "ui/views/widget/widget_delegate.h"
     25 
     26 namespace ash {
     27 
     28 namespace {
     29 
     30 // The distance between buttons.
     31 const int kDistanceBetweenButtons = -1;
     32 
     33 // Converts |point| from |src| to |dst| and hittests against |dst|.
     34 bool ConvertPointToViewAndHitTest(const views::View* src,
     35                                   const views::View* dst,
     36                                   const gfx::Point& point) {
     37   gfx::Point converted(point);
     38   views::View::ConvertPointToTarget(src, dst, &converted);
     39   return dst->HitTestPoint(converted);
     40 }
     41 
     42 }  // namespace
     43 
     44 // static
     45 const char FrameCaptionButtonContainerView::kViewClassName[] =
     46     "FrameCaptionButtonContainerView";
     47 
     48 FrameCaptionButtonContainerView::FrameCaptionButtonContainerView(
     49     views::Widget* frame,
     50     MinimizeAllowed minimize_allowed)
     51     : frame_(frame),
     52       header_style_(HEADER_STYLE_SHORT),
     53       minimize_button_(NULL),
     54       size_button_(NULL),
     55       close_button_(NULL) {
     56   bool alternate_style = switches::UseAlternateFrameCaptionButtonStyle();
     57 
     58   // Insert the buttons left to right.
     59   minimize_button_ = new FrameCaptionButton(this, CAPTION_BUTTON_ICON_MINIMIZE);
     60   minimize_button_->SetAccessibleName(
     61       l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MINIMIZE));
     62   // Hide |minimize_button_| when using the non-alternate button style because
     63   // |size_button_| is capable of minimizing in this case.
     64   minimize_button_->SetVisible(
     65       minimize_allowed == MINIMIZE_ALLOWED &&
     66       (alternate_style || !frame_->widget_delegate()->CanMaximize()));
     67   AddChildView(minimize_button_);
     68 
     69   if (alternate_style)
     70     size_button_ = new AlternateFrameSizeButton(this, frame, this);
     71   else
     72     size_button_ = new FrameMaximizeButton(this, frame);
     73   size_button_->SetAccessibleName(
     74       l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MAXIMIZE));
     75   size_button_->SetVisible(frame_->widget_delegate()->CanMaximize());
     76   AddChildView(size_button_);
     77 
     78   close_button_ = new FrameCaptionButton(this, CAPTION_BUTTON_ICON_CLOSE);
     79   close_button_->SetAccessibleName(
     80       l10n_util::GetStringUTF16(IDS_APP_ACCNAME_CLOSE));
     81   AddChildView(close_button_);
     82 
     83   button_separator_ = ui::ResourceBundle::GetSharedInstance().GetImageNamed(
     84       IDR_AURA_WINDOW_BUTTON_SEPARATOR).AsImageSkia();
     85 }
     86 
     87 FrameCaptionButtonContainerView::~FrameCaptionButtonContainerView() {
     88 }
     89 
     90 FrameMaximizeButton*
     91 FrameCaptionButtonContainerView::GetOldStyleSizeButton() {
     92   return switches::UseAlternateFrameCaptionButtonStyle() ?
     93       NULL : static_cast<FrameMaximizeButton*>(size_button_);
     94 }
     95 
     96 void FrameCaptionButtonContainerView::ResetWindowControls() {
     97   SetButtonsToNormal(ANIMATE_NO);
     98 }
     99 
    100 int FrameCaptionButtonContainerView::NonClientHitTest(
    101     const gfx::Point& point) const {
    102   if (close_button_->visible() &&
    103       ConvertPointToViewAndHitTest(this, close_button_, point)) {
    104     return HTCLOSE;
    105   } else if (size_button_->visible() &&
    106              ConvertPointToViewAndHitTest(this, size_button_, point)) {
    107     return HTMAXBUTTON;
    108   } else if (minimize_button_->visible() &&
    109              ConvertPointToViewAndHitTest(this, minimize_button_, point)) {
    110     return HTMINBUTTON;
    111   }
    112   return HTNOWHERE;
    113 }
    114 
    115 gfx::Size FrameCaptionButtonContainerView::GetPreferredSize() {
    116   int width = 0;
    117   bool first_visible = true;
    118   for (int i = 0; i < child_count(); ++i) {
    119     views::View* child = child_at(i);
    120     if (!child->visible())
    121       continue;
    122 
    123     width += child_at(i)->GetPreferredSize().width();
    124     if (!first_visible)
    125       width += kDistanceBetweenButtons;
    126     first_visible = false;
    127   }
    128   return gfx::Size(width, close_button_->GetPreferredSize().height());
    129 }
    130 
    131 void FrameCaptionButtonContainerView::Layout() {
    132   FrameCaptionButton::Style style = FrameCaptionButton::STYLE_SHORT_RESTORED;
    133   if (header_style_ == HEADER_STYLE_SHORT) {
    134     if (frame_->IsMaximized() || frame_->IsFullscreen())
    135       style = FrameCaptionButton::STYLE_SHORT_MAXIMIZED_OR_FULLSCREEN;
    136     // Else: FrameCaptionButton::STYLE_SHORT_RESTORED;
    137   } else {
    138     style = FrameCaptionButton::STYLE_TALL_RESTORED;
    139   }
    140 
    141   minimize_button_->SetStyle(style);
    142   size_button_->SetStyle(style);
    143   close_button_->SetStyle(style);
    144 
    145   int x = 0;
    146   for (int i = 0; i < child_count(); ++i) {
    147     views::View* child = child_at(i);
    148     if (!child->visible())
    149       continue;
    150 
    151     gfx::Size size = child->GetPreferredSize();
    152     child->SetBounds(x, 0, size.width(), size.height());
    153     x += size.width() + kDistanceBetweenButtons;
    154   }
    155 }
    156 
    157 const char* FrameCaptionButtonContainerView::GetClassName() const {
    158   return kViewClassName;
    159 }
    160 
    161 void FrameCaptionButtonContainerView::OnPaint(gfx::Canvas* canvas) {
    162   views::View::OnPaint(canvas);
    163 
    164   // The alternate button style does not paint the button separator.
    165   if (!switches::UseAlternateFrameCaptionButtonStyle()) {
    166     // We should have at most two visible buttons. The button separator is
    167     // always painted underneath the close button regardless of whether a
    168     // button other than the close button is visible.
    169     gfx::Rect divider(close_button_->bounds().origin(),
    170                       button_separator_.size());
    171     canvas->DrawImageInt(button_separator_,
    172                          GetMirroredXForRect(divider),
    173                          divider.y());
    174   }
    175 }
    176 
    177 void FrameCaptionButtonContainerView::ButtonPressed(views::Button* sender,
    178                                                     const ui::Event& event) {
    179   // When shift-clicking, slow down animations for visual debugging.
    180   // We used to do this via an event filter that looked for the shift key being
    181   // pressed but this interfered with several normal keyboard shortcuts.
    182   scoped_ptr<ui::ScopedAnimationDurationScaleMode> slow_duration_mode;
    183   if (event.IsShiftDown()) {
    184     slow_duration_mode.reset(new ui::ScopedAnimationDurationScaleMode(
    185         ui::ScopedAnimationDurationScaleMode::SLOW_DURATION));
    186   }
    187 
    188   // Abort any animations of the button icons.
    189   SetButtonsToNormal(ANIMATE_NO);
    190 
    191   ash::UserMetricsAction action =
    192       ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_MINIMIZE;
    193   if (sender == minimize_button_) {
    194     frame_->Minimize();
    195   } else if (sender == size_button_) {
    196     if (frame_->IsFullscreen()) { // Can be clicked in immersive fullscreen.
    197       frame_->SetFullscreen(false);
    198       action = ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_EXIT_FULLSCREEN;
    199     } else if (frame_->IsMaximized()) {
    200       frame_->Restore();
    201       action = ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_RESTORE;
    202     } else {
    203       frame_->Maximize();
    204       action = ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_MAXIMIZE;
    205     }
    206   } else if(sender == close_button_) {
    207     frame_->Close();
    208     action = ash::UMA_WINDOW_CLOSE_BUTTON_CLICK;
    209   } else {
    210     return;
    211   }
    212   ash::Shell::GetInstance()->metrics()->RecordUserMetricsAction(action);
    213 }
    214 
    215 bool FrameCaptionButtonContainerView::IsMinimizeButtonVisible() const {
    216   return minimize_button_->visible();
    217 }
    218 
    219 void FrameCaptionButtonContainerView::SetButtonsToNormal(Animate animate) {
    220   SetButtonIcons(CAPTION_BUTTON_ICON_MINIMIZE, CAPTION_BUTTON_ICON_CLOSE,
    221       animate);
    222   minimize_button_->SetState(views::Button::STATE_NORMAL);
    223   size_button_->SetState(views::Button::STATE_NORMAL);
    224   close_button_->SetState(views::Button::STATE_NORMAL);
    225 }
    226 
    227 void FrameCaptionButtonContainerView::SetButtonIcons(
    228     CaptionButtonIcon minimize_button_icon,
    229     CaptionButtonIcon close_button_icon,
    230     Animate animate) {
    231   FrameCaptionButton::Animate fcb_animate = (animate == ANIMATE_YES) ?
    232       FrameCaptionButton::ANIMATE_YES : FrameCaptionButton::ANIMATE_NO;
    233   minimize_button_->SetIcon(minimize_button_icon, fcb_animate);
    234   close_button_->SetIcon(close_button_icon, fcb_animate);
    235 }
    236 
    237 const FrameCaptionButton*
    238 FrameCaptionButtonContainerView::PressButtonAt(
    239     const gfx::Point& position_in_screen,
    240     const gfx::Insets& pressed_hittest_outer_insets) const {
    241   DCHECK(switches::UseAlternateFrameCaptionButtonStyle());
    242   gfx::Point position(position_in_screen);
    243   views::View::ConvertPointFromScreen(this, &position);
    244 
    245   FrameCaptionButton* buttons[] = {
    246     close_button_, size_button_, minimize_button_
    247   };
    248   FrameCaptionButton* pressed_button = NULL;
    249   for (size_t i = 0; i < arraysize(buttons); ++i) {
    250     FrameCaptionButton* button = buttons[i];
    251     if (!button->visible())
    252       continue;
    253 
    254     if (button->state() == views::Button::STATE_PRESSED) {
    255       gfx::Rect expanded_bounds = button->bounds();
    256       expanded_bounds.Inset(pressed_hittest_outer_insets);
    257       if (expanded_bounds.Contains(position)) {
    258         pressed_button = button;
    259         // Do not break in order to give preference to buttons which are
    260         // closer to |position_in_screen| than the currently pressed button.
    261         // TODO(pkotwicz): Make the caption buttons not overlap.
    262       }
    263     } else if (ConvertPointToViewAndHitTest(this, button, position)) {
    264       pressed_button = button;
    265       break;
    266     }
    267   }
    268 
    269   for (size_t i = 0; i < arraysize(buttons); ++i) {
    270     buttons[i]->SetState(buttons[i] == pressed_button ?
    271         views::Button::STATE_PRESSED : views::Button::STATE_NORMAL);
    272   }
    273   return pressed_button;
    274 }
    275 
    276 }  // namespace ash
    277