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