OLD | NEW |
| (Empty) |
1 // Copyright (c) 2006-2008 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/views/custom_frame_window.h" | |
6 | |
7 #include "base/gfx/point.h" | |
8 #include "base/gfx/size.h" | |
9 #include "chrome/common/gfx/chrome_canvas.h" | |
10 #include "chrome/common/gfx/path.h" | |
11 #include "chrome/common/l10n_util.h" | |
12 #include "chrome/common/win_util.h" | |
13 #include "chrome/views/client_view.h" | |
14 #include "chrome/views/default_non_client_view.h" | |
15 #include "chrome/views/root_view.h" | |
16 #include "chrome/views/window_delegate.h" | |
17 #include "grit/generated_resources.h" | |
18 | |
19 namespace views { | |
20 | |
21 // A scoping class that prevents a window from being able to redraw in response | |
22 // to invalidations that may occur within it for the lifetime of the object. | |
23 // | |
24 // Why would we want such a thing? Well, it turns out Windows has some | |
25 // "unorthodox" behavior when it comes to painting its non-client areas. | |
26 // Occasionally, Windows will paint portions of the default non-client area | |
27 // right over the top of the custom frame. This is not simply fixed by handling | |
28 // WM_NCPAINT/WM_PAINT, with some investigation it turns out that this | |
29 // rendering is being done *inside* the default implementation of some message | |
30 // handlers and functions: | |
31 // . WM_SETTEXT | |
32 // . WM_SETICON | |
33 // . WM_NCLBUTTONDOWN | |
34 // . EnableMenuItem, called from our WM_INITMENU handler | |
35 // The solution is to handle these messages and call DefWindowProc ourselves, | |
36 // but prevent the window from being able to update itself for the duration of | |
37 // the call. We do this with this class, which automatically calls its | |
38 // associated CustomFrameWindow's lock and unlock functions as it is created | |
39 // and destroyed. See documentation in those methods for the technique used. | |
40 // | |
41 // IMPORTANT: Do not use this scoping object for large scopes or periods of | |
42 // time! IT WILL PREVENT THE WINDOW FROM BEING REDRAWN! (duh). | |
43 // | |
44 // I would love to hear Raymond Chen's explanation for all this. And maybe a | |
45 // list of other messages that this applies to ;-) | |
46 class CustomFrameWindow::ScopedRedrawLock { | |
47 public: | |
48 explicit ScopedRedrawLock(CustomFrameWindow* window) : window_(window) { | |
49 window_->LockUpdates(); | |
50 } | |
51 | |
52 ~ScopedRedrawLock() { | |
53 window_->UnlockUpdates(); | |
54 } | |
55 | |
56 private: | |
57 // The window having its style changed. | |
58 CustomFrameWindow* window_; | |
59 }; | |
60 | |
61 HCURSOR CustomFrameWindow::resize_cursors_[6]; | |
62 | |
63 /////////////////////////////////////////////////////////////////////////////// | |
64 // CustomFrameWindow, public: | |
65 | |
66 CustomFrameWindow::CustomFrameWindow(WindowDelegate* window_delegate) | |
67 : Window(window_delegate), | |
68 is_active_(false), | |
69 lock_updates_(false), | |
70 saved_window_style_(0) { | |
71 InitClass(); | |
72 non_client_view_ = new DefaultNonClientView(this); | |
73 } | |
74 | |
75 CustomFrameWindow::CustomFrameWindow(WindowDelegate* window_delegate, | |
76 NonClientView* non_client_view) | |
77 : Window(window_delegate) { | |
78 InitClass(); | |
79 non_client_view_ = non_client_view; | |
80 } | |
81 | |
82 CustomFrameWindow::~CustomFrameWindow() { | |
83 } | |
84 | |
85 /////////////////////////////////////////////////////////////////////////////// | |
86 // CustomFrameWindow, Window overrides: | |
87 | |
88 void CustomFrameWindow::Init(HWND parent, const gfx::Rect& bounds) { | |
89 // TODO(beng): (Cleanup) Right now, the only way to specify a different | |
90 // non-client view is to subclass this object and provide one | |
91 // by setting this member before calling Init. | |
92 if (!non_client_view_) | |
93 non_client_view_ = new DefaultNonClientView(this); | |
94 Window::Init(parent, bounds); | |
95 | |
96 ResetWindowRegion(); | |
97 } | |
98 | |
99 void CustomFrameWindow::UpdateWindowTitle() { | |
100 // Layout winds up causing the title to be re-validated during | |
101 // string measurement. | |
102 non_client_view_->Layout(); | |
103 // Must call the base class too so that places like the Task Bar get updated. | |
104 Window::UpdateWindowTitle(); | |
105 } | |
106 | |
107 void CustomFrameWindow::UpdateWindowIcon() { | |
108 // The icon will be re-validated during painting. | |
109 non_client_view_->SchedulePaint(); | |
110 // Call the base class so that places like the Task Bar get updated. | |
111 Window::UpdateWindowIcon(); | |
112 } | |
113 | |
114 void CustomFrameWindow::EnableClose(bool enable) { | |
115 non_client_view_->EnableClose(enable); | |
116 // Make sure the SysMenu changes to reflect this change as well. | |
117 Window::EnableClose(enable); | |
118 } | |
119 | |
120 void CustomFrameWindow::DisableInactiveRendering(bool disable) { | |
121 Window::DisableInactiveRendering(disable); | |
122 non_client_view_->set_paint_as_active(disable); | |
123 if (!disable) | |
124 non_client_view_->SchedulePaint(); | |
125 } | |
126 | |
127 void CustomFrameWindow::SizeWindowToDefault() { | |
128 gfx::Size pref = client_view()->GetPreferredSize(); | |
129 DCHECK(pref.width() > 0 && pref.height() > 0); | |
130 gfx::Size window_size = | |
131 non_client_view_->CalculateWindowSizeForClientSize(pref.width(), | |
132 pref.height()); | |
133 win_util::CenterAndSizeWindow(owning_window(), GetHWND(), | |
134 window_size.ToSIZE(), false); | |
135 } | |
136 | |
137 /////////////////////////////////////////////////////////////////////////////// | |
138 // CustomFrameWindow, WidgetWin overrides: | |
139 | |
140 static void EnableMenuItem(HMENU menu, UINT command, bool enabled) { | |
141 UINT flags = MF_BYCOMMAND | (enabled ? MF_ENABLED : MF_DISABLED | MF_GRAYED); | |
142 EnableMenuItem(menu, command, flags); | |
143 } | |
144 | |
145 void CustomFrameWindow::OnInitMenu(HMENU menu) { | |
146 bool is_minimized = IsMinimized(); | |
147 bool is_maximized = IsMaximized(); | |
148 bool is_restored = !is_minimized && !is_maximized; | |
149 | |
150 ScopedRedrawLock lock(this); | |
151 EnableMenuItem(menu, SC_RESTORE, !is_restored); | |
152 EnableMenuItem(menu, SC_MOVE, is_restored); | |
153 EnableMenuItem(menu, SC_SIZE, window_delegate()->CanResize() && is_restored); | |
154 EnableMenuItem(menu, SC_MAXIMIZE, | |
155 window_delegate()->CanMaximize() && !is_maximized); | |
156 EnableMenuItem(menu, SC_MINIMIZE, | |
157 window_delegate()->CanMaximize() && !is_minimized); | |
158 } | |
159 | |
160 void CustomFrameWindow::OnMouseLeave() { | |
161 bool process_mouse_exited = true; | |
162 POINT pt; | |
163 if (GetCursorPos(&pt)) { | |
164 LRESULT ht_component = | |
165 ::SendMessage(GetHWND(), WM_NCHITTEST, 0, MAKELPARAM(pt.x, pt.y)); | |
166 if (ht_component != HTNOWHERE) { | |
167 // If the mouse moved into a part of the window's non-client area, then | |
168 // don't send a mouse exited event since the mouse is still within the | |
169 // bounds of the ChromeView that's rendering the frame. Note that we do | |
170 // _NOT_ do this for windows with native frames, since in that case the | |
171 // mouse really will have left the bounds of the RootView. | |
172 process_mouse_exited = false; | |
173 } | |
174 } | |
175 | |
176 if (process_mouse_exited) | |
177 ProcessMouseExited(); | |
178 } | |
179 | |
180 LRESULT CustomFrameWindow::OnNCActivate(BOOL active) { | |
181 is_active_ = !!active; | |
182 | |
183 // We can get WM_NCACTIVATE before we're actually visible. If we're not | |
184 // visible, no need to paint. | |
185 if (IsWindowVisible(GetHWND())) { | |
186 non_client_view_->SchedulePaint(); | |
187 // We need to force a paint now, as a user dragging a window will block | |
188 // painting operations while the move is in progress. | |
189 PaintNow(root_view_->GetScheduledPaintRect()); | |
190 } | |
191 | |
192 // Defering to our parent as it is important that the NCActivate message gets | |
193 // DefProc'ed or the task bar won't show our process as active. | |
194 // See bug http://crbug.com/4513. | |
195 return Window::OnNCActivate(active); | |
196 } | |
197 | |
198 LRESULT CustomFrameWindow::OnNCCalcSize(BOOL mode, LPARAM l_param) { | |
199 // We need to repaint all when the window bounds change. | |
200 return WVR_REDRAW; | |
201 } | |
202 | |
203 LRESULT CustomFrameWindow::OnNCHitTest(const CPoint& point) { | |
204 // NC points are in screen coordinates. | |
205 CPoint temp = point; | |
206 MapWindowPoints(HWND_DESKTOP, GetHWND(), &temp, 1); | |
207 return non_client_view_->NonClientHitTest(gfx::Point(temp.x, temp.y)); | |
208 } | |
209 | |
210 struct ClipState { | |
211 // The window being painted. | |
212 HWND parent; | |
213 | |
214 // DC painting to. | |
215 HDC dc; | |
216 | |
217 // Origin of the window in terms of the screen. | |
218 int x; | |
219 int y; | |
220 }; | |
221 | |
222 // See comments in OnNCPaint for details of this function. | |
223 static BOOL CALLBACK ClipDCToChild(HWND window, LPARAM param) { | |
224 ClipState* clip_state = reinterpret_cast<ClipState*>(param); | |
225 if (GetParent(window) == clip_state->parent && IsWindowVisible(window)) { | |
226 RECT bounds; | |
227 GetWindowRect(window, &bounds); | |
228 ExcludeClipRect(clip_state->dc, | |
229 bounds.left - clip_state->x, | |
230 bounds.top - clip_state->y, | |
231 bounds.right - clip_state->x, | |
232 bounds.bottom - clip_state->y); | |
233 } | |
234 return TRUE; | |
235 } | |
236 | |
237 void CustomFrameWindow::OnNCPaint(HRGN rgn) { | |
238 // We have an NC region and need to paint it. We expand the NC region to | |
239 // include the dirty region of the root view. This is done to minimize | |
240 // paints. | |
241 CRect window_rect; | |
242 GetWindowRect(&window_rect); | |
243 | |
244 if (window_rect.Width() != root_view_->width() || | |
245 window_rect.Height() != root_view_->height()) { | |
246 // If the size of the window differs from the size of the root view it | |
247 // means we're being asked to paint before we've gotten a WM_SIZE. This can | |
248 // happen when the user is interactively resizing the window. To avoid | |
249 // mass flickering we don't do anything here. Once we get the WM_SIZE we'll | |
250 // reset the region of the window which triggers another WM_NCPAINT and | |
251 // all is well. | |
252 return; | |
253 } | |
254 | |
255 CRect dirty_region; | |
256 // A value of 1 indicates paint all. | |
257 if (!rgn || rgn == reinterpret_cast<HRGN>(1)) { | |
258 dirty_region = CRect(0, 0, window_rect.Width(), window_rect.Height()); | |
259 } else { | |
260 RECT rgn_bounding_box; | |
261 GetRgnBox(rgn, &rgn_bounding_box); | |
262 if (!IntersectRect(&dirty_region, &rgn_bounding_box, &window_rect)) | |
263 return; // Dirty region doesn't intersect window bounds, bale. | |
264 | |
265 // rgn_bounding_box is in screen coordinates. Map it to window coordinates. | |
266 OffsetRect(&dirty_region, -window_rect.left, -window_rect.top); | |
267 } | |
268 | |
269 // In theory GetDCEx should do what we want, but I couldn't get it to work. | |
270 // In particular the docs mentiond DCX_CLIPCHILDREN, but as far as I can tell | |
271 // it doesn't work at all. So, instead we get the DC for the window then | |
272 // manually clip out the children. | |
273 HDC dc = GetWindowDC(GetHWND()); | |
274 ClipState clip_state; | |
275 clip_state.x = window_rect.left; | |
276 clip_state.y = window_rect.top; | |
277 clip_state.parent = GetHWND(); | |
278 clip_state.dc = dc; | |
279 EnumChildWindows(GetHWND(), &ClipDCToChild, | |
280 reinterpret_cast<LPARAM>(&clip_state)); | |
281 | |
282 RootView* root_view = GetRootView(); | |
283 CRect old_paint_region = root_view->GetScheduledPaintRectConstrainedToSize(); | |
284 | |
285 if (!old_paint_region.IsRectEmpty()) { | |
286 // The root view has a region that needs to be painted. Include it in the | |
287 // region we're going to paint. | |
288 | |
289 CRect tmp = dirty_region; | |
290 UnionRect(&dirty_region, &tmp, &old_paint_region); | |
291 } | |
292 | |
293 root_view->SchedulePaint(gfx::Rect(dirty_region), false); | |
294 | |
295 // ChromeCanvasPaints destructor does the actual painting. As such, wrap the | |
296 // following in a block to force paint to occur so that we can release the dc. | |
297 { | |
298 ChromeCanvasPaint canvas(dc, opaque(), dirty_region.left, dirty_region.top, | |
299 dirty_region.Width(), dirty_region.Height()); | |
300 | |
301 root_view->ProcessPaint(&canvas); | |
302 } | |
303 | |
304 ReleaseDC(GetHWND(), dc); | |
305 } | |
306 | |
307 void CustomFrameWindow::OnNCLButtonDown(UINT ht_component, | |
308 const CPoint& point) { | |
309 switch (ht_component) { | |
310 case HTCLOSE: | |
311 case HTMINBUTTON: | |
312 case HTMAXBUTTON: { | |
313 // When the mouse is pressed down in these specific non-client areas, we | |
314 // need to tell the RootView to send the mouse pressed event (which sets | |
315 // capture, allowing subsequent WM_LBUTTONUP (note, _not_ WM_NCLBUTTONUP) | |
316 // to fire so that the appropriate WM_SYSCOMMAND can be sent by the | |
317 // applicable button's ButtonListener. We _have_ to do this this way | |
318 // rather than letting Windows just send the syscommand itself (as would | |
319 // happen if we never did this dance) because for some insane reason | |
320 // DefWindowProc for WM_NCLBUTTONDOWN also renders the pressed window | |
321 // control button appearance, in the Windows classic style, over our | |
322 // view! Ick! By handling this message we prevent Windows from doing this | |
323 // undesirable thing, but that means we need to roll the sys-command | |
324 // handling ourselves. | |
325 ProcessNCMousePress(point, MK_LBUTTON); | |
326 return; | |
327 } | |
328 default: | |
329 Window::OnNCLButtonDown(ht_component, point); | |
330 /* | |
331 if (!IsMsgHandled()) { | |
332 // Window::OnNCLButtonDown set the message as unhandled. This normally | |
333 // means WidgetWin::ProcessWindowMessage will pass it to | |
334 // DefWindowProc. Sadly, DefWindowProc for WM_NCLBUTTONDOWN does weird | |
335 // non-client painting, so we need to call it directly here inside a | |
336 // scoped update lock. | |
337 ScopedRedrawLock lock(this); | |
338 DefWindowProc(GetHWND(), WM_NCLBUTTONDOWN, ht_component, | |
339 MAKELPARAM(point.x, point.y)); | |
340 SetMsgHandled(TRUE); | |
341 } | |
342 */ | |
343 break; | |
344 } | |
345 } | |
346 | |
347 LRESULT CustomFrameWindow::OnNCUAHDrawCaption(UINT msg, WPARAM w_param, | |
348 LPARAM l_param) { | |
349 // See comment in widget_win.h at the definition of WM_NCUAHDRAWCAPTION for | |
350 // an explanation about why we need to handle this message. | |
351 SetMsgHandled(TRUE); | |
352 return 0; | |
353 } | |
354 | |
355 LRESULT CustomFrameWindow::OnNCUAHDrawFrame(UINT msg, WPARAM w_param, | |
356 LPARAM l_param) { | |
357 // See comment in widget_win.h at the definition of WM_NCUAHDRAWCAPTION for | |
358 // an explanation about why we need to handle this message. | |
359 SetMsgHandled(TRUE); | |
360 return 0; | |
361 } | |
362 | |
363 LRESULT CustomFrameWindow::OnSetCursor(HWND window, UINT hittest_code, | |
364 UINT message) { | |
365 int index = RC_NORMAL; | |
366 switch (hittest_code) { | |
367 case HTTOP: | |
368 case HTBOTTOM: | |
369 index = RC_VERTICAL; | |
370 break; | |
371 case HTTOPLEFT: | |
372 case HTBOTTOMRIGHT: | |
373 index = RC_NWSE; | |
374 break; | |
375 case HTTOPRIGHT: | |
376 case HTBOTTOMLEFT: | |
377 index = RC_NESW; | |
378 break; | |
379 case HTLEFT: | |
380 case HTRIGHT: | |
381 index = RC_HORIZONTAL; | |
382 break; | |
383 case HTCAPTION: | |
384 case HTCLIENT: | |
385 index = RC_NORMAL; | |
386 break; | |
387 } | |
388 SetCursor(resize_cursors_[index]); | |
389 return 0; | |
390 } | |
391 | |
392 LRESULT CustomFrameWindow::OnSetIcon(UINT size_type, HICON new_icon) { | |
393 ScopedRedrawLock lock(this); | |
394 return DefWindowProc(GetHWND(), WM_SETICON, size_type, | |
395 reinterpret_cast<LPARAM>(new_icon)); | |
396 } | |
397 | |
398 LRESULT CustomFrameWindow::OnSetText(const wchar_t* text) { | |
399 ScopedRedrawLock lock(this); | |
400 return DefWindowProc(GetHWND(), WM_SETTEXT, NULL, | |
401 reinterpret_cast<LPARAM>(text)); | |
402 } | |
403 | |
404 void CustomFrameWindow::OnSize(UINT param, const CSize& size) { | |
405 Window::OnSize(param, size); | |
406 | |
407 // ResetWindowRegion is going to trigger WM_NCPAINT. By doing it after we've | |
408 // invoked OnSize we ensure the RootView has been layed out. | |
409 ResetWindowRegion(); | |
410 } | |
411 | |
412 void CustomFrameWindow::OnSysCommand(UINT notification_code, CPoint click) { | |
413 // Windows uses the 4 lower order bits of |notification_code| for type- | |
414 // specific information so we must exclude this when comparing. | |
415 static const int sc_mask = 0xFFF0; | |
416 if ((notification_code & sc_mask) == SC_MINIMIZE || | |
417 (notification_code & sc_mask) == SC_MAXIMIZE || | |
418 (notification_code & sc_mask) == SC_RESTORE) { | |
419 non_client_view_->ResetWindowControls(); | |
420 } else if ((notification_code & sc_mask) == SC_MOVE || | |
421 (notification_code & sc_mask) == SC_SIZE) { | |
422 if (lock_updates_) { | |
423 // We were locked, before entering a resize or move modal loop. Now that | |
424 // we've begun to move the window, we need to unlock updates so that the | |
425 // sizing/moving feedback can be continuous. | |
426 UnlockUpdates(); | |
427 } | |
428 } | |
429 Window::OnSysCommand(notification_code, click); | |
430 } | |
431 | |
432 /////////////////////////////////////////////////////////////////////////////// | |
433 // CustomFrameWindow, private: | |
434 | |
435 // static | |
436 void CustomFrameWindow::InitClass() { | |
437 static bool initialized = false; | |
438 if (!initialized) { | |
439 resize_cursors_[RC_NORMAL] = LoadCursor(NULL, IDC_ARROW); | |
440 resize_cursors_[RC_VERTICAL] = LoadCursor(NULL, IDC_SIZENS); | |
441 resize_cursors_[RC_HORIZONTAL] = LoadCursor(NULL, IDC_SIZEWE); | |
442 resize_cursors_[RC_NESW] = LoadCursor(NULL, IDC_SIZENESW); | |
443 resize_cursors_[RC_NWSE] = LoadCursor(NULL, IDC_SIZENWSE); | |
444 initialized = true; | |
445 } | |
446 } | |
447 | |
448 void CustomFrameWindow::LockUpdates() { | |
449 lock_updates_ = true; | |
450 saved_window_style_ = GetWindowLong(GetHWND(), GWL_STYLE); | |
451 SetWindowLong(GetHWND(), GWL_STYLE, saved_window_style_ & ~WS_VISIBLE); | |
452 } | |
453 | |
454 void CustomFrameWindow::UnlockUpdates() { | |
455 SetWindowLong(GetHWND(), GWL_STYLE, saved_window_style_); | |
456 lock_updates_ = false; | |
457 } | |
458 | |
459 void CustomFrameWindow::ResetWindowRegion() { | |
460 // Changing the window region is going to force a paint. Only change the | |
461 // window region if the region really differs. | |
462 HRGN current_rgn = CreateRectRgn(0, 0, 0, 0); | |
463 int current_rgn_result = GetWindowRgn(GetHWND(), current_rgn); | |
464 | |
465 CRect window_rect; | |
466 GetWindowRect(&window_rect); | |
467 HRGN new_region; | |
468 if (IsMaximized()) { | |
469 HMONITOR monitor = MonitorFromWindow(GetHWND(), MONITOR_DEFAULTTONEAREST); | |
470 MONITORINFO mi; | |
471 mi.cbSize = sizeof mi; | |
472 GetMonitorInfo(monitor, &mi); | |
473 CRect work_rect = mi.rcWork; | |
474 work_rect.OffsetRect(-window_rect.left, -window_rect.top); | |
475 new_region = CreateRectRgnIndirect(&work_rect); | |
476 } else { | |
477 gfx::Path window_mask; | |
478 non_client_view_->GetWindowMask(gfx::Size(window_rect.Width(), | |
479 window_rect.Height()), | |
480 &window_mask); | |
481 new_region = window_mask.CreateHRGN(); | |
482 } | |
483 | |
484 if (current_rgn_result == ERROR || !EqualRgn(current_rgn, new_region)) { | |
485 // SetWindowRgn takes ownership of the HRGN created by CreateHRGN. | |
486 SetWindowRgn(new_region, TRUE); | |
487 } else { | |
488 DeleteObject(new_region); | |
489 } | |
490 | |
491 DeleteObject(current_rgn); | |
492 } | |
493 | |
494 void CustomFrameWindow::ProcessNCMousePress(const CPoint& point, int flags) { | |
495 CPoint temp = point; | |
496 MapWindowPoints(HWND_DESKTOP, GetHWND(), &temp, 1); | |
497 UINT message_flags = 0; | |
498 if ((GetKeyState(VK_CONTROL) & 0x80) == 0x80) | |
499 message_flags |= MK_CONTROL; | |
500 if ((GetKeyState(VK_SHIFT) & 0x80) == 0x80) | |
501 message_flags |= MK_SHIFT; | |
502 message_flags |= flags; | |
503 ProcessMousePressed(temp, message_flags, false, false); | |
504 } | |
505 | |
506 } // namespace views | |
OLD | NEW |