13551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)// Copyright (c) 2013 The Chromium Authors. All rights reserved. 23551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 33551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)// found in the LICENSE file. 43551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) 5a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)#include "ui/wm/core/capture_controller.h" 63551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) 73551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)#include "base/logging.h" 823730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles)#include "base/path_service.h" 93551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)#include "ui/aura/env.h" 103551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)#include "ui/aura/test/test_window_delegate.h" 11a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)#include "ui/aura/window_event_dispatcher.h" 1223730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles)#include "ui/aura/window_tree_host.h" 1323730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles)#include "ui/base/resource/resource_bundle.h" 1423730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles)#include "ui/base/ui_base_paths.h" 15d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)#include "ui/events/event.h" 165f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)#include "ui/events/test/event_generator.h" 1723730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles)#include "ui/gl/gl_surface.h" 183551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)#include "ui/views/test/views_test_base.h" 193551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)#include "ui/views/view.h" 203551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h" 213551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)#include "ui/views/widget/desktop_aura/desktop_screen_position_client.h" 223551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)#include "ui/views/widget/root_view.h" 233551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)#include "ui/views/widget/widget.h" 243551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) 258bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)// NOTE: these tests do native capture, so they have to be in 268bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)// interactive_ui_tests. 278bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 283551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)namespace views { 293551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) 3023730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles)class DesktopCaptureControllerTest : public ViewsTestBase { 3123730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles) public: 3223730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles) DesktopCaptureControllerTest() {} 3323730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles) virtual ~DesktopCaptureControllerTest() {} 3423730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles) 3523730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles) virtual void SetUp() OVERRIDE { 3623730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles) gfx::GLSurface::InitializeOneOffForTests(); 37116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch ui::RegisterPathProvider(); 38116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch base::FilePath ui_test_pak_path; 39116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch ASSERT_TRUE(PathService::Get(ui::UI_TEST_PAK, &ui_test_pak_path)); 40116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch ui::ResourceBundle::InitSharedInstanceWithPakPath(ui_test_pak_path); 4123730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles) 4223730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles) ViewsTestBase::SetUp(); 4323730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles) } 4423730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles)}; 453551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) 463551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)// This class provides functionality to verify whether the View instance 473551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)// received the gesture event. 483551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)class DesktopViewInputTest : public View { 493551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) public: 503551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) DesktopViewInputTest() 513551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) : received_gesture_event_(false) {} 523551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) 533551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE { 543551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) received_gesture_event_ = true; 553551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) return View::OnGestureEvent(event); 563551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) } 573551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) 583551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) // Resets state maintained by this class. 593551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) void Reset() { 603551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) received_gesture_event_ = false; 613551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) } 623551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) 633551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) bool received_gesture_event() const { return received_gesture_event_; } 643551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) 653551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) private: 663551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) bool received_gesture_event_; 673551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) 683551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) DISALLOW_COPY_AND_ASSIGN(DesktopViewInputTest); 693551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)}; 703551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) 711e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)views::Widget* CreateWidget() { 721e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) views::Widget* widget = new views::Widget; 731e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) views::Widget::InitParams params; 741e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) params.type = views::Widget::InitParams::TYPE_WINDOW_FRAMELESS; 751e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) params.accept_events = true; 761e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; 771e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) params.native_widget = new DesktopNativeWidgetAura(widget); 781e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) params.bounds = gfx::Rect(0, 0, 200, 100); 791e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) widget->Init(params); 801e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) widget->Show(); 811e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) return widget; 821e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)} 831e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 841e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)// Verifies mouse handlers are reset when a window gains capture. Specifically 851e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)// creates two widgets, does a mouse press in one, sets capture in the other and 861e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)// verifies state is reset in the first. 871e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)TEST_F(DesktopCaptureControllerTest, ResetMouseHandlers) { 881e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) scoped_ptr<Widget> w1(CreateWidget()); 891e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) scoped_ptr<Widget> w2(CreateWidget()); 905f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) ui::test::EventGenerator generator1(w1->GetNativeView()->GetRootWindow()); 911e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) generator1.MoveMouseToCenterOf(w1->GetNativeView()); 921e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) generator1.PressLeftButton(); 931e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) EXPECT_FALSE(w1->HasCapture()); 94a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) aura::WindowEventDispatcher* w1_dispatcher = 95a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) w1->GetNativeView()->GetHost()->dispatcher(); 96a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) EXPECT_TRUE(w1_dispatcher->mouse_pressed_handler() != NULL); 97a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) EXPECT_TRUE(w1_dispatcher->mouse_moved_handler() != NULL); 981e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) w2->SetCapture(w2->GetRootView()); 991e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) EXPECT_TRUE(w2->HasCapture()); 100a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) EXPECT_TRUE(w1_dispatcher->mouse_pressed_handler() == NULL); 101a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) EXPECT_TRUE(w1_dispatcher->mouse_moved_handler() == NULL); 1025d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) w2->ReleaseCapture(); 103effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch RunPendingMessages(); 1041e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)} 1051e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 1063551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)// Tests aura::Window capture and whether gesture events are sent to the window 1073551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)// which has capture. 1083551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)// The test case creates two visible widgets and sets capture to the underlying 1093551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)// aura::Windows one by one. It then sends a gesture event and validates whether 1103551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)// the window which had capture receives the gesture. 1118bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)// TODO(sky): move this test, it should be part of ScopedCaptureClient tests. 1123551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)TEST_F(DesktopCaptureControllerTest, CaptureWindowInputEventTest) { 1133551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) scoped_ptr<aura::client::ScreenPositionClient> desktop_position_client1; 1143551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) scoped_ptr<aura::client::ScreenPositionClient> desktop_position_client2; 1153551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) 1163551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) scoped_ptr<Widget> widget1(new Widget()); 1173551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP); 118a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) scoped_ptr<wm::ScopedCaptureClient> scoped_capture_client( 119a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) new wm::ScopedCaptureClient(params.context->GetRootWindow())); 1203551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) aura::client::CaptureClient* capture_client = 1213551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) scoped_capture_client->capture_client(); 1223551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; 1233551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) params.bounds = gfx::Rect(50, 50, 650, 650); 1243551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) widget1->Init(params); 1253551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) internal::RootView* root1 = 1263551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) static_cast<internal::RootView*>(widget1->GetRootView()); 1273551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) 128a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) desktop_position_client1.reset( 129a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) new DesktopScreenPositionClient(params.context->GetRootWindow())); 1303551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) aura::client::SetScreenPositionClient( 1313551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) widget1->GetNativeView()->GetRootWindow(), 1323551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) desktop_position_client1.get()); 1333551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) 1343551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) DesktopViewInputTest* v1 = new DesktopViewInputTest(); 1353551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) v1->SetBoundsRect(gfx::Rect(0, 0, 300, 300)); 1363551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) root1->AddChildView(v1); 1373551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) widget1->Show(); 1383551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) 1393551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) scoped_ptr<Widget> widget2(new Widget()); 1403551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) 1413551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) params = CreateParams(Widget::InitParams::TYPE_POPUP); 1423551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; 1433551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) params.bounds = gfx::Rect(50, 50, 650, 650); 1443551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) widget2->Init(params); 1453551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) 1463551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) internal::RootView* root2 = 1473551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) static_cast<internal::RootView*>(widget2->GetRootView()); 148a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) desktop_position_client2.reset( 149a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) new DesktopScreenPositionClient(params.context->GetRootWindow())); 1503551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) aura::client::SetScreenPositionClient( 1513551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) widget2->GetNativeView()->GetRootWindow(), 1523551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) desktop_position_client2.get()); 153a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) ui::EventDispatchDetails details; 1543551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) 1553551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) DesktopViewInputTest* v2 = new DesktopViewInputTest(); 1563551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) v2->SetBoundsRect(gfx::Rect(0, 0, 300, 300)); 1573551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) root2->AddChildView(v2); 1583551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) widget2->Show(); 1593551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) 1603551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) EXPECT_FALSE(widget1->GetNativeView()->HasCapture()); 1613551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) EXPECT_FALSE(widget2->GetNativeView()->HasCapture()); 1623551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) EXPECT_EQ(reinterpret_cast<aura::Window*>(0), 1633551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) capture_client->GetCaptureWindow()); 1643551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) 1653551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) widget1->GetNativeView()->SetCapture(); 1663551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) EXPECT_TRUE(widget1->GetNativeView()->HasCapture()); 1673551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) EXPECT_FALSE(widget2->GetNativeView()->HasCapture()); 1683551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) EXPECT_EQ(capture_client->GetCaptureWindow(), widget1->GetNativeView()); 1693551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) 1701320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci ui::GestureEvent g1(80, 1711320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 80, 1721320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 0, 1731320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci base::TimeDelta(), 1741320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS)); 175a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) details = root1->OnEventFromSource(&g1); 176a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) EXPECT_FALSE(details.dispatcher_destroyed); 177a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) EXPECT_FALSE(details.target_destroyed); 178a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 1793551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) EXPECT_TRUE(v1->received_gesture_event()); 1803551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) EXPECT_FALSE(v2->received_gesture_event()); 1813551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) v1->Reset(); 1823551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) v2->Reset(); 1833551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) 1843551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) widget2->GetNativeView()->SetCapture(); 1853551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) 1863551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) EXPECT_FALSE(widget1->GetNativeView()->HasCapture()); 1873551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) EXPECT_TRUE(widget2->GetNativeView()->HasCapture()); 1883551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) EXPECT_EQ(capture_client->GetCaptureWindow(), widget2->GetNativeView()); 1893551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) 190a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) details = root2->OnEventFromSource(&g1); 191a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) EXPECT_FALSE(details.dispatcher_destroyed); 192a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) EXPECT_FALSE(details.target_destroyed); 193a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 1943551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) EXPECT_TRUE(v2->received_gesture_event()); 1953551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) EXPECT_FALSE(v1->received_gesture_event()); 1963551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) 1973551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) widget1->CloseNow(); 1983551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) widget2->CloseNow(); 1993551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) RunPendingMessages(); 2003551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)} 2013551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) 2023551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)} // namespace views 203