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.
5 #include "chrome/browser/ui/tabs/dock_info.h"
7 #include "base/win/scoped_gdi_object.h"
8 #include "base/win/windows_version.h"
9 #include "chrome/browser/ui/ash/tabs/dock_info_ash.h"
10 #include "chrome/browser/ui/browser_list.h"
11 #include "chrome/browser/ui/browser_window.h"
12 #include "chrome/browser/ui/views/frame/browser_view.h"
13 #include "chrome/browser/ui/views/tabs/tab.h"
14 #include "ui/aura/root_window.h"
15 #include "ui/aura/window.h"
16 #include "ui/gfx/screen.h"
17 #include "ui/views/win/hwnd_util.h"
20 #include "ui/views/widget/desktop_aura/desktop_root_window_host_win.h"
25 // BaseWindowFinder -----------------------------------------------------------
27 // Base class used to locate a window. This is intended to be used with the
28 // various win32 functions that iterate over windows.
30 // A subclass need only override ShouldStopIterating to determine when
31 // iteration should stop.
32 class BaseWindowFinder
{
34 // Creates a BaseWindowFinder with the specified set of HWNDs to ignore.
35 explicit BaseWindowFinder(const std::set
<HWND
>& ignore
) : ignore_(ignore
) {}
36 virtual ~BaseWindowFinder() {}
39 static BOOL CALLBACK
WindowCallbackProc(HWND hwnd
, LPARAM lParam
) {
40 // Cast must match that in as_lparam().
41 BaseWindowFinder
* finder
= reinterpret_cast<BaseWindowFinder
*>(lParam
);
42 if (finder
->ignore_
.find(hwnd
) != finder
->ignore_
.end())
45 return finder
->ShouldStopIterating(hwnd
) ? FALSE
: TRUE
;
49 // Cast must match that in WindowCallbackProc().
50 return reinterpret_cast<LPARAM
>(static_cast<BaseWindowFinder
*>(this));
53 // Returns true if iteration should stop, false if iteration should continue.
54 virtual bool ShouldStopIterating(HWND window
) = 0;
57 const std::set
<HWND
>& ignore_
;
59 DISALLOW_COPY_AND_ASSIGN(BaseWindowFinder
);
62 // TopMostFinder --------------------------------------------------------------
64 // Helper class to determine if a particular point of a window is not obscured
66 class TopMostFinder
: public BaseWindowFinder
{
68 // Returns true if |window| is the topmost window at the location
69 // |screen_loc|, not including the windows in |ignore|.
70 static bool IsTopMostWindowAtPoint(HWND window
,
71 const gfx::Point
& screen_loc
,
72 const std::set
<HWND
>& ignore
) {
73 TopMostFinder
finder(window
, screen_loc
, ignore
);
74 return finder
.is_top_most_
;
77 virtual bool ShouldStopIterating(HWND hwnd
) {
78 if (hwnd
== target_
) {
79 // Window is topmost, stop iterating.
84 if (!IsWindowVisible(hwnd
)) {
85 // The window isn't visible, keep iterating.
90 if (!GetWindowRect(hwnd
, &r
) || !PtInRect(&r
, screen_loc_
.ToPOINT())) {
91 // The window doesn't contain the point, keep iterating.
95 LONG ex_styles
= GetWindowLong(hwnd
, GWL_EXSTYLE
);
96 if (ex_styles
& WS_EX_TRANSPARENT
|| ex_styles
& WS_EX_LAYERED
) {
97 // Mouse events fall through WS_EX_TRANSPARENT windows, so we ignore them.
99 // WS_EX_LAYERED is trickier. Apps like Switcher create a totally
100 // transparent WS_EX_LAYERED window that is always on top. If we don't
101 // ignore WS_EX_LAYERED windows and there are totally transparent
102 // WS_EX_LAYERED windows then there are effectively holes on the screen
103 // that the user can't reattach tabs to. So we ignore them. This is a bit
104 // problematic in so far as WS_EX_LAYERED windows need not be totally
105 // transparent in which case we treat chrome windows as not being obscured
106 // when they really are, but this is better than not being able to
111 // hwnd is at the point. Make sure the point is within the windows region.
112 if (GetWindowRgn(hwnd
, tmp_region_
.Get()) == ERROR
) {
113 // There's no region on the window and the window contains the point. Stop
118 // The region is relative to the window's rect.
119 BOOL is_point_in_region
= PtInRegion(tmp_region_
.Get(),
120 screen_loc_
.x() - r
.left
, screen_loc_
.y() - r
.top
);
121 tmp_region_
= CreateRectRgn(0, 0, 0, 0);
122 // Stop iterating if the region contains the point.
123 return !!is_point_in_region
;
127 TopMostFinder(HWND window
,
128 const gfx::Point
& screen_loc
,
129 const std::set
<HWND
>& ignore
)
130 : BaseWindowFinder(ignore
),
132 screen_loc_(screen_loc
),
134 tmp_region_(CreateRectRgn(0, 0, 0, 0)) {
135 EnumWindows(WindowCallbackProc
, as_lparam());
138 // The window we're looking for.
141 // Location of window to find.
142 gfx::Point screen_loc_
;
144 // Is target_ the top most window? This is initially false but set to true
145 // in ShouldStopIterating if target_ is passed in.
148 base::win::ScopedRegion tmp_region_
;
150 DISALLOW_COPY_AND_ASSIGN(TopMostFinder
);
153 // WindowFinder ---------------------------------------------------------------
155 // Helper class to determine if a particular point contains a window from our
157 class LocalProcessWindowFinder
: public BaseWindowFinder
{
159 // Returns the hwnd from our process at screen_loc that is not obscured by
160 // another window. Returns NULL otherwise.
161 static gfx::NativeWindow
GetProcessWindowAtPoint(
162 const gfx::Point
& screen_loc
,
163 const std::set
<HWND
>& ignore
) {
164 LocalProcessWindowFinder
finder(screen_loc
, ignore
);
165 // Windows 8 has a window that appears first in the list of iterated
166 // windows, yet is not visually on top of everything.
167 // TODO(sky): figure out a better way to ignore this window.
168 if (finder
.result_
&&
169 ((base::win::OSInfo::GetInstance()->version() >=
170 base::win::VERSION_WIN8
) ||
171 TopMostFinder::IsTopMostWindowAtPoint(finder
.result_
, screen_loc
,
173 #if defined(USE_AURA)
174 return views::DesktopWindowTreeHostWin::GetContentWindowForHWND(
177 return finder
.result_
;
184 virtual bool ShouldStopIterating(HWND hwnd
) {
186 if (IsWindowVisible(hwnd
) && GetWindowRect(hwnd
, &r
) &&
187 PtInRect(&r
, screen_loc_
.ToPOINT())) {
195 LocalProcessWindowFinder(const gfx::Point
& screen_loc
,
196 const std::set
<HWND
>& ignore
)
197 : BaseWindowFinder(ignore
),
198 screen_loc_(screen_loc
),
200 EnumThreadWindows(GetCurrentThreadId(), WindowCallbackProc
, as_lparam());
203 // Position of the mouse.
204 gfx::Point screen_loc_
;
206 // The resulting window. This is initially null but set to true in
207 // ShouldStopIterating if an appropriate window is found.
210 DISALLOW_COPY_AND_ASSIGN(LocalProcessWindowFinder
);
213 // DockToWindowFinder ---------------------------------------------------------
215 // Helper class for creating a DockInfo from a specified point.
216 class DockToWindowFinder
: public BaseWindowFinder
{
218 // Returns the DockInfo for the specified point. If there is no docking
219 // position for the specified point the returned DockInfo has a type of NONE.
220 static DockInfo
GetDockInfoAtPoint(const gfx::Point
& screen_loc
,
221 const std::set
<HWND
>& ignore
) {
222 DockToWindowFinder
finder(screen_loc
, ignore
);
223 HWND hwnd
= views::HWNDForNativeWindow(finder
.result_
.window());
224 if (!finder
.result_
.window() ||
225 !TopMostFinder::IsTopMostWindowAtPoint(hwnd
,
226 finder
.result_
.hot_spot(),
228 finder
.result_
.set_type(DockInfo::NONE
);
230 return finder
.result_
;
234 virtual bool ShouldStopIterating(HWND hwnd
) {
235 #if defined(USE_AURA)
236 BrowserView
* window
= BrowserView::GetBrowserViewForNativeWindow(
237 views::DesktopWindowTreeHostWin::GetContentWindowForHWND(hwnd
));
239 BrowserView
* window
= BrowserView::GetBrowserViewForNativeWindow(hwnd
);
242 if (!window
|| !IsWindowVisible(hwnd
) ||
243 !GetWindowRect(hwnd
, &bounds
)) {
247 // Check the three corners we allow docking to. We don't currently allow
248 // docking to top of window as it conflicts with docking to the tab strip.
249 if (CheckPoint(hwnd
, bounds
.left
, (bounds
.top
+ bounds
.bottom
) / 2,
250 DockInfo::LEFT_OF_WINDOW
) ||
251 CheckPoint(hwnd
, bounds
.right
- 1, (bounds
.top
+ bounds
.bottom
) / 2,
252 DockInfo::RIGHT_OF_WINDOW
) ||
253 CheckPoint(hwnd
, (bounds
.left
+ bounds
.right
) / 2, bounds
.bottom
- 1,
254 DockInfo::BOTTOM_OF_WINDOW
)) {
261 DockToWindowFinder(const gfx::Point
& screen_loc
,
262 const std::set
<HWND
>& ignore
)
263 : BaseWindowFinder(ignore
),
264 screen_loc_(screen_loc
) {
265 gfx::Rect work_area
= gfx::Screen::GetNativeScreen()->
266 GetDisplayNearestPoint(screen_loc
).bounds();
267 if (!work_area
.IsEmpty()) {
268 result_
.set_monitor_bounds(work_area
);
269 EnumThreadWindows(GetCurrentThreadId(), WindowCallbackProc
, as_lparam());
273 bool CheckPoint(HWND hwnd
, int x
, int y
, DockInfo::Type type
) {
275 if (DockInfo::IsCloseToPoint(screen_loc_
, x
, y
, &in_enable_area
)) {
276 result_
.set_in_enable_area(in_enable_area
);
277 #if defined(USE_AURA)
279 views::DesktopWindowTreeHostWin::GetContentWindowForHWND(hwnd
));
281 result_
.set_window(hwnd
);
283 result_
.set_type(type
);
284 result_
.set_hot_spot(gfx::Point(x
, y
));
285 // Only show the hotspot if the monitor contains the bounds of the popup
286 // window. Otherwise we end with a weird situation where the popup window
287 // isn't completely visible.
288 return result_
.monitor_bounds().Contains(result_
.GetPopupRect());
293 // The location to look for.
294 gfx::Point screen_loc_
;
296 // The resulting DockInfo.
299 DISALLOW_COPY_AND_ASSIGN(DockToWindowFinder
);
302 std::set
<HWND
> RemapIgnoreSet(const std::set
<gfx::NativeView
>& ignore
) {
303 #if defined(USE_AURA)
304 std::set
<HWND
> hwnd_set
;
305 std::set
<gfx::NativeView
>::const_iterator it
= ignore
.begin();
306 for (; it
!= ignore
.end(); ++it
) {
307 HWND w
= (*it
)->GetDispatcher()->host()->GetAcceleratedWidget();
313 // NativeViews are already HWNDs on non-Aura Windows.
320 // DockInfo -------------------------------------------------------------------
323 DockInfo
DockInfo::GetDockInfoAtPoint(chrome::HostDesktopType host_desktop_type
,
324 const gfx::Point
& screen_point
,
325 const std::set
<gfx::NativeView
>& ignore
) {
326 #if defined(USE_AURA)
327 if (host_desktop_type
== chrome::HOST_DESKTOP_TYPE_ASH
)
328 return chrome::ash::GetDockInfoAtPointAsh(screen_point
, ignore
);
330 // Try docking to a window first.
331 DockInfo info
= DockToWindowFinder::GetDockInfoAtPoint(
332 screen_point
, RemapIgnoreSet(ignore
));
333 if (info
.type() != DockInfo::NONE
)
336 // No window relative positions. Try monitor relative positions.
337 const gfx::Rect
& m_bounds
= info
.monitor_bounds();
338 int mid_x
= m_bounds
.x() + m_bounds
.width() / 2;
339 int mid_y
= m_bounds
.y() + m_bounds
.height() / 2;
342 info
.CheckMonitorPoint(screen_point
, mid_x
, m_bounds
.y(),
343 DockInfo::MAXIMIZE
) ||
344 info
.CheckMonitorPoint(screen_point
, mid_x
, m_bounds
.bottom(),
345 DockInfo::BOTTOM_HALF
) ||
346 info
.CheckMonitorPoint(screen_point
, m_bounds
.x(), mid_y
,
347 DockInfo::LEFT_HALF
) ||
348 info
.CheckMonitorPoint(screen_point
, m_bounds
.right(), mid_y
,
349 DockInfo::RIGHT_HALF
);
354 gfx::NativeView
DockInfo::GetLocalProcessWindowAtPoint(
355 chrome::HostDesktopType host_desktop_type
,
356 const gfx::Point
& screen_point
,
357 const std::set
<gfx::NativeView
>& ignore
) {
358 #if defined(USE_AURA)
359 if (host_desktop_type
== chrome::HOST_DESKTOP_TYPE_ASH
)
360 return chrome::ash::GetLocalProcessWindowAtPointAsh(screen_point
, ignore
);
363 LocalProcessWindowFinder::GetProcessWindowAtPoint(
364 screen_point
, RemapIgnoreSet(ignore
));
367 #if defined(USE_AURA)
369 bool DockInfo::GetWindowBounds(gfx::Rect
* bounds
) const {
372 *bounds
= window_
->bounds();
376 void DockInfo::SizeOtherWindowTo(const gfx::Rect
& bounds
) const {
377 window_
->SetBounds(bounds
);
382 bool DockInfo::GetWindowBounds(gfx::Rect
* bounds
) const {
384 if (!window() || !GetWindowRect(window(), &window_rect
))
386 *bounds
= gfx::Rect(window_rect
);
390 void DockInfo::SizeOtherWindowTo(const gfx::Rect
& bounds
) const {
391 if (IsZoomed(window())) {
392 // We're docking relative to another window, we need to make sure the
393 // window we're docking to isn't maximized.
394 ShowWindow(window(), SW_RESTORE
| SW_SHOWNA
);
396 SetWindowPos(window(), HWND_TOP
, bounds
.x(), bounds
.y(), bounds
.width(),
397 bounds
.height(), SWP_NOACTIVATE
| SWP_NOOWNERZORDER
);
401 int DockInfo::GetHotSpotDeltaY() {
402 return Tab::GetMinimumUnselectedSize().height() - 1;