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