// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/dock_info.h" #include "base/basictypes.h" #include "base/logging.h" #include "chrome/browser/browser.h" #include "chrome/browser/browser_list.h" #include "chrome/browser/browser_window.h" #include "chrome/browser/views/frame/browser_view.h" #include "chrome/browser/views/tabs/tab.h" namespace { // Distance in pixels between the hotspot and when the hint should be shown. const int kHotSpotDeltaX = 120; const int kHotSpotDeltaY = 120; // Size of the popup window. const int kPopupWidth = 70; const int kPopupHeight = 70; // Returns true if |screen_loc| is close to the hotspot at |x|, |y|. If the // point is close enough to the hotspot true is returned and |in_enable_area| // is set appropriately. bool IsCloseToPoint(const gfx::Point& screen_loc, int x, int y, bool* in_enable_area) { int delta_x = abs(x - screen_loc.x()); int delta_y = abs(y - screen_loc.y()); *in_enable_area = (delta_x < kPopupWidth / 2 && delta_y < kPopupHeight / 2); return *in_enable_area || (delta_x < kHotSpotDeltaX && delta_y < kHotSpotDeltaY); } // Variant of IsCloseToPoint used for monitor relative positions. bool IsCloseToMonitorPoint(const gfx::Point& screen_loc, int x, int y, DockInfo::Type type, bool* in_enable_area) { // Because the monitor relative positions are aligned with the edge of the // monitor these need to be handled differently. int delta_x = abs(x - screen_loc.x()); int delta_y = abs(y - screen_loc.y()); int enable_delta_x = kPopupWidth / 2; int enable_delta_y = kPopupHeight / 2; int hot_spot_delta_x = kHotSpotDeltaX; int hot_spot_delta_y = kHotSpotDeltaY; switch (type) { case DockInfo::LEFT_HALF: case DockInfo::RIGHT_HALF: enable_delta_x += enable_delta_x; hot_spot_delta_x += hot_spot_delta_x; break; case DockInfo::MAXIMIZE: { // Make the maximize height smaller than the tab height to avoid showing // the dock indicator when close to maximized browser. hot_spot_delta_y = Tab::GetMinimumUnselectedSize().height() - 1; enable_delta_y = hot_spot_delta_y / 2; break; } case DockInfo::BOTTOM_HALF: enable_delta_y += enable_delta_y; hot_spot_delta_y += hot_spot_delta_y; break; default: NOTREACHED(); return false; } *in_enable_area = (delta_x < enable_delta_x && delta_y < enable_delta_y); bool result = (*in_enable_area || (delta_x < hot_spot_delta_x && delta_y < hot_spot_delta_y)); if (type != DockInfo::MAXIMIZE) return result; // Make the hot spot/enable spot for maximized windows the whole top of the // monitor. int max_delta_y = abs(screen_loc.y() - y); *in_enable_area = (*in_enable_area || (max_delta_y < enable_delta_y)); return *in_enable_area || (max_delta_y < hot_spot_delta_y); } // BaseWindowFinder ----------------------------------------------------------- // Base class used to locate a window. This is intended to be used with the // various win32 functions that iterate over windows. // // A subclass need only override ShouldStopIterating to determine when // iteration should stop. class BaseWindowFinder { public: // Creates a BaseWindowFinder with the specified set of HWNDs to ignore. BaseWindowFinder(const std::set& ignore) : ignore_(ignore) {} virtual ~BaseWindowFinder() {} protected: // Returns true if iteration should stop, false if iteration should continue. virtual bool ShouldStopIterating(HWND window) = 0; static BOOL CALLBACK WindowCallbackProc(HWND hwnd, LPARAM lParam) { BaseWindowFinder* finder = reinterpret_cast(lParam); if (finder->ignore_.find(hwnd) != finder->ignore_.end()) return TRUE; return finder->ShouldStopIterating(hwnd) ? FALSE : TRUE; } private: const std::set& ignore_; DISALLOW_COPY_AND_ASSIGN(BaseWindowFinder); }; // TopMostFinder -------------------------------------------------------------- // Helper class to determine if a particular point of a window is not obscured // by another window. class TopMostFinder : public BaseWindowFinder { public: // Returns true if |window| is the topmost window at the location // |screen_loc|, not including the windows in |ignore|. static bool IsTopMostWindowAtPoint(HWND window, const gfx::Point& screen_loc, const std::set& ignore) { TopMostFinder finder(window, screen_loc, ignore); return finder.is_top_most_; } virtual bool ShouldStopIterating(HWND hwnd) { if (hwnd == target_) { // Window is topmost, stop iterating. is_top_most_ = true; return true; } if (!::IsWindowVisible(hwnd)) { // The window isn't visible, keep iterating. return false; } CRect r; if (!::GetWindowRect(hwnd, &r) || !r.PtInRect(screen_loc_.ToPOINT())) { // The window doesn't contain the point, keep iterating. return false; } // hwnd is at the point. Make sure the point is within the windows region. if (GetWindowRgn(hwnd, tmp_region_.Get()) == ERROR) { // There's no region on the window and the window contains the point. Stop // iterating. return true; } // The region is relative to the window's rect. BOOL is_point_in_region = PtInRegion(tmp_region_.Get(), screen_loc_.x() - r.left, screen_loc_.y() - r.top); tmp_region_ = CreateRectRgn(0, 0, 0, 0); // Stop iterating if the region contains the point. return !!is_point_in_region; } private: TopMostFinder(HWND window, const gfx::Point& screen_loc, const std::set& ignore) : BaseWindowFinder(ignore), target_(window), screen_loc_(screen_loc), is_top_most_(false), tmp_region_(CreateRectRgn(0, 0, 0, 0)) { EnumWindows(WindowCallbackProc, reinterpret_cast(this)); } // The window we're looking for. HWND target_; // Location of window to find. gfx::Point screen_loc_; // Is target_ the top most window? This is initially false but set to true // in ShouldStopIterating if target_ is passed in. bool is_top_most_; ScopedHRGN tmp_region_; DISALLOW_COPY_AND_ASSIGN(TopMostFinder); }; // WindowFinder --------------------------------------------------------------- // Helper class to determine if a particular point contains a window from our // process. class LocalProcessWindowFinder : public BaseWindowFinder { public: // Returns the hwnd from our process at screen_loc that is not obscured by // another window. Returns NULL otherwise. static HWND GetProcessWindowAtPoint(const gfx::Point& screen_loc, const std::set& ignore) { LocalProcessWindowFinder finder(screen_loc, ignore); if (finder.result_ && TopMostFinder::IsTopMostWindowAtPoint(finder.result_, screen_loc, ignore)) { return finder.result_; } return NULL; } protected: virtual bool ShouldStopIterating(HWND hwnd) { CRect r; if (::IsWindowVisible(hwnd) && ::GetWindowRect(hwnd, &r) && r.PtInRect(screen_loc_.ToPOINT())) { result_ = hwnd; return true; } return false; } private: LocalProcessWindowFinder(const gfx::Point& screen_loc, const std::set& ignore) : BaseWindowFinder(ignore), screen_loc_(screen_loc), result_(NULL) { EnumThreadWindows(GetCurrentThreadId(), WindowCallbackProc, reinterpret_cast(this)); } // Position of the mouse. gfx::Point screen_loc_; // The resulting window. This is initially null but set to true in // ShouldStopIterating if an appropriate window is found. HWND result_; DISALLOW_COPY_AND_ASSIGN(LocalProcessWindowFinder); }; // DockToWindowFinder --------------------------------------------------------- // Helper class for creating a DockInfo from a specified point. class DockToWindowFinder : public BaseWindowFinder { public: // Returns the DockInfo for the specified point. If there is no docking // position for the specified point the returned DockInfo has a type of NONE. static DockInfo GetDockInfoAtPoint(const gfx::Point& screen_loc, const std::set& ignore) { DockToWindowFinder finder(screen_loc, ignore); if (!finder.result_.hwnd() || !TopMostFinder::IsTopMostWindowAtPoint(finder.result_.hwnd(), finder.result_.hot_spot(), ignore)) { finder.result_.set_type(DockInfo::NONE); } return finder.result_; } protected: virtual bool ShouldStopIterating(HWND hwnd) { BrowserView* window = BrowserView::GetBrowserViewForHWND(hwnd); CRect bounds; if (!window || !::IsWindowVisible(hwnd) || !::GetWindowRect(hwnd, &bounds)) { return false; } // Check the three corners we allow docking to. We don't currently allow // docking to top of window as it conflicts with docking to the tab strip. if (CheckPoint(hwnd, bounds.left, (bounds.top + bounds.bottom) / 2, DockInfo::LEFT_OF_WINDOW) || CheckPoint(hwnd, bounds.right - 1, (bounds.top + bounds.bottom) / 2, DockInfo::RIGHT_OF_WINDOW) || CheckPoint(hwnd, (bounds.left + bounds.right) / 2, bounds.bottom - 1, DockInfo::BOTTOM_OF_WINDOW)) { return true; } return false; } private: DockToWindowFinder(const gfx::Point& screen_loc, const std::set& ignore) : BaseWindowFinder(ignore), screen_loc_(screen_loc) { HMONITOR monitor = MonitorFromPoint(screen_loc.ToPOINT(), MONITOR_DEFAULTTONULL); MONITORINFO monitor_info = {0}; monitor_info.cbSize = sizeof(MONITORINFO); if (monitor && GetMonitorInfo(monitor, &monitor_info)) { result_.set_monitor_bounds(gfx::Rect(monitor_info.rcWork)); EnumThreadWindows(GetCurrentThreadId(), WindowCallbackProc, reinterpret_cast(this)); } } bool CheckPoint(HWND hwnd, int x, int y, DockInfo::Type type) { bool in_enable_area; if (IsCloseToPoint(screen_loc_, x, y, &in_enable_area)) { result_.set_in_enable_area(in_enable_area); result_.set_hwnd(hwnd); result_.set_type(type); result_.set_hot_spot(gfx::Point(x, y)); // Only show the hotspot if the monitor contains the bounds of the popup // window. Otherwise we end with a weird situation where the popup window // isn't completely visible. return result_.monitor_bounds().Contains(result_.GetPopupRect()); } return false; } // The location to look for. gfx::Point screen_loc_; // The resulting DockInfo. DockInfo result_; DISALLOW_COPY_AND_ASSIGN(DockToWindowFinder); }; } // namespace // DockInfo ------------------------------------------------------------------- // static DockInfo DockInfo::GetDockInfoAtPoint(const gfx::Point& screen_point, const std::set& ignore) { // Try docking to a window first. DockInfo info = DockToWindowFinder::GetDockInfoAtPoint(screen_point, ignore); if (info.type() != DockInfo::NONE) return info; // No window relative positions. Try monitor relative positions. const gfx::Rect& m_bounds = info.monitor_bounds(); int mid_x = m_bounds.x() + m_bounds.width() / 2; int mid_y = m_bounds.y() + m_bounds.height() / 2; bool result = info.CheckMonitorPoint(screen_point, mid_x, m_bounds.y(), DockInfo::MAXIMIZE) || info.CheckMonitorPoint(screen_point, mid_x, m_bounds.bottom(), DockInfo::BOTTOM_HALF) || info.CheckMonitorPoint(screen_point, m_bounds.x(), mid_y, DockInfo::LEFT_HALF) || info.CheckMonitorPoint(screen_point, m_bounds.right(), mid_y, DockInfo::RIGHT_HALF); return info; } // static int DockInfo::popup_width() { return kPopupWidth; } // static int DockInfo::popup_height() { return kPopupHeight; } HWND DockInfo::GetLocalProcessWindowAtPoint(const gfx::Point& screen_point, const std::set& ignore) { return LocalProcessWindowFinder::GetProcessWindowAtPoint(screen_point, ignore); } bool DockInfo::IsValidForPoint(const gfx::Point& screen_point) { if (type_ == NONE) return false; if (hwnd_) { return IsCloseToPoint(screen_point, hot_spot_.x(), hot_spot_.y(), &in_enable_area_); } return monitor_bounds_.Contains(screen_point) && IsCloseToMonitorPoint(screen_point, hot_spot_.x(), hot_spot_.y(), type_, &in_enable_area_); } bool DockInfo::GetNewWindowBounds(gfx::Rect* new_window_bounds, bool* maximize_new_window) const { if (type_ == NONE || !in_enable_area_) return false; RECT window_rect; if (hwnd_ && !GetWindowRect(hwnd_, &window_rect)) return false; int half_m_width = (monitor_bounds_.right() - monitor_bounds_.x()) / 2; int half_m_height = (monitor_bounds_.bottom() - monitor_bounds_.y()) / 2; bool unmaximize_other_window = false; *maximize_new_window = false; switch (type_) { case LEFT_OF_WINDOW: new_window_bounds->SetRect(monitor_bounds_.x(), window_rect.top, half_m_width, window_rect.bottom - window_rect.top); break; case RIGHT_OF_WINDOW: new_window_bounds->SetRect(monitor_bounds_.x() + half_m_width, window_rect.top, half_m_width, window_rect.bottom - window_rect.top); break; case TOP_OF_WINDOW: new_window_bounds->SetRect(window_rect.left, monitor_bounds_.y(), window_rect.right - window_rect.left, half_m_height); break; case BOTTOM_OF_WINDOW: new_window_bounds->SetRect(window_rect.left, monitor_bounds_.y() + half_m_height, window_rect.right - window_rect.left, half_m_height); break; case LEFT_HALF: new_window_bounds->SetRect(monitor_bounds_.x(), monitor_bounds_.y(), half_m_width, monitor_bounds_.height()); break; case RIGHT_HALF: new_window_bounds->SetRect(monitor_bounds_.right() - half_m_width, monitor_bounds_.y(), half_m_width, monitor_bounds_.height()); break; case BOTTOM_HALF: new_window_bounds->SetRect(monitor_bounds_.x(), monitor_bounds_.y() + half_m_height, monitor_bounds_.width(), half_m_height); break; case MAXIMIZE: *maximize_new_window = true; break; default: NOTREACHED(); } return true; } void DockInfo::AdjustOtherWindowBounds() const { if (!in_enable_area_) return; RECT window_rect; if (!hwnd_ || !GetWindowRect(hwnd_, &window_rect)) return; gfx::Rect other_window_bounds; int half_m_width = (monitor_bounds_.right() - monitor_bounds_.x()) / 2; int half_m_height = (monitor_bounds_.bottom() - monitor_bounds_.y()) / 2; switch (type_) { case LEFT_OF_WINDOW: other_window_bounds.SetRect(monitor_bounds_.x() + half_m_width, window_rect.top, half_m_width, window_rect.bottom - window_rect.top); break; case RIGHT_OF_WINDOW: other_window_bounds.SetRect(monitor_bounds_.x(), window_rect.top, half_m_width, window_rect.bottom - window_rect.top); break; case TOP_OF_WINDOW: other_window_bounds.SetRect(window_rect.left, monitor_bounds_.y() + half_m_height, window_rect.right - window_rect.left, half_m_height); break; case BOTTOM_OF_WINDOW: other_window_bounds.SetRect(window_rect.left, monitor_bounds_.y(), window_rect.right - window_rect.left, half_m_height); break; default: return; } if (IsZoomed(hwnd_)) { // We're docking relative to another window, we need to make sure the // window we're docking to isn't maximized. ShowWindow(hwnd_, SW_RESTORE | SW_SHOWNA); } ::SetWindowPos(hwnd_, HWND_TOP, other_window_bounds.x(), other_window_bounds.y(), other_window_bounds.width(), other_window_bounds.height(), SWP_NOACTIVATE | SWP_NOOWNERZORDER); } gfx::Rect DockInfo::GetPopupRect() const { int x = hot_spot_.x() - popup_width() / 2; int y = hot_spot_.y() - popup_height() / 2; switch (type_) { case LEFT_OF_WINDOW: case RIGHT_OF_WINDOW: case TOP_OF_WINDOW: case BOTTOM_OF_WINDOW: { // Constrain the popup to the monitor's bounds. gfx::Rect ideal_bounds(x, y, popup_width(), popup_height()); ideal_bounds = ideal_bounds.AdjustToFit(monitor_bounds_); return ideal_bounds; } case DockInfo::MAXIMIZE: y += popup_height() / 2; break; case DockInfo::LEFT_HALF: x += popup_width() / 2; break; case DockInfo::RIGHT_HALF: x -= popup_width() / 2; break; case DockInfo::BOTTOM_HALF: y -= popup_height() / 2; break; default: NOTREACHED(); } return gfx::Rect(x, y, popup_width(), popup_height()); } bool DockInfo::CheckMonitorPoint(const gfx::Point& screen_loc, int x, int y, Type type) { if (IsCloseToMonitorPoint(screen_loc, x, y, type, &in_enable_area_)) { hot_spot_.SetPoint(x, y); type_ = type; return true; } return false; }