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