x11_whole_screen_move_loop.cc revision 5c02ac1a9c1b504631c0a3d2b6e737b5d738bae1
190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved.
290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)// found in the LICENSE file.
490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#include "ui/views/widget/desktop_aura/x11_whole_screen_move_loop.h"
690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#include <X11/Xlib.h>
890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)// Get rid of a macro from Xlib.h that conflicts with Aura's RootWindow class.
990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#undef RootWindow
1090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
11effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch#include "base/bind.h"
127d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)#include "base/message_loop/message_loop.h"
1390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#include "base/run_loop.h"
14a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)#include "third_party/skia/include/core/SkBitmap.h"
1590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#include "ui/aura/env.h"
16f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "ui/aura/window.h"
17a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)#include "ui/aura/window_event_dispatcher.h"
18f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "ui/aura/window_tree_host.h"
1990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#include "ui/base/x/x11_util.h"
20d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)#include "ui/events/event.h"
215c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu#include "ui/events/event_utils.h"
22a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)#include "ui/events/keycodes/keyboard_code_conversion_x.h"
235c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu#include "ui/events/platform/scoped_event_dispatcher.h"
24c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch#include "ui/events/platform/x11/x11_event_source.h"
25f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "ui/gfx/point_conversions.h"
2690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#include "ui/gfx/screen.h"
27f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "ui/views/controls/image_view.h"
28f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "ui/views/widget/widget.h"
2990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
3090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)namespace views {
3190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
32f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)namespace {
33f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
34a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)// The minimum alpha before we declare a pixel transparent when searching in
35a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)// our source image.
36a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)const uint32 kMinAlpha = 32;
37effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochconst unsigned char kDragWidgetOpacity = 0xc0;
38a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
39f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)class ScopedCapturer {
40f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) public:
415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  explicit ScopedCapturer(aura::WindowTreeHost* host)
42f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      : host_(host) {
43f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    host_->SetCapture();
44f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  }
45f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
46f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  ~ScopedCapturer() {
47f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    host_->ReleaseCapture();
48f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  }
49f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
50f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles) private:
515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  aura::WindowTreeHost* host_;
52f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
53f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  DISALLOW_COPY_AND_ASSIGN(ScopedCapturer);
54f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)};
55f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
56f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)}  // namespace
57f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
5890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)X11WholeScreenMoveLoop::X11WholeScreenMoveLoop(
5990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    X11WholeScreenMoveLoopDelegate* delegate)
6090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    : delegate_(delegate),
6190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)      in_move_loop_(false),
625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      should_reset_mouse_flags_(false),
63effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      grab_input_window_(None),
64c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch      canceled_(false),
65effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      weak_factory_(this) {
66effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  last_xmotion_.type = LASTEvent;
6790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)}
6890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
6990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)X11WholeScreenMoveLoop::~X11WholeScreenMoveLoop() {}
7090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
71effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochvoid X11WholeScreenMoveLoop::DispatchMouseMovement() {
72effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  if (!weak_factory_.HasWeakPtrs())
73effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    return;
74effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  weak_factory_.InvalidateWeakPtrs();
75effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  DCHECK_EQ(MotionNotify, last_xmotion_.type);
76effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  delegate_->OnMouseMovement(&last_xmotion_);
77effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  last_xmotion_.type = LASTEvent;
78effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch}
79effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
80c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch////////////////////////////////////////////////////////////////////////////////
81c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch// DesktopWindowTreeHostLinux, ui::PlatformEventDispatcher implementation:
82c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch
83c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdochbool X11WholeScreenMoveLoop::CanDispatchEvent(const ui::PlatformEvent& event) {
845c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  return in_move_loop_;
85c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch}
86c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch
87c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdochuint32_t X11WholeScreenMoveLoop::DispatchEvent(const ui::PlatformEvent& event) {
885c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // This method processes all events for the grab_input_window_ as well as
895c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // mouse events for all windows while the move loop is active - even before
905c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // the grab is granted by X. This allows mouse notification events that were
915c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // sent after the capture was requested but before the capture was granted
925c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // to be dispatched. It is especially important to process the mouse release
935c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // event that should have stopped the drag even if that mouse release happened
945c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // before the grab was granted.
955c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (!in_move_loop_)
965c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return ui::POST_DISPATCH_PERFORM_DEFAULT;
9790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  XEvent* xev = event;
9890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
9990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  // Note: the escape key is handled in the tab drag controller, which has
10090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  // keyboard focus even though we took pointer grab.
10190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  switch (xev->type) {
10290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    case MotionNotify: {
103f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      if (drag_widget_.get()) {
104f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        gfx::Screen* screen = gfx::Screen::GetNativeScreen();
105f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        gfx::Point location = gfx::ToFlooredPoint(
106f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            screen->GetCursorScreenPoint() - drag_offset_);
107f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        drag_widget_->SetBounds(gfx::Rect(location, drag_image_.size()));
108effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch        drag_widget_->StackAtTop();
109effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      }
110effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      last_xmotion_ = xev->xmotion;
111effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      if (!weak_factory_.HasWeakPtrs()) {
112effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch        // Post a task to dispatch mouse movement event when control returns to
113effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch        // the message loop. This allows smoother dragging since the events are
114effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch        // dispatched without waiting for the drag widget updates.
115effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch        base::MessageLoopForUI::current()->PostTask(
116effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch            FROM_HERE,
117effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch            base::Bind(&X11WholeScreenMoveLoop::DispatchMouseMovement,
118effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                       weak_factory_.GetWeakPtr()));
119f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      }
1205c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      return ui::POST_DISPATCH_NONE;
12190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    }
12290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    case ButtonRelease: {
123eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      if (xev->xbutton.button == Button1) {
124eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        // Assume that drags are being done with the left mouse button. Only
125eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        // break the drag if the left mouse button was released.
126effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch        DispatchMouseMovement();
127eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        delegate_->OnMouseReleased();
128eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      }
1295c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      return ui::POST_DISPATCH_NONE;
13090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    }
131a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    case KeyPress: {
132c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch      if (ui::KeyboardCodeFromXKeyEvent(xev) == ui::VKEY_ESCAPE) {
133c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch        canceled_ = true;
134a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        EndMoveLoop();
1355c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        return ui::POST_DISPATCH_NONE;
136c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch      }
137a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      break;
138a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    }
1395c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    case GenericEvent: {
1405c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      ui::EventType type = ui::EventTypeFromNative(xev);
1415c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      switch (type) {
1425c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        case ui::ET_MOUSE_MOVED:
1435c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        case ui::ET_MOUSE_DRAGGED:
1445c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        case ui::ET_MOUSE_RELEASED: {
1455c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          XEvent xevent = {0};
1465c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          if (type == ui::ET_MOUSE_RELEASED) {
1475c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu            xevent.type = ButtonRelease;
1485c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu            xevent.xbutton.button = ui::EventButtonFromNative(xev);
1495c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          } else {
1505c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu            xevent.type = MotionNotify;
1515c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          }
1525c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          xevent.xany.display = xev->xgeneric.display;
1535c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          xevent.xany.window = grab_input_window_;
1545c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          // The fields used below are in the same place for all of events
1555c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          // above. Using xmotion from XEvent's unions to avoid repeating
1565c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          // the code.
1575c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          xevent.xmotion.root = DefaultRootWindow(xev->xgeneric.display);
1585c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          xevent.xmotion.time = ui::EventTimeFromNative(xev).InMilliseconds();
1595c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          gfx::Point point(ui::EventSystemLocationFromNative(xev));
1605c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          xevent.xmotion.x_root = point.x();
1615c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          xevent.xmotion.y_root = point.y();
1625c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          DispatchEvent(&xevent);
1635c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          return ui::POST_DISPATCH_NONE;
1645c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        }
1655c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        default:
1665c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          break;
1675c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      }
1685c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    }
16990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  }
17090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
1715c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  return (event->xany.window == grab_input_window_) ?
1725c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      ui::POST_DISPATCH_NONE : ui::POST_DISPATCH_PERFORM_DEFAULT;
17390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)}
17490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
1757dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdochbool X11WholeScreenMoveLoop::RunMoveLoop(aura::Window* source,
1767dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch                                         gfx::NativeCursor cursor) {
1770529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  DCHECK(!in_move_loop_);  // Can only handle one nested loop at a time.
1780529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch
179f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // Start a capture on the host, so that it continues to receive events during
180a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // the drag. This may be second time we are capturing the mouse events - the
181a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // first being when a mouse is first pressed. That first capture needs to be
182a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // released before the call to GrabPointerAndKeyboard below, otherwise it may
183a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // get released while we still need the pointer grab, which is why we restrict
184a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // the scope here.
185a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  {
186a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    ScopedCapturer capturer(source->GetHost());
187a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
1880529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    grab_input_window_ = CreateDragInputWindow(gfx::GetXDisplay());
189a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    // Releasing ScopedCapturer ensures that any other instance of
190a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    // X11ScopedCapture will not prematurely release grab that will be acquired
191a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    // below.
192a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  }
193a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // TODO(varkha): Consider integrating GrabPointerAndKeyboard with
194a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // ScopedCapturer to avoid possibility of logically keeping multiple grabs.
1950529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  if (!GrabPointerAndKeyboard(cursor)) {
1960529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    XDestroyWindow(gfx::GetXDisplay(), grab_input_window_);
19790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    return false;
1980529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  }
1990529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch
2005c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  scoped_ptr<ui::ScopedEventDispatcher> old_dispatcher =
2015c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      nested_dispatcher_.Pass();
2025c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  nested_dispatcher_ =
2035c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu         ui::PlatformEventSource::GetInstance()->OverrideDispatcher(this);
2040529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  if (!drag_image_.isNull() && CheckIfIconValid())
2050529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    CreateDragImageWindow();
20690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
2071e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)  // We are handling a mouse drag outside of the aura::RootWindow system. We
2081e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)  // must manually make aura think that the mouse button is pressed so that we
2091e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)  // don't draw extraneous tooltips.
2105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  aura::Env* env = aura::Env::GetInstance();
2115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if (!env->IsMouseButtonDown()) {
2125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    env->set_mouse_button_flags(ui::EF_LEFT_MOUSE_BUTTON);
2135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    should_reset_mouse_flags_ = true;
2145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  }
2151e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
2160529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  in_move_loop_ = true;
217c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch  canceled_ = false;
21890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  base::MessageLoopForUI* loop = base::MessageLoopForUI::current();
21990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  base::MessageLoop::ScopedNestableTaskAllower allow_nested(loop);
2205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  base::RunLoop run_loop;
22190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  quit_closure_ = run_loop.QuitClosure();
22290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  run_loop.Run();
2235c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  nested_dispatcher_ = old_dispatcher.Pass();
224c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch  return !canceled_;
22590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)}
22690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
2277dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdochvoid X11WholeScreenMoveLoop::UpdateCursor(gfx::NativeCursor cursor) {
2285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if (in_move_loop_) {
2295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    // If we're still in the move loop, regrab the pointer with the updated
2305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    // cursor. Note: we can be called from handling an XdndStatus message after
2315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    // EndMoveLoop() was called, but before we return from the nested RunLoop.
232a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    GrabPointerAndKeyboard(cursor);
2335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  }
2347dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch}
2357dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
23690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)void X11WholeScreenMoveLoop::EndMoveLoop() {
23790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  if (!in_move_loop_)
23890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    return;
23990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
240effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  // Prevent DispatchMouseMovement from dispatching any posted motion event.
241effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  weak_factory_.InvalidateWeakPtrs();
242effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  last_xmotion_.type = LASTEvent;
243effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
2441e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)  // We undo our emulated mouse click from RunMoveLoop();
2455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if (should_reset_mouse_flags_) {
2465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    aura::Env::GetInstance()->set_mouse_button_flags(0);
2475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    should_reset_mouse_flags_ = false;
2485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  }
2491e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
25090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  // TODO(erg): Is this ungrab the cause of having to click to give input focus
25190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  // on drawn out windows? Not ungrabbing here screws the X server until I kill
25290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  // the chrome process.
25390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
25490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  // Ungrab before we let go of the window.
25568043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)  XDisplay* display = gfx::GetXDisplay();
25690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  XUngrabPointer(display, CurrentTime);
257a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  XUngrabKeyboard(display, CurrentTime);
25890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
2595c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // Restore the previous dispatcher.
2605c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  nested_dispatcher_.reset();
261f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  drag_widget_.reset();
26290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  delegate_->OnMoveLoopEnded();
26390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  XDestroyWindow(display, grab_input_window_);
2645c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  grab_input_window_ = None;
26590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
26690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  in_move_loop_ = false;
26790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  quit_closure_.Run();
26890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)}
26990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
270f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)void X11WholeScreenMoveLoop::SetDragImage(const gfx::ImageSkia& image,
271f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                                          gfx::Vector2dF offset) {
272f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  drag_image_ = image;
273f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  drag_offset_ = offset;
274f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // Reset the Y offset, so that the drag-image is always just below the cursor,
275f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // so that it is possible to see where the cursor is going.
276f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  drag_offset_.set_y(0.f);
277f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)}
278f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
279a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)bool X11WholeScreenMoveLoop::GrabPointerAndKeyboard(gfx::NativeCursor cursor) {
28068043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)  XDisplay* display = gfx::GetXDisplay();
2817dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  XGrabServer(display);
282a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
2837dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  XUngrabPointer(display, CurrentTime);
2847dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  int ret = XGrabPointer(
2857dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      display,
2867dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      grab_input_window_,
2877dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      False,
2887dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
2897dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      GrabModeAsync,
2907dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      GrabModeAsync,
2917dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      None,
2927dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      cursor.platform(),
2937dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      CurrentTime);
2947dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  if (ret != GrabSuccess) {
295a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    DLOG(ERROR) << "Grabbing pointer for dragging failed: "
2967dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch                << ui::GetX11ErrorString(display, ret);
297a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  } else {
298a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    XUngrabKeyboard(display, CurrentTime);
299a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    ret = XGrabKeyboard(
300a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        display,
301a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        grab_input_window_,
302a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        False,
303a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        GrabModeAsync,
304a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        GrabModeAsync,
305a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        CurrentTime);
306a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    if (ret != GrabSuccess) {
307a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      DLOG(ERROR) << "Grabbing keyboard for dragging failed: "
308a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                  << ui::GetX11ErrorString(display, ret);
309a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    }
3107dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  }
3117dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
312a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  XUngrabServer(display);
3135c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  XFlush(display);
314a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  return ret == GrabSuccess;
3157dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch}
3167dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
317f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)Window X11WholeScreenMoveLoop::CreateDragInputWindow(XDisplay* display) {
318f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // Creates an invisible, InputOnly toplevel window. This window will receive
319f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // all mouse movement for drags. It turns out that normal windows doing a
320f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // grab doesn't redirect pointer motion events if the pointer isn't over the
321f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // grabbing window. But InputOnly windows are able to grab everything. This
322f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // is what GTK+ does, and I found a patch to KDE that did something similar.
323f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  unsigned long attribute_mask = CWEventMask | CWOverrideRedirect;
324f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  XSetWindowAttributes swa;
325f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  memset(&swa, 0, sizeof(swa));
326f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  swa.event_mask = ButtonPressMask | ButtonReleaseMask | PointerMotionMask |
327a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                   KeyPressMask | KeyReleaseMask | StructureNotifyMask;
328f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  swa.override_redirect = True;
329f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  Window window = XCreateWindow(display,
330f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                                DefaultRootWindow(display),
331f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                                -100, -100, 10, 10,
332f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                                0, CopyFromParent, InputOnly, CopyFromParent,
333f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                                attribute_mask, &swa);
334f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  XMapRaised(display, window);
335c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch  ui::X11EventSource::GetInstance()->BlockUntilWindowMapped(window);
336f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  return window;
337f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)}
338f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
339f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)void X11WholeScreenMoveLoop::CreateDragImageWindow() {
340f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  Widget* widget = new Widget;
341f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  Widget::InitParams params(Widget::InitParams::TYPE_DRAG);
342f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  params.opacity = Widget::InitParams::OPAQUE_WINDOW;
343f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
344f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  params.accept_events = false;
345f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
346f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  gfx::Point location = gfx::ToFlooredPoint(
347f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      gfx::Screen::GetNativeScreen()->GetCursorScreenPoint() - drag_offset_);
348f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  params.bounds = gfx::Rect(location, drag_image_.size());
349f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  widget->set_focus_on_creation(false);
350f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  widget->set_frame_type(Widget::FRAME_TYPE_FORCE_NATIVE);
351f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  widget->Init(params);
352effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  widget->SetOpacity(kDragWidgetOpacity);
353f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  widget->GetNativeWindow()->SetName("DragWindow");
354f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
355f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  ImageView* image = new ImageView();
356f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  image->SetImage(drag_image_);
357f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  image->SetBounds(0, 0, drag_image_.width(), drag_image_.height());
358f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  widget->SetContentsView(image);
359f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  widget->Show();
360f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  widget->GetNativeWindow()->layer()->SetFillsBoundsOpaquely(false);
361f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
362f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  drag_widget_.reset(widget);
363f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)}
364f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
365a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)bool X11WholeScreenMoveLoop::CheckIfIconValid() {
366a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // TODO(erg): I've tried at least five different strategies for trying to
367a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // build a mask based off the alpha channel. While all of them have worked,
368a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // none of them have been performant and introduced multiple second
369a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // delays. (I spent a day getting a rectangle segmentation algorithm polished
370a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // here...and then found that even through I had the rectangle extraction
371a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // down to mere milliseconds, SkRegion still fell over on the number of
372a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // rectangles.)
373a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  //
374a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // Creating a mask here near instantaneously should be possible, as GTK does
375a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // it, but I've blown days on this and I'm punting now.
376a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
377a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  const SkBitmap* in_bitmap = drag_image_.bitmap();
378a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  SkAutoLockPixels in_lock(*in_bitmap);
379a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  for (int y = 0; y < in_bitmap->height(); ++y) {
380a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    uint32* in_row = in_bitmap->getAddr32(0, y);
381a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
382a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    for (int x = 0; x < in_bitmap->width(); ++x) {
383a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      if (SkColorGetA(in_row[x]) > kMinAlpha)
384a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        return true;
385a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    }
386a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  }
387a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
388a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  return false;
389a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)}
390a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
39190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)}  // namespace views
392