blob: f21ec73977e6dbe9daf5eac73d5a0571b641276a [file] [log] [blame]
// 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"
namespace {
// Distance in pixels between the hotspot and when the hint should be shown.
const int kHotSpotDeltaX = 100;
const int kHotSpotDeltaY = 80;
// Distance in pixels between the hotspot and when the hint should be shown
// and enabled.
const int kEnableDeltaX = 50;
const int kEnableDeltaY = 37;
// Distance used when maximizing. The maximize area is the whole top of the
// monitor.
const int kMaximizeHotSpotDeltaY = 100;
const int kMaximizeEnableDeltaY = 50;
// 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 < kEnableDeltaX && delta_y < kEnableDeltaY);
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 = kEnableDeltaX;
int enable_delta_y = kEnableDeltaY;
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:
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 maximize area the whole top of the monitor.
int max_delta_y = abs(screen_loc.y() - y);
*in_enable_area = (*in_enable_area || (max_delta_y < kMaximizeEnableDeltaY));
return *in_enable_area || (max_delta_y < kMaximizeHotSpotDeltaY);
}
// Returns true if there is a maximized tabbed browser on the monitor
// |monitor|.
bool IsMaximizedTabbedBrowserOnMonitor(HMONITOR monitor) {
for (BrowserList::const_iterator i = BrowserList::begin();
i != BrowserList::end(); ++i) {
Browser* browser = *i;
if (browser->type() == Browser::TYPE_NORMAL) {
HWND browser_hwnd =
reinterpret_cast<HWND>(browser->window()->GetNativeHandle());
if (IsZoomed(browser_hwnd) &&
MonitorFromWindow(browser_hwnd, MONITOR_DEFAULTTONEAREST) ==
monitor) {
return true;
}
}
}
return false;
}
// 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<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)) {
CRect r;
if (::GetWindowRect(hwnd, &r) && r.PtInRect(screen_loc_.ToPOINT())) {
// Not the topmost, stop iterating.
return true;
}
}
return false;
}
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) {
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_;
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) {
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<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_.hwnd() ||
!TopMostFinder::IsTopMostWindowAtPoint(finder.result_.hwnd(),
finder.result_.hot_spot(),
ignore)) {
return DockInfo();
}
return finder.result_;
}
protected:
virtual bool ShouldStopIterating(HWND hwnd) {
BrowserWindow* window = BrowserView::GetBrowserWindowForHWND(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<HWND>& ignore)
: BaseWindowFinder(ignore),
screen_loc_(screen_loc) {
EnumThreadWindows(GetCurrentThreadId(), WindowCallbackProc,
reinterpret_cast<LPARAM>(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));
return true;
}
return false;
}
// The location to look for.
gfx::Point screen_loc_;
// The resulting DockInfo.
DockInfo result_;
DISALLOW_COPY_AND_ASSIGN(DockToWindowFinder);
};
} // namespace
// DockInfo -------------------------------------------------------------------
DockInfo DockInfo::GetDockInfoAtPoint(const gfx::Point& screen_point,
const std::set<HWND>& ignore) {
// Try docking to a window first.
DockInfo info = DockToWindowFinder::GetDockInfoAtPoint(screen_point, ignore);
HMONITOR monitor = MonitorFromPoint(screen_point.ToPOINT(),
MONITOR_DEFAULTTONULL);
MONITORINFO monitor_info = {0};
monitor_info.cbSize = sizeof(MONITORINFO);
if (!monitor || !GetMonitorInfo(monitor, &monitor_info)) {
info.type_ = NONE;
return info;
}
info.monitor_bounds_ = gfx::Rect(monitor_info.rcWork);
if (info.type() != DockInfo::NONE)
return info;
// No window relative positions. Try monitor relative positions.
RECT& m_bounds = monitor_info.rcWork;
int mid_x = (m_bounds.left + m_bounds.right) / 2;
int mid_y = (m_bounds.top + m_bounds.bottom) / 2;
bool result =
info.CheckMonitorPoint(monitor, screen_point, mid_x, m_bounds.top,
DockInfo::MAXIMIZE) ||
info.CheckMonitorPoint(monitor, screen_point, mid_x, m_bounds.bottom,
DockInfo::BOTTOM_HALF) ||
info.CheckMonitorPoint(monitor, screen_point, m_bounds.left, mid_y,
DockInfo::LEFT_HALF) ||
info.CheckMonitorPoint(monitor, 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) {
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);
}
bool DockInfo::CheckMonitorPoint(HMONITOR monitor,
const gfx::Point& screen_loc,
int x,
int y,
Type type) {
if (IsCloseToMonitorPoint(screen_loc, x, y, type, &in_enable_area_) &&
(type != MAXIMIZE ||
!IsMaximizedTabbedBrowserOnMonitor(monitor))) {
hot_spot_.SetPoint(x, y);
type_ = type;
return true;
}
return false;
}