x11_whole_screen_move_loop.cc revision cedac228d2dd51db4b79ea1e72c7f249408ee061
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),
65cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      has_grab_(false),
66effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      weak_factory_(this) {
67effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  last_xmotion_.type = LASTEvent;
6890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)}
6990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
7090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)X11WholeScreenMoveLoop::~X11WholeScreenMoveLoop() {}
7190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
72effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochvoid X11WholeScreenMoveLoop::DispatchMouseMovement() {
73effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  if (!weak_factory_.HasWeakPtrs())
74effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    return;
75effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  weak_factory_.InvalidateWeakPtrs();
76effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  DCHECK_EQ(MotionNotify, last_xmotion_.type);
77effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  delegate_->OnMouseMovement(&last_xmotion_);
78effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  last_xmotion_.type = LASTEvent;
79effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch}
80effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
81c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch////////////////////////////////////////////////////////////////////////////////
82c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch// DesktopWindowTreeHostLinux, ui::PlatformEventDispatcher implementation:
83c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch
84c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdochbool X11WholeScreenMoveLoop::CanDispatchEvent(const ui::PlatformEvent& event) {
855c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  return in_move_loop_;
86c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch}
87c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch
88c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdochuint32_t X11WholeScreenMoveLoop::DispatchEvent(const ui::PlatformEvent& event) {
895c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // This method processes all events for the grab_input_window_ as well as
905c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // mouse events for all windows while the move loop is active - even before
915c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // the grab is granted by X. This allows mouse notification events that were
925c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // sent after the capture was requested but before the capture was granted
935c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // to be dispatched. It is especially important to process the mouse release
945c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // event that should have stopped the drag even if that mouse release happened
955c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // before the grab was granted.
965c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (!in_move_loop_)
975c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return ui::POST_DISPATCH_PERFORM_DEFAULT;
9890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  XEvent* xev = event;
9990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
10090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  // Note: the escape key is handled in the tab drag controller, which has
10190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  // keyboard focus even though we took pointer grab.
10290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  switch (xev->type) {
10390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    case MotionNotify: {
104f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      if (drag_widget_.get()) {
105f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        gfx::Screen* screen = gfx::Screen::GetNativeScreen();
106f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        gfx::Point location = gfx::ToFlooredPoint(
107f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)            screen->GetCursorScreenPoint() - drag_offset_);
108f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        drag_widget_->SetBounds(gfx::Rect(location, drag_image_.size()));
109effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch        drag_widget_->StackAtTop();
110effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      }
111effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      last_xmotion_ = xev->xmotion;
112effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      if (!weak_factory_.HasWeakPtrs()) {
113effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch        // Post a task to dispatch mouse movement event when control returns to
114effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch        // the message loop. This allows smoother dragging since the events are
115effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch        // dispatched without waiting for the drag widget updates.
116effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch        base::MessageLoopForUI::current()->PostTask(
117effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch            FROM_HERE,
118effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch            base::Bind(&X11WholeScreenMoveLoop::DispatchMouseMovement,
119effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                       weak_factory_.GetWeakPtr()));
120f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      }
1215c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      return ui::POST_DISPATCH_NONE;
12290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    }
12390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    case ButtonRelease: {
124eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      if (xev->xbutton.button == Button1) {
125eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        // Assume that drags are being done with the left mouse button. Only
126eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        // break the drag if the left mouse button was released.
127effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch        DispatchMouseMovement();
128eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        delegate_->OnMouseReleased();
129eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      }
1305c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      return ui::POST_DISPATCH_NONE;
13190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    }
132a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    case KeyPress: {
133c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch      if (ui::KeyboardCodeFromXKeyEvent(xev) == ui::VKEY_ESCAPE) {
134c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch        canceled_ = true;
135a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        EndMoveLoop();
1365c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        return ui::POST_DISPATCH_NONE;
137c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch      }
138a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      break;
139a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    }
140cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    case FocusOut: {
141cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      if (xev->xfocus.mode != NotifyGrab)
142cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        has_grab_ = false;
143cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      break;
144cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
1455c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    case GenericEvent: {
1465c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      ui::EventType type = ui::EventTypeFromNative(xev);
1475c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      switch (type) {
1485c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        case ui::ET_MOUSE_MOVED:
1495c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        case ui::ET_MOUSE_DRAGGED:
1505c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        case ui::ET_MOUSE_RELEASED: {
1515c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          XEvent xevent = {0};
1525c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          if (type == ui::ET_MOUSE_RELEASED) {
1535c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu            xevent.type = ButtonRelease;
1545c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu            xevent.xbutton.button = ui::EventButtonFromNative(xev);
1555c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          } else {
1565c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu            xevent.type = MotionNotify;
1575c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          }
1585c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          xevent.xany.display = xev->xgeneric.display;
1595c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          xevent.xany.window = grab_input_window_;
1605c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          // The fields used below are in the same place for all of events
1615c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          // above. Using xmotion from XEvent's unions to avoid repeating
1625c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          // the code.
1635c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          xevent.xmotion.root = DefaultRootWindow(xev->xgeneric.display);
1645c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          xevent.xmotion.time = ui::EventTimeFromNative(xev).InMilliseconds();
1655c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          gfx::Point point(ui::EventSystemLocationFromNative(xev));
1665c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          xevent.xmotion.x_root = point.x();
1675c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          xevent.xmotion.y_root = point.y();
1685c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          DispatchEvent(&xevent);
1695c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          return ui::POST_DISPATCH_NONE;
1705c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        }
1715c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        default:
1725c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          break;
1735c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      }
1745c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    }
17590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  }
17690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
1775c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  return (event->xany.window == grab_input_window_) ?
1785c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      ui::POST_DISPATCH_NONE : ui::POST_DISPATCH_PERFORM_DEFAULT;
17990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)}
18090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
1817dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdochbool X11WholeScreenMoveLoop::RunMoveLoop(aura::Window* source,
1827dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch                                         gfx::NativeCursor cursor) {
1830529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  DCHECK(!in_move_loop_);  // Can only handle one nested loop at a time.
1840529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch
185f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // Start a capture on the host, so that it continues to receive events during
186a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // the drag. This may be second time we are capturing the mouse events - the
187a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // first being when a mouse is first pressed. That first capture needs to be
188a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // released before the call to GrabPointerAndKeyboard below, otherwise it may
189a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // get released while we still need the pointer grab, which is why we restrict
190a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // the scope here.
191a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  {
192a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    ScopedCapturer capturer(source->GetHost());
193a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
1940529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    grab_input_window_ = CreateDragInputWindow(gfx::GetXDisplay());
195a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    // Releasing ScopedCapturer ensures that any other instance of
196a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    // X11ScopedCapture will not prematurely release grab that will be acquired
197a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    // below.
198a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  }
199a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // TODO(varkha): Consider integrating GrabPointerAndKeyboard with
200a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // ScopedCapturer to avoid possibility of logically keeping multiple grabs.
2010529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  if (!GrabPointerAndKeyboard(cursor)) {
2020529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    XDestroyWindow(gfx::GetXDisplay(), grab_input_window_);
20390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    return false;
2040529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  }
2050529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch
2065c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  scoped_ptr<ui::ScopedEventDispatcher> old_dispatcher =
2075c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      nested_dispatcher_.Pass();
2085c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  nested_dispatcher_ =
2095c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu         ui::PlatformEventSource::GetInstance()->OverrideDispatcher(this);
2100529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  if (!drag_image_.isNull() && CheckIfIconValid())
2110529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    CreateDragImageWindow();
21290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
2131e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)  // We are handling a mouse drag outside of the aura::RootWindow system. We
2141e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)  // must manually make aura think that the mouse button is pressed so that we
2151e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)  // don't draw extraneous tooltips.
2165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  aura::Env* env = aura::Env::GetInstance();
2175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if (!env->IsMouseButtonDown()) {
2185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    env->set_mouse_button_flags(ui::EF_LEFT_MOUSE_BUTTON);
2195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    should_reset_mouse_flags_ = true;
2205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  }
2211e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
2220529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch  in_move_loop_ = true;
223c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch  canceled_ = false;
22490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  base::MessageLoopForUI* loop = base::MessageLoopForUI::current();
22590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  base::MessageLoop::ScopedNestableTaskAllower allow_nested(loop);
2265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  base::RunLoop run_loop;
22790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  quit_closure_ = run_loop.QuitClosure();
22890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  run_loop.Run();
2295c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  nested_dispatcher_ = old_dispatcher.Pass();
230c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch  return !canceled_;
23190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)}
23290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
2337dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdochvoid X11WholeScreenMoveLoop::UpdateCursor(gfx::NativeCursor cursor) {
2345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if (in_move_loop_) {
2355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    // If we're still in the move loop, regrab the pointer with the updated
2365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    // cursor. Note: we can be called from handling an XdndStatus message after
2375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    // EndMoveLoop() was called, but before we return from the nested RunLoop.
238a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    GrabPointerAndKeyboard(cursor);
2395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  }
2407dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch}
2417dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
24290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)void X11WholeScreenMoveLoop::EndMoveLoop() {
24390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  if (!in_move_loop_)
24490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    return;
24590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
246effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  // Prevent DispatchMouseMovement from dispatching any posted motion event.
247effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  weak_factory_.InvalidateWeakPtrs();
248effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  last_xmotion_.type = LASTEvent;
249effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
2501e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)  // We undo our emulated mouse click from RunMoveLoop();
2515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if (should_reset_mouse_flags_) {
2525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    aura::Env::GetInstance()->set_mouse_button_flags(0);
2535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    should_reset_mouse_flags_ = false;
2545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  }
2551e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
25690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  // TODO(erg): Is this ungrab the cause of having to click to give input focus
25790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  // on drawn out windows? Not ungrabbing here screws the X server until I kill
25890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  // the chrome process.
25990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
26090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  // Ungrab before we let go of the window.
26168043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)  XDisplay* display = gfx::GetXDisplay();
262cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // Only ungrab pointer if capture was not switched to another window.
263cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (has_grab_) {
264cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    XUngrabPointer(display, CurrentTime);
265cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    XUngrabKeyboard(display, CurrentTime);
266cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
26790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
2685c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // Restore the previous dispatcher.
2695c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  nested_dispatcher_.reset();
270f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  drag_widget_.reset();
27190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  delegate_->OnMoveLoopEnded();
27290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  XDestroyWindow(display, grab_input_window_);
2735c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  grab_input_window_ = None;
27490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
27590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  in_move_loop_ = false;
27690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  quit_closure_.Run();
27790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)}
27890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)
279f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)void X11WholeScreenMoveLoop::SetDragImage(const gfx::ImageSkia& image,
280f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                                          gfx::Vector2dF offset) {
281f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  drag_image_ = image;
282f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  drag_offset_ = offset;
283f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // Reset the Y offset, so that the drag-image is always just below the cursor,
284f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // so that it is possible to see where the cursor is going.
285f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  drag_offset_.set_y(0.f);
286f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)}
287f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
288a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)bool X11WholeScreenMoveLoop::GrabPointerAndKeyboard(gfx::NativeCursor cursor) {
28968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)  XDisplay* display = gfx::GetXDisplay();
2907dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  XGrabServer(display);
291a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
2927dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  XUngrabPointer(display, CurrentTime);
2937dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  int ret = XGrabPointer(
2947dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      display,
2957dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      grab_input_window_,
2967dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      False,
2977dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
2987dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      GrabModeAsync,
2997dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      GrabModeAsync,
3007dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      None,
3017dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      cursor.platform(),
3027dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch      CurrentTime);
3037dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  if (ret != GrabSuccess) {
304a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    DLOG(ERROR) << "Grabbing pointer for dragging failed: "
3057dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch                << ui::GetX11ErrorString(display, ret);
306a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  } else {
307cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    has_grab_ = true;
308a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    XUngrabKeyboard(display, CurrentTime);
309a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    ret = XGrabKeyboard(
310a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        display,
311a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        grab_input_window_,
312a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        False,
313a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        GrabModeAsync,
314a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        GrabModeAsync,
315a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        CurrentTime);
316a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    if (ret != GrabSuccess) {
317a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      DLOG(ERROR) << "Grabbing keyboard for dragging failed: "
318a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                  << ui::GetX11ErrorString(display, ret);
319a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    }
3207dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch  }
3217dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
322a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  XUngrabServer(display);
3235c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  XFlush(display);
324a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  return ret == GrabSuccess;
3257dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch}
3267dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch
327f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)Window X11WholeScreenMoveLoop::CreateDragInputWindow(XDisplay* display) {
328f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // Creates an invisible, InputOnly toplevel window. This window will receive
329f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // all mouse movement for drags. It turns out that normal windows doing a
330f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // grab doesn't redirect pointer motion events if the pointer isn't over the
331f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // grabbing window. But InputOnly windows are able to grab everything. This
332f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // is what GTK+ does, and I found a patch to KDE that did something similar.
333f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  unsigned long attribute_mask = CWEventMask | CWOverrideRedirect;
334f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  XSetWindowAttributes swa;
335f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  memset(&swa, 0, sizeof(swa));
336f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  swa.event_mask = ButtonPressMask | ButtonReleaseMask | PointerMotionMask |
337a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                   KeyPressMask | KeyReleaseMask | StructureNotifyMask;
338f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  swa.override_redirect = True;
339f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  Window window = XCreateWindow(display,
340f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                                DefaultRootWindow(display),
341f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                                -100, -100, 10, 10,
342f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                                0, CopyFromParent, InputOnly, CopyFromParent,
343f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                                attribute_mask, &swa);
344f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  XMapRaised(display, window);
345c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch  ui::X11EventSource::GetInstance()->BlockUntilWindowMapped(window);
346f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  return window;
347f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)}
348f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
349f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)void X11WholeScreenMoveLoop::CreateDragImageWindow() {
350f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  Widget* widget = new Widget;
351f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  Widget::InitParams params(Widget::InitParams::TYPE_DRAG);
352f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  params.opacity = Widget::InitParams::OPAQUE_WINDOW;
353f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
354f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  params.accept_events = false;
355f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
356f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  gfx::Point location = gfx::ToFlooredPoint(
357f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      gfx::Screen::GetNativeScreen()->GetCursorScreenPoint() - drag_offset_);
358f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  params.bounds = gfx::Rect(location, drag_image_.size());
359f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  widget->set_focus_on_creation(false);
360f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  widget->set_frame_type(Widget::FRAME_TYPE_FORCE_NATIVE);
361f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  widget->Init(params);
362effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  widget->SetOpacity(kDragWidgetOpacity);
363f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  widget->GetNativeWindow()->SetName("DragWindow");
364f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
365f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  ImageView* image = new ImageView();
366f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  image->SetImage(drag_image_);
367f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  image->SetBounds(0, 0, drag_image_.width(), drag_image_.height());
368f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  widget->SetContentsView(image);
369f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  widget->Show();
370f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  widget->GetNativeWindow()->layer()->SetFillsBoundsOpaquely(false);
371f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
372f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  drag_widget_.reset(widget);
373f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)}
374f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
375a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)bool X11WholeScreenMoveLoop::CheckIfIconValid() {
376010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)  // Because we need a GL context per window, we do a quick check so that we
377010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)  // don't make another context if the window would just be displaying a mostly
378010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)  // transparent image.
379a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  const SkBitmap* in_bitmap = drag_image_.bitmap();
380a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  SkAutoLockPixels in_lock(*in_bitmap);
381a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  for (int y = 0; y < in_bitmap->height(); ++y) {
382a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    uint32* in_row = in_bitmap->getAddr32(0, y);
383a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
384a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    for (int x = 0; x < in_bitmap->width(); ++x) {
385a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      if (SkColorGetA(in_row[x]) > kMinAlpha)
386a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        return true;
387a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    }
388a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  }
389a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
390a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  return false;
391a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)}
392a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
39390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)}  // namespace views
394