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