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 "ui/views/window/non_client_view.h" 6 7 #include "ui/base/accessibility/accessible_view_state.h" 8 #include "ui/base/hit_test.h" 9 #include "ui/views/widget/root_view.h" 10 #include "ui/views/widget/widget.h" 11 #include "ui/views/window/client_view.h" 12 13 namespace views { 14 15 // static 16 const int NonClientFrameView::kFrameShadowThickness = 1; 17 const int NonClientFrameView::kClientEdgeThickness = 1; 18 const char NonClientFrameView::kViewClassName[] = 19 "ui/views/window/NonClientFrameView"; 20 21 const char NonClientView::kViewClassName[] = 22 "ui/views/window/NonClientView"; 23 24 // The frame view and the client view are always at these specific indices, 25 // because the RootView message dispatch sends messages to items higher in the 26 // z-order first and we always want the client view to have first crack at 27 // handling mouse messages. 28 static const int kFrameViewIndex = 0; 29 static const int kClientViewIndex = 1; 30 // The overlay view is always on top (index == child_count() - 1). 31 32 //////////////////////////////////////////////////////////////////////////////// 33 // NonClientView, public: 34 35 NonClientView::NonClientView() 36 : client_view_(NULL), 37 overlay_view_(NULL) { 38 } 39 40 NonClientView::~NonClientView() { 41 // This value may have been reset before the window hierarchy shuts down, 42 // so we need to manually remove it. 43 RemoveChildView(frame_view_.get()); 44 } 45 46 void NonClientView::SetFrameView(NonClientFrameView* frame_view) { 47 // See comment in header about ownership. 48 frame_view->set_owned_by_client(); 49 if (frame_view_.get()) 50 RemoveChildView(frame_view_.get()); 51 frame_view_.reset(frame_view); 52 if (parent()) 53 AddChildViewAt(frame_view_.get(), kFrameViewIndex); 54 } 55 56 void NonClientView::SetOverlayView(View* view) { 57 if (overlay_view_) 58 RemoveChildView(overlay_view_); 59 60 if (!view) 61 return; 62 63 overlay_view_ = view; 64 if (parent()) 65 AddChildView(overlay_view_); 66 } 67 68 bool NonClientView::CanClose() { 69 return client_view_->CanClose(); 70 } 71 72 void NonClientView::WindowClosing() { 73 client_view_->WidgetClosing(); 74 } 75 76 void NonClientView::UpdateFrame(bool layout) { 77 Widget* widget = GetWidget(); 78 SetFrameView(widget->CreateNonClientFrameView()); 79 widget->ThemeChanged(); 80 if (layout) { 81 Layout(); 82 SchedulePaint(); 83 } 84 } 85 86 void NonClientView::SetInactiveRenderingDisabled(bool disable) { 87 frame_view_->SetInactiveRenderingDisabled(disable); 88 } 89 90 gfx::Rect NonClientView::GetWindowBoundsForClientBounds( 91 const gfx::Rect client_bounds) const { 92 return frame_view_->GetWindowBoundsForClientBounds(client_bounds); 93 } 94 95 int NonClientView::NonClientHitTest(const gfx::Point& point) { 96 // The NonClientFrameView is responsible for also asking the ClientView. 97 return frame_view_->NonClientHitTest(point); 98 } 99 100 void NonClientView::GetWindowMask(const gfx::Size& size, 101 gfx::Path* window_mask) { 102 frame_view_->GetWindowMask(size, window_mask); 103 } 104 105 void NonClientView::ResetWindowControls() { 106 frame_view_->ResetWindowControls(); 107 } 108 109 void NonClientView::UpdateWindowIcon() { 110 frame_view_->UpdateWindowIcon(); 111 } 112 113 void NonClientView::UpdateWindowTitle() { 114 frame_view_->UpdateWindowTitle(); 115 } 116 117 void NonClientView::LayoutFrameView() { 118 // First layout the NonClientFrameView, which determines the size of the 119 // ClientView... 120 frame_view_->SetBounds(0, 0, width(), height()); 121 122 // We need to manually call Layout here because layout for the frame view can 123 // change independently of the bounds changing - e.g. after the initial 124 // display of the window the metrics of the native window controls can change, 125 // which does not change the bounds of the window but requires a re-layout to 126 // trigger a repaint. We override OnBoundsChanged() for the NonClientFrameView 127 // to do nothing so that SetBounds above doesn't cause Layout to be called 128 // twice. 129 frame_view_->Layout(); 130 } 131 132 void NonClientView::SetAccessibleName(const string16& name) { 133 accessible_name_ = name; 134 } 135 136 //////////////////////////////////////////////////////////////////////////////// 137 // NonClientView, View overrides: 138 139 gfx::Size NonClientView::GetPreferredSize() { 140 // TODO(pkasting): This should probably be made to look similar to 141 // GetMinimumSize() below. This will require implementing GetPreferredSize() 142 // better in the various frame views. 143 gfx::Rect client_bounds(gfx::Point(), client_view_->GetPreferredSize()); 144 return GetWindowBoundsForClientBounds(client_bounds).size(); 145 } 146 147 gfx::Size NonClientView::GetMinimumSize() { 148 return frame_view_->GetMinimumSize(); 149 } 150 151 gfx::Size NonClientView::GetMaximumSize() { 152 return frame_view_->GetMaximumSize(); 153 } 154 155 void NonClientView::Layout() { 156 LayoutFrameView(); 157 158 // Then layout the ClientView, using those bounds. 159 client_view_->SetBoundsRect(frame_view_->GetBoundsForClientView()); 160 161 // We need to manually call Layout on the ClientView as well for the same 162 // reason as above. 163 client_view_->Layout(); 164 165 if (overlay_view_ && overlay_view_->visible()) 166 overlay_view_->SetBoundsRect(GetLocalBounds()); 167 } 168 169 void NonClientView::ViewHierarchyChanged( 170 const ViewHierarchyChangedDetails& details) { 171 // Add our two child views here as we are added to the Widget so that if we 172 // are subsequently resized all the parent-child relationships are 173 // established. 174 if (details.is_add && GetWidget() && details.child == this) { 175 AddChildViewAt(frame_view_.get(), kFrameViewIndex); 176 AddChildViewAt(client_view_, kClientViewIndex); 177 if (overlay_view_) 178 AddChildView(overlay_view_); 179 } 180 } 181 182 void NonClientView::GetAccessibleState(ui::AccessibleViewState* state) { 183 state->role = ui::AccessibilityTypes::ROLE_CLIENT; 184 state->name = accessible_name_; 185 } 186 187 const char* NonClientView::GetClassName() const { 188 return kViewClassName; 189 } 190 191 views::View* NonClientView::GetEventHandlerForPoint(const gfx::Point& point) { 192 // Because of the z-ordering of our child views (the client view is positioned 193 // over the non-client frame view, if the client view ever overlaps the frame 194 // view visually (as it does for the browser window), then it will eat mouse 195 // events for the window controls. We override this method here so that we can 196 // detect this condition and re-route the events to the non-client frame view. 197 // The assumption is that the frame view's implementation of HitTest will only 198 // return true for area not occupied by the client view. 199 if (frame_view_->parent() == this) { 200 // During the reset of the frame_view_ it's possible to be in this code 201 // after it's been removed from the view hierarchy but before it's been 202 // removed from the NonClientView. 203 gfx::Point point_in_child_coords(point); 204 View::ConvertPointToTarget(this, frame_view_.get(), &point_in_child_coords); 205 if (frame_view_->HitTestPoint(point_in_child_coords)) 206 return frame_view_->GetEventHandlerForPoint(point_in_child_coords); 207 } 208 209 return View::GetEventHandlerForPoint(point); 210 } 211 212 views::View* NonClientView::GetTooltipHandlerForPoint(const gfx::Point& point) { 213 // The same logic as for |GetEventHandlerForPoint()| applies here. 214 if (frame_view_->parent() == this) { 215 // During the reset of the frame_view_ it's possible to be in this code 216 // after it's been removed from the view hierarchy but before it's been 217 // removed from the NonClientView. 218 gfx::Point point_in_child_coords(point); 219 View::ConvertPointToTarget(this, frame_view_.get(), &point_in_child_coords); 220 views::View* handler = 221 frame_view_->GetTooltipHandlerForPoint(point_in_child_coords); 222 if (handler) 223 return handler; 224 } 225 226 return View::GetTooltipHandlerForPoint(point); 227 } 228 229 //////////////////////////////////////////////////////////////////////////////// 230 // NonClientFrameView, public: 231 232 void NonClientFrameView::SetInactiveRenderingDisabled(bool disable) { 233 if (paint_as_active_ == disable) 234 return; 235 236 paint_as_active_ = disable; 237 ShouldPaintAsActiveChanged(); 238 } 239 240 int NonClientFrameView::GetHTComponentForFrame(const gfx::Point& point, 241 int top_resize_border_height, 242 int resize_border_thickness, 243 int top_resize_corner_height, 244 int resize_corner_width, 245 bool can_resize) { 246 // Tricky: In XP, native behavior is to return HTTOPLEFT and HTTOPRIGHT for 247 // a |resize_corner_size|-length strip of both the side and top borders, but 248 // only to return HTBOTTOMLEFT/HTBOTTOMRIGHT along the bottom border + corner 249 // (not the side border). Vista goes further and doesn't return these on any 250 // of the side borders. We allow callers to match either behavior. 251 int component; 252 if (point.x() < resize_border_thickness) { 253 if (point.y() < top_resize_corner_height) 254 component = HTTOPLEFT; 255 else if (point.y() >= (height() - resize_border_thickness)) 256 component = HTBOTTOMLEFT; 257 else 258 component = HTLEFT; 259 } else if (point.x() >= (width() - resize_border_thickness)) { 260 if (point.y() < top_resize_corner_height) 261 component = HTTOPRIGHT; 262 else if (point.y() >= (height() - resize_border_thickness)) 263 component = HTBOTTOMRIGHT; 264 else 265 component = HTRIGHT; 266 } else if (point.y() < top_resize_border_height) { 267 if (point.x() < resize_corner_width) 268 component = HTTOPLEFT; 269 else if (point.x() >= (width() - resize_corner_width)) 270 component = HTTOPRIGHT; 271 else 272 component = HTTOP; 273 } else if (point.y() >= (height() - resize_border_thickness)) { 274 if (point.x() < resize_corner_width) 275 component = HTBOTTOMLEFT; 276 else if (point.x() >= (width() - resize_corner_width)) 277 component = HTBOTTOMRIGHT; 278 else 279 component = HTBOTTOM; 280 } else { 281 return HTNOWHERE; 282 } 283 284 // If the window can't be resized, there are no resize boundaries, just 285 // window borders. 286 return can_resize ? component : HTBORDER; 287 } 288 289 //////////////////////////////////////////////////////////////////////////////// 290 // NonClientFrameView, View overrides: 291 292 bool NonClientFrameView::HitTestRect(const gfx::Rect& rect) const { 293 // For the default case, we assume the non-client frame view never overlaps 294 // the client view. 295 return !GetWidget()->client_view()->bounds().Intersects(rect); 296 } 297 298 //////////////////////////////////////////////////////////////////////////////// 299 // NonClientFrameView, protected: 300 301 bool NonClientFrameView::ShouldPaintAsActive() const { 302 return GetWidget()->IsActive() || paint_as_active_; 303 } 304 305 void NonClientFrameView::ShouldPaintAsActiveChanged() { 306 SchedulePaint(); 307 } 308 309 void NonClientFrameView::GetAccessibleState(ui::AccessibleViewState* state) { 310 state->role = ui::AccessibilityTypes::ROLE_CLIENT; 311 } 312 313 const char* NonClientFrameView::GetClassName() const { 314 return kViewClassName; 315 } 316 317 void NonClientFrameView::OnBoundsChanged(const gfx::Rect& previous_bounds) { 318 // Overridden to do nothing. The NonClientView manually calls Layout on the 319 // FrameView when it is itself laid out, see comment in NonClientView::Layout. 320 } 321 322 } // namespace views 323