Home | History | Annotate | Download | only in tabs
      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