OLD | NEW |
| (Empty) |
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/tabs/dock_info.h" | |
6 | |
7 #include "base/win/scoped_gdi_object.h" | |
8 #include "base/win/windows_version.h" | |
9 #include "chrome/browser/ui/browser_list.h" | |
10 #include "chrome/browser/ui/browser_window.h" | |
11 #include "chrome/browser/ui/views/frame/browser_view.h" | |
12 #include "chrome/browser/ui/views/tabs/tab.h" | |
13 #include "ui/gfx/screen.h" | |
14 | |
15 namespace { | |
16 | |
17 // BaseWindowFinder ----------------------------------------------------------- | |
18 | |
19 // Base class used to locate a window. This is intended to be used with the | |
20 // various win32 functions that iterate over windows. | |
21 // | |
22 // A subclass need only override ShouldStopIterating to determine when | |
23 // iteration should stop. | |
24 class BaseWindowFinder { | |
25 public: | |
26 // Creates a BaseWindowFinder with the specified set of HWNDs to ignore. | |
27 explicit BaseWindowFinder(const std::set<HWND>& ignore) : ignore_(ignore) {} | |
28 virtual ~BaseWindowFinder() {} | |
29 | |
30 protected: | |
31 static BOOL CALLBACK WindowCallbackProc(HWND hwnd, LPARAM lParam) { | |
32 // Cast must match that in as_lparam(). | |
33 BaseWindowFinder* finder = reinterpret_cast<BaseWindowFinder*>(lParam); | |
34 if (finder->ignore_.find(hwnd) != finder->ignore_.end()) | |
35 return TRUE; | |
36 | |
37 return finder->ShouldStopIterating(hwnd) ? FALSE : TRUE; | |
38 } | |
39 | |
40 LPARAM as_lparam() { | |
41 // Cast must match that in WindowCallbackProc(). | |
42 return reinterpret_cast<LPARAM>(static_cast<BaseWindowFinder*>(this)); | |
43 } | |
44 | |
45 // Returns true if iteration should stop, false if iteration should continue. | |
46 virtual bool ShouldStopIterating(HWND window) = 0; | |
47 | |
48 private: | |
49 const std::set<HWND>& ignore_; | |
50 | |
51 DISALLOW_COPY_AND_ASSIGN(BaseWindowFinder); | |
52 }; | |
53 | |
54 // TopMostFinder -------------------------------------------------------------- | |
55 | |
56 // Helper class to determine if a particular point of a window is not obscured | |
57 // by another window. | |
58 class TopMostFinder : public BaseWindowFinder { | |
59 public: | |
60 // Returns true if |window| is the topmost window at the location | |
61 // |screen_loc|, not including the windows in |ignore|. | |
62 static bool IsTopMostWindowAtPoint(HWND window, | |
63 const gfx::Point& screen_loc, | |
64 const std::set<HWND>& ignore) { | |
65 TopMostFinder finder(window, screen_loc, ignore); | |
66 return finder.is_top_most_; | |
67 } | |
68 | |
69 virtual bool ShouldStopIterating(HWND hwnd) { | |
70 if (hwnd == target_) { | |
71 // Window is topmost, stop iterating. | |
72 is_top_most_ = true; | |
73 return true; | |
74 } | |
75 | |
76 if (!IsWindowVisible(hwnd)) { | |
77 // The window isn't visible, keep iterating. | |
78 return false; | |
79 } | |
80 | |
81 RECT r; | |
82 if (!GetWindowRect(hwnd, &r) || !PtInRect(&r, screen_loc_.ToPOINT())) { | |
83 // The window doesn't contain the point, keep iterating. | |
84 return false; | |
85 } | |
86 | |
87 LONG ex_styles = GetWindowLong(hwnd, GWL_EXSTYLE); | |
88 if (ex_styles & WS_EX_TRANSPARENT || ex_styles & WS_EX_LAYERED) { | |
89 // Mouse events fall through WS_EX_TRANSPARENT windows, so we ignore them. | |
90 // | |
91 // WS_EX_LAYERED is trickier. Apps like Switcher create a totally | |
92 // transparent WS_EX_LAYERED window that is always on top. If we don't | |
93 // ignore WS_EX_LAYERED windows and there are totally transparent | |
94 // WS_EX_LAYERED windows then there are effectively holes on the screen | |
95 // that the user can't reattach tabs to. So we ignore them. This is a bit | |
96 // problematic in so far as WS_EX_LAYERED windows need not be totally | |
97 // transparent in which case we treat chrome windows as not being obscured | |
98 // when they really are, but this is better than not being able to | |
99 // reattach tabs. | |
100 return false; | |
101 } | |
102 | |
103 // hwnd is at the point. Make sure the point is within the windows region. | |
104 if (GetWindowRgn(hwnd, tmp_region_.Get()) == ERROR) { | |
105 // There's no region on the window and the window contains the point. Stop | |
106 // iterating. | |
107 return true; | |
108 } | |
109 | |
110 // The region is relative to the window's rect. | |
111 BOOL is_point_in_region = PtInRegion(tmp_region_.Get(), | |
112 screen_loc_.x() - r.left, screen_loc_.y() - r.top); | |
113 tmp_region_ = CreateRectRgn(0, 0, 0, 0); | |
114 // Stop iterating if the region contains the point. | |
115 return !!is_point_in_region; | |
116 } | |
117 | |
118 private: | |
119 TopMostFinder(HWND window, | |
120 const gfx::Point& screen_loc, | |
121 const std::set<HWND>& ignore) | |
122 : BaseWindowFinder(ignore), | |
123 target_(window), | |
124 screen_loc_(screen_loc), | |
125 is_top_most_(false), | |
126 tmp_region_(CreateRectRgn(0, 0, 0, 0)) { | |
127 EnumWindows(WindowCallbackProc, as_lparam()); | |
128 } | |
129 | |
130 // The window we're looking for. | |
131 HWND target_; | |
132 | |
133 // Location of window to find. | |
134 gfx::Point screen_loc_; | |
135 | |
136 // Is target_ the top most window? This is initially false but set to true | |
137 // in ShouldStopIterating if target_ is passed in. | |
138 bool is_top_most_; | |
139 | |
140 base::win::ScopedRegion tmp_region_; | |
141 | |
142 DISALLOW_COPY_AND_ASSIGN(TopMostFinder); | |
143 }; | |
144 | |
145 // WindowFinder --------------------------------------------------------------- | |
146 | |
147 // Helper class to determine if a particular point contains a window from our | |
148 // process. | |
149 class LocalProcessWindowFinder : public BaseWindowFinder { | |
150 public: | |
151 // Returns the hwnd from our process at screen_loc that is not obscured by | |
152 // another window. Returns NULL otherwise. | |
153 static HWND GetProcessWindowAtPoint(const gfx::Point& screen_loc, | |
154 const std::set<HWND>& ignore) { | |
155 LocalProcessWindowFinder finder(screen_loc, ignore); | |
156 // Windows 8 has a window that appears first in the list of iterated | |
157 // windows, yet is not visually on top of everything. | |
158 // TODO(sky): figure out a better way to ignore this window. | |
159 if (finder.result_ && | |
160 ((base::win::OSInfo::GetInstance()->version() >= | |
161 base::win::VERSION_WIN8) || | |
162 TopMostFinder::IsTopMostWindowAtPoint(finder.result_, screen_loc, | |
163 ignore))) { | |
164 return finder.result_; | |
165 } | |
166 return NULL; | |
167 } | |
168 | |
169 protected: | |
170 virtual bool ShouldStopIterating(HWND hwnd) { | |
171 RECT r; | |
172 if (IsWindowVisible(hwnd) && GetWindowRect(hwnd, &r) && | |
173 PtInRect(&r, screen_loc_.ToPOINT())) { | |
174 result_ = hwnd; | |
175 return true; | |
176 } | |
177 return false; | |
178 } | |
179 | |
180 private: | |
181 LocalProcessWindowFinder(const gfx::Point& screen_loc, | |
182 const std::set<HWND>& ignore) | |
183 : BaseWindowFinder(ignore), | |
184 screen_loc_(screen_loc), | |
185 result_(NULL) { | |
186 EnumThreadWindows(GetCurrentThreadId(), WindowCallbackProc, as_lparam()); | |
187 } | |
188 | |
189 // Position of the mouse. | |
190 gfx::Point screen_loc_; | |
191 | |
192 // The resulting window. This is initially null but set to true in | |
193 // ShouldStopIterating if an appropriate window is found. | |
194 HWND result_; | |
195 | |
196 DISALLOW_COPY_AND_ASSIGN(LocalProcessWindowFinder); | |
197 }; | |
198 | |
199 // DockToWindowFinder --------------------------------------------------------- | |
200 | |
201 // Helper class for creating a DockInfo from a specified point. | |
202 class DockToWindowFinder : public BaseWindowFinder { | |
203 public: | |
204 // Returns the DockInfo for the specified point. If there is no docking | |
205 // position for the specified point the returned DockInfo has a type of NONE. | |
206 static DockInfo GetDockInfoAtPoint(const gfx::Point& screen_loc, | |
207 const std::set<HWND>& ignore) { | |
208 DockToWindowFinder finder(screen_loc, ignore); | |
209 if (!finder.result_.window() || | |
210 !TopMostFinder::IsTopMostWindowAtPoint(finder.result_.window(), | |
211 finder.result_.hot_spot(), | |
212 ignore)) { | |
213 finder.result_.set_type(DockInfo::NONE); | |
214 } | |
215 return finder.result_; | |
216 } | |
217 | |
218 protected: | |
219 virtual bool ShouldStopIterating(HWND hwnd) { | |
220 BrowserView* window = BrowserView::GetBrowserViewForNativeWindow(hwnd); | |
221 RECT bounds; | |
222 if (!window || !IsWindowVisible(hwnd) || | |
223 !GetWindowRect(hwnd, &bounds)) { | |
224 return false; | |
225 } | |
226 | |
227 // Check the three corners we allow docking to. We don't currently allow | |
228 // docking to top of window as it conflicts with docking to the tab strip. | |
229 if (CheckPoint(hwnd, bounds.left, (bounds.top + bounds.bottom) / 2, | |
230 DockInfo::LEFT_OF_WINDOW) || | |
231 CheckPoint(hwnd, bounds.right - 1, (bounds.top + bounds.bottom) / 2, | |
232 DockInfo::RIGHT_OF_WINDOW) || | |
233 CheckPoint(hwnd, (bounds.left + bounds.right) / 2, bounds.bottom - 1, | |
234 DockInfo::BOTTOM_OF_WINDOW)) { | |
235 return true; | |
236 } | |
237 return false; | |
238 } | |
239 | |
240 private: | |
241 DockToWindowFinder(const gfx::Point& screen_loc, | |
242 const std::set<HWND>& ignore) | |
243 : BaseWindowFinder(ignore), | |
244 screen_loc_(screen_loc) { | |
245 gfx::Rect work_area = gfx::Screen::GetMonitorNearestPoint( | |
246 screen_loc).bounds(); | |
247 if (!work_area.IsEmpty()) { | |
248 result_.set_monitor_bounds(work_area); | |
249 EnumThreadWindows(GetCurrentThreadId(), WindowCallbackProc, as_lparam()); | |
250 } | |
251 } | |
252 | |
253 bool CheckPoint(HWND hwnd, int x, int y, DockInfo::Type type) { | |
254 bool in_enable_area; | |
255 if (DockInfo::IsCloseToPoint(screen_loc_, x, y, &in_enable_area)) { | |
256 result_.set_in_enable_area(in_enable_area); | |
257 result_.set_window(hwnd); | |
258 result_.set_type(type); | |
259 result_.set_hot_spot(gfx::Point(x, y)); | |
260 // Only show the hotspot if the monitor contains the bounds of the popup | |
261 // window. Otherwise we end with a weird situation where the popup window | |
262 // isn't completely visible. | |
263 return result_.monitor_bounds().Contains(result_.GetPopupRect()); | |
264 } | |
265 return false; | |
266 } | |
267 | |
268 // The location to look for. | |
269 gfx::Point screen_loc_; | |
270 | |
271 // The resulting DockInfo. | |
272 DockInfo result_; | |
273 | |
274 DISALLOW_COPY_AND_ASSIGN(DockToWindowFinder); | |
275 }; | |
276 | |
277 } // namespace | |
278 | |
279 // DockInfo ------------------------------------------------------------------- | |
280 | |
281 // static | |
282 DockInfo DockInfo::GetDockInfoAtPoint(const gfx::Point& screen_point, | |
283 const std::set<HWND>& ignore) { | |
284 if (factory_) | |
285 return factory_->GetDockInfoAtPoint(screen_point, ignore); | |
286 | |
287 // Try docking to a window first. | |
288 DockInfo info = DockToWindowFinder::GetDockInfoAtPoint(screen_point, ignore); | |
289 if (info.type() != DockInfo::NONE) | |
290 return info; | |
291 | |
292 // No window relative positions. Try monitor relative positions. | |
293 const gfx::Rect& m_bounds = info.monitor_bounds(); | |
294 int mid_x = m_bounds.x() + m_bounds.width() / 2; | |
295 int mid_y = m_bounds.y() + m_bounds.height() / 2; | |
296 | |
297 bool result = | |
298 info.CheckMonitorPoint(screen_point, mid_x, m_bounds.y(), | |
299 DockInfo::MAXIMIZE) || | |
300 info.CheckMonitorPoint(screen_point, mid_x, m_bounds.bottom(), | |
301 DockInfo::BOTTOM_HALF) || | |
302 info.CheckMonitorPoint(screen_point, m_bounds.x(), mid_y, | |
303 DockInfo::LEFT_HALF) || | |
304 info.CheckMonitorPoint(screen_point, m_bounds.right(), mid_y, | |
305 DockInfo::RIGHT_HALF); | |
306 | |
307 return info; | |
308 } | |
309 | |
310 HWND DockInfo::GetLocalProcessWindowAtPoint(const gfx::Point& screen_point, | |
311 const std::set<HWND>& ignore) { | |
312 if (factory_) | |
313 return factory_->GetLocalProcessWindowAtPoint(screen_point, ignore); | |
314 return | |
315 LocalProcessWindowFinder::GetProcessWindowAtPoint(screen_point, ignore); | |
316 } | |
317 | |
318 bool DockInfo::GetWindowBounds(gfx::Rect* bounds) const { | |
319 RECT window_rect; | |
320 if (!window() || !GetWindowRect(window(), &window_rect)) | |
321 return false; | |
322 *bounds = gfx::Rect(window_rect); | |
323 return true; | |
324 } | |
325 | |
326 void DockInfo::SizeOtherWindowTo(const gfx::Rect& bounds) const { | |
327 if (IsZoomed(window())) { | |
328 // We're docking relative to another window, we need to make sure the | |
329 // window we're docking to isn't maximized. | |
330 ShowWindow(window(), SW_RESTORE | SW_SHOWNA); | |
331 } | |
332 SetWindowPos(window(), HWND_TOP, bounds.x(), bounds.y(), bounds.width(), | |
333 bounds.height(), SWP_NOACTIVATE | SWP_NOOWNERZORDER); | |
334 } | |
OLD | NEW |