diff options
author | Jocelyn Turcotte <jocelyn.turcotte@digia.com> | 2014-08-08 14:30:41 +0200 |
---|---|---|
committer | Jocelyn Turcotte <jocelyn.turcotte@digia.com> | 2014-08-12 13:49:54 +0200 |
commit | ab0a50979b9eb4dfa3320eff7e187e41efedf7a9 (patch) | |
tree | 498dfb8a97ff3361a9f7486863a52bb4e26bb898 /chromium/ui/wm/core | |
parent | 4ce69f7403811819800e7c5ae1318b2647e778d1 (diff) |
Update Chromium to beta version 37.0.2062.68
Change-Id: I188e3b5aff1bec75566014291b654eb19f5bc8ca
Reviewed-by: Andras Becsi <andras.becsi@digia.com>
Diffstat (limited to 'chromium/ui/wm/core')
75 files changed, 10710 insertions, 0 deletions
diff --git a/chromium/ui/wm/core/DEPS b/chromium/ui/wm/core/DEPS new file mode 100644 index 00000000000..ddcff7e29ff --- /dev/null +++ b/chromium/ui/wm/core/DEPS @@ -0,0 +1,16 @@ +include_rules = [ + "+grit/ui_resources.h", + "+ui/aura", + "+ui/base/accelerators", + "+ui/base/cursor", + "+ui/base/hit_test.h", + "+ui/base/ime", + "+ui/base/resource", + "+ui/base/ui_base_switches_util.h", + "+ui/base/ui_base_types.h", + "+ui/compositor", + "+ui/events", + "+ui/gfx", + "+ui/views/views_export.h", + "+third_party/skia", +] diff --git a/chromium/ui/wm/core/accelerator_delegate.h b/chromium/ui/wm/core/accelerator_delegate.h new file mode 100644 index 00000000000..886d7dba445 --- /dev/null +++ b/chromium/ui/wm/core/accelerator_delegate.h @@ -0,0 +1,33 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_WM_CORE_ACCELERATOR_DELEGATE_H_ +#define UI_WM_CORE_ACCELERATOR_DELEGATE_H_ + +namespace ui { +class Accelerator; +class KeyEvent; +} + +namespace wm { + +class AcceleratorDelegate { + public: + virtual ~AcceleratorDelegate() {} + + // Type of keys that triggers accelerators. + enum KeyType { + KEY_TYPE_SYSTEM, + KEY_TYPE_OTHER, + }; + + // Return true if the |accelerator| has been processed. + virtual bool ProcessAccelerator(const ui::KeyEvent& event, + const ui::Accelerator& accelerator, + KeyType key_type) = 0; +}; + +} // namespace wm + +#endif // UI_WM_CORE_ACCELERATOR_DELEGATE_H_ diff --git a/chromium/ui/wm/core/accelerator_filter.cc b/chromium/ui/wm/core/accelerator_filter.cc new file mode 100644 index 00000000000..51c6106d91e --- /dev/null +++ b/chromium/ui/wm/core/accelerator_filter.cc @@ -0,0 +1,80 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/accelerator_filter.h" + +#include "ui/base/accelerators/accelerator.h" +#include "ui/events/event.h" +#include "ui/wm/core/accelerator_delegate.h" + +namespace wm { +namespace { + +// Returns true if |key_code| is a key usually handled directly by the shell. +bool IsSystemKey(ui::KeyboardCode key_code) { +#if defined(OS_CHROMEOS) + switch (key_code) { + case ui::VKEY_MEDIA_LAUNCH_APP2: // Fullscreen button. + case ui::VKEY_MEDIA_LAUNCH_APP1: // Overview button. + case ui::VKEY_BRIGHTNESS_DOWN: + case ui::VKEY_BRIGHTNESS_UP: + case ui::VKEY_KBD_BRIGHTNESS_DOWN: + case ui::VKEY_KBD_BRIGHTNESS_UP: + case ui::VKEY_VOLUME_MUTE: + case ui::VKEY_VOLUME_DOWN: + case ui::VKEY_VOLUME_UP: + return true; + default: + return false; + } +#endif // defined(OS_CHROMEOS) + return false; +} + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// +// AcceleratorFilter, public: + +AcceleratorFilter::AcceleratorFilter(scoped_ptr<AcceleratorDelegate> delegate) + : delegate_(delegate.Pass()) { +} + +AcceleratorFilter::~AcceleratorFilter() { +} + +//////////////////////////////////////////////////////////////////////////////// +// AcceleratorFilter, EventFilter implementation: + +void AcceleratorFilter::OnKeyEvent(ui::KeyEvent* event) { + const ui::EventType type = event->type(); + DCHECK(event->target()); + if ((type != ui::ET_KEY_PRESSED && type != ui::ET_KEY_RELEASED) || + event->is_char() || !event->target()) { + return; + } + + ui::Accelerator accelerator = CreateAcceleratorFromKeyEvent(*event); + + AcceleratorDelegate::KeyType key_type = + IsSystemKey(event->key_code()) ? AcceleratorDelegate::KEY_TYPE_SYSTEM + : AcceleratorDelegate::KEY_TYPE_OTHER; + + if (delegate_->ProcessAccelerator(*event, accelerator, key_type)) + event->StopPropagation(); +} + +ui::Accelerator CreateAcceleratorFromKeyEvent(const ui::KeyEvent& key_event) { + const int kModifierFlagMask = + (ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN); + + ui::Accelerator accelerator(key_event.key_code(), + key_event.flags() & kModifierFlagMask); + if (key_event.type() == ui::ET_KEY_RELEASED) + accelerator.set_type(ui::ET_KEY_RELEASED); + accelerator.set_is_repeat(key_event.IsRepeat()); + return accelerator; +} + +} // namespace wm diff --git a/chromium/ui/wm/core/accelerator_filter.h b/chromium/ui/wm/core/accelerator_filter.h new file mode 100644 index 00000000000..4e5295e098b --- /dev/null +++ b/chromium/ui/wm/core/accelerator_filter.h @@ -0,0 +1,40 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_WM_CORE_ACCELERATOR_FILTER_H_ +#define UI_WM_CORE_ACCELERATOR_FILTER_H_ + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "ui/events/event_handler.h" +#include "ui/wm/wm_export.h" + +namespace ui { +class Accelerator; +} + +namespace wm { +class AcceleratorDelegate; + +// AcceleratorFilter filters key events for AcceleratorControler handling global +// keyboard accelerators. +class WM_EXPORT AcceleratorFilter : public ui::EventHandler { + public: + AcceleratorFilter(scoped_ptr<AcceleratorDelegate> delegate); + virtual ~AcceleratorFilter(); + + // Overridden from ui::EventHandler: + virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE; + + private: + scoped_ptr<AcceleratorDelegate> delegate_; + + DISALLOW_COPY_AND_ASSIGN(AcceleratorFilter); +}; + +ui::Accelerator CreateAcceleratorFromKeyEvent(const ui::KeyEvent& key_event); + +} // namespace wm + +#endif // UI_WM_CORE_ACCELERATOR_FILTER_H_ diff --git a/chromium/ui/wm/core/base_focus_rules.cc b/chromium/ui/wm/core/base_focus_rules.cc new file mode 100644 index 00000000000..e7160b7b3fe --- /dev/null +++ b/chromium/ui/wm/core/base_focus_rules.cc @@ -0,0 +1,195 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/base_focus_rules.h" + +#include "ui/aura/client/focus_client.h" +#include "ui/aura/window.h" +#include "ui/aura/window_event_dispatcher.h" +#include "ui/wm/core/window_modality_controller.h" +#include "ui/wm/core/window_util.h" +#include "ui/wm/public/activation_delegate.h" + +namespace wm { +namespace { + +aura::Window* GetFocusedWindow(aura::Window* context) { + aura::client::FocusClient* focus_client = + aura::client::GetFocusClient(context); + return focus_client ? focus_client->GetFocusedWindow() : NULL; +} + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// +// BaseFocusRules, protected: + +BaseFocusRules::BaseFocusRules() { +} + +BaseFocusRules::~BaseFocusRules() { +} + +bool BaseFocusRules::IsWindowConsideredVisibleForActivation( + aura::Window* window) const { + return window->IsVisible(); +} + +//////////////////////////////////////////////////////////////////////////////// +// BaseFocusRules, FocusRules implementation: + +bool BaseFocusRules::IsToplevelWindow(aura::Window* window) const { + // The window must in a valid hierarchy. + if (!window->GetRootWindow()) + return false; + + // The window must exist within a container that supports activation. + // The window cannot be blocked by a modal transient. + return SupportsChildActivation(window->parent()); +} + +bool BaseFocusRules::CanActivateWindow(aura::Window* window) const { + // It is possible to activate a NULL window, it is equivalent to clearing + // activation. + if (!window) + return true; + + // Only toplevel windows can be activated. + if (!IsToplevelWindow(window)) + return false; + + // The window must be visible. + if (!IsWindowConsideredVisibleForActivation(window)) + return false; + + // The window's activation delegate must allow this window to be activated. + if (aura::client::GetActivationDelegate(window) && + !aura::client::GetActivationDelegate(window)->ShouldActivate()) { + return false; + } + + // A window must be focusable to be activatable. We don't call + // CanFocusWindow() from here because it will call back to us via + // GetActivatableWindow(). + if (!window->CanFocus()) + return false; + + // The window cannot be blocked by a modal transient. + return !GetModalTransient(window); +} + +bool BaseFocusRules::CanFocusWindow(aura::Window* window) const { + // It is possible to focus a NULL window, it is equivalent to clearing focus. + if (!window) + return true; + + // The focused window is always inside the active window, so windows that + // aren't activatable can't contain the focused window. + aura::Window* activatable = GetActivatableWindow(window); + if (!activatable || !activatable->Contains(window)) + return false; + return window->CanFocus(); +} + +aura::Window* BaseFocusRules::GetToplevelWindow(aura::Window* window) const { + aura::Window* parent = window->parent(); + aura::Window* child = window; + while (parent) { + if (IsToplevelWindow(child)) + return child; + + parent = parent->parent(); + child = child->parent(); + } + return NULL; +} + +aura::Window* BaseFocusRules::GetActivatableWindow(aura::Window* window) const { + aura::Window* parent = window->parent(); + aura::Window* child = window; + while (parent) { + if (CanActivateWindow(child)) + return child; + + // CanActivateWindow() above will return false if |child| is blocked by a + // modal transient. In this case the modal is or contains the activatable + // window. We recurse because the modal may itself be blocked by a modal + // transient. + aura::Window* modal_transient = GetModalTransient(child); + if (modal_transient) + return GetActivatableWindow(modal_transient); + + if (wm::GetTransientParent(child)) { + // To avoid infinite recursion, if |child| has a transient parent + // whose own modal transient is |child| itself, just return |child|. + aura::Window* parent_modal_transient = + GetModalTransient(wm::GetTransientParent(child)); + if (parent_modal_transient == child) + return child; + + return GetActivatableWindow(wm::GetTransientParent(child)); + } + + parent = parent->parent(); + child = child->parent(); + } + return NULL; +} + +aura::Window* BaseFocusRules::GetFocusableWindow(aura::Window* window) const { + if (CanFocusWindow(window)) + return window; + + // |window| may be in a hierarchy that is non-activatable, in which case we + // need to cut over to the activatable hierarchy. + aura::Window* activatable = GetActivatableWindow(window); + if (!activatable) { + // There may not be a related activatable hierarchy to cut over to, in which + // case we try an unrelated one. + aura::Window* toplevel = GetToplevelWindow(window); + if (toplevel) + activatable = GetNextActivatableWindow(toplevel); + if (!activatable) + return NULL; + } + + if (!activatable->Contains(window)) { + // If there's already a child window focused in the activatable hierarchy, + // just use that (i.e. don't shift focus), otherwise we need to at least cut + // over to the activatable hierarchy. + aura::Window* focused = GetFocusedWindow(activatable); + return activatable->Contains(focused) ? focused : activatable; + } + + while (window && !CanFocusWindow(window)) + window = window->parent(); + return window; +} + +aura::Window* BaseFocusRules::GetNextActivatableWindow( + aura::Window* ignore) const { + DCHECK(ignore); + + // Can be called from the RootWindow's destruction, which has a NULL parent. + if (!ignore->parent()) + return NULL; + + // In the basic scenarios handled by BasicFocusRules, the pool of activatable + // windows is limited to the |ignore|'s siblings. + const aura::Window::Windows& siblings = ignore->parent()->children(); + DCHECK(!siblings.empty()); + + for (aura::Window::Windows::const_reverse_iterator rit = siblings.rbegin(); + rit != siblings.rend(); + ++rit) { + aura::Window* cur = *rit; + if (cur == ignore) + continue; + if (CanActivateWindow(cur)) + return cur; + } + return NULL; +} + +} // namespace wm diff --git a/chromium/ui/wm/core/base_focus_rules.h b/chromium/ui/wm/core/base_focus_rules.h new file mode 100644 index 00000000000..07dfb65fbc9 --- /dev/null +++ b/chromium/ui/wm/core/base_focus_rules.h @@ -0,0 +1,45 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_WM_CORE_BASE_FOCUS_RULES_H_ +#define UI_WM_CORE_BASE_FOCUS_RULES_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "ui/wm/core/focus_rules.h" + +namespace wm { + +// A set of basic focus and activation rules. Specializations should most likely +// subclass this and call up to these methods rather than reimplementing them. +class WM_EXPORT BaseFocusRules : public FocusRules { + protected: + BaseFocusRules(); + virtual ~BaseFocusRules(); + + // Returns true if the children of |window| can be activated. + virtual bool SupportsChildActivation(aura::Window* window) const = 0; + + // Returns true if |window| is considered visible for activation purposes. + virtual bool IsWindowConsideredVisibleForActivation( + aura::Window* window) const; + + // Overridden from FocusRules: + virtual bool IsToplevelWindow(aura::Window* window) const OVERRIDE; + virtual bool CanActivateWindow(aura::Window* window) const OVERRIDE; + virtual bool CanFocusWindow(aura::Window* window) const OVERRIDE; + virtual aura::Window* GetToplevelWindow(aura::Window* window) const OVERRIDE; + virtual aura::Window* GetActivatableWindow( + aura::Window* window) const OVERRIDE; + virtual aura::Window* GetFocusableWindow(aura::Window* window) const OVERRIDE; + virtual aura::Window* GetNextActivatableWindow( + aura::Window* ignore) const OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(BaseFocusRules); +}; + +} // namespace wm + +#endif // UI_WM_CORE_BASE_FOCUS_RULES_H_ diff --git a/chromium/ui/wm/core/capture_controller.cc b/chromium/ui/wm/core/capture_controller.cc new file mode 100644 index 00000000000..b3d8620c05e --- /dev/null +++ b/chromium/ui/wm/core/capture_controller.cc @@ -0,0 +1,145 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/capture_controller.h" + +#include "ui/aura/window.h" +#include "ui/aura/window_event_dispatcher.h" +#include "ui/aura/window_tree_host.h" + +namespace wm { + +//////////////////////////////////////////////////////////////////////////////// +// CaptureController, public: + +void CaptureController::Attach(aura::Window* root) { + DCHECK_EQ(0u, root_windows_.count(root)); + root_windows_.insert(root); + aura::client::SetCaptureClient(root, this); +} + +void CaptureController::Detach(aura::Window* root) { + root_windows_.erase(root); + aura::client::SetCaptureClient(root, NULL); +} + +//////////////////////////////////////////////////////////////////////////////// +// CaptureController, aura::client::CaptureClient implementation: + +void CaptureController::SetCapture(aura::Window* new_capture_window) { + if (capture_window_ == new_capture_window) + return; + + // Make sure window has a root window. + DCHECK(!new_capture_window || new_capture_window->GetRootWindow()); + DCHECK(!capture_window_ || capture_window_->GetRootWindow()); + + aura::Window* old_capture_window = capture_window_; + aura::Window* old_capture_root = old_capture_window ? + old_capture_window->GetRootWindow() : NULL; + + // Copy the list in case it's modified out from under us. + RootWindows root_windows(root_windows_); + + // If we're actually starting capture, then cancel any touches/gestures + // that aren't already locked to the new window, and transfer any on the + // old capture window to the new one. When capture is released we have no + // distinction between the touches/gestures that were in the window all + // along (and so shouldn't be canceled) and those that got moved, so + // just leave them all where they are. + if (new_capture_window) { + ui::GestureRecognizer::Get()->TransferEventsTo(old_capture_window, + new_capture_window); + } + + capture_window_ = new_capture_window; + + for (RootWindows::const_iterator i = root_windows.begin(); + i != root_windows.end(); ++i) { + aura::client::CaptureDelegate* delegate = (*i)->GetHost()->dispatcher(); + delegate->UpdateCapture(old_capture_window, new_capture_window); + } + + aura::Window* capture_root = + capture_window_ ? capture_window_->GetRootWindow() : NULL; + if (capture_root != old_capture_root) { + if (old_capture_root) { + aura::client::CaptureDelegate* delegate = + old_capture_root->GetHost()->dispatcher(); + delegate->ReleaseNativeCapture(); + } + if (capture_root) { + aura::client::CaptureDelegate* delegate = + capture_root->GetHost()->dispatcher(); + delegate->SetNativeCapture(); + } + } +} + +void CaptureController::ReleaseCapture(aura::Window* window) { + if (capture_window_ != window) + return; + SetCapture(NULL); +} + +aura::Window* CaptureController::GetCaptureWindow() { + return capture_window_; +} + +aura::Window* CaptureController::GetGlobalCaptureWindow() { + return capture_window_; +} + +//////////////////////////////////////////////////////////////////////////////// +// CaptureController, private: + +CaptureController::CaptureController() + : capture_window_(NULL) { +} + +CaptureController::~CaptureController() { +} + +//////////////////////////////////////////////////////////////////////////////// +// ScopedCaptureClient: + +// static +CaptureController* ScopedCaptureClient::capture_controller_ = NULL; + +ScopedCaptureClient::ScopedCaptureClient(aura::Window* root) + : root_window_(root) { + root->AddObserver(this); + if (!capture_controller_) + capture_controller_ = new CaptureController; + capture_controller_->Attach(root); +} + +ScopedCaptureClient::~ScopedCaptureClient() { + Shutdown(); +} + +// static +bool ScopedCaptureClient::IsActive() { + return capture_controller_ && capture_controller_->is_active(); +} + +void ScopedCaptureClient::OnWindowDestroyed(aura::Window* window) { + DCHECK_EQ(window, root_window_); + Shutdown(); +} + +void ScopedCaptureClient::Shutdown() { + if (!root_window_) + return; + + root_window_->RemoveObserver(this); + capture_controller_->Detach(root_window_); + if (!capture_controller_->is_active()) { + delete capture_controller_; + capture_controller_ = NULL; + } + root_window_ = NULL; +} + +} // namespace wm diff --git a/chromium/ui/wm/core/capture_controller.h b/chromium/ui/wm/core/capture_controller.h new file mode 100644 index 00000000000..d8028183e29 --- /dev/null +++ b/chromium/ui/wm/core/capture_controller.h @@ -0,0 +1,86 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_WM_CORE_CAPTURE_CONTROLLER_H_ +#define UI_WM_CORE_CAPTURE_CONTROLLER_H_ + +#include <set> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "ui/aura/client/capture_client.h" +#include "ui/aura/window_observer.h" +#include "ui/wm/wm_export.h" + +namespace wm { + +// Internal CaptureClient implementation. See ScopedCaptureClient for details. +class WM_EXPORT CaptureController : public aura::client::CaptureClient { + public: + // Adds |root| to the list of RootWindows notified when capture changes. + void Attach(aura::Window* root); + + // Removes |root| from the list of RootWindows notified when capture changes. + void Detach(aura::Window* root); + + // Returns true if this CaptureController is installed on at least one + // RootWindow. + bool is_active() const { return !root_windows_.empty(); } + + // Overridden from aura::client::CaptureClient: + virtual void SetCapture(aura::Window* window) OVERRIDE; + virtual void ReleaseCapture(aura::Window* window) OVERRIDE; + virtual aura::Window* GetCaptureWindow() OVERRIDE; + virtual aura::Window* GetGlobalCaptureWindow() OVERRIDE; + + private: + friend class ScopedCaptureClient; + typedef std::set<aura::Window*> RootWindows; + + CaptureController(); + virtual ~CaptureController(); + + // The current capture window. NULL if there is no capture window. + aura::Window* capture_window_; + + // Set of RootWindows notified when capture changes. + RootWindows root_windows_; + + DISALLOW_COPY_AND_ASSIGN(CaptureController); +}; + +// ScopedCaptureClient is responsible for creating a CaptureClient for a +// RootWindow. Specifically it creates a single CaptureController that is shared +// among all ScopedCaptureClients and adds the RootWindow to it. +class WM_EXPORT ScopedCaptureClient : public aura::WindowObserver { + public: + explicit ScopedCaptureClient(aura::Window* root); + virtual ~ScopedCaptureClient(); + + // Returns true if there is a CaptureController with at least one RootWindow. + static bool IsActive(); + + aura::client::CaptureClient* capture_client() { + return capture_controller_; + } + + // Overridden from aura::WindowObserver: + virtual void OnWindowDestroyed(aura::Window* window) OVERRIDE; + + private: + // Invoked from destructor and OnWindowDestroyed() to cleanup. + void Shutdown(); + + // The single CaptureController instance. + static CaptureController* capture_controller_; + + // RootWindow this ScopedCaptureClient was create for. + aura::Window* root_window_; + + DISALLOW_COPY_AND_ASSIGN(ScopedCaptureClient); +}; + +} // namespace wm + +#endif // UI_WM_CORE_CAPTURE_CONTROLLER_H_ diff --git a/chromium/ui/wm/core/compound_event_filter.cc b/chromium/ui/wm/core/compound_event_filter.cc new file mode 100644 index 00000000000..d1f652c02f6 --- /dev/null +++ b/chromium/ui/wm/core/compound_event_filter.cc @@ -0,0 +1,263 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/compound_event_filter.h" + +#include "base/containers/hash_tables.h" +#include "base/logging.h" +#include "ui/aura/client/cursor_client.h" +#include "ui/aura/env.h" +#include "ui/aura/window.h" +#include "ui/aura/window_delegate.h" +#include "ui/aura/window_event_dispatcher.h" +#include "ui/aura/window_tracker.h" +#include "ui/base/hit_test.h" +#include "ui/events/event.h" +#include "ui/wm/public/activation_client.h" +#include "ui/wm/public/drag_drop_client.h" + +#if defined(OS_CHROMEOS) && defined(USE_X11) +#include "ui/events/x/touch_factory_x11.h" +#endif + +namespace wm { + +namespace { + +// Returns true if the cursor should be hidden on touch events. +// TODO(tdanderson|rsadam): Move this function into CursorClient. +bool ShouldHideCursorOnTouch(const ui::TouchEvent& event) { +#if defined(OS_WIN) + return true; +#elif defined(OS_CHROMEOS) +#if defined(USE_X11) + int device_id = event.source_device_id(); + if (device_id >= 0 && + !ui::TouchFactory::GetInstance()->IsMultiTouchDevice(device_id)) { + // If the touch event is coming from a mouse-device (i.e. not a real + // touch-device), then do not hide the cursor. + return false; + } +#endif // defined(USE_X11) + return true; +#else + // Linux Aura does not hide the cursor on touch by default. + // TODO(tdanderson): Change this if having consistency across + // all platforms which use Aura is desired. + return false; +#endif +} + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// +// CompoundEventFilter, public: + +CompoundEventFilter::CompoundEventFilter() { +} + +CompoundEventFilter::~CompoundEventFilter() { + // Additional filters are not owned by CompoundEventFilter and they + // should all be removed when running here. |handlers_| has + // check_empty == true and will DCHECK failure if it is not empty. +} + +// static +gfx::NativeCursor CompoundEventFilter::CursorForWindowComponent( + int window_component) { + switch (window_component) { + case HTBOTTOM: + return ui::kCursorSouthResize; + case HTBOTTOMLEFT: + return ui::kCursorSouthWestResize; + case HTBOTTOMRIGHT: + return ui::kCursorSouthEastResize; + case HTLEFT: + return ui::kCursorWestResize; + case HTRIGHT: + return ui::kCursorEastResize; + case HTTOP: + return ui::kCursorNorthResize; + case HTTOPLEFT: + return ui::kCursorNorthWestResize; + case HTTOPRIGHT: + return ui::kCursorNorthEastResize; + default: + return ui::kCursorNull; + } +} + +void CompoundEventFilter::AddHandler(ui::EventHandler* handler) { + handlers_.AddObserver(handler); +} + +void CompoundEventFilter::RemoveHandler(ui::EventHandler* handler) { + handlers_.RemoveObserver(handler); +} + +//////////////////////////////////////////////////////////////////////////////// +// CompoundEventFilter, private: + +void CompoundEventFilter::UpdateCursor(aura::Window* target, + ui::MouseEvent* event) { + // If drag and drop is in progress, let the drag drop client set the cursor + // instead of setting the cursor here. + aura::Window* root_window = target->GetRootWindow(); + aura::client::DragDropClient* drag_drop_client = + aura::client::GetDragDropClient(root_window); + if (drag_drop_client && drag_drop_client->IsDragDropInProgress()) + return; + + aura::client::CursorClient* cursor_client = + aura::client::GetCursorClient(root_window); + if (cursor_client) { + gfx::NativeCursor cursor = target->GetCursor(event->location()); + if ((event->flags() & ui::EF_IS_NON_CLIENT)) { + if (target->delegate()) { + int window_component = + target->delegate()->GetNonClientComponent(event->location()); + cursor = CursorForWindowComponent(window_component); + } else { + // Allow the OS to handle non client cursors if we don't have a + // a delegate to handle the non client hittest. + return; + } + } + cursor_client->SetCursor(cursor); + } +} + +void CompoundEventFilter::FilterKeyEvent(ui::KeyEvent* event) { + if (handlers_.might_have_observers()) { + ObserverListBase<ui::EventHandler>::Iterator it(handlers_); + ui::EventHandler* handler; + while (!event->stopped_propagation() && (handler = it.GetNext()) != NULL) + handler->OnKeyEvent(event); + } +} + +void CompoundEventFilter::FilterMouseEvent(ui::MouseEvent* event) { + if (handlers_.might_have_observers()) { + ObserverListBase<ui::EventHandler>::Iterator it(handlers_); + ui::EventHandler* handler; + while (!event->stopped_propagation() && (handler = it.GetNext()) != NULL) + handler->OnMouseEvent(event); + } +} + +void CompoundEventFilter::FilterTouchEvent(ui::TouchEvent* event) { + if (handlers_.might_have_observers()) { + ObserverListBase<ui::EventHandler>::Iterator it(handlers_); + ui::EventHandler* handler; + while (!event->stopped_propagation() && (handler = it.GetNext()) != NULL) + handler->OnTouchEvent(event); + } +} + +void CompoundEventFilter::SetCursorVisibilityOnEvent(aura::Window* target, + ui::Event* event, + bool show) { + if (event->flags() & ui::EF_IS_SYNTHESIZED) + return; + + aura::client::CursorClient* client = + aura::client::GetCursorClient(target->GetRootWindow()); + if (!client) + return; + + if (show) + client->ShowCursor(); + else + client->HideCursor(); +} + +void CompoundEventFilter::SetMouseEventsEnableStateOnEvent(aura::Window* target, + ui::Event* event, + bool enable) { + if (event->flags() & ui::EF_IS_SYNTHESIZED) + return; + aura::client::CursorClient* client = + aura::client::GetCursorClient(target->GetRootWindow()); + if (!client) + return; + + if (enable) + client->EnableMouseEvents(); + else + client->DisableMouseEvents(); +} + +//////////////////////////////////////////////////////////////////////////////// +// CompoundEventFilter, ui::EventHandler implementation: + +void CompoundEventFilter::OnKeyEvent(ui::KeyEvent* event) { + aura::Window* target = static_cast<aura::Window*>(event->target()); + aura::client::CursorClient* client = + aura::client::GetCursorClient(target->GetRootWindow()); + if (client && client->ShouldHideCursorOnKeyEvent(*event)) + SetCursorVisibilityOnEvent(target, event, false); + + FilterKeyEvent(event); +} + +void CompoundEventFilter::OnMouseEvent(ui::MouseEvent* event) { + aura::Window* window = static_cast<aura::Window*>(event->target()); + aura::WindowTracker window_tracker; + window_tracker.Add(window); + + // We must always update the cursor, otherwise the cursor can get stuck if an + // event filter registered with us consumes the event. + // It should also update the cursor for clicking and wheels for ChromeOS boot. + // When ChromeOS is booted, it hides the mouse cursor but immediate mouse + // operation will show the cursor. + // We also update the cursor for mouse enter in case a mouse cursor is sent to + // outside of the root window and moved back for some reasons (e.g. running on + // on Desktop for testing, or a bug in pointer barrier). + if (!(event->flags() & ui::EF_FROM_TOUCH) && + (event->type() == ui::ET_MOUSE_ENTERED || + event->type() == ui::ET_MOUSE_MOVED || + event->type() == ui::ET_MOUSE_PRESSED || + event->type() == ui::ET_MOUSEWHEEL)) { + SetMouseEventsEnableStateOnEvent(window, event, true); + SetCursorVisibilityOnEvent(window, event, true); + UpdateCursor(window, event); + } + + FilterMouseEvent(event); +} + +void CompoundEventFilter::OnScrollEvent(ui::ScrollEvent* event) { +} + +void CompoundEventFilter::OnTouchEvent(ui::TouchEvent* event) { + FilterTouchEvent(event); + if (!event->handled() && event->type() == ui::ET_TOUCH_PRESSED && + ShouldHideCursorOnTouch(*event) && + !aura::Env::GetInstance()->IsMouseButtonDown()) { + SetMouseEventsEnableStateOnEvent( + static_cast<aura::Window*>(event->target()), event, false); + } +} + +void CompoundEventFilter::OnGestureEvent(ui::GestureEvent* event) { + if (handlers_.might_have_observers()) { + ObserverListBase<ui::EventHandler>::Iterator it(handlers_); + ui::EventHandler* handler; + while (!event->stopped_propagation() && (handler = it.GetNext()) != NULL) + handler->OnGestureEvent(event); + } + +#if defined(OS_WIN) + // A Win8 edge swipe event is a special event that does not have location + // information associated with it, and is not preceeded by an ET_TOUCH_PRESSED + // event. So we treat it specially here. + if (!event->handled() && event->type() == ui::ET_GESTURE_WIN8_EDGE_SWIPE && + !aura::Env::GetInstance()->IsMouseButtonDown()) { + SetMouseEventsEnableStateOnEvent( + static_cast<aura::Window*>(event->target()), event, false); + } +#endif +} + +} // namespace wm diff --git a/chromium/ui/wm/core/compound_event_filter.h b/chromium/ui/wm/core/compound_event_filter.h new file mode 100644 index 00000000000..ab4fdf83d06 --- /dev/null +++ b/chromium/ui/wm/core/compound_event_filter.h @@ -0,0 +1,93 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_WM_CORE_COMPOUND_EVENT_FILTER_H_ +#define UI_WM_CORE_COMPOUND_EVENT_FILTER_H_ + +#include "base/compiler_specific.h" +#include "base/observer_list.h" +#include "ui/events/event.h" +#include "ui/events/event_handler.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/wm/wm_export.h" + +namespace aura { +class CursorManager; +class RootWindow; +} + +namespace ui { +class GestureEvent; +class KeyEvent; +class LocatedEvent; +class MouseEvent; +class TouchEvent; +} + +namespace wm { + +// TODO(beng): This class should die. AddEventHandler() on the root Window +// should be used instead. +// CompoundEventFilter gets all events first and can provide actions to those +// events. It implements global features such as click to activate a window and +// cursor change when moving mouse. +// Additional event filters can be added to CompoundEventFilter. Events will +// pass through those additional filters in their addition order and could be +// consumed by any of those filters. If an event is consumed by a filter, the +// rest of the filter(s) and CompoundEventFilter will not see the consumed +// event. +class WM_EXPORT CompoundEventFilter : public ui::EventHandler { + public: + CompoundEventFilter(); + virtual ~CompoundEventFilter(); + + // Returns the cursor for the specified component. + static gfx::NativeCursor CursorForWindowComponent(int window_component); + + // Adds/removes additional event filters. This does not take ownership of + // the EventHandler. + // NOTE: These handlers are deprecated. Use env::AddPreTargetEventHandler etc. + // instead. + void AddHandler(ui::EventHandler* filter); + void RemoveHandler(ui::EventHandler* filter); + + private: + // Updates the cursor if the target provides a custom one, and provides + // default resize cursors for window edges. + void UpdateCursor(aura::Window* target, ui::MouseEvent* event); + + // Dispatches event to additional filters. + void FilterKeyEvent(ui::KeyEvent* event); + void FilterMouseEvent(ui::MouseEvent* event); + void FilterTouchEvent(ui::TouchEvent* event); + + // Sets the visibility of the cursor if the event is not synthesized. + void SetCursorVisibilityOnEvent(aura::Window* target, + ui::Event* event, + bool show); + + // Enables or disables mouse events if the event is not synthesized. + void SetMouseEventsEnableStateOnEvent(aura::Window* target, + ui::Event* event, + bool enable); + + // Overridden from ui::EventHandler: + virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE; + virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE; + virtual void OnScrollEvent(ui::ScrollEvent* event) OVERRIDE; + virtual void OnTouchEvent(ui::TouchEvent* event) OVERRIDE; + virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE; + + // Additional pre-target event handlers. + ObserverList<ui::EventHandler, true> handlers_; + + // True if the cursur was hidden by the filter. + bool cursor_hidden_by_filter_; + + DISALLOW_COPY_AND_ASSIGN(CompoundEventFilter); +}; + +} // namespace wm + +#endif // UI_WM_CORE_COMPOUND_EVENT_FILTER_H_ diff --git a/chromium/ui/wm/core/compound_event_filter_unittest.cc b/chromium/ui/wm/core/compound_event_filter_unittest.cc new file mode 100644 index 00000000000..afd3bcb5bd8 --- /dev/null +++ b/chromium/ui/wm/core/compound_event_filter_unittest.cc @@ -0,0 +1,250 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/compound_event_filter.h" + +#include "ui/aura/client/cursor_client.h" +#include "ui/aura/env.h" +#include "ui/aura/test/aura_test_base.h" +#include "ui/aura/test/event_generator.h" +#include "ui/aura/test/test_cursor_client.h" +#include "ui/aura/test/test_windows.h" +#include "ui/aura/window.h" +#include "ui/aura/window_event_dispatcher.h" +#include "ui/events/event.h" +#include "ui/events/event_utils.h" +#include "ui/wm/core/default_activation_client.h" +#include "ui/wm/public/activation_client.h" + +namespace { + +#if defined(OS_CHROMEOS) || defined(OS_WIN) +base::TimeDelta GetTime() { + return ui::EventTimeForNow(); +} +#endif // defined(OS_CHROMEOS) || defined(OS_WIN) + +} + +namespace wm { + +namespace { + +// An event filter that consumes all gesture events. +class ConsumeGestureEventFilter : public ui::EventHandler { + public: + ConsumeGestureEventFilter() {} + virtual ~ConsumeGestureEventFilter() {} + + private: + // Overridden from ui::EventHandler: + virtual void OnGestureEvent(ui::GestureEvent* e) OVERRIDE { + e->StopPropagation(); + } + + DISALLOW_COPY_AND_ASSIGN(ConsumeGestureEventFilter); +}; + +} // namespace + +typedef aura::test::AuraTestBase CompoundEventFilterTest; + +#if defined(OS_CHROMEOS) +// A keypress only hides the cursor on ChromeOS (crbug.com/304296). +TEST_F(CompoundEventFilterTest, CursorVisibilityChange) { + scoped_ptr<CompoundEventFilter> compound_filter(new CompoundEventFilter); + aura::Env::GetInstance()->AddPreTargetHandler(compound_filter.get()); + aura::test::TestWindowDelegate delegate; + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate(&delegate, 1234, + gfx::Rect(5, 5, 100, 100), root_window())); + window->Show(); + window->SetCapture(); + + aura::test::TestCursorClient cursor_client(root_window()); + + // Send key event to hide the cursor. + ui::KeyEvent key(ui::ET_KEY_PRESSED, ui::VKEY_A, 0, true); + DispatchEventUsingWindowDispatcher(&key); + EXPECT_FALSE(cursor_client.IsCursorVisible()); + + // Synthesized mouse event should not show the cursor. + ui::MouseEvent enter(ui::ET_MOUSE_ENTERED, gfx::Point(10, 10), + gfx::Point(10, 10), 0, 0); + enter.set_flags(enter.flags() | ui::EF_IS_SYNTHESIZED); + DispatchEventUsingWindowDispatcher(&enter); + EXPECT_FALSE(cursor_client.IsCursorVisible()); + + ui::MouseEvent move(ui::ET_MOUSE_MOVED, gfx::Point(10, 10), + gfx::Point(10, 10), 0, 0); + move.set_flags(enter.flags() | ui::EF_IS_SYNTHESIZED); + DispatchEventUsingWindowDispatcher(&move); + EXPECT_FALSE(cursor_client.IsCursorVisible()); + + // A real mouse event should show the cursor. + ui::MouseEvent real_move(ui::ET_MOUSE_MOVED, gfx::Point(10, 10), + gfx::Point(10, 10), 0, 0); + DispatchEventUsingWindowDispatcher(&real_move); + EXPECT_TRUE(cursor_client.IsCursorVisible()); + + // Disallow hiding the cursor on keypress. + cursor_client.set_should_hide_cursor_on_key_event(false); + key = ui::KeyEvent(ui::ET_KEY_PRESSED, ui::VKEY_A, 0, true); + DispatchEventUsingWindowDispatcher(&key); + EXPECT_TRUE(cursor_client.IsCursorVisible()); + + // Allow hiding the cursor on keypress. + cursor_client.set_should_hide_cursor_on_key_event(true); + key = ui::KeyEvent(ui::ET_KEY_PRESSED, ui::VKEY_A, 0, true); + DispatchEventUsingWindowDispatcher(&key); + EXPECT_FALSE(cursor_client.IsCursorVisible()); + + // Mouse synthesized exit event should not show the cursor. + ui::MouseEvent exit(ui::ET_MOUSE_EXITED, gfx::Point(10, 10), + gfx::Point(10, 10), 0, 0); + exit.set_flags(enter.flags() | ui::EF_IS_SYNTHESIZED); + DispatchEventUsingWindowDispatcher(&exit); + EXPECT_FALSE(cursor_client.IsCursorVisible()); + + aura::Env::GetInstance()->RemovePreTargetHandler(compound_filter.get()); +} +#endif // defined(OS_CHROMEOS) + +#if defined(OS_CHROMEOS) || defined(OS_WIN) +// Touch visually hides the cursor on ChromeOS and Windows. +TEST_F(CompoundEventFilterTest, TouchHidesCursor) { + new wm::DefaultActivationClient(root_window()); + scoped_ptr<CompoundEventFilter> compound_filter(new CompoundEventFilter); + aura::Env::GetInstance()->AddPreTargetHandler(compound_filter.get()); + aura::test::TestWindowDelegate delegate; + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate(&delegate, 1234, + gfx::Rect(5, 5, 100, 100), root_window())); + window->Show(); + window->SetCapture(); + + aura::test::TestCursorClient cursor_client(root_window()); + + ui::MouseEvent mouse0(ui::ET_MOUSE_MOVED, gfx::Point(10, 10), + gfx::Point(10, 10), 0, 0); + DispatchEventUsingWindowDispatcher(&mouse0); + EXPECT_TRUE(cursor_client.IsMouseEventsEnabled()); + + // This press is required for the GestureRecognizer to associate a target + // with kTouchId + ui::TouchEvent press0( + ui::ET_TOUCH_PRESSED, gfx::Point(90, 90), 1, GetTime()); + DispatchEventUsingWindowDispatcher(&press0); + EXPECT_FALSE(cursor_client.IsMouseEventsEnabled()); + + ui::TouchEvent move(ui::ET_TOUCH_MOVED, gfx::Point(10, 10), 1, GetTime()); + DispatchEventUsingWindowDispatcher(&move); + EXPECT_FALSE(cursor_client.IsMouseEventsEnabled()); + + ui::TouchEvent release( + ui::ET_TOUCH_RELEASED, gfx::Point(10, 10), 1, GetTime()); + DispatchEventUsingWindowDispatcher(&release); + EXPECT_FALSE(cursor_client.IsMouseEventsEnabled()); + + ui::MouseEvent mouse1(ui::ET_MOUSE_MOVED, gfx::Point(10, 10), + gfx::Point(10, 10), 0, 0); + // Move the cursor again. The cursor should be visible. + DispatchEventUsingWindowDispatcher(&mouse1); + EXPECT_TRUE(cursor_client.IsMouseEventsEnabled()); + + // Now activate the window and press on it again. + ui::TouchEvent press1( + ui::ET_TOUCH_PRESSED, gfx::Point(90, 90), 1, GetTime()); + aura::client::GetActivationClient( + root_window())->ActivateWindow(window.get()); + DispatchEventUsingWindowDispatcher(&press1); + EXPECT_FALSE(cursor_client.IsMouseEventsEnabled()); + aura::Env::GetInstance()->RemovePreTargetHandler(compound_filter.get()); +} +#endif // defined(OS_CHROMEOS) || defined(OS_WIN) + +// Tests that if an event filter consumes a gesture, then it doesn't focus the +// window. +TEST_F(CompoundEventFilterTest, FilterConsumedGesture) { + scoped_ptr<CompoundEventFilter> compound_filter(new CompoundEventFilter); + scoped_ptr<ui::EventHandler> gesure_handler(new ConsumeGestureEventFilter); + compound_filter->AddHandler(gesure_handler.get()); + aura::Env::GetInstance()->AddPreTargetHandler(compound_filter.get()); + aura::test::TestWindowDelegate delegate; + DCHECK(root_window()); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate(&delegate, 1234, + gfx::Rect(5, 5, 100, 100), root_window())); + window->Show(); + + EXPECT_TRUE(window->CanFocus()); + EXPECT_FALSE(window->HasFocus()); + + // Tap on the window should not focus it since the filter will be consuming + // the gestures. + aura::test::EventGenerator generator(root_window(), gfx::Point(50, 50)); + generator.PressTouch(); + EXPECT_FALSE(window->HasFocus()); + + compound_filter->RemoveHandler(gesure_handler.get()); + aura::Env::GetInstance()->RemovePreTargetHandler(compound_filter.get()); +} + +// Verifies we don't attempt to hide the mouse when the mouse is down and a +// touch event comes in. +TEST_F(CompoundEventFilterTest, DontHideWhenMouseDown) { + aura::test::EventGenerator event_generator(root_window()); + + scoped_ptr<CompoundEventFilter> compound_filter(new CompoundEventFilter); + aura::Env::GetInstance()->AddPreTargetHandler(compound_filter.get()); + aura::test::TestWindowDelegate delegate; + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate(&delegate, 1234, + gfx::Rect(5, 5, 100, 100), root_window())); + window->Show(); + + aura::test::TestCursorClient cursor_client(root_window()); + + // Move and press the mouse over the window. + event_generator.MoveMouseTo(10, 10); + EXPECT_TRUE(cursor_client.IsMouseEventsEnabled()); + event_generator.PressLeftButton(); + EXPECT_TRUE(cursor_client.IsMouseEventsEnabled()); + EXPECT_TRUE(aura::Env::GetInstance()->IsMouseButtonDown()); + + // Do a touch event. As the mouse button is down this should not disable mouse + // events. + event_generator.PressTouch(); + EXPECT_TRUE(cursor_client.IsMouseEventsEnabled()); + aura::Env::GetInstance()->RemovePreTargetHandler(compound_filter.get()); +} + +#if defined(OS_WIN) +// Windows synthesizes mouse messages for touch events. We should not be +// showing the cursor when we receive such messages. +TEST_F(CompoundEventFilterTest, DontShowCursorOnMouseMovesFromTouch) { + scoped_ptr<CompoundEventFilter> compound_filter(new CompoundEventFilter); + aura::Env::GetInstance()->AddPreTargetHandler(compound_filter.get()); + aura::test::TestWindowDelegate delegate; + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate(&delegate, 1234, + gfx::Rect(5, 5, 100, 100), root_window())); + window->Show(); + window->SetCapture(); + + aura::test::TestCursorClient cursor_client(root_window()); + cursor_client.DisableMouseEvents(); + EXPECT_FALSE(cursor_client.IsMouseEventsEnabled()); + + ui::MouseEvent mouse0(ui::ET_MOUSE_MOVED, gfx::Point(10, 10), + gfx::Point(10, 10), 0, 0); + mouse0.set_flags(mouse0.flags() | ui::EF_FROM_TOUCH); + + DispatchEventUsingWindowDispatcher(&mouse0); + EXPECT_FALSE(cursor_client.IsMouseEventsEnabled()); + + mouse0.set_flags(mouse0.flags() & ~ui::EF_FROM_TOUCH); + DispatchEventUsingWindowDispatcher(&mouse0); + EXPECT_TRUE(cursor_client.IsMouseEventsEnabled()); + + aura::Env::GetInstance()->RemovePreTargetHandler(compound_filter.get()); +} +#endif + +} // namespace wm diff --git a/chromium/ui/wm/core/cursor_manager.cc b/chromium/ui/wm/core/cursor_manager.cc new file mode 100644 index 00000000000..e9f988566a3 --- /dev/null +++ b/chromium/ui/wm/core/cursor_manager.cc @@ -0,0 +1,217 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/cursor_manager.h" + +#include "base/logging.h" +#include "ui/aura/client/cursor_client_observer.h" +#include "ui/wm/core/native_cursor_manager.h" +#include "ui/wm/core/native_cursor_manager_delegate.h" + +namespace wm { + +namespace internal { + +// Represents the cursor state which is composed of cursor type, visibility, and +// mouse events enable state. When mouse events are disabled, the cursor is +// always invisible. +class CursorState { + public: + CursorState() + : cursor_(ui::kCursorNone), + visible_(true), + cursor_set_(ui::CURSOR_SET_NORMAL), + mouse_events_enabled_(true), + visible_on_mouse_events_enabled_(true) { + } + + gfx::NativeCursor cursor() const { return cursor_; } + void set_cursor(gfx::NativeCursor cursor) { cursor_ = cursor; } + + bool visible() const { return visible_; } + void SetVisible(bool visible) { + if (mouse_events_enabled_) + visible_ = visible; + // Ignores the call when mouse events disabled. + } + + ui::CursorSetType cursor_set() const { return cursor_set_; } + void set_cursor_set(ui::CursorSetType cursor_set) { + cursor_set_ = cursor_set; + } + + bool mouse_events_enabled() const { return mouse_events_enabled_; } + void SetMouseEventsEnabled(bool enabled) { + if (mouse_events_enabled_ == enabled) + return; + mouse_events_enabled_ = enabled; + + // Restores the visibility when mouse events are enabled. + if (enabled) { + visible_ = visible_on_mouse_events_enabled_; + } else { + visible_on_mouse_events_enabled_ = visible_; + visible_ = false; + } + } + + private: + gfx::NativeCursor cursor_; + bool visible_; + ui::CursorSetType cursor_set_; + bool mouse_events_enabled_; + + // The visibility to set when mouse events are enabled. + bool visible_on_mouse_events_enabled_; + + DISALLOW_COPY_AND_ASSIGN(CursorState); +}; + +} // namespace internal + +CursorManager::CursorManager(scoped_ptr<NativeCursorManager> delegate) + : delegate_(delegate.Pass()), + cursor_lock_count_(0), + current_state_(new internal::CursorState), + state_on_unlock_(new internal::CursorState) { +} + +CursorManager::~CursorManager() { +} + +void CursorManager::SetCursor(gfx::NativeCursor cursor) { + state_on_unlock_->set_cursor(cursor); + if (cursor_lock_count_ == 0 && + GetCursor() != state_on_unlock_->cursor()) { + delegate_->SetCursor(state_on_unlock_->cursor(), this); + } +} + +gfx::NativeCursor CursorManager::GetCursor() const { + return current_state_->cursor(); +} + +void CursorManager::ShowCursor() { + state_on_unlock_->SetVisible(true); + if (cursor_lock_count_ == 0 && + IsCursorVisible() != state_on_unlock_->visible()) { + delegate_->SetVisibility(state_on_unlock_->visible(), this); + FOR_EACH_OBSERVER(aura::client::CursorClientObserver, observers_, + OnCursorVisibilityChanged(true)); + } +} + +void CursorManager::HideCursor() { + state_on_unlock_->SetVisible(false); + if (cursor_lock_count_ == 0 && + IsCursorVisible() != state_on_unlock_->visible()) { + delegate_->SetVisibility(state_on_unlock_->visible(), this); + FOR_EACH_OBSERVER(aura::client::CursorClientObserver, observers_, + OnCursorVisibilityChanged(false)); + } +} + +bool CursorManager::IsCursorVisible() const { + return current_state_->visible(); +} + +void CursorManager::SetCursorSet(ui::CursorSetType cursor_set) { + state_on_unlock_->set_cursor_set(cursor_set); + if (GetCursorSet() != state_on_unlock_->cursor_set()) + delegate_->SetCursorSet(state_on_unlock_->cursor_set(), this); +} + +ui::CursorSetType CursorManager::GetCursorSet() const { + return current_state_->cursor_set(); +} + +void CursorManager::EnableMouseEvents() { + state_on_unlock_->SetMouseEventsEnabled(true); + if (cursor_lock_count_ == 0 && + IsMouseEventsEnabled() != state_on_unlock_->mouse_events_enabled()) { + delegate_->SetMouseEventsEnabled(state_on_unlock_->mouse_events_enabled(), + this); + } +} + +void CursorManager::DisableMouseEvents() { + state_on_unlock_->SetMouseEventsEnabled(false); + if (cursor_lock_count_ == 0 && + IsMouseEventsEnabled() != state_on_unlock_->mouse_events_enabled()) { + delegate_->SetMouseEventsEnabled(state_on_unlock_->mouse_events_enabled(), + this); + } +} + +bool CursorManager::IsMouseEventsEnabled() const { + return current_state_->mouse_events_enabled(); +} + +void CursorManager::SetDisplay(const gfx::Display& display) { + delegate_->SetDisplay(display, this); +} + +void CursorManager::LockCursor() { + cursor_lock_count_++; +} + +void CursorManager::UnlockCursor() { + cursor_lock_count_--; + DCHECK_GE(cursor_lock_count_, 0); + if (cursor_lock_count_ > 0) + return; + + if (GetCursor() != state_on_unlock_->cursor()) { + delegate_->SetCursor(state_on_unlock_->cursor(), this); + } + if (IsMouseEventsEnabled() != state_on_unlock_->mouse_events_enabled()) { + delegate_->SetMouseEventsEnabled(state_on_unlock_->mouse_events_enabled(), + this); + } + if (IsCursorVisible() != state_on_unlock_->visible()) { + delegate_->SetVisibility(state_on_unlock_->visible(), + this); + } +} + +bool CursorManager::IsCursorLocked() const { + return cursor_lock_count_ > 0; +} + +void CursorManager::AddObserver( + aura::client::CursorClientObserver* observer) { + observers_.AddObserver(observer); +} + +void CursorManager::RemoveObserver( + aura::client::CursorClientObserver* observer) { + observers_.RemoveObserver(observer); +} + +bool CursorManager::ShouldHideCursorOnKeyEvent( + const ui::KeyEvent& event) const { + return false; +} + +void CursorManager::CommitCursor(gfx::NativeCursor cursor) { + current_state_->set_cursor(cursor); +} + +void CursorManager::CommitVisibility(bool visible) { + // TODO(tdanderson): Find a better place for this so we don't + // notify the observers more than is necessary. + FOR_EACH_OBSERVER(aura::client::CursorClientObserver, observers_, + OnCursorVisibilityChanged(visible)); + current_state_->SetVisible(visible); +} + +void CursorManager::CommitCursorSet(ui::CursorSetType cursor_set) { + current_state_->set_cursor_set(cursor_set); +} + +void CursorManager::CommitMouseEventsEnabled(bool enabled) { + current_state_->SetMouseEventsEnabled(enabled); +} + +} // namespace wm diff --git a/chromium/ui/wm/core/cursor_manager.h b/chromium/ui/wm/core/cursor_manager.h new file mode 100644 index 00000000000..f60ef14174c --- /dev/null +++ b/chromium/ui/wm/core/cursor_manager.h @@ -0,0 +1,93 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_WM_CORE_CURSOR_MANAGER_H_ +#define UI_WM_CORE_CURSOR_MANAGER_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/observer_list.h" +#include "ui/aura/client/cursor_client.h" +#include "ui/base/cursor/cursor.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/gfx/point.h" +#include "ui/wm/core/native_cursor_manager_delegate.h" +#include "ui/wm/wm_export.h" + +namespace gfx { +class Display; +} + +namespace ui { +class KeyEvent; +} + +namespace wm { + +namespace internal { +class CursorState; +} + +class NativeCursorManager; + +// This class receives requests to change cursor properties, as well as +// requests to queue any further changes until a later time. It sends changes +// to the NativeCursorManager, which communicates back to us when these changes +// were made through the NativeCursorManagerDelegate interface. +class WM_EXPORT CursorManager : public aura::client::CursorClient, + public NativeCursorManagerDelegate { + public: + explicit CursorManager(scoped_ptr<NativeCursorManager> delegate); + virtual ~CursorManager(); + + // Overridden from aura::client::CursorClient: + virtual void SetCursor(gfx::NativeCursor) OVERRIDE; + virtual gfx::NativeCursor GetCursor() const OVERRIDE; + virtual void ShowCursor() OVERRIDE; + virtual void HideCursor() OVERRIDE; + virtual bool IsCursorVisible() const OVERRIDE; + virtual void SetCursorSet(ui::CursorSetType cursor_set) OVERRIDE; + virtual ui::CursorSetType GetCursorSet() const OVERRIDE; + virtual void EnableMouseEvents() OVERRIDE; + virtual void DisableMouseEvents() OVERRIDE; + virtual bool IsMouseEventsEnabled() const OVERRIDE; + virtual void SetDisplay(const gfx::Display& display) OVERRIDE; + virtual void LockCursor() OVERRIDE; + virtual void UnlockCursor() OVERRIDE; + virtual bool IsCursorLocked() const OVERRIDE; + virtual void AddObserver( + aura::client::CursorClientObserver* observer) OVERRIDE; + virtual void RemoveObserver( + aura::client::CursorClientObserver* observer) OVERRIDE; + virtual bool ShouldHideCursorOnKeyEvent( + const ui::KeyEvent& event) const OVERRIDE; + + private: + // Overridden from NativeCursorManagerDelegate: + virtual void CommitCursor(gfx::NativeCursor cursor) OVERRIDE; + virtual void CommitVisibility(bool visible) OVERRIDE; + virtual void CommitCursorSet(ui::CursorSetType cursor_set) OVERRIDE; + virtual void CommitMouseEventsEnabled(bool enabled) OVERRIDE; + + scoped_ptr<NativeCursorManager> delegate_; + + // Number of times LockCursor() has been invoked without a corresponding + // UnlockCursor(). + int cursor_lock_count_; + + // The current state of the cursor. + scoped_ptr<internal::CursorState> current_state_; + + // The cursor state to restore when the cursor is unlocked. + scoped_ptr<internal::CursorState> state_on_unlock_; + + ObserverList<aura::client::CursorClientObserver> observers_; + + DISALLOW_COPY_AND_ASSIGN(CursorManager); +}; + +} // namespace wm + +#endif // UI_WM_CORE_CURSOR_MANAGER_H_ diff --git a/chromium/ui/wm/core/cursor_manager_unittest.cc b/chromium/ui/wm/core/cursor_manager_unittest.cc new file mode 100644 index 00000000000..58c3b174d80 --- /dev/null +++ b/chromium/ui/wm/core/cursor_manager_unittest.cc @@ -0,0 +1,330 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/cursor_manager.h" + +#include "ui/aura/client/cursor_client_observer.h" +#include "ui/aura/test/aura_test_base.h" +#include "ui/wm/core/native_cursor_manager.h" + +namespace { + +class TestingCursorManager : public wm::NativeCursorManager { + public: + // Overridden from wm::NativeCursorManager: + virtual void SetDisplay( + const gfx::Display& display, + wm::NativeCursorManagerDelegate* delegate) OVERRIDE {} + + virtual void SetCursor( + gfx::NativeCursor cursor, + wm::NativeCursorManagerDelegate* delegate) OVERRIDE { + delegate->CommitCursor(cursor); + } + + virtual void SetVisibility( + bool visible, + wm::NativeCursorManagerDelegate* delegate) OVERRIDE { + delegate->CommitVisibility(visible); + } + + virtual void SetMouseEventsEnabled( + bool enabled, + wm::NativeCursorManagerDelegate* delegate) OVERRIDE { + delegate->CommitMouseEventsEnabled(enabled); + } + + virtual void SetCursorSet( + ui::CursorSetType cursor_set, + wm::NativeCursorManagerDelegate* delegate) OVERRIDE { + delegate->CommitCursorSet(cursor_set); + } +}; + +} // namespace + +class CursorManagerTest : public aura::test::AuraTestBase { + protected: + CursorManagerTest() + : delegate_(new TestingCursorManager), + cursor_manager_(scoped_ptr<wm::NativeCursorManager>( + delegate_)) { + } + + TestingCursorManager* delegate_; + wm::CursorManager cursor_manager_; +}; + +class TestingCursorClientObserver : public aura::client::CursorClientObserver { + public: + TestingCursorClientObserver() + : cursor_visibility_(false), + did_visibility_change_(false) {} + void reset() { cursor_visibility_ = did_visibility_change_ = false; } + bool is_cursor_visible() const { return cursor_visibility_; } + bool did_visibility_change() const { return did_visibility_change_; } + + // Overridden from aura::client::CursorClientObserver: + virtual void OnCursorVisibilityChanged(bool is_visible) OVERRIDE { + cursor_visibility_ = is_visible; + did_visibility_change_ = true; + } + + private: + bool cursor_visibility_; + bool did_visibility_change_; + + DISALLOW_COPY_AND_ASSIGN(TestingCursorClientObserver); +}; + +TEST_F(CursorManagerTest, ShowHideCursor) { + cursor_manager_.SetCursor(ui::kCursorCopy); + EXPECT_EQ(ui::kCursorCopy, cursor_manager_.GetCursor().native_type()); + + cursor_manager_.ShowCursor(); + EXPECT_TRUE(cursor_manager_.IsCursorVisible()); + cursor_manager_.HideCursor(); + EXPECT_FALSE(cursor_manager_.IsCursorVisible()); + // The current cursor does not change even when the cursor is not shown. + EXPECT_EQ(ui::kCursorCopy, cursor_manager_.GetCursor().native_type()); + + // Check if cursor visibility is locked. + cursor_manager_.LockCursor(); + EXPECT_FALSE(cursor_manager_.IsCursorVisible()); + cursor_manager_.ShowCursor(); + EXPECT_FALSE(cursor_manager_.IsCursorVisible()); + cursor_manager_.UnlockCursor(); + EXPECT_TRUE(cursor_manager_.IsCursorVisible()); + + cursor_manager_.LockCursor(); + EXPECT_TRUE(cursor_manager_.IsCursorVisible()); + cursor_manager_.HideCursor(); + EXPECT_TRUE(cursor_manager_.IsCursorVisible()); + cursor_manager_.UnlockCursor(); + EXPECT_FALSE(cursor_manager_.IsCursorVisible()); + + // Checks setting visiblity while cursor is locked does not affect the + // subsequent uses of UnlockCursor. + cursor_manager_.LockCursor(); + cursor_manager_.HideCursor(); + cursor_manager_.UnlockCursor(); + EXPECT_FALSE(cursor_manager_.IsCursorVisible()); + + cursor_manager_.ShowCursor(); + cursor_manager_.LockCursor(); + cursor_manager_.UnlockCursor(); + EXPECT_TRUE(cursor_manager_.IsCursorVisible()); + + cursor_manager_.LockCursor(); + cursor_manager_.ShowCursor(); + cursor_manager_.UnlockCursor(); + EXPECT_TRUE(cursor_manager_.IsCursorVisible()); + + cursor_manager_.HideCursor(); + cursor_manager_.LockCursor(); + cursor_manager_.UnlockCursor(); + EXPECT_FALSE(cursor_manager_.IsCursorVisible()); +} + +// Verifies that LockCursor/UnlockCursor work correctly with +// EnableMouseEvents and DisableMouseEvents +TEST_F(CursorManagerTest, EnableDisableMouseEvents) { + cursor_manager_.SetCursor(ui::kCursorCopy); + EXPECT_EQ(ui::kCursorCopy, cursor_manager_.GetCursor().native_type()); + + cursor_manager_.EnableMouseEvents(); + EXPECT_TRUE(cursor_manager_.IsMouseEventsEnabled()); + cursor_manager_.DisableMouseEvents(); + EXPECT_FALSE(cursor_manager_.IsMouseEventsEnabled()); + // The current cursor does not change even when the cursor is not shown. + EXPECT_EQ(ui::kCursorCopy, cursor_manager_.GetCursor().native_type()); + + // Check if cursor enable state is locked. + cursor_manager_.LockCursor(); + EXPECT_FALSE(cursor_manager_.IsMouseEventsEnabled()); + cursor_manager_.EnableMouseEvents(); + EXPECT_FALSE(cursor_manager_.IsMouseEventsEnabled()); + cursor_manager_.UnlockCursor(); + EXPECT_TRUE(cursor_manager_.IsMouseEventsEnabled()); + + cursor_manager_.LockCursor(); + EXPECT_TRUE(cursor_manager_.IsMouseEventsEnabled()); + cursor_manager_.DisableMouseEvents(); + EXPECT_TRUE(cursor_manager_.IsMouseEventsEnabled()); + cursor_manager_.UnlockCursor(); + EXPECT_FALSE(cursor_manager_.IsMouseEventsEnabled()); + + // Checks enabling cursor while cursor is locked does not affect the + // subsequent uses of UnlockCursor. + cursor_manager_.LockCursor(); + cursor_manager_.DisableMouseEvents(); + cursor_manager_.UnlockCursor(); + EXPECT_FALSE(cursor_manager_.IsMouseEventsEnabled()); + + cursor_manager_.EnableMouseEvents(); + cursor_manager_.LockCursor(); + cursor_manager_.UnlockCursor(); + EXPECT_TRUE(cursor_manager_.IsMouseEventsEnabled()); + + cursor_manager_.LockCursor(); + cursor_manager_.EnableMouseEvents(); + cursor_manager_.UnlockCursor(); + EXPECT_TRUE(cursor_manager_.IsMouseEventsEnabled()); + + cursor_manager_.DisableMouseEvents(); + cursor_manager_.LockCursor(); + cursor_manager_.UnlockCursor(); + EXPECT_FALSE(cursor_manager_.IsMouseEventsEnabled()); +} + +TEST_F(CursorManagerTest, SetCursorSet) { + EXPECT_EQ(ui::CURSOR_SET_NORMAL, cursor_manager_.GetCursorSet()); + + cursor_manager_.SetCursorSet(ui::CURSOR_SET_NORMAL); + EXPECT_EQ(ui::CURSOR_SET_NORMAL, cursor_manager_.GetCursorSet()); + + cursor_manager_.SetCursorSet(ui::CURSOR_SET_LARGE); + EXPECT_EQ(ui::CURSOR_SET_LARGE, cursor_manager_.GetCursorSet()); + + cursor_manager_.SetCursorSet(ui::CURSOR_SET_NORMAL); + EXPECT_EQ(ui::CURSOR_SET_NORMAL, cursor_manager_.GetCursorSet()); +} + +TEST_F(CursorManagerTest, IsMouseEventsEnabled) { + cursor_manager_.EnableMouseEvents(); + EXPECT_TRUE(cursor_manager_.IsMouseEventsEnabled()); + cursor_manager_.DisableMouseEvents(); + EXPECT_FALSE(cursor_manager_.IsMouseEventsEnabled()); +} + +// Verifies that the mouse events enable state changes correctly when +// ShowCursor/HideCursor and EnableMouseEvents/DisableMouseEvents are used +// together. +TEST_F(CursorManagerTest, ShowAndEnable) { + // Changing the visibility of the cursor does not affect the enable state. + cursor_manager_.EnableMouseEvents(); + cursor_manager_.ShowCursor(); + EXPECT_TRUE(cursor_manager_.IsCursorVisible()); + EXPECT_TRUE(cursor_manager_.IsMouseEventsEnabled()); + cursor_manager_.HideCursor(); + EXPECT_FALSE(cursor_manager_.IsCursorVisible()); + EXPECT_TRUE(cursor_manager_.IsMouseEventsEnabled()); + cursor_manager_.ShowCursor(); + EXPECT_TRUE(cursor_manager_.IsCursorVisible()); + EXPECT_TRUE(cursor_manager_.IsMouseEventsEnabled()); + + // When mouse events are disabled, it also gets invisible. + EXPECT_TRUE(cursor_manager_.IsCursorVisible()); + cursor_manager_.DisableMouseEvents(); + EXPECT_FALSE(cursor_manager_.IsCursorVisible()); + EXPECT_FALSE(cursor_manager_.IsMouseEventsEnabled()); + + // When mouse events are enabled, it restores the visibility state. + cursor_manager_.EnableMouseEvents(); + EXPECT_TRUE(cursor_manager_.IsCursorVisible()); + EXPECT_TRUE(cursor_manager_.IsMouseEventsEnabled()); + + cursor_manager_.ShowCursor(); + cursor_manager_.DisableMouseEvents(); + EXPECT_FALSE(cursor_manager_.IsCursorVisible()); + EXPECT_FALSE(cursor_manager_.IsMouseEventsEnabled()); + cursor_manager_.EnableMouseEvents(); + EXPECT_TRUE(cursor_manager_.IsCursorVisible()); + EXPECT_TRUE(cursor_manager_.IsMouseEventsEnabled()); + + cursor_manager_.HideCursor(); + cursor_manager_.DisableMouseEvents(); + EXPECT_FALSE(cursor_manager_.IsCursorVisible()); + EXPECT_FALSE(cursor_manager_.IsMouseEventsEnabled()); + cursor_manager_.EnableMouseEvents(); + EXPECT_FALSE(cursor_manager_.IsCursorVisible()); + EXPECT_TRUE(cursor_manager_.IsMouseEventsEnabled()); + + // When mouse events are disabled, ShowCursor is ignored. + cursor_manager_.DisableMouseEvents(); + EXPECT_FALSE(cursor_manager_.IsCursorVisible()); + EXPECT_FALSE(cursor_manager_.IsMouseEventsEnabled()); + cursor_manager_.ShowCursor(); + EXPECT_FALSE(cursor_manager_.IsCursorVisible()); + EXPECT_FALSE(cursor_manager_.IsMouseEventsEnabled()); + cursor_manager_.DisableMouseEvents(); + EXPECT_FALSE(cursor_manager_.IsCursorVisible()); + EXPECT_FALSE(cursor_manager_.IsMouseEventsEnabled()); +} + +// Verifies that calling DisableMouseEvents multiple times in a row makes no +// difference compared with calling it once. +// This is a regression test for http://crbug.com/169404. +TEST_F(CursorManagerTest, MultipleDisableMouseEvents) { + cursor_manager_.DisableMouseEvents(); + cursor_manager_.DisableMouseEvents(); + cursor_manager_.EnableMouseEvents(); + cursor_manager_.LockCursor(); + cursor_manager_.UnlockCursor(); + EXPECT_TRUE(cursor_manager_.IsCursorVisible()); +} + +// Verifies that calling EnableMouseEvents multiple times in a row makes no +// difference compared with calling it once. +TEST_F(CursorManagerTest, MultipleEnableMouseEvents) { + cursor_manager_.DisableMouseEvents(); + cursor_manager_.EnableMouseEvents(); + cursor_manager_.EnableMouseEvents(); + cursor_manager_.LockCursor(); + cursor_manager_.UnlockCursor(); + EXPECT_TRUE(cursor_manager_.IsCursorVisible()); +} + +TEST_F(CursorManagerTest, TestCursorClientObserver) { + // Add two observers. Both should have OnCursorVisibilityChanged() + // invoked when the visibility of the cursor changes. + TestingCursorClientObserver observer_a; + TestingCursorClientObserver observer_b; + cursor_manager_.AddObserver(&observer_a); + cursor_manager_.AddObserver(&observer_b); + + // Initial state before any events have been sent. + observer_a.reset(); + observer_b.reset(); + EXPECT_FALSE(observer_a.did_visibility_change()); + EXPECT_FALSE(observer_b.did_visibility_change()); + EXPECT_FALSE(observer_a.is_cursor_visible()); + EXPECT_FALSE(observer_b.is_cursor_visible()); + + // Hide the cursor using HideCursor(). + cursor_manager_.HideCursor(); + EXPECT_TRUE(observer_a.did_visibility_change()); + EXPECT_TRUE(observer_b.did_visibility_change()); + EXPECT_FALSE(observer_a.is_cursor_visible()); + EXPECT_FALSE(observer_b.is_cursor_visible()); + + // Show the cursor using ShowCursor(). + observer_a.reset(); + observer_b.reset(); + cursor_manager_.ShowCursor(); + EXPECT_TRUE(observer_a.did_visibility_change()); + EXPECT_TRUE(observer_b.did_visibility_change()); + EXPECT_TRUE(observer_a.is_cursor_visible()); + EXPECT_TRUE(observer_b.is_cursor_visible()); + + // Remove observer_b. Its OnCursorVisibilityChanged() should + // not be invoked past this point. + cursor_manager_.RemoveObserver(&observer_b); + + // Hide the cursor using HideCursor(). + observer_a.reset(); + observer_b.reset(); + cursor_manager_.HideCursor(); + EXPECT_TRUE(observer_a.did_visibility_change()); + EXPECT_FALSE(observer_b.did_visibility_change()); + EXPECT_FALSE(observer_a.is_cursor_visible()); + + // Show the cursor using ShowCursor(). + observer_a.reset(); + observer_b.reset(); + cursor_manager_.ShowCursor(); + EXPECT_TRUE(observer_a.did_visibility_change()); + EXPECT_FALSE(observer_b.did_visibility_change()); + EXPECT_TRUE(observer_a.is_cursor_visible()); +} diff --git a/chromium/ui/wm/core/default_activation_client.cc b/chromium/ui/wm/core/default_activation_client.cc new file mode 100644 index 00000000000..222c9acb4cb --- /dev/null +++ b/chromium/ui/wm/core/default_activation_client.cc @@ -0,0 +1,157 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/default_activation_client.h" + +#include "ui/aura/window.h" +#include "ui/wm/public/activation_change_observer.h" +#include "ui/wm/public/activation_delegate.h" + +namespace wm { + +// Takes care of observing root window destruction & destroying the client. +class DefaultActivationClient::Deleter : public aura::WindowObserver { + public: + Deleter(DefaultActivationClient* client, aura::Window* root_window) + : client_(client), + root_window_(root_window) { + root_window_->AddObserver(this); + } + + private: + virtual ~Deleter() {} + + // Overridden from WindowObserver: + virtual void OnWindowDestroyed(aura::Window* window) OVERRIDE { + DCHECK_EQ(window, root_window_); + root_window_->RemoveObserver(this); + delete client_; + delete this; + } + + DefaultActivationClient* client_; + aura::Window* root_window_; + + DISALLOW_COPY_AND_ASSIGN(Deleter); +}; + +//////////////////////////////////////////////////////////////////////////////// +// DefaultActivationClient, public: + +DefaultActivationClient::DefaultActivationClient(aura::Window* root_window) + : last_active_(NULL) { + aura::client::SetActivationClient(root_window, this); + new Deleter(this, root_window); +} + +//////////////////////////////////////////////////////////////////////////////// +// DefaultActivationClient, client::ActivationClient implementation: + +void DefaultActivationClient::AddObserver( + aura::client::ActivationChangeObserver* observer) { + observers_.AddObserver(observer); +} + +void DefaultActivationClient::RemoveObserver( + aura::client::ActivationChangeObserver* observer) { + observers_.RemoveObserver(observer); +} + +void DefaultActivationClient::ActivateWindow(aura::Window* window) { + aura::Window* last_active = GetActiveWindow(); + if (last_active == window) + return; + + last_active_ = last_active; + RemoveActiveWindow(window); + active_windows_.push_back(window); + window->parent()->StackChildAtTop(window); + window->AddObserver(this); + + FOR_EACH_OBSERVER(aura::client::ActivationChangeObserver, + observers_, + OnWindowActivated(window, last_active)); + + aura::client::ActivationChangeObserver* observer = + aura::client::GetActivationChangeObserver(last_active); + if (observer) + observer->OnWindowActivated(window, last_active); + observer = aura::client::GetActivationChangeObserver(window); + if (observer) + observer->OnWindowActivated(window, last_active); +} + +void DefaultActivationClient::DeactivateWindow(aura::Window* window) { + aura::client::ActivationChangeObserver* observer = + aura::client::GetActivationChangeObserver(window); + if (observer) + observer->OnWindowActivated(NULL, window); + if (last_active_) + ActivateWindow(last_active_); +} + +aura::Window* DefaultActivationClient::GetActiveWindow() { + if (active_windows_.empty()) + return NULL; + return active_windows_.back(); +} + +aura::Window* DefaultActivationClient::GetActivatableWindow( + aura::Window* window) { + return NULL; +} + +aura::Window* DefaultActivationClient::GetToplevelWindow(aura::Window* window) { + return NULL; +} + +bool DefaultActivationClient::OnWillFocusWindow(aura::Window* window, + const ui::Event* event) { + return true; +} + +bool DefaultActivationClient::CanActivateWindow(aura::Window* window) const { + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// DefaultActivationClient, aura::WindowObserver implementation: + +void DefaultActivationClient::OnWindowDestroyed(aura::Window* window) { + if (window == last_active_) + last_active_ = NULL; + + if (window == GetActiveWindow()) { + active_windows_.pop_back(); + aura::Window* next_active = GetActiveWindow(); + if (next_active && aura::client::GetActivationChangeObserver(next_active)) { + aura::client::GetActivationChangeObserver(next_active)->OnWindowActivated( + next_active, NULL); + } + return; + } + + RemoveActiveWindow(window); +} + +//////////////////////////////////////////////////////////////////////////////// +// DefaultActivationClient, private: + +DefaultActivationClient::~DefaultActivationClient() { + for (unsigned int i = 0; i < active_windows_.size(); ++i) { + active_windows_[i]->RemoveObserver(this); + } +} + +void DefaultActivationClient::RemoveActiveWindow(aura::Window* window) { + for (unsigned int i = 0; i < active_windows_.size(); ++i) { + if (active_windows_[i] == window) { + active_windows_.erase(active_windows_.begin() + i); + window->RemoveObserver(this); + return; + } + } +} + +} // namespace wm diff --git a/chromium/ui/wm/core/default_activation_client.h b/chromium/ui/wm/core/default_activation_client.h new file mode 100644 index 00000000000..3b6bffc3196 --- /dev/null +++ b/chromium/ui/wm/core/default_activation_client.h @@ -0,0 +1,73 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_WM_CORE_DEFAULT_ACTIVATION_CLIENT_H_ +#define UI_WM_CORE_DEFAULT_ACTIVATION_CLIENT_H_ + +#include <vector> + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/observer_list.h" +#include "ui/aura/window_observer.h" +#include "ui/wm/public/activation_client.h" +#include "ui/wm/wm_export.h" + +namespace aura { +namespace client { +class ActivationChangeObserver; +} +} + +namespace wm { + +// Simple ActivationClient implementation for use by tests and other targets +// that just need basic behavior (e.g. activate windows whenever requested, +// restack windows at the top when they're activated, etc.). This object deletes +// itself when the root window it is associated with is destroyed. +class WM_EXPORT DefaultActivationClient : public aura::client::ActivationClient, + public aura::WindowObserver { + public: + explicit DefaultActivationClient(aura::Window* root_window); + + // Overridden from aura::client::ActivationClient: + virtual void AddObserver( + aura::client::ActivationChangeObserver* observer) OVERRIDE; + virtual void RemoveObserver( + aura::client::ActivationChangeObserver* observer) OVERRIDE; + virtual void ActivateWindow(aura::Window* window) OVERRIDE; + virtual void DeactivateWindow(aura::Window* window) OVERRIDE; + virtual aura::Window* GetActiveWindow() OVERRIDE; + virtual aura::Window* GetActivatableWindow(aura::Window* window) OVERRIDE; + virtual aura::Window* GetToplevelWindow(aura::Window* window) OVERRIDE; + virtual bool OnWillFocusWindow(aura::Window* window, + const ui::Event* event) OVERRIDE; + virtual bool CanActivateWindow(aura::Window* window) const OVERRIDE; + + // Overridden from WindowObserver: + virtual void OnWindowDestroyed(aura::Window* window) OVERRIDE; + + private: + class Deleter; + + virtual ~DefaultActivationClient(); + void RemoveActiveWindow(aura::Window* window); + + // This class explicitly does NOT store the active window in a window property + // to make sure that ActivationChangeObserver is not treated as part of the + // aura API. Assumptions to that end will cause tests that use this client to + // fail. + std::vector<aura::Window*> active_windows_; + + // The window which was active before the currently active one. + aura::Window* last_active_; + + ObserverList<aura::client::ActivationChangeObserver> observers_; + + DISALLOW_COPY_AND_ASSIGN(DefaultActivationClient); +}; + +} // namespace wm + +#endif // UI_WM_CORE_DEFAULT_ACTIVATION_CLIENT_H_ diff --git a/chromium/ui/wm/core/easy_resize_window_targeter.cc b/chromium/ui/wm/core/easy_resize_window_targeter.cc new file mode 100644 index 00000000000..445028cbf92 --- /dev/null +++ b/chromium/ui/wm/core/easy_resize_window_targeter.cc @@ -0,0 +1,62 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/easy_resize_window_targeter.h" + +#include "ui/aura/window.h" +#include "ui/gfx/geometry/insets_f.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/wm/public/transient_window_client.h" + +namespace wm { + +EasyResizeWindowTargeter::EasyResizeWindowTargeter( + aura::Window* container, + const gfx::Insets& mouse_extend, + const gfx::Insets& touch_extend) + : container_(container), + mouse_extend_(mouse_extend), + touch_extend_(touch_extend) { +} + +EasyResizeWindowTargeter::~EasyResizeWindowTargeter() { +} + +bool EasyResizeWindowTargeter::EventLocationInsideBounds( + ui::EventTarget* target, + const ui::LocatedEvent& event) const { + aura::Window* window = static_cast<aura::Window*>(target); + if (ShouldUseExtendedBounds(window)) { + // Note that |event|'s location is in |window|'s parent's coordinate system, + // so convert it to |window|'s coordinate system first. + gfx::Point point = event.location(); + if (window->parent()) + aura::Window::ConvertPointToTarget(window->parent(), window, &point); + + gfx::Rect bounds(window->bounds().size()); + if (event.IsTouchEvent() || event.IsGestureEvent()) + bounds.Inset(touch_extend_); + else + bounds.Inset(mouse_extend_); + + return bounds.Contains(point); + } + return WindowTargeter::EventLocationInsideBounds(window, event); +} + +bool EasyResizeWindowTargeter::ShouldUseExtendedBounds( + const aura::Window* window) const { + // Use the extended bounds only for immediate child windows of |container_|. + // Use the default targetter otherwise. + if (window->parent() != container_) + return false; + + aura::client::TransientWindowClient* transient_window_client = + aura::client::GetTransientWindowClient(); + return !transient_window_client || + !transient_window_client->GetTransientParent(window) || + transient_window_client->GetTransientParent(window) == container_; +} + +} // namespace wm diff --git a/chromium/ui/wm/core/easy_resize_window_targeter.h b/chromium/ui/wm/core/easy_resize_window_targeter.h new file mode 100644 index 00000000000..35b9eb2b865 --- /dev/null +++ b/chromium/ui/wm/core/easy_resize_window_targeter.h @@ -0,0 +1,53 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_WM_CORE_EASY_RESIZE_WINDOW_TARGETER_H_ +#define UI_WM_CORE_EASY_RESIZE_WINDOW_TARGETER_H_ + +#include "ui/aura/window_targeter.h" +#include "ui/gfx/geometry/insets.h" +#include "ui/wm/wm_export.h" + +namespace wm { + +// An EventTargeter for a container window that uses a slightly larger +// hit-target region for easier resize. +class WM_EXPORT EasyResizeWindowTargeter : public aura::WindowTargeter { + public: + // |container| window is the owner of this targeter. + EasyResizeWindowTargeter(aura::Window* container, + const gfx::Insets& mouse_extend, + const gfx::Insets& touch_extend); + + virtual ~EasyResizeWindowTargeter(); + + protected: + void set_mouse_extend(const gfx::Insets& mouse_extend) { + mouse_extend_ = mouse_extend; + } + + void set_touch_extend(const gfx::Insets& touch_extend) { + touch_extend_ = touch_extend; + } + + // ui::EventTargeter: + virtual bool EventLocationInsideBounds( + ui::EventTarget* target, + const ui::LocatedEvent& event) const OVERRIDE; + + private: + // Returns true if the hit testing (EventLocationInsideBounds()) should use + // the extended bounds. + bool ShouldUseExtendedBounds(const aura::Window* window) const; + + aura::Window* container_; + gfx::Insets mouse_extend_; + gfx::Insets touch_extend_; + + DISALLOW_COPY_AND_ASSIGN(EasyResizeWindowTargeter); +}; + +} // namespace wm + +#endif // UI_WM_CORE_EASY_RESIZE_WINDOW_TARGETER_H_ diff --git a/chromium/ui/wm/core/focus_controller.cc b/chromium/ui/wm/core/focus_controller.cc new file mode 100644 index 00000000000..1534e402e19 --- /dev/null +++ b/chromium/ui/wm/core/focus_controller.cc @@ -0,0 +1,368 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/focus_controller.h" + +#include "base/auto_reset.h" +#include "ui/aura/client/aura_constants.h" +#include "ui/aura/client/capture_client.h" +#include "ui/aura/client/focus_change_observer.h" +#include "ui/aura/env.h" +#include "ui/aura/window_tracker.h" +#include "ui/base/ime/text_input_focus_manager.h" +#include "ui/events/event.h" +#include "ui/wm/core/focus_rules.h" +#include "ui/wm/core/window_util.h" +#include "ui/wm/public/activation_change_observer.h" + +namespace wm { +namespace { + +// When a modal window is activated, we bring its entire transient parent chain +// to the front. This function must be called before the modal transient is +// stacked at the top to ensure correct stacking order. +void StackTransientParentsBelowModalWindow(aura::Window* window) { + if (window->GetProperty(aura::client::kModalKey) != ui::MODAL_TYPE_WINDOW) + return; + + aura::Window* transient_parent = wm::GetTransientParent(window); + while (transient_parent) { + transient_parent->parent()->StackChildAtTop(transient_parent); + transient_parent = wm::GetTransientParent(transient_parent); + } +} + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// +// FocusController, public: + +FocusController::FocusController(FocusRules* rules) + : active_window_(NULL), + focused_window_(NULL), + updating_focus_(false), + updating_activation_(false), + rules_(rules), + observer_manager_(this) { + DCHECK(rules); +} + +FocusController::~FocusController() { +} + +//////////////////////////////////////////////////////////////////////////////// +// FocusController, aura::client::ActivationClient implementation: + +void FocusController::AddObserver( + aura::client::ActivationChangeObserver* observer) { + activation_observers_.AddObserver(observer); +} + +void FocusController::RemoveObserver( + aura::client::ActivationChangeObserver* observer) { + activation_observers_.RemoveObserver(observer); +} + +void FocusController::ActivateWindow(aura::Window* window) { + FocusWindow(window); +} + +void FocusController::DeactivateWindow(aura::Window* window) { + if (window) + FocusWindow(rules_->GetNextActivatableWindow(window)); +} + +aura::Window* FocusController::GetActiveWindow() { + return active_window_; +} + +aura::Window* FocusController::GetActivatableWindow(aura::Window* window) { + return rules_->GetActivatableWindow(window); +} + +aura::Window* FocusController::GetToplevelWindow(aura::Window* window) { + return rules_->GetToplevelWindow(window); +} + +bool FocusController::OnWillFocusWindow(aura::Window* window, + const ui::Event* event) { + NOTREACHED(); + return false; +} + +bool FocusController::CanActivateWindow(aura::Window* window) const { + return rules_->CanActivateWindow(window); +} + +//////////////////////////////////////////////////////////////////////////////// +// FocusController, aura::client::FocusClient implementation: + +void FocusController::AddObserver( + aura::client::FocusChangeObserver* observer) { + focus_observers_.AddObserver(observer); +} + +void FocusController::RemoveObserver( + aura::client::FocusChangeObserver* observer) { + focus_observers_.RemoveObserver(observer); +} + +void FocusController::FocusWindow(aura::Window* window) { + if (window && + (window->Contains(focused_window_) || window->Contains(active_window_))) { + return; + } + + // We should not be messing with the focus if the window has capture, unless + // no has focus. + if (window && (aura::client::GetCaptureWindow(window) == window) && + focused_window_) { + return; + } + + // Focusing a window also activates its containing activatable window. Note + // that the rules could redirect activation activation and/or focus. + aura::Window* focusable = rules_->GetFocusableWindow(window); + aura::Window* activatable = + focusable ? rules_->GetActivatableWindow(focusable) : NULL; + + // We need valid focusable/activatable windows in the event we're not clearing + // focus. "Clearing focus" is inferred by whether or not |window| passed to + // this function is non-NULL. + if (window && (!focusable || !activatable)) + return; + DCHECK((focusable && activatable) || !window); + + // Activation change observers may change the focused window. If this happens + // we must not adjust the focus below since this will clobber that change. + aura::Window* last_focused_window = focused_window_; + if (!updating_activation_) + SetActiveWindow(window, activatable); + + // If the window's ActivationChangeObserver shifted focus to a valid window, + // we don't want to focus the window we thought would be focused by default. + bool activation_changed_focus = last_focused_window != focused_window_; + if (!updating_focus_ && (!activation_changed_focus || !focused_window_)) { + if (active_window_ && focusable) + DCHECK(active_window_->Contains(focusable)); + SetFocusedWindow(focusable); + } +} + +void FocusController::ResetFocusWithinActiveWindow(aura::Window* window) { + DCHECK(window); + if (!active_window_) + return; + if (!active_window_->Contains(window)) + return; + SetFocusedWindow(window); +} + +aura::Window* FocusController::GetFocusedWindow() { + return focused_window_; +} + +//////////////////////////////////////////////////////////////////////////////// +// FocusController, ui::EventHandler implementation: +void FocusController::OnKeyEvent(ui::KeyEvent* event) { +} + +void FocusController::OnMouseEvent(ui::MouseEvent* event) { + if (event->type() == ui::ET_MOUSE_PRESSED && !event->handled()) + WindowFocusedFromInputEvent(static_cast<aura::Window*>(event->target())); +} + +void FocusController::OnScrollEvent(ui::ScrollEvent* event) { +} + +void FocusController::OnTouchEvent(ui::TouchEvent* event) { +} + +void FocusController::OnGestureEvent(ui::GestureEvent* event) { + if (event->type() == ui::ET_GESTURE_BEGIN && + event->details().touch_points() == 1 && + !event->handled()) { + WindowFocusedFromInputEvent(static_cast<aura::Window*>(event->target())); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// FocusController, aura::WindowObserver implementation: + +void FocusController::OnWindowVisibilityChanged(aura::Window* window, + bool visible) { + if (!visible) + WindowLostFocusFromDispositionChange(window, window->parent()); +} + +void FocusController::OnWindowDestroying(aura::Window* window) { + WindowLostFocusFromDispositionChange(window, window->parent()); +} + +void FocusController::OnWindowHierarchyChanging( + const HierarchyChangeParams& params) { + if (params.receiver == active_window_ && + params.target->Contains(params.receiver) && (!params.new_parent || + aura::client::GetFocusClient(params.new_parent) != + aura::client::GetFocusClient(params.receiver))) { + WindowLostFocusFromDispositionChange(params.receiver, params.old_parent); + } +} + +void FocusController::OnWindowHierarchyChanged( + const HierarchyChangeParams& params) { + if (params.receiver == focused_window_ && + params.target->Contains(params.receiver) && (!params.new_parent || + aura::client::GetFocusClient(params.new_parent) != + aura::client::GetFocusClient(params.receiver))) { + WindowLostFocusFromDispositionChange(params.receiver, params.old_parent); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// FocusController, private: + +void FocusController::SetFocusedWindow(aura::Window* window) { + if (updating_focus_ || window == focused_window_) + return; + DCHECK(rules_->CanFocusWindow(window)); + if (window) + DCHECK_EQ(window, rules_->GetFocusableWindow(window)); + + base::AutoReset<bool> updating_focus(&updating_focus_, true); + aura::Window* lost_focus = focused_window_; + + // |window| is going to get the focus, so reset the text input client. + // OnWindowFocused() may set a proper text input client if the implementation + // supports text input. + ui::TextInputFocusManager* text_input_focus_manager = + ui::TextInputFocusManager::GetInstance(); + if (window) + text_input_focus_manager->FocusTextInputClient(NULL); + + // Allow for the window losing focus to be deleted during dispatch. If it is + // deleted pass NULL to observers instead of a deleted window. + aura::WindowTracker window_tracker; + if (lost_focus) + window_tracker.Add(lost_focus); + if (focused_window_ && observer_manager_.IsObserving(focused_window_) && + focused_window_ != active_window_) { + observer_manager_.Remove(focused_window_); + } + focused_window_ = window; + if (focused_window_ && !observer_manager_.IsObserving(focused_window_)) + observer_manager_.Add(focused_window_); + + FOR_EACH_OBSERVER(aura::client::FocusChangeObserver, + focus_observers_, + OnWindowFocused(focused_window_, + window_tracker.Contains(lost_focus) ? + lost_focus : NULL)); + if (window_tracker.Contains(lost_focus)) { + aura::client::FocusChangeObserver* observer = + aura::client::GetFocusChangeObserver(lost_focus); + if (observer) + observer->OnWindowFocused(focused_window_, lost_focus); + } + aura::client::FocusChangeObserver* observer = + aura::client::GetFocusChangeObserver(focused_window_); + if (observer) { + observer->OnWindowFocused( + focused_window_, + window_tracker.Contains(lost_focus) ? lost_focus : NULL); + } + + // Ensure that the text input client is reset when the window loses the focus. + if (!window) + text_input_focus_manager->FocusTextInputClient(NULL); +} + +void FocusController::SetActiveWindow(aura::Window* requested_window, + aura::Window* window) { + if (updating_activation_) + return; + + if (window == active_window_) { + if (requested_window) { + FOR_EACH_OBSERVER(aura::client::ActivationChangeObserver, + activation_observers_, + OnAttemptToReactivateWindow(requested_window, + active_window_)); + } + return; + } + + DCHECK(rules_->CanActivateWindow(window)); + if (window) + DCHECK_EQ(window, rules_->GetActivatableWindow(window)); + + base::AutoReset<bool> updating_activation(&updating_activation_, true); + aura::Window* lost_activation = active_window_; + // Allow for the window losing activation to be deleted during dispatch. If + // it is deleted pass NULL to observers instead of a deleted window. + aura::WindowTracker window_tracker; + if (lost_activation) + window_tracker.Add(lost_activation); + if (active_window_ && observer_manager_.IsObserving(active_window_) && + focused_window_ != active_window_) { + observer_manager_.Remove(active_window_); + } + active_window_ = window; + if (active_window_ && !observer_manager_.IsObserving(active_window_)) + observer_manager_.Add(active_window_); + if (active_window_) { + StackTransientParentsBelowModalWindow(active_window_); + active_window_->parent()->StackChildAtTop(active_window_); + } + + aura::client::ActivationChangeObserver* observer = NULL; + if (window_tracker.Contains(lost_activation)) { + observer = aura::client::GetActivationChangeObserver(lost_activation); + if (observer) + observer->OnWindowActivated(active_window_, lost_activation); + } + observer = aura::client::GetActivationChangeObserver(active_window_); + if (observer) { + observer->OnWindowActivated( + active_window_, + window_tracker.Contains(lost_activation) ? lost_activation : NULL); + } + FOR_EACH_OBSERVER(aura::client::ActivationChangeObserver, + activation_observers_, + OnWindowActivated(active_window_, + window_tracker.Contains(lost_activation) ? + lost_activation : NULL)); +} + +void FocusController::WindowLostFocusFromDispositionChange( + aura::Window* window, + aura::Window* next) { + // A window's modality state will interfere with focus restoration during its + // destruction. + window->ClearProperty(aura::client::kModalKey); + // TODO(beng): See if this function can be replaced by a call to + // FocusWindow(). + // Activation adjustments are handled first in the event of a disposition + // changed. If an activation change is necessary, focus is reset as part of + // that process so there's no point in updating focus independently. + if (window == active_window_) { + aura::Window* next_activatable = rules_->GetNextActivatableWindow(window); + SetActiveWindow(NULL, next_activatable); + if (!(active_window_ && active_window_->Contains(focused_window_))) + SetFocusedWindow(next_activatable); + } else if (window->Contains(focused_window_)) { + // Active window isn't changing, but focused window might be. + SetFocusedWindow(rules_->GetFocusableWindow(next)); + } +} + +void FocusController::WindowFocusedFromInputEvent(aura::Window* window) { + // Only focus |window| if it or any of its parents can be focused. Otherwise + // FocusWindow() will focus the topmost window, which may not be the + // currently focused one. + if (rules_->CanFocusWindow(GetToplevelWindow(window))) + FocusWindow(window); +} + +} // namespace wm diff --git a/chromium/ui/wm/core/focus_controller.h b/chromium/ui/wm/core/focus_controller.h new file mode 100644 index 00000000000..3366ea0c18f --- /dev/null +++ b/chromium/ui/wm/core/focus_controller.h @@ -0,0 +1,128 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_WM_CORE_FOCUS_CONTROLLER_H_ +#define UI_WM_CORE_FOCUS_CONTROLLER_H_ + +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/observer_list.h" +#include "base/scoped_observer.h" +#include "ui/aura/client/focus_client.h" +#include "ui/aura/window_observer.h" +#include "ui/events/event_handler.h" +#include "ui/wm/public/activation_client.h" +#include "ui/wm/wm_export.h" + +namespace wm { + +class FocusRules; + +// FocusController handles focus and activation changes for an environment +// encompassing one or more RootWindows. Within an environment there can be +// only one focused and one active window at a time. When focus or activation +// changes notifications are sent using the +// aura::client::Focus/ActivationChangeObserver interfaces. +// Changes to focus and activation can be from three sources: +// . the Aura Client API (implemented here in aura::client::ActivationClient). +// (The FocusController must be set as the ActivationClient implementation +// for all RootWindows). +// . input events (implemented here in ui::EventHandler). +// (The FocusController must be registered as a pre-target handler for +// the applicable environment owner, either a RootWindow or another type). +// . Window disposition changes (implemented here in aura::WindowObserver). +// (The FocusController registers itself as an observer of the active and +// focused windows). +class WM_EXPORT FocusController : public aura::client::ActivationClient, + public aura::client::FocusClient, + public ui::EventHandler, + public aura::WindowObserver { + public: + // |rules| cannot be NULL. + explicit FocusController(FocusRules* rules); + virtual ~FocusController(); + + private: + // Overridden from aura::client::ActivationClient: + virtual void AddObserver( + aura::client::ActivationChangeObserver* observer) OVERRIDE; + virtual void RemoveObserver( + aura::client::ActivationChangeObserver* observer) OVERRIDE; + virtual void ActivateWindow(aura::Window* window) OVERRIDE; + virtual void DeactivateWindow(aura::Window* window) OVERRIDE; + virtual aura::Window* GetActiveWindow() OVERRIDE; + virtual aura::Window* GetActivatableWindow(aura::Window* window) OVERRIDE; + virtual aura::Window* GetToplevelWindow(aura::Window* window) OVERRIDE; + virtual bool OnWillFocusWindow(aura::Window* window, + const ui::Event* event) OVERRIDE; + virtual bool CanActivateWindow(aura::Window* window) const OVERRIDE; + + // Overridden from aura::client::FocusClient: + virtual void AddObserver( + aura::client::FocusChangeObserver* observer) OVERRIDE; + virtual void RemoveObserver( + aura::client::FocusChangeObserver* observer) OVERRIDE; + virtual void FocusWindow(aura::Window* window) OVERRIDE; + virtual void ResetFocusWithinActiveWindow(aura::Window* window) OVERRIDE; + virtual aura::Window* GetFocusedWindow() OVERRIDE; + + // Overridden from ui::EventHandler: + virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE; + virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE; + virtual void OnScrollEvent(ui::ScrollEvent* event) OVERRIDE; + virtual void OnTouchEvent(ui::TouchEvent* event) OVERRIDE; + virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE; + + // Overridden from aura::WindowObserver: + virtual void OnWindowVisibilityChanged(aura::Window* window, + bool visible) OVERRIDE; + virtual void OnWindowDestroying(aura::Window* window) OVERRIDE; + virtual void OnWindowHierarchyChanging( + const HierarchyChangeParams& params) OVERRIDE; + virtual void OnWindowHierarchyChanged( + const HierarchyChangeParams& params) OVERRIDE; + + // Internal implementation that sets the focused window, fires events etc. + // This function must be called with a valid focusable window. + void SetFocusedWindow(aura::Window* window); + + // Internal implementation that sets the active window, fires events etc. + // This function must be called with a valid |activatable_window|. + // |requested window| refers to the window that was passed in to an external + // request (e.g. FocusWindow or ActivateWindow). It may be NULL, e.g. if + // SetActiveWindow was not called by an external request. |activatable_window| + // refers to the actual window to be activated, which may be different. + void SetActiveWindow(aura::Window* requested_window, + aura::Window* activatable_window); + + // Called when a window's disposition changed such that it and its hierarchy + // are no longer focusable/activatable. |next| is a valid window that is used + // as a starting point for finding a window to focus next based on rules. + void WindowLostFocusFromDispositionChange(aura::Window* window, + aura::Window* next); + + // Called when an attempt is made to focus or activate a window via an input + // event targeted at that window. Rules determine the best focusable window + // for the input window. + void WindowFocusedFromInputEvent(aura::Window* window); + + aura::Window* active_window_; + aura::Window* focused_window_; + + bool updating_focus_; + bool updating_activation_; + + scoped_ptr<FocusRules> rules_; + + ObserverList<aura::client::ActivationChangeObserver> activation_observers_; + ObserverList<aura::client::FocusChangeObserver> focus_observers_; + + ScopedObserver<aura::Window, aura::WindowObserver> observer_manager_; + + DISALLOW_COPY_AND_ASSIGN(FocusController); +}; + +} // namespace wm + +#endif // UI_WM_CORE_FOCUS_CONTROLLER_H_ diff --git a/chromium/ui/wm/core/focus_controller_unittest.cc b/chromium/ui/wm/core/focus_controller_unittest.cc new file mode 100644 index 00000000000..5d65943d1b4 --- /dev/null +++ b/chromium/ui/wm/core/focus_controller_unittest.cc @@ -0,0 +1,1285 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/focus_controller.h" + +#include <map> + +#include "ui/aura/client/aura_constants.h" +#include "ui/aura/client/default_capture_client.h" +#include "ui/aura/client/focus_change_observer.h" +#include "ui/aura/test/aura_test_base.h" +#include "ui/aura/test/event_generator.h" +#include "ui/aura/test/test_window_delegate.h" +#include "ui/aura/test/test_windows.h" +#include "ui/aura/window.h" +#include "ui/aura/window_event_dispatcher.h" +#include "ui/aura/window_tracker.h" +#include "ui/base/ime/dummy_text_input_client.h" +#include "ui/base/ime/text_input_focus_manager.h" +#include "ui/events/event.h" +#include "ui/events/event_constants.h" +#include "ui/events/event_handler.h" +#include "ui/wm/core/base_focus_rules.h" +#include "ui/wm/core/wm_state.h" +#include "ui/wm/public/activation_change_observer.h" +#include "ui/wm/public/activation_client.h" + +namespace wm { + +class FocusNotificationObserver : public aura::client::ActivationChangeObserver, + public aura::client::FocusChangeObserver { + public: + FocusNotificationObserver() + : activation_changed_count_(0), + focus_changed_count_(0), + reactivation_count_(0), + reactivation_requested_window_(NULL), + reactivation_actual_window_(NULL) {} + virtual ~FocusNotificationObserver() {} + + void ExpectCounts(int activation_changed_count, int focus_changed_count) { + EXPECT_EQ(activation_changed_count, activation_changed_count_); + EXPECT_EQ(focus_changed_count, focus_changed_count_); + } + int reactivation_count() const { + return reactivation_count_; + } + aura::Window* reactivation_requested_window() const { + return reactivation_requested_window_; + } + aura::Window* reactivation_actual_window() const { + return reactivation_actual_window_; + } + + private: + // Overridden from aura::client::ActivationChangeObserver: + virtual void OnWindowActivated(aura::Window* gained_active, + aura::Window* lost_active) OVERRIDE { + ++activation_changed_count_; + } + virtual void OnAttemptToReactivateWindow( + aura::Window* request_active, + aura::Window* actual_active) OVERRIDE { + ++reactivation_count_; + reactivation_requested_window_ = request_active; + reactivation_actual_window_ = actual_active; + } + + // Overridden from aura::client::FocusChangeObserver: + virtual void OnWindowFocused(aura::Window* gained_focus, + aura::Window* lost_focus) OVERRIDE { + ++focus_changed_count_; + } + + int activation_changed_count_; + int focus_changed_count_; + int reactivation_count_; + aura::Window* reactivation_requested_window_; + aura::Window* reactivation_actual_window_; + + DISALLOW_COPY_AND_ASSIGN(FocusNotificationObserver); +}; + +class WindowDeleter { + public: + virtual aura::Window* GetDeletedWindow() = 0; + + protected: + virtual ~WindowDeleter() {} +}; + +// ActivationChangeObserver and FocusChangeObserver that keeps track of whether +// it was notified about activation changes or focus changes with a deleted +// window. +class RecordingActivationAndFocusChangeObserver + : public aura::client::ActivationChangeObserver, + public aura::client::FocusChangeObserver { + public: + RecordingActivationAndFocusChangeObserver(aura::Window* root, + WindowDeleter* deleter) + : root_(root), + deleter_(deleter), + was_notified_with_deleted_window_(false) { + aura::client::GetActivationClient(root_)->AddObserver(this); + aura::client::GetFocusClient(root_)->AddObserver(this); + } + virtual ~RecordingActivationAndFocusChangeObserver() { + aura::client::GetActivationClient(root_)->RemoveObserver(this); + aura::client::GetFocusClient(root_)->RemoveObserver(this); + } + + bool was_notified_with_deleted_window() const { + return was_notified_with_deleted_window_; + } + + // Overridden from aura::client::ActivationChangeObserver: + virtual void OnWindowActivated(aura::Window* gained_active, + aura::Window* lost_active) OVERRIDE { + if (lost_active && lost_active == deleter_->GetDeletedWindow()) + was_notified_with_deleted_window_ = true; + } + + // Overridden from aura::client::FocusChangeObserver: + virtual void OnWindowFocused(aura::Window* gained_focus, + aura::Window* lost_focus) OVERRIDE { + if (lost_focus && lost_focus == deleter_->GetDeletedWindow()) + was_notified_with_deleted_window_ = true; + } + + private: + aura::Window* root_; + + // Not owned. + WindowDeleter* deleter_; + + // Whether the observer was notified about the loss of activation or the + // loss of focus with a window already deleted by |deleter_| as the + // |lost_active| or |lost_focus| parameter. + bool was_notified_with_deleted_window_; + + DISALLOW_COPY_AND_ASSIGN(RecordingActivationAndFocusChangeObserver); +}; + +// ActivationChangeObserver that deletes the window losing activation. +class DeleteOnLoseActivationChangeObserver : + public aura::client::ActivationChangeObserver, + public WindowDeleter { + public: + explicit DeleteOnLoseActivationChangeObserver(aura::Window* window) + : root_(window->GetRootWindow()), + window_(window), + did_delete_(false) { + aura::client::GetActivationClient(root_)->AddObserver(this); + } + virtual ~DeleteOnLoseActivationChangeObserver() { + aura::client::GetActivationClient(root_)->RemoveObserver(this); + } + + // Overridden from aura::client::ActivationChangeObserver: + virtual void OnWindowActivated(aura::Window* gained_active, + aura::Window* lost_active) OVERRIDE { + if (window_ && lost_active == window_) { + delete lost_active; + did_delete_ = true; + } + } + + // Overridden from WindowDeleter: + virtual aura::Window* GetDeletedWindow() OVERRIDE { + return did_delete_ ? window_ : NULL; + } + + private: + aura::Window* root_; + aura::Window* window_; + bool did_delete_; + + DISALLOW_COPY_AND_ASSIGN(DeleteOnLoseActivationChangeObserver); +}; + +// FocusChangeObserver that deletes the window losing focus. +class DeleteOnLoseFocusChangeObserver + : public aura::client::FocusChangeObserver, + public WindowDeleter { + public: + explicit DeleteOnLoseFocusChangeObserver(aura::Window* window) + : root_(window->GetRootWindow()), + window_(window), + did_delete_(false) { + aura::client::GetFocusClient(root_)->AddObserver(this); + } + virtual ~DeleteOnLoseFocusChangeObserver() { + aura::client::GetFocusClient(root_)->RemoveObserver(this); + } + + // Overridden from aura::client::FocusChangeObserver: + virtual void OnWindowFocused(aura::Window* gained_focus, + aura::Window* lost_focus) OVERRIDE { + if (window_ && lost_focus == window_) { + delete lost_focus; + did_delete_ = true; + } + } + + // Overridden from WindowDeleter: + virtual aura::Window* GetDeletedWindow() OVERRIDE { + return did_delete_ ? window_ : NULL; + } + + private: + aura::Window* root_; + aura::Window* window_; + bool did_delete_; + + DISALLOW_COPY_AND_ASSIGN(DeleteOnLoseFocusChangeObserver); +}; + +class ScopedFocusNotificationObserver : public FocusNotificationObserver { + public: + ScopedFocusNotificationObserver(aura::Window* root_window) + : root_window_(root_window) { + aura::client::GetActivationClient(root_window_)->AddObserver(this); + aura::client::GetFocusClient(root_window_)->AddObserver(this); + } + virtual ~ScopedFocusNotificationObserver() { + aura::client::GetActivationClient(root_window_)->RemoveObserver(this); + aura::client::GetFocusClient(root_window_)->RemoveObserver(this); + } + + private: + aura::Window* root_window_; + + DISALLOW_COPY_AND_ASSIGN(ScopedFocusNotificationObserver); +}; + +class ScopedTargetFocusNotificationObserver : public FocusNotificationObserver { + public: + ScopedTargetFocusNotificationObserver(aura::Window* root_window, int id) + : target_(root_window->GetChildById(id)) { + aura::client::SetActivationChangeObserver(target_, this); + aura::client::SetFocusChangeObserver(target_, this); + tracker_.Add(target_); + } + virtual ~ScopedTargetFocusNotificationObserver() { + if (tracker_.Contains(target_)) { + aura::client::SetActivationChangeObserver(target_, NULL); + aura::client::SetFocusChangeObserver(target_, NULL); + } + } + + private: + aura::Window* target_; + aura::WindowTracker tracker_; + + DISALLOW_COPY_AND_ASSIGN(ScopedTargetFocusNotificationObserver); +}; + +class ScopedFocusedTextInputClientChanger + : public ScopedFocusNotificationObserver { + public: + ScopedFocusedTextInputClientChanger(aura::Window* root_window, + ui::TextInputClient* text_input_client) + : ScopedFocusNotificationObserver(root_window), + text_input_client_(text_input_client) {} + + private: + // Overridden from aura::client::FocusChangeObserver: + virtual void OnWindowFocused(aura::Window* gained_focus, + aura::Window* lost_focus) OVERRIDE { + ui::TextInputFocusManager::GetInstance()->FocusTextInputClient( + text_input_client_); + } + + ui::TextInputClient* text_input_client_; +}; + +// Used to fake the handling of events in the pre-target phase. +class SimpleEventHandler : public ui::EventHandler { + public: + SimpleEventHandler() {} + virtual ~SimpleEventHandler() {} + + // Overridden from ui::EventHandler: + virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE { + event->SetHandled(); + } + virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE { + event->SetHandled(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(SimpleEventHandler); +}; + +class FocusShiftingActivationObserver + : public aura::client::ActivationChangeObserver { + public: + explicit FocusShiftingActivationObserver(aura::Window* activated_window) + : activated_window_(activated_window), + shift_focus_to_(NULL) {} + virtual ~FocusShiftingActivationObserver() {} + + void set_shift_focus_to(aura::Window* shift_focus_to) { + shift_focus_to_ = shift_focus_to; + } + + private: + // Overridden from aura::client::ActivationChangeObserver: + virtual void OnWindowActivated(aura::Window* gained_active, + aura::Window* lost_active) OVERRIDE { + // Shift focus to a child. This should prevent the default focusing from + // occurring in FocusController::FocusWindow(). + if (gained_active == activated_window_) { + aura::client::FocusClient* client = + aura::client::GetFocusClient(gained_active); + client->FocusWindow(shift_focus_to_); + } + } + + aura::Window* activated_window_; + aura::Window* shift_focus_to_; + + DISALLOW_COPY_AND_ASSIGN(FocusShiftingActivationObserver); +}; + +// BaseFocusRules subclass that allows basic overrides of focus/activation to +// be tested. This is intended more as a test that the override system works at +// all, rather than as an exhaustive set of use cases, those should be covered +// in tests for those FocusRules implementations. +class TestFocusRules : public BaseFocusRules { + public: + TestFocusRules() : focus_restriction_(NULL) {} + + // Restricts focus and activation to this window and its child hierarchy. + void set_focus_restriction(aura::Window* focus_restriction) { + focus_restriction_ = focus_restriction; + } + + // Overridden from BaseFocusRules: + virtual bool SupportsChildActivation(aura::Window* window) const OVERRIDE { + // In FocusControllerTests, only the RootWindow has activatable children. + return window->GetRootWindow() == window; + } + virtual bool CanActivateWindow(aura::Window* window) const OVERRIDE { + // Restricting focus to a non-activatable child window means the activatable + // parent outside the focus restriction is activatable. + bool can_activate = + CanFocusOrActivate(window) || window->Contains(focus_restriction_); + return can_activate ? BaseFocusRules::CanActivateWindow(window) : false; + } + virtual bool CanFocusWindow(aura::Window* window) const OVERRIDE { + return CanFocusOrActivate(window) ? + BaseFocusRules::CanFocusWindow(window) : false; + } + virtual aura::Window* GetActivatableWindow( + aura::Window* window) const OVERRIDE { + return BaseFocusRules::GetActivatableWindow( + CanFocusOrActivate(window) ? window : focus_restriction_); + } + virtual aura::Window* GetFocusableWindow( + aura::Window* window) const OVERRIDE { + return BaseFocusRules::GetFocusableWindow( + CanFocusOrActivate(window) ? window : focus_restriction_); + } + virtual aura::Window* GetNextActivatableWindow( + aura::Window* ignore) const OVERRIDE { + aura::Window* next_activatable = + BaseFocusRules::GetNextActivatableWindow(ignore); + return CanFocusOrActivate(next_activatable) ? + next_activatable : GetActivatableWindow(focus_restriction_); + } + + private: + bool CanFocusOrActivate(aura::Window* window) const { + return !focus_restriction_ || focus_restriction_->Contains(window); + } + + aura::Window* focus_restriction_; + + DISALLOW_COPY_AND_ASSIGN(TestFocusRules); +}; + +// Common infrastructure shared by all FocusController test types. +class FocusControllerTestBase : public aura::test::AuraTestBase { + protected: + FocusControllerTestBase() {} + + // Overridden from aura::test::AuraTestBase: + virtual void SetUp() OVERRIDE { + wm_state_.reset(new wm::WMState); + // FocusController registers itself as an Env observer so it can catch all + // window initializations, including the root_window()'s, so we create it + // before allowing the base setup. + test_focus_rules_ = new TestFocusRules; + focus_controller_.reset(new FocusController(test_focus_rules_)); + aura::test::AuraTestBase::SetUp(); + root_window()->AddPreTargetHandler(focus_controller_.get()); + aura::client::SetFocusClient(root_window(), focus_controller_.get()); + aura::client::SetActivationClient(root_window(), focus_controller_.get()); + + // Hierarchy used by all tests: + // root_window + // +-- w1 + // | +-- w11 + // | +-- w12 + // +-- w2 + // | +-- w21 + // | +-- w211 + // +-- w3 + aura::Window* w1 = aura::test::CreateTestWindowWithDelegate( + aura::test::TestWindowDelegate::CreateSelfDestroyingDelegate(), 1, + gfx::Rect(0, 0, 50, 50), root_window()); + aura::test::CreateTestWindowWithDelegate( + aura::test::TestWindowDelegate::CreateSelfDestroyingDelegate(), 11, + gfx::Rect(5, 5, 10, 10), w1); + aura::test::CreateTestWindowWithDelegate( + aura::test::TestWindowDelegate::CreateSelfDestroyingDelegate(), 12, + gfx::Rect(15, 15, 10, 10), w1); + aura::Window* w2 = aura::test::CreateTestWindowWithDelegate( + aura::test::TestWindowDelegate::CreateSelfDestroyingDelegate(), 2, + gfx::Rect(75, 75, 50, 50), root_window()); + aura::Window* w21 = aura::test::CreateTestWindowWithDelegate( + aura::test::TestWindowDelegate::CreateSelfDestroyingDelegate(), 21, + gfx::Rect(5, 5, 10, 10), w2); + aura::test::CreateTestWindowWithDelegate( + aura::test::TestWindowDelegate::CreateSelfDestroyingDelegate(), 211, + gfx::Rect(1, 1, 5, 5), w21); + aura::test::CreateTestWindowWithDelegate( + aura::test::TestWindowDelegate::CreateSelfDestroyingDelegate(), 3, + gfx::Rect(125, 125, 50, 50), root_window()); + } + virtual void TearDown() OVERRIDE { + root_window()->RemovePreTargetHandler(focus_controller_.get()); + aura::test::AuraTestBase::TearDown(); + test_focus_rules_ = NULL; // Owned by FocusController. + focus_controller_.reset(); + wm_state_.reset(); + } + + void FocusWindow(aura::Window* window) { + aura::client::GetFocusClient(root_window())->FocusWindow(window); + } + aura::Window* GetFocusedWindow() { + return aura::client::GetFocusClient(root_window())->GetFocusedWindow(); + } + int GetFocusedWindowId() { + aura::Window* focused_window = GetFocusedWindow(); + return focused_window ? focused_window->id() : -1; + } + void ActivateWindow(aura::Window* window) { + aura::client::GetActivationClient(root_window())->ActivateWindow(window); + } + void DeactivateWindow(aura::Window* window) { + aura::client::GetActivationClient(root_window())->DeactivateWindow(window); + } + aura::Window* GetActiveWindow() { + return aura::client::GetActivationClient(root_window())->GetActiveWindow(); + } + int GetActiveWindowId() { + aura::Window* active_window = GetActiveWindow(); + return active_window ? active_window->id() : -1; + } + + TestFocusRules* test_focus_rules() { return test_focus_rules_; } + + // Test functions. + virtual void BasicFocus() = 0; + virtual void BasicActivation() = 0; + virtual void FocusEvents() = 0; + virtual void DuplicateFocusEvents() {} + virtual void ActivationEvents() = 0; + virtual void ReactivationEvents() {} + virtual void DuplicateActivationEvents() {} + virtual void ShiftFocusWithinActiveWindow() {} + virtual void ShiftFocusToChildOfInactiveWindow() {} + virtual void ShiftFocusToParentOfFocusedWindow() {} + virtual void FocusRulesOverride() = 0; + virtual void ActivationRulesOverride() = 0; + virtual void ShiftFocusOnActivation() {} + virtual void ShiftFocusOnActivationDueToHide() {} + virtual void NoShiftActiveOnActivation() {} + virtual void NoFocusChangeOnClickOnCaptureWindow() {} + virtual void ChangeFocusWhenNothingFocusedAndCaptured() {} + virtual void DontPassDeletedWindow() {} + virtual void FocusedTextInputClient() {} + + private: + scoped_ptr<FocusController> focus_controller_; + TestFocusRules* test_focus_rules_; + scoped_ptr<wm::WMState> wm_state_; + + DISALLOW_COPY_AND_ASSIGN(FocusControllerTestBase); +}; + +// Test base for tests where focus is directly set to a target window. +class FocusControllerDirectTestBase : public FocusControllerTestBase { + protected: + FocusControllerDirectTestBase() {} + + // Different test types shift focus in different ways. + virtual void FocusWindowDirect(aura::Window* window) = 0; + virtual void ActivateWindowDirect(aura::Window* window) = 0; + virtual void DeactivateWindowDirect(aura::Window* window) = 0; + + // Input events do not change focus if the window can not be focused. + virtual bool IsInputEvent() = 0; + + void FocusWindowById(int id) { + aura::Window* window = root_window()->GetChildById(id); + DCHECK(window); + FocusWindowDirect(window); + } + void ActivateWindowById(int id) { + aura::Window* window = root_window()->GetChildById(id); + DCHECK(window); + ActivateWindowDirect(window); + } + + // Overridden from FocusControllerTestBase: + virtual void BasicFocus() OVERRIDE { + EXPECT_EQ(NULL, GetFocusedWindow()); + FocusWindowById(1); + EXPECT_EQ(1, GetFocusedWindowId()); + FocusWindowById(2); + EXPECT_EQ(2, GetFocusedWindowId()); + } + virtual void BasicActivation() OVERRIDE { + EXPECT_EQ(NULL, GetActiveWindow()); + ActivateWindowById(1); + EXPECT_EQ(1, GetActiveWindowId()); + ActivateWindowById(2); + EXPECT_EQ(2, GetActiveWindowId()); + // Verify that attempting to deactivate NULL does not crash and does not + // change activation. + DeactivateWindow(NULL); + EXPECT_EQ(2, GetActiveWindowId()); + DeactivateWindow(GetActiveWindow()); + EXPECT_EQ(1, GetActiveWindowId()); + } + virtual void FocusEvents() OVERRIDE { + ScopedFocusNotificationObserver root_observer(root_window()); + ScopedTargetFocusNotificationObserver observer1(root_window(), 1); + ScopedTargetFocusNotificationObserver observer2(root_window(), 2); + + root_observer.ExpectCounts(0, 0); + observer1.ExpectCounts(0, 0); + observer2.ExpectCounts(0, 0); + + FocusWindowById(1); + root_observer.ExpectCounts(1, 1); + observer1.ExpectCounts(1, 1); + observer2.ExpectCounts(0, 0); + + FocusWindowById(2); + root_observer.ExpectCounts(2, 2); + observer1.ExpectCounts(2, 2); + observer2.ExpectCounts(1, 1); + } + virtual void DuplicateFocusEvents() OVERRIDE { + // Focusing an existing focused window should not resend focus events. + ScopedFocusNotificationObserver root_observer(root_window()); + ScopedTargetFocusNotificationObserver observer1(root_window(), 1); + + root_observer.ExpectCounts(0, 0); + observer1.ExpectCounts(0, 0); + + FocusWindowById(1); + root_observer.ExpectCounts(1, 1); + observer1.ExpectCounts(1, 1); + + FocusWindowById(1); + root_observer.ExpectCounts(1, 1); + observer1.ExpectCounts(1, 1); + } + virtual void ActivationEvents() OVERRIDE { + ActivateWindowById(1); + + ScopedFocusNotificationObserver root_observer(root_window()); + ScopedTargetFocusNotificationObserver observer1(root_window(), 1); + ScopedTargetFocusNotificationObserver observer2(root_window(), 2); + + root_observer.ExpectCounts(0, 0); + observer1.ExpectCounts(0, 0); + observer2.ExpectCounts(0, 0); + + ActivateWindowById(2); + root_observer.ExpectCounts(1, 1); + observer1.ExpectCounts(1, 1); + observer2.ExpectCounts(1, 1); + } + virtual void ReactivationEvents() OVERRIDE { + ActivateWindowById(1); + ScopedFocusNotificationObserver root_observer(root_window()); + EXPECT_EQ(0, root_observer.reactivation_count()); + root_window()->GetChildById(2)->Hide(); + // When we attempt to activate "2", which cannot be activated because it + // is not visible, "1" will be reactivated. + ActivateWindowById(2); + EXPECT_EQ(1, root_observer.reactivation_count()); + EXPECT_EQ(root_window()->GetChildById(2), + root_observer.reactivation_requested_window()); + EXPECT_EQ(root_window()->GetChildById(1), + root_observer.reactivation_actual_window()); + } + virtual void DuplicateActivationEvents() OVERRIDE { + // Activating an existing active window should not resend activation events. + ActivateWindowById(1); + + ScopedFocusNotificationObserver root_observer(root_window()); + ScopedTargetFocusNotificationObserver observer1(root_window(), 1); + ScopedTargetFocusNotificationObserver observer2(root_window(), 2); + + root_observer.ExpectCounts(0, 0); + observer1.ExpectCounts(0, 0); + observer2.ExpectCounts(0, 0); + + ActivateWindowById(2); + root_observer.ExpectCounts(1, 1); + observer1.ExpectCounts(1, 1); + observer2.ExpectCounts(1, 1); + + ActivateWindowById(2); + root_observer.ExpectCounts(1, 1); + observer1.ExpectCounts(1, 1); + observer2.ExpectCounts(1, 1); + } + virtual void ShiftFocusWithinActiveWindow() OVERRIDE { + ActivateWindowById(1); + EXPECT_EQ(1, GetActiveWindowId()); + EXPECT_EQ(1, GetFocusedWindowId()); + FocusWindowById(11); + EXPECT_EQ(11, GetFocusedWindowId()); + FocusWindowById(12); + EXPECT_EQ(12, GetFocusedWindowId()); + } + virtual void ShiftFocusToChildOfInactiveWindow() OVERRIDE { + ActivateWindowById(2); + EXPECT_EQ(2, GetActiveWindowId()); + EXPECT_EQ(2, GetFocusedWindowId()); + FocusWindowById(11); + EXPECT_EQ(1, GetActiveWindowId()); + EXPECT_EQ(11, GetFocusedWindowId()); + } + virtual void ShiftFocusToParentOfFocusedWindow() OVERRIDE { + ActivateWindowById(1); + EXPECT_EQ(1, GetFocusedWindowId()); + FocusWindowById(11); + EXPECT_EQ(11, GetFocusedWindowId()); + FocusWindowById(1); + // Focus should _not_ shift to the parent of the already-focused window. + EXPECT_EQ(11, GetFocusedWindowId()); + } + virtual void FocusRulesOverride() OVERRIDE { + EXPECT_EQ(NULL, GetFocusedWindow()); + FocusWindowById(11); + EXPECT_EQ(11, GetFocusedWindowId()); + + test_focus_rules()->set_focus_restriction(root_window()->GetChildById(211)); + FocusWindowById(12); + // Input events leave focus unchanged; direct API calls will change focus + // to the restricted window. + int focused_window = IsInputEvent() ? 11 : 211; + EXPECT_EQ(focused_window, GetFocusedWindowId()); + + test_focus_rules()->set_focus_restriction(NULL); + FocusWindowById(12); + EXPECT_EQ(12, GetFocusedWindowId()); + } + virtual void ActivationRulesOverride() OVERRIDE { + ActivateWindowById(1); + EXPECT_EQ(1, GetActiveWindowId()); + EXPECT_EQ(1, GetFocusedWindowId()); + + aura::Window* w3 = root_window()->GetChildById(3); + test_focus_rules()->set_focus_restriction(w3); + + ActivateWindowById(2); + // Input events leave activation unchanged; direct API calls will activate + // the restricted window. + int active_window = IsInputEvent() ? 1 : 3; + EXPECT_EQ(active_window, GetActiveWindowId()); + EXPECT_EQ(active_window, GetFocusedWindowId()); + + test_focus_rules()->set_focus_restriction(NULL); + ActivateWindowById(2); + EXPECT_EQ(2, GetActiveWindowId()); + EXPECT_EQ(2, GetFocusedWindowId()); + } + virtual void ShiftFocusOnActivation() OVERRIDE { + // When a window is activated, by default that window is also focused. + // An ActivationChangeObserver may shift focus to another window within the + // same activatable window. + ActivateWindowById(2); + EXPECT_EQ(2, GetFocusedWindowId()); + ActivateWindowById(1); + EXPECT_EQ(1, GetFocusedWindowId()); + + ActivateWindowById(2); + + aura::Window* target = root_window()->GetChildById(1); + aura::client::ActivationClient* client = + aura::client::GetActivationClient(root_window()); + + scoped_ptr<FocusShiftingActivationObserver> observer( + new FocusShiftingActivationObserver(target)); + observer->set_shift_focus_to(target->GetChildById(11)); + client->AddObserver(observer.get()); + + ActivateWindowById(1); + + // w1's ActivationChangeObserver shifted focus to this child, pre-empting + // FocusController's default setting. + EXPECT_EQ(11, GetFocusedWindowId()); + + ActivateWindowById(2); + EXPECT_EQ(2, GetFocusedWindowId()); + + // Simulate a focus reset by the ActivationChangeObserver. This should + // trigger the default setting in FocusController. + observer->set_shift_focus_to(NULL); + ActivateWindowById(1); + EXPECT_EQ(1, GetFocusedWindowId()); + + client->RemoveObserver(observer.get()); + + ActivateWindowById(2); + EXPECT_EQ(2, GetFocusedWindowId()); + ActivateWindowById(1); + EXPECT_EQ(1, GetFocusedWindowId()); + } + virtual void ShiftFocusOnActivationDueToHide() OVERRIDE { + // Similar to ShiftFocusOnActivation except the activation change is + // triggered by hiding the active window. + ActivateWindowById(1); + EXPECT_EQ(1, GetFocusedWindowId()); + + // Removes window 3 as candidate for next activatable window. + root_window()->GetChildById(3)->Hide(); + EXPECT_EQ(1, GetFocusedWindowId()); + + aura::Window* target = root_window()->GetChildById(2); + aura::client::ActivationClient* client = + aura::client::GetActivationClient(root_window()); + + scoped_ptr<FocusShiftingActivationObserver> observer( + new FocusShiftingActivationObserver(target)); + observer->set_shift_focus_to(target->GetChildById(21)); + client->AddObserver(observer.get()); + + // Hide the active window. + root_window()->GetChildById(1)->Hide(); + + EXPECT_EQ(21, GetFocusedWindowId()); + + client->RemoveObserver(observer.get()); + } + virtual void NoShiftActiveOnActivation() OVERRIDE { + // When a window is activated, we need to prevent any change to activation + // from being made in response to an activation change notification. + } + + virtual void NoFocusChangeOnClickOnCaptureWindow() OVERRIDE { + scoped_ptr<aura::client::DefaultCaptureClient> capture_client( + new aura::client::DefaultCaptureClient(root_window())); + // Clicking on a window which has capture should not cause a focus change + // to the window. This test verifies whether that is indeed the case. + ActivateWindowById(1); + + EXPECT_EQ(1, GetActiveWindowId()); + EXPECT_EQ(1, GetFocusedWindowId()); + + aura::Window* w2 = root_window()->GetChildById(2); + aura::client::GetCaptureClient(root_window())->SetCapture(w2); + aura::test::EventGenerator generator(root_window(), w2); + generator.ClickLeftButton(); + + EXPECT_EQ(1, GetActiveWindowId()); + EXPECT_EQ(1, GetFocusedWindowId()); + aura::client::GetCaptureClient(root_window())->ReleaseCapture(w2); + } + + // Verifies focus change is honored while capture held. + virtual void ChangeFocusWhenNothingFocusedAndCaptured() OVERRIDE { + scoped_ptr<aura::client::DefaultCaptureClient> capture_client( + new aura::client::DefaultCaptureClient(root_window())); + aura::Window* w1 = root_window()->GetChildById(1); + aura::client::GetCaptureClient(root_window())->SetCapture(w1); + + EXPECT_EQ(-1, GetActiveWindowId()); + EXPECT_EQ(-1, GetFocusedWindowId()); + + FocusWindowById(1); + + EXPECT_EQ(1, GetActiveWindowId()); + EXPECT_EQ(1, GetFocusedWindowId()); + + aura::client::GetCaptureClient(root_window())->ReleaseCapture(w1); + } + + // Verifies if a window that loses activation or focus is deleted during + // observer notification we don't pass the deleted window to other observers. + virtual void DontPassDeletedWindow() OVERRIDE { + FocusWindowById(1); + + EXPECT_EQ(1, GetActiveWindowId()); + EXPECT_EQ(1, GetFocusedWindowId()); + + { + aura::Window* to_delete = root_window()->GetChildById(1); + DeleteOnLoseActivationChangeObserver observer1(to_delete); + RecordingActivationAndFocusChangeObserver observer2(root_window(), + &observer1); + + FocusWindowById(2); + + EXPECT_EQ(2, GetActiveWindowId()); + EXPECT_EQ(2, GetFocusedWindowId()); + + EXPECT_EQ(to_delete, observer1.GetDeletedWindow()); + EXPECT_FALSE(observer2.was_notified_with_deleted_window()); + } + + { + aura::Window* to_delete = root_window()->GetChildById(2); + DeleteOnLoseFocusChangeObserver observer1(to_delete); + RecordingActivationAndFocusChangeObserver observer2(root_window(), + &observer1); + + FocusWindowById(3); + + EXPECT_EQ(3, GetActiveWindowId()); + EXPECT_EQ(3, GetFocusedWindowId()); + + EXPECT_EQ(to_delete, observer1.GetDeletedWindow()); + EXPECT_FALSE(observer2.was_notified_with_deleted_window()); + } + } + + // Verifies if the focused text input client is cleared when a window gains + // or loses the focus. + virtual void FocusedTextInputClient() OVERRIDE { + ui::TextInputFocusManager* text_input_focus_manager = + ui::TextInputFocusManager::GetInstance(); + ui::DummyTextInputClient text_input_client; + ui::TextInputClient* null_text_input_client = NULL; + + EXPECT_EQ(null_text_input_client, + text_input_focus_manager->GetFocusedTextInputClient()); + + text_input_focus_manager->FocusTextInputClient(&text_input_client); + EXPECT_EQ(&text_input_client, + text_input_focus_manager->GetFocusedTextInputClient()); + FocusWindowById(1); + // The focused text input client gets cleared when a window gets focused + // unless any of observers sets the focused text input client. + EXPECT_EQ(null_text_input_client, + text_input_focus_manager->GetFocusedTextInputClient()); + + ScopedFocusedTextInputClientChanger text_input_focus_changer( + root_window(), &text_input_client); + EXPECT_EQ(null_text_input_client, + text_input_focus_manager->GetFocusedTextInputClient()); + FocusWindowById(2); + // |text_input_focus_changer| sets the focused text input client. + EXPECT_EQ(&text_input_client, + text_input_focus_manager->GetFocusedTextInputClient()); + + FocusWindow(NULL); + // The focused text input client gets cleared when a window loses the focus. + EXPECT_EQ(null_text_input_client, + text_input_focus_manager->GetFocusedTextInputClient()); + } + + private: + DISALLOW_COPY_AND_ASSIGN(FocusControllerDirectTestBase); +}; + +// Focus and Activation changes via aura::client::ActivationClient API. +class FocusControllerApiTest : public FocusControllerDirectTestBase { + public: + FocusControllerApiTest() {} + + private: + // Overridden from FocusControllerTestBase: + virtual void FocusWindowDirect(aura::Window* window) OVERRIDE { + FocusWindow(window); + } + virtual void ActivateWindowDirect(aura::Window* window) OVERRIDE { + ActivateWindow(window); + } + virtual void DeactivateWindowDirect(aura::Window* window) OVERRIDE { + DeactivateWindow(window); + } + virtual bool IsInputEvent() OVERRIDE { return false; } + + DISALLOW_COPY_AND_ASSIGN(FocusControllerApiTest); +}; + +// Focus and Activation changes via input events. +class FocusControllerMouseEventTest : public FocusControllerDirectTestBase { + public: + FocusControllerMouseEventTest() {} + + // Tests that a handled mouse or gesture event does not trigger a window + // activation. + void IgnoreHandledEvent() { + EXPECT_EQ(NULL, GetActiveWindow()); + aura::Window* w1 = root_window()->GetChildById(1); + SimpleEventHandler handler; + root_window()->PrependPreTargetHandler(&handler); + aura::test::EventGenerator generator(root_window(), w1); + generator.ClickLeftButton(); + EXPECT_EQ(NULL, GetActiveWindow()); + generator.GestureTapAt(w1->bounds().CenterPoint()); + EXPECT_EQ(NULL, GetActiveWindow()); + root_window()->RemovePreTargetHandler(&handler); + generator.ClickLeftButton(); + EXPECT_EQ(1, GetActiveWindowId()); + } + + private: + // Overridden from FocusControllerTestBase: + virtual void FocusWindowDirect(aura::Window* window) OVERRIDE { + aura::test::EventGenerator generator(root_window(), window); + generator.ClickLeftButton(); + } + virtual void ActivateWindowDirect(aura::Window* window) OVERRIDE { + aura::test::EventGenerator generator(root_window(), window); + generator.ClickLeftButton(); + } + virtual void DeactivateWindowDirect(aura::Window* window) OVERRIDE { + aura::Window* next_activatable = + test_focus_rules()->GetNextActivatableWindow(window); + aura::test::EventGenerator generator(root_window(), next_activatable); + generator.ClickLeftButton(); + } + virtual bool IsInputEvent() OVERRIDE { return true; } + + DISALLOW_COPY_AND_ASSIGN(FocusControllerMouseEventTest); +}; + +class FocusControllerGestureEventTest : public FocusControllerDirectTestBase { + public: + FocusControllerGestureEventTest() {} + + private: + // Overridden from FocusControllerTestBase: + virtual void FocusWindowDirect(aura::Window* window) OVERRIDE { + aura::test::EventGenerator generator(root_window(), window); + generator.GestureTapAt(window->bounds().CenterPoint()); + } + virtual void ActivateWindowDirect(aura::Window* window) OVERRIDE { + aura::test::EventGenerator generator(root_window(), window); + generator.GestureTapAt(window->bounds().CenterPoint()); + } + virtual void DeactivateWindowDirect(aura::Window* window) OVERRIDE { + aura::Window* next_activatable = + test_focus_rules()->GetNextActivatableWindow(window); + aura::test::EventGenerator generator(root_window(), next_activatable); + generator.GestureTapAt(window->bounds().CenterPoint()); + } + virtual bool IsInputEvent() OVERRIDE { return true; } + + DISALLOW_COPY_AND_ASSIGN(FocusControllerGestureEventTest); +}; + +// Test base for tests where focus is implicitly set to a window as the result +// of a disposition change to the focused window or the hierarchy that contains +// it. +class FocusControllerImplicitTestBase : public FocusControllerTestBase { + protected: + explicit FocusControllerImplicitTestBase(bool parent) : parent_(parent) {} + + aura::Window* GetDispositionWindow(aura::Window* window) { + return parent_ ? window->parent() : window; + } + + // Change the disposition of |window| in such a way as it will lose focus. + virtual void ChangeWindowDisposition(aura::Window* window) = 0; + + // Allow each disposition change test to add additional post-disposition + // change expectations. + virtual void PostDispostionChangeExpectations() {} + + // Overridden from FocusControllerTestBase: + virtual void BasicFocus() OVERRIDE { + EXPECT_EQ(NULL, GetFocusedWindow()); + + aura::Window* w211 = root_window()->GetChildById(211); + FocusWindow(w211); + EXPECT_EQ(211, GetFocusedWindowId()); + + ChangeWindowDisposition(w211); + // BasicFocusRules passes focus to the parent. + EXPECT_EQ(parent_ ? 2 : 21, GetFocusedWindowId()); + } + virtual void BasicActivation() OVERRIDE { + DCHECK(!parent_) << "Activation tests don't support parent changes."; + + EXPECT_EQ(NULL, GetActiveWindow()); + + aura::Window* w2 = root_window()->GetChildById(2); + ActivateWindow(w2); + EXPECT_EQ(2, GetActiveWindowId()); + + ChangeWindowDisposition(w2); + EXPECT_EQ(3, GetActiveWindowId()); + PostDispostionChangeExpectations(); + } + virtual void FocusEvents() OVERRIDE { + aura::Window* w211 = root_window()->GetChildById(211); + FocusWindow(w211); + + ScopedFocusNotificationObserver root_observer(root_window()); + ScopedTargetFocusNotificationObserver observer211(root_window(), 211); + root_observer.ExpectCounts(0, 0); + observer211.ExpectCounts(0, 0); + + ChangeWindowDisposition(w211); + root_observer.ExpectCounts(0, 1); + observer211.ExpectCounts(0, 1); + } + virtual void ActivationEvents() OVERRIDE { + DCHECK(!parent_) << "Activation tests don't support parent changes."; + + aura::Window* w2 = root_window()->GetChildById(2); + ActivateWindow(w2); + + ScopedFocusNotificationObserver root_observer(root_window()); + ScopedTargetFocusNotificationObserver observer2(root_window(), 2); + ScopedTargetFocusNotificationObserver observer3(root_window(), 3); + root_observer.ExpectCounts(0, 0); + observer2.ExpectCounts(0, 0); + observer3.ExpectCounts(0, 0); + + ChangeWindowDisposition(w2); + root_observer.ExpectCounts(1, 1); + observer2.ExpectCounts(1, 1); + observer3.ExpectCounts(1, 1); + } + virtual void FocusRulesOverride() OVERRIDE { + EXPECT_EQ(NULL, GetFocusedWindow()); + aura::Window* w211 = root_window()->GetChildById(211); + FocusWindow(w211); + EXPECT_EQ(211, GetFocusedWindowId()); + + test_focus_rules()->set_focus_restriction(root_window()->GetChildById(11)); + ChangeWindowDisposition(w211); + // Normally, focus would shift to the parent (w21) but the override shifts + // it to 11. + EXPECT_EQ(11, GetFocusedWindowId()); + + test_focus_rules()->set_focus_restriction(NULL); + } + virtual void ActivationRulesOverride() OVERRIDE { + DCHECK(!parent_) << "Activation tests don't support parent changes."; + + aura::Window* w1 = root_window()->GetChildById(1); + ActivateWindow(w1); + + EXPECT_EQ(1, GetActiveWindowId()); + EXPECT_EQ(1, GetFocusedWindowId()); + + aura::Window* w3 = root_window()->GetChildById(3); + test_focus_rules()->set_focus_restriction(w3); + + // Normally, activation/focus would move to w2, but since we have a focus + // restriction, it should move to w3 instead. + ChangeWindowDisposition(w1); + EXPECT_EQ(3, GetActiveWindowId()); + EXPECT_EQ(3, GetFocusedWindowId()); + + test_focus_rules()->set_focus_restriction(NULL); + ActivateWindow(root_window()->GetChildById(2)); + EXPECT_EQ(2, GetActiveWindowId()); + EXPECT_EQ(2, GetFocusedWindowId()); + } + + private: + // When true, the disposition change occurs to the parent of the window + // instead of to the window. This verifies that changes occurring in the + // hierarchy that contains the window affect the window's focus. + bool parent_; + + DISALLOW_COPY_AND_ASSIGN(FocusControllerImplicitTestBase); +}; + +// Focus and Activation changes in response to window visibility changes. +class FocusControllerHideTest : public FocusControllerImplicitTestBase { + public: + FocusControllerHideTest() : FocusControllerImplicitTestBase(false) {} + + protected: + FocusControllerHideTest(bool parent) + : FocusControllerImplicitTestBase(parent) {} + + // Overridden from FocusControllerImplicitTestBase: + virtual void ChangeWindowDisposition(aura::Window* window) OVERRIDE { + GetDispositionWindow(window)->Hide(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(FocusControllerHideTest); +}; + +// Focus and Activation changes in response to window parent visibility +// changes. +class FocusControllerParentHideTest : public FocusControllerHideTest { + public: + FocusControllerParentHideTest() : FocusControllerHideTest(true) {} + + private: + DISALLOW_COPY_AND_ASSIGN(FocusControllerParentHideTest); +}; + +// Focus and Activation changes in response to window destruction. +class FocusControllerDestructionTest : public FocusControllerImplicitTestBase { + public: + FocusControllerDestructionTest() : FocusControllerImplicitTestBase(false) {} + + protected: + FocusControllerDestructionTest(bool parent) + : FocusControllerImplicitTestBase(parent) {} + + // Overridden from FocusControllerImplicitTestBase: + virtual void ChangeWindowDisposition(aura::Window* window) OVERRIDE { + delete GetDispositionWindow(window); + } + + private: + DISALLOW_COPY_AND_ASSIGN(FocusControllerDestructionTest); +}; + +// Focus and Activation changes in response to window parent destruction. +class FocusControllerParentDestructionTest + : public FocusControllerDestructionTest { + public: + FocusControllerParentDestructionTest() + : FocusControllerDestructionTest(true) {} + + private: + DISALLOW_COPY_AND_ASSIGN(FocusControllerParentDestructionTest); +}; + +// Focus and Activation changes in response to window removal. +class FocusControllerRemovalTest : public FocusControllerImplicitTestBase { + public: + FocusControllerRemovalTest() : FocusControllerImplicitTestBase(false) {} + + protected: + FocusControllerRemovalTest(bool parent) + : FocusControllerImplicitTestBase(parent) {} + + // Overridden from FocusControllerImplicitTestBase: + virtual void ChangeWindowDisposition(aura::Window* window) OVERRIDE { + aura::Window* disposition_window = GetDispositionWindow(window); + disposition_window->parent()->RemoveChild(disposition_window); + window_owner_.reset(disposition_window); + } + virtual void TearDown() OVERRIDE { + window_owner_.reset(); + FocusControllerImplicitTestBase::TearDown(); + } + + private: + scoped_ptr<aura::Window> window_owner_; + + DISALLOW_COPY_AND_ASSIGN(FocusControllerRemovalTest); +}; + +// Focus and Activation changes in response to window parent removal. +class FocusControllerParentRemovalTest : public FocusControllerRemovalTest { + public: + FocusControllerParentRemovalTest() : FocusControllerRemovalTest(true) {} + + private: + DISALLOW_COPY_AND_ASSIGN(FocusControllerParentRemovalTest); +}; + + +#define FOCUS_CONTROLLER_TEST(TESTCLASS, TESTNAME) \ + TEST_F(TESTCLASS, TESTNAME) { TESTNAME(); } + +// Runs direct focus change tests (input events and API calls). +#define DIRECT_FOCUS_CHANGE_TESTS(TESTNAME) \ + FOCUS_CONTROLLER_TEST(FocusControllerApiTest, TESTNAME) \ + FOCUS_CONTROLLER_TEST(FocusControllerMouseEventTest, TESTNAME) \ + FOCUS_CONTROLLER_TEST(FocusControllerGestureEventTest, TESTNAME) + +// Runs implicit focus change tests for disposition changes to target. +#define IMPLICIT_FOCUS_CHANGE_TARGET_TESTS(TESTNAME) \ + FOCUS_CONTROLLER_TEST(FocusControllerHideTest, TESTNAME) \ + FOCUS_CONTROLLER_TEST(FocusControllerDestructionTest, TESTNAME) \ + FOCUS_CONTROLLER_TEST(FocusControllerRemovalTest, TESTNAME) + +// Runs implicit focus change tests for disposition changes to target's parent +// hierarchy. +#define IMPLICIT_FOCUS_CHANGE_PARENT_TESTS(TESTNAME) \ + /* TODO(beng): parent destruction tests are not supported at + present due to workspace manager issues. \ + FOCUS_CONTROLLER_TEST(FocusControllerParentDestructionTest, TESTNAME) */ \ + FOCUS_CONTROLLER_TEST(FocusControllerParentHideTest, TESTNAME) \ + FOCUS_CONTROLLER_TEST(FocusControllerParentRemovalTest, TESTNAME) + +// Runs all implicit focus change tests (changes to the target and target's +// parent hierarchy) +#define IMPLICIT_FOCUS_CHANGE_TESTS(TESTNAME) \ + IMPLICIT_FOCUS_CHANGE_TARGET_TESTS(TESTNAME) \ + IMPLICIT_FOCUS_CHANGE_PARENT_TESTS(TESTNAME) + +// Runs all possible focus change tests. +#define ALL_FOCUS_TESTS(TESTNAME) \ + DIRECT_FOCUS_CHANGE_TESTS(TESTNAME) \ + IMPLICIT_FOCUS_CHANGE_TESTS(TESTNAME) + +// Runs focus change tests that apply only to the target. For example, +// implicit activation changes caused by window disposition changes do not +// occur when changes to the containing hierarchy happen. +#define TARGET_FOCUS_TESTS(TESTNAME) \ + DIRECT_FOCUS_CHANGE_TESTS(TESTNAME) \ + IMPLICIT_FOCUS_CHANGE_TARGET_TESTS(TESTNAME) + +// - Focuses a window, verifies that focus changed. +ALL_FOCUS_TESTS(BasicFocus); + +// - Activates a window, verifies that activation changed. +TARGET_FOCUS_TESTS(BasicActivation); + +// - Focuses a window, verifies that focus events were dispatched. +ALL_FOCUS_TESTS(FocusEvents); + +// - Focuses or activates a window multiple times, verifies that events are only +// dispatched when focus/activation actually changes. +DIRECT_FOCUS_CHANGE_TESTS(DuplicateFocusEvents); +DIRECT_FOCUS_CHANGE_TESTS(DuplicateActivationEvents); + +// - Activates a window, verifies that activation events were dispatched. +TARGET_FOCUS_TESTS(ActivationEvents); + +// - Attempts to active a hidden window, verifies that current window is +// attempted to be reactivated and the appropriate event dispatched. +FOCUS_CONTROLLER_TEST(FocusControllerApiTest, ReactivationEvents); + +// - Input events/API calls shift focus between focusable windows within the +// active window. +DIRECT_FOCUS_CHANGE_TESTS(ShiftFocusWithinActiveWindow); + +// - Input events/API calls to a child window of an inactive window shifts +// activation to the activatable parent and focuses the child. +DIRECT_FOCUS_CHANGE_TESTS(ShiftFocusToChildOfInactiveWindow); + +// - Input events/API calls to focus the parent of the focused window do not +// shift focus away from the child. +DIRECT_FOCUS_CHANGE_TESTS(ShiftFocusToParentOfFocusedWindow); + +// - Verifies that FocusRules determine what can be focused. +ALL_FOCUS_TESTS(FocusRulesOverride); + +// - Verifies that FocusRules determine what can be activated. +TARGET_FOCUS_TESTS(ActivationRulesOverride); + +// - Verifies that attempts to change focus or activation from a focus or +// activation change observer are ignored. +DIRECT_FOCUS_CHANGE_TESTS(ShiftFocusOnActivation); +DIRECT_FOCUS_CHANGE_TESTS(ShiftFocusOnActivationDueToHide); +DIRECT_FOCUS_CHANGE_TESTS(NoShiftActiveOnActivation); + +// Clicking on a window which has capture should not result in a focus change. +DIRECT_FOCUS_CHANGE_TESTS(NoFocusChangeOnClickOnCaptureWindow); + +FOCUS_CONTROLLER_TEST(FocusControllerApiTest, + ChangeFocusWhenNothingFocusedAndCaptured); + +// See description above DontPassDeletedWindow() for details. +FOCUS_CONTROLLER_TEST(FocusControllerApiTest, DontPassDeletedWindow); + +// - Verifies that the focused text input client is cleard when the window focus +// changes. +ALL_FOCUS_TESTS(FocusedTextInputClient); + +// If a mouse event was handled, it should not activate a window. +FOCUS_CONTROLLER_TEST(FocusControllerMouseEventTest, IgnoreHandledEvent); + +} // namespace wm diff --git a/chromium/ui/wm/core/focus_rules.h b/chromium/ui/wm/core/focus_rules.h new file mode 100644 index 00000000000..cb9a4ddbd8f --- /dev/null +++ b/chromium/ui/wm/core/focus_rules.h @@ -0,0 +1,66 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_WM_CORE_FOCUS_RULES_H_ +#define UI_WM_CORE_FOCUS_RULES_H_ + +#include "ui/wm/wm_export.h" + +namespace aura { +class Window; +} + +namespace wm { + +// Implemented by an object that establishes the rules about what can be +// focused or activated. +class WM_EXPORT FocusRules { + public: + virtual ~FocusRules() {} + + // Returns true if |window| is a toplevel window. Whether or not a window + // is considered toplevel is determined by a similar set of rules that + // govern activation and focus. Not all toplevel windows are activatable, + // call CanActivateWindow() to determine if a window can be activated. + virtual bool IsToplevelWindow(aura::Window* window) const = 0; + // Returns true if |window| can be activated or focused. + virtual bool CanActivateWindow(aura::Window* window) const = 0; + // For CanFocusWindow(), NULL is supported, because NULL is a valid focusable + // window (in the case of clearing focus). + virtual bool CanFocusWindow(aura::Window* window) const = 0; + + // Returns the toplevel window containing |window|. Not all toplevel windows + // are activatable, call GetActivatableWindow() instead to return the + // activatable window, which might be in a different hierarchy. + // Will return NULL if |window| is not contained by a window considered to be + // a toplevel window. + virtual aura::Window* GetToplevelWindow(aura::Window* window) const = 0; + // Returns the activatable or focusable window given an attempt to activate or + // focus |window|. Some possible scenarios (not intended to be exhaustive): + // - |window| is a child of a non-focusable window and so focus must be set + // according to rules defined by the delegate, e.g. to a parent. + // - |window| is an activatable window that is the transient parent of a modal + // window, so attempts to activate |window| should result in the modal + // transient being activated instead. + // These methods may return NULL if they are unable to find an activatable + // or focusable window given |window|. + virtual aura::Window* GetActivatableWindow(aura::Window* window) const = 0; + virtual aura::Window* GetFocusableWindow(aura::Window* window) const = 0; + + // Returns the next window to activate in the event that |ignore| is no longer + // activatable. This function is called when something is happening to + // |ignore| that means it can no longer have focus or activation, including + // but not limited to: + // - it or its parent hierarchy is being hidden, or removed from the + // RootWindow. + // - it is being destroyed. + // - it is being explicitly deactivated. + // |ignore| cannot be NULL. + virtual aura::Window* GetNextActivatableWindow( + aura::Window* ignore) const = 0; +}; + +} // namespace wm + +#endif // UI_WM_CORE_FOCUS_RULES_H_ diff --git a/chromium/ui/wm/core/image_grid.cc b/chromium/ui/wm/core/image_grid.cc new file mode 100644 index 00000000000..db41628ea38 --- /dev/null +++ b/chromium/ui/wm/core/image_grid.cc @@ -0,0 +1,338 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/image_grid.h" + +#include <algorithm> + +#include "third_party/skia/include/core/SkColor.h" +#include "third_party/skia/include/core/SkXfermode.h" +#include "ui/compositor/dip_util.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/image/image.h" +#include "ui/gfx/image/image_skia_operations.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/rect_conversions.h" +#include "ui/gfx/size.h" +#include "ui/gfx/size_conversions.h" +#include "ui/gfx/transform.h" + +using std::max; +using std::min; + +namespace wm { +namespace { + +// Sets the scaling for the transform applied to a layer. The left, top, +// right and bottom layers are stretched to the height or width of the +// center image. + +void ScaleWidth(gfx::Size center, ui::Layer* layer, gfx::Transform& transform) { + float layer_width = layer->bounds().width() * layer->device_scale_factor(); + float scale = static_cast<float>(center.width()) / layer_width; + transform.Scale(scale, 1.0); +} + +void ScaleHeight(gfx::Size center, + ui::Layer* layer, + gfx::Transform& transform) { + float layer_height = layer->bounds().height() * layer->device_scale_factor(); + float scale = static_cast<float>(center.height()) / layer_height; + transform.Scale(1.0, scale); +} + +// Returns the dimensions of |image| if non-NULL or gfx::Size(0, 0) otherwise. +gfx::Size GetImageSize(const gfx::Image* image) { + return image ? gfx::Size(image->ToImageSkia()->width(), + image->ToImageSkia()->height()) + : gfx::Size(); +} + +// Returns true if |layer|'s bounds don't fit within |size|. +bool LayerExceedsSize(const ui::Layer* layer, const gfx::Size& size) { + return layer->bounds().width() > size.width() || + layer->bounds().height() > size.height(); +} + +} // namespace + +gfx::RectF ImageGrid::TestAPI::GetTransformedLayerBounds( + const ui::Layer& layer) { + gfx::RectF bounds = layer.bounds(); + layer.transform().TransformRect(&bounds); + return bounds; +} + +ImageGrid::ImageGrid() + : layer_(new ui::Layer(ui::LAYER_NOT_DRAWN)), + top_image_height_(0), + bottom_image_height_(0), + left_image_width_(0), + right_image_width_(0), + base_top_row_height_(0), + base_bottom_row_height_(0), + base_left_column_width_(0), + base_right_column_width_(0) { +} + +ImageGrid::~ImageGrid() { +} + +void ImageGrid::SetImages(const gfx::Image* top_left_image, + const gfx::Image* top_image, + const gfx::Image* top_right_image, + const gfx::Image* left_image, + const gfx::Image* center_image, + const gfx::Image* right_image, + const gfx::Image* bottom_left_image, + const gfx::Image* bottom_image, + const gfx::Image* bottom_right_image) { + SetImage(top_left_image, &top_left_layer_, &top_left_painter_, NONE); + SetImage(top_image, &top_layer_, &top_painter_, HORIZONTAL); + SetImage(top_right_image, &top_right_layer_, &top_right_painter_, NONE); + SetImage(left_image, &left_layer_, &left_painter_, VERTICAL); + SetImage(center_image, ¢er_layer_, ¢er_painter_, NONE); + SetImage(right_image, &right_layer_, &right_painter_, VERTICAL); + SetImage(bottom_left_image, &bottom_left_layer_, &bottom_left_painter_, NONE); + SetImage(bottom_image, &bottom_layer_, &bottom_painter_, HORIZONTAL); + SetImage( + bottom_right_image, &bottom_right_layer_, &bottom_right_painter_, NONE); + + top_image_height_ = GetImageSize(top_image).height(); + bottom_image_height_ = GetImageSize(bottom_image).height(); + left_image_width_ = GetImageSize(left_image).width(); + right_image_width_ = GetImageSize(right_image).width(); + + base_top_row_height_ = max(GetImageSize(top_left_image).height(), + max(GetImageSize(top_image).height(), + GetImageSize(top_right_image).height())); + base_bottom_row_height_ = max(GetImageSize(bottom_left_image).height(), + max(GetImageSize(bottom_image).height(), + GetImageSize(bottom_right_image).height())); + base_left_column_width_ = max(GetImageSize(top_left_image).width(), + max(GetImageSize(left_image).width(), + GetImageSize(bottom_left_image).width())); + base_right_column_width_ = max(GetImageSize(top_right_image).width(), + max(GetImageSize(right_image).width(), + GetImageSize(bottom_right_image).width())); + + // Invalidate previous |size_| so calls to SetSize() will recompute it. + size_.SetSize(0, 0); +} + +void ImageGrid::SetSize(const gfx::Size& size) { + if (size_ == size) + return; + + size_ = size; + + gfx::Rect updated_bounds = layer_->bounds(); + updated_bounds.set_size(size); + layer_->SetBounds(updated_bounds); + + // Calculate the available amount of space for corner images on all sides of + // the grid. If the images don't fit, we need to clip them. + const int left = min(base_left_column_width_, size_.width() / 2); + const int right = min(base_right_column_width_, size_.width() - left); + const int top = min(base_top_row_height_, size_.height() / 2); + const int bottom = min(base_bottom_row_height_, size_.height() - top); + + // The remaining space goes to the center image. + int center_width = std::max(size.width() - left - right, 0); + int center_height = std::max(size.height() - top - bottom, 0); + + // At non-integer scale factors, the ratio of dimensions in DIP is not + // necessarily the same as the ratio in physical pixels due to rounding. Set + // the transform on each of the layers based on dimensions in pixels. + gfx::Size center_size_in_pixels = gfx::ToFlooredSize(gfx::ScaleSize( + gfx::Size(center_width, center_height), layer_->device_scale_factor())); + + if (top_layer_.get()) { + if (center_width > 0) { + gfx::Transform transform; + transform.Translate(left, 0); + ScaleWidth(center_size_in_pixels, top_layer_.get(), transform); + top_layer_->SetTransform(transform); + } + top_layer_->SetVisible(center_width > 0); + } + if (bottom_layer_.get()) { + if (center_width > 0) { + gfx::Transform transform; + transform.Translate( + left, size.height() - bottom_layer_->bounds().height()); + ScaleWidth(center_size_in_pixels, bottom_layer_.get(), transform); + bottom_layer_->SetTransform(transform); + } + bottom_layer_->SetVisible(center_width > 0); + } + if (left_layer_.get()) { + if (center_height > 0) { + gfx::Transform transform; + transform.Translate(0, top); + ScaleHeight(center_size_in_pixels, left_layer_.get(), transform); + left_layer_->SetTransform(transform); + } + left_layer_->SetVisible(center_height > 0); + } + if (right_layer_.get()) { + if (center_height > 0) { + gfx::Transform transform; + transform.Translate( + size.width() - right_layer_->bounds().width(), top); + ScaleHeight(center_size_in_pixels, right_layer_.get(), transform); + right_layer_->SetTransform(transform); + } + right_layer_->SetVisible(center_height > 0); + } + + if (top_left_layer_.get()) { + // No transformation needed; it should be at (0, 0) and unscaled. + top_left_painter_->SetClipRect( + LayerExceedsSize(top_left_layer_.get(), gfx::Size(left, top)) ? + gfx::Rect(gfx::Rect(0, 0, left, top)) : + gfx::Rect(), + top_left_layer_.get()); + } + if (top_right_layer_.get()) { + gfx::Transform transform; + transform.Translate(size.width() - top_right_layer_->bounds().width(), 0.0); + top_right_layer_->SetTransform(transform); + top_right_painter_->SetClipRect( + LayerExceedsSize(top_right_layer_.get(), gfx::Size(right, top)) ? + gfx::Rect(top_right_layer_->bounds().width() - right, 0, + right, top) : + gfx::Rect(), + top_right_layer_.get()); + } + if (bottom_left_layer_.get()) { + gfx::Transform transform; + transform.Translate( + 0.0, size.height() - bottom_left_layer_->bounds().height()); + bottom_left_layer_->SetTransform(transform); + bottom_left_painter_->SetClipRect( + LayerExceedsSize(bottom_left_layer_.get(), gfx::Size(left, bottom)) ? + gfx::Rect(0, bottom_left_layer_->bounds().height() - bottom, + left, bottom) : + gfx::Rect(), + bottom_left_layer_.get()); + } + if (bottom_right_layer_.get()) { + gfx::Transform transform; + transform.Translate( + size.width() - bottom_right_layer_->bounds().width(), + size.height() - bottom_right_layer_->bounds().height()); + bottom_right_layer_->SetTransform(transform); + bottom_right_painter_->SetClipRect( + LayerExceedsSize(bottom_right_layer_.get(), gfx::Size(right, bottom)) ? + gfx::Rect(bottom_right_layer_->bounds().width() - right, + bottom_right_layer_->bounds().height() - bottom, + right, bottom) : + gfx::Rect(), + bottom_right_layer_.get()); + } + + if (center_layer_.get()) { + if (center_width > 0 && center_height > 0) { + gfx::Transform transform; + transform.Translate(left, top); + transform.Scale(center_width / center_layer_->bounds().width(), + center_height / center_layer_->bounds().height()); + center_layer_->SetTransform(transform); + } + center_layer_->SetVisible(center_width > 0 && center_height > 0); + } +} + +void ImageGrid::SetContentBounds(const gfx::Rect& content_bounds) { + + SetSize(gfx::Size( + content_bounds.width() + left_image_width_ + right_image_width_, + content_bounds.height() + top_image_height_ + + bottom_image_height_)); + layer_->SetBounds( + gfx::Rect(content_bounds.x() - left_image_width_, + content_bounds.y() - top_image_height_, + layer_->bounds().width(), + layer_->bounds().height())); +} + +void ImageGrid::ImagePainter::SetClipRect(const gfx::Rect& clip_rect, + ui::Layer* layer) { + if (clip_rect != clip_rect_) { + clip_rect_ = clip_rect; + layer->SchedulePaint(layer->bounds()); + } +} + +void ImageGrid::ImagePainter::OnPaintLayer(gfx::Canvas* canvas) { + if (!clip_rect_.IsEmpty()) + canvas->ClipRect(clip_rect_); + canvas->DrawImageInt(image_, 0, 0); +} + +void ImageGrid::ImagePainter::OnDeviceScaleFactorChanged( + float device_scale_factor) { + // Redrawing will take care of scale factor change. +} + +base::Closure ImageGrid::ImagePainter::PrepareForLayerBoundsChange() { + return base::Closure(); +} + +void ImageGrid::SetImage(const gfx::Image* image, + scoped_ptr<ui::Layer>* layer_ptr, + scoped_ptr<ImagePainter>* painter_ptr, + ImageType type) { + // Minimum width (for HORIZONTAL) or height (for VERTICAL) of the + // |image| so that layers are scaled property if the device scale + // factor is non integral. + const int kMinimumSize = 20; + + // Clean out old layers and painters. + if (layer_ptr->get()) + layer_->Remove(layer_ptr->get()); + layer_ptr->reset(); + painter_ptr->reset(); + + // If we're not using an image, we're done. + if (!image) + return; + + gfx::ImageSkia image_skia = image->AsImageSkia(); + switch (type) { + case HORIZONTAL: + if (image_skia.width() < kMinimumSize) { + image_skia = gfx::ImageSkiaOperations::CreateResizedImage( + image_skia, + skia::ImageOperations::RESIZE_GOOD, + gfx::Size(kMinimumSize, image_skia.height())); + } + break; + case VERTICAL: + if (image_skia.height() < kMinimumSize) { + image_skia = gfx::ImageSkiaOperations::CreateResizedImage( + image_skia, + skia::ImageOperations::RESIZE_GOOD, + gfx::Size(image_skia.width(), kMinimumSize)); + } + break; + case NONE: + break; + } + + // Set up the new layer and painter. + layer_ptr->reset(new ui::Layer(ui::LAYER_TEXTURED)); + + const gfx::Size size = image_skia.size(); + layer_ptr->get()->SetBounds(gfx::Rect(0, 0, size.width(), size.height())); + + painter_ptr->reset(new ImagePainter(image_skia)); + layer_ptr->get()->set_delegate(painter_ptr->get()); + layer_ptr->get()->SetFillsBoundsOpaquely(false); + layer_ptr->get()->SetVisible(true); + layer_->Add(layer_ptr->get()); +} + +} // namespace wm diff --git a/chromium/ui/wm/core/image_grid.h b/chromium/ui/wm/core/image_grid.h new file mode 100644 index 00000000000..987ee26c726 --- /dev/null +++ b/chromium/ui/wm/core/image_grid.h @@ -0,0 +1,221 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_WM_CORE_IMAGE_GRID_H_ +#define UI_WM_CORE_IMAGE_GRID_H_ + +#include "base/basictypes.h" +#include "base/gtest_prod_util.h" +#include "base/memory/scoped_ptr.h" +#include "ui/compositor/layer.h" +#include "ui/compositor/layer_delegate.h" +#include "ui/gfx/image/image_skia.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/size.h" +#include "ui/wm/wm_export.h" + +namespace gfx { +class Image; +} // namespace gfx + +namespace wm { + +// An ImageGrid is a 3x3 array of ui::Layers, each containing an image. +// +// As the grid is resized, its images fill the requested space: +// - corner images are not scaled +// - top and bottom images are scaled horizontally +// - left and right images are scaled vertically +// - the center image is scaled in both directions +// +// If one of the non-center images is smaller than the largest images in its +// row or column, it will be aligned with the outside of the grid. For +// example, given 4x4 top-left and top-right images and a 1x2 top images: +// +// +--------+---------------------+--------+ +// | | top | | +// | top- +---------------------+ top- + +// | left | | right | +// +----+---+ +---+----+ +// | | | | +// ... +// +// This may seem odd at first, but it lets ImageGrid be used to draw shadows +// with curved corners that extend inwards beyond a window's borders. In the +// below example, the top-left corner image is overlaid on top of the window's +// top-left corner: +// +// +---------+----------------------- +// | ..xxx|XXXXXXXXXXXXXXXXXX +// | .xXXXXX|XXXXXXXXXXXXXXXXXX_____ +// | .xXX | ^ window's top edge +// | .xXX | +// +---------+ +// | xXX| +// | xXX|< window's left edge +// | xXX| +// ... +// +class WM_EXPORT ImageGrid { + public: + // Helper class for use by tests. + class WM_EXPORT TestAPI { + public: + TestAPI(ImageGrid* grid) : grid_(grid) {} + + gfx::Rect top_left_clip_rect() const { + return grid_->top_left_painter_->clip_rect_; + } + gfx::Rect top_right_clip_rect() const { + return grid_->top_right_painter_->clip_rect_; + } + gfx::Rect bottom_left_clip_rect() const { + return grid_->bottom_left_painter_->clip_rect_; + } + gfx::Rect bottom_right_clip_rect() const { + return grid_->bottom_right_painter_->clip_rect_; + } + + // Returns |layer|'s bounds after applying the layer's current transform. + gfx::RectF GetTransformedLayerBounds(const ui::Layer& layer); + + private: + ImageGrid* grid_; // not owned + + DISALLOW_COPY_AND_ASSIGN(TestAPI); + }; + + ImageGrid(); + ~ImageGrid(); + + ui::Layer* layer() { return layer_.get(); } + int top_image_height() const { return top_image_height_; } + int bottom_image_height() const { return bottom_image_height_; } + int left_image_width() const { return left_image_width_; } + int right_image_width() const { return right_image_width_; } + + // Visible to allow independent layer animations and for testing. + ui::Layer* top_left_layer() const { return top_left_layer_.get(); } + ui::Layer* top_layer() const { return top_layer_.get(); } + ui::Layer* top_right_layer() const { return top_right_layer_.get(); } + ui::Layer* left_layer() const { return left_layer_.get(); } + ui::Layer* center_layer() const { return center_layer_.get(); } + ui::Layer* right_layer() const { return right_layer_.get(); } + ui::Layer* bottom_left_layer() const { return bottom_left_layer_.get(); } + ui::Layer* bottom_layer() const { return bottom_layer_.get(); } + ui::Layer* bottom_right_layer() const { return bottom_right_layer_.get(); } + + // Sets the grid to display the passed-in images (any of which can be NULL). + // Ownership of the images remains with the caller. May be called more than + // once to switch images. + void SetImages(const gfx::Image* top_left_image, + const gfx::Image* top_image, + const gfx::Image* top_right_image, + const gfx::Image* left_image, + const gfx::Image* center_image, + const gfx::Image* right_image, + const gfx::Image* bottom_left_image, + const gfx::Image* bottom_image, + const gfx::Image* bottom_right_image); + + void SetSize(const gfx::Size& size); + + // Sets the grid to a position and size such that the inner edges of the top, + // bottom, left and right images will be flush with |content_bounds_in_dip|. + void SetContentBounds(const gfx::Rect& content_bounds_in_dip); + + private: + // Delegate responsible for painting a specific image on a layer. + class ImagePainter : public ui::LayerDelegate { + public: + ImagePainter(const gfx::ImageSkia& image) : image_(image) {} + virtual ~ImagePainter() {} + + // Clips |layer| to |clip_rect|. Triggers a repaint if the clipping + // rectangle has changed. An empty rectangle disables clipping. + void SetClipRect(const gfx::Rect& clip_rect, ui::Layer* layer); + + // ui::LayerDelegate implementation: + virtual void OnPaintLayer(gfx::Canvas* canvas) OVERRIDE; + virtual void OnDeviceScaleFactorChanged(float device_scale_factor) OVERRIDE; + virtual base::Closure PrepareForLayerBoundsChange() OVERRIDE; + + private: + friend class TestAPI; + + const gfx::ImageSkia image_; + + gfx::Rect clip_rect_; + + DISALLOW_COPY_AND_ASSIGN(ImagePainter); + }; + + enum ImageType { + HORIZONTAL, + VERTICAL, + NONE, + }; + + // Sets |layer_ptr| and |painter_ptr| to display |image| and adds the + // passed-in layer to |layer_|. If image is NULL resets |layer_ptr| and + // |painter_ptr| and removes any existing layer from |layer_|. + // If |type| is either HORIZONTAL or VERTICAL, it may resize the image to + // guarantee that it has minimum size in order for scaling work properly + // with fractional device scale factors. crbug.com/376519. + void SetImage(const gfx::Image* image, + scoped_ptr<ui::Layer>* layer_ptr, + scoped_ptr<ImagePainter>* painter_ptr, + ImageType type); + + // Layer that contains all of the image layers. + scoped_ptr<ui::Layer> layer_; + + // The grid's dimensions. + gfx::Size size_; + + // Heights and widths of the images displayed by |top_layer_|, + // |bottom_layer_|, |left_layer_|, and |right_layer_|. + int top_image_height_; + int bottom_image_height_; + int left_image_width_; + int right_image_width_; + + // Heights of the tallest images in the top and bottom rows and the widest + // images in the left and right columns. Note that we may have less actual + // space than this available if the images are large and |size_| is small. + int base_top_row_height_; + int base_bottom_row_height_; + int base_left_column_width_; + int base_right_column_width_; + + // Layers used to display the various images. Children of |layer_|. + // Positions for which no images were supplied are NULL. + scoped_ptr<ui::Layer> top_left_layer_; + scoped_ptr<ui::Layer> top_layer_; + scoped_ptr<ui::Layer> top_right_layer_; + scoped_ptr<ui::Layer> left_layer_; + scoped_ptr<ui::Layer> center_layer_; + scoped_ptr<ui::Layer> right_layer_; + scoped_ptr<ui::Layer> bottom_left_layer_; + scoped_ptr<ui::Layer> bottom_layer_; + scoped_ptr<ui::Layer> bottom_right_layer_; + + // Delegates responsible for painting the above layers. + // Positions for which no images were supplied are NULL. + scoped_ptr<ImagePainter> top_left_painter_; + scoped_ptr<ImagePainter> top_painter_; + scoped_ptr<ImagePainter> top_right_painter_; + scoped_ptr<ImagePainter> left_painter_; + scoped_ptr<ImagePainter> center_painter_; + scoped_ptr<ImagePainter> right_painter_; + scoped_ptr<ImagePainter> bottom_left_painter_; + scoped_ptr<ImagePainter> bottom_painter_; + scoped_ptr<ImagePainter> bottom_right_painter_; + + DISALLOW_COPY_AND_ASSIGN(ImageGrid); +}; + +} // namespace wm + +#endif // UI_WM_CORE_IMAGE_GRID_H_ diff --git a/chromium/ui/wm/core/image_grid_unittest.cc b/chromium/ui/wm/core/image_grid_unittest.cc new file mode 100644 index 00000000000..4b413e44af0 --- /dev/null +++ b/chromium/ui/wm/core/image_grid_unittest.cc @@ -0,0 +1,340 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/image_grid.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/aura/test/aura_test_base.h" +#include "ui/gfx/image/image.h" +#include "ui/gfx/image/image_skia.h" + +namespace wm { + +namespace { + +// Creates a gfx::Image with the requested dimensions. +gfx::Image* CreateImage(const gfx::Size& size) { + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, size.width(), size.height()); + return new gfx::Image(gfx::ImageSkia::CreateFrom1xBitmap(bitmap)); +} + +} // namespace + +typedef aura::test::AuraTestBase ImageGridTest; + +// Test that an ImageGrid's layers are transformed correctly when SetSize() is +// called. +TEST_F(ImageGridTest, Basic) { + // Size of the images around the grid's border. + const int kBorder = 2; + + scoped_ptr<gfx::Image> image_1x1(CreateImage(gfx::Size(1, 1))); + scoped_ptr<gfx::Image> image_1xB(CreateImage(gfx::Size(1, kBorder))); + scoped_ptr<gfx::Image> image_Bx1(CreateImage(gfx::Size(kBorder, 1))); + scoped_ptr<gfx::Image> image_BxB(CreateImage(gfx::Size(kBorder, kBorder))); + + ImageGrid grid; + grid.SetImages(image_BxB.get(), image_1xB.get(), image_BxB.get(), + image_Bx1.get(), image_1x1.get(), image_Bx1.get(), + image_BxB.get(), image_1xB.get(), image_BxB.get()); + + ImageGrid::TestAPI test_api(&grid); + ASSERT_TRUE(grid.top_left_layer() != NULL); + ASSERT_TRUE(grid.top_layer() != NULL); + ASSERT_TRUE(grid.top_right_layer() != NULL); + ASSERT_TRUE(grid.left_layer() != NULL); + ASSERT_TRUE(grid.center_layer() != NULL); + ASSERT_TRUE(grid.right_layer() != NULL); + ASSERT_TRUE(grid.bottom_left_layer() != NULL); + ASSERT_TRUE(grid.bottom_layer() != NULL); + ASSERT_TRUE(grid.bottom_right_layer() != NULL); + + const gfx::Size size(20, 30); + grid.SetSize(size); + + // The top-left layer should be flush with the top-left corner and unscaled. + EXPECT_EQ(gfx::RectF(0, 0, kBorder, kBorder).ToString(), + test_api.GetTransformedLayerBounds( + *grid.top_left_layer()).ToString()); + + // The top layer should be flush with the top edge and stretched horizontally + // between the two top corners. + EXPECT_EQ(gfx::RectF( + kBorder, 0, size.width() - 2 * kBorder, kBorder).ToString(), + test_api.GetTransformedLayerBounds( + *grid.top_layer()).ToString()); + + // The top-right layer should be flush with the top-right corner and unscaled. + EXPECT_EQ(gfx::RectF(size.width() - kBorder, 0, kBorder, kBorder).ToString(), + test_api.GetTransformedLayerBounds( + *grid.top_right_layer()).ToString()); + + // The left layer should be flush with the left edge and stretched vertically + // between the two left corners. + EXPECT_EQ(gfx::RectF( + 0, kBorder, kBorder, size.height() - 2 * kBorder).ToString(), + test_api.GetTransformedLayerBounds( + *grid.left_layer()).ToString()); + + // The center layer should fill the space in the middle of the grid. + EXPECT_EQ(gfx::RectF( + kBorder, kBorder, size.width() - 2 * kBorder, + size.height() - 2 * kBorder).ToString(), + test_api.GetTransformedLayerBounds( + *grid.center_layer()).ToString()); + + // The right layer should be flush with the right edge and stretched + // vertically between the two right corners. + EXPECT_EQ(gfx::RectF( + size.width() - kBorder, kBorder, + kBorder, size.height() - 2 * kBorder).ToString(), + test_api.GetTransformedLayerBounds( + *grid.right_layer()).ToString()); + + // The bottom-left layer should be flush with the bottom-left corner and + // unscaled. + EXPECT_EQ(gfx::RectF(0, size.height() - kBorder, kBorder, kBorder).ToString(), + test_api.GetTransformedLayerBounds( + *grid.bottom_left_layer()).ToString()); + + // The bottom layer should be flush with the bottom edge and stretched + // horizontally between the two bottom corners. + EXPECT_EQ(gfx::RectF( + kBorder, size.height() - kBorder, + size.width() - 2 * kBorder, kBorder).ToString(), + test_api.GetTransformedLayerBounds( + *grid.bottom_layer()).ToString()); + + // The bottom-right layer should be flush with the bottom-right corner and + // unscaled. + EXPECT_EQ(gfx::RectF( + size.width() - kBorder, size.height() - kBorder, + kBorder, kBorder).ToString(), + test_api.GetTransformedLayerBounds( + *grid.bottom_right_layer()).ToString()); +} + +// Test that an ImageGrid's layers are transformed correctly when +// SetContentBounds() is called. +TEST_F(ImageGridTest, SetContentBounds) { + // Size of the images around the grid's border. + const int kBorder = 2; + + scoped_ptr<gfx::Image> image_1x1(CreateImage(gfx::Size(1, 1))); + scoped_ptr<gfx::Image> image_1xB(CreateImage(gfx::Size(1, kBorder))); + scoped_ptr<gfx::Image> image_Bx1(CreateImage(gfx::Size(kBorder, 1))); + scoped_ptr<gfx::Image> image_BxB(CreateImage(gfx::Size(kBorder, kBorder))); + + ImageGrid grid; + grid.SetImages(image_BxB.get(), image_1xB.get(), image_BxB.get(), + image_Bx1.get(), image_1x1.get(), image_Bx1.get(), + image_BxB.get(), image_1xB.get(), image_BxB.get()); + + ImageGrid::TestAPI test_api(&grid); + + const gfx::Point origin(5, 10); + const gfx::Size size(20, 30); + grid.SetContentBounds(gfx::Rect(origin, size)); + + // The master layer is positioned above the top-left corner of the content + // bounds and has height/width that contain the grid and bounds. + EXPECT_EQ(gfx::RectF(origin.x() - kBorder, + origin.y() - kBorder, + size.width() + 2 * kBorder, + size.height() + 2 * kBorder).ToString(), + test_api.GetTransformedLayerBounds(*grid.layer()).ToString()); +} + +// Check that we don't crash if only a single image is supplied. +TEST_F(ImageGridTest, SingleImage) { + const int kBorder = 1; + scoped_ptr<gfx::Image> image(CreateImage(gfx::Size(kBorder, kBorder))); + + ImageGrid grid; + grid.SetImages(NULL, image.get(), NULL, + NULL, NULL, NULL, + NULL, NULL, NULL); + + ImageGrid::TestAPI test_api(&grid); + EXPECT_TRUE(grid.top_left_layer() == NULL); + ASSERT_TRUE(grid.top_layer() != NULL); + EXPECT_TRUE(grid.top_right_layer() == NULL); + EXPECT_TRUE(grid.left_layer() == NULL); + EXPECT_TRUE(grid.center_layer() == NULL); + EXPECT_TRUE(grid.right_layer() == NULL); + EXPECT_TRUE(grid.bottom_left_layer() == NULL); + EXPECT_TRUE(grid.bottom_layer() == NULL); + EXPECT_TRUE(grid.bottom_right_layer() == NULL); + + const gfx::Size kSize(10, 10); + grid.SetSize(kSize); + + // The top layer should be scaled horizontally across the entire width, but it + // shouldn't be scaled vertically. + EXPECT_EQ(gfx::RectF(0, 0, kSize.width(), kBorder).ToString(), + test_api.GetTransformedLayerBounds( + *grid.top_layer()).ToString()); +} + +// Check that we don't crash when we reset existing images to NULL and +// reset NULL images to new ones. +TEST_F(ImageGridTest, ResetImages) { + const int kBorder = 1; + scoped_ptr<gfx::Image> image(CreateImage(gfx::Size(kBorder, kBorder))); + + ImageGrid grid; + grid.SetImages(NULL, image.get(), NULL, + NULL, NULL, NULL, + NULL, NULL, NULL); + + // Only the top edge has a layer. + ImageGrid::TestAPI test_api(&grid); + ASSERT_TRUE(grid.top_left_layer() == NULL); + ASSERT_FALSE(grid.top_layer() == NULL); + ASSERT_TRUE(grid.top_right_layer() == NULL); + ASSERT_TRUE(grid.left_layer() == NULL); + ASSERT_TRUE(grid.center_layer() == NULL); + ASSERT_TRUE(grid.right_layer() == NULL); + ASSERT_TRUE(grid.bottom_left_layer() == NULL); + ASSERT_TRUE(grid.bottom_layer() == NULL); + ASSERT_TRUE(grid.bottom_right_layer() == NULL); + + grid.SetImages(NULL, NULL, NULL, + NULL, NULL, NULL, + NULL, image.get(), NULL); + + // Now only the bottom edge has a layer. + ASSERT_TRUE(grid.top_left_layer() == NULL); + ASSERT_TRUE(grid.top_layer() == NULL); + ASSERT_TRUE(grid.top_right_layer() == NULL); + ASSERT_TRUE(grid.left_layer() == NULL); + ASSERT_TRUE(grid.center_layer() == NULL); + ASSERT_TRUE(grid.right_layer() == NULL); + ASSERT_TRUE(grid.bottom_left_layer() == NULL); + ASSERT_FALSE(grid.bottom_layer() == NULL); + ASSERT_TRUE(grid.bottom_right_layer() == NULL); +} + +// Test that side (top, left, right, bottom) layers that are narrower than their +// adjacent corner layers stay pinned to the outside edges instead of getting +// moved inwards or scaled. This exercises the scenario used for shadows. +TEST_F(ImageGridTest, SmallerSides) { + const int kCorner = 2; + const int kEdge = 1; + + scoped_ptr<gfx::Image> top_left_image( + CreateImage(gfx::Size(kCorner, kCorner))); + scoped_ptr<gfx::Image> top_image(CreateImage(gfx::Size(kEdge, kEdge))); + scoped_ptr<gfx::Image> top_right_image( + CreateImage(gfx::Size(kCorner, kCorner))); + scoped_ptr<gfx::Image> left_image(CreateImage(gfx::Size(kEdge, kEdge))); + scoped_ptr<gfx::Image> right_image(CreateImage(gfx::Size(kEdge, kEdge))); + + ImageGrid grid; + grid.SetImages(top_left_image.get(), top_image.get(), top_right_image.get(), + left_image.get(), NULL, right_image.get(), + NULL, NULL, NULL); + ImageGrid::TestAPI test_api(&grid); + + const gfx::Size kSize(20, 30); + grid.SetSize(kSize); + + // The top layer should be flush with the top edge and stretched horizontally + // between the two top corners. + EXPECT_EQ(gfx::RectF( + kCorner, 0, kSize.width() - 2 * kCorner, kEdge).ToString(), + test_api.GetTransformedLayerBounds( + *grid.top_layer()).ToString()); + + // The left layer should be flush with the left edge and stretched vertically + // between the top left corner and the bottom. + EXPECT_EQ(gfx::RectF( + 0, kCorner, kEdge, kSize.height() - kCorner).ToString(), + test_api.GetTransformedLayerBounds( + *grid.left_layer()).ToString()); + + // The right layer should be flush with the right edge and stretched + // vertically between the top right corner and the bottom. + EXPECT_EQ(gfx::RectF( + kSize.width() - kEdge, kCorner, + kEdge, kSize.height() - kCorner).ToString(), + test_api.GetTransformedLayerBounds( + *grid.right_layer()).ToString()); +} + +// Test that we hide or clip layers as needed when the grid is assigned a small +// size. +TEST_F(ImageGridTest, TooSmall) { + const int kCorner = 5; + const int kCenter = 3; + const int kEdge = 3; + + scoped_ptr<gfx::Image> top_left_image( + CreateImage(gfx::Size(kCorner, kCorner))); + scoped_ptr<gfx::Image> top_image(CreateImage(gfx::Size(kEdge, kEdge))); + scoped_ptr<gfx::Image> top_right_image( + CreateImage(gfx::Size(kCorner, kCorner))); + scoped_ptr<gfx::Image> left_image(CreateImage(gfx::Size(kEdge, kEdge))); + scoped_ptr<gfx::Image> center_image(CreateImage(gfx::Size(kCenter, kCenter))); + scoped_ptr<gfx::Image> right_image(CreateImage(gfx::Size(kEdge, kEdge))); + scoped_ptr<gfx::Image> bottom_left_image( + CreateImage(gfx::Size(kCorner, kCorner))); + scoped_ptr<gfx::Image> bottom_image(CreateImage(gfx::Size(kEdge, kEdge))); + scoped_ptr<gfx::Image> bottom_right_image( + CreateImage(gfx::Size(kCorner, kCorner))); + + ImageGrid grid; + grid.SetImages( + top_left_image.get(), top_image.get(), top_right_image.get(), + left_image.get(), center_image.get(), right_image.get(), + bottom_left_image.get(), bottom_image.get(), bottom_right_image.get()); + ImageGrid::TestAPI test_api(&grid); + + // Set a size that's smaller than the combined (unscaled) corner images. + const gfx::Size kSmallSize(kCorner + kCorner - 3, kCorner + kCorner - 5); + grid.SetSize(kSmallSize); + + // The scalable images around the sides and in the center should be hidden. + EXPECT_FALSE(grid.top_layer()->visible()); + EXPECT_FALSE(grid.bottom_layer()->visible()); + EXPECT_FALSE(grid.left_layer()->visible()); + EXPECT_FALSE(grid.right_layer()->visible()); + EXPECT_FALSE(grid.center_layer()->visible()); + + // The corner images' clip rects should sum to the expected size. + EXPECT_EQ(kSmallSize.width(), + test_api.top_left_clip_rect().width() + + test_api.top_right_clip_rect().width()); + EXPECT_EQ(kSmallSize.width(), + test_api.bottom_left_clip_rect().width() + + test_api.bottom_right_clip_rect().width()); + EXPECT_EQ(kSmallSize.height(), + test_api.top_left_clip_rect().height() + + test_api.bottom_left_clip_rect().height()); + EXPECT_EQ(kSmallSize.height(), + test_api.top_right_clip_rect().height() + + test_api.bottom_right_clip_rect().height()); + + // Resize the grid to be large enough to show all images. + const gfx::Size kLargeSize(kCorner + kCorner + kCenter, + kCorner + kCorner + kCenter); + grid.SetSize(kLargeSize); + + // The scalable images should be visible now. + EXPECT_TRUE(grid.top_layer()->visible()); + EXPECT_TRUE(grid.bottom_layer()->visible()); + EXPECT_TRUE(grid.left_layer()->visible()); + EXPECT_TRUE(grid.right_layer()->visible()); + EXPECT_TRUE(grid.center_layer()->visible()); + + // We shouldn't be clipping the corner images anymore. + EXPECT_TRUE(test_api.top_left_clip_rect().IsEmpty()); + EXPECT_TRUE(test_api.top_right_clip_rect().IsEmpty()); + EXPECT_TRUE(test_api.bottom_left_clip_rect().IsEmpty()); + EXPECT_TRUE(test_api.bottom_right_clip_rect().IsEmpty()); +} + +} // namespace wm diff --git a/chromium/ui/wm/core/input_method_event_filter.cc b/chromium/ui/wm/core/input_method_event_filter.cc new file mode 100644 index 00000000000..3488e4adb8c --- /dev/null +++ b/chromium/ui/wm/core/input_method_event_filter.cc @@ -0,0 +1,105 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/input_method_event_filter.h" + +#include "ui/aura/client/aura_constants.h" +#include "ui/aura/window_tree_host.h" +#include "ui/base/ime/input_method.h" +#include "ui/base/ime/input_method_factory.h" +#include "ui/base/ime/text_input_client.h" +#include "ui/events/event.h" +#include "ui/events/event_processor.h" + +namespace wm { + +//////////////////////////////////////////////////////////////////////////////// +// InputMethodEventFilter, public: + +InputMethodEventFilter::InputMethodEventFilter(gfx::AcceleratedWidget widget) + : input_method_(ui::CreateInputMethod(this, widget)) { + // TODO(yusukes): Check if the root window is currently focused and pass the + // result to Init(). + input_method_->Init(true); +} + +InputMethodEventFilter::~InputMethodEventFilter() { +} + +void InputMethodEventFilter::SetInputMethodPropertyInRootWindow( + aura::Window* root_window) { + root_window->SetProperty(aura::client::kRootWindowInputMethodKey, + input_method_.get()); +} + +//////////////////////////////////////////////////////////////////////////////// +// InputMethodEventFilter, EventFilter implementation: + +void InputMethodEventFilter::OnKeyEvent(ui::KeyEvent* event) { + // We're processing key events as follows (details are simplified). + // + // At the beginning, key events have a ET_KEY_{PRESSED,RELEASED} event type, + // and they're passed from step 1 through step 3. + // 1. EventProcessor::OnEventFromSource() + // 2. InputMethodEventFilter::OnKeyEvent() + // 3. InputMethod::DispatchKeyEvent() + // where InputMethod may call DispatchKeyEventPostIME() if IME didn't consume + // the key event. Otherwise, step 4 through step 6 are skipped and we fall + // down to step 7 directly. + // 4. InputMethodEventFilter::DispatchKeyEventPostIME() + // where the key event is marked as TRANSLATED and the event type becomes + // ET_TRANSLATED_KEY_{PRESS,RELEASE}. Then, we dispatch the event again from + // the beginning. + // 5. EventProcessor::OnEventFromSource() [second time] + // 6. InputMethodEventFilter::OnKeyEvent() [second time] + // where we know that the event was already processed once by IME and + // re-dispatched, we don't pass the event to IME again. Instead we unmark the + // event as not translated (as same as the original state), and let the event + // dispatcher continue to dispatch the event to the rest event handlers. + // 7. EventHandler::OnKeyEvent() + if (event->IsTranslated()) { + // The |event| was already processed by IME, so we don't pass the event to + // IME again. Just let the event dispatcher continue to dispatch the event. + event->SetTranslated(false); + } else { + if (input_method_->DispatchKeyEvent(*event)) + event->StopPropagation(); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// InputMethodEventFilter, ui::InputMethodDelegate implementation: + +bool InputMethodEventFilter::DispatchKeyEventPostIME( + const ui::KeyEvent& event) { +#if defined(OS_WIN) + DCHECK(!event.HasNativeEvent() || event.native_event().message != WM_CHAR); +#endif + // Since the underlying IME didn't consume the key event, we're going to + // dispatch the event again from the beginning of the tree of event targets. + // This time we have to skip dispatching the event to the IME, we mark the + // event as TRANSLATED so we can distinguish this event as a second time + // dispatched event. + // For the target where to dispatch the event, always tries the current + // focused text input client's attached window. And fallback to the target + // carried by event. + aura::Window* target_window = NULL; + ui::TextInputClient* input = input_method_->GetTextInputClient(); + if (input) + target_window = input->GetAttachedWindow(); + if (!target_window) + target_window = static_cast<aura::Window*>(event.target()); + if (!target_window) + return false; + ui::EventProcessor* target_dispatcher = + target_window->GetRootWindow()->GetHost()->event_processor(); + ui::KeyEvent aura_event(event); + aura_event.SetTranslated(true); + ui::EventDispatchDetails details = + target_dispatcher->OnEventFromSource(&aura_event); + CHECK(!details.dispatcher_destroyed); + return aura_event.handled(); +} + +} // namespace wm diff --git a/chromium/ui/wm/core/input_method_event_filter.h b/chromium/ui/wm/core/input_method_event_filter.h new file mode 100644 index 00000000000..af823089b13 --- /dev/null +++ b/chromium/ui/wm/core/input_method_event_filter.h @@ -0,0 +1,50 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_WM_CORE_INPUT_METHOD_EVENT_FILTER_H_ +#define UI_WM_CORE_INPUT_METHOD_EVENT_FILTER_H_ + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "ui/base/ime/input_method_delegate.h" +#include "ui/events/event_handler.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/wm/wm_export.h" + +namespace ui { +class EventProcessor; +class InputMethod; +} + +namespace wm { + +// An event filter that forwards a KeyEvent to a system IME, and dispatches a +// TranslatedKeyEvent to the root window as needed. +class WM_EXPORT InputMethodEventFilter + : public ui::EventHandler, + public ui::internal::InputMethodDelegate { + public: + explicit InputMethodEventFilter(gfx::AcceleratedWidget widget); + virtual ~InputMethodEventFilter(); + + void SetInputMethodPropertyInRootWindow(aura::Window* root_window); + + ui::InputMethod* input_method() const { return input_method_.get(); } + + private: + // Overridden from ui::EventHandler: + virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE; + + // Overridden from ui::internal::InputMethodDelegate: + virtual bool DispatchKeyEventPostIME(const ui::KeyEvent& event) OVERRIDE; + + scoped_ptr<ui::InputMethod> input_method_; + + DISALLOW_COPY_AND_ASSIGN(InputMethodEventFilter); +}; + +} // namespace wm + +#endif // UI_WM_CORE_INPUT_METHOD_EVENT_FILTER_H_ diff --git a/chromium/ui/wm/core/input_method_event_filter_unittest.cc b/chromium/ui/wm/core/input_method_event_filter_unittest.cc new file mode 100644 index 00000000000..50c1fbc0c55 --- /dev/null +++ b/chromium/ui/wm/core/input_method_event_filter_unittest.cc @@ -0,0 +1,143 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/input_method_event_filter.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/aura/client/aura_constants.h" +#include "ui/aura/test/aura_test_base.h" +#include "ui/aura/test/event_generator.h" +#include "ui/aura/test/test_windows.h" +#include "ui/aura/window_event_dispatcher.h" +#include "ui/base/ime/dummy_text_input_client.h" +#include "ui/base/ime/input_method.h" +#include "ui/base/ime/text_input_focus_manager.h" +#include "ui/base/ui_base_switches_util.h" +#include "ui/events/test/test_event_handler.h" +#include "ui/wm/core/compound_event_filter.h" +#include "ui/wm/core/default_activation_client.h" +#include "ui/wm/public/activation_client.h" + +#if !defined(OS_WIN) && !defined(USE_X11) +// On platforms except Windows and X11, aura::test::EventGenerator::PressKey +// generates a key event without native_event(), which is not supported by +// ui::MockInputMethod. +#define TestInputMethodKeyEventPropagation \ +DISABLED_TestInputMethodKeyEventPropagation +#endif + +namespace wm { + +class TestTextInputClient : public ui::DummyTextInputClient { + public: + explicit TestTextInputClient(aura::Window* window) : window_(window) {} + + virtual aura::Window* GetAttachedWindow() const OVERRIDE { return window_; } + + private: + aura::Window* window_; + + DISALLOW_COPY_AND_ASSIGN(TestTextInputClient); +}; + +class InputMethodEventFilterTest : public aura::test::AuraTestBase { + public: + InputMethodEventFilterTest() {} + virtual ~InputMethodEventFilterTest() {} + + // testing::Test overrides: + virtual void SetUp() OVERRIDE { + aura::test::AuraTestBase::SetUp(); + + root_window()->AddPreTargetHandler(&root_filter_); + input_method_event_filter_.reset( + new InputMethodEventFilter(host()->GetAcceleratedWidget())); + input_method_event_filter_->SetInputMethodPropertyInRootWindow( + root_window()); + root_filter_.AddHandler(input_method_event_filter_.get()); + root_filter_.AddHandler(&test_filter_); + + test_window_.reset(aura::test::CreateTestWindowWithDelegate( + &test_window_delegate_, -1, gfx::Rect(), root_window())); + test_input_client_.reset(new TestTextInputClient(test_window_.get())); + + input_method_event_filter_->input_method()->SetFocusedTextInputClient( + test_input_client_.get()); + } + + virtual void TearDown() OVERRIDE { + test_window_.reset(); + root_filter_.RemoveHandler(&test_filter_); + root_filter_.RemoveHandler(input_method_event_filter_.get()); + root_window()->RemovePreTargetHandler(&root_filter_); + + input_method_event_filter_.reset(); + test_input_client_.reset(); + aura::test::AuraTestBase::TearDown(); + } + + protected: + CompoundEventFilter root_filter_; + ui::test::TestEventHandler test_filter_; + scoped_ptr<InputMethodEventFilter> input_method_event_filter_; + aura::test::TestWindowDelegate test_window_delegate_; + scoped_ptr<aura::Window> test_window_; + scoped_ptr<TestTextInputClient> test_input_client_; + + private: + DISALLOW_COPY_AND_ASSIGN(InputMethodEventFilterTest); +}; + +TEST_F(InputMethodEventFilterTest, TestInputMethodProperty) { + // Tests if InputMethodEventFilter adds a window property on its + // construction. + EXPECT_TRUE(root_window()->GetProperty( + aura::client::kRootWindowInputMethodKey)); +} + +// Tests if InputMethodEventFilter dispatches a ui::ET_TRANSLATED_KEY_* event to +// the root window. +TEST_F(InputMethodEventFilterTest, TestInputMethodKeyEventPropagation) { + // Send a fake key event to the root window. InputMethodEventFilter, which is + // automatically set up by AshTestBase, consumes it and sends a new + // ui::ET_TRANSLATED_KEY_* event to the root window, which will be consumed by + // the test event filter. + aura::test::EventGenerator generator(root_window()); + EXPECT_EQ(0, test_filter_.num_key_events()); + generator.PressKey(ui::VKEY_SPACE, 0); + EXPECT_EQ(1, test_filter_.num_key_events()); + generator.ReleaseKey(ui::VKEY_SPACE, 0); + EXPECT_EQ(2, test_filter_.num_key_events()); +} + +TEST_F(InputMethodEventFilterTest, TestEventDispatching) { + ui::KeyEvent evt(ui::ET_KEY_PRESSED, + ui::VKEY_PROCESSKEY, + ui::EF_IME_FABRICATED_KEY, + false); + // Calls DispatchKeyEventPostIME() without a focused text input client. + if (switches::IsTextInputFocusManagerEnabled()) + ui::TextInputFocusManager::GetInstance()->FocusTextInputClient(NULL); + else + input_method_event_filter_->input_method()->SetFocusedTextInputClient(NULL); + input_method_event_filter_->input_method()->DispatchKeyEvent(evt); + // Verifies 0 key event happened because InputMethodEventFilter:: + // DispatchKeyEventPostIME() returns false. + EXPECT_EQ(0, test_filter_.num_key_events()); + + // Calls DispatchKeyEventPostIME() with a focused text input client. + if (switches::IsTextInputFocusManagerEnabled()) { + ui::TextInputFocusManager::GetInstance()->FocusTextInputClient( + test_input_client_.get()); + } else { + input_method_event_filter_->input_method()->SetFocusedTextInputClient( + test_input_client_.get()); + } + input_method_event_filter_->input_method()->DispatchKeyEvent(evt); + // Verifies 1 key event happened because InputMethodEventFilter:: + // DispatchKeyEventPostIME() returns true. + EXPECT_EQ(1, test_filter_.num_key_events()); +} + +} // namespace wm diff --git a/chromium/ui/wm/core/masked_window_targeter.cc b/chromium/ui/wm/core/masked_window_targeter.cc new file mode 100644 index 00000000000..5cbdb1993b1 --- /dev/null +++ b/chromium/ui/wm/core/masked_window_targeter.cc @@ -0,0 +1,43 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/masked_window_targeter.h" + +#include "ui/aura/window.h" +#include "ui/gfx/path.h" + +namespace wm { + +MaskedWindowTargeter::MaskedWindowTargeter(aura::Window* masked_window) + : masked_window_(masked_window) { +} + +MaskedWindowTargeter::~MaskedWindowTargeter() {} + +bool MaskedWindowTargeter::EventLocationInsideBounds( + ui::EventTarget* target, + const ui::LocatedEvent& event) const { + aura::Window* window = static_cast<aura::Window*>(target); + if (window == masked_window_) { + gfx::Path mask; + if (!GetHitTestMask(window, &mask)) + return WindowTargeter::EventLocationInsideBounds(window, event); + + gfx::Size size = window->bounds().size(); + SkRegion clip_region; + clip_region.setRect(0, 0, size.width(), size.height()); + + gfx::Point point = event.location(); + if (window->parent()) + aura::Window::ConvertPointToTarget(window->parent(), window, &point); + + SkRegion mask_region; + return mask_region.setPath(mask, clip_region) && + mask_region.contains(point.x(), point.y()); + } + + return WindowTargeter::EventLocationInsideBounds(window, event); +} + +} // namespace wm diff --git a/chromium/ui/wm/core/masked_window_targeter.h b/chromium/ui/wm/core/masked_window_targeter.h new file mode 100644 index 00000000000..61bb3b1eeb4 --- /dev/null +++ b/chromium/ui/wm/core/masked_window_targeter.h @@ -0,0 +1,40 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_WM_CORE_MASKED_WINDOW_TARGETER_H_ +#define UI_WM_CORE_MASKED_WINDOW_TARGETER_H_ + +#include "ui/aura/window_targeter.h" +#include "ui/wm/wm_export.h" + +namespace gfx { +class Path; +} + +namespace wm { + +class WM_EXPORT MaskedWindowTargeter : public aura::WindowTargeter { + public: + explicit MaskedWindowTargeter(aura::Window* masked_window); + virtual ~MaskedWindowTargeter(); + + protected: + // Sets the hit-test mask for |window| in |mask| (in |window|'s local + // coordinate system). Returns whether a valid mask has been set in |mask|. + virtual bool GetHitTestMask(aura::Window* window, gfx::Path* mask) const = 0; + + // ui::EventTargeter: + virtual bool EventLocationInsideBounds( + ui::EventTarget* target, + const ui::LocatedEvent& event) const OVERRIDE; + + private: + aura::Window* masked_window_; + + DISALLOW_COPY_AND_ASSIGN(MaskedWindowTargeter); +}; + +} // namespace wm + +#endif // UI_WM_CORE_MASKED_WINDOW_TARGETER_H_ diff --git a/chromium/ui/wm/core/native_cursor_manager.h b/chromium/ui/wm/core/native_cursor_manager.h new file mode 100644 index 00000000000..de16ef8ce2d --- /dev/null +++ b/chromium/ui/wm/core/native_cursor_manager.h @@ -0,0 +1,61 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_WM_CORE_NATIVE_CURSOR_MANAGER_H_ +#define UI_WM_CORE_NATIVE_CURSOR_MANAGER_H_ + +#include "base/strings/string16.h" +#include "ui/base/cursor/cursor.h" +#include "ui/wm/core/native_cursor_manager_delegate.h" +#include "ui/wm/wm_export.h" + +namespace gfx { +class Display; +} + +namespace wm { + +// Interface where platforms such as Ash or Desktop aura are notified of +// requested changes to cursor state. When requested, implementer should tell +// the CursorManager of any actual state changes performed through the +// delegate. +class WM_EXPORT NativeCursorManager { + public: + virtual ~NativeCursorManager() {} + + // A request to set the screen DPI. Can cause changes in the current cursor. + virtual void SetDisplay( + const gfx::Display& display, + NativeCursorManagerDelegate* delegate) = 0; + + // A request to set the cursor to |cursor|. At minimum, implementer should + // call NativeCursorManagerDelegate::CommitCursor() with whatever cursor is + // actually used. + virtual void SetCursor( + gfx::NativeCursor cursor, + NativeCursorManagerDelegate* delegate) = 0; + + // A request to set the visibility of the cursor. At minimum, implementer + // should call NativeCursorManagerDelegate::CommitVisibility() with whatever + // the visibility is. + virtual void SetVisibility( + bool visible, + NativeCursorManagerDelegate* delegate) = 0; + + // A request to set the cursor set. + virtual void SetCursorSet( + ui::CursorSetType cursor_set, + NativeCursorManagerDelegate* delegate) = 0; + + // A request to set whether mouse events are disabled. At minimum, + // implementer should call NativeCursorManagerDelegate:: + // CommitMouseEventsEnabled() with whether mouse events are actually enabled. + virtual void SetMouseEventsEnabled( + bool enabled, + NativeCursorManagerDelegate* delegate) = 0; +}; + +} // namespace wm + +#endif // UI_WM_CORE_NATIVE_CURSOR_MANAGER_H_ diff --git a/chromium/ui/wm/core/native_cursor_manager_delegate.h b/chromium/ui/wm/core/native_cursor_manager_delegate.h new file mode 100644 index 00000000000..1393b2cd4b4 --- /dev/null +++ b/chromium/ui/wm/core/native_cursor_manager_delegate.h @@ -0,0 +1,33 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_WM_CORE_NATIVE_CURSOR_MANAGER_DELEGATE_H_ +#define UI_WM_CORE_NATIVE_CURSOR_MANAGER_DELEGATE_H_ + +#include "ui/base/cursor/cursor.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/wm/wm_export.h" + +namespace wm { + +// The non-public interface that CursorManager exposes to its users. This +// gives accessors to all the current state, and mutators to all the current +// state. +class WM_EXPORT NativeCursorManagerDelegate { + public: + virtual ~NativeCursorManagerDelegate() {} + + // TODO(tdanderson): Possibly remove this interface. + virtual gfx::NativeCursor GetCursor() const = 0; + virtual bool IsCursorVisible() const = 0; + + virtual void CommitCursor(gfx::NativeCursor cursor) = 0; + virtual void CommitVisibility(bool visible) = 0; + virtual void CommitCursorSet(ui::CursorSetType cursor_set) = 0; + virtual void CommitMouseEventsEnabled(bool enabled) = 0; +}; + +} // namespace wm + +#endif // UI_WM_CORE_NATIVE_CURSOR_MANAGER_DELEGATE_H_ diff --git a/chromium/ui/wm/core/nested_accelerator_controller.cc b/chromium/ui/wm/core/nested_accelerator_controller.cc new file mode 100644 index 00000000000..e4ace4f777a --- /dev/null +++ b/chromium/ui/wm/core/nested_accelerator_controller.cc @@ -0,0 +1,57 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/nested_accelerator_controller.h" + +#include "base/auto_reset.h" +#include "base/bind.h" +#include "base/run_loop.h" +#include "ui/wm/core/nested_accelerator_delegate.h" +#include "ui/wm/core/nested_accelerator_dispatcher.h" + +namespace wm { + +NestedAcceleratorController::NestedAcceleratorController( + NestedAcceleratorDelegate* delegate) + : dispatcher_delegate_(delegate) { + DCHECK(delegate); +} + +NestedAcceleratorController::~NestedAcceleratorController() { +} + +void NestedAcceleratorController::PrepareNestedLoopClosures( + base::MessagePumpDispatcher* nested_dispatcher, + base::Closure* run_closure, + base::Closure* quit_closure) { + scoped_ptr<NestedAcceleratorDispatcher> old_accelerator_dispatcher = + accelerator_dispatcher_.Pass(); + accelerator_dispatcher_ = NestedAcceleratorDispatcher::Create( + dispatcher_delegate_.get(), nested_dispatcher); + + scoped_ptr<base::RunLoop> run_loop = accelerator_dispatcher_->CreateRunLoop(); + *quit_closure = + base::Bind(&NestedAcceleratorController::QuitNestedMessageLoop, + base::Unretained(this), + run_loop->QuitClosure()); + *run_closure = base::Bind(&NestedAcceleratorController::RunNestedMessageLoop, + base::Unretained(this), + base::Passed(&run_loop), + base::Passed(&old_accelerator_dispatcher)); +} + +void NestedAcceleratorController::RunNestedMessageLoop( + scoped_ptr<base::RunLoop> run_loop, + scoped_ptr<NestedAcceleratorDispatcher> old_accelerator_dispatcher) { + run_loop->Run(); + accelerator_dispatcher_ = old_accelerator_dispatcher.Pass(); +} + +void NestedAcceleratorController::QuitNestedMessageLoop( + const base::Closure& quit_runloop) { + quit_runloop.Run(); + accelerator_dispatcher_.reset(); +} + +} // namespace wm diff --git a/chromium/ui/wm/core/nested_accelerator_controller.h b/chromium/ui/wm/core/nested_accelerator_controller.h new file mode 100644 index 00000000000..2826d72ea27 --- /dev/null +++ b/chromium/ui/wm/core/nested_accelerator_controller.h @@ -0,0 +1,47 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_WM_CORE_NESTED_ACCELERATOR_CONTROLLER_H_ +#define UI_WM_CORE_NESTED_ACCELERATOR_CONTROLLER_H_ + +#include "base/callback.h" +#include "base/message_loop/message_loop.h" +#include "ui/wm/public/dispatcher_client.h" +#include "ui/wm/wm_export.h" + +namespace wm { + +class NestedAcceleratorDelegate; +class NestedAcceleratorDispatcher; + +// Creates a dispatcher which wraps another dispatcher. +// The outer dispatcher runs first and performs ash specific handling. +// If it does not consume the event it forwards the event to the nested +// dispatcher. +class WM_EXPORT NestedAcceleratorController + : public aura::client::DispatcherClient { + public: + explicit NestedAcceleratorController(NestedAcceleratorDelegate* delegate); + virtual ~NestedAcceleratorController(); + + // aura::client::DispatcherClient: + virtual void PrepareNestedLoopClosures( + base::MessagePumpDispatcher* dispatcher, + base::Closure* run_closure, + base::Closure* quit_closure) OVERRIDE; + + private: + void RunNestedMessageLoop(scoped_ptr<base::RunLoop> run_loop, + scoped_ptr<NestedAcceleratorDispatcher> dispatcher); + void QuitNestedMessageLoop(const base::Closure& quit_runloop); + + scoped_ptr<NestedAcceleratorDispatcher> accelerator_dispatcher_; + scoped_ptr<NestedAcceleratorDelegate> dispatcher_delegate_; + + DISALLOW_COPY_AND_ASSIGN(NestedAcceleratorController); +}; + +} // namespace wm + +#endif // UI_WM_CORE_NESTED_ACCELERATOR_CONTROLLER_H_ diff --git a/chromium/ui/wm/core/nested_accelerator_controller_unittest.cc b/chromium/ui/wm/core/nested_accelerator_controller_unittest.cc new file mode 100644 index 00000000000..9fa394ef5f6 --- /dev/null +++ b/chromium/ui/wm/core/nested_accelerator_controller_unittest.cc @@ -0,0 +1,205 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/nested_accelerator_controller.h" + +#include "base/bind.h" +#include "base/event_types.h" +#include "base/message_loop/message_loop.h" +#include "ui/aura/test/aura_test_base.h" +#include "ui/aura/test/test_windows.h" +#include "ui/aura/window.h" +#include "ui/aura/window_event_dispatcher.h" +#include "ui/base/accelerators/accelerator.h" +#include "ui/base/accelerators/accelerator.h" +#include "ui/base/accelerators/accelerator_manager.h" +#include "ui/events/event_constants.h" +#include "ui/events/event_utils.h" +#include "ui/events/platform/platform_event_dispatcher.h" +#include "ui/events/platform/platform_event_source.h" +#include "ui/events/platform/scoped_event_dispatcher.h" +#include "ui/wm/core/nested_accelerator_delegate.h" +#include "ui/wm/public/dispatcher_client.h" + +#if defined(USE_X11) +#include <X11/Xlib.h> +#include "ui/events/test/events_test_utils_x11.h" +#endif // USE_X11 + +namespace wm { +namespace test { + +namespace { + +class MockDispatcher : public ui::PlatformEventDispatcher { + public: + MockDispatcher() : num_key_events_dispatched_(0) {} + + int num_key_events_dispatched() { return num_key_events_dispatched_; } + + private: + // ui::PlatformEventDispatcher: + virtual bool CanDispatchEvent(const ui::PlatformEvent& event) OVERRIDE { + return true; + } + virtual uint32_t DispatchEvent(const ui::PlatformEvent& event) OVERRIDE { + if (ui::EventTypeFromNative(event) == ui::ET_KEY_RELEASED) + num_key_events_dispatched_++; + return ui::POST_DISPATCH_NONE; + } + + int num_key_events_dispatched_; + + DISALLOW_COPY_AND_ASSIGN(MockDispatcher); +}; + +class TestTarget : public ui::AcceleratorTarget { + public: + TestTarget() : accelerator_pressed_count_(0) {} + virtual ~TestTarget() {} + + int accelerator_pressed_count() const { return accelerator_pressed_count_; } + + // Overridden from ui::AcceleratorTarget: + virtual bool AcceleratorPressed(const ui::Accelerator& accelerator) OVERRIDE { + accelerator_pressed_count_++; + return true; + } + virtual bool CanHandleAccelerators() const OVERRIDE { return true; } + + private: + int accelerator_pressed_count_; + + DISALLOW_COPY_AND_ASSIGN(TestTarget); +}; + +void DispatchKeyReleaseA(aura::Window* root_window) { +// Sending both keydown and keyup is necessary here because the accelerator +// manager only checks a keyup event following a keydown event. See +// ShouldHandle() in ui/base/accelerators/accelerator_manager.cc for details. +#if defined(OS_WIN) + MSG native_event_down = {NULL, WM_KEYDOWN, ui::VKEY_A, 0}; + aura::WindowTreeHost* host = root_window->GetHost(); + host->PostNativeEvent(native_event_down); + MSG native_event_up = {NULL, WM_KEYUP, ui::VKEY_A, 0}; + host->PostNativeEvent(native_event_up); +#elif defined(USE_X11) + ui::ScopedXI2Event native_event; + native_event.InitKeyEvent(ui::ET_KEY_PRESSED, ui::VKEY_A, 0); + aura::WindowTreeHost* host = root_window->GetHost(); + host->PostNativeEvent(native_event); + native_event.InitKeyEvent(ui::ET_KEY_RELEASED, ui::VKEY_A, 0); + host->PostNativeEvent(native_event); +#endif + // Make sure the inner message-loop terminates after dispatching the events. + base::MessageLoop::current()->PostTask( + FROM_HERE, base::MessageLoop::current()->QuitClosure()); +} + +class MockNestedAcceleratorDelegate : public NestedAcceleratorDelegate { + public: + MockNestedAcceleratorDelegate() + : accelerator_manager_(new ui::AcceleratorManager) {} + virtual ~MockNestedAcceleratorDelegate() {} + + // NestedAcceleratorDelegate: + virtual Result ProcessAccelerator( + const ui::Accelerator& accelerator) OVERRIDE { + return accelerator_manager_->Process(accelerator) ? + RESULT_PROCESSED : RESULT_NOT_PROCESSED; + } + + void Register(const ui::Accelerator& accelerator, + ui::AcceleratorTarget* target) { + accelerator_manager_->Register( + accelerator, ui::AcceleratorManager::kNormalPriority, target); + } + + private: + scoped_ptr<ui::AcceleratorManager> accelerator_manager_; + + DISALLOW_COPY_AND_ASSIGN(MockNestedAcceleratorDelegate); +}; + +class NestedAcceleratorTest : public aura::test::AuraTestBase { + public: + NestedAcceleratorTest() {} + virtual ~NestedAcceleratorTest() {} + + virtual void SetUp() OVERRIDE { + AuraTestBase::SetUp(); + delegate_ = new MockNestedAcceleratorDelegate(); + nested_accelerator_controller_.reset( + new NestedAcceleratorController(delegate_)); + aura::client::SetDispatcherClient(root_window(), + nested_accelerator_controller_.get()); + } + + virtual void TearDown() OVERRIDE { + aura::client::SetDispatcherClient(root_window(), NULL); + AuraTestBase::TearDown(); + delegate_ = NULL; + nested_accelerator_controller_.reset(); + } + + MockNestedAcceleratorDelegate* delegate() { return delegate_; } + + private: + scoped_ptr<NestedAcceleratorController> nested_accelerator_controller_; + MockNestedAcceleratorDelegate* delegate_; + + DISALLOW_COPY_AND_ASSIGN(NestedAcceleratorTest); +}; + +} // namespace + +// Aura window above lock screen in z order. +TEST_F(NestedAcceleratorTest, AssociatedWindowAboveLockScreen) { + // TODO(oshima|sadrul): remove when Win implements PES. + if (!ui::PlatformEventSource::GetInstance()) + return; + MockDispatcher inner_dispatcher; + scoped_ptr<aura::Window> mock_lock_container( + CreateNormalWindow(0, root_window(), NULL)); + aura::test::CreateTestWindowWithId(1, mock_lock_container.get()); + + scoped_ptr<aura::Window> associated_window( + CreateNormalWindow(2, root_window(), NULL)); + EXPECT_TRUE(aura::test::WindowIsAbove(associated_window.get(), + mock_lock_container.get())); + + DispatchKeyReleaseA(root_window()); + scoped_ptr<ui::ScopedEventDispatcher> override_dispatcher = + ui::PlatformEventSource::GetInstance()->OverrideDispatcher( + &inner_dispatcher); + aura::client::DispatcherRunLoop run_loop( + aura::client::GetDispatcherClient(root_window()), NULL); + run_loop.Run(); + EXPECT_EQ(1, inner_dispatcher.num_key_events_dispatched()); +} + +// Test that the nested dispatcher handles accelerators. +TEST_F(NestedAcceleratorTest, AcceleratorsHandled) { + // TODO(oshima|sadrul): remove when Win implements PES. + if (!ui::PlatformEventSource::GetInstance()) + return; + MockDispatcher inner_dispatcher; + ui::Accelerator accelerator(ui::VKEY_A, ui::EF_NONE); + accelerator.set_type(ui::ET_KEY_RELEASED); + TestTarget target; + delegate()->Register(accelerator, &target); + + DispatchKeyReleaseA(root_window()); + scoped_ptr<ui::ScopedEventDispatcher> override_dispatcher = + ui::PlatformEventSource::GetInstance()->OverrideDispatcher( + &inner_dispatcher); + aura::client::DispatcherRunLoop run_loop( + aura::client::GetDispatcherClient(root_window()), NULL); + run_loop.Run(); + EXPECT_EQ(0, inner_dispatcher.num_key_events_dispatched()); + EXPECT_EQ(1, target.accelerator_pressed_count()); +} + +} // namespace test +} // namespace wm diff --git a/chromium/ui/wm/core/nested_accelerator_delegate.h b/chromium/ui/wm/core/nested_accelerator_delegate.h new file mode 100644 index 00000000000..4b13c0d882b --- /dev/null +++ b/chromium/ui/wm/core/nested_accelerator_delegate.h @@ -0,0 +1,34 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_WM_CORE_NESTED_ACCELERATOR_DELEGATE_H_ +#define UI_WM_CORE_NESTED_ACCELERATOR_DELEGATE_H_ + +namespace ui { +class Accelerator; +} + +namespace wm { + +// A delegate interface that implements the behavior of nested accelerator +// handling. +class NestedAcceleratorDelegate { + public: + enum Result { + RESULT_PROCESSED, + RESULT_NOT_PROCESSED, + // The key event should be ignored now and instead be reposted so that + // next event loop. + RESULT_PROCESS_LATER, + }; + + virtual ~NestedAcceleratorDelegate() {} + + // Attempts to process the |accelerator|. + virtual Result ProcessAccelerator(const ui::Accelerator& accelerator) = 0; +}; + +} // namespace wm + +#endif // UI_WM_CORE_NESTED_ACCELERATOR_DELEGATE_H_ diff --git a/chromium/ui/wm/core/nested_accelerator_dispatcher.cc b/chromium/ui/wm/core/nested_accelerator_dispatcher.cc new file mode 100644 index 00000000000..d37c93c796f --- /dev/null +++ b/chromium/ui/wm/core/nested_accelerator_dispatcher.cc @@ -0,0 +1,21 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/nested_accelerator_dispatcher.h" + +#include "base/logging.h" +#include "ui/wm/core/nested_accelerator_delegate.h" + +namespace wm { + +NestedAcceleratorDispatcher::NestedAcceleratorDispatcher( + NestedAcceleratorDelegate* delegate) + : delegate_(delegate) { + DCHECK(delegate); +} + +NestedAcceleratorDispatcher::~NestedAcceleratorDispatcher() { +} + +} // namespace wm diff --git a/chromium/ui/wm/core/nested_accelerator_dispatcher.h b/chromium/ui/wm/core/nested_accelerator_dispatcher.h new file mode 100644 index 00000000000..df5dd0862d9 --- /dev/null +++ b/chromium/ui/wm/core/nested_accelerator_dispatcher.h @@ -0,0 +1,55 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_WM_CORE_NESTED_ACCELERATOR_DISPATCHER_H_ +#define UI_WM_CORE_NESTED_ACCELERATOR_DISPATCHER_H_ + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "ui/wm/wm_export.h" + +namespace base { +class MessagePumpDispatcher; +class RunLoop; +} + +namespace ui { +class KeyEvent; +} + +namespace wm { + +class NestedAcceleratorDelegate; + +// Dispatcher for handling accelerators from menu. +// +// Wraps a nested dispatcher to which control is passed if no accelerator key +// has been pressed. If the nested dispatcher is NULL, then the control is +// passed back to the default dispatcher. +// TODO(pkotwicz): Add support for a |nested_dispatcher| which sends +// events to a system IME. +class WM_EXPORT NestedAcceleratorDispatcher { + public: + virtual ~NestedAcceleratorDispatcher(); + + static scoped_ptr<NestedAcceleratorDispatcher> Create( + NestedAcceleratorDelegate* dispatcher_delegate, + base::MessagePumpDispatcher* nested_dispatcher); + + // Creates a base::RunLoop object to run a nested message loop. + virtual scoped_ptr<base::RunLoop> CreateRunLoop() = 0; + + protected: + explicit NestedAcceleratorDispatcher(NestedAcceleratorDelegate* delegate); + + NestedAcceleratorDelegate* + delegate_; // Owned by NestedAcceleratorController. + + private: + DISALLOW_COPY_AND_ASSIGN(NestedAcceleratorDispatcher); +}; + +} // namespace wm + +#endif // UI_WM_CORE_NESTED_ACCELERATOR_DISPATCHER_H_ diff --git a/chromium/ui/wm/core/nested_accelerator_dispatcher_linux.cc b/chromium/ui/wm/core/nested_accelerator_dispatcher_linux.cc new file mode 100644 index 00000000000..12340b66a7b --- /dev/null +++ b/chromium/ui/wm/core/nested_accelerator_dispatcher_linux.cc @@ -0,0 +1,104 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/nested_accelerator_dispatcher.h" + +#include "base/memory/scoped_ptr.h" +#include "base/run_loop.h" +#include "ui/base/accelerators/accelerator.h" +#include "ui/events/event.h" +#include "ui/events/platform/platform_event_dispatcher.h" +#include "ui/events/platform/platform_event_source.h" +#include "ui/events/platform/scoped_event_dispatcher.h" +#include "ui/wm/core/accelerator_filter.h" +#include "ui/wm/core/nested_accelerator_delegate.h" + +#if defined(USE_X11) +#include <X11/Xlib.h> +#endif + +namespace wm { + +namespace { + +#if defined(USE_OZONE) +bool IsKeyEvent(const base::NativeEvent& native_event) { + const ui::KeyEvent* event = static_cast<const ui::KeyEvent*>(native_event); + return event->IsKeyEvent(); +} +#elif defined(USE_X11) +bool IsKeyEvent(const XEvent* xev) { + return xev->type == KeyPress || xev->type == KeyRelease; +} +#else +#error Unknown build platform: you should have either use_ozone or use_x11. +#endif + +scoped_ptr<ui::ScopedEventDispatcher> OverrideDispatcher( + ui::PlatformEventDispatcher* dispatcher) { + ui::PlatformEventSource* source = ui::PlatformEventSource::GetInstance(); + return source ? source->OverrideDispatcher(dispatcher) + : scoped_ptr<ui::ScopedEventDispatcher>(); +} + +} // namespace + +class NestedAcceleratorDispatcherLinux : public NestedAcceleratorDispatcher, + public ui::PlatformEventDispatcher { + public: + explicit NestedAcceleratorDispatcherLinux(NestedAcceleratorDelegate* delegate) + : NestedAcceleratorDispatcher(delegate), + restore_dispatcher_(OverrideDispatcher(this)) {} + + virtual ~NestedAcceleratorDispatcherLinux() {} + + private: + // AcceleratorDispatcher: + virtual scoped_ptr<base::RunLoop> CreateRunLoop() OVERRIDE { + return scoped_ptr<base::RunLoop>(new base::RunLoop()); + } + + // ui::PlatformEventDispatcher: + virtual bool CanDispatchEvent(const ui::PlatformEvent& event) OVERRIDE { + return true; + } + + virtual uint32_t DispatchEvent(const ui::PlatformEvent& event) OVERRIDE { + if (IsKeyEvent(event)) { + ui::KeyEvent key_event(event, false); + ui::Accelerator accelerator = CreateAcceleratorFromKeyEvent(key_event); + + switch (delegate_->ProcessAccelerator(accelerator)) { + case NestedAcceleratorDelegate::RESULT_PROCESS_LATER: +#if defined(USE_X11) + XPutBackEvent(event->xany.display, event); +#else + NOTIMPLEMENTED(); +#endif + return ui::POST_DISPATCH_NONE; + case NestedAcceleratorDelegate::RESULT_PROCESSED: + return ui::POST_DISPATCH_NONE; + case NestedAcceleratorDelegate::RESULT_NOT_PROCESSED: + break; + } + } + ui::PlatformEventDispatcher* prev = *restore_dispatcher_; + + return prev ? prev->DispatchEvent(event) + : ui::POST_DISPATCH_PERFORM_DEFAULT; + } + + scoped_ptr<ui::ScopedEventDispatcher> restore_dispatcher_; + + DISALLOW_COPY_AND_ASSIGN(NestedAcceleratorDispatcherLinux); +}; + +scoped_ptr<NestedAcceleratorDispatcher> NestedAcceleratorDispatcher::Create( + NestedAcceleratorDelegate* delegate, + base::MessagePumpDispatcher* nested_dispatcher) { + return scoped_ptr<NestedAcceleratorDispatcher>( + new NestedAcceleratorDispatcherLinux(delegate)); +} + +} // namespace wm diff --git a/chromium/ui/wm/core/nested_accelerator_dispatcher_win.cc b/chromium/ui/wm/core/nested_accelerator_dispatcher_win.cc new file mode 100644 index 00000000000..a810bb9d88e --- /dev/null +++ b/chromium/ui/wm/core/nested_accelerator_dispatcher_win.cc @@ -0,0 +1,74 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/nested_accelerator_dispatcher.h" + +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_pump_dispatcher.h" +#include "base/run_loop.h" +#include "ui/base/accelerators/accelerator.h" +#include "ui/events/event.h" +#include "ui/wm/core/accelerator_filter.h" +#include "ui/wm/core/nested_accelerator_delegate.h" + +using base::MessagePumpDispatcher; + +namespace wm { + +namespace { + +bool IsKeyEvent(const MSG& msg) { + return msg.message == WM_KEYDOWN || msg.message == WM_SYSKEYDOWN || + msg.message == WM_KEYUP || msg.message == WM_SYSKEYUP; +} + +} // namespace + +class NestedAcceleratorDispatcherWin : public NestedAcceleratorDispatcher, + public MessagePumpDispatcher { + public: + NestedAcceleratorDispatcherWin(NestedAcceleratorDelegate* delegate, + MessagePumpDispatcher* nested) + : NestedAcceleratorDispatcher(delegate), nested_dispatcher_(nested) {} + virtual ~NestedAcceleratorDispatcherWin() {} + + private: + // NestedAcceleratorDispatcher: + virtual scoped_ptr<base::RunLoop> CreateRunLoop() OVERRIDE { + return scoped_ptr<base::RunLoop>(new base::RunLoop(this)); + } + + // MessagePumpDispatcher: + virtual uint32_t Dispatch(const MSG& event) OVERRIDE { + if (IsKeyEvent(event)) { + ui::KeyEvent key_event(event, false); + ui::Accelerator accelerator = CreateAcceleratorFromKeyEvent(key_event); + + switch (delegate_->ProcessAccelerator(accelerator)) { + case NestedAcceleratorDelegate::RESULT_PROCESS_LATER: + return POST_DISPATCH_QUIT_LOOP; + case NestedAcceleratorDelegate::RESULT_PROCESSED: + return POST_DISPATCH_NONE; + case NestedAcceleratorDelegate::RESULT_NOT_PROCESSED: + break; + } + } + + return nested_dispatcher_ ? nested_dispatcher_->Dispatch(event) + : POST_DISPATCH_PERFORM_DEFAULT; + } + + MessagePumpDispatcher* nested_dispatcher_; + + DISALLOW_COPY_AND_ASSIGN(NestedAcceleratorDispatcherWin); +}; + +scoped_ptr<NestedAcceleratorDispatcher> NestedAcceleratorDispatcher::Create( + NestedAcceleratorDelegate* delegate, + MessagePumpDispatcher* nested_dispatcher) { + return scoped_ptr<NestedAcceleratorDispatcher>( + new NestedAcceleratorDispatcherWin(delegate, nested_dispatcher)); +} + +} // namespace wm diff --git a/chromium/ui/wm/core/shadow.cc b/chromium/ui/wm/core/shadow.cc new file mode 100644 index 00000000000..1eb0ac6b4e3 --- /dev/null +++ b/chromium/ui/wm/core/shadow.cc @@ -0,0 +1,193 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/shadow.h" + +#include "grit/ui_resources.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/compositor/scoped_layer_animation_settings.h" +#include "ui/wm/core/image_grid.h" + +namespace { + +// Shadow opacity for different styles. +const float kActiveShadowOpacity = 1.0f; +const float kInactiveShadowOpacity = 0.2f; +const float kSmallShadowOpacity = 1.0f; + +// Interior inset for different styles. +const int kActiveInteriorInset = 0; +const int kInactiveInteriorInset = 0; +const int kSmallInteriorInset = 5; + +// Duration for opacity animation in milliseconds. +const int kShadowAnimationDurationMs = 100; + +float GetOpacityForStyle(wm::Shadow::Style style) { + switch (style) { + case wm::Shadow::STYLE_ACTIVE: + return kActiveShadowOpacity; + case wm::Shadow::STYLE_INACTIVE: + return kInactiveShadowOpacity; + case wm::Shadow::STYLE_SMALL: + return kSmallShadowOpacity; + } + return 1.0f; +} + +int GetInteriorInsetForStyle(wm::Shadow::Style style) { + switch (style) { + case wm::Shadow::STYLE_ACTIVE: + return kActiveInteriorInset; + case wm::Shadow::STYLE_INACTIVE: + return kInactiveInteriorInset; + case wm::Shadow::STYLE_SMALL: + return kSmallInteriorInset; + } + return 0; +} + +} // namespace + +namespace wm { + +Shadow::Shadow() : style_(STYLE_ACTIVE), interior_inset_(0) { +} + +Shadow::~Shadow() { +} + +void Shadow::Init(Style style) { + style_ = style; + image_grid_.reset(new ImageGrid); + UpdateImagesForStyle(); + image_grid_->layer()->set_name("Shadow"); + image_grid_->layer()->SetOpacity(GetOpacityForStyle(style_)); +} + +void Shadow::SetContentBounds(const gfx::Rect& content_bounds) { + content_bounds_ = content_bounds; + UpdateImageGridBounds(); +} + +ui::Layer* Shadow::layer() const { + return image_grid_->layer(); +} + +void Shadow::SetStyle(Style style) { + if (style_ == style) + return; + + Style old_style = style_; + style_ = style; + + // Stop waiting for any as yet unfinished implicit animations. + StopObservingImplicitAnimations(); + + // If we're switching to or from the small style, don't bother with + // animations. + if (style == STYLE_SMALL || old_style == STYLE_SMALL) { + UpdateImagesForStyle(); + image_grid_->layer()->SetOpacity(GetOpacityForStyle(style)); + return; + } + + // If we're becoming active, switch images now. Because the inactive image + // has a very low opacity the switch isn't noticeable and this approach + // allows us to use only a single set of shadow images at a time. + if (style == STYLE_ACTIVE) { + UpdateImagesForStyle(); + // Opacity was baked into inactive image, start opacity low to match. + image_grid_->layer()->SetOpacity(kInactiveShadowOpacity); + } + + { + // Property sets within this scope will be implicitly animated. + ui::ScopedLayerAnimationSettings settings(layer()->GetAnimator()); + settings.AddObserver(this); + settings.SetTransitionDuration( + base::TimeDelta::FromMilliseconds(kShadowAnimationDurationMs)); + switch (style_) { + case STYLE_ACTIVE: + image_grid_->layer()->SetOpacity(kActiveShadowOpacity); + break; + case STYLE_INACTIVE: + image_grid_->layer()->SetOpacity(kInactiveShadowOpacity); + break; + default: + NOTREACHED() << "Unhandled style " << style_; + break; + } + } +} + +void Shadow::OnImplicitAnimationsCompleted() { + // If we just finished going inactive, switch images. This doesn't cause + // a visual pop because the inactive image opacity is so low. + if (style_ == STYLE_INACTIVE) { + UpdateImagesForStyle(); + // Opacity is baked into inactive image, so set fully opaque. + image_grid_->layer()->SetOpacity(1.0f); + } +} + +void Shadow::UpdateImagesForStyle() { + ResourceBundle& res = ResourceBundle::GetSharedInstance(); + switch (style_) { + case STYLE_ACTIVE: + image_grid_->SetImages( + &res.GetImageNamed(IDR_AURA_SHADOW_ACTIVE_TOP_LEFT), + &res.GetImageNamed(IDR_AURA_SHADOW_ACTIVE_TOP), + &res.GetImageNamed(IDR_AURA_SHADOW_ACTIVE_TOP_RIGHT), + &res.GetImageNamed(IDR_AURA_SHADOW_ACTIVE_LEFT), + NULL, + &res.GetImageNamed(IDR_AURA_SHADOW_ACTIVE_RIGHT), + &res.GetImageNamed(IDR_AURA_SHADOW_ACTIVE_BOTTOM_LEFT), + &res.GetImageNamed(IDR_AURA_SHADOW_ACTIVE_BOTTOM), + &res.GetImageNamed(IDR_AURA_SHADOW_ACTIVE_BOTTOM_RIGHT)); + break; + case STYLE_INACTIVE: + image_grid_->SetImages( + &res.GetImageNamed(IDR_AURA_SHADOW_INACTIVE_TOP_LEFT), + &res.GetImageNamed(IDR_AURA_SHADOW_INACTIVE_TOP), + &res.GetImageNamed(IDR_AURA_SHADOW_INACTIVE_TOP_RIGHT), + &res.GetImageNamed(IDR_AURA_SHADOW_INACTIVE_LEFT), + NULL, + &res.GetImageNamed(IDR_AURA_SHADOW_INACTIVE_RIGHT), + &res.GetImageNamed(IDR_AURA_SHADOW_INACTIVE_BOTTOM_LEFT), + &res.GetImageNamed(IDR_AURA_SHADOW_INACTIVE_BOTTOM), + &res.GetImageNamed(IDR_AURA_SHADOW_INACTIVE_BOTTOM_RIGHT)); + break; + case STYLE_SMALL: + image_grid_->SetImages( + &res.GetImageNamed(IDR_WINDOW_BUBBLE_SHADOW_SMALL_TOP_LEFT), + &res.GetImageNamed(IDR_WINDOW_BUBBLE_SHADOW_SMALL_TOP), + &res.GetImageNamed(IDR_WINDOW_BUBBLE_SHADOW_SMALL_TOP_RIGHT), + &res.GetImageNamed(IDR_WINDOW_BUBBLE_SHADOW_SMALL_LEFT), + NULL, + &res.GetImageNamed(IDR_WINDOW_BUBBLE_SHADOW_SMALL_RIGHT), + &res.GetImageNamed(IDR_WINDOW_BUBBLE_SHADOW_SMALL_BOTTOM_LEFT), + &res.GetImageNamed(IDR_WINDOW_BUBBLE_SHADOW_SMALL_BOTTOM), + &res.GetImageNamed(IDR_WINDOW_BUBBLE_SHADOW_SMALL_BOTTOM_RIGHT)); + break; + default: + NOTREACHED() << "Unhandled style " << style_; + break; + } + + // Update interior inset for style. + interior_inset_ = GetInteriorInsetForStyle(style_); + + // Image sizes may have changed. + UpdateImageGridBounds(); +} + +void Shadow::UpdateImageGridBounds() { + // Update bounds based on content bounds and image sizes. + gfx::Rect image_grid_bounds = content_bounds_; + image_grid_bounds.Inset(interior_inset_, interior_inset_); + image_grid_->SetContentBounds(image_grid_bounds); +} + +} // namespace wm diff --git a/chromium/ui/wm/core/shadow.h b/chromium/ui/wm/core/shadow.h new file mode 100644 index 00000000000..c153ecf158e --- /dev/null +++ b/chromium/ui/wm/core/shadow.h @@ -0,0 +1,86 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_WM_CORE_SHADOW_H_ +#define UI_WM_CORE_SHADOW_H_ + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "ui/compositor/layer_animation_observer.h" +#include "ui/gfx/rect.h" +#include "ui/wm/wm_export.h" + +namespace ui { +class Layer; +} // namespace ui + +namespace wm { + +class ImageGrid; + +// Simple class that draws a drop shadow around content at given bounds. +class WM_EXPORT Shadow : public ui::ImplicitAnimationObserver { + public: + enum Style { + // Active windows have more opaque shadows, shifted down to make the window + // appear "higher". + STYLE_ACTIVE, + + // Inactive windows have less opaque shadows. + STYLE_INACTIVE, + + // Small windows like tooltips and context menus have lighter, smaller + // shadows. + STYLE_SMALL, + }; + + Shadow(); + virtual ~Shadow(); + + void Init(Style style); + + // Returns |image_grid_|'s ui::Layer. This is exposed so it can be added to + // the same layer as the content and stacked below it. SetContentBounds() + // should be used to adjust the shadow's size and position (rather than + // applying transformations to this layer). + ui::Layer* layer() const; + + const gfx::Rect& content_bounds() const { return content_bounds_; } + Style style() const { return style_; } + + // Moves and resizes |image_grid_| to frame |content_bounds|. + void SetContentBounds(const gfx::Rect& content_bounds); + + // Sets the shadow's style, animating opacity as necessary. + void SetStyle(Style style); + + // ui::ImplicitAnimationObserver overrides: + virtual void OnImplicitAnimationsCompleted() OVERRIDE; + + private: + // Updates the |image_grid_| images to the current |style_|. + void UpdateImagesForStyle(); + + // Updates the |image_grid_| bounds based on its image sizes and the + // current |content_bounds_|. + void UpdateImageGridBounds(); + + // The current style, set when the transition animation starts. + Style style_; + + scoped_ptr<ImageGrid> image_grid_; + + // Bounds of the content that the shadow encloses. + gfx::Rect content_bounds_; + + // The interior inset of the shadow images. The content bounds of the image + // grid should be set to |content_bounds_| inset by this amount. + int interior_inset_; + + DISALLOW_COPY_AND_ASSIGN(Shadow); +}; + +} // namespace wm + +#endif // UI_WM_CORE_SHADOW_H_ diff --git a/chromium/ui/wm/core/shadow_controller.cc b/chromium/ui/wm/core/shadow_controller.cc new file mode 100644 index 00000000000..c809389fadb --- /dev/null +++ b/chromium/ui/wm/core/shadow_controller.cc @@ -0,0 +1,272 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/shadow_controller.h" + +#include <utility> + +#include "base/command_line.h" +#include "base/logging.h" +#include "base/memory/linked_ptr.h" +#include "base/scoped_observer.h" +#include "ui/aura/env.h" +#include "ui/aura/env_observer.h" +#include "ui/aura/window.h" +#include "ui/aura/window_event_dispatcher.h" +#include "ui/aura/window_observer.h" +#include "ui/compositor/layer.h" +#include "ui/wm/core/shadow.h" +#include "ui/wm/core/shadow_types.h" +#include "ui/wm/core/window_util.h" +#include "ui/wm/public/activation_client.h" + +using std::make_pair; + +namespace wm { + +namespace { + +ShadowType GetShadowTypeFromWindow(aura::Window* window) { + switch (window->type()) { + case ui::wm::WINDOW_TYPE_NORMAL: + case ui::wm::WINDOW_TYPE_PANEL: + case ui::wm::WINDOW_TYPE_MENU: + case ui::wm::WINDOW_TYPE_TOOLTIP: + return SHADOW_TYPE_RECTANGULAR; + default: + break; + } + return SHADOW_TYPE_NONE; +} + +bool ShouldUseSmallShadowForWindow(aura::Window* window) { + switch (window->type()) { + case ui::wm::WINDOW_TYPE_MENU: + case ui::wm::WINDOW_TYPE_TOOLTIP: + return true; + default: + break; + } + return false; +} + +// Returns the shadow style to be applied to |losing_active| when it is losing +// active to |gaining_active|. |gaining_active| may be of a type that hides when +// inactive, and as such we do not want to render |losing_active| as inactive. +Shadow::Style GetShadowStyleForWindowLosingActive( + aura::Window* losing_active, + aura::Window* gaining_active) { + if (gaining_active && aura::client::GetHideOnDeactivate(gaining_active)) { + aura::Window::Windows::const_iterator it = + std::find(GetTransientChildren(losing_active).begin(), + GetTransientChildren(losing_active).end(), + gaining_active); + if (it != GetTransientChildren(losing_active).end()) + return Shadow::STYLE_ACTIVE; + } + return Shadow::STYLE_INACTIVE; +} + +} // namespace + +// ShadowController::Impl ------------------------------------------------------ + +// Real implementation of the ShadowController. ShadowController observes +// ActivationChangeObserver, which are per ActivationClient, where as there is +// only a single Impl (as it observes all window creation by way of an +// EnvObserver). +class ShadowController::Impl : + public aura::EnvObserver, + public aura::WindowObserver, + public base::RefCounted<Impl> { + public: + // Returns the singleton instance, destroyed when there are no more refs. + static Impl* GetInstance(); + + // aura::EnvObserver override: + virtual void OnWindowInitialized(aura::Window* window) OVERRIDE; + + // aura::WindowObserver overrides: + virtual void OnWindowPropertyChanged( + aura::Window* window, const void* key, intptr_t old) OVERRIDE; + virtual void OnWindowBoundsChanged( + aura::Window* window, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) OVERRIDE; + virtual void OnWindowDestroyed(aura::Window* window) OVERRIDE; + + private: + friend class base::RefCounted<Impl>; + friend class ShadowController; + friend class ShadowController::TestApi; + + typedef std::map<aura::Window*, linked_ptr<Shadow> > WindowShadowMap; + + Impl(); + virtual ~Impl(); + + // Forwarded from ShadowController. + void OnWindowActivated(aura::Window* gained_active, + aura::Window* lost_active); + + // Checks if |window| is visible and contains a property requesting a shadow. + bool ShouldShowShadowForWindow(aura::Window* window) const; + + // Returns |window|'s shadow from |window_shadows_|, or NULL if no shadow + // exists. + Shadow* GetShadowForWindow(aura::Window* window); + + // Updates the shadow styles for windows when activation changes. + void HandleWindowActivationChange(aura::Window* gaining_active, + aura::Window* losing_active); + + // Shows or hides |window|'s shadow as needed (creating the shadow if + // necessary). + void HandlePossibleShadowVisibilityChange(aura::Window* window); + + // Creates a new shadow for |window| and stores it in |window_shadows_|. The + // shadow's bounds are initialized and it is added to the window's layer. + void CreateShadowForWindow(aura::Window* window); + + WindowShadowMap window_shadows_; + + ScopedObserver<aura::Window, aura::WindowObserver> observer_manager_; + + static Impl* instance_; + + DISALLOW_COPY_AND_ASSIGN(Impl); +}; + +// static +ShadowController::Impl* ShadowController::Impl::instance_ = NULL; + +// static +ShadowController::Impl* ShadowController::Impl::GetInstance() { + if (!instance_) + instance_ = new Impl(); + return instance_; +} + +void ShadowController::Impl::OnWindowInitialized(aura::Window* window) { + observer_manager_.Add(window); + SetShadowType(window, GetShadowTypeFromWindow(window)); + HandlePossibleShadowVisibilityChange(window); +} + +void ShadowController::Impl::OnWindowPropertyChanged(aura::Window* window, + const void* key, + intptr_t old) { + if (key == kShadowTypeKey) { + HandlePossibleShadowVisibilityChange(window); + return; + } +} + +void ShadowController::Impl::OnWindowBoundsChanged( + aura::Window* window, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) { + Shadow* shadow = GetShadowForWindow(window); + if (shadow) + shadow->SetContentBounds(gfx::Rect(new_bounds.size())); +} + +void ShadowController::Impl::OnWindowDestroyed(aura::Window* window) { + window_shadows_.erase(window); + observer_manager_.Remove(window); +} + +void ShadowController::Impl::OnWindowActivated(aura::Window* gained_active, + aura::Window* lost_active) { + if (gained_active) { + Shadow* shadow = GetShadowForWindow(gained_active); + if (shadow && !ShouldUseSmallShadowForWindow(gained_active)) + shadow->SetStyle(Shadow::STYLE_ACTIVE); + } + if (lost_active) { + Shadow* shadow = GetShadowForWindow(lost_active); + if (shadow && !ShouldUseSmallShadowForWindow(lost_active)) { + shadow->SetStyle(GetShadowStyleForWindowLosingActive(lost_active, + gained_active)); + } + } +} + +bool ShadowController::Impl::ShouldShowShadowForWindow( + aura::Window* window) const { + const ShadowType type = GetShadowType(window); + switch (type) { + case SHADOW_TYPE_NONE: + return false; + case SHADOW_TYPE_RECTANGULAR: + return true; + default: + NOTREACHED() << "Unknown shadow type " << type; + return false; + } +} + +Shadow* ShadowController::Impl::GetShadowForWindow(aura::Window* window) { + WindowShadowMap::const_iterator it = window_shadows_.find(window); + return it != window_shadows_.end() ? it->second.get() : NULL; +} + +void ShadowController::Impl::HandlePossibleShadowVisibilityChange( + aura::Window* window) { + const bool should_show = ShouldShowShadowForWindow(window); + Shadow* shadow = GetShadowForWindow(window); + if (shadow) + shadow->layer()->SetVisible(should_show); + else if (should_show && !shadow) + CreateShadowForWindow(window); +} + +void ShadowController::Impl::CreateShadowForWindow(aura::Window* window) { + linked_ptr<Shadow> shadow(new Shadow()); + window_shadows_.insert(make_pair(window, shadow)); + + shadow->Init(ShouldUseSmallShadowForWindow(window) ? + Shadow::STYLE_SMALL : Shadow::STYLE_ACTIVE); + shadow->SetContentBounds(gfx::Rect(window->bounds().size())); + shadow->layer()->SetVisible(ShouldShowShadowForWindow(window)); + window->layer()->Add(shadow->layer()); +} + +ShadowController::Impl::Impl() + : observer_manager_(this) { + aura::Env::GetInstance()->AddObserver(this); +} + +ShadowController::Impl::~Impl() { + DCHECK_EQ(instance_, this); + aura::Env::GetInstance()->RemoveObserver(this); + instance_ = NULL; +} + +// ShadowController ------------------------------------------------------------ + +ShadowController::ShadowController( + aura::client::ActivationClient* activation_client) + : activation_client_(activation_client), + impl_(Impl::GetInstance()) { + // Watch for window activation changes. + activation_client_->AddObserver(this); +} + +ShadowController::~ShadowController() { + activation_client_->RemoveObserver(this); +} + +void ShadowController::OnWindowActivated(aura::Window* gained_active, + aura::Window* lost_active) { + impl_->OnWindowActivated(gained_active, lost_active); +} + +// ShadowController::TestApi --------------------------------------------------- + +Shadow* ShadowController::TestApi::GetShadowForWindow(aura::Window* window) { + return controller_->impl_->GetShadowForWindow(window); +} + +} // namespace wm diff --git a/chromium/ui/wm/core/shadow_controller.h b/chromium/ui/wm/core/shadow_controller.h new file mode 100644 index 00000000000..c84689453f3 --- /dev/null +++ b/chromium/ui/wm/core/shadow_controller.h @@ -0,0 +1,69 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_WM_CORE_SHADOW_CONTROLLER_H_ +#define UI_WM_CORE_SHADOW_CONTROLLER_H_ + +#include <map> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "ui/wm/public/activation_change_observer.h" +#include "ui/wm/wm_export.h" + +namespace aura { +class Window; +namespace client { +class ActivationClient; +} +} +namespace gfx { +class Rect; +} + +namespace wm { + +class Shadow; + +// ShadowController observes changes to windows and creates and updates drop +// shadows as needed. ShadowController itself is light weight and per +// ActivationClient. ShadowController delegates to its implementation class, +// which observes all window creation. +class WM_EXPORT ShadowController : + public aura::client::ActivationChangeObserver { + public: + class WM_EXPORT TestApi { + public: + explicit TestApi(ShadowController* controller) : controller_(controller) {} + ~TestApi() {} + + Shadow* GetShadowForWindow(aura::Window* window); + + private: + ShadowController* controller_; // not owned + + DISALLOW_COPY_AND_ASSIGN(TestApi); + }; + + explicit ShadowController(aura::client::ActivationClient* activation_client); + virtual ~ShadowController(); + + // aura::client::ActivationChangeObserver overrides: + virtual void OnWindowActivated(aura::Window* gained_active, + aura::Window* lost_active) OVERRIDE; + + private: + class Impl; + + aura::client::ActivationClient* activation_client_; + + scoped_refptr<Impl> impl_; + + DISALLOW_COPY_AND_ASSIGN(ShadowController); +}; + +} // namespace wm + +#endif // UI_WM_CORE_SHADOW_CONTROLLER_H_ diff --git a/chromium/ui/wm/core/shadow_controller_unittest.cc b/chromium/ui/wm/core/shadow_controller_unittest.cc new file mode 100644 index 00000000000..d82d015f1a0 --- /dev/null +++ b/chromium/ui/wm/core/shadow_controller_unittest.cc @@ -0,0 +1,219 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/shadow_controller.h" + +#include <algorithm> +#include <vector> + +#include "base/memory/scoped_ptr.h" +#include "ui/aura/client/window_tree_client.h" +#include "ui/aura/test/aura_test_base.h" +#include "ui/aura/window.h" +#include "ui/aura/window_event_dispatcher.h" +#include "ui/compositor/layer.h" +#include "ui/wm/core/default_activation_client.h" +#include "ui/wm/core/shadow.h" +#include "ui/wm/core/shadow_types.h" +#include "ui/wm/core/window_util.h" +#include "ui/wm/core/wm_state.h" +#include "ui/wm/public/activation_client.h" + +namespace wm { + +class ShadowControllerTest : public aura::test::AuraTestBase { + public: + ShadowControllerTest() {} + virtual ~ShadowControllerTest() {} + + virtual void SetUp() OVERRIDE { + wm_state_.reset(new wm::WMState); + AuraTestBase::SetUp(); + new wm::DefaultActivationClient(root_window()); + aura::client::ActivationClient* activation_client = + aura::client::GetActivationClient(root_window()); + shadow_controller_.reset(new ShadowController(activation_client)); + } + virtual void TearDown() OVERRIDE { + shadow_controller_.reset(); + AuraTestBase::TearDown(); + wm_state_.reset(); + } + + protected: + ShadowController* shadow_controller() { return shadow_controller_.get(); } + + void ActivateWindow(aura::Window* window) { + DCHECK(window); + DCHECK(window->GetRootWindow()); + aura::client::GetActivationClient(window->GetRootWindow())->ActivateWindow( + window); + } + + private: + scoped_ptr<ShadowController> shadow_controller_; + scoped_ptr<wm::WMState> wm_state_; + + DISALLOW_COPY_AND_ASSIGN(ShadowControllerTest); +}; + +// Tests that various methods in Window update the Shadow object as expected. +TEST_F(ShadowControllerTest, Shadow) { + scoped_ptr<aura::Window> window(new aura::Window(NULL)); + window->SetType(ui::wm::WINDOW_TYPE_NORMAL); + window->Init(aura::WINDOW_LAYER_TEXTURED); + ParentWindow(window.get()); + + // We should create the shadow before the window is visible (the shadow's + // layer won't get drawn yet since it's a child of the window's layer). + ShadowController::TestApi api(shadow_controller()); + const Shadow* shadow = api.GetShadowForWindow(window.get()); + ASSERT_TRUE(shadow != NULL); + EXPECT_TRUE(shadow->layer()->visible()); + + // The shadow should remain visible after window visibility changes. + window->Show(); + EXPECT_TRUE(shadow->layer()->visible()); + window->Hide(); + EXPECT_TRUE(shadow->layer()->visible()); + + // If the shadow is disabled, it should be hidden. + SetShadowType(window.get(), SHADOW_TYPE_NONE); + window->Show(); + EXPECT_FALSE(shadow->layer()->visible()); + SetShadowType(window.get(), SHADOW_TYPE_RECTANGULAR); + EXPECT_TRUE(shadow->layer()->visible()); + + // The shadow's layer should be a child of the window's layer. + EXPECT_EQ(window->layer(), shadow->layer()->parent()); + + window->parent()->RemoveChild(window.get()); + aura::Window* window_ptr = window.get(); + window.reset(); + EXPECT_TRUE(api.GetShadowForWindow(window_ptr) == NULL); +} + +// Tests that the window's shadow's bounds are updated correctly. +TEST_F(ShadowControllerTest, ShadowBounds) { + scoped_ptr<aura::Window> window(new aura::Window(NULL)); + window->SetType(ui::wm::WINDOW_TYPE_NORMAL); + window->Init(aura::WINDOW_LAYER_TEXTURED); + ParentWindow(window.get()); + window->Show(); + + const gfx::Rect kOldBounds(20, 30, 400, 300); + window->SetBounds(kOldBounds); + + // When the shadow is first created, it should use the window's size (but + // remain at the origin, since it's a child of the window's layer). + SetShadowType(window.get(), SHADOW_TYPE_RECTANGULAR); + ShadowController::TestApi api(shadow_controller()); + const Shadow* shadow = api.GetShadowForWindow(window.get()); + ASSERT_TRUE(shadow != NULL); + EXPECT_EQ(gfx::Rect(kOldBounds.size()).ToString(), + shadow->content_bounds().ToString()); + + // When we change the window's bounds, the shadow's should be updated too. + gfx::Rect kNewBounds(50, 60, 500, 400); + window->SetBounds(kNewBounds); + EXPECT_EQ(gfx::Rect(kNewBounds.size()).ToString(), + shadow->content_bounds().ToString()); +} + +// Tests that activating a window changes the shadow style. +TEST_F(ShadowControllerTest, ShadowStyle) { + ShadowController::TestApi api(shadow_controller()); + + scoped_ptr<aura::Window> window1(new aura::Window(NULL)); + window1->SetType(ui::wm::WINDOW_TYPE_NORMAL); + window1->Init(aura::WINDOW_LAYER_TEXTURED); + ParentWindow(window1.get()); + window1->SetBounds(gfx::Rect(10, 20, 300, 400)); + window1->Show(); + ActivateWindow(window1.get()); + + // window1 is active, so style should have active appearance. + Shadow* shadow1 = api.GetShadowForWindow(window1.get()); + ASSERT_TRUE(shadow1 != NULL); + EXPECT_EQ(Shadow::STYLE_ACTIVE, shadow1->style()); + + // Create another window and activate it. + scoped_ptr<aura::Window> window2(new aura::Window(NULL)); + window2->SetType(ui::wm::WINDOW_TYPE_NORMAL); + window2->Init(aura::WINDOW_LAYER_TEXTURED); + ParentWindow(window2.get()); + window2->SetBounds(gfx::Rect(11, 21, 301, 401)); + window2->Show(); + ActivateWindow(window2.get()); + + // window1 is now inactive, so shadow should go inactive. + Shadow* shadow2 = api.GetShadowForWindow(window2.get()); + ASSERT_TRUE(shadow2 != NULL); + EXPECT_EQ(Shadow::STYLE_INACTIVE, shadow1->style()); + EXPECT_EQ(Shadow::STYLE_ACTIVE, shadow2->style()); +} + +// Tests that we use smaller shadows for tooltips and menus. +TEST_F(ShadowControllerTest, SmallShadowsForTooltipsAndMenus) { + ShadowController::TestApi api(shadow_controller()); + + scoped_ptr<aura::Window> tooltip_window(new aura::Window(NULL)); + tooltip_window->SetType(ui::wm::WINDOW_TYPE_TOOLTIP); + tooltip_window->Init(aura::WINDOW_LAYER_TEXTURED); + ParentWindow(tooltip_window.get()); + tooltip_window->SetBounds(gfx::Rect(10, 20, 300, 400)); + tooltip_window->Show(); + + Shadow* tooltip_shadow = api.GetShadowForWindow(tooltip_window.get()); + ASSERT_TRUE(tooltip_shadow != NULL); + EXPECT_EQ(Shadow::STYLE_SMALL, tooltip_shadow->style()); + + scoped_ptr<aura::Window> menu_window(new aura::Window(NULL)); + menu_window->SetType(ui::wm::WINDOW_TYPE_MENU); + menu_window->Init(aura::WINDOW_LAYER_TEXTURED); + ParentWindow(menu_window.get()); + menu_window->SetBounds(gfx::Rect(10, 20, 300, 400)); + menu_window->Show(); + + Shadow* menu_shadow = api.GetShadowForWindow(tooltip_window.get()); + ASSERT_TRUE(menu_shadow != NULL); + EXPECT_EQ(Shadow::STYLE_SMALL, menu_shadow->style()); +} + +// http://crbug.com/120210 - transient parents of certain types of transients +// should not lose their shadow when they lose activation to the transient. +TEST_F(ShadowControllerTest, TransientParentKeepsActiveShadow) { + ShadowController::TestApi api(shadow_controller()); + + scoped_ptr<aura::Window> window1(new aura::Window(NULL)); + window1->SetType(ui::wm::WINDOW_TYPE_NORMAL); + window1->Init(aura::WINDOW_LAYER_TEXTURED); + ParentWindow(window1.get()); + window1->SetBounds(gfx::Rect(10, 20, 300, 400)); + window1->Show(); + ActivateWindow(window1.get()); + + // window1 is active, so style should have active appearance. + Shadow* shadow1 = api.GetShadowForWindow(window1.get()); + ASSERT_TRUE(shadow1 != NULL); + EXPECT_EQ(Shadow::STYLE_ACTIVE, shadow1->style()); + + // Create a window that is transient to window1, and that has the 'hide on + // deactivate' property set. Upon activation, window1 should still have an + // active shadow. + scoped_ptr<aura::Window> window2(new aura::Window(NULL)); + window2->SetType(ui::wm::WINDOW_TYPE_NORMAL); + window2->Init(aura::WINDOW_LAYER_TEXTURED); + ParentWindow(window2.get()); + window2->SetBounds(gfx::Rect(11, 21, 301, 401)); + AddTransientChild(window1.get(), window2.get()); + aura::client::SetHideOnDeactivate(window2.get(), true); + window2->Show(); + ActivateWindow(window2.get()); + + // window1 is now inactive, but its shadow should still appear active. + EXPECT_EQ(Shadow::STYLE_ACTIVE, shadow1->style()); +} + +} // namespace wm diff --git a/chromium/ui/wm/core/shadow_types.cc b/chromium/ui/wm/core/shadow_types.cc new file mode 100644 index 00000000000..a7afe89b50d --- /dev/null +++ b/chromium/ui/wm/core/shadow_types.cc @@ -0,0 +1,23 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/shadow_types.h" + +#include "ui/aura/window_property.h" + +DECLARE_WINDOW_PROPERTY_TYPE(wm::ShadowType); + +namespace wm { + +void SetShadowType(aura::Window* window, ShadowType shadow_type) { + window->SetProperty(kShadowTypeKey, shadow_type); +} + +ShadowType GetShadowType(aura::Window* window) { + return window->GetProperty(kShadowTypeKey); +} + +DEFINE_WINDOW_PROPERTY_KEY(ShadowType, kShadowTypeKey, SHADOW_TYPE_NONE); + +} // namespace wm diff --git a/chromium/ui/wm/core/shadow_types.h b/chromium/ui/wm/core/shadow_types.h new file mode 100644 index 00000000000..1198a43b16c --- /dev/null +++ b/chromium/ui/wm/core/shadow_types.h @@ -0,0 +1,34 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_WM_CORE_SHADOW_TYPES_H_ +#define UI_WM_CORE_SHADOW_TYPES_H_ + +#include "ui/aura/window.h" +#include "ui/wm/wm_export.h" + +namespace aura { +class Window; +} + +namespace wm { + +// Different types of drop shadows that can be drawn under a window by the +// shell. Used as a value for the kShadowTypeKey property. +enum ShadowType { + // Starts at 0 due to the cast in GetShadowType(). + SHADOW_TYPE_NONE = 0, + SHADOW_TYPE_RECTANGULAR, +}; + +WM_EXPORT void SetShadowType(aura::Window* window, ShadowType shadow_type); +WM_EXPORT ShadowType GetShadowType(aura::Window* window); + +// A property key describing the drop shadow that should be displayed under the +// window. If unset, no shadow is displayed. +extern const aura::WindowProperty<ShadowType>* const kShadowTypeKey; + +} // namespace wm + +#endif // UI_WM_CORE_SHADOW_TYPES_H_ diff --git a/chromium/ui/wm/core/transient_window_controller.cc b/chromium/ui/wm/core/transient_window_controller.cc new file mode 100644 index 00000000000..bb1945c09b2 --- /dev/null +++ b/chromium/ui/wm/core/transient_window_controller.cc @@ -0,0 +1,40 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/transient_window_controller.h" + +#include "ui/wm/core/transient_window_manager.h" + +namespace wm { + +TransientWindowController::TransientWindowController() { +} + +TransientWindowController::~TransientWindowController() { +} + +void TransientWindowController::AddTransientChild(aura::Window* parent, + aura::Window* child) { + TransientWindowManager::Get(parent)->AddTransientChild(child); +} + +void TransientWindowController::RemoveTransientChild(aura::Window* parent, + aura::Window* child) { + TransientWindowManager::Get(parent)->RemoveTransientChild(child); +} + +aura::Window* TransientWindowController::GetTransientParent( + aura::Window* window) { + return const_cast<aura::Window*>(GetTransientParent( + const_cast<const aura::Window*>(window))); +} + +const aura::Window* TransientWindowController::GetTransientParent( + const aura::Window* window) { + const TransientWindowManager* window_manager = + TransientWindowManager::Get(window); + return window_manager ? window_manager->transient_parent() : NULL; +} + +} // namespace wm diff --git a/chromium/ui/wm/core/transient_window_controller.h b/chromium/ui/wm/core/transient_window_controller.h new file mode 100644 index 00000000000..8638824ae36 --- /dev/null +++ b/chromium/ui/wm/core/transient_window_controller.h @@ -0,0 +1,36 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_WM_CORE_TRANSIENT_WINDOW_CONTROLLER_H_ +#define UI_WM_CORE_TRANSIENT_WINDOW_CONTROLLER_H_ + +#include "ui/wm/public/transient_window_client.h" +#include "ui/wm/wm_export.h" + +namespace wm { + +// TransientWindowClient implementation. Uses TransientWindowManager to handle +// tracking transient per window. +class WM_EXPORT TransientWindowController + : public aura::client::TransientWindowClient { + public: + TransientWindowController(); + virtual ~TransientWindowController(); + + // TransientWindowClient: + virtual void AddTransientChild(aura::Window* parent, + aura::Window* child) OVERRIDE; + virtual void RemoveTransientChild(aura::Window* parent, + aura::Window* child) OVERRIDE; + virtual aura::Window* GetTransientParent(aura::Window* window) OVERRIDE; + virtual const aura::Window* GetTransientParent( + const aura::Window* window) OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(TransientWindowController); +}; + +} // namespace wm + +#endif // UI_WM_CORE_TRANSIENT_WINDOW_CONTROLLER_H_ diff --git a/chromium/ui/wm/core/transient_window_manager.cc b/chromium/ui/wm/core/transient_window_manager.cc new file mode 100644 index 00000000000..120af84786c --- /dev/null +++ b/chromium/ui/wm/core/transient_window_manager.cc @@ -0,0 +1,182 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/transient_window_manager.h" + +#include <algorithm> +#include <functional> + +#include "base/auto_reset.h" +#include "base/stl_util.h" +#include "ui/aura/window.h" +#include "ui/aura/window_property.h" +#include "ui/wm/core/transient_window_observer.h" +#include "ui/wm/core/transient_window_stacking_client.h" +#include "ui/wm/core/window_util.h" + +using aura::Window; + +namespace wm { + +DEFINE_OWNED_WINDOW_PROPERTY_KEY(TransientWindowManager, kPropertyKey, NULL); + +TransientWindowManager::~TransientWindowManager() { +} + +// static +TransientWindowManager* TransientWindowManager::Get(Window* window) { + TransientWindowManager* manager = window->GetProperty(kPropertyKey); + if (!manager) { + manager = new TransientWindowManager(window); + window->SetProperty(kPropertyKey, manager); + } + return manager; +} + +// static +const TransientWindowManager* TransientWindowManager::Get( + const Window* window) { + return window->GetProperty(kPropertyKey); +} + +void TransientWindowManager::AddObserver(TransientWindowObserver* observer) { + observers_.AddObserver(observer); +} + +void TransientWindowManager::RemoveObserver(TransientWindowObserver* observer) { + observers_.RemoveObserver(observer); +} + +void TransientWindowManager::AddTransientChild(Window* child) { + // TransientWindowStackingClient does the stacking of transient windows. If it + // isn't installed stacking is going to be wrong. + DCHECK(TransientWindowStackingClient::instance_); + + TransientWindowManager* child_manager = Get(child); + if (child_manager->transient_parent_) + Get(child_manager->transient_parent_)->RemoveTransientChild(child); + DCHECK(std::find(transient_children_.begin(), transient_children_.end(), + child) == transient_children_.end()); + transient_children_.push_back(child); + child_manager->transient_parent_ = window_; + + // Restack |child| properly above its transient parent, if they share the same + // parent. + if (child->parent() == window_->parent()) + RestackTransientDescendants(); + + FOR_EACH_OBSERVER(TransientWindowObserver, observers_, + OnTransientChildAdded(window_, child)); +} + +void TransientWindowManager::RemoveTransientChild(Window* child) { + Windows::iterator i = + std::find(transient_children_.begin(), transient_children_.end(), child); + DCHECK(i != transient_children_.end()); + transient_children_.erase(i); + TransientWindowManager* child_manager = Get(child); + DCHECK_EQ(window_, child_manager->transient_parent_); + child_manager->transient_parent_ = NULL; + + // If |child| and its former transient parent share the same parent, |child| + // should be restacked properly so it is not among transient children of its + // former parent, anymore. + if (window_->parent() == child->parent()) + RestackTransientDescendants(); + + FOR_EACH_OBSERVER(TransientWindowObserver, observers_, + OnTransientChildRemoved(window_, child)); +} + +bool TransientWindowManager::IsStackingTransient( + const aura::Window* target) const { + return stacking_target_ == target; +} + +TransientWindowManager::TransientWindowManager(Window* window) + : window_(window), + transient_parent_(NULL), + stacking_target_(NULL) { + window_->AddObserver(this); +} + +void TransientWindowManager::RestackTransientDescendants() { + Window* parent = window_->parent(); + if (!parent) + return; + + // Stack any transient children that share the same parent to be in front of + // |window_|. The existing stacking order is preserved by iterating backwards + // and always stacking on top. + Window::Windows children(parent->children()); + for (Window::Windows::reverse_iterator it = children.rbegin(); + it != children.rend(); ++it) { + if ((*it) != window_ && HasTransientAncestor(*it, window_)) { + TransientWindowManager* descendant_manager = Get(*it); + base::AutoReset<Window*> resetter( + &descendant_manager->stacking_target_, + window_); + parent->StackChildAbove((*it), window_); + } + } +} + +void TransientWindowManager::OnWindowParentChanged(aura::Window* window, + aura::Window* parent) { + DCHECK_EQ(window_, window); + // Stack |window| properly if it is transient child of a sibling. + Window* transient_parent = wm::GetTransientParent(window); + if (transient_parent && transient_parent->parent() == parent) { + TransientWindowManager* transient_parent_manager = + Get(transient_parent); + transient_parent_manager->RestackTransientDescendants(); + } +} + +void TransientWindowManager::OnWindowVisibilityChanging(Window* window, + bool visible) { + // TODO(sky): move handling of becoming visible here. + if (!visible) { + std::for_each(transient_children_.begin(), transient_children_.end(), + std::mem_fun(&Window::Hide)); + } +} + +void TransientWindowManager::OnWindowStackingChanged(Window* window) { + DCHECK_EQ(window_, window); + + // Do nothing if we initiated the stacking change. + const TransientWindowManager* transient_manager = + Get(static_cast<const Window*>(window)); + if (transient_manager && transient_manager->stacking_target_) { + Windows::const_iterator window_i = std::find( + window->parent()->children().begin(), + window->parent()->children().end(), + window); + DCHECK(window_i != window->parent()->children().end()); + if (window_i != window->parent()->children().begin() && + (*(window_i - 1) == transient_manager->stacking_target_)) + return; + } + + RestackTransientDescendants(); +} + +void TransientWindowManager::OnWindowDestroying(Window* window) { + // Removes ourselves from our transient parent (if it hasn't been done by the + // RootWindow). + if (transient_parent_) { + TransientWindowManager::Get(transient_parent_)->RemoveTransientChild( + window_); + } + + // Destroy transient children, only after we've removed ourselves from our + // parent, as destroying an active transient child may otherwise attempt to + // refocus us. + Windows transient_children(transient_children_); + STLDeleteElements(&transient_children); + DCHECK(transient_children_.empty()); +} + +} // namespace wm diff --git a/chromium/ui/wm/core/transient_window_manager.h b/chromium/ui/wm/core/transient_window_manager.h new file mode 100644 index 00000000000..7d37679f924 --- /dev/null +++ b/chromium/ui/wm/core/transient_window_manager.h @@ -0,0 +1,91 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_WM_CORE_TRANSIENT_WINDOW_MANAGER_H_ +#define UI_WM_CORE_TRANSIENT_WINDOW_MANAGER_H_ + +#include <vector> + +#include "base/observer_list.h" +#include "ui/aura/window_observer.h" +#include "ui/wm/wm_export.h" + +namespace wm { + +class TransientWindowObserver; + +// TransientWindowManager manages the set of transient children for a window +// along with the transient parent. Transient children get the following +// behavior: +// . The transient parent destroys any transient children when it is +// destroyed. This means a transient child is destroyed if either its parent +// or transient parent is destroyed. +// . If a transient child and its transient parent share the same parent, then +// transient children are always ordered above the transient parent. +// Transient windows are typically used for popups and menus. +// TODO(sky): when we nuke TransientWindowClient rename this to +// TransientWindowController. +class WM_EXPORT TransientWindowManager : public aura::WindowObserver { + public: + typedef std::vector<aura::Window*> Windows; + + virtual ~TransientWindowManager(); + + // Returns the TransientWindowManager for |window|. This never returns NULL. + static TransientWindowManager* Get(aura::Window* window); + + // Returns the TransientWindowManager for |window| only if it already exists. + // WARNING: this may return NULL. + static const TransientWindowManager* Get(const aura::Window* window); + + void AddObserver(TransientWindowObserver* observer); + void RemoveObserver(TransientWindowObserver* observer); + + // Adds or removes a transient child. + void AddTransientChild(aura::Window* child); + void RemoveTransientChild(aura::Window* child); + + const Windows& transient_children() const { return transient_children_; } + + aura::Window* transient_parent() { return transient_parent_; } + const aura::Window* transient_parent() const { return transient_parent_; } + + // Returns true if in the process of stacking |window_| on top of |target|. + // That is, when the stacking order of a window changes + // (OnWindowStackingChanged()) the transients may get restacked as well. This + // function can be used to detect if TransientWindowManager is in the process + // of stacking a transient as the result of window stacking changing. + bool IsStackingTransient(const aura::Window* target) const; + + private: + explicit TransientWindowManager(aura::Window* window); + + // Stacks transient descendants of this window that are its siblings just + // above it. + void RestackTransientDescendants(); + + // WindowObserver: + virtual void OnWindowParentChanged(aura::Window* window, + aura::Window* parent) OVERRIDE; + virtual void OnWindowVisibilityChanging(aura::Window* window, + bool visible) OVERRIDE; + virtual void OnWindowStackingChanged(aura::Window* window) OVERRIDE; + virtual void OnWindowDestroying(aura::Window* window) OVERRIDE; + + aura::Window* window_; + aura::Window* transient_parent_; + Windows transient_children_; + + // If non-null we're actively restacking transient as the result of a + // transient ancestor changing. + aura::Window* stacking_target_; + + ObserverList<TransientWindowObserver> observers_; + + DISALLOW_COPY_AND_ASSIGN(TransientWindowManager); +}; + +} // namespace wm + +#endif // UI_WM_CORE_TRANSIENT_WINDOW_MANAGER_H_ diff --git a/chromium/ui/wm/core/transient_window_manager_unittest.cc b/chromium/ui/wm/core/transient_window_manager_unittest.cc new file mode 100644 index 00000000000..05b8e88e4de --- /dev/null +++ b/chromium/ui/wm/core/transient_window_manager_unittest.cc @@ -0,0 +1,666 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/transient_window_manager.h" + +#include "ui/aura/client/visibility_client.h" +#include "ui/aura/client/window_tree_client.h" +#include "ui/aura/layout_manager.h" +#include "ui/aura/test/aura_test_base.h" +#include "ui/aura/test/test_windows.h" +#include "ui/aura/window.h" +#include "ui/wm/core/transient_window_observer.h" +#include "ui/wm/core/window_util.h" +#include "ui/wm/core/wm_state.h" + +using aura::Window; + +using aura::test::ChildWindowIDsAsString; +using aura::test::CreateTestWindowWithId; + +namespace wm { + +class TestTransientWindowObserver : public TransientWindowObserver { + public: + TestTransientWindowObserver() : add_count_(0), remove_count_(0) { + } + + virtual ~TestTransientWindowObserver() { + } + + int add_count() const { return add_count_; } + int remove_count() const { return remove_count_; } + + // TransientWindowObserver overrides: + virtual void OnTransientChildAdded(Window* window, + Window* transient) OVERRIDE { + add_count_++; + } + virtual void OnTransientChildRemoved(Window* window, + Window* transient) OVERRIDE { + remove_count_++; + } + + private: + int add_count_; + int remove_count_; + + DISALLOW_COPY_AND_ASSIGN(TestTransientWindowObserver); +}; + +class TransientWindowManagerTest : public aura::test::AuraTestBase { + public: + TransientWindowManagerTest() {} + virtual ~TransientWindowManagerTest() {} + + virtual void SetUp() OVERRIDE { + AuraTestBase::SetUp(); + wm_state_.reset(new wm::WMState); + } + + virtual void TearDown() OVERRIDE { + wm_state_.reset(); + AuraTestBase::TearDown(); + } + + protected: + // Creates a transient window that is transient to |parent|. + Window* CreateTransientChild(int id, Window* parent) { + Window* window = new Window(NULL); + window->set_id(id); + window->SetType(ui::wm::WINDOW_TYPE_NORMAL); + window->Init(aura::WINDOW_LAYER_TEXTURED); + AddTransientChild(parent, window); + aura::client::ParentWindowWithContext(window, root_window(), gfx::Rect()); + return window; + } + + private: + scoped_ptr<wm::WMState> wm_state_; + + DISALLOW_COPY_AND_ASSIGN(TransientWindowManagerTest); +}; + +// Various assertions for transient children. +TEST_F(TransientWindowManagerTest, TransientChildren) { + scoped_ptr<Window> parent(CreateTestWindowWithId(0, root_window())); + scoped_ptr<Window> w1(CreateTestWindowWithId(1, parent.get())); + scoped_ptr<Window> w3(CreateTestWindowWithId(3, parent.get())); + Window* w2 = CreateTestWindowWithId(2, parent.get()); + // w2 is now owned by w1. + AddTransientChild(w1.get(), w2); + // Stack w1 at the top (end), this should force w2 to be last (on top of w1). + parent->StackChildAtTop(w1.get()); + ASSERT_EQ(3u, parent->children().size()); + EXPECT_EQ(w2, parent->children().back()); + + // Destroy w1, which should also destroy w3 (since it's a transient child). + w1.reset(); + w2 = NULL; + ASSERT_EQ(1u, parent->children().size()); + EXPECT_EQ(w3.get(), parent->children()[0]); + + w1.reset(CreateTestWindowWithId(4, parent.get())); + w2 = CreateTestWindowWithId(5, w3.get()); + AddTransientChild(w1.get(), w2); + parent->StackChildAtTop(w3.get()); + // Stack w1 at the top (end), this shouldn't affect w2 since it has a + // different parent. + parent->StackChildAtTop(w1.get()); + ASSERT_EQ(2u, parent->children().size()); + EXPECT_EQ(w3.get(), parent->children()[0]); + EXPECT_EQ(w1.get(), parent->children()[1]); + + // Hiding parent should hide transient children. + EXPECT_TRUE(w2->IsVisible()); + w1->Hide(); + EXPECT_FALSE(w2->IsVisible()); +} + +// Tests that transient children are stacked as a unit when using stack above. +TEST_F(TransientWindowManagerTest, TransientChildrenGroupAbove) { + scoped_ptr<Window> parent(CreateTestWindowWithId(0, root_window())); + scoped_ptr<Window> w1(CreateTestWindowWithId(1, parent.get())); + Window* w11 = CreateTestWindowWithId(11, parent.get()); + scoped_ptr<Window> w2(CreateTestWindowWithId(2, parent.get())); + Window* w21 = CreateTestWindowWithId(21, parent.get()); + Window* w211 = CreateTestWindowWithId(211, parent.get()); + Window* w212 = CreateTestWindowWithId(212, parent.get()); + Window* w213 = CreateTestWindowWithId(213, parent.get()); + Window* w22 = CreateTestWindowWithId(22, parent.get()); + ASSERT_EQ(8u, parent->children().size()); + + // w11 is now owned by w1. + AddTransientChild(w1.get(), w11); + // w21 is now owned by w2. + AddTransientChild(w2.get(), w21); + // w22 is now owned by w2. + AddTransientChild(w2.get(), w22); + // w211 is now owned by w21. + AddTransientChild(w21, w211); + // w212 is now owned by w21. + AddTransientChild(w21, w212); + // w213 is now owned by w21. + AddTransientChild(w21, w213); + EXPECT_EQ("1 11 2 21 211 212 213 22", ChildWindowIDsAsString(parent.get())); + + // Stack w1 at the top (end), this should force w11 to be last (on top of w1). + parent->StackChildAtTop(w1.get()); + EXPECT_EQ(w11, parent->children().back()); + EXPECT_EQ("2 21 211 212 213 22 1 11", ChildWindowIDsAsString(parent.get())); + + // This tests that the order in children_ array rather than in + // transient_children_ array is used when reinserting transient children. + // If transient_children_ array was used '22' would be following '21'. + parent->StackChildAtTop(w2.get()); + EXPECT_EQ(w22, parent->children().back()); + EXPECT_EQ("1 11 2 21 211 212 213 22", ChildWindowIDsAsString(parent.get())); + + parent->StackChildAbove(w11, w2.get()); + EXPECT_EQ(w11, parent->children().back()); + EXPECT_EQ("2 21 211 212 213 22 1 11", ChildWindowIDsAsString(parent.get())); + + parent->StackChildAbove(w21, w1.get()); + EXPECT_EQ(w22, parent->children().back()); + EXPECT_EQ("1 11 2 21 211 212 213 22", ChildWindowIDsAsString(parent.get())); + + parent->StackChildAbove(w21, w22); + EXPECT_EQ(w213, parent->children().back()); + EXPECT_EQ("1 11 2 22 21 211 212 213", ChildWindowIDsAsString(parent.get())); + + parent->StackChildAbove(w11, w21); + EXPECT_EQ(w11, parent->children().back()); + EXPECT_EQ("2 22 21 211 212 213 1 11", ChildWindowIDsAsString(parent.get())); + + parent->StackChildAbove(w213, w21); + EXPECT_EQ(w11, parent->children().back()); + EXPECT_EQ("2 22 21 213 211 212 1 11", ChildWindowIDsAsString(parent.get())); + + // No change when stacking a transient parent above its transient child. + parent->StackChildAbove(w21, w211); + EXPECT_EQ(w11, parent->children().back()); + EXPECT_EQ("2 22 21 213 211 212 1 11", ChildWindowIDsAsString(parent.get())); + + // This tests that the order in children_ array rather than in + // transient_children_ array is used when reinserting transient children. + // If transient_children_ array was used '22' would be following '21'. + parent->StackChildAbove(w2.get(), w1.get()); + EXPECT_EQ(w212, parent->children().back()); + EXPECT_EQ("1 11 2 22 21 213 211 212", ChildWindowIDsAsString(parent.get())); + + parent->StackChildAbove(w11, w213); + EXPECT_EQ(w11, parent->children().back()); + EXPECT_EQ("2 22 21 213 211 212 1 11", ChildWindowIDsAsString(parent.get())); +} + +// Tests that transient children are stacked as a unit when using stack below. +TEST_F(TransientWindowManagerTest, TransientChildrenGroupBelow) { + scoped_ptr<Window> parent(CreateTestWindowWithId(0, root_window())); + scoped_ptr<Window> w1(CreateTestWindowWithId(1, parent.get())); + Window* w11 = CreateTestWindowWithId(11, parent.get()); + scoped_ptr<Window> w2(CreateTestWindowWithId(2, parent.get())); + Window* w21 = CreateTestWindowWithId(21, parent.get()); + Window* w211 = CreateTestWindowWithId(211, parent.get()); + Window* w212 = CreateTestWindowWithId(212, parent.get()); + Window* w213 = CreateTestWindowWithId(213, parent.get()); + Window* w22 = CreateTestWindowWithId(22, parent.get()); + ASSERT_EQ(8u, parent->children().size()); + + // w11 is now owned by w1. + AddTransientChild(w1.get(), w11); + // w21 is now owned by w2. + AddTransientChild(w2.get(), w21); + // w22 is now owned by w2. + AddTransientChild(w2.get(), w22); + // w211 is now owned by w21. + AddTransientChild(w21, w211); + // w212 is now owned by w21. + AddTransientChild(w21, w212); + // w213 is now owned by w21. + AddTransientChild(w21, w213); + EXPECT_EQ("1 11 2 21 211 212 213 22", ChildWindowIDsAsString(parent.get())); + + // Stack w2 at the bottom, this should force w11 to be last (on top of w1). + // This also tests that the order in children_ array rather than in + // transient_children_ array is used when reinserting transient children. + // If transient_children_ array was used '22' would be following '21'. + parent->StackChildAtBottom(w2.get()); + EXPECT_EQ(w11, parent->children().back()); + EXPECT_EQ("2 21 211 212 213 22 1 11", ChildWindowIDsAsString(parent.get())); + + parent->StackChildAtBottom(w1.get()); + EXPECT_EQ(w22, parent->children().back()); + EXPECT_EQ("1 11 2 21 211 212 213 22", ChildWindowIDsAsString(parent.get())); + + parent->StackChildBelow(w21, w1.get()); + EXPECT_EQ(w11, parent->children().back()); + EXPECT_EQ("2 21 211 212 213 22 1 11", ChildWindowIDsAsString(parent.get())); + + parent->StackChildBelow(w11, w2.get()); + EXPECT_EQ(w22, parent->children().back()); + EXPECT_EQ("1 11 2 21 211 212 213 22", ChildWindowIDsAsString(parent.get())); + + parent->StackChildBelow(w22, w21); + EXPECT_EQ(w213, parent->children().back()); + EXPECT_EQ("1 11 2 22 21 211 212 213", ChildWindowIDsAsString(parent.get())); + + parent->StackChildBelow(w21, w11); + EXPECT_EQ(w11, parent->children().back()); + EXPECT_EQ("2 22 21 211 212 213 1 11", ChildWindowIDsAsString(parent.get())); + + parent->StackChildBelow(w213, w211); + EXPECT_EQ(w11, parent->children().back()); + EXPECT_EQ("2 22 21 213 211 212 1 11", ChildWindowIDsAsString(parent.get())); + + // No change when stacking a transient parent below its transient child. + parent->StackChildBelow(w21, w211); + EXPECT_EQ(w11, parent->children().back()); + EXPECT_EQ("2 22 21 213 211 212 1 11", ChildWindowIDsAsString(parent.get())); + + parent->StackChildBelow(w1.get(), w2.get()); + EXPECT_EQ(w212, parent->children().back()); + EXPECT_EQ("1 11 2 22 21 213 211 212", ChildWindowIDsAsString(parent.get())); + + parent->StackChildBelow(w213, w11); + EXPECT_EQ(w11, parent->children().back()); + EXPECT_EQ("2 22 21 213 211 212 1 11", ChildWindowIDsAsString(parent.get())); +} + +// Tests that transient windows are stacked properly when created. +TEST_F(TransientWindowManagerTest, StackUponCreation) { + scoped_ptr<Window> window0(CreateTestWindowWithId(0, root_window())); + scoped_ptr<Window> window1(CreateTestWindowWithId(1, root_window())); + + scoped_ptr<Window> window2(CreateTransientChild(2, window0.get())); + EXPECT_EQ("0 2 1", ChildWindowIDsAsString(root_window())); +} + +// Tests that windows are restacked properly after a call to AddTransientChild() +// or RemoveTransientChild(). +TEST_F(TransientWindowManagerTest, RestackUponAddOrRemoveTransientChild) { + scoped_ptr<Window> windows[4]; + for (int i = 0; i < 4; i++) + windows[i].reset(CreateTestWindowWithId(i, root_window())); + EXPECT_EQ("0 1 2 3", ChildWindowIDsAsString(root_window())); + + AddTransientChild(windows[0].get(), windows[2].get()); + EXPECT_EQ("0 2 1 3", ChildWindowIDsAsString(root_window())); + + AddTransientChild(windows[0].get(), windows[3].get()); + EXPECT_EQ("0 2 3 1", ChildWindowIDsAsString(root_window())); + + RemoveTransientChild(windows[0].get(), windows[2].get()); + EXPECT_EQ("0 3 2 1", ChildWindowIDsAsString(root_window())); + + RemoveTransientChild(windows[0].get(), windows[3].get()); + EXPECT_EQ("0 3 2 1", ChildWindowIDsAsString(root_window())); +} + +namespace { + +// Used by NotifyDelegateAfterDeletingTransients. Adds a string to a vector when +// OnWindowDestroyed() is invoked so that destruction order can be verified. +class DestroyedTrackingDelegate : public aura::test::TestWindowDelegate { + public: + explicit DestroyedTrackingDelegate(const std::string& name, + std::vector<std::string>* results) + : name_(name), + results_(results) {} + + virtual void OnWindowDestroyed(aura::Window* window) OVERRIDE { + results_->push_back(name_); + } + + private: + const std::string name_; + std::vector<std::string>* results_; + + DISALLOW_COPY_AND_ASSIGN(DestroyedTrackingDelegate); +}; + +} // namespace + +// Verifies the delegate is notified of destruction after transients are +// destroyed. +TEST_F(TransientWindowManagerTest, NotifyDelegateAfterDeletingTransients) { + std::vector<std::string> destruction_order; + + DestroyedTrackingDelegate parent_delegate("parent", &destruction_order); + scoped_ptr<Window> parent(new Window(&parent_delegate)); + parent->Init(aura::WINDOW_LAYER_NOT_DRAWN); + + DestroyedTrackingDelegate transient_delegate("transient", &destruction_order); + Window* transient = new Window(&transient_delegate); // Owned by |parent|. + transient->Init(aura::WINDOW_LAYER_NOT_DRAWN); + AddTransientChild(parent.get(), transient); + parent.reset(); + + ASSERT_EQ(2u, destruction_order.size()); + EXPECT_EQ("transient", destruction_order[0]); + EXPECT_EQ("parent", destruction_order[1]); +} + +TEST_F(TransientWindowManagerTest, StackTransientsWhoseLayersHaveNoDelegate) { + // Create a window with several transients, then a couple windows on top. + scoped_ptr<Window> window1(CreateTestWindowWithId(1, root_window())); + scoped_ptr<Window> window11(CreateTransientChild(11, window1.get())); + scoped_ptr<Window> window12(CreateTransientChild(12, window1.get())); + scoped_ptr<Window> window13(CreateTransientChild(13, window1.get())); + scoped_ptr<Window> window2(CreateTestWindowWithId(2, root_window())); + scoped_ptr<Window> window3(CreateTestWindowWithId(3, root_window())); + + EXPECT_EQ("1 11 12 13 2 3", ChildWindowIDsAsString(root_window())); + + // Remove the delegates of a couple of transients, as if they are closing + // and animating out. + window11->layer()->set_delegate(NULL); + window13->layer()->set_delegate(NULL); + + // Move window1 to the front. All transients should move with it, and their + // order should be preserved. + root_window()->StackChildAtTop(window1.get()); + + EXPECT_EQ("2 3 1 11 12 13", ChildWindowIDsAsString(root_window())); +} + +TEST_F(TransientWindowManagerTest, + StackTransientsLayersRelativeToOtherTransients) { + // Create a window with several transients, then a couple windows on top. + scoped_ptr<Window> window1(CreateTestWindowWithId(1, root_window())); + scoped_ptr<Window> window11(CreateTransientChild(11, window1.get())); + scoped_ptr<Window> window12(CreateTransientChild(12, window1.get())); + scoped_ptr<Window> window13(CreateTransientChild(13, window1.get())); + + EXPECT_EQ("1 11 12 13", ChildWindowIDsAsString(root_window())); + + // Stack 11 above 12. + root_window()->StackChildAbove(window11.get(), window12.get()); + EXPECT_EQ("1 12 11 13", ChildWindowIDsAsString(root_window())); + + // Stack 13 below 12. + root_window()->StackChildBelow(window13.get(), window12.get()); + EXPECT_EQ("1 13 12 11", ChildWindowIDsAsString(root_window())); + + // Stack 11 above 1. + root_window()->StackChildAbove(window11.get(), window1.get()); + EXPECT_EQ("1 11 13 12", ChildWindowIDsAsString(root_window())); + + // Stack 12 below 13. + root_window()->StackChildBelow(window12.get(), window13.get()); + EXPECT_EQ("1 11 12 13", ChildWindowIDsAsString(root_window())); +} + +TEST_F(TransientWindowManagerTest, + StackTransientsLayersRelativeToOtherTransientsNoLayerDelegate) { + // Create a window with several transients, then a couple windows on top. + scoped_ptr<Window> window1(CreateTestWindowWithId(1, root_window())); + scoped_ptr<Window> window11(CreateTransientChild(11, window1.get())); + scoped_ptr<Window> window12(CreateTransientChild(12, window1.get())); + scoped_ptr<Window> window13(CreateTransientChild(13, window1.get())); + scoped_ptr<Window> window2(CreateTestWindowWithId(2, root_window())); + scoped_ptr<Window> window3(CreateTestWindowWithId(3, root_window())); + + EXPECT_EQ("1 11 12 13 2 3", ChildWindowIDsAsString(root_window())); + + window1->layer()->set_delegate(NULL); + + // Stack 1 at top. + root_window()->StackChildAtTop(window1.get()); + EXPECT_EQ("2 3 1 11 12 13", ChildWindowIDsAsString(root_window())); +} + +class StackingMadrigalLayoutManager : public aura::LayoutManager { + public: + explicit StackingMadrigalLayoutManager(Window* root_window) + : root_window_(root_window) { + root_window_->SetLayoutManager(this); + } + virtual ~StackingMadrigalLayoutManager() { + } + + private: + // Overridden from LayoutManager: + virtual void OnWindowResized() OVERRIDE {} + virtual void OnWindowAddedToLayout(Window* child) OVERRIDE {} + virtual void OnWillRemoveWindowFromLayout(Window* child) OVERRIDE {} + virtual void OnWindowRemovedFromLayout(Window* child) OVERRIDE {} + virtual void OnChildWindowVisibilityChanged(Window* child, + bool visible) OVERRIDE { + Window::Windows::const_iterator it = root_window_->children().begin(); + Window* last_window = NULL; + for (; it != root_window_->children().end(); ++it) { + if (*it == child && last_window) { + if (!visible) + root_window_->StackChildAbove(last_window, *it); + else + root_window_->StackChildAbove(*it, last_window); + break; + } + last_window = *it; + } + } + virtual void SetChildBounds(Window* child, + const gfx::Rect& requested_bounds) OVERRIDE { + SetChildBoundsDirect(child, requested_bounds); + } + + Window* root_window_; + + DISALLOW_COPY_AND_ASSIGN(StackingMadrigalLayoutManager); +}; + +class StackingMadrigalVisibilityClient : public aura::client::VisibilityClient { + public: + explicit StackingMadrigalVisibilityClient(Window* root_window) + : ignored_window_(NULL) { + aura::client::SetVisibilityClient(root_window, this); + } + virtual ~StackingMadrigalVisibilityClient() { + } + + void set_ignored_window(Window* ignored_window) { + ignored_window_ = ignored_window; + } + + private: + // Overridden from client::VisibilityClient: + virtual void UpdateLayerVisibility(Window* window, bool visible) OVERRIDE { + if (!visible) { + if (window == ignored_window_) + window->layer()->set_delegate(NULL); + else + window->layer()->SetVisible(visible); + } else { + window->layer()->SetVisible(visible); + } + } + + Window* ignored_window_; + + DISALLOW_COPY_AND_ASSIGN(StackingMadrigalVisibilityClient); +}; + +// This test attempts to reconstruct a circumstance that can happen when the +// aura client attempts to manipulate the visibility and delegate of a layer +// independent of window visibility. +// A use case is where the client attempts to keep a window visible onscreen +// even after code has called Hide() on the window. The use case for this would +// be that window hides are animated (e.g. the window fades out). To prevent +// spurious updating the client code may also clear window's layer's delegate, +// so that the window cannot attempt to paint or update it further. The window +// uses the presence of a NULL layer delegate as a signal in stacking to note +// that the window is being manipulated by such a use case and its stacking +// should not be adjusted. +// One issue that can arise when a window opens two transient children, and the +// first is hidden. Subsequent attempts to activate the transient parent can +// result in the transient parent being stacked above the second transient +// child. A fix is made to Window::StackAbove to prevent this, and this test +// verifies this fix. +TEST_F(TransientWindowManagerTest, StackingMadrigal) { + new StackingMadrigalLayoutManager(root_window()); + StackingMadrigalVisibilityClient visibility_client(root_window()); + + scoped_ptr<Window> window1(CreateTestWindowWithId(1, root_window())); + scoped_ptr<Window> window11(CreateTransientChild(11, window1.get())); + + visibility_client.set_ignored_window(window11.get()); + + window11->Show(); + window11->Hide(); + + // As a transient, window11 should still be stacked above window1, even when + // hidden. + EXPECT_TRUE(aura::test::WindowIsAbove(window11.get(), window1.get())); + EXPECT_TRUE(aura::test::LayerIsAbove(window11.get(), window1.get())); + + // A new transient should still be above window1. It will appear behind + // window11 because we don't stack windows on top of targets with NULL + // delegates. + scoped_ptr<Window> window12(CreateTransientChild(12, window1.get())); + window12->Show(); + + EXPECT_TRUE(aura::test::WindowIsAbove(window12.get(), window1.get())); + EXPECT_TRUE(aura::test::LayerIsAbove(window12.get(), window1.get())); + + // In earlier versions of the StackChildAbove() method, attempting to stack + // window1 above window12 at this point would actually restack the layers + // resulting in window12's layer being below window1's layer (though the + // windows themselves would still be correctly stacked, so events would pass + // through.) + root_window()->StackChildAbove(window1.get(), window12.get()); + + // Both window12 and its layer should be stacked above window1. + EXPECT_TRUE(aura::test::WindowIsAbove(window12.get(), window1.get())); + EXPECT_TRUE(aura::test::LayerIsAbove(window12.get(), window1.get())); +} + +// Test for an issue where attempting to stack a primary window on top of a +// transient with a NULL layer delegate causes that primary window to be moved, +// but the layer order not changed to match. http://crbug.com/112562 +TEST_F(TransientWindowManagerTest, StackOverClosingTransient) { + scoped_ptr<Window> window1(CreateTestWindowWithId(1, root_window())); + scoped_ptr<Window> transient1(CreateTransientChild(11, window1.get())); + scoped_ptr<Window> window2(CreateTestWindowWithId(2, root_window())); + scoped_ptr<Window> transient2(CreateTransientChild(21, window2.get())); + + // Both windows and layers are stacked in creation order. + Window* root = root_window(); + ASSERT_EQ(4u, root->children().size()); + EXPECT_EQ(root->children()[0], window1.get()); + EXPECT_EQ(root->children()[1], transient1.get()); + EXPECT_EQ(root->children()[2], window2.get()); + EXPECT_EQ(root->children()[3], transient2.get()); + ASSERT_EQ(4u, root->layer()->children().size()); + EXPECT_EQ(root->layer()->children()[0], window1->layer()); + EXPECT_EQ(root->layer()->children()[1], transient1->layer()); + EXPECT_EQ(root->layer()->children()[2], window2->layer()); + EXPECT_EQ(root->layer()->children()[3], transient2->layer()); + EXPECT_EQ("1 11 2 21", ChildWindowIDsAsString(root_window())); + + // This brings window1 and its transient to the front. + root->StackChildAtTop(window1.get()); + EXPECT_EQ("2 21 1 11", ChildWindowIDsAsString(root_window())); + + EXPECT_EQ(root->children()[0], window2.get()); + EXPECT_EQ(root->children()[1], transient2.get()); + EXPECT_EQ(root->children()[2], window1.get()); + EXPECT_EQ(root->children()[3], transient1.get()); + EXPECT_EQ(root->layer()->children()[0], window2->layer()); + EXPECT_EQ(root->layer()->children()[1], transient2->layer()); + EXPECT_EQ(root->layer()->children()[2], window1->layer()); + EXPECT_EQ(root->layer()->children()[3], transient1->layer()); + + // Pretend we're closing the top-most transient, then bring window2 to the + // front. This mimics activating a browser window while the status bubble + // is fading out. The transient should stay topmost. + transient1->layer()->set_delegate(NULL); + root->StackChildAtTop(window2.get()); + + EXPECT_EQ(root->children()[0], window1.get()); + EXPECT_EQ(root->children()[1], window2.get()); + EXPECT_EQ(root->children()[2], transient2.get()); + EXPECT_EQ(root->children()[3], transient1.get()); + EXPECT_EQ(root->layer()->children()[0], window1->layer()); + EXPECT_EQ(root->layer()->children()[1], window2->layer()); + EXPECT_EQ(root->layer()->children()[2], transient2->layer()); + EXPECT_EQ(root->layer()->children()[3], transient1->layer()); + + // Close the transient. Remaining windows are stable. + transient1.reset(); + + ASSERT_EQ(3u, root->children().size()); + EXPECT_EQ(root->children()[0], window1.get()); + EXPECT_EQ(root->children()[1], window2.get()); + EXPECT_EQ(root->children()[2], transient2.get()); + ASSERT_EQ(3u, root->layer()->children().size()); + EXPECT_EQ(root->layer()->children()[0], window1->layer()); + EXPECT_EQ(root->layer()->children()[1], window2->layer()); + EXPECT_EQ(root->layer()->children()[2], transient2->layer()); + + // Open another window on top. + scoped_ptr<Window> window3(CreateTestWindowWithId(3, root_window())); + + ASSERT_EQ(4u, root->children().size()); + EXPECT_EQ(root->children()[0], window1.get()); + EXPECT_EQ(root->children()[1], window2.get()); + EXPECT_EQ(root->children()[2], transient2.get()); + EXPECT_EQ(root->children()[3], window3.get()); + ASSERT_EQ(4u, root->layer()->children().size()); + EXPECT_EQ(root->layer()->children()[0], window1->layer()); + EXPECT_EQ(root->layer()->children()[1], window2->layer()); + EXPECT_EQ(root->layer()->children()[2], transient2->layer()); + EXPECT_EQ(root->layer()->children()[3], window3->layer()); + + // Pretend we're closing the topmost non-transient window, then bring + // window2 to the top. It should not move. + window3->layer()->set_delegate(NULL); + root->StackChildAtTop(window2.get()); + + ASSERT_EQ(4u, root->children().size()); + EXPECT_EQ(root->children()[0], window1.get()); + EXPECT_EQ(root->children()[1], window2.get()); + EXPECT_EQ(root->children()[2], transient2.get()); + EXPECT_EQ(root->children()[3], window3.get()); + ASSERT_EQ(4u, root->layer()->children().size()); + EXPECT_EQ(root->layer()->children()[0], window1->layer()); + EXPECT_EQ(root->layer()->children()[1], window2->layer()); + EXPECT_EQ(root->layer()->children()[2], transient2->layer()); + EXPECT_EQ(root->layer()->children()[3], window3->layer()); + + // Bring window1 to the top. It should move ahead of window2, but not + // ahead of window3 (with NULL delegate). + root->StackChildAtTop(window1.get()); + + ASSERT_EQ(4u, root->children().size()); + EXPECT_EQ(root->children()[0], window2.get()); + EXPECT_EQ(root->children()[1], transient2.get()); + EXPECT_EQ(root->children()[2], window1.get()); + EXPECT_EQ(root->children()[3], window3.get()); + ASSERT_EQ(4u, root->layer()->children().size()); + EXPECT_EQ(root->layer()->children()[0], window2->layer()); + EXPECT_EQ(root->layer()->children()[1], transient2->layer()); + EXPECT_EQ(root->layer()->children()[2], window1->layer()); + EXPECT_EQ(root->layer()->children()[3], window3->layer()); +} + +// Verifies TransientWindowObserver is notified appropriately. +TEST_F(TransientWindowManagerTest, TransientWindowObserverNotified) { + scoped_ptr<Window> parent(CreateTestWindowWithId(0, root_window())); + scoped_ptr<Window> w1(CreateTestWindowWithId(1, parent.get())); + + TestTransientWindowObserver test_observer; + TransientWindowManager::Get(parent.get())->AddObserver(&test_observer); + + AddTransientChild(parent.get(), w1.get()); + EXPECT_EQ(1, test_observer.add_count()); + EXPECT_EQ(0, test_observer.remove_count()); + + RemoveTransientChild(parent.get(), w1.get()); + EXPECT_EQ(1, test_observer.add_count()); + EXPECT_EQ(1, test_observer.remove_count()); + + TransientWindowManager::Get(parent.get())->RemoveObserver(&test_observer); +} + +} // namespace wm diff --git a/chromium/ui/wm/core/transient_window_observer.h b/chromium/ui/wm/core/transient_window_observer.h new file mode 100644 index 00000000000..aff5af5e2ef --- /dev/null +++ b/chromium/ui/wm/core/transient_window_observer.h @@ -0,0 +1,32 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_WM_CORE_TRANSIENT_WINDOW_OBSERVER_H_ +#define UI_WM_CORE_TRANSIENT_WINDOW_OBSERVER_H_ + +#include "ui/wm/wm_export.h" + +namespace aura { +class Window; +} + +namespace wm { + +class WM_EXPORT TransientWindowObserver { + public: + // Called when a transient child is added to |window|. + virtual void OnTransientChildAdded(aura::Window* window, + aura::Window* transient) = 0; + + // Called when a transient child is removed from |window|. + virtual void OnTransientChildRemoved(aura::Window* window, + aura::Window* transient) = 0; + + protected: + virtual ~TransientWindowObserver() {} +}; + +} // namespace wm + +#endif // UI_WM_CORE_TRANSIENT_WINDOW_OBSERVER_H_ diff --git a/chromium/ui/wm/core/transient_window_stacking_client.cc b/chromium/ui/wm/core/transient_window_stacking_client.cc new file mode 100644 index 00000000000..f8dc42c51f0 --- /dev/null +++ b/chromium/ui/wm/core/transient_window_stacking_client.cc @@ -0,0 +1,131 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/transient_window_stacking_client.h" + +#include <algorithm> + +#include "ui/wm/core/transient_window_manager.h" +#include "ui/wm/core/window_util.h" + +using aura::Window; + +namespace wm { + +namespace { + +// Populates |ancestors| with all transient ancestors of |window| that are +// siblings of |window|. Returns true if any ancestors were found, false if not. +bool GetAllTransientAncestors(Window* window, Window::Windows* ancestors) { + Window* parent = window->parent(); + for (; window; window = GetTransientParent(window)) { + if (window->parent() == parent) + ancestors->push_back(window); + } + return (!ancestors->empty()); +} + +// Replaces |window1| and |window2| with their possible transient ancestors that +// are still siblings (have a common transient parent). |window1| and |window2| +// are not modified if such ancestors cannot be found. +void FindCommonTransientAncestor(Window** window1, Window** window2) { + DCHECK(window1); + DCHECK(window2); + DCHECK(*window1); + DCHECK(*window2); + // Assemble chains of ancestors of both windows. + Window::Windows ancestors1; + Window::Windows ancestors2; + if (!GetAllTransientAncestors(*window1, &ancestors1) || + !GetAllTransientAncestors(*window2, &ancestors2)) { + return; + } + // Walk the two chains backwards and look for the first difference. + Window::Windows::reverse_iterator it1 = ancestors1.rbegin(); + Window::Windows::reverse_iterator it2 = ancestors2.rbegin(); + for (; it1 != ancestors1.rend() && it2 != ancestors2.rend(); ++it1, ++it2) { + if (*it1 != *it2) { + *window1 = *it1; + *window2 = *it2; + break; + } + } +} + +// Adjusts |target| so that we don't attempt to stack on top of a window with a +// NULL delegate. +void SkipNullDelegates(Window::StackDirection direction, Window** target) { + const Window::Windows& children((*target)->parent()->children()); + size_t target_i = + std::find(children.begin(), children.end(), *target) - + children.begin(); + + // By convention we don't stack on top of windows with layers with NULL + // delegates. Walk backward to find a valid target window. See tests + // TransientWindowManagerTest.StackingMadrigal and StackOverClosingTransient + // for an explanation of this. + while (target_i > 0) { + const size_t index = direction == Window::STACK_ABOVE ? + target_i : target_i - 1; + if (!children[index]->layer() || + children[index]->layer()->delegate() != NULL) + break; + --target_i; + } + *target = children[target_i]; +} + +} // namespace + +// static +TransientWindowStackingClient* TransientWindowStackingClient::instance_ = NULL; + +TransientWindowStackingClient::TransientWindowStackingClient() { + instance_ = this; +} + +TransientWindowStackingClient::~TransientWindowStackingClient() { + if (instance_ == this) + instance_ = NULL; +} + +bool TransientWindowStackingClient::AdjustStacking( + Window** child, + Window** target, + Window::StackDirection* direction) { + const TransientWindowManager* transient_manager = + TransientWindowManager::Get(static_cast<const Window*>(*child)); + if (transient_manager && transient_manager->IsStackingTransient(*target)) + return true; + + // For windows that have transient children stack the transient ancestors that + // are siblings. This prevents one transient group from being inserted in the + // middle of another. + FindCommonTransientAncestor(child, target); + + // When stacking above skip to the topmost transient descendant of the target. + if (*direction == Window::STACK_ABOVE && + !HasTransientAncestor(*child, *target)) { + const Window::Windows& siblings((*child)->parent()->children()); + size_t target_i = + std::find(siblings.begin(), siblings.end(), *target) - siblings.begin(); + while (target_i + 1 < siblings.size() && + HasTransientAncestor(siblings[target_i + 1], *target)) { + ++target_i; + } + *target = siblings[target_i]; + } + + SkipNullDelegates(*direction, target); + + // If we couldn't find a valid target position, don't move anything. + if (*direction == Window::STACK_ABOVE && + ((*target)->layer() && (*target)->layer()->delegate() == NULL)) { + return false; + } + + return *child != *target; +} + +} // namespace wm diff --git a/chromium/ui/wm/core/transient_window_stacking_client.h b/chromium/ui/wm/core/transient_window_stacking_client.h new file mode 100644 index 00000000000..4ab4aea1742 --- /dev/null +++ b/chromium/ui/wm/core/transient_window_stacking_client.h @@ -0,0 +1,37 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_WM_CORE_TRANSIENT_WINDOW_STACKING_CLIENT_H_ +#define UI_WM_CORE_TRANSIENT_WINDOW_STACKING_CLIENT_H_ + +#include "ui/aura/client/window_stacking_client.h" +#include "ui/wm/wm_export.h" + +namespace wm { + +class TransientWindowManager; + +class WM_EXPORT TransientWindowStackingClient + : public aura::client::WindowStackingClient { + public: + TransientWindowStackingClient(); + virtual ~TransientWindowStackingClient(); + + // WindowStackingClient: + virtual bool AdjustStacking(aura::Window** child, + aura::Window** target, + aura::Window::StackDirection* direction) OVERRIDE; + + private: + // Purely for DCHECKs. + friend class TransientWindowManager; + + static TransientWindowStackingClient* instance_; + + DISALLOW_COPY_AND_ASSIGN(TransientWindowStackingClient); +}; + +} // namespace wm + +#endif // UI_WM_CORE_TRANSIENT_WINDOW_STACKING_CLIENT_H_ diff --git a/chromium/ui/wm/core/transient_window_stacking_client_unittest.cc b/chromium/ui/wm/core/transient_window_stacking_client_unittest.cc new file mode 100644 index 00000000000..c5d3bd05319 --- /dev/null +++ b/chromium/ui/wm/core/transient_window_stacking_client_unittest.cc @@ -0,0 +1,215 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/transient_window_stacking_client.h" + +#include "base/memory/scoped_ptr.h" +#include "ui/aura/test/aura_test_base.h" +#include "ui/aura/test/test_windows.h" +#include "ui/compositor/test/test_layers.h" +#include "ui/wm/core/window_util.h" + +using aura::test::ChildWindowIDsAsString; +using aura::test::CreateTestWindowWithId; +using aura::Window; + +namespace wm { + +class TransientWindowStackingClientTest : public aura::test::AuraTestBase { + public: + TransientWindowStackingClientTest() {} + virtual ~TransientWindowStackingClientTest() {} + + virtual void SetUp() OVERRIDE { + AuraTestBase::SetUp(); + client_.reset(new TransientWindowStackingClient); + aura::client::SetWindowStackingClient(client_.get()); + } + + virtual void TearDown() OVERRIDE { + aura::client::SetWindowStackingClient(NULL); + AuraTestBase::TearDown(); + } + + private: + scoped_ptr<TransientWindowStackingClient> client_; + DISALLOW_COPY_AND_ASSIGN(TransientWindowStackingClientTest); +}; + +// Tests that transient children are stacked as a unit when using stack above. +TEST_F(TransientWindowStackingClientTest, TransientChildrenGroupAbove) { + scoped_ptr<Window> parent(CreateTestWindowWithId(0, root_window())); + scoped_ptr<Window> w1(CreateTestWindowWithId(1, parent.get())); + Window* w11 = CreateTestWindowWithId(11, parent.get()); + scoped_ptr<Window> w2(CreateTestWindowWithId(2, parent.get())); + Window* w21 = CreateTestWindowWithId(21, parent.get()); + Window* w211 = CreateTestWindowWithId(211, parent.get()); + Window* w212 = CreateTestWindowWithId(212, parent.get()); + Window* w213 = CreateTestWindowWithId(213, parent.get()); + Window* w22 = CreateTestWindowWithId(22, parent.get()); + ASSERT_EQ(8u, parent->children().size()); + + AddTransientChild(w1.get(), w11); // w11 is now owned by w1. + AddTransientChild(w2.get(), w21); // w21 is now owned by w2. + AddTransientChild(w2.get(), w22); // w22 is now owned by w2. + AddTransientChild(w21, w211); // w211 is now owned by w21. + AddTransientChild(w21, w212); // w212 is now owned by w21. + AddTransientChild(w21, w213); // w213 is now owned by w21. + EXPECT_EQ("1 11 2 21 211 212 213 22", ChildWindowIDsAsString(parent.get())); + + // Stack w1 at the top (end), this should force w11 to be last (on top of w1). + parent->StackChildAtTop(w1.get()); + EXPECT_EQ(w11, parent->children().back()); + EXPECT_EQ("2 21 211 212 213 22 1 11", ChildWindowIDsAsString(parent.get())); + + // This tests that the order in children_ array rather than in + // transient_children_ array is used when reinserting transient children. + // If transient_children_ array was used '22' would be following '21'. + parent->StackChildAtTop(w2.get()); + EXPECT_EQ(w22, parent->children().back()); + EXPECT_EQ("1 11 2 21 211 212 213 22", ChildWindowIDsAsString(parent.get())); + + parent->StackChildAbove(w11, w2.get()); + EXPECT_EQ(w11, parent->children().back()); + EXPECT_EQ("2 21 211 212 213 22 1 11", ChildWindowIDsAsString(parent.get())); + + parent->StackChildAbove(w21, w1.get()); + EXPECT_EQ(w22, parent->children().back()); + EXPECT_EQ("1 11 2 21 211 212 213 22", ChildWindowIDsAsString(parent.get())); + + parent->StackChildAbove(w21, w22); + EXPECT_EQ(w213, parent->children().back()); + EXPECT_EQ("1 11 2 22 21 211 212 213", ChildWindowIDsAsString(parent.get())); + + parent->StackChildAbove(w11, w21); + EXPECT_EQ(w11, parent->children().back()); + EXPECT_EQ("2 22 21 211 212 213 1 11", ChildWindowIDsAsString(parent.get())); + + parent->StackChildAbove(w213, w21); + EXPECT_EQ(w11, parent->children().back()); + EXPECT_EQ("2 22 21 213 211 212 1 11", ChildWindowIDsAsString(parent.get())); + + // No change when stacking a transient parent above its transient child. + parent->StackChildAbove(w21, w211); + EXPECT_EQ(w11, parent->children().back()); + EXPECT_EQ("2 22 21 213 211 212 1 11", ChildWindowIDsAsString(parent.get())); + + // This tests that the order in children_ array rather than in + // transient_children_ array is used when reinserting transient children. + // If transient_children_ array was used '22' would be following '21'. + parent->StackChildAbove(w2.get(), w1.get()); + EXPECT_EQ(w212, parent->children().back()); + EXPECT_EQ("1 11 2 22 21 213 211 212", ChildWindowIDsAsString(parent.get())); + + parent->StackChildAbove(w11, w213); + EXPECT_EQ(w11, parent->children().back()); + EXPECT_EQ("2 22 21 213 211 212 1 11", ChildWindowIDsAsString(parent.get())); +} + +// Tests that transient children are stacked as a unit when using stack below. +TEST_F(TransientWindowStackingClientTest, TransientChildrenGroupBelow) { + scoped_ptr<Window> parent(CreateTestWindowWithId(0, root_window())); + scoped_ptr<Window> w1(CreateTestWindowWithId(1, parent.get())); + Window* w11 = CreateTestWindowWithId(11, parent.get()); + scoped_ptr<Window> w2(CreateTestWindowWithId(2, parent.get())); + Window* w21 = CreateTestWindowWithId(21, parent.get()); + Window* w211 = CreateTestWindowWithId(211, parent.get()); + Window* w212 = CreateTestWindowWithId(212, parent.get()); + Window* w213 = CreateTestWindowWithId(213, parent.get()); + Window* w22 = CreateTestWindowWithId(22, parent.get()); + ASSERT_EQ(8u, parent->children().size()); + + AddTransientChild(w1.get(), w11); // w11 is now owned by w1. + AddTransientChild(w2.get(), w21); // w21 is now owned by w2. + AddTransientChild(w2.get(), w22); // w22 is now owned by w2. + AddTransientChild(w21, w211); // w211 is now owned by w21. + AddTransientChild(w21, w212); // w212 is now owned by w21. + AddTransientChild(w21, w213); // w213 is now owned by w21. + EXPECT_EQ("1 11 2 21 211 212 213 22", ChildWindowIDsAsString(parent.get())); + + // Stack w2 at the bottom, this should force w11 to be last (on top of w1). + // This also tests that the order in children_ array rather than in + // transient_children_ array is used when reinserting transient children. + // If transient_children_ array was used '22' would be following '21'. + parent->StackChildAtBottom(w2.get()); + EXPECT_EQ(w11, parent->children().back()); + EXPECT_EQ("2 21 211 212 213 22 1 11", ChildWindowIDsAsString(parent.get())); + + parent->StackChildAtBottom(w1.get()); + EXPECT_EQ(w22, parent->children().back()); + EXPECT_EQ("1 11 2 21 211 212 213 22", ChildWindowIDsAsString(parent.get())); + + parent->StackChildBelow(w21, w1.get()); + EXPECT_EQ(w11, parent->children().back()); + EXPECT_EQ("2 21 211 212 213 22 1 11", ChildWindowIDsAsString(parent.get())); + + parent->StackChildBelow(w11, w2.get()); + EXPECT_EQ(w22, parent->children().back()); + EXPECT_EQ("1 11 2 21 211 212 213 22", ChildWindowIDsAsString(parent.get())); + + parent->StackChildBelow(w22, w21); + EXPECT_EQ(w213, parent->children().back()); + EXPECT_EQ("1 11 2 22 21 211 212 213", ChildWindowIDsAsString(parent.get())); + + parent->StackChildBelow(w21, w11); + EXPECT_EQ(w11, parent->children().back()); + EXPECT_EQ("2 22 21 211 212 213 1 11", ChildWindowIDsAsString(parent.get())); + + parent->StackChildBelow(w213, w211); + EXPECT_EQ(w11, parent->children().back()); + EXPECT_EQ("2 22 21 213 211 212 1 11", ChildWindowIDsAsString(parent.get())); + + // No change when stacking a transient parent below its transient child. + parent->StackChildBelow(w21, w211); + EXPECT_EQ(w11, parent->children().back()); + EXPECT_EQ("2 22 21 213 211 212 1 11", ChildWindowIDsAsString(parent.get())); + + parent->StackChildBelow(w1.get(), w2.get()); + EXPECT_EQ(w212, parent->children().back()); + EXPECT_EQ("1 11 2 22 21 213 211 212", ChildWindowIDsAsString(parent.get())); + + parent->StackChildBelow(w213, w11); + EXPECT_EQ(w11, parent->children().back()); + EXPECT_EQ("2 22 21 213 211 212 1 11", ChildWindowIDsAsString(parent.get())); +} + +TEST_F(TransientWindowStackingClientTest, + StackWindowsWhoseLayersHaveNoDelegate) { + scoped_ptr<Window> window1(CreateTestWindowWithId(1, root_window())); + window1->layer()->set_name("1"); + scoped_ptr<Window> window2(CreateTestWindowWithId(2, root_window())); + window2->layer()->set_name("2"); + scoped_ptr<Window> window3(CreateTestWindowWithId(3, root_window())); + window3->layer()->set_name("3"); + + // This brings |window1| (and its layer) to the front. + root_window()->StackChildAbove(window1.get(), window3.get()); + EXPECT_EQ("2 3 1", ChildWindowIDsAsString(root_window())); + EXPECT_EQ("2 3 1", + ui::test::ChildLayerNamesAsString(*root_window()->layer())); + + // Since |window1| does not have a delegate, |window2| should not move in + // front of it, nor should its layer. + window1->layer()->set_delegate(NULL); + root_window()->StackChildAbove(window2.get(), window1.get()); + EXPECT_EQ("3 2 1", ChildWindowIDsAsString(root_window())); + EXPECT_EQ("3 2 1", + ui::test::ChildLayerNamesAsString(*root_window()->layer())); + + // It should still be possible to stack |window3| immediately below |window1|. + root_window()->StackChildBelow(window3.get(), window1.get()); + EXPECT_EQ("2 3 1", ChildWindowIDsAsString(root_window())); + EXPECT_EQ("2 3 1", + ui::test::ChildLayerNamesAsString(*root_window()->layer())); + + // Since neither |window3| nor |window1| have a delegate, |window2| should + // not move in front of either. + window3->layer()->set_delegate(NULL); + root_window()->StackChildBelow(window2.get(), window1.get()); + EXPECT_EQ("2 3 1", ChildWindowIDsAsString(root_window())); + EXPECT_EQ("2 3 1", + ui::test::ChildLayerNamesAsString(*root_window()->layer())); +} + +} // namespace wm diff --git a/chromium/ui/wm/core/user_activity_detector.cc b/chromium/ui/wm/core/user_activity_detector.cc new file mode 100644 index 00000000000..56bf563b3d0 --- /dev/null +++ b/chromium/ui/wm/core/user_activity_detector.cc @@ -0,0 +1,112 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/user_activity_detector.h" + +#include "base/format_macros.h" +#include "base/logging.h" +#include "base/strings/stringprintf.h" +#include "ui/events/event.h" +#include "ui/wm/core/user_activity_observer.h" + +namespace wm { + +namespace { + +// Returns a string describing |event|. +std::string GetEventDebugString(const ui::Event* event) { + std::string details = base::StringPrintf( + "type=%d name=%s flags=%d time=%" PRId64, + event->type(), event->name().c_str(), event->flags(), + event->time_stamp().InMilliseconds()); + + if (event->IsKeyEvent()) { + details += base::StringPrintf(" key_code=%d", + static_cast<const ui::KeyEvent*>(event)->key_code()); + } else if (event->IsMouseEvent() || event->IsTouchEvent() || + event->IsGestureEvent()) { + details += base::StringPrintf(" location=%s", + static_cast<const ui::LocatedEvent*>( + event)->location().ToString().c_str()); + } + + return details; +} + +} // namespace + +const int UserActivityDetector::kNotifyIntervalMs = 200; + +// Too low and mouse events generated at the tail end of reconfiguration +// will be reported as user activity and turn the screen back on; too high +// and we'll ignore legitimate activity. +const int UserActivityDetector::kDisplayPowerChangeIgnoreMouseMs = 1000; + +UserActivityDetector::UserActivityDetector() { +} + +UserActivityDetector::~UserActivityDetector() { +} + +bool UserActivityDetector::HasObserver(UserActivityObserver* observer) const { + return observers_.HasObserver(observer); +} + +void UserActivityDetector::AddObserver(UserActivityObserver* observer) { + observers_.AddObserver(observer); +} + +void UserActivityDetector::RemoveObserver(UserActivityObserver* observer) { + observers_.RemoveObserver(observer); +} + +void UserActivityDetector::OnDisplayPowerChanging() { + honor_mouse_events_time_ = GetCurrentTime() + + base::TimeDelta::FromMilliseconds(kDisplayPowerChangeIgnoreMouseMs); +} + +void UserActivityDetector::OnKeyEvent(ui::KeyEvent* event) { + HandleActivity(event); +} + +void UserActivityDetector::OnMouseEvent(ui::MouseEvent* event) { + if (event->flags() & ui::EF_IS_SYNTHESIZED) + return; + if (!honor_mouse_events_time_.is_null() && + GetCurrentTime() < honor_mouse_events_time_) + return; + + HandleActivity(event); +} + +void UserActivityDetector::OnScrollEvent(ui::ScrollEvent* event) { + HandleActivity(event); +} + +void UserActivityDetector::OnTouchEvent(ui::TouchEvent* event) { + HandleActivity(event); +} + +void UserActivityDetector::OnGestureEvent(ui::GestureEvent* event) { + HandleActivity(event); +} + +base::TimeTicks UserActivityDetector::GetCurrentTime() const { + return !now_for_test_.is_null() ? now_for_test_ : base::TimeTicks::Now(); +} + +void UserActivityDetector::HandleActivity(const ui::Event* event) { + base::TimeTicks now = GetCurrentTime(); + last_activity_time_ = now; + if (last_observer_notification_time_.is_null() || + (now - last_observer_notification_time_).InMillisecondsF() >= + kNotifyIntervalMs) { + if (VLOG_IS_ON(1)) + VLOG(1) << "Reporting user activity: " << GetEventDebugString(event); + FOR_EACH_OBSERVER(UserActivityObserver, observers_, OnUserActivity(event)); + last_observer_notification_time_ = now; + } +} + +} // namespace wm diff --git a/chromium/ui/wm/core/user_activity_detector.h b/chromium/ui/wm/core/user_activity_detector.h new file mode 100644 index 00000000000..851f07ac325 --- /dev/null +++ b/chromium/ui/wm/core/user_activity_detector.h @@ -0,0 +1,81 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_WM_CORE_USER_ACTIVITY_DETECTOR_H_ +#define UI_WM_CORE_USER_ACTIVITY_DETECTOR_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/observer_list.h" +#include "base/time/time.h" +#include "ui/events/event_handler.h" +#include "ui/wm/wm_export.h" + +namespace wm { + +class UserActivityObserver; + +// Watches for input events and notifies observers that the user is active. +class WM_EXPORT UserActivityDetector : public ui::EventHandler { + public: + // Minimum amount of time between notifications to observers. + static const int kNotifyIntervalMs; + + // Amount of time that mouse events should be ignored after notification + // is received that displays' power states are being changed. + static const int kDisplayPowerChangeIgnoreMouseMs; + + UserActivityDetector(); + virtual ~UserActivityDetector(); + + base::TimeTicks last_activity_time() const { return last_activity_time_; } + + void set_now_for_test(base::TimeTicks now) { now_for_test_ = now; } + + bool HasObserver(UserActivityObserver* observer) const; + void AddObserver(UserActivityObserver* observer); + void RemoveObserver(UserActivityObserver* observer); + + // Called when displays are about to be turned on or off. + void OnDisplayPowerChanging(); + + // ui::EventHandler implementation. + virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE; + virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE; + virtual void OnScrollEvent(ui::ScrollEvent* event) OVERRIDE; + virtual void OnTouchEvent(ui::TouchEvent* event) OVERRIDE; + virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE; + + private: + // Returns |now_for_test_| if set or base::TimeTicks::Now() otherwise. + base::TimeTicks GetCurrentTime() const; + + // Updates |last_activity_time_|. Additionally notifies observers and + // updates |last_observer_notification_time_| if enough time has passed + // since the last notification. + void HandleActivity(const ui::Event* event); + + ObserverList<UserActivityObserver> observers_; + + // Last time at which user activity was observed. + base::TimeTicks last_activity_time_; + + // Last time at which we notified observers that the user was active. + base::TimeTicks last_observer_notification_time_; + + // If set, used when the current time is needed. This can be set by tests to + // simulate the passage of time. + base::TimeTicks now_for_test_; + + // If set, mouse events will be ignored until this time is reached. This + // is to avoid reporting mouse events that occur when displays are turned + // on or off as user activity. + base::TimeTicks honor_mouse_events_time_; + + DISALLOW_COPY_AND_ASSIGN(UserActivityDetector); +}; + +} // namespace wm + +#endif // UI_WM_CORE_USER_ACTIVITY_DETECTOR_H_ diff --git a/chromium/ui/wm/core/user_activity_detector_unittest.cc b/chromium/ui/wm/core/user_activity_detector_unittest.cc new file mode 100644 index 00000000000..8ff14b4154f --- /dev/null +++ b/chromium/ui/wm/core/user_activity_detector_unittest.cc @@ -0,0 +1,200 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/user_activity_detector.h" + +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/time/time.h" +#include "ui/aura/test/aura_test_base.h" +#include "ui/events/event.h" +#include "ui/events/event_constants.h" +#include "ui/events/keycodes/keyboard_codes.h" +#include "ui/gfx/point.h" +#include "ui/wm/core/user_activity_observer.h" + +namespace wm { + +// Implementation that just counts the number of times we've been told that the +// user is active. +class TestUserActivityObserver : public UserActivityObserver { + public: + TestUserActivityObserver() : num_invocations_(0) {} + + int num_invocations() const { return num_invocations_; } + void reset_stats() { num_invocations_ = 0; } + + // UserActivityObserver implementation. + virtual void OnUserActivity(const ui::Event* event) OVERRIDE { + num_invocations_++; + } + + private: + // Number of times that OnUserActivity() has been called. + int num_invocations_; + + DISALLOW_COPY_AND_ASSIGN(TestUserActivityObserver); +}; + +class UserActivityDetectorTest : public aura::test::AuraTestBase { + public: + UserActivityDetectorTest() {} + virtual ~UserActivityDetectorTest() {} + + virtual void SetUp() OVERRIDE { + AuraTestBase::SetUp(); + observer_.reset(new TestUserActivityObserver); + detector_.reset(new UserActivityDetector); + detector_->AddObserver(observer_.get()); + + now_ = base::TimeTicks::Now(); + detector_->set_now_for_test(now_); + } + + virtual void TearDown() OVERRIDE { + detector_->RemoveObserver(observer_.get()); + AuraTestBase::TearDown(); + } + + protected: + // Move |detector_|'s idea of the current time forward by |delta|. + void AdvanceTime(base::TimeDelta delta) { + now_ += delta; + detector_->set_now_for_test(now_); + } + + scoped_ptr<UserActivityDetector> detector_; + scoped_ptr<TestUserActivityObserver> observer_; + + base::TimeTicks now_; + + private: + DISALLOW_COPY_AND_ASSIGN(UserActivityDetectorTest); +}; + +// Checks that the observer is notified in response to different types of input +// events. +TEST_F(UserActivityDetectorTest, Basic) { + ui::KeyEvent key_event(ui::ET_KEY_PRESSED, ui::VKEY_A, ui::EF_NONE, false); + detector_->OnKeyEvent(&key_event); + EXPECT_FALSE(key_event.handled()); + EXPECT_EQ(now_.ToInternalValue(), + detector_->last_activity_time().ToInternalValue()); + EXPECT_EQ(1, observer_->num_invocations()); + observer_->reset_stats(); + + base::TimeDelta advance_delta = base::TimeDelta::FromMilliseconds( + UserActivityDetector::kNotifyIntervalMs); + AdvanceTime(advance_delta); + ui::MouseEvent mouse_event( + ui::ET_MOUSE_MOVED, gfx::Point(), gfx::Point(), ui::EF_NONE, ui::EF_NONE); + detector_->OnMouseEvent(&mouse_event); + EXPECT_FALSE(mouse_event.handled()); + EXPECT_EQ(now_.ToInternalValue(), + detector_->last_activity_time().ToInternalValue()); + EXPECT_EQ(1, observer_->num_invocations()); + observer_->reset_stats(); + + base::TimeTicks time_before_ignore = now_; + + // Temporarily ignore mouse events when displays are turned on or off. + detector_->OnDisplayPowerChanging(); + detector_->OnMouseEvent(&mouse_event); + EXPECT_FALSE(mouse_event.handled()); + EXPECT_EQ(time_before_ignore.ToInternalValue(), + detector_->last_activity_time().ToInternalValue()); + EXPECT_EQ(0, observer_->num_invocations()); + observer_->reset_stats(); + + const base::TimeDelta kIgnoreMouseTime = + base::TimeDelta::FromMilliseconds( + UserActivityDetector::kDisplayPowerChangeIgnoreMouseMs); + AdvanceTime(kIgnoreMouseTime / 2); + detector_->OnMouseEvent(&mouse_event); + EXPECT_FALSE(mouse_event.handled()); + EXPECT_EQ(time_before_ignore.ToInternalValue(), + detector_->last_activity_time().ToInternalValue()); + EXPECT_EQ(0, observer_->num_invocations()); + observer_->reset_stats(); + + // After enough time has passed, mouse events should be reported again. + AdvanceTime(std::max(kIgnoreMouseTime, advance_delta)); + detector_->OnMouseEvent(&mouse_event); + EXPECT_FALSE(mouse_event.handled()); + EXPECT_EQ(now_.ToInternalValue(), + detector_->last_activity_time().ToInternalValue()); + EXPECT_EQ(1, observer_->num_invocations()); + observer_->reset_stats(); + + AdvanceTime(advance_delta); + ui::TouchEvent touch_event( + ui::ET_TOUCH_PRESSED, gfx::Point(), 0, base::TimeDelta()); + detector_->OnTouchEvent(&touch_event); + EXPECT_FALSE(touch_event.handled()); + EXPECT_EQ(now_.ToInternalValue(), + detector_->last_activity_time().ToInternalValue()); + EXPECT_EQ(1, observer_->num_invocations()); + observer_->reset_stats(); + + AdvanceTime(advance_delta); + ui::GestureEvent gesture_event( + ui::ET_GESTURE_TAP, 0, 0, ui::EF_NONE, + base::TimeDelta::FromMilliseconds(base::Time::Now().ToDoubleT() * 1000), + ui::GestureEventDetails(ui::ET_GESTURE_TAP, 0, 0), 0U); + detector_->OnGestureEvent(&gesture_event); + EXPECT_FALSE(gesture_event.handled()); + EXPECT_EQ(now_.ToInternalValue(), + detector_->last_activity_time().ToInternalValue()); + EXPECT_EQ(1, observer_->num_invocations()); + observer_->reset_stats(); +} + +// Checks that observers aren't notified too frequently. +TEST_F(UserActivityDetectorTest, RateLimitNotifications) { + // The observer should be notified about a key event. + ui::KeyEvent event(ui::ET_KEY_PRESSED, ui::VKEY_A, ui::EF_NONE, false); + detector_->OnKeyEvent(&event); + EXPECT_FALSE(event.handled()); + EXPECT_EQ(1, observer_->num_invocations()); + observer_->reset_stats(); + + // It shouldn't be notified if a second event occurs in the same instant in + // time. + detector_->OnKeyEvent(&event); + EXPECT_FALSE(event.handled()); + EXPECT_EQ(0, observer_->num_invocations()); + observer_->reset_stats(); + + // Advance the time, but not quite enough for another notification to be sent. + AdvanceTime( + base::TimeDelta::FromMilliseconds( + UserActivityDetector::kNotifyIntervalMs - 100)); + detector_->OnKeyEvent(&event); + EXPECT_FALSE(event.handled()); + EXPECT_EQ(0, observer_->num_invocations()); + observer_->reset_stats(); + + // Advance time by the notification interval, definitely moving out of the + // rate limit. This should let us trigger another notification. + AdvanceTime(base::TimeDelta::FromMilliseconds( + UserActivityDetector::kNotifyIntervalMs)); + + detector_->OnKeyEvent(&event); + EXPECT_FALSE(event.handled()); + EXPECT_EQ(1, observer_->num_invocations()); +} + +// Checks that the detector ignores synthetic mouse events. +TEST_F(UserActivityDetectorTest, IgnoreSyntheticMouseEvents) { + ui::MouseEvent mouse_event( + ui::ET_MOUSE_MOVED, gfx::Point(), gfx::Point(), ui::EF_IS_SYNTHESIZED, + ui::EF_NONE); + detector_->OnMouseEvent(&mouse_event); + EXPECT_FALSE(mouse_event.handled()); + EXPECT_EQ(base::TimeTicks().ToInternalValue(), + detector_->last_activity_time().ToInternalValue()); + EXPECT_EQ(0, observer_->num_invocations()); +} + +} // namespace wm diff --git a/chromium/ui/wm/core/user_activity_observer.h b/chromium/ui/wm/core/user_activity_observer.h new file mode 100644 index 00000000000..0fb35a61f06 --- /dev/null +++ b/chromium/ui/wm/core/user_activity_observer.h @@ -0,0 +1,35 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_WM_CORE_USER_ACTIVITY_OBSERVER_H_ +#define UI_WM_CORE_USER_ACTIVITY_OBSERVER_H_ + +#include "base/basictypes.h" +#include "ui/wm/wm_export.h" + +namespace ui { +class Event; +} + +namespace wm { + +// Interface for classes that want to be notified about user activity. +// Implementations should register themselves with UserActivityDetector. +class WM_EXPORT UserActivityObserver { + public: + // Invoked periodically while the user is active (i.e. generating input + // events). |event| is the event that triggered the notification; it may + // be NULL in some cases (e.g. testing or synthetic invocations). + virtual void OnUserActivity(const ui::Event* event) = 0; + + protected: + UserActivityObserver() {} + virtual ~UserActivityObserver() {} + + DISALLOW_COPY_AND_ASSIGN(UserActivityObserver); +}; + +} // namespace wm + +#endif // UI_WM_CORE_USER_ACTIVITY_OBSERVER_H_ diff --git a/chromium/ui/wm/core/visibility_controller.cc b/chromium/ui/wm/core/visibility_controller.cc new file mode 100644 index 00000000000..fc187a94902 --- /dev/null +++ b/chromium/ui/wm/core/visibility_controller.cc @@ -0,0 +1,89 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/visibility_controller.h" + +#include "ui/aura/window.h" +#include "ui/aura/window_property.h" +#include "ui/compositor/layer.h" +#include "ui/wm/core/window_animations.h" + +namespace wm { + +namespace { + +// Property set on all windows whose child windows' visibility changes are +// animated. +DEFINE_WINDOW_PROPERTY_KEY( + bool, kChildWindowVisibilityChangesAnimatedKey, false); + +bool ShouldAnimateWindow(aura::Window* window) { + return window->parent() && window->parent()->GetProperty( + kChildWindowVisibilityChangesAnimatedKey); +} + +} // namespace + +VisibilityController::VisibilityController() { +} + +VisibilityController::~VisibilityController() { +} + +bool VisibilityController::CallAnimateOnChildWindowVisibilityChanged( + aura::Window* window, + bool visible) { + return AnimateOnChildWindowVisibilityChanged(window, visible); +} + +void VisibilityController::UpdateLayerVisibility(aura::Window* window, + bool visible) { + bool animated = window->type() != ui::wm::WINDOW_TYPE_CONTROL && + window->type() != ui::wm::WINDOW_TYPE_UNKNOWN && + ShouldAnimateWindow(window); + animated = animated && + CallAnimateOnChildWindowVisibilityChanged(window, visible); + + // If we're already in the process of hiding don't do anything. Otherwise we + // may end up prematurely canceling the animation. + // This does not check opacity as when fading out a visibility change should + // also be scheduled (to do otherwise would mean the window can not be seen, + // opacity is 0, yet the window is marked as visible) (see CL 132903003). + // TODO(vollick): remove this. + if (!visible && + window->layer()->GetAnimator()->IsAnimatingProperty( + ui::LayerAnimationElement::VISIBILITY) && + !window->layer()->GetTargetVisibility()) { + return; + } + + // When a window is made visible, we always make its layer visible + // immediately. When a window is hidden, the layer must be left visible and + // only made not visible once the animation is complete. + if (!animated || visible) + window->layer()->SetVisible(visible); +} + +SuspendChildWindowVisibilityAnimations::SuspendChildWindowVisibilityAnimations( + aura::Window* window) + : window_(window), + original_enabled_(window->GetProperty( + kChildWindowVisibilityChangesAnimatedKey)) { + window_->ClearProperty(kChildWindowVisibilityChangesAnimatedKey); +} + +SuspendChildWindowVisibilityAnimations:: + ~SuspendChildWindowVisibilityAnimations() { + if (original_enabled_) + window_->SetProperty(kChildWindowVisibilityChangesAnimatedKey, true); + else + window_->ClearProperty(kChildWindowVisibilityChangesAnimatedKey); +} + +void SetChildWindowVisibilityChangesAnimated(aura::Window* window) { + window->SetProperty(kChildWindowVisibilityChangesAnimatedKey, true); +} + +} // namespace wm + diff --git a/chromium/ui/wm/core/visibility_controller.h b/chromium/ui/wm/core/visibility_controller.h new file mode 100644 index 00000000000..47ae9910c7d --- /dev/null +++ b/chromium/ui/wm/core/visibility_controller.h @@ -0,0 +1,74 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_WM_CORE_VISIBILITY_CONTROLLER_H_ +#define UI_WM_CORE_VISIBILITY_CONTROLLER_H_ + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "ui/aura/client/visibility_client.h" +#include "ui/wm/wm_export.h" + +namespace wm { + +class WM_EXPORT VisibilityController + : public aura::client::VisibilityClient { + public: + VisibilityController(); + virtual ~VisibilityController(); + + protected: + // Subclasses override if they want to call a different implementation of + // this function. + // TODO(beng): potentially replace by an actual window animator class in + // window_animations.h. + virtual bool CallAnimateOnChildWindowVisibilityChanged(aura::Window* window, + bool visible); + + private: + // Overridden from aura::client::VisibilityClient: + virtual void UpdateLayerVisibility(aura::Window* window, + bool visible) OVERRIDE; + + DISALLOW_COPY_AND_ASSIGN(VisibilityController); +}; + +// Suspends the animations for visibility changes during the lifetime of an +// instance of this class. +// +// Example: +// +// void ViewName::UnanimatedAction() { +// SuspendChildWindowVisibilityAnimations suspend(parent); +// // Perform unanimated action here. +// // ... +// // When the method finishes, visibility animations will return to their +// // previous state. +// } +// +class WM_EXPORT SuspendChildWindowVisibilityAnimations { + public: + // Suspend visibility animations of child windows. + explicit SuspendChildWindowVisibilityAnimations(aura::Window* window); + + // Restore visibility animations to their original state. + ~SuspendChildWindowVisibilityAnimations(); + + private: + // The window to manage. + aura::Window* window_; + + // Whether the visibility animations on child windows were originally enabled. + const bool original_enabled_; + + DISALLOW_COPY_AND_ASSIGN(SuspendChildWindowVisibilityAnimations); +}; + +// Tells |window| to animate visibility changes to its children. +void WM_EXPORT SetChildWindowVisibilityChangesAnimated( + aura::Window* window); + +} // namespace wm + +#endif // UI_WM_CORE_VISIBILITY_CONTROLLER_H_ diff --git a/chromium/ui/wm/core/visibility_controller_unittest.cc b/chromium/ui/wm/core/visibility_controller_unittest.cc new file mode 100644 index 00000000000..5825d49530c --- /dev/null +++ b/chromium/ui/wm/core/visibility_controller_unittest.cc @@ -0,0 +1,56 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/visibility_controller.h" + +#include "ui/aura/test/aura_test_base.h" +#include "ui/aura/test/test_window_delegate.h" +#include "ui/aura/test/test_windows.h" +#include "ui/aura/window.h" +#include "ui/aura/window_event_dispatcher.h" +#include "ui/compositor/layer.h" +#include "ui/compositor/layer_animator.h" +#include "ui/compositor/scoped_animation_duration_scale_mode.h" +#include "ui/compositor/scoped_layer_animation_settings.h" + +namespace wm { + +typedef aura::test::AuraTestBase VisibilityControllerTest; + +// Check that a transparency change to 0 will not cause a hide call to be +// ignored. +TEST_F(VisibilityControllerTest, AnimateTransparencyToZeroAndHideHides) { + // We cannot disable animations for this test. + ui::ScopedAnimationDurationScaleMode normal_duration_mode( + ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION); + + VisibilityController controller; + aura::client::SetVisibilityClient(root_window(), &controller); + + SetChildWindowVisibilityChangesAnimated(root_window()); + + aura::test::TestWindowDelegate d; + scoped_ptr<aura::Window> window(aura::test::CreateTestWindowWithDelegate( + &d, -2, gfx::Rect(0, 0, 50, 50), root_window())); + ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator()); + settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(5)); + + EXPECT_TRUE(window->layer()->visible()); + EXPECT_TRUE(window->IsVisible()); + + window->layer()->SetOpacity(0.0); + EXPECT_TRUE(window->layer()->visible()); + EXPECT_TRUE(window->IsVisible()); + EXPECT_TRUE(window->layer()->GetAnimator()-> + IsAnimatingProperty(ui::LayerAnimationElement::OPACITY)); + EXPECT_EQ(0.0f, window->layer()->GetTargetOpacity()); + + // Check that the visibility is correct after the hide animation has finished. + window->Hide(); + window->layer()->GetAnimator()->StopAnimating(); + EXPECT_FALSE(window->layer()->visible()); + EXPECT_FALSE(window->IsVisible()); +} + +} // namespace wm diff --git a/chromium/ui/wm/core/window_animations.cc b/chromium/ui/wm/core/window_animations.cc new file mode 100644 index 00000000000..a79db3a15d6 --- /dev/null +++ b/chromium/ui/wm/core/window_animations.cc @@ -0,0 +1,658 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/window_animations.h" + +#include <math.h> + +#include <algorithm> +#include <vector> + +#include "base/command_line.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/stl_util.h" +#include "base/time/time.h" +#include "ui/aura/client/aura_constants.h" +#include "ui/aura/window.h" +#include "ui/aura/window_delegate.h" +#include "ui/aura/window_observer.h" +#include "ui/aura/window_property.h" +#include "ui/compositor/compositor_observer.h" +#include "ui/compositor/layer.h" +#include "ui/compositor/layer_animation_observer.h" +#include "ui/compositor/layer_animation_sequence.h" +#include "ui/compositor/layer_animator.h" +#include "ui/compositor/layer_tree_owner.h" +#include "ui/compositor/scoped_layer_animation_settings.h" +#include "ui/gfx/animation/animation.h" +#include "ui/gfx/interpolated_transform.h" +#include "ui/gfx/rect_conversions.h" +#include "ui/gfx/screen.h" +#include "ui/gfx/vector2d.h" +#include "ui/gfx/vector3d_f.h" +#include "ui/wm/core/window_util.h" +#include "ui/wm/core/wm_core_switches.h" +#include "ui/wm/public/animation_host.h" + +DECLARE_WINDOW_PROPERTY_TYPE(int) +DECLARE_WINDOW_PROPERTY_TYPE(wm::WindowVisibilityAnimationType) +DECLARE_WINDOW_PROPERTY_TYPE(wm::WindowVisibilityAnimationTransition) +DECLARE_WINDOW_PROPERTY_TYPE(float) +DECLARE_EXPORTED_WINDOW_PROPERTY_TYPE(WM_EXPORT, bool) + +namespace wm { +namespace { +const float kWindowAnimation_Vertical_TranslateY = 15.f; + +// A base class for hiding animation observer which has two roles: +// 1) Notifies AnimationHost at the end of hiding animation. +// 2) Detaches the window's layers for hiding animation and deletes +// them upon completion of the animation. This is necessary to a) +// ensure that the animation continues in the event of the window being +// deleted, and b) to ensure that the animation is visible even if the +// window gets restacked below other windows when focus or activation +// changes. +// The subclass will determine when the animation is completed. +class HidingWindowAnimationObserverBase : public aura::WindowObserver { + public: + HidingWindowAnimationObserverBase(aura::Window* window) : window_(window) { + window_->AddObserver(this); + } + virtual ~HidingWindowAnimationObserverBase() { + if (window_) + window_->RemoveObserver(this); + } + + // aura::WindowObserver: + virtual void OnWindowDestroying(aura::Window* window) OVERRIDE { + DCHECK_EQ(window, window_); + WindowInvalid(); + } + + virtual void OnWindowDestroyed(aura::Window* window) OVERRIDE { + DCHECK_EQ(window, window_); + WindowInvalid(); + } + + // Detach the current layers and create new layers for |window_|. + // Stack the original layers above |window_| and its transient + // children. If the window has transient children, the original + // layers will be moved above the top most transient child so that + // activation change does not put the window above the animating + // layer. + void DetachAndRecreateLayers() { + layer_owner_ = RecreateLayers(window_); + if (window_->parent()) { + const aura::Window::Windows& transient_children = + GetTransientChildren(window_); + aura::Window::Windows::const_iterator iter = + std::find(window_->parent()->children().begin(), + window_->parent()->children().end(), + window_); + DCHECK(iter != window_->parent()->children().end()); + aura::Window* topmost_transient_child = NULL; + for (++iter; iter != window_->parent()->children().end(); ++iter) { + if (std::find(transient_children.begin(), + transient_children.end(), + *iter) != transient_children.end()) { + topmost_transient_child = *iter; + } + } + if (topmost_transient_child) { + window_->parent()->layer()->StackAbove( + layer_owner_->root(), topmost_transient_child->layer()); + } + } + } + + protected: + // Invoked when the hiding animation is completed. It will delete + // 'this', and no operation should be made on this object after this + // point. + void OnAnimationCompleted() { + // Window may have been destroyed by this point. + if (window_) { + aura::client::AnimationHost* animation_host = + aura::client::GetAnimationHost(window_); + if (animation_host) + animation_host->OnWindowHidingAnimationCompleted(); + window_->RemoveObserver(this); + } + delete this; + } + + private: + // Invoked when the window is destroyed (or destroying). + void WindowInvalid() { + layer_owner_->root()->SuppressPaint(); + + window_->RemoveObserver(this); + window_ = NULL; + } + + aura::Window* window_; + + // The owner of detached layers. + scoped_ptr<ui::LayerTreeOwner> layer_owner_; + + DISALLOW_COPY_AND_ASSIGN(HidingWindowAnimationObserverBase); +}; + +} // namespace + +DEFINE_WINDOW_PROPERTY_KEY(int, + kWindowVisibilityAnimationTypeKey, + WINDOW_VISIBILITY_ANIMATION_TYPE_DEFAULT); +DEFINE_WINDOW_PROPERTY_KEY(int, kWindowVisibilityAnimationDurationKey, 0); +DEFINE_WINDOW_PROPERTY_KEY(WindowVisibilityAnimationTransition, + kWindowVisibilityAnimationTransitionKey, + ANIMATE_BOTH); +DEFINE_WINDOW_PROPERTY_KEY(float, + kWindowVisibilityAnimationVerticalPositionKey, + kWindowAnimation_Vertical_TranslateY); + +// A HidingWindowAnimationObserver that deletes observer and detached +// layers upon the completion of the implicit animation. +class ImplicitHidingWindowAnimationObserver + : public HidingWindowAnimationObserverBase, + public ui::ImplicitAnimationObserver { + public: + ImplicitHidingWindowAnimationObserver( + aura::Window* window, + ui::ScopedLayerAnimationSettings* settings); + virtual ~ImplicitHidingWindowAnimationObserver() {} + + // ui::ImplicitAnimationObserver: + virtual void OnImplicitAnimationsCompleted() OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(ImplicitHidingWindowAnimationObserver); +}; + +namespace { + +const int kDefaultAnimationDurationForMenuMS = 150; + +const float kWindowAnimation_HideOpacity = 0.f; +const float kWindowAnimation_ShowOpacity = 1.f; +const float kWindowAnimation_TranslateFactor = 0.5f; +const float kWindowAnimation_ScaleFactor = .95f; + +const int kWindowAnimation_Rotate_DurationMS = 180; +const int kWindowAnimation_Rotate_OpacityDurationPercent = 90; +const float kWindowAnimation_Rotate_TranslateY = -20.f; +const float kWindowAnimation_Rotate_PerspectiveDepth = 500.f; +const float kWindowAnimation_Rotate_DegreesX = 5.f; +const float kWindowAnimation_Rotate_ScaleFactor = .99f; + +const float kWindowAnimation_Bounce_Scale = 1.02f; +const int kWindowAnimation_Bounce_DurationMS = 180; +const int kWindowAnimation_Bounce_GrowShrinkDurationPercent = 40; + +base::TimeDelta GetWindowVisibilityAnimationDuration( + const aura::Window& window) { + int duration = + window.GetProperty(kWindowVisibilityAnimationDurationKey); + if (duration == 0 && window.type() == ui::wm::WINDOW_TYPE_MENU) { + return base::TimeDelta::FromMilliseconds( + kDefaultAnimationDurationForMenuMS); + } + return base::TimeDelta::FromInternalValue(duration); +} + +// Gets/sets the WindowVisibilityAnimationType associated with a window. +// TODO(beng): redundant/fold into method on public api? +int GetWindowVisibilityAnimationType(aura::Window* window) { + int type = window->GetProperty(kWindowVisibilityAnimationTypeKey); + if (type == WINDOW_VISIBILITY_ANIMATION_TYPE_DEFAULT) { + return (window->type() == ui::wm::WINDOW_TYPE_MENU || + window->type() == ui::wm::WINDOW_TYPE_TOOLTIP) + ? WINDOW_VISIBILITY_ANIMATION_TYPE_FADE + : WINDOW_VISIBILITY_ANIMATION_TYPE_DROP; + } + return type; +} + +void GetTransformRelativeToRoot(ui::Layer* layer, gfx::Transform* transform) { + const ui::Layer* root = layer; + while (root->parent()) + root = root->parent(); + layer->GetTargetTransformRelativeTo(root, transform); +} + +gfx::Rect GetLayerWorldBoundsAfterTransform(ui::Layer* layer, + const gfx::Transform& transform) { + gfx::Transform in_world = transform; + GetTransformRelativeToRoot(layer, &in_world); + + gfx::RectF transformed = layer->bounds(); + in_world.TransformRect(&transformed); + + return gfx::ToEnclosingRect(transformed); +} + +// Augment the host window so that the enclosing bounds of the full +// animation will fit inside of it. +void AugmentWindowSize(aura::Window* window, + const gfx::Transform& end_transform) { + aura::client::AnimationHost* animation_host = + aura::client::GetAnimationHost(window); + if (!animation_host) + return; + + const gfx::Rect& world_at_start = window->bounds(); + gfx::Rect world_at_end = + GetLayerWorldBoundsAfterTransform(window->layer(), end_transform); + gfx::Rect union_in_window_space = + gfx::UnionRects(world_at_start, world_at_end); + + // Calculate the top left and bottom right deltas to be added to the window + // bounds. + gfx::Vector2d top_left_delta(world_at_start.x() - union_in_window_space.x(), + world_at_start.y() - union_in_window_space.y()); + + gfx::Vector2d bottom_right_delta( + union_in_window_space.x() + union_in_window_space.width() - + (world_at_start.x() + world_at_start.width()), + union_in_window_space.y() + union_in_window_space.height() - + (world_at_start.y() + world_at_start.height())); + + DCHECK(top_left_delta.x() >= 0 && top_left_delta.y() >= 0 && + bottom_right_delta.x() >= 0 && bottom_right_delta.y() >= 0); + + animation_host->SetHostTransitionOffsets(top_left_delta, bottom_right_delta); +} + +// Shows a window using an animation, animating its opacity from 0.f to 1.f, +// its visibility to true, and its transform from |start_transform| to +// |end_transform|. +void AnimateShowWindowCommon(aura::Window* window, + const gfx::Transform& start_transform, + const gfx::Transform& end_transform) { + AugmentWindowSize(window, end_transform); + + window->layer()->SetOpacity(kWindowAnimation_HideOpacity); + window->layer()->SetTransform(start_transform); + window->layer()->SetVisible(true); + + { + // Property sets within this scope will be implicitly animated. + ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator()); + base::TimeDelta duration = GetWindowVisibilityAnimationDuration(*window); + if (duration.ToInternalValue() > 0) + settings.SetTransitionDuration(duration); + + window->layer()->SetTransform(end_transform); + window->layer()->SetOpacity(kWindowAnimation_ShowOpacity); + } +} + +// Hides a window using an animation, animating its opacity from 1.f to 0.f, +// its visibility to false, and its transform to |end_transform|. +void AnimateHideWindowCommon(aura::Window* window, + const gfx::Transform& end_transform) { + AugmentWindowSize(window, end_transform); + + // Property sets within this scope will be implicitly animated. + ScopedHidingAnimationSettings hiding_settings(window); + base::TimeDelta duration = GetWindowVisibilityAnimationDuration(*window); + if (duration.ToInternalValue() > 0) + hiding_settings.layer_animation_settings()->SetTransitionDuration(duration); + + window->layer()->SetOpacity(kWindowAnimation_HideOpacity); + window->layer()->SetTransform(end_transform); + window->layer()->SetVisible(false); +} + +static gfx::Transform GetScaleForWindow(aura::Window* window) { + gfx::Rect bounds = window->bounds(); + gfx::Transform scale = gfx::GetScaleTransform( + gfx::Point(kWindowAnimation_TranslateFactor * bounds.width(), + kWindowAnimation_TranslateFactor * bounds.height()), + kWindowAnimation_ScaleFactor); + return scale; +} + +// Show/Hide windows using a shrink animation. +void AnimateShowWindow_Drop(aura::Window* window) { + AnimateShowWindowCommon(window, GetScaleForWindow(window), gfx::Transform()); +} + +void AnimateHideWindow_Drop(aura::Window* window) { + AnimateHideWindowCommon(window, GetScaleForWindow(window)); +} + +// Show/Hide windows using a vertical Glenimation. +void AnimateShowWindow_Vertical(aura::Window* window) { + gfx::Transform transform; + transform.Translate(0, window->GetProperty( + kWindowVisibilityAnimationVerticalPositionKey)); + AnimateShowWindowCommon(window, transform, gfx::Transform()); +} + +void AnimateHideWindow_Vertical(aura::Window* window) { + gfx::Transform transform; + transform.Translate(0, window->GetProperty( + kWindowVisibilityAnimationVerticalPositionKey)); + AnimateHideWindowCommon(window, transform); +} + +// Show/Hide windows using a fade. +void AnimateShowWindow_Fade(aura::Window* window) { + AnimateShowWindowCommon(window, gfx::Transform(), gfx::Transform()); +} + +void AnimateHideWindow_Fade(aura::Window* window) { + AnimateHideWindowCommon(window, gfx::Transform()); +} + +ui::LayerAnimationElement* CreateGrowShrinkElement( + aura::Window* window, bool grow) { + scoped_ptr<ui::InterpolatedTransform> scale(new ui::InterpolatedScale( + gfx::Point3F(kWindowAnimation_Bounce_Scale, + kWindowAnimation_Bounce_Scale, + 1), + gfx::Point3F(1, 1, 1))); + scoped_ptr<ui::InterpolatedTransform> scale_about_pivot( + new ui::InterpolatedTransformAboutPivot( + gfx::Point(window->bounds().width() * 0.5, + window->bounds().height() * 0.5), + scale.release())); + scale_about_pivot->SetReversed(grow); + scoped_ptr<ui::LayerAnimationElement> transition( + ui::LayerAnimationElement::CreateInterpolatedTransformElement( + scale_about_pivot.release(), + base::TimeDelta::FromMilliseconds( + kWindowAnimation_Bounce_DurationMS * + kWindowAnimation_Bounce_GrowShrinkDurationPercent / 100))); + transition->set_tween_type(grow ? gfx::Tween::EASE_OUT : gfx::Tween::EASE_IN); + return transition.release(); +} + +void AnimateBounce(aura::Window* window) { + ui::ScopedLayerAnimationSettings scoped_settings( + window->layer()->GetAnimator()); + scoped_settings.SetPreemptionStrategy( + ui::LayerAnimator::REPLACE_QUEUED_ANIMATIONS); + scoped_ptr<ui::LayerAnimationSequence> sequence( + new ui::LayerAnimationSequence); + sequence->AddElement(CreateGrowShrinkElement(window, true)); + sequence->AddElement(ui::LayerAnimationElement::CreatePauseElement( + ui::LayerAnimationElement::BOUNDS, + base::TimeDelta::FromMilliseconds( + kWindowAnimation_Bounce_DurationMS * + (100 - 2 * kWindowAnimation_Bounce_GrowShrinkDurationPercent) / + 100))); + sequence->AddElement(CreateGrowShrinkElement(window, false)); + window->layer()->GetAnimator()->StartAnimation(sequence.release()); +} + +// A HidingWindowAnimationObserver that deletes observer and detached +// layers when the last_sequence has been completed or aborted. +class RotateHidingWindowAnimationObserver + : public HidingWindowAnimationObserverBase, + public ui::LayerAnimationObserver { + public: + RotateHidingWindowAnimationObserver(aura::Window* window) + : HidingWindowAnimationObserverBase(window), last_sequence_(NULL) {} + virtual ~RotateHidingWindowAnimationObserver() {} + + void set_last_sequence(ui::LayerAnimationSequence* last_sequence) { + last_sequence_ = last_sequence; + } + + // ui::LayerAnimationObserver: + virtual void OnLayerAnimationEnded( + ui::LayerAnimationSequence* sequence) OVERRIDE { + if (last_sequence_ == sequence) + OnAnimationCompleted(); + } + virtual void OnLayerAnimationAborted( + ui::LayerAnimationSequence* sequence) OVERRIDE { + if (last_sequence_ == sequence) + OnAnimationCompleted(); + } + virtual void OnLayerAnimationScheduled( + ui::LayerAnimationSequence* sequence) OVERRIDE {} + + private: + ui::LayerAnimationSequence* last_sequence_; + + DISALLOW_COPY_AND_ASSIGN(RotateHidingWindowAnimationObserver); +}; + +void AddLayerAnimationsForRotate(aura::Window* window, bool show) { + if (show) + window->layer()->SetOpacity(kWindowAnimation_HideOpacity); + + base::TimeDelta duration = base::TimeDelta::FromMilliseconds( + kWindowAnimation_Rotate_DurationMS); + + RotateHidingWindowAnimationObserver* observer = NULL; + + if (!show) { + observer = new RotateHidingWindowAnimationObserver(window); + window->layer()->GetAnimator()->SchedulePauseForProperties( + duration * (100 - kWindowAnimation_Rotate_OpacityDurationPercent) / 100, + ui::LayerAnimationElement::OPACITY); + } + scoped_ptr<ui::LayerAnimationElement> opacity( + ui::LayerAnimationElement::CreateOpacityElement( + show ? kWindowAnimation_ShowOpacity : kWindowAnimation_HideOpacity, + duration * kWindowAnimation_Rotate_OpacityDurationPercent / 100)); + opacity->set_tween_type(gfx::Tween::EASE_IN_OUT); + window->layer()->GetAnimator()->ScheduleAnimation( + new ui::LayerAnimationSequence(opacity.release())); + + float xcenter = window->bounds().width() * 0.5; + + gfx::Transform transform; + transform.Translate(xcenter, 0); + transform.ApplyPerspectiveDepth(kWindowAnimation_Rotate_PerspectiveDepth); + transform.Translate(-xcenter, 0); + scoped_ptr<ui::InterpolatedTransform> perspective( + new ui::InterpolatedConstantTransform(transform)); + + scoped_ptr<ui::InterpolatedTransform> scale( + new ui::InterpolatedScale(1, kWindowAnimation_Rotate_ScaleFactor)); + scoped_ptr<ui::InterpolatedTransform> scale_about_pivot( + new ui::InterpolatedTransformAboutPivot( + gfx::Point(xcenter, kWindowAnimation_Rotate_TranslateY), + scale.release())); + + scoped_ptr<ui::InterpolatedTransform> translation( + new ui::InterpolatedTranslation(gfx::Point(), gfx::Point( + 0, kWindowAnimation_Rotate_TranslateY))); + + scoped_ptr<ui::InterpolatedTransform> rotation( + new ui::InterpolatedAxisAngleRotation( + gfx::Vector3dF(1, 0, 0), 0, kWindowAnimation_Rotate_DegreesX)); + + scale_about_pivot->SetChild(perspective.release()); + translation->SetChild(scale_about_pivot.release()); + rotation->SetChild(translation.release()); + rotation->SetReversed(show); + + scoped_ptr<ui::LayerAnimationElement> transition( + ui::LayerAnimationElement::CreateInterpolatedTransformElement( + rotation.release(), duration)); + ui::LayerAnimationSequence* last_sequence = + new ui::LayerAnimationSequence(transition.release()); + window->layer()->GetAnimator()->ScheduleAnimation(last_sequence); + if (observer) { + observer->set_last_sequence(last_sequence); + observer->DetachAndRecreateLayers(); + } +} + +void AnimateShowWindow_Rotate(aura::Window* window) { + AddLayerAnimationsForRotate(window, true); +} + +void AnimateHideWindow_Rotate(aura::Window* window) { + AddLayerAnimationsForRotate(window, false); +} + +bool AnimateShowWindow(aura::Window* window) { + if (!HasWindowVisibilityAnimationTransition(window, ANIMATE_SHOW)) { + if (HasWindowVisibilityAnimationTransition(window, ANIMATE_HIDE)) { + // Since hide animation may have changed opacity and transform, + // reset them to show the window. + window->layer()->SetOpacity(kWindowAnimation_ShowOpacity); + window->layer()->SetTransform(gfx::Transform()); + } + return false; + } + + switch (GetWindowVisibilityAnimationType(window)) { + case WINDOW_VISIBILITY_ANIMATION_TYPE_DROP: + AnimateShowWindow_Drop(window); + return true; + case WINDOW_VISIBILITY_ANIMATION_TYPE_VERTICAL: + AnimateShowWindow_Vertical(window); + return true; + case WINDOW_VISIBILITY_ANIMATION_TYPE_FADE: + AnimateShowWindow_Fade(window); + return true; + case WINDOW_VISIBILITY_ANIMATION_TYPE_ROTATE: + AnimateShowWindow_Rotate(window); + return true; + default: + return false; + } +} + +bool AnimateHideWindow(aura::Window* window) { + if (!HasWindowVisibilityAnimationTransition(window, ANIMATE_HIDE)) { + if (HasWindowVisibilityAnimationTransition(window, ANIMATE_SHOW)) { + // Since show animation may have changed opacity and transform, + // reset them, though the change should be hidden. + window->layer()->SetOpacity(kWindowAnimation_HideOpacity); + window->layer()->SetTransform(gfx::Transform()); + } + return false; + } + + switch (GetWindowVisibilityAnimationType(window)) { + case WINDOW_VISIBILITY_ANIMATION_TYPE_DROP: + AnimateHideWindow_Drop(window); + return true; + case WINDOW_VISIBILITY_ANIMATION_TYPE_VERTICAL: + AnimateHideWindow_Vertical(window); + return true; + case WINDOW_VISIBILITY_ANIMATION_TYPE_FADE: + AnimateHideWindow_Fade(window); + return true; + case WINDOW_VISIBILITY_ANIMATION_TYPE_ROTATE: + AnimateHideWindow_Rotate(window); + return true; + default: + return false; + } +} + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// +// ImplicitHidingWindowAnimationObserver + +ImplicitHidingWindowAnimationObserver::ImplicitHidingWindowAnimationObserver( + aura::Window* window, + ui::ScopedLayerAnimationSettings* settings) + : HidingWindowAnimationObserverBase(window) { + settings->AddObserver(this); +} + +void ImplicitHidingWindowAnimationObserver::OnImplicitAnimationsCompleted() { + OnAnimationCompleted(); +} + +//////////////////////////////////////////////////////////////////////////////// +// ScopedHidingAnimationSettings + +ScopedHidingAnimationSettings::ScopedHidingAnimationSettings( + aura::Window* window) + : layer_animation_settings_(window->layer()->GetAnimator()), + observer_(new ImplicitHidingWindowAnimationObserver( + window, + &layer_animation_settings_)) { +} + +ScopedHidingAnimationSettings::~ScopedHidingAnimationSettings() { + observer_->DetachAndRecreateLayers(); +} + +//////////////////////////////////////////////////////////////////////////////// +// External interface + +void SetWindowVisibilityAnimationType(aura::Window* window, int type) { + window->SetProperty(kWindowVisibilityAnimationTypeKey, type); +} + +int GetWindowVisibilityAnimationType(aura::Window* window) { + return window->GetProperty(kWindowVisibilityAnimationTypeKey); +} + +void SetWindowVisibilityAnimationTransition( + aura::Window* window, + WindowVisibilityAnimationTransition transition) { + window->SetProperty(kWindowVisibilityAnimationTransitionKey, transition); +} + +bool HasWindowVisibilityAnimationTransition( + aura::Window* window, + WindowVisibilityAnimationTransition transition) { + WindowVisibilityAnimationTransition prop = window->GetProperty( + kWindowVisibilityAnimationTransitionKey); + return (prop & transition) != 0; +} + +void SetWindowVisibilityAnimationDuration(aura::Window* window, + const base::TimeDelta& duration) { + window->SetProperty(kWindowVisibilityAnimationDurationKey, + static_cast<int>(duration.ToInternalValue())); +} + +base::TimeDelta GetWindowVisibilityAnimationDuration( + const aura::Window& window) { + return base::TimeDelta::FromInternalValue( + window.GetProperty(kWindowVisibilityAnimationDurationKey)); +} + +void SetWindowVisibilityAnimationVerticalPosition(aura::Window* window, + float position) { + window->SetProperty(kWindowVisibilityAnimationVerticalPositionKey, position); +} + +bool AnimateOnChildWindowVisibilityChanged(aura::Window* window, bool visible) { + if (WindowAnimationsDisabled(window)) + return false; + if (visible) + return AnimateShowWindow(window); + // Don't start hiding the window again if it's already being hidden. + return window->layer()->GetTargetOpacity() != 0.0f && + AnimateHideWindow(window); +} + +bool AnimateWindow(aura::Window* window, WindowAnimationType type) { + switch (type) { + case WINDOW_ANIMATION_TYPE_BOUNCE: + AnimateBounce(window); + return true; + default: + NOTREACHED(); + return false; + } +} + +bool WindowAnimationsDisabled(aura::Window* window) { + return (!gfx::Animation::ShouldRenderRichAnimation() || (window && + window->GetProperty(aura::client::kAnimationsDisabledKey)) || + CommandLine::ForCurrentProcess()->HasSwitch( + switches::kWindowAnimationsDisabled)); +} + +} // namespace wm diff --git a/chromium/ui/wm/core/window_animations.h b/chromium/ui/wm/core/window_animations.h new file mode 100644 index 00000000000..422167e35d7 --- /dev/null +++ b/chromium/ui/wm/core/window_animations.h @@ -0,0 +1,117 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_WM_CORE_WINDOW_ANIMATIONS_H_ +#define UI_WM_CORE_WINDOW_ANIMATIONS_H_ + +#include <vector> + +#include "ui/compositor/scoped_layer_animation_settings.h" +#include "ui/wm/wm_export.h" + +namespace aura { +class Window; +} +namespace base { +class TimeDelta; +} +namespace gfx { +class Rect; +} +namespace ui { +class ImplicitAnimationObserver; +class Layer; +class LayerAnimationSequence; +} + +namespace wm { + +// A variety of canned animations for window transitions. +enum WindowVisibilityAnimationType { + WINDOW_VISIBILITY_ANIMATION_TYPE_DEFAULT = 0, // Default. Lets the system + // decide based on window + // type. + WINDOW_VISIBILITY_ANIMATION_TYPE_DROP, // Window shrinks in. + WINDOW_VISIBILITY_ANIMATION_TYPE_VERTICAL, // Vertical Glenimation. + WINDOW_VISIBILITY_ANIMATION_TYPE_FADE, // Fades in/out. + WINDOW_VISIBILITY_ANIMATION_TYPE_ROTATE, // Window rotates in. + + // Downstream library animations start above this point. + WINDOW_VISIBILITY_ANIMATION_MAX +}; + +// Canned animations that take effect once but don't have a symmetric pair as +// visibility animations do. +enum WindowAnimationType { + WINDOW_ANIMATION_TYPE_BOUNCE = 0, // Window scales up and down. +}; + +// Type of visibility change transition that a window should animate. +// Default behavior is to animate both show and hide. +enum WindowVisibilityAnimationTransition { + ANIMATE_SHOW = 0x1, + ANIMATE_HIDE = 0x2, + ANIMATE_BOTH = ANIMATE_SHOW | ANIMATE_HIDE, + ANIMATE_NONE = 0x4, +}; + +// These two methods use int for type rather than WindowVisibilityAnimationType +// since downstream libraries can extend the set of animations. +WM_EXPORT void SetWindowVisibilityAnimationType(aura::Window* window, int type); +WM_EXPORT int GetWindowVisibilityAnimationType(aura::Window* window); + +WM_EXPORT void SetWindowVisibilityAnimationTransition( + aura::Window* window, + WindowVisibilityAnimationTransition transition); + +WM_EXPORT bool HasWindowVisibilityAnimationTransition( + aura::Window* window, + WindowVisibilityAnimationTransition transition); + +WM_EXPORT void SetWindowVisibilityAnimationDuration( + aura::Window* window, + const base::TimeDelta& duration); + +WM_EXPORT base::TimeDelta GetWindowVisibilityAnimationDuration( + const aura::Window& window); + +WM_EXPORT void SetWindowVisibilityAnimationVerticalPosition( + aura::Window* window, + float position); + +class ImplicitHidingWindowAnimationObserver; +// A wrapper of ui::ScopedLayerAnimationSettings for implicit hiding animations. +// Use this to ensure that the hiding animation is visible even after +// the window is deleted or deactivated, instead of using +// ui::ScopedLayerAnimationSettings directly. +class WM_EXPORT ScopedHidingAnimationSettings { + public: + explicit ScopedHidingAnimationSettings(aura::Window* window); + ~ScopedHidingAnimationSettings(); + + // Returns the wrapped ScopedLayeAnimationSettings instance. + ui::ScopedLayerAnimationSettings* layer_animation_settings() { + return &layer_animation_settings_; + } + + private: + ui::ScopedLayerAnimationSettings layer_animation_settings_; + ImplicitHidingWindowAnimationObserver* observer_; + + DISALLOW_COPY_AND_ASSIGN(ScopedHidingAnimationSettings); +}; + +// Returns false if the |window| didn't animate. +WM_EXPORT bool AnimateOnChildWindowVisibilityChanged(aura::Window* window, + bool visible); +WM_EXPORT bool AnimateWindow(aura::Window* window, WindowAnimationType type); + +// Returns true if window animations are disabled for |window|. Window +// animations are enabled by default. If |window| is NULL, this just checks +// if the global flag disabling window animations is present. +WM_EXPORT bool WindowAnimationsDisabled(aura::Window* window); + +} // namespace wm + +#endif // UI_WM_CORE_WINDOW_ANIMATIONS_H_ diff --git a/chromium/ui/wm/core/window_animations_unittest.cc b/chromium/ui/wm/core/window_animations_unittest.cc new file mode 100644 index 00000000000..5968bf32440 --- /dev/null +++ b/chromium/ui/wm/core/window_animations_unittest.cc @@ -0,0 +1,291 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/window_animations.h" + +#include "base/time/time.h" +#include "ui/aura/test/aura_test_base.h" +#include "ui/aura/test/test_windows.h" +#include "ui/aura/window.h" +#include "ui/compositor/layer.h" +#include "ui/compositor/layer_animator.h" +#include "ui/compositor/scoped_animation_duration_scale_mode.h" +#include "ui/gfx/animation/animation_container_element.h" +#include "ui/gfx/vector2d.h" +#include "ui/wm/core/transient_window_manager.h" +#include "ui/wm/core/transient_window_stacking_client.h" +#include "ui/wm/core/window_util.h" +#include "ui/wm/public/animation_host.h" + +using aura::Window; +using ui::Layer; + +namespace wm { +namespace { + +template<typename T>int GetZPosition(const T* child) { + const T* parent = child->parent(); + const std::vector<T*> children = parent->children(); + typename std::vector<T*>::const_iterator iter = + std::find(children.begin(), children.end(), child); + DCHECK(iter != children.end()); + return iter - children.begin(); +} + +int GetWindowZPosition(const aura::Window* child) { + return GetZPosition<aura::Window>(child); +} + +int GetLayerZPosition(const ui::Layer* child) { + return GetZPosition<ui::Layer>(child); +} + +} // namespace + +class WindowAnimationsTest : public aura::test::AuraTestBase { + public: + WindowAnimationsTest() {} + + virtual void TearDown() OVERRIDE { + AuraTestBase::TearDown(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(WindowAnimationsTest); +}; + +TEST_F(WindowAnimationsTest, LayerTargetVisibility) { + scoped_ptr<aura::Window> window( + aura::test::CreateTestWindowWithId(0, NULL)); + + // Layer target visibility changes according to Show/Hide. + window->Show(); + EXPECT_TRUE(window->layer()->GetTargetVisibility()); + window->Hide(); + EXPECT_FALSE(window->layer()->GetTargetVisibility()); + window->Show(); + EXPECT_TRUE(window->layer()->GetTargetVisibility()); +} + +TEST_F(WindowAnimationsTest, LayerTargetVisibility_AnimateShow) { + // Tests if opacity and transform are reset when only show animation is + // enabled. See also LayerTargetVisibility_AnimateHide. + // Since the window is not visible after Hide() is called, opacity and + // transform shouldn't matter in case of ANIMATE_SHOW, but we reset them + // to keep consistency. + + scoped_ptr<aura::Window> window(aura::test::CreateTestWindowWithId(0, NULL)); + SetWindowVisibilityAnimationTransition(window.get(), ANIMATE_SHOW); + + // Layer target visibility and opacity change according to Show/Hide. + window->Show(); + AnimateOnChildWindowVisibilityChanged(window.get(), true); + EXPECT_TRUE(window->layer()->GetTargetVisibility()); + EXPECT_EQ(1, window->layer()->opacity()); + + window->Hide(); + AnimateOnChildWindowVisibilityChanged(window.get(), false); + EXPECT_FALSE(window->layer()->GetTargetVisibility()); + EXPECT_EQ(0, window->layer()->opacity()); + EXPECT_EQ(gfx::Transform(), window->layer()->transform()); + + window->Show(); + AnimateOnChildWindowVisibilityChanged(window.get(), true); + EXPECT_TRUE(window->layer()->GetTargetVisibility()); + EXPECT_EQ(1, window->layer()->opacity()); +} + +TEST_F(WindowAnimationsTest, LayerTargetVisibility_AnimateHide) { + // Tests if opacity and transform are reset when only hide animation is + // enabled. Hide animation changes opacity and transform in addition to + // visibility, so we need to reset not only visibility but also opacity + // and transform to show the window. + + scoped_ptr<aura::Window> window(aura::test::CreateTestWindowWithId(0, NULL)); + SetWindowVisibilityAnimationTransition(window.get(), ANIMATE_HIDE); + + // Layer target visibility and opacity change according to Show/Hide. + window->Show(); + AnimateOnChildWindowVisibilityChanged(window.get(), true); + EXPECT_TRUE(window->layer()->GetTargetVisibility()); + EXPECT_EQ(1, window->layer()->opacity()); + EXPECT_EQ(gfx::Transform(), window->layer()->transform()); + + window->Hide(); + AnimateOnChildWindowVisibilityChanged(window.get(), false); + EXPECT_FALSE(window->layer()->GetTargetVisibility()); + EXPECT_EQ(0, window->layer()->opacity()); + + window->Show(); + AnimateOnChildWindowVisibilityChanged(window.get(), true); + EXPECT_TRUE(window->layer()->GetTargetVisibility()); + EXPECT_EQ(1, window->layer()->opacity()); + EXPECT_EQ(gfx::Transform(), window->layer()->transform()); +} + +TEST_F(WindowAnimationsTest, HideAnimationDetachLayers) { + scoped_ptr<aura::Window> parent(aura::test::CreateTestWindowWithId(0, NULL)); + + scoped_ptr<aura::Window> other( + aura::test::CreateTestWindowWithId(1, parent.get())); + + scoped_ptr<aura::Window> animating_window( + aura::test::CreateTestWindowWithId(2, parent.get())); + SetWindowVisibilityAnimationTransition(animating_window.get(), ANIMATE_HIDE); + + EXPECT_EQ(0, GetWindowZPosition(other.get())); + EXPECT_EQ(1, GetWindowZPosition(animating_window.get())); + EXPECT_EQ(0, GetLayerZPosition(other->layer())); + EXPECT_EQ(1, GetLayerZPosition(animating_window->layer())); + + { + ui::ScopedAnimationDurationScaleMode scale_mode( + ui::ScopedAnimationDurationScaleMode::FAST_DURATION); + ui::Layer* animating_layer = animating_window->layer(); + + animating_window->Hide(); + EXPECT_TRUE(AnimateOnChildWindowVisibilityChanged( + animating_window.get(), false)); + EXPECT_TRUE(animating_layer->GetAnimator()->is_animating()); + EXPECT_FALSE(animating_layer->delegate()); + + // Make sure the Hide animation create another layer, and both are in + // the parent layer. + EXPECT_NE(animating_window->layer(), animating_layer); + EXPECT_TRUE( + std::find(parent->layer()->children().begin(), + parent->layer()->children().end(), + animating_layer) != + parent->layer()->children().end()); + EXPECT_TRUE( + std::find(parent->layer()->children().begin(), + parent->layer()->children().end(), + animating_window->layer()) != + parent->layer()->children().end()); + // Current layer must be already hidden. + EXPECT_FALSE(animating_window->layer()->visible()); + + EXPECT_EQ(1, GetWindowZPosition(animating_window.get())); + EXPECT_EQ(1, GetLayerZPosition(animating_window->layer())); + EXPECT_EQ(2, GetLayerZPosition(animating_layer)); + + parent->StackChildAtTop(other.get()); + EXPECT_EQ(0, GetWindowZPosition(animating_window.get())); + EXPECT_EQ(1, GetWindowZPosition(other.get())); + + EXPECT_EQ(0, GetLayerZPosition(animating_window->layer())); + EXPECT_EQ(1, GetLayerZPosition(other->layer())); + // Make sure the animating layer is on top. + EXPECT_EQ(2, GetLayerZPosition(animating_layer)); + + // Animating layer must be gone + animating_layer->GetAnimator()->StopAnimating(); + EXPECT_TRUE( + std::find(parent->layer()->children().begin(), + parent->layer()->children().end(), + animating_layer) == + parent->layer()->children().end()); + } +} + +TEST_F(WindowAnimationsTest, HideAnimationDetachLayersWithTransientChildren) { + TransientWindowStackingClient transient_stacking_client; + + scoped_ptr<aura::Window> parent(aura::test::CreateTestWindowWithId(0, NULL)); + + scoped_ptr<aura::Window> other( + aura::test::CreateTestWindowWithId(1, parent.get())); + + scoped_ptr<aura::Window> animating_window( + aura::test::CreateTestWindowWithId(2, parent.get())); + SetWindowVisibilityAnimationTransition(animating_window.get(), ANIMATE_HIDE); + + scoped_ptr<aura::Window> transient1( + aura::test::CreateTestWindowWithId(3, parent.get())); + scoped_ptr<aura::Window> transient2( + aura::test::CreateTestWindowWithId(4, parent.get())); + + TransientWindowManager::Get(animating_window.get()); + AddTransientChild(animating_window.get(), transient1.get()); + AddTransientChild(animating_window.get(), transient2.get()); + + EXPECT_EQ(0, GetWindowZPosition(other.get())); + EXPECT_EQ(1, GetWindowZPosition(animating_window.get())); + EXPECT_EQ(2, GetWindowZPosition(transient1.get())); + EXPECT_EQ(3, GetWindowZPosition(transient2.get())); + + { + ui::ScopedAnimationDurationScaleMode scale_mode( + ui::ScopedAnimationDurationScaleMode::FAST_DURATION); + ui::Layer* animating_layer = animating_window->layer(); + + animating_window->Hide(); + EXPECT_TRUE(AnimateOnChildWindowVisibilityChanged( + animating_window.get(), false)); + EXPECT_TRUE(animating_layer->GetAnimator()->is_animating()); + EXPECT_FALSE(animating_layer->delegate()); + + EXPECT_EQ(1, GetWindowZPosition(animating_window.get())); + EXPECT_EQ(2, GetWindowZPosition(transient1.get())); + EXPECT_EQ(3, GetWindowZPosition(transient2.get())); + + EXPECT_EQ(1, GetLayerZPosition(animating_window->layer())); + EXPECT_EQ(2, GetLayerZPosition(transient1->layer())); + EXPECT_EQ(3, GetLayerZPosition(transient2->layer())); + EXPECT_EQ(4, GetLayerZPosition(animating_layer)); + + parent->StackChildAtTop(other.get()); + + EXPECT_EQ(0, GetWindowZPosition(animating_window.get())); + EXPECT_EQ(1, GetWindowZPosition(transient1.get())); + EXPECT_EQ(2, GetWindowZPosition(transient2.get())); + EXPECT_EQ(3, GetWindowZPosition(other.get())); + + EXPECT_EQ(0, GetLayerZPosition(animating_window->layer())); + EXPECT_EQ(1, GetLayerZPosition(transient1->layer())); + EXPECT_EQ(2, GetLayerZPosition(transient2->layer())); + EXPECT_EQ(3, GetLayerZPosition(other->layer())); + // Make sure the animating layer is on top of all windows. + EXPECT_EQ(4, GetLayerZPosition(animating_layer)); + } +} + +// A simple AnimationHost implementation for the NotifyHideCompleted test. +class NotifyHideCompletedAnimationHost : public aura::client::AnimationHost { + public: + NotifyHideCompletedAnimationHost() : hide_completed_(false) {} + virtual ~NotifyHideCompletedAnimationHost() {} + + // Overridden from TestWindowDelegate: + virtual void OnWindowHidingAnimationCompleted() OVERRIDE { + hide_completed_ = true; + } + + virtual void SetHostTransitionOffsets( + const gfx::Vector2d& top_left, + const gfx::Vector2d& bottom_right) OVERRIDE {} + + bool hide_completed() const { return hide_completed_; } + + private: + bool hide_completed_; + + DISALLOW_COPY_AND_ASSIGN(NotifyHideCompletedAnimationHost); +}; + +TEST_F(WindowAnimationsTest, NotifyHideCompleted) { + NotifyHideCompletedAnimationHost animation_host; + scoped_ptr<aura::Window> window(aura::test::CreateTestWindowWithId(0, NULL)); + aura::client::SetAnimationHost(window.get(), &animation_host); + wm::SetWindowVisibilityAnimationType( + window.get(), WINDOW_VISIBILITY_ANIMATION_TYPE_FADE); + AnimateOnChildWindowVisibilityChanged(window.get(), true); + EXPECT_TRUE(window->layer()->visible()); + + EXPECT_FALSE(animation_host.hide_completed()); + AnimateOnChildWindowVisibilityChanged(window.get(), false); + EXPECT_TRUE(animation_host.hide_completed()); +} + +} // namespace wm diff --git a/chromium/ui/wm/core/window_modality_controller.cc b/chromium/ui/wm/core/window_modality_controller.cc new file mode 100644 index 00000000000..1df375b439a --- /dev/null +++ b/chromium/ui/wm/core/window_modality_controller.cc @@ -0,0 +1,201 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/window_modality_controller.h" + +#include <algorithm> + +#include "ui/aura/client/aura_constants.h" +#include "ui/aura/client/capture_client.h" +#include "ui/aura/env.h" +#include "ui/aura/window.h" +#include "ui/aura/window_event_dispatcher.h" +#include "ui/aura/window_property.h" +#include "ui/base/ui_base_types.h" +#include "ui/events/event.h" +#include "ui/events/event_target.h" +#include "ui/events/gestures/gesture_recognizer.h" +#include "ui/wm/core/window_animations.h" +#include "ui/wm/core/window_util.h" + +namespace wm { + +// Transient child's modal parent. +extern const aura::WindowProperty<aura::Window*>* const kModalParentKey; +DEFINE_WINDOW_PROPERTY_KEY(aura::Window*, kModalParentKey, NULL); + +namespace { + +bool HasAncestor(aura::Window* window, aura::Window* ancestor) { + if (!window) + return false; + if (window == ancestor) + return true; + return HasAncestor(window->parent(), ancestor); +} + +bool TransientChildIsWindowModal(aura::Window* window) { + return window->GetProperty(aura::client::kModalKey) == ui::MODAL_TYPE_WINDOW; +} + +bool TransientChildIsSystemModal(aura::Window* window) { + return window->GetProperty(aura::client::kModalKey) == ui::MODAL_TYPE_SYSTEM; +} + +bool TransientChildIsChildModal(aura::Window* window) { + return window->GetProperty(aura::client::kModalKey) == ui::MODAL_TYPE_CHILD; +} + +aura::Window* GetModalParent(aura::Window* window) { + return window->GetProperty(kModalParentKey); +} + +bool IsModalTransientChild(aura::Window* transient, aura::Window* original) { + return transient->IsVisible() && + (TransientChildIsWindowModal(transient) || + TransientChildIsSystemModal(transient) || + (TransientChildIsChildModal(transient) && + (HasAncestor(original, GetModalParent(transient))))); +} + +aura::Window* GetModalTransientChild( + aura::Window* activatable, + aura::Window* original) { + for (aura::Window::Windows::const_iterator it = + GetTransientChildren(activatable).begin(); + it != GetTransientChildren(activatable).end(); + ++it) { + aura::Window* transient = *it; + if (IsModalTransientChild(transient, original)) { + return GetTransientChildren(transient).empty() ? + transient : GetModalTransientChild(transient, original); + } + } + return NULL; +} + +} // namespace + +void SetModalParent(aura::Window* child, aura::Window* parent) { + child->SetProperty(kModalParentKey, parent); +} + +aura::Window* GetModalTransient(aura::Window* window) { + if (!window) + return NULL; + + // We always want to check the for the transient child of the toplevel window. + aura::Window* toplevel = GetToplevelWindow(window); + if (!toplevel) + return NULL; + + return GetModalTransientChild(toplevel, window); +} + +//////////////////////////////////////////////////////////////////////////////// +// WindowModalityController, public: + +WindowModalityController::WindowModalityController( + ui::EventTarget* event_target) + : event_target_(event_target) { + aura::Env::GetInstance()->AddObserver(this); + DCHECK(event_target->IsPreTargetListEmpty()); + event_target_->AddPreTargetHandler(this); +} + +WindowModalityController::~WindowModalityController() { + event_target_->RemovePreTargetHandler(this); + aura::Env::GetInstance()->RemoveObserver(this); + for (size_t i = 0; i < windows_.size(); ++i) + windows_[i]->RemoveObserver(this); +} + +//////////////////////////////////////////////////////////////////////////////// +// WindowModalityController, aura::EventFilter implementation: + +void WindowModalityController::OnKeyEvent(ui::KeyEvent* event) { + aura::Window* target = static_cast<aura::Window*>(event->target()); + if (GetModalTransient(target)) + event->SetHandled(); +} + +void WindowModalityController::OnMouseEvent(ui::MouseEvent* event) { + aura::Window* target = static_cast<aura::Window*>(event->target()); + if (ProcessLocatedEvent(target, event)) + event->SetHandled(); +} + +void WindowModalityController::OnTouchEvent(ui::TouchEvent* event) { + aura::Window* target = static_cast<aura::Window*>(event->target()); + if (ProcessLocatedEvent(target, event)) + event->SetHandled(); +} + +//////////////////////////////////////////////////////////////////////////////// +// WindowModalityController, aura::EnvObserver implementation: + +void WindowModalityController::OnWindowInitialized(aura::Window* window) { + windows_.push_back(window); + window->AddObserver(this); +} + +//////////////////////////////////////////////////////////////////////////////// +// WindowModalityController, aura::WindowObserver implementation: + +void WindowModalityController::OnWindowPropertyChanged(aura::Window* window, + const void* key, + intptr_t old) { + // In tests, we sometimes create the modality relationship after a window is + // visible. + if (key == aura::client::kModalKey && + window->GetProperty(aura::client::kModalKey) != ui::MODAL_TYPE_NONE && + window->IsVisible()) { + ActivateWindow(window); + ui::GestureRecognizer::Get()->TransferEventsTo(GetTransientParent(window), + NULL); + } +} + +void WindowModalityController::OnWindowVisibilityChanged( + aura::Window* window, + bool visible) { + if (visible && window->GetProperty(aura::client::kModalKey) != + ui::MODAL_TYPE_NONE) { + ui::GestureRecognizer::Get()->TransferEventsTo(GetTransientParent(window), + NULL); + // Make sure no other window has capture, otherwise |window| won't get mouse + // events. + aura::Window* capture_window = aura::client::GetCaptureWindow(window); + if (capture_window) + capture_window->ReleaseCapture(); + } +} + +void WindowModalityController::OnWindowDestroyed(aura::Window* window) { + windows_.erase(std::find(windows_.begin(), windows_.end(), window)); + window->RemoveObserver(this); +} + +bool WindowModalityController::ProcessLocatedEvent(aura::Window* target, + ui::LocatedEvent* event) { + if (event->handled()) + return false; + aura::Window* modal_transient_child = GetModalTransient(target); + if (modal_transient_child && (event->type() == ui::ET_MOUSE_PRESSED || + event->type() == ui::ET_TOUCH_PRESSED)) { + // Activate top window if transient child window is window modal. + if (TransientChildIsWindowModal(modal_transient_child)) { + aura::Window* toplevel = GetToplevelWindow(target); + DCHECK(toplevel); + ActivateWindow(toplevel); + } + + AnimateWindow(modal_transient_child, WINDOW_ANIMATION_TYPE_BOUNCE); + } + if (event->type() == ui::ET_TOUCH_CANCELLED) + return false; + return !!modal_transient_child; +} + +} // namespace wm diff --git a/chromium/ui/wm/core/window_modality_controller.h b/chromium/ui/wm/core/window_modality_controller.h new file mode 100644 index 00000000000..4d52c2190ba --- /dev/null +++ b/chromium/ui/wm/core/window_modality_controller.h @@ -0,0 +1,71 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_WM_CORE_WINDOW_MODALITY_CONTROLLER_H_ +#define UI_WM_CORE_WINDOW_MODALITY_CONTROLLER_H_ + +#include <vector> + +#include "base/compiler_specific.h" +#include "ui/aura/env_observer.h" +#include "ui/aura/window_observer.h" +#include "ui/events/event_handler.h" +#include "ui/wm/wm_export.h" + +namespace ui { +class EventTarget; +class LocatedEvent; +} + +namespace wm { + +// Sets the modal parent for the child. +WM_EXPORT void SetModalParent(aura::Window* child, aura::Window* parent); + +// Returns the modal transient child of |window|, or NULL if |window| does not +// have any modal transient children. +WM_EXPORT aura::Window* GetModalTransient(aura::Window* window); + +// WindowModalityController is an event filter that consumes events sent to +// windows that are the transient parents of window-modal windows. This filter +// must be added to the CompoundEventFilter so that activation works properly. +class WM_EXPORT WindowModalityController : public ui::EventHandler, + public aura::EnvObserver, + public aura::WindowObserver { + public: + explicit WindowModalityController(ui::EventTarget* event_target); + virtual ~WindowModalityController(); + + // Overridden from ui::EventHandler: + virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE; + virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE; + virtual void OnTouchEvent(ui::TouchEvent* event) OVERRIDE; + + // Overridden from aura::EnvObserver: + virtual void OnWindowInitialized(aura::Window* window) OVERRIDE; + + // Overridden from aura::WindowObserver: + virtual void OnWindowPropertyChanged(aura::Window* window, + const void* key, + intptr_t old) OVERRIDE; + virtual void OnWindowVisibilityChanged(aura::Window* window, + bool visible) OVERRIDE; + virtual void OnWindowDestroyed(aura::Window* window) OVERRIDE; + + private: + // Processes a mouse/touch event, and returns true if the event should be + // consumed. + bool ProcessLocatedEvent(aura::Window* target, + ui::LocatedEvent* event); + + std::vector<aura::Window*> windows_; + + ui::EventTarget* event_target_; + + DISALLOW_COPY_AND_ASSIGN(WindowModalityController); +}; + +} // namespace wm + +#endif // UI_WM_CORE_WINDOW_MODALITY_CONTROLLER_H_ diff --git a/chromium/ui/wm/core/window_util.cc b/chromium/ui/wm/core/window_util.cc new file mode 100644 index 00000000000..6d8cdc97ba0 --- /dev/null +++ b/chromium/ui/wm/core/window_util.cc @@ -0,0 +1,129 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/window_util.h" + +#include "ui/aura/window.h" +#include "ui/aura/window_event_dispatcher.h" +#include "ui/compositor/layer.h" +#include "ui/compositor/layer_tree_owner.h" +#include "ui/wm/core/transient_window_manager.h" +#include "ui/wm/public/activation_client.h" + +namespace { + +// Invokes RecreateLayer() on all the children of |to_clone|, adding the newly +// cloned children to |parent|. +// +// WARNING: It is assumed that |parent| is ultimately owned by a LayerTreeOwner. +void CloneChildren(ui::Layer* to_clone, ui::Layer* parent) { + typedef std::vector<ui::Layer*> Layers; + // Make a copy of the children since RecreateLayer() mutates it. + Layers children(to_clone->children()); + for (Layers::const_iterator i = children.begin(); i != children.end(); ++i) { + ui::LayerOwner* owner = (*i)->owner(); + ui::Layer* old_layer = owner ? owner->RecreateLayer().release() : NULL; + if (old_layer) { + parent->Add(old_layer); + // RecreateLayer() moves the existing children to the new layer. Create a + // copy of those. + CloneChildren(owner->layer(), old_layer); + } + } +} + +} // namespace + +namespace wm { + +void ActivateWindow(aura::Window* window) { + DCHECK(window); + DCHECK(window->GetRootWindow()); + aura::client::GetActivationClient(window->GetRootWindow())->ActivateWindow( + window); +} + +void DeactivateWindow(aura::Window* window) { + DCHECK(window); + DCHECK(window->GetRootWindow()); + aura::client::GetActivationClient(window->GetRootWindow())->DeactivateWindow( + window); +} + +bool IsActiveWindow(aura::Window* window) { + DCHECK(window); + if (!window->GetRootWindow()) + return false; + aura::client::ActivationClient* client = + aura::client::GetActivationClient(window->GetRootWindow()); + return client && client->GetActiveWindow() == window; +} + +bool CanActivateWindow(aura::Window* window) { + DCHECK(window); + if (!window->GetRootWindow()) + return false; + aura::client::ActivationClient* client = + aura::client::GetActivationClient(window->GetRootWindow()); + return client && client->CanActivateWindow(window); +} + +aura::Window* GetActivatableWindow(aura::Window* window) { + aura::client::ActivationClient* client = + aura::client::GetActivationClient(window->GetRootWindow()); + return client ? client->GetActivatableWindow(window) : NULL; +} + +aura::Window* GetToplevelWindow(aura::Window* window) { + aura::client::ActivationClient* client = + aura::client::GetActivationClient(window->GetRootWindow()); + return client ? client->GetToplevelWindow(window) : NULL; +} + +scoped_ptr<ui::LayerTreeOwner> RecreateLayers(ui::LayerOwner* root) { + scoped_ptr<ui::LayerTreeOwner> old_layer( + new ui::LayerTreeOwner(root->RecreateLayer().release())); + if (old_layer->root()) + CloneChildren(root->layer(), old_layer->root()); + return old_layer.Pass(); +} + +aura::Window* GetTransientParent(aura::Window* window) { + return const_cast<aura::Window*>(GetTransientParent( + const_cast<const aura::Window*>(window))); +} + +const aura::Window* GetTransientParent(const aura::Window* window) { + const TransientWindowManager* manager = TransientWindowManager::Get(window); + return manager ? manager->transient_parent() : NULL; +} + +const std::vector<aura::Window*>& GetTransientChildren( + const aura::Window* window) { + const TransientWindowManager* manager = TransientWindowManager::Get(window); + if (manager) + return manager->transient_children(); + + static std::vector<aura::Window*>* shared = new std::vector<aura::Window*>; + return *shared; +} + +void AddTransientChild(aura::Window* parent, aura::Window* child) { + TransientWindowManager::Get(parent)->AddTransientChild(child); +} + +void RemoveTransientChild(aura::Window* parent, aura::Window* child) { + TransientWindowManager::Get(parent)->RemoveTransientChild(child); +} + +bool HasTransientAncestor(const aura::Window* window, + const aura::Window* ancestor) { + const aura::Window* transient_parent = GetTransientParent(window); + if (transient_parent == ancestor) + return true; + return transient_parent ? + HasTransientAncestor(transient_parent, ancestor) : false; +} + +} // namespace wm diff --git a/chromium/ui/wm/core/window_util.h b/chromium/ui/wm/core/window_util.h new file mode 100644 index 00000000000..78f2ffbf112 --- /dev/null +++ b/chromium/ui/wm/core/window_util.h @@ -0,0 +1,67 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_WM_CORE_WINDOW_UTIL_H_ +#define UI_WM_CORE_WINDOW_UTIL_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "ui/wm/wm_export.h" + +namespace aura { +class Window; +} + +namespace ui { +class Layer; +class LayerOwner; +class LayerTreeOwner; +} + +namespace wm { + +WM_EXPORT void ActivateWindow(aura::Window* window); +WM_EXPORT void DeactivateWindow(aura::Window* window); +WM_EXPORT bool IsActiveWindow(aura::Window* window); +WM_EXPORT bool CanActivateWindow(aura::Window* window); + +// Retrieves the activatable window for |window|. The ActivationClient makes +// this determination. +WM_EXPORT aura::Window* GetActivatableWindow(aura::Window* window); + +// Retrieves the toplevel window for |window|. The ActivationClient makes this +// determination. +WM_EXPORT aura::Window* GetToplevelWindow(aura::Window* window); + +// Returns the existing Layer for |root| (and all its descendants) and creates +// a new layer for |root| and all its descendants. This is intended for +// animations that want to animate between the existing visuals and a new state. +// +// As a result of this |root| has freshly created layers, meaning the layers +// have not yet been painted to. +WM_EXPORT scoped_ptr<ui::LayerTreeOwner> RecreateLayers( + ui::LayerOwner* root); + +// Convenience functions that get the TransientWindowManager for the window and +// redirect appropriately. These are preferable to calling functions on +// TransientWindowManager as they handle the appropriate NULL checks. +WM_EXPORT aura::Window* GetTransientParent(aura::Window* window); +WM_EXPORT const aura::Window* GetTransientParent( + const aura::Window* window); +WM_EXPORT const std::vector<aura::Window*>& GetTransientChildren( + const aura::Window* window); +WM_EXPORT void AddTransientChild(aura::Window* parent, aura::Window* child); +WM_EXPORT void RemoveTransientChild(aura::Window* parent, aura::Window* child); + +// Returns true if |window| has |ancestor| as a transient ancestor. A transient +// ancestor is found by following the transient parent chain of the window. +WM_EXPORT bool HasTransientAncestor(const aura::Window* window, + const aura::Window* ancestor); + +} // namespace wm + +#endif // UI_WM_CORE_WINDOW_UTIL_H_ diff --git a/chromium/ui/wm/core/window_util_unittest.cc b/chromium/ui/wm/core/window_util_unittest.cc new file mode 100644 index 00000000000..35be2259179 --- /dev/null +++ b/chromium/ui/wm/core/window_util_unittest.cc @@ -0,0 +1,52 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/window_util.h" + +#include "base/memory/scoped_ptr.h" +#include "ui/aura/test/aura_test_base.h" +#include "ui/aura/test/test_windows.h" +#include "ui/aura/window.h" +#include "ui/compositor/layer.h" +#include "ui/compositor/layer_tree_owner.h" + +namespace wm { + +typedef aura::test::AuraTestBase WindowUtilTest; + +// Test if the recreate layers does not recreate layers that have +// already been acquired. +TEST_F(WindowUtilTest, RecreateLayers) { + scoped_ptr<aura::Window> window1( + aura::test::CreateTestWindowWithId(0, NULL)); + scoped_ptr<aura::Window> window11( + aura::test::CreateTestWindowWithId(1, window1.get())); + scoped_ptr<aura::Window> window12( + aura::test::CreateTestWindowWithId(2, window1.get())); + + ASSERT_EQ(2u, window1->layer()->children().size()); + + scoped_ptr<ui::Layer> acquired(window11->AcquireLayer()); + EXPECT_TRUE(acquired.get()); + EXPECT_EQ(acquired.get(), window11->layer()); + + scoped_ptr<ui::LayerTreeOwner> tree = + wm::RecreateLayers(window1.get()); + + // The detached layer should not have the layer that has + // already been detached. + ASSERT_EQ(1u, tree->root()->children().size()); + // Child layer is new instance. + EXPECT_NE(window11->layer(), tree->root()->children()[0]); + EXPECT_NE(window12->layer(), tree->root()->children()[0]); + + // The original window should have both. + ASSERT_EQ(2u, window1->layer()->children().size()); + EXPECT_EQ(window11->layer(), window1->layer()->children()[0]); + EXPECT_EQ(window12->layer(), window1->layer()->children()[1]); + + // Delete the window before the acquired layer is deleted. + window11.reset(); +} +} // namespace wm diff --git a/chromium/ui/wm/core/wm_core_switches.cc b/chromium/ui/wm/core/wm_core_switches.cc new file mode 100644 index 00000000000..12fbfcca9df --- /dev/null +++ b/chromium/ui/wm/core/wm_core_switches.cc @@ -0,0 +1,16 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/wm_core_switches.h" + +#include "base/command_line.h" + +namespace wm { +namespace switches { + +// If present animations are disabled. +const char kWindowAnimationsDisabled[] = "wm-window-animations-disabled"; + +} // namespace switches +} // namespace wm diff --git a/chromium/ui/wm/core/wm_core_switches.h b/chromium/ui/wm/core/wm_core_switches.h new file mode 100644 index 00000000000..05658d6abbb --- /dev/null +++ b/chromium/ui/wm/core/wm_core_switches.h @@ -0,0 +1,24 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_WM_CORE_WM_CORE_SWITCHES_H_ +#define UI_WM_CORE_WM_CORE_SWITCHES_H_ + +#include "build/build_config.h" +#include "ui/wm/wm_export.h" + +namespace wm { +namespace switches { + +// Note: If you add a switch, consider if it needs to be copied to a subsequent +// command line if the process executes a new copy of itself. (For example, +// see chromeos::LoginUtil::GetOffTheRecordCommandLine().) + +// Please keep alphabetized. +WM_EXPORT extern const char kWindowAnimationsDisabled[]; + +} // namespace switches +} // namespace wm + +#endif // UI_WM_CORE_WM_CORE_SWITCHES_H_ diff --git a/chromium/ui/wm/core/wm_state.cc b/chromium/ui/wm/core/wm_state.cc new file mode 100644 index 00000000000..6d580863394 --- /dev/null +++ b/chromium/ui/wm/core/wm_state.cc @@ -0,0 +1,30 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/wm/core/wm_state.h" + +#include "ui/events/platform/platform_event_source.h" +#include "ui/wm/core/transient_window_controller.h" +#include "ui/wm/core/transient_window_stacking_client.h" + +namespace wm { + +WMState::WMState() + : window_stacking_client_(new TransientWindowStackingClient), + transient_window_client_(new TransientWindowController) { + aura::client::SetWindowStackingClient(window_stacking_client_.get()); + aura::client::SetTransientWindowClient(transient_window_client_.get()); +} + +WMState::~WMState() { + if (aura::client::GetWindowStackingClient() == window_stacking_client_.get()) + aura::client::SetWindowStackingClient(NULL); + + if (aura::client::GetTransientWindowClient() == + transient_window_client_.get()) { + aura::client::SetTransientWindowClient(NULL); + } +} + +} // namespace wm diff --git a/chromium/ui/wm/core/wm_state.h b/chromium/ui/wm/core/wm_state.h new file mode 100644 index 00000000000..289c9dac5b5 --- /dev/null +++ b/chromium/ui/wm/core/wm_state.h @@ -0,0 +1,32 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_WM_CORE_WM_STATE_H_ +#define UI_WM_CORE_WM_STATE_H_ + +#include "base/memory/scoped_ptr.h" +#include "ui/wm/wm_export.h" + +namespace wm { + +class TransientWindowController; +class TransientWindowStackingClient; + +// Installs state needed by the window manager. +class WM_EXPORT WMState { + public: + WMState(); + ~WMState(); + + // WindowStackingClient: + private: + scoped_ptr<TransientWindowStackingClient> window_stacking_client_; + scoped_ptr<TransientWindowController> transient_window_client_; + + DISALLOW_COPY_AND_ASSIGN(WMState); +}; + +} // namespace wm + +#endif // UI_WM_CORE_WM_STATE_H_ |