// Copyright (c) 2011 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/ui/tabs/dock_info.h"
#include "base/win/scoped_gdi_object.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/tabs/tab.h"
#include "views/screen.h"
namespace {
// 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.
explicit BaseWindowFinder(const std::set<HWND>& 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<BaseWindowFinder*>(lParam);
if (finder->ignore_.find(hwnd) != finder->ignore_.end())
return TRUE;
return finder->ShouldStopIterating(hwnd) ? FALSE : TRUE;
}
private:
const std::set<HWND>& 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<HWND>& 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;
}
RECT r;
if (!GetWindowRect(hwnd, &r) || !PtInRect(&r, screen_loc_.ToPOINT())) {
// The window doesn't contain the point, keep iterating.
return false;
}
LONG ex_styles = GetWindowLong(hwnd, GWL_EXSTYLE);
if (ex_styles & WS_EX_TRANSPARENT || ex_styles & WS_EX_LAYERED) {
// Mouse events fall through WS_EX_TRANSPARENT windows, so we ignore them.
//
// WS_EX_LAYERED is trickier. Apps like Switcher create a totally
// transparent WS_EX_LAYERED window that is always on top. If we don't
// ignore WS_EX_LAYERED windows and there are totally transparent
// WS_EX_LAYERED windows then there are effectively holes on the screen
// that the user can't reattach tabs to. So we ignore them. This is a bit
// problematic in so far as WS_EX_LAYERED windows need not be totally
// transparent in which case we treat chrome windows as not being obscured
// when they really are, but this is better than not being able to
// reattach tabs.
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<HWND>& ignore)
: BaseWindowFinder(ignore),
target_(window),
screen_loc_(screen_loc),
is_top_most_(false),
tmp_region_(CreateRectRgn(0, 0, 0, 0)) {
EnumWindows(WindowCallbackProc, reinterpret_cast<LPARAM>(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_;
base::win::ScopedRegion 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<HWND>& 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) {
RECT r;
if (IsWindowVisible(hwnd) && GetWindowRect(hwnd, &r) &&
PtInRect(&r, screen_loc_.ToPOINT())) {
result_ = hwnd;
return true;
}
return false;
}
private:
LocalProcessWindowFinder(const gfx::Point& screen_loc,
const std::set<HWND>& ignore)
: BaseWindowFinder(ignore),
screen_loc_(screen_loc),
result_(NULL) {
EnumThreadWindows(GetCurrentThreadId(), WindowCallbackProc,
reinterpret_cast<LPARAM>(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<HWND>& ignore) {
DockToWindowFinder finder(screen_loc, ignore);
if (!finder.result_.window() ||
!TopMostFinder::IsTopMostWindowAtPoint(finder.result_.window(),
finder.result_.hot_spot(),
ignore)) {
finder.result_.set_type(DockInfo::NONE);
}
return finder.result_;
}
protected:
virtual bool ShouldStopIterating(HWND hwnd) {
BrowserView* window = BrowserView::GetBrowserViewForNativeWindow(hwnd);
RECT 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<HWND>& ignore)
: BaseWindowFinder(ignore),
screen_loc_(screen_loc) {
gfx::Rect work_area = views::Screen::GetMonitorWorkAreaNearestPoint(
screen_loc);
if (!work_area.IsEmpty()) {
result_.set_monitor_bounds(work_area);
EnumThreadWindows(GetCurrentThreadId(), WindowCallbackProc,
reinterpret_cast<LPARAM>(this));
}
}
bool CheckPoint(HWND hwnd, int x, int y, DockInfo::Type type) {
bool in_enable_area;
if (DockInfo::IsCloseToPoint(screen_loc_, x, y, &in_enable_area)) {
result_.set_in_enable_area(in_enable_area);
result_.set_window(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<HWND>& ignore) {
if (factory_)
return factory_->GetDockInfoAtPoint(screen_point, 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;
}
HWND DockInfo::GetLocalProcessWindowAtPoint(const gfx::Point& screen_point,
const std::set<HWND>& ignore) {
if (factory_)
return factory_->GetLocalProcessWindowAtPoint(screen_point, ignore);
return
LocalProcessWindowFinder::GetProcessWindowAtPoint(screen_point, ignore);
}
bool DockInfo::GetWindowBounds(gfx::Rect* bounds) const {
RECT window_rect;
if (!window() || !GetWindowRect(window(), &window_rect))
return false;
*bounds = gfx::Rect(window_rect);
return true;
}
void DockInfo::SizeOtherWindowTo(const gfx::Rect& bounds) const {
if (IsZoomed(window())) {
// We're docking relative to another window, we need to make sure the
// window we're docking to isn't maximized.
ShowWindow(window(), SW_RESTORE | SW_SHOWNA);
}
SetWindowPos(window(), HWND_TOP, bounds.x(), bounds.y(), bounds.width(),
bounds.height(), SWP_NOACTIVATE | SWP_NOOWNERZORDER);
}