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