diff options
author | ben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-02-02 21:24:22 +0000 |
---|---|---|
committer | ben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-02-02 21:24:22 +0000 |
commit | 72fc4b91fd45c00004a69790b59ba4917ba117ed (patch) | |
tree | 908a0e34be29a3206f88cac0064d43ec00db7ab9 | |
parent | d89ff811f8c0e5033a38273979d6b367ed057b0e (diff) | |
download | chromium_src-72fc4b91fd45c00004a69790b59ba4917ba117ed.zip chromium_src-72fc4b91fd45c00004a69790b59ba4917ba117ed.tar.gz chromium_src-72fc4b91fd45c00004a69790b59ba4917ba117ed.tar.bz2 |
Copy more focus manager into V2. Comment out a bunch of stuff and add stub impls of methods with TODOs so that it builds.
BUG=none
TEST=none
Review URL: http://codereview.chromium.org/6334062
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@73513 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | ui/base/models/accelerator.h | 1 | ||||
-rw-r--r-- | ui/views/events/accelerator.cc | 179 | ||||
-rw-r--r-- | ui/views/events/accelerator.h | 32 | ||||
-rw-r--r-- | ui/views/events/event.cc | 13 | ||||
-rw-r--r-- | ui/views/events/event.h | 4 | ||||
-rw-r--r-- | ui/views/focus/focus_manager.cc | 530 | ||||
-rw-r--r-- | ui/views/focus/focus_manager.h | 344 | ||||
-rw-r--r-- | ui/views/focus/focus_manager_unittest.cc | 1730 | ||||
-rw-r--r-- | ui/views/focus/focus_search.cc | 274 | ||||
-rw-r--r-- | ui/views/focus/focus_search.h | 122 | ||||
-rw-r--r-- | ui/views/focus/view_storage.cc | 115 | ||||
-rw-r--r-- | ui/views/focus/view_storage.h | 74 | ||||
-rw-r--r-- | ui/views/view.cc | 24 | ||||
-rw-r--r-- | ui/views/view.h | 10 | ||||
-rw-r--r-- | ui/views/views.gyp | 10 |
15 files changed, 3462 insertions, 0 deletions
diff --git a/ui/base/models/accelerator.h b/ui/base/models/accelerator.h index 0017985..137f999 100644 --- a/ui/base/models/accelerator.h +++ b/ui/base/models/accelerator.h @@ -52,6 +52,7 @@ class Accelerator { return !(*this == rhs); } + // TODO(beng): unix_hacker ui::KeyboardCode GetKeyCode() const { return key_code_; } diff --git a/ui/views/events/accelerator.cc b/ui/views/events/accelerator.cc new file mode 100644 index 0000000..582c3a3 --- /dev/null +++ b/ui/views/events/accelerator.cc @@ -0,0 +1,179 @@ +// Copyright (c) 2011 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/views/events/accelerator.h" + +#if defined(OS_WIN) +#include <windows.h> +#elif defined(OS_LINUX) +#include <gdk/gdk.h> +#endif + +#include "base/i18n/rtl.h" +#include "base/logging.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "grit/app_strings.h" +#include "ui/base/l10n/l10n_util.h" + +namespace ui { + +namespace { + +bool IsShiftDown(const Accelerator& accelerator) { + return (accelerator.GetKeyCode() & VKEY_SHIFT) == VKEY_SHIFT; +} + +bool IsCtrlDown(const Accelerator& accelerator) { + return (accelerator.GetKeyCode() & VKEY_CONTROL) == VKEY_CONTROL; +} + +bool IsAltDown(const Accelerator& accelerator) { + return (accelerator.GetKeyCode() & VKEY_MENU) == VKEY_MENU; +} + +} // namespace + +string16 GetShortcutTextForAccelerator(const Accelerator& accelerator) { + int string_id = 0; + switch(accelerator.GetKeyCode()) { + case ui::VKEY_TAB: + string_id = IDS_APP_TAB_KEY; + break; + case ui::VKEY_RETURN: + string_id = IDS_APP_ENTER_KEY; + break; + case ui::VKEY_ESCAPE: + string_id = IDS_APP_ESC_KEY; + break; + case ui::VKEY_PRIOR: + string_id = IDS_APP_PAGEUP_KEY; + break; + case ui::VKEY_NEXT: + string_id = IDS_APP_PAGEDOWN_KEY; + break; + case ui::VKEY_END: + string_id = IDS_APP_END_KEY; + break; + case ui::VKEY_HOME: + string_id = IDS_APP_HOME_KEY; + break; + case ui::VKEY_INSERT: + string_id = IDS_APP_INSERT_KEY; + break; + case ui::VKEY_DELETE: + string_id = IDS_APP_DELETE_KEY; + break; + case ui::VKEY_LEFT: + string_id = IDS_APP_LEFT_ARROW_KEY; + break; + case ui::VKEY_RIGHT: + string_id = IDS_APP_RIGHT_ARROW_KEY; + break; + case ui::VKEY_BACK: + string_id = IDS_APP_BACKSPACE_KEY; + break; + case ui::VKEY_F1: + string_id = IDS_APP_F1_KEY; + break; + case ui::VKEY_F11: + string_id = IDS_APP_F11_KEY; + break; + default: + break; + } + + string16 shortcut; + if (!string_id) { +#if defined(OS_WIN) + // Our fallback is to try translate the key code to a regular character + // unless it is one of digits (VK_0 to VK_9). Some keyboard + // layouts have characters other than digits assigned in + // an unshifted mode (e.g. French AZERY layout has 'a with grave + // accent' for '0'). For display in the menu (e.g. Ctrl-0 for the + // default zoom level), we leave VK_[0-9] alone without translation. + wchar_t key; + if (accelerator.GetKeyCode() >= '0' && accelerator.GetKeyCode() <= '9') + key = accelerator.GetKeyCode(); + else + key = LOWORD(::MapVirtualKeyW(accelerator.GetKeyCode(), MAPVK_VK_TO_CHAR)); + shortcut += key; +#elif defined(OS_LINUX) + const gchar* name = NULL; + switch (accelerator.GetKeyCode()) { + case ui::VKEY_OEM_2: + name = static_cast<const gchar*>("/"); + break; + default: + name = gdk_keyval_name(gdk_keyval_to_lower(accelerator.GetKeyCode())); + break; + } + if (name) { + if (name[0] != 0 && name[1] == 0) + shortcut += static_cast<string16::value_type>(g_ascii_toupper(name[0])); + else + shortcut += UTF8ToUTF16(name); + } +#endif + } else { + shortcut = l10n_util::GetStringUTF16(string_id); + } + + // Checking whether the character used for the accelerator is alphanumeric. + // If it is not, then we need to adjust the string later on if the locale is + // right-to-left. See below for more information of why such adjustment is + // required. + string16 shortcut_rtl; + bool adjust_shortcut_for_rtl = false; + if (base::i18n::IsRTL() && shortcut.length() == 1 && + !IsAsciiAlpha(shortcut.at(0)) && !IsAsciiDigit(shortcut.at(0))) { + adjust_shortcut_for_rtl = true; + shortcut_rtl.assign(shortcut); + } + + if (IsShiftDown(accelerator)) + shortcut = l10n_util::GetStringFUTF16(IDS_APP_SHIFT_MODIFIER, shortcut); + + // Note that we use 'else-if' in order to avoid using Ctrl+Alt as a shortcut. + // See http://blogs.msdn.com/oldnewthing/archive/2004/03/29/101121.aspx for + // more information. + if (IsCtrlDown(accelerator)) + shortcut = l10n_util::GetStringFUTF16(IDS_APP_CONTROL_MODIFIER, shortcut); + else if (IsAltDown(accelerator)) + shortcut = l10n_util::GetStringFUTF16(IDS_APP_ALT_MODIFIER, shortcut); + + // For some reason, menus in Windows ignore standard Unicode directionality + // marks (such as LRE, PDF, etc.). On RTL locales, we use RTL menus and + // therefore any text we draw for the menu items is drawn in an RTL context. + // Thus, the text "Ctrl++" (which we currently use for the Zoom In option) + // appears as "++Ctrl" in RTL because the Unicode BiDi algorithm puts + // punctuations on the left when the context is right-to-left. Shortcuts that + // do not end with a punctuation mark (such as "Ctrl+H" do not have this + // problem). + // + // The only way to solve this problem is to adjust the string if the locale + // is RTL so that it is drawn correnctly in an RTL context. Instead of + // returning "Ctrl++" in the above example, we return "++Ctrl". This will + // cause the text to appear as "Ctrl++" when Windows draws the string in an + // RTL context because the punctunation no longer appears at the end of the + // string. + // + // TODO(idana) bug# 1232732: this hack can be avoided if instead of using + // views::Menu we use views::MenuItemView because the latter is a View + // subclass and therefore it supports marking text as RTL or LTR using + // standard Unicode directionality marks. + if (adjust_shortcut_for_rtl) { + int key_length = static_cast<int>(shortcut_rtl.length()); + DCHECK_GT(key_length, 0); + shortcut_rtl.append(ASCIIToUTF16("+")); + + // Subtracting the size of the shortcut key and 1 for the '+' sign. + shortcut_rtl.append(shortcut, 0, shortcut.length() - key_length - 1); + shortcut.swap(shortcut_rtl); + } + + return shortcut; +} + +} // namespace ui diff --git a/ui/views/events/accelerator.h b/ui/views/events/accelerator.h new file mode 100644 index 0000000..c75d73d --- /dev/null +++ b/ui/views/events/accelerator.h @@ -0,0 +1,32 @@ +// Copyright (c) 2011 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_VIEWS_EVENTS_ACCELERATOR_H_ +#define UI_VIEWS_EVENTS_ACCELERATOR_H_ +#pragma once + +#include <string> + +#include "base/string16.h" +#include "ui/base/models/accelerator.h" +#include "ui/views/events/event.h" + +namespace ui { + +string16 GetShortcutTextForAccelerator(const Accelerator& accelerator); + +// An interface that classes that want to register for keyboard accelerators +// should implement. +class AcceleratorTarget { + public: + // This method should return true if the accelerator was processed. + virtual bool AcceleratorPressed(const Accelerator& accelerator) = 0; + + protected: + virtual ~AcceleratorTarget() {} +}; + +} // namespace ui + +#endif // UI_VIEWS_EVENTS_ACCELERATOR_H_ diff --git a/ui/views/events/event.cc b/ui/views/events/event.cc index 4c62b8a..f1d04b9 100644 --- a/ui/views/events/event.cc +++ b/ui/views/events/event.cc @@ -16,6 +16,19 @@ Event::Event(EventType type, int flags) flags_(flags) { } +int Event::GetModifiers() const { + int modifiers = 0; + if (IsShiftDown()) + modifiers |= VKEY_SHIFT; + if (IsControlDown()) + modifiers |= VKEY_CONTROL; + if (IsAltDown()) + modifiers |= VKEY_MENU; + if (IsCapsLockDown()) + modifiers |= VKEY_CAPITAL; + return modifiers; +} + //////////////////////////////////////////////////////////////////////////////// // LocatedEvent, protected: diff --git a/ui/views/events/event.h b/ui/views/events/event.h index fc0a0d6..d8ad17f 100644 --- a/ui/views/events/event.h +++ b/ui/views/events/event.h @@ -57,6 +57,10 @@ class Event { bool IsCapsLockDown() const { return (flags_ & EF_CAPS_LOCK_DOWN) != 0; } bool IsAltDown() const { return (flags_ & EF_ALT_DOWN) != 0; } + // Return a mask of active modifier keycodes from + // ui/base/keycodes/keyboard_codes.h + int GetModifiers() const; + // Returns true if the event is any kind of mouse event. bool IsMouseEvent() const { return type_ == ET_MOUSE_PRESSED || diff --git a/ui/views/focus/focus_manager.cc b/ui/views/focus/focus_manager.cc new file mode 100644 index 0000000..c2c7aa8 --- /dev/null +++ b/ui/views/focus/focus_manager.cc @@ -0,0 +1,530 @@ +// Copyright (c) 2011 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/views/focus/focus_manager.h" + +#include <algorithm> + +#include "build/build_config.h" + +#if defined(OS_LINUX) +#include <gtk/gtk.h> +#endif + +#include "base/logging.h" +#include "ui/base/keycodes/keyboard_codes.h" +#include "ui/views/focus/focus_search.h" +#include "ui/views/focus/view_storage.h" +#include "ui/views/view.h" +#include "ui/views/widget/root_view.h" +#include "ui/views/widget/native_widget.h" +#include "ui/views/widget/widget.h" + +namespace ui { + +// FocusManager::WidgetFocusManager --------------------------------- + +void FocusManager::WidgetFocusManager::AddFocusChangeListener( + WidgetFocusChangeListener* listener) { + DCHECK(std::find(focus_change_listeners_.begin(), + focus_change_listeners_.end(), listener) == + focus_change_listeners_.end()) << + "Adding a WidgetFocusChangeListener twice."; + focus_change_listeners_.push_back(listener); +} + +void FocusManager::WidgetFocusManager::RemoveFocusChangeListener( + WidgetFocusChangeListener* listener) { + WidgetFocusChangeListenerList::iterator iter(std::find( + focus_change_listeners_.begin(), + focus_change_listeners_.end(), + listener)); + if (iter != focus_change_listeners_.end()) { + focus_change_listeners_.erase(iter); + } else { + NOTREACHED() << + "Attempting to remove an unregistered WidgetFocusChangeListener."; + } +} + +void FocusManager::WidgetFocusManager::OnWidgetFocusEvent( + gfx::NativeView focused_before, + gfx::NativeView focused_now) { + if (!enabled_) + return; + + // Perform a safe iteration over the focus listeners, as the array of + // may change during notification. + WidgetFocusChangeListenerList local_listeners(focus_change_listeners_); + WidgetFocusChangeListenerList::iterator iter(local_listeners.begin()); + for (;iter != local_listeners.end(); ++iter) { + (*iter)->NativeFocusWillChange(focused_before, focused_now); + } +} + +// static +FocusManager::WidgetFocusManager* +FocusManager::WidgetFocusManager::GetInstance() { + return Singleton<WidgetFocusManager>::get(); +} + +// FocusManager ----------------------------------------------------- + +FocusManager::FocusManager(Widget* widget) + : widget_(widget), + focused_view_(NULL), + focus_change_reason_(kReasonDirectFocusChange) { + DCHECK(widget_); + stored_focused_view_storage_id_ = + ViewStorage::GetInstance()->CreateStorageID(); +} + +FocusManager::~FocusManager() { + // If there are still registered FocusChange listeners, chances are they were + // leaked so warn about them. + DCHECK(focus_change_listeners_.empty()); +} + +// static +FocusManager::WidgetFocusManager* FocusManager::GetWidgetFocusManager() { + return WidgetFocusManager::GetInstance(); +} + +bool FocusManager::OnKeyEvent(const KeyEvent& event) { +#if defined(OS_WIN) + // If the focused view wants to process the key event as is, let it be. + // On Linux we always dispatch key events to the focused view first, so + // we should not do this check here. See also WidgetGtk::OnKeyEvent(). + if (focused_view_ && focused_view_->SkipDefaultKeyEventProcessing(event)) + return true; +#endif + + // Intercept Tab related messages for focus traversal. + // Note that we don't do focus traversal if the root window is not part of the + // active window hierarchy as this would mean we have no focused view and + // would focus the first focusable view. +#if defined(OS_WIN) + HWND top_window = widget_->native_widget()->GetNativeView(); + HWND active_window = ::GetActiveWindow(); + if ((active_window == top_window || ::IsChild(active_window, top_window)) && + IsTabTraversalKeyEvent(event)) { + AdvanceFocus(event.IsShiftDown()); + return false; + } +#else + if (IsTabTraversalKeyEvent(event)) { + AdvanceFocus(event.IsShiftDown()); + return false; + } +#endif + + // Intercept arrow key messages to switch between grouped views. + ui::KeyboardCode key_code = event.key_code(); + if (focused_view_ && focused_view_->group() != -1 && + (key_code == ui::VKEY_UP || key_code == ui::VKEY_DOWN || + key_code == ui::VKEY_LEFT || key_code == ui::VKEY_RIGHT)) { + bool next = (key_code == ui::VKEY_RIGHT || key_code == ui::VKEY_DOWN); + std::vector<View*> views; + focused_view_->parent()->GetViewsWithGroup(focused_view_->group(), &views); + std::vector<View*>::const_iterator iter = std::find(views.begin(), + views.end(), + focused_view_); + DCHECK(iter != views.end()); + int index = static_cast<int>(iter - views.begin()); + index += next ? 1 : -1; + if (index < 0) { + index = static_cast<int>(views.size()) - 1; + } else if (index >= static_cast<int>(views.size())) { + index = 0; + } + SetFocusedViewWithReason(views[index], kReasonFocusTraversal); + return false; + } + + // Process keyboard accelerators. + // If the key combination matches an accelerator, the accelerator is + // triggered, otherwise the key event is processed as usual. + Accelerator accelerator(event.key_code(), event.GetModifiers()); + if (ProcessAccelerator(accelerator)) { + // If a shortcut was activated for this keydown message, do not propagate + // the event further. + return false; + } + return true; +} + +void FocusManager::ValidateFocusedView() { + if (focused_view_) { + if (!ContainsView(focused_view_)) + ClearFocus(); + } +} + +// Tests whether a view is valid, whether it still belongs to the window +// hierarchy of the FocusManager. +bool FocusManager::ContainsView(View* view) { + DCHECK(view); + Widget* widget = view->GetWidget(); + if (!widget) + return false; + + gfx::NativeView top_window = widget_->native_widget()->GetNativeView(); + gfx::NativeView window = widget->native_widget()->GetNativeView(); + while (window) { + if (window == top_window) + return true; +#if defined(OS_WIN) + window = ::GetParent(window); +#else + window = gtk_widget_get_parent(window); +#endif + } + return false; +} + +void FocusManager::AdvanceFocus(bool reverse) { + View* v = GetNextFocusableView(focused_view_, reverse, false); + // Note: Do not skip this next block when v == focused_view_. If the user + // tabs past the last focusable element in a webpage, we'll get here, and if + // the TabContentsContainerView is the only focusable view (possible in + // fullscreen mode), we need to run this block in order to cycle around to the + // first element on the page. + if (v) { + v->OnFocus(); // TODO(beng): AboutToRequestFocusFromTabTraversal(reverse); + SetFocusedViewWithReason(v, kReasonFocusTraversal); + } +} + +View* FocusManager::GetNextFocusableView(View* original_starting_view, + bool reverse, + bool dont_loop) { + FocusTraversable* focus_traversable = NULL; + + // Let's revalidate the focused view. + ValidateFocusedView(); + + View* starting_view = NULL; + if (original_starting_view) { + // Search up the containment hierarchy to see if a view is acting as + // a pane, and wants to implement its own focus traversable to keep + // the focus trapped within that pane. + View* pane_search = original_starting_view; + while (pane_search) { + focus_traversable = pane_search->GetPaneFocusTraversable(); + if (focus_traversable) { + starting_view = original_starting_view; + break; + } + pane_search = pane_search->parent(); + } + + /* + TODO(beng): figure out traversal + if (!focus_traversable) { + if (!reverse) { + // If the starting view has a focus traversable, use it. + // This is the case with WidgetWins for example. + focus_traversable = original_starting_view->GetFocusTraversable(); + + // Otherwise default to the root view. + if (!focus_traversable) { + focus_traversable = original_starting_view->GetRootView(); + starting_view = original_starting_view; + } + } else { + // When you are going back, starting view's FocusTraversable + // should not be used. + focus_traversable = original_starting_view->GetRootView(); + starting_view = original_starting_view; + } + } + */ + } else { + /* + TODO(beng): figure out traversal + focus_traversable = widget_->GetRootView(); + */ + } + + // Traverse the FocusTraversable tree down to find the focusable view. + View* v = FindFocusableView(focus_traversable, starting_view, reverse); + if (v) { + return v; + } else { + // Let's go up in the FocusTraversable tree. + FocusTraversable* parent_focus_traversable = + focus_traversable->GetFocusTraversableParent(); + starting_view = focus_traversable->GetFocusTraversableParentView(); + while (parent_focus_traversable) { + FocusTraversable* new_focus_traversable = NULL; + View* new_starting_view = NULL; + // When we are going backward, the parent view might gain the next focus. + bool check_starting_view = reverse; + v = parent_focus_traversable->GetFocusSearch()->FindNextFocusableView( + starting_view, reverse, FocusSearch::UP, + check_starting_view, &new_focus_traversable, &new_starting_view); + + if (new_focus_traversable) { + DCHECK(!v); + + // There is a FocusTraversable, traverse it down. + v = FindFocusableView(new_focus_traversable, NULL, reverse); + } + + if (v) + return v; + + starting_view = focus_traversable->GetFocusTraversableParentView(); + parent_focus_traversable = + parent_focus_traversable->GetFocusTraversableParent(); + } + + // If we get here, we have reached the end of the focus hierarchy, let's + // loop. Make sure there was at least a view to start with, to prevent + // infinitely looping in empty windows. + if (!dont_loop && original_starting_view) { + // Easy, just clear the selection and press tab again. + // By calling with NULL as the starting view, we'll start from the + // top_root_view. + return GetNextFocusableView(NULL, reverse, true); + } + } + return NULL; +} + +void FocusManager::SetFocusedViewWithReason( + View* view, FocusChangeReason reason) { + focus_change_reason_ = reason; + + if (focused_view_ == view) + return; + + View* prev_focused_view = focused_view_; + if (focused_view_) + focused_view_->OnBlur(); + + // Notified listeners that the focus changed. + FocusChangeListenerList::const_iterator iter; + for (iter = focus_change_listeners_.begin(); + iter != focus_change_listeners_.end(); ++iter) { + (*iter)->FocusWillChange(prev_focused_view, view); + } + + focused_view_ = view; + + if (prev_focused_view) + prev_focused_view->Invalidate(); // Remove focus artifacts. + + if (view) { + view->Invalidate(); + view->OnFocus(); + // The view might be deleted now. + } +} + +void FocusManager::ClearFocus() { + SetFocusedView(NULL); + ClearNativeFocus(); +} + +void FocusManager::StoreFocusedView() { + ViewStorage* view_storage = ViewStorage::GetInstance(); + if (!view_storage) { + // This should never happen but bug 981648 seems to indicate it could. + NOTREACHED(); + return; + } + + // TODO (jcampan): when a TabContents containing a popup is closed, the focus + // is stored twice causing an assert. We should find a better alternative than + // removing the view from the storage explicitly. + view_storage->RemoveView(stored_focused_view_storage_id_); + + if (!focused_view_) + return; + + view_storage->StoreView(stored_focused_view_storage_id_, focused_view_); + + View* v = focused_view_; + + { + // Temporarily disable notification. ClearFocus() will set the focus to the + // main browser window. This extra focus bounce which happens during + // deactivation can confuse registered WidgetFocusListeners, as the focus + // is not changing due to a user-initiated event. + AutoNativeNotificationDisabler local_notification_disabler; + ClearFocus(); + } + + if (v) + v->Invalidate(); // Remove focus border. +} + +void FocusManager::RestoreFocusedView() { + ViewStorage* view_storage = ViewStorage::GetInstance(); + if (!view_storage) { + // This should never happen but bug 981648 seems to indicate it could. + NOTREACHED(); + return; + } + + View* view = view_storage->RetrieveView(stored_focused_view_storage_id_); + if (view) { + if (ContainsView(view)) { + if (!view->IsFocusableInRootView() && + view->IsAccessibilityFocusableInRootView()) { + // RequestFocus would fail, but we want to restore focus to controls + // that had focus in accessibility mode. + SetFocusedViewWithReason(view, kReasonFocusRestore); + } else { + // This usually just sets the focus if this view is focusable, but + // let the view override RequestFocus if necessary. + view->RequestFocus(); + + // If it succeeded, the reason would be incorrect; set it to + // focus restore. + if (focused_view_ == view) + focus_change_reason_ = kReasonFocusRestore; + } + } + } else { + // Clearing the focus will focus the root window, so we still get key + // events. + ClearFocus(); + } +} + +void FocusManager::ClearStoredFocusedView() { + ViewStorage* view_storage = ViewStorage::GetInstance(); + if (!view_storage) { + // This should never happen but bug 981648 seems to indicate it could. + NOTREACHED(); + return; + } + view_storage->RemoveView(stored_focused_view_storage_id_); +} + +// Find the next (previous if reverse is true) focusable view for the specified +// FocusTraversable, starting at the specified view, traversing down the +// FocusTraversable hierarchy. +View* FocusManager::FindFocusableView(FocusTraversable* focus_traversable, + View* starting_view, + bool reverse) { + FocusTraversable* new_focus_traversable = NULL; + View* new_starting_view = NULL; + View* v = focus_traversable->GetFocusSearch()->FindNextFocusableView( + starting_view, + reverse, + FocusSearch::DOWN, + false, + &new_focus_traversable, + &new_starting_view); + + // Let's go down the FocusTraversable tree as much as we can. + while (new_focus_traversable) { + DCHECK(!v); + focus_traversable = new_focus_traversable; + starting_view = new_starting_view; + new_focus_traversable = NULL; + starting_view = NULL; + v = focus_traversable->GetFocusSearch()->FindNextFocusableView( + starting_view, + reverse, + FocusSearch::DOWN, + false, + &new_focus_traversable, + &new_starting_view); + } + return v; +} + +void FocusManager::RegisterAccelerator( + const Accelerator& accelerator, + AcceleratorTarget* target) { + AcceleratorTargetList& targets = accelerators_[accelerator]; + DCHECK(std::find(targets.begin(), targets.end(), target) == targets.end()) + << "Registering the same target multiple times"; + targets.push_front(target); +} + +void FocusManager::UnregisterAccelerator(const Accelerator& accelerator, + AcceleratorTarget* target) { + AcceleratorMap::iterator map_iter = accelerators_.find(accelerator); + if (map_iter == accelerators_.end()) { + NOTREACHED() << "Unregistering non-existing accelerator"; + return; + } + + AcceleratorTargetList* targets = &map_iter->second; + AcceleratorTargetList::iterator target_iter = + std::find(targets->begin(), targets->end(), target); + if (target_iter == targets->end()) { + NOTREACHED() << "Unregistering accelerator for wrong target"; + return; + } + + targets->erase(target_iter); +} + +void FocusManager::UnregisterAccelerators(AcceleratorTarget* target) { + for (AcceleratorMap::iterator map_iter = accelerators_.begin(); + map_iter != accelerators_.end(); ++map_iter) { + AcceleratorTargetList* targets = &map_iter->second; + targets->remove(target); + } +} + +bool FocusManager::ProcessAccelerator(const Accelerator& accelerator) { + AcceleratorMap::iterator map_iter = accelerators_.find(accelerator); + if (map_iter != accelerators_.end()) { + // We have to copy the target list here, because an AcceleratorPressed + // event handler may modify the list. + AcceleratorTargetList targets(map_iter->second); + for (AcceleratorTargetList::iterator iter = targets.begin(); + iter != targets.end(); ++iter) { + if ((*iter)->AcceleratorPressed(accelerator)) + return true; + } + } + return false; +} + +AcceleratorTarget* FocusManager::GetCurrentTargetForAccelerator( + const Accelerator& accelerator) const { + AcceleratorMap::const_iterator map_iter = accelerators_.find(accelerator); + if (map_iter == accelerators_.end() || map_iter->second.empty()) + return NULL; + return map_iter->second.front(); +} + +// static +bool FocusManager::IsTabTraversalKeyEvent(const KeyEvent& key_event) { + return key_event.key_code() == ui::VKEY_TAB && + !key_event.IsControlDown(); +} + +void FocusManager::ViewRemoved(View* parent, View* removed) { + if (focused_view_ && focused_view_ == removed) + ClearFocus(); +} + +void FocusManager::AddFocusChangeListener(FocusChangeListener* listener) { + DCHECK(std::find(focus_change_listeners_.begin(), + focus_change_listeners_.end(), listener) == + focus_change_listeners_.end()) << "Adding a listener twice."; + focus_change_listeners_.push_back(listener); +} + +void FocusManager::RemoveFocusChangeListener(FocusChangeListener* listener) { + FocusChangeListenerList::iterator place = + std::find(focus_change_listeners_.begin(), focus_change_listeners_.end(), + listener); + if (place == focus_change_listeners_.end()) { + NOTREACHED() << "Removing a listener that isn't registered."; + return; + } + focus_change_listeners_.erase(place); +} + +} // namespace ui diff --git a/ui/views/focus/focus_manager.h b/ui/views/focus/focus_manager.h new file mode 100644 index 0000000..e94f307 --- /dev/null +++ b/ui/views/focus/focus_manager.h @@ -0,0 +1,344 @@ +// Copyright (c) 2011 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_VIEWS_FOCUS_FOCUS_MANAGER_H_ +#define UI_VIEWS_FOCUS_FOCUS_MANAGER_H_ +#pragma once + +#include <list> +#include <map> +#include <vector> + +#include "base/basictypes.h" +#include "base/singleton.h" +#include "gfx/native_widget_types.h" +#include "ui/views/events/accelerator.h" + +// The FocusManager class is used to handle focus traversal, store/restore +// focused views and handle keyboard accelerators. +// +// There are 2 types of focus: +// - the native focus, which is the focus that an gfx::NativeView has. +// - the view focus, which is the focus that a views::View has. +// +// Each native view must register with their Focus Manager so the focus manager +// gets notified when they are focused (and keeps track of the native focus) and +// as well so that the tab key events can be intercepted. +// They can provide when they register a View that is kept in synch in term of +// focus. This is used in NativeControl for example, where a View wraps an +// actual native window. +// This is already done for you if you subclass the NativeControl class or if +// you use the NativeViewHost class. +// +// When creating a top window (derived from views::Widget) that is not a child +// window, it creates and owns a FocusManager to manage the focus for itself and +// all its child windows. +// +// The FocusTraversable interface exposes the methods a class should implement +// in order to be able to be focus traversed when tab key is pressed. +// RootViews implement FocusTraversable. +// The FocusManager contains a top FocusTraversable instance, which is the top +// RootView. +// +// If you just use views, then the focus traversal is handled for you by the +// RootView. The default traversal order is the order in which the views have +// been added to their container. You can modify this order by using the View +// method SetNextFocusableView(). +// +// If you are embedding a native view containing a nested RootView (for example +// by adding a NativeControl that contains a WidgetWin as its native +// component), then you need to: +// - override the View::GetFocusTraversable() method in your outer component. +// It should return the RootView of the inner component. This is used when +// the focus traversal traverse down the focus hierarchy to enter the nested +// RootView. In the example mentioned above, the NativeControl overrides +// GetFocusTraversable() and returns hwnd_view_container_->GetRootView(). +// - call RootView::SetFocusTraversableParent() on the nested RootView and point +// it to the outter RootView. This is used when the focus goes out of the +// nested RootView. In the example: +// hwnd_view_container_->GetRootView()->SetFocusTraversableParent( +// native_control->GetRootView()); +// - call RootView::SetFocusTraversableParentView() on the nested RootView with +// the parent view that directly contains the native window. This is needed +// when traversing up from the nested RootView to know which view to start +// with when going to the next/previous view. +// In our example: +// hwnd_view_container_->GetRootView()->SetFocusTraversableParent( +// native_control); +// +// Note that FocusTraversable do not have to be RootViews: AccessibleToolbarView +// is FocusTraversable. + +namespace ui { + +class FocusSearch; +class RootView; +class View; +class Widget; + +// The FocusTraversable interface is used by components that want to process +// focus traversal events (due to Tab/Shift-Tab key events). +class FocusTraversable { + public: + // Return a FocusSearch object that implements the algorithm to find + // the next or previous focusable view. + virtual FocusSearch* GetFocusSearch() = 0; + + // Should return the parent FocusTraversable. + // The top RootView which is the top FocusTraversable returns NULL. + virtual FocusTraversable* GetFocusTraversableParent() = 0; + + // This should return the View this FocusTraversable belongs to. + // It is used when walking up the view hierarchy tree to find which view + // should be used as the starting view for finding the next/previous view. + virtual View* GetFocusTraversableParentView() = 0; + + protected: + virtual ~FocusTraversable() {} +}; + +// This interface should be implemented by classes that want to be notified when +// the focus is about to change. See the Add/RemoveFocusChangeListener methods. +class FocusChangeListener { + public: + virtual void FocusWillChange(View* focused_before, View* focused_now) = 0; + + protected: + virtual ~FocusChangeListener() {} +}; + +// This interface should be implemented by classes that want to be notified when +// the native focus is about to change. Listeners implementing this interface +// will be invoked for all native focus changes across the entire Chrome +// application. FocusChangeListeners are only called for changes within the +// children of a single top-level native-view. +class WidgetFocusChangeListener { + public: + virtual void NativeFocusWillChange(gfx::NativeView focused_before, + gfx::NativeView focused_now) = 0; + + protected: + virtual ~WidgetFocusChangeListener() {} +}; + +class FocusManager { + public: + class WidgetFocusManager { + public: + // Returns the singleton instance. + static WidgetFocusManager* GetInstance(); + + // Adds/removes a WidgetFocusChangeListener |listener| to the set of + // active listeners. + void AddFocusChangeListener(WidgetFocusChangeListener* listener); + void RemoveFocusChangeListener(WidgetFocusChangeListener* listener); + + // To be called when native-focus shifts from |focused_before| to + // |focused_now|. + // TODO(port) : Invocations to this routine are only implemented for + // the Win32 platform. Calls need to be placed appropriately for + // non-Windows environments. + void OnWidgetFocusEvent(gfx::NativeView focused_before, + gfx::NativeView focused_now); + + // Enable/Disable notification of registered listeners during calls + // to OnWidgetFocusEvent. Used to prevent unwanted focus changes from + // propagating notifications. + void EnableNotifications() { enabled_ = true; } + void DisableNotifications() { enabled_ = false; } + + private: + WidgetFocusManager() : enabled_(true) {} + + typedef std::vector<WidgetFocusChangeListener*> + WidgetFocusChangeListenerList; + WidgetFocusChangeListenerList focus_change_listeners_; + + bool enabled_; + + friend struct DefaultSingletonTraits<WidgetFocusManager>; + DISALLOW_COPY_AND_ASSIGN(WidgetFocusManager); + }; + + // The reason why the focus changed. + enum FocusChangeReason { + // The focus changed because the user traversed focusable views using + // keys like Tab or Shift+Tab. + kReasonFocusTraversal, + + // The focus changed due to restoring the focus. + kReasonFocusRestore, + + // The focus changed due to a click or a shortcut to jump directly to + // a particular view. + kReasonDirectFocusChange + }; + + explicit FocusManager(Widget* widget); + virtual ~FocusManager(); + + // Returns the global WidgetFocusManager instance for the running application. + static WidgetFocusManager* GetWidgetFocusManager(); + + // Processes the passed key event for accelerators and tab traversal. + // Returns false if the event has been consumed and should not be processed + // further. + bool OnKeyEvent(const KeyEvent& event); + + // Returns true is the specified is part of the hierarchy of the window + // associated with this FocusManager. + bool ContainsView(View* view); + + // Advances the focus (backward if reverse is true). + void AdvanceFocus(bool reverse); + + // The FocusManager keeps track of the focused view within a RootView. + View* GetFocusedView() const { return focused_view_; } + + // Low-level methods to force the focus to change (and optionally provide + // a reason). If the focus change should only happen if the view is + // currently focusable, enabled, and visible, call view->RequestFocus(). + void SetFocusedViewWithReason(View* view, FocusChangeReason reason); + void SetFocusedView(View* view) { + SetFocusedViewWithReason(view, kReasonDirectFocusChange); + } + + // Get the reason why the focus most recently changed. + FocusChangeReason focus_change_reason() const { + return focus_change_reason_; + } + + // Clears the focused view. The window associated with the top root view gets + // the native focus (so we still get keyboard events). + void ClearFocus(); + + // Validates the focused view, clearing it if the window it belongs too is not + // attached to the window hierarchy anymore. + void ValidateFocusedView(); + + // Stores and restores the focused view. Used when the window becomes + // active/inactive. + void StoreFocusedView(); + void RestoreFocusedView(); + + // Clears the stored focused view. + void ClearStoredFocusedView(); + + // Register a keyboard accelerator for the specified target. If multiple + // targets are registered for an accelerator, a target registered later has + // higher priority. + // Note that we are currently limited to accelerators that are either: + // - a key combination including Ctrl or Alt + // - the escape key + // - the enter key + // - any F key (F1, F2, F3 ...) + // - any browser specific keys (as available on special keyboards) + void RegisterAccelerator(const Accelerator& accelerator, + AcceleratorTarget* target); + + // Unregister the specified keyboard accelerator for the specified target. + void UnregisterAccelerator(const Accelerator& accelerator, + AcceleratorTarget* target); + + // Unregister all keyboard accelerator for the specified target. + void UnregisterAccelerators(AcceleratorTarget* target); + + // Activate the target associated with the specified accelerator. + // First, AcceleratorPressed handler of the most recently registered target + // is called, and if that handler processes the event (i.e. returns true), + // this method immediately returns. If not, we do the same thing on the next + // target, and so on. + // Returns true if an accelerator was activated. + bool ProcessAccelerator(const Accelerator& accelerator); + + // Called by a RootView when a view within its hierarchy is removed from its + // parent. This will only be called by a RootView in a hierarchy of Widgets + // that this FocusManager is attached to the parent Widget of. + void ViewRemoved(View* parent, View* removed); + + // Adds/removes a listener. The FocusChangeListener is notified every time + // the focused view is about to change. + void AddFocusChangeListener(FocusChangeListener* listener); + void RemoveFocusChangeListener(FocusChangeListener* listener); + + // Returns the AcceleratorTarget that should be activated for the specified + // keyboard accelerator, or NULL if no view is registered for that keyboard + // accelerator. + AcceleratorTarget* GetCurrentTargetForAccelerator( + const Accelerator& accelertor) const; + + // Convenience method that returns true if the passed |key_event| should + // trigger tab traversal (if it is a TAB key press with or without SHIFT + // pressed). + static bool IsTabTraversalKeyEvent(const KeyEvent& key_event); + + // Sets the focus to the specified native view. + virtual void FocusNativeView(gfx::NativeView native_view); + + // Clears the native view having the focus. + virtual void ClearNativeFocus(); + + // Retrieves the FocusManager associated with the passed native view. + static FocusManager* GetFocusManagerForNativeView( + gfx::NativeView native_view); + + // Retrieves the FocusManager associated with the passed native view. + static FocusManager* GetFocusManagerForNativeWindow( + gfx::NativeWindow native_window); + + private: + // Returns the next focusable view. + View* GetNextFocusableView(View* starting_view, bool reverse, bool dont_loop); + + // Returns the focusable view found in the FocusTraversable specified starting + // at the specified view. This traverses down along the FocusTraversable + // hierarchy. + // Returns NULL if no focusable view were found. + View* FindFocusableView(FocusTraversable* focus_traversable, + View* starting_view, + bool reverse); + + // The top-level Widget this FocusManager is associated with. + Widget* widget_; + + // The view that currently is focused. + View* focused_view_; + + // The storage id used in the ViewStorage to store/restore the view that last + // had focus. + int stored_focused_view_storage_id_; + + // The reason why the focus most recently changed. + FocusChangeReason focus_change_reason_; + + // The accelerators and associated targets. + typedef std::list<AcceleratorTarget*> AcceleratorTargetList; + typedef std::map<Accelerator, AcceleratorTargetList> AcceleratorMap; + AcceleratorMap accelerators_; + + // The list of registered FocusChange listeners. + typedef std::vector<FocusChangeListener*> FocusChangeListenerList; + FocusChangeListenerList focus_change_listeners_; + + DISALLOW_COPY_AND_ASSIGN(FocusManager); +}; + +// A basic helper class that is used to disable native focus change +// notifications within a scope. +class AutoNativeNotificationDisabler { + public: + AutoNativeNotificationDisabler() { + FocusManager::GetWidgetFocusManager()->DisableNotifications(); + } + + ~AutoNativeNotificationDisabler() { + FocusManager::GetWidgetFocusManager()->EnableNotifications(); + } + private: + DISALLOW_COPY_AND_ASSIGN(AutoNativeNotificationDisabler); +}; + +} // namespace ui + +#endif // UI_VIEWS_FOCUS_FOCUS_MANAGER_H_ diff --git a/ui/views/focus/focus_manager_unittest.cc b/ui/views/focus/focus_manager_unittest.cc new file mode 100644 index 0000000..2ab5cae --- /dev/null +++ b/ui/views/focus/focus_manager_unittest.cc @@ -0,0 +1,1730 @@ +// Copyright (c) 2011 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 "testing/gtest/include/gtest/gtest.h" + +#include "base/logging.h" +#include "base/string16.h" +#include "base/string_number_conversions.h" +#include "base/utf_string_conversions.h" +#include "gfx/rect.h" +#include "third_party/skia/include/core/SkColor.h" +#include "ui/base/keycodes/keyboard_codes.h" +#include "ui/base/models/combobox_model.h" +#include "views/background.h" +#include "views/border.h" +#include "views/controls/button/checkbox.h" +#include "views/controls/button/native_button.h" +#include "views/controls/button/radio_button.h" +#include "views/controls/combobox/combobox.h" +#include "views/controls/combobox/native_combobox_wrapper.h" +#include "views/controls/label.h" +#include "views/controls/link.h" +#include "views/controls/native/native_view_host.h" +#include "views/controls/textfield/textfield.h" +#include "views/controls/scroll_view.h" +#include "views/controls/tabbed_pane/native_tabbed_pane_wrapper.h" +#include "views/controls/tabbed_pane/tabbed_pane.h" +#include "views/focus/accelerator_handler.h" +#include "views/widget/root_view.h" +#include "views/window/non_client_view.h" +#include "views/window/window.h" +#include "views/window/window_delegate.h" + +#if defined(OS_WIN) +#include "views/widget/widget_win.h" +#include "views/window/window_win.h" +#elif defined(OS_LINUX) +#include "ui/base/keycodes/keyboard_code_conversion_gtk.h" +#include "views/window/window_gtk.h" +#endif + +using ui::ComboboxModel; // TODO(beng): remove + +namespace { +const int kWindowWidth = 600; +const int kWindowHeight = 500; + +int count = 1; + +const int kTopCheckBoxID = count++; // 1 +const int kLeftContainerID = count++; +const int kAppleLabelID = count++; +const int kAppleTextfieldID = count++; +const int kOrangeLabelID = count++; // 5 +const int kOrangeTextfieldID = count++; +const int kBananaLabelID = count++; +const int kBananaTextfieldID = count++; +const int kKiwiLabelID = count++; +const int kKiwiTextfieldID = count++; // 10 +const int kFruitButtonID = count++; +const int kFruitCheckBoxID = count++; +const int kComboboxID = count++; + +const int kRightContainerID = count++; +const int kAsparagusButtonID = count++; // 15 +const int kBroccoliButtonID = count++; +const int kCauliflowerButtonID = count++; + +const int kInnerContainerID = count++; +const int kScrollViewID = count++; +const int kRosettaLinkID = count++; // 20 +const int kStupeurEtTremblementLinkID = count++; +const int kDinerGameLinkID = count++; +const int kRidiculeLinkID = count++; +const int kClosetLinkID = count++; +const int kVisitingLinkID = count++; // 25 +const int kAmelieLinkID = count++; +const int kJoyeuxNoelLinkID = count++; +const int kCampingLinkID = count++; +const int kBriceDeNiceLinkID = count++; +const int kTaxiLinkID = count++; // 30 +const int kAsterixLinkID = count++; + +const int kOKButtonID = count++; +const int kCancelButtonID = count++; +const int kHelpButtonID = count++; + + +const int kStyleContainerID = count++; // 35 +const int kBoldCheckBoxID = count++; +const int kItalicCheckBoxID = count++; +const int kUnderlinedCheckBoxID = count++; +const int kStyleHelpLinkID = count++; +const int kStyleTextEditID = count++; // 40 + +const int kSearchContainerID = count++; +const int kSearchTextfieldID = count++; +const int kSearchButtonID = count++; +const int kHelpLinkID = count++; + +const int kThumbnailContainerID = count++; // 45 +const int kThumbnailStarID = count++; +const int kThumbnailSuperStarID = count++; +} + +namespace views { + +class FocusManagerTest : public testing::Test, public WindowDelegate { + public: + FocusManagerTest() + : window_(NULL), + content_view_(NULL), + focus_change_listener_(NULL) { +#if defined(OS_WIN) + OleInitialize(NULL); +#endif + } + + ~FocusManagerTest() { +#if defined(OS_WIN) + OleUninitialize(); +#endif + } + + virtual void SetUp() { + window_ = Window::CreateChromeWindow(NULL, bounds(), this); + InitContentView(); + window_->Show(); + } + + virtual void TearDown() { + if (focus_change_listener_) + GetFocusManager()->RemoveFocusChangeListener(focus_change_listener_); + window_->Close(); + + // Flush the message loop to make Purify happy. + message_loop()->RunAllPending(); + } + + FocusManager* GetFocusManager() { +#if defined(OS_WIN) + return static_cast<WindowWin*>(window_)->GetFocusManager(); +#elif defined(OS_LINUX) + return static_cast<WindowGtk*>(window_)->GetFocusManager(); +#else + NOTIMPLEMENTED(); +#endif + } + + void FocusNativeView(gfx::NativeView native_view) { +#if defined(OS_WIN) + ::SendMessage(native_view, WM_SETFOCUS, NULL, NULL); +#else + gint return_val; + GdkEventFocus event; + event.type = GDK_FOCUS_CHANGE; + event.window = + gtk_widget_get_root_window(GTK_WIDGET(window_->GetNativeWindow())); + event.send_event = TRUE; + event.in = TRUE; + gtk_signal_emit_by_name(GTK_OBJECT(native_view), "focus-in-event", + &event, &return_val); +#endif + } + + // WindowDelegate Implementation. + virtual View* GetContentsView() { + if (!content_view_) + content_view_ = new View(); + return content_view_; + } + + virtual void InitContentView() { + } + + protected: + virtual gfx::Rect bounds() { + return gfx::Rect(0, 0, 500, 500); + } + + // Mocks activating/deactivating the window. + void SimulateActivateWindow() { +#if defined(OS_WIN) + ::SendMessage(window_->GetNativeWindow(), WM_ACTIVATE, WA_ACTIVE, NULL); +#else + gboolean result; + g_signal_emit_by_name(G_OBJECT(window_->GetNativeWindow()), + "focus_in_event", 0, &result); +#endif + } + void SimulateDeactivateWindow() { +#if defined(OS_WIN) + ::SendMessage(window_->GetNativeWindow(), WM_ACTIVATE, WA_INACTIVE, NULL); +#else + gboolean result; + g_signal_emit_by_name(G_OBJECT(window_->GetNativeWindow()), + "focus_out_event", 0, & result); +#endif + } + + MessageLoopForUI* message_loop() { return &message_loop_; } + + Window* window_; + View* content_view_; + + void AddFocusChangeListener(FocusChangeListener* listener) { + ASSERT_FALSE(focus_change_listener_); + focus_change_listener_ = listener; + GetFocusManager()->AddFocusChangeListener(listener); + } + +#if defined(OS_WIN) + void PostKeyDown(ui::KeyboardCode key_code) { + ::PostMessage(window_->GetNativeWindow(), WM_KEYDOWN, key_code, 0); + } + + void PostKeyUp(ui::KeyboardCode key_code) { + ::PostMessage(window_->GetNativeWindow(), WM_KEYUP, key_code, 0); + } +#elif defined(OS_LINUX) + void PostKeyDown(ui::KeyboardCode key_code) { + PostKeyEvent(key_code, true); + } + + void PostKeyUp(ui::KeyboardCode key_code) { + PostKeyEvent(key_code, false); + } + + void PostKeyEvent(ui::KeyboardCode key_code, bool pressed) { + int keyval = GdkKeyCodeForWindowsKeyCode(key_code, false); + GdkKeymapKey* keys; + gint n_keys; + gdk_keymap_get_entries_for_keyval( + gdk_keymap_get_default(), + keyval, + &keys, + &n_keys); + GdkEvent* event = gdk_event_new(pressed ? GDK_KEY_PRESS : GDK_KEY_RELEASE); + GdkEventKey* key_event = reinterpret_cast<GdkEventKey*>(event); + int modifier = 0; + if (pressed) + key_event->state = modifier | GDK_KEY_PRESS_MASK; + else + key_event->state = modifier | GDK_KEY_RELEASE_MASK; + + key_event->window = GTK_WIDGET(window_->GetNativeWindow())->window; + DCHECK(key_event->window != NULL); + g_object_ref(key_event->window); + key_event->send_event = true; + key_event->time = GDK_CURRENT_TIME; + key_event->keyval = keyval; + key_event->hardware_keycode = keys[0].keycode; + key_event->group = keys[0].group; + + g_free(keys); + + gdk_event_put(event); + gdk_event_free(event); + } +#endif + + private: + FocusChangeListener* focus_change_listener_; + MessageLoopForUI message_loop_; + + DISALLOW_COPY_AND_ASSIGN(FocusManagerTest); +}; + +// BorderView is a view containing a native window with its own view hierarchy. +// It is interesting to test focus traversal from a view hierarchy to an inner +// view hierarchy. +class BorderView : public NativeViewHost { + public: + explicit BorderView(View* child) : child_(child), widget_(NULL) { + DCHECK(child); + SetFocusable(false); + } + + virtual ~BorderView() {} + + virtual RootView* GetContentsRootView() { + return widget_->GetRootView(); + } + + virtual FocusTraversable* GetFocusTraversable() { + return widget_->GetRootView(); + } + + virtual void ViewHierarchyChanged(bool is_add, View *parent, View *child) { + NativeViewHost::ViewHierarchyChanged(is_add, parent, child); + + if (child == this && is_add) { + if (!widget_) { +#if defined(OS_WIN) + WidgetWin* widget_win = new WidgetWin(); + widget_win->Init(parent->GetRootView()->GetWidget()->GetNativeView(), + gfx::Rect(0, 0, 0, 0)); + widget_win->SetFocusTraversableParentView(this); + widget_ = widget_win; +#else + WidgetGtk* widget_gtk = new WidgetGtk(WidgetGtk::TYPE_CHILD); + widget_gtk->Init(native_view(), gfx::Rect(0, 0, 0, 0)); + widget_gtk->SetFocusTraversableParentView(this); + widget_ = widget_gtk; +#endif + widget_->SetContentsView(child_); + } + + // We have been added to a view hierarchy, attach the native view. + Attach(widget_->GetNativeView()); + // Also update the FocusTraversable parent so the focus traversal works. + widget_->GetRootView()->SetFocusTraversableParent(GetRootView()); + } + } + + private: + View* child_; + Widget* widget_; + + DISALLOW_COPY_AND_ASSIGN(BorderView); +}; + +class DummyComboboxModel : public ComboboxModel { + public: + virtual int GetItemCount() { return 10; } + + virtual string16 GetItemAt(int index) { + return ASCIIToUTF16("Item ") + base::IntToString16(index); + } +}; + +// A View that can act as a pane. +class PaneView : public View, public FocusTraversable { + public: + PaneView() : focus_search_(NULL) {} + + // If this method is called, this view will use GetPaneFocusTraversable to + // have this provided FocusSearch used instead of the default one, allowing + // you to trap focus within the pane. + void EnablePaneFocus(FocusSearch* focus_search) { + focus_search_ = focus_search; + } + + // Overridden from views::View: + virtual FocusTraversable* GetPaneFocusTraversable() { + if (focus_search_) + return this; + else + return NULL; + } + + // Overridden from views::FocusTraversable: + virtual views::FocusSearch* GetFocusSearch() { + return focus_search_; + } + virtual FocusTraversable* GetFocusTraversableParent() { + return NULL; + } + virtual View* GetFocusTraversableParentView() { + return NULL; + } + + private: + FocusSearch* focus_search_; +}; + +class FocusTraversalTest : public FocusManagerTest { + public: + ~FocusTraversalTest(); + + virtual void InitContentView(); + + protected: + FocusTraversalTest(); + + virtual gfx::Rect bounds() { + return gfx::Rect(0, 0, 600, 460); + } + + View* FindViewByID(int id) { + View* view = GetContentsView()->GetViewByID(id); + if (view) + return view; + view = style_tab_->GetSelectedTab()->GetViewByID(id); + if (view) + return view; + view = search_border_view_->GetContentsRootView()->GetViewByID(id); + if (view) + return view; + return NULL; + } + + protected: + TabbedPane* style_tab_; + BorderView* search_border_view_; + DummyComboboxModel combobox_model_; + PaneView* left_container_; + PaneView* right_container_; + + DISALLOW_COPY_AND_ASSIGN(FocusTraversalTest); +}; + +//////////////////////////////////////////////////////////////////////////////// +// FocusTraversalTest +//////////////////////////////////////////////////////////////////////////////// + +FocusTraversalTest::FocusTraversalTest() + : style_tab_(NULL), + search_border_view_(NULL) { +} + +FocusTraversalTest::~FocusTraversalTest() { +} + +void FocusTraversalTest::InitContentView() { + // Create a complicated view hierarchy with lots of control types for + // use by all of the focus traversal tests. + // + // Class name, ID, and asterisk next to focusable views: + // + // View + // Checkbox * kTopCheckBoxID + // PaneView kLeftContainerID + // Label kAppleLabelID + // Textfield * kAppleTextfieldID + // Label kOrangeLabelID + // Textfield * kOrangeTextfieldID + // Label kBananaLabelID + // Textfield * kBananaTextfieldID + // Label kKiwiLabelID + // Textfield * kKiwiTextfieldID + // NativeButton * kFruitButtonID + // Checkbox * kFruitCheckBoxID + // Combobox * kComboboxID + // PaneView kRightContainerID + // RadioButton * kAsparagusButtonID + // RadioButton * kBroccoliButtonID + // RadioButton * kCauliflowerButtonID + // View kInnerContainerID + // ScrollView kScrollViewID + // View + // Link * kRosettaLinkID + // Link * kStupeurEtTremblementLinkID + // Link * kDinerGameLinkID + // Link * kRidiculeLinkID + // Link * kClosetLinkID + // Link * kVisitingLinkID + // Link * kAmelieLinkID + // Link * kJoyeuxNoelLinkID + // Link * kCampingLinkID + // Link * kBriceDeNiceLinkID + // Link * kTaxiLinkID + // Link * kAsterixLinkID + // NativeButton * kOKButtonID + // NativeButton * kCancelButtonID + // NativeButton * kHelpButtonID + // TabbedPane * kStyleContainerID + // View + // Checkbox * kBoldCheckBoxID + // Checkbox * kItalicCheckBoxID + // Checkbox * kUnderlinedCheckBoxID + // Link * kStyleHelpLinkID + // Textfield * kStyleTextEditID + // Other + // BorderView kSearchContainerID + // View + // Textfield * kSearchTextfieldID + // NativeButton * kSearchButtonID + // Link * kHelpLinkID + // View * kThumbnailContainerID + // NativeButton * kThumbnailStarID + // NativeButton * kThumbnailSuperStarID + + content_view_->set_background( + Background::CreateSolidBackground(SK_ColorWHITE)); + + Checkbox* cb = new Checkbox(L"This is a checkbox"); + content_view_->AddChildView(cb); + // In this fast paced world, who really has time for non hard-coded layout? + cb->SetBounds(10, 10, 200, 20); + cb->SetID(kTopCheckBoxID); + + left_container_ = new PaneView(); + left_container_->set_border(Border::CreateSolidBorder(1, SK_ColorBLACK)); + left_container_->set_background( + Background::CreateSolidBackground(240, 240, 240)); + left_container_->SetID(kLeftContainerID); + content_view_->AddChildView(left_container_); + left_container_->SetBounds(10, 35, 250, 200); + + int label_x = 5; + int label_width = 50; + int label_height = 15; + int text_field_width = 150; + int y = 10; + int gap_between_labels = 10; + + Label* label = new Label(L"Apple:"); + label->SetID(kAppleLabelID); + left_container_->AddChildView(label); + label->SetBounds(label_x, y, label_width, label_height); + + Textfield* text_field = new Textfield(); + text_field->SetID(kAppleTextfieldID); + left_container_->AddChildView(text_field); + text_field->SetBounds(label_x + label_width + 5, y, + text_field_width, label_height); + + y += label_height + gap_between_labels; + + label = new Label(L"Orange:"); + label->SetID(kOrangeLabelID); + left_container_->AddChildView(label); + label->SetBounds(label_x, y, label_width, label_height); + + text_field = new Textfield(); + text_field->SetID(kOrangeTextfieldID); + left_container_->AddChildView(text_field); + text_field->SetBounds(label_x + label_width + 5, y, + text_field_width, label_height); + + y += label_height + gap_between_labels; + + label = new Label(L"Banana:"); + label->SetID(kBananaLabelID); + left_container_->AddChildView(label); + label->SetBounds(label_x, y, label_width, label_height); + + text_field = new Textfield(); + text_field->SetID(kBananaTextfieldID); + left_container_->AddChildView(text_field); + text_field->SetBounds(label_x + label_width + 5, y, + text_field_width, label_height); + + y += label_height + gap_between_labels; + + label = new Label(L"Kiwi:"); + label->SetID(kKiwiLabelID); + left_container_->AddChildView(label); + label->SetBounds(label_x, y, label_width, label_height); + + text_field = new Textfield(); + text_field->SetID(kKiwiTextfieldID); + left_container_->AddChildView(text_field); + text_field->SetBounds(label_x + label_width + 5, y, + text_field_width, label_height); + + y += label_height + gap_between_labels; + + NativeButton* button = new NativeButton(NULL, L"Click me"); + button->SetBounds(label_x, y + 10, 80, 30); + button->SetID(kFruitButtonID); + left_container_->AddChildView(button); + y += 40; + + cb = new Checkbox(L"This is another check box"); + cb->SetBounds(label_x + label_width + 5, y, 180, 20); + cb->SetID(kFruitCheckBoxID); + left_container_->AddChildView(cb); + y += 20; + + Combobox* combobox = new Combobox(&combobox_model_); + combobox->SetBounds(label_x + label_width + 5, y, 150, 30); + combobox->SetID(kComboboxID); + left_container_->AddChildView(combobox); + + right_container_ = new PaneView(); + right_container_->set_border(Border::CreateSolidBorder(1, SK_ColorBLACK)); + right_container_->set_background( + Background::CreateSolidBackground(240, 240, 240)); + right_container_->SetID(kRightContainerID); + content_view_->AddChildView(right_container_); + right_container_->SetBounds(270, 35, 300, 200); + + y = 10; + int radio_button_height = 18; + int gap_between_radio_buttons = 10; + RadioButton* radio_button = new RadioButton(L"Asparagus", 1); + radio_button->SetID(kAsparagusButtonID); + right_container_->AddChildView(radio_button); + radio_button->SetBounds(5, y, 70, radio_button_height); + radio_button->SetGroup(1); + y += radio_button_height + gap_between_radio_buttons; + radio_button = new RadioButton(L"Broccoli", 1); + radio_button->SetID(kBroccoliButtonID); + right_container_->AddChildView(radio_button); + radio_button->SetBounds(5, y, 70, radio_button_height); + radio_button->SetGroup(1); + RadioButton* radio_button_to_check = radio_button; + y += radio_button_height + gap_between_radio_buttons; + radio_button = new RadioButton(L"Cauliflower", 1); + radio_button->SetID(kCauliflowerButtonID); + right_container_->AddChildView(radio_button); + radio_button->SetBounds(5, y, 70, radio_button_height); + radio_button->SetGroup(1); + y += radio_button_height + gap_between_radio_buttons; + + View* inner_container = new View(); + inner_container->set_border(Border::CreateSolidBorder(1, SK_ColorBLACK)); + inner_container->set_background( + Background::CreateSolidBackground(230, 230, 230)); + inner_container->SetID(kInnerContainerID); + right_container_->AddChildView(inner_container); + inner_container->SetBounds(100, 10, 150, 180); + + ScrollView* scroll_view = new ScrollView(); + scroll_view->SetID(kScrollViewID); + inner_container->AddChildView(scroll_view); + scroll_view->SetBounds(1, 1, 148, 178); + + View* scroll_content = new View(); + scroll_content->SetBounds(0, 0, 200, 200); + scroll_content->set_background( + Background::CreateSolidBackground(200, 200, 200)); + scroll_view->SetContents(scroll_content); + + static const wchar_t* const kTitles[] = { + L"Rosetta", L"Stupeur et tremblement", L"The diner game", + L"Ridicule", L"Le placard", L"Les Visiteurs", L"Amelie", + L"Joyeux Noel", L"Camping", L"Brice de Nice", + L"Taxi", L"Asterix" + }; + + static const int kIDs[] = { + kRosettaLinkID, kStupeurEtTremblementLinkID, kDinerGameLinkID, + kRidiculeLinkID, kClosetLinkID, kVisitingLinkID, kAmelieLinkID, + kJoyeuxNoelLinkID, kCampingLinkID, kBriceDeNiceLinkID, + kTaxiLinkID, kAsterixLinkID + }; + + DCHECK(arraysize(kTitles) == arraysize(kIDs)); + + y = 5; + for (size_t i = 0; i < arraysize(kTitles); ++i) { + Link* link = new Link(kTitles[i]); + link->SetHorizontalAlignment(Label::ALIGN_LEFT); + link->SetID(kIDs[i]); + scroll_content->AddChildView(link); + link->SetBounds(5, y, 300, 15); + y += 15; + } + + y = 250; + int width = 60; + button = new NativeButton(NULL, L"OK"); + button->SetID(kOKButtonID); + button->SetIsDefault(true); + + content_view_->AddChildView(button); + button->SetBounds(150, y, width, 30); + + button = new NativeButton(NULL, L"Cancel"); + button->SetID(kCancelButtonID); + content_view_->AddChildView(button); + button->SetBounds(220, y, width, 30); + + button = new NativeButton(NULL, L"Help"); + button->SetID(kHelpButtonID); + content_view_->AddChildView(button); + button->SetBounds(290, y, width, 30); + + y += 40; + + // Left bottom box with style checkboxes. + View* contents = new View(); + contents->set_background(Background::CreateSolidBackground(SK_ColorWHITE)); + cb = new Checkbox(L"Bold"); + contents->AddChildView(cb); + cb->SetBounds(10, 10, 50, 20); + cb->SetID(kBoldCheckBoxID); + + cb = new Checkbox(L"Italic"); + contents->AddChildView(cb); + cb->SetBounds(70, 10, 50, 20); + cb->SetID(kItalicCheckBoxID); + + cb = new Checkbox(L"Underlined"); + contents->AddChildView(cb); + cb->SetBounds(130, 10, 70, 20); + cb->SetID(kUnderlinedCheckBoxID); + + Link* link = new Link(L"Help"); + contents->AddChildView(link); + link->SetBounds(10, 35, 70, 10); + link->SetID(kStyleHelpLinkID); + + text_field = new Textfield(); + contents->AddChildView(text_field); + text_field->SetBounds(10, 50, 100, 20); + text_field->SetID(kStyleTextEditID); + + style_tab_ = new TabbedPane(); + style_tab_->SetID(kStyleContainerID); + content_view_->AddChildView(style_tab_); + style_tab_->SetBounds(10, y, 210, 100); + style_tab_->AddTab(L"Style", contents); + style_tab_->AddTab(L"Other", new View()); + + // Right bottom box with search. + contents = new View(); + contents->set_background(Background::CreateSolidBackground(SK_ColorWHITE)); + text_field = new Textfield(); + contents->AddChildView(text_field); + text_field->SetBounds(10, 10, 100, 20); + text_field->SetID(kSearchTextfieldID); + + button = new NativeButton(NULL, L"Search"); + contents->AddChildView(button); + button->SetBounds(112, 5, 60, 30); + button->SetID(kSearchButtonID); + + link = new Link(L"Help"); + link->SetHorizontalAlignment(Label::ALIGN_LEFT); + link->SetID(kHelpLinkID); + contents->AddChildView(link); + link->SetBounds(175, 10, 30, 20); + + search_border_view_ = new BorderView(contents); + search_border_view_->SetID(kSearchContainerID); + + content_view_->AddChildView(search_border_view_); + search_border_view_->SetBounds(300, y, 240, 50); + + y += 60; + + contents = new View(); + contents->SetFocusable(true); + contents->set_background(Background::CreateSolidBackground(SK_ColorBLUE)); + contents->SetID(kThumbnailContainerID); + button = new NativeButton(NULL, L"Star"); + contents->AddChildView(button); + button->SetBounds(5, 5, 50, 30); + button->SetID(kThumbnailStarID); + button = new NativeButton(NULL, L"SuperStar"); + contents->AddChildView(button); + button->SetBounds(60, 5, 100, 30); + button->SetID(kThumbnailSuperStarID); + + content_view_->AddChildView(contents); + contents->SetBounds(250, y, 200, 50); + // We can only call RadioButton::SetChecked() on the radio-button is part of + // the view hierarchy. + radio_button_to_check->SetChecked(true); +} + +//////////////////////////////////////////////////////////////////////////////// +// The tests +//////////////////////////////////////////////////////////////////////////////// + +enum FocusTestEventType { + WILL_GAIN_FOCUS = 0, + DID_GAIN_FOCUS, + WILL_LOSE_FOCUS +}; + +struct FocusTestEvent { + FocusTestEvent(FocusTestEventType type, int view_id) + : type(type), + view_id(view_id) { + } + + FocusTestEventType type; + int view_id; +}; + +class SimpleTestView : public View { + public: + SimpleTestView(std::vector<FocusTestEvent>* event_list, int view_id) + : event_list_(event_list) { + SetFocusable(true); + SetID(view_id); + } + + virtual void WillGainFocus() { + event_list_->push_back(FocusTestEvent(WILL_GAIN_FOCUS, GetID())); + } + + virtual void DidGainFocus() { + event_list_->push_back(FocusTestEvent(DID_GAIN_FOCUS, GetID())); + } + + virtual void WillLoseFocus() { + event_list_->push_back(FocusTestEvent(WILL_LOSE_FOCUS, GetID())); + } + + private: + std::vector<FocusTestEvent>* event_list_; +}; + +// Tests that the appropriate Focus related methods are called when a View +// gets/loses focus. +TEST_F(FocusManagerTest, ViewFocusCallbacks) { + std::vector<FocusTestEvent> event_list; + const int kView1ID = 1; + const int kView2ID = 2; + + SimpleTestView* view1 = new SimpleTestView(&event_list, kView1ID); + SimpleTestView* view2 = new SimpleTestView(&event_list, kView2ID); + content_view_->AddChildView(view1); + content_view_->AddChildView(view2); + + view1->RequestFocus(); + ASSERT_EQ(2, static_cast<int>(event_list.size())); + EXPECT_EQ(WILL_GAIN_FOCUS, event_list[0].type); + EXPECT_EQ(kView1ID, event_list[0].view_id); + EXPECT_EQ(DID_GAIN_FOCUS, event_list[1].type); + EXPECT_EQ(kView1ID, event_list[0].view_id); + + event_list.clear(); + view2->RequestFocus(); + ASSERT_EQ(3, static_cast<int>(event_list.size())); + EXPECT_EQ(WILL_LOSE_FOCUS, event_list[0].type); + EXPECT_EQ(kView1ID, event_list[0].view_id); + EXPECT_EQ(WILL_GAIN_FOCUS, event_list[1].type); + EXPECT_EQ(kView2ID, event_list[1].view_id); + EXPECT_EQ(DID_GAIN_FOCUS, event_list[2].type); + EXPECT_EQ(kView2ID, event_list[2].view_id); + + event_list.clear(); + GetFocusManager()->ClearFocus(); + ASSERT_EQ(1, static_cast<int>(event_list.size())); + EXPECT_EQ(WILL_LOSE_FOCUS, event_list[0].type); + EXPECT_EQ(kView2ID, event_list[0].view_id); +} + +typedef std::pair<View*, View*> ViewPair; +class TestFocusChangeListener : public FocusChangeListener { + public: + virtual void FocusWillChange(View* focused_before, View* focused_now) { + focus_changes_.push_back(ViewPair(focused_before, focused_now)); + } + + const std::vector<ViewPair>& focus_changes() const { return focus_changes_; } + void ClearFocusChanges() { focus_changes_.clear(); } + + private: + // A vector of which views lost/gained focus. + std::vector<ViewPair> focus_changes_; +}; + +TEST_F(FocusManagerTest, FocusChangeListener) { + View* view1 = new View(); + view1->SetFocusable(true); + View* view2 = new View(); + view2->SetFocusable(true); + content_view_->AddChildView(view1); + content_view_->AddChildView(view2); + + TestFocusChangeListener listener; + AddFocusChangeListener(&listener); + + view1->RequestFocus(); + ASSERT_EQ(1, static_cast<int>(listener.focus_changes().size())); + EXPECT_TRUE(listener.focus_changes()[0] == ViewPair(NULL, view1)); + listener.ClearFocusChanges(); + + view2->RequestFocus(); + ASSERT_EQ(1, static_cast<int>(listener.focus_changes().size())); + EXPECT_TRUE(listener.focus_changes()[0] == ViewPair(view1, view2)); + listener.ClearFocusChanges(); + + GetFocusManager()->ClearFocus(); + ASSERT_EQ(1, static_cast<int>(listener.focus_changes().size())); + EXPECT_TRUE(listener.focus_changes()[0] == ViewPair(view2, NULL)); +} + +class TestNativeButton : public NativeButton { + public: + explicit TestNativeButton(const std::wstring& text) + : NativeButton(NULL, text) { + }; + virtual gfx::NativeView TestGetNativeControlView() { + return native_wrapper_->GetTestingHandle(); + } +}; + +class TestCheckbox : public Checkbox { + public: + explicit TestCheckbox(const std::wstring& text) : Checkbox(text) { + }; + virtual gfx::NativeView TestGetNativeControlView() { + return native_wrapper_->GetTestingHandle(); + } +}; + +class TestRadioButton : public RadioButton { + public: + explicit TestRadioButton(const std::wstring& text) : RadioButton(text, 1) { + }; + virtual gfx::NativeView TestGetNativeControlView() { + return native_wrapper_->GetTestingHandle(); + } +}; + +class TestTextfield : public Textfield { + public: + TestTextfield() { } + virtual gfx::NativeView TestGetNativeControlView() { + return native_wrapper_->GetTestingHandle(); + } +}; + +class TestCombobox : public Combobox, public ComboboxModel { + public: + TestCombobox() : Combobox(this) { } + virtual gfx::NativeView TestGetNativeControlView() { + return native_wrapper_->GetTestingHandle(); + } + virtual int GetItemCount() { + return 10; + } + virtual string16 GetItemAt(int index) { + return ASCIIToUTF16("Hello combo"); + } +}; + +class TestTabbedPane : public TabbedPane { + public: + TestTabbedPane() { } + virtual gfx::NativeView TestGetNativeControlView() { + return native_tabbed_pane_->GetTestingHandle(); + } +}; + +// Tests that NativeControls do set the focus View appropriately on the +// FocusManager. +TEST_F(FocusManagerTest, FocusNativeControls) { + TestNativeButton* button = new TestNativeButton(L"Press me"); + TestCheckbox* checkbox = new TestCheckbox(L"Checkbox"); + TestRadioButton* radio_button = new TestRadioButton(L"RadioButton"); + TestTextfield* textfield = new TestTextfield(); + TestCombobox* combobox = new TestCombobox(); + TestTabbedPane* tabbed_pane = new TestTabbedPane(); + TestNativeButton* tab_button = new TestNativeButton(L"tab button"); + + content_view_->AddChildView(button); + content_view_->AddChildView(checkbox); + content_view_->AddChildView(radio_button); + content_view_->AddChildView(textfield); + content_view_->AddChildView(combobox); + content_view_->AddChildView(tabbed_pane); + tabbed_pane->AddTab(L"Awesome tab", tab_button); + + // Simulate the native view getting the native focus (such as by user click). + FocusNativeView(button->TestGetNativeControlView()); + EXPECT_EQ(button, GetFocusManager()->GetFocusedView()); + + FocusNativeView(checkbox->TestGetNativeControlView()); + EXPECT_EQ(checkbox, GetFocusManager()->GetFocusedView()); + + FocusNativeView(radio_button->TestGetNativeControlView()); + EXPECT_EQ(radio_button, GetFocusManager()->GetFocusedView()); + + FocusNativeView(textfield->TestGetNativeControlView()); + EXPECT_EQ(textfield, GetFocusManager()->GetFocusedView()); + + FocusNativeView(combobox->TestGetNativeControlView()); + EXPECT_EQ(combobox, GetFocusManager()->GetFocusedView()); + + FocusNativeView(tabbed_pane->TestGetNativeControlView()); + EXPECT_EQ(tabbed_pane, GetFocusManager()->GetFocusedView()); + + FocusNativeView(tab_button->TestGetNativeControlView()); + EXPECT_EQ(tab_button, GetFocusManager()->GetFocusedView()); +} + +// Test that when activating/deactivating the top window, the focus is stored/ +// restored properly. +TEST_F(FocusManagerTest, FocusStoreRestore) { + // Simulate an activate, otherwise the deactivate isn't going to do anything. + SimulateActivateWindow(); + + NativeButton* button = new NativeButton(NULL, L"Press me"); + View* view = new View(); + view->SetFocusable(true); + + content_view_->AddChildView(button); + button->SetBounds(10, 10, 200, 30); + content_view_->AddChildView(view); + message_loop()->RunAllPending(); + + TestFocusChangeListener listener; + AddFocusChangeListener(&listener); + + view->RequestFocus(); + message_loop()->RunAllPending(); + // MessageLoopForUI::current()->Run(new AcceleratorHandler()); + + // Deacivate the window, it should store its focus. + SimulateDeactivateWindow(); + EXPECT_EQ(NULL, GetFocusManager()->GetFocusedView()); + ASSERT_EQ(2, static_cast<int>(listener.focus_changes().size())); + EXPECT_TRUE(listener.focus_changes()[0] == ViewPair(NULL, view)); + EXPECT_TRUE(listener.focus_changes()[1] == ViewPair(view, NULL)); + listener.ClearFocusChanges(); + + // Reactivate, focus should come-back to the previously focused view. + SimulateActivateWindow(); + EXPECT_EQ(view, GetFocusManager()->GetFocusedView()); + ASSERT_EQ(1, static_cast<int>(listener.focus_changes().size())); + EXPECT_TRUE(listener.focus_changes()[0] == ViewPair(NULL, view)); + listener.ClearFocusChanges(); + + // Same test with a NativeControl. + button->RequestFocus(); + SimulateDeactivateWindow(); + EXPECT_EQ(NULL, GetFocusManager()->GetFocusedView()); + ASSERT_EQ(2, static_cast<int>(listener.focus_changes().size())); + EXPECT_TRUE(listener.focus_changes()[0] == ViewPair(view, button)); + EXPECT_TRUE(listener.focus_changes()[1] == ViewPair(button, NULL)); + listener.ClearFocusChanges(); + + SimulateActivateWindow(); + EXPECT_EQ(button, GetFocusManager()->GetFocusedView()); + ASSERT_EQ(1, static_cast<int>(listener.focus_changes().size())); + EXPECT_TRUE(listener.focus_changes()[0] == ViewPair(NULL, button)); + listener.ClearFocusChanges(); + + /* + // Now test that while the window is inactive we can change the focused view + // (we do that in several places). + SimulateDeactivateWindow(); + // TODO: would have to mock the window being inactive (with a TestWidgetWin + // that would return false on IsActive()). + GetFocusManager()->SetFocusedView(view); + ::SendMessage(window_->GetNativeWindow(), WM_ACTIVATE, WA_ACTIVE, NULL); + + EXPECT_EQ(view, GetFocusManager()->GetFocusedView()); + ASSERT_EQ(2, static_cast<int>(listener.focus_changes().size())); + EXPECT_TRUE(listener.focus_changes()[0] == ViewPair(button, NULL)); + EXPECT_TRUE(listener.focus_changes()[1] == ViewPair(NULL, view)); + */ +} + +TEST_F(FocusManagerTest, ContainsView) { + View* view = new View(); + scoped_ptr<View> detached_view(new View()); + TabbedPane* tabbed_pane = new TabbedPane(); + TabbedPane* nested_tabbed_pane = new TabbedPane(); + NativeButton* tab_button = new NativeButton(NULL, L"tab button"); + + content_view_->AddChildView(view); + content_view_->AddChildView(tabbed_pane); + // Adding a View inside a TabbedPane to test the case of nested root view. + + tabbed_pane->AddTab(L"Awesome tab", nested_tabbed_pane); + nested_tabbed_pane->AddTab(L"Awesomer tab", tab_button); + + EXPECT_TRUE(GetFocusManager()->ContainsView(view)); + EXPECT_TRUE(GetFocusManager()->ContainsView(tabbed_pane)); + EXPECT_TRUE(GetFocusManager()->ContainsView(nested_tabbed_pane)); + EXPECT_TRUE(GetFocusManager()->ContainsView(tab_button)); + EXPECT_FALSE(GetFocusManager()->ContainsView(detached_view.get())); +} + +TEST_F(FocusTraversalTest, NormalTraversal) { + const int kTraversalIDs[] = { kTopCheckBoxID, kAppleTextfieldID, + kOrangeTextfieldID, kBananaTextfieldID, kKiwiTextfieldID, + kFruitButtonID, kFruitCheckBoxID, kComboboxID, kBroccoliButtonID, + kRosettaLinkID, kStupeurEtTremblementLinkID, + kDinerGameLinkID, kRidiculeLinkID, kClosetLinkID, kVisitingLinkID, + kAmelieLinkID, kJoyeuxNoelLinkID, kCampingLinkID, kBriceDeNiceLinkID, + kTaxiLinkID, kAsterixLinkID, kOKButtonID, kCancelButtonID, kHelpButtonID, + kStyleContainerID, kBoldCheckBoxID, kItalicCheckBoxID, + kUnderlinedCheckBoxID, kStyleHelpLinkID, kStyleTextEditID, + kSearchTextfieldID, kSearchButtonID, kHelpLinkID, + kThumbnailContainerID, kThumbnailStarID, kThumbnailSuperStarID }; + + // Uncomment the following line if you want to test manually the UI of this + // test. + // MessageLoopForUI::current()->Run(new AcceleratorHandler()); + + // Let's traverse the whole focus hierarchy (several times, to make sure it + // loops OK). + GetFocusManager()->ClearFocus(); + for (int i = 0; i < 3; ++i) { + for (size_t j = 0; j < arraysize(kTraversalIDs); j++) { + GetFocusManager()->AdvanceFocus(false); + View* focused_view = GetFocusManager()->GetFocusedView(); + EXPECT_TRUE(focused_view != NULL); + if (focused_view) + EXPECT_EQ(kTraversalIDs[j], focused_view->GetID()); + } + } + + // Let's traverse in reverse order. + GetFocusManager()->ClearFocus(); + for (int i = 0; i < 3; ++i) { + for (int j = arraysize(kTraversalIDs) - 1; j >= 0; --j) { + GetFocusManager()->AdvanceFocus(true); + View* focused_view = GetFocusManager()->GetFocusedView(); + EXPECT_TRUE(focused_view != NULL); + if (focused_view) + EXPECT_EQ(kTraversalIDs[j], focused_view->GetID()); + } + } +} + +TEST_F(FocusTraversalTest, TraversalWithNonEnabledViews) { + const int kDisabledIDs[] = { + kBananaTextfieldID, kFruitCheckBoxID, kComboboxID, kAsparagusButtonID, + kCauliflowerButtonID, kClosetLinkID, kVisitingLinkID, kBriceDeNiceLinkID, + kTaxiLinkID, kAsterixLinkID, kHelpButtonID, kBoldCheckBoxID, + kSearchTextfieldID, kHelpLinkID }; + + const int kTraversalIDs[] = { kTopCheckBoxID, kAppleTextfieldID, + kOrangeTextfieldID, kKiwiTextfieldID, kFruitButtonID, kBroccoliButtonID, + kRosettaLinkID, kStupeurEtTremblementLinkID, kDinerGameLinkID, + kRidiculeLinkID, kAmelieLinkID, kJoyeuxNoelLinkID, kCampingLinkID, + kOKButtonID, kCancelButtonID, kStyleContainerID, kItalicCheckBoxID, + kUnderlinedCheckBoxID, kStyleHelpLinkID, kStyleTextEditID, + kSearchButtonID, kThumbnailContainerID, kThumbnailStarID, + kThumbnailSuperStarID }; + + // Let's disable some views. + for (size_t i = 0; i < arraysize(kDisabledIDs); i++) { + View* v = FindViewByID(kDisabledIDs[i]); + ASSERT_TRUE(v != NULL); + v->SetEnabled(false); + } + + // Uncomment the following line if you want to test manually the UI of this + // test. + // MessageLoopForUI::current()->Run(new AcceleratorHandler()); + + View* focused_view; + // Let's do one traversal (several times, to make sure it loops ok). + GetFocusManager()->ClearFocus(); + for (int i = 0; i < 3; ++i) { + for (size_t j = 0; j < arraysize(kTraversalIDs); j++) { + GetFocusManager()->AdvanceFocus(false); + focused_view = GetFocusManager()->GetFocusedView(); + EXPECT_TRUE(focused_view != NULL); + if (focused_view) + EXPECT_EQ(kTraversalIDs[j], focused_view->GetID()); + } + } + + // Same thing in reverse. + GetFocusManager()->ClearFocus(); + for (int i = 0; i < 3; ++i) { + for (int j = arraysize(kTraversalIDs) - 1; j >= 0; --j) { + GetFocusManager()->AdvanceFocus(true); + focused_view = GetFocusManager()->GetFocusedView(); + EXPECT_TRUE(focused_view != NULL); + if (focused_view) + EXPECT_EQ(kTraversalIDs[j], focused_view->GetID()); + } + } +} + +TEST_F(FocusTraversalTest, TraversalWithInvisibleViews) { + const int kInvisibleIDs[] = { kTopCheckBoxID, kOKButtonID, + kThumbnailContainerID }; + + const int kTraversalIDs[] = { kAppleTextfieldID, kOrangeTextfieldID, + kBananaTextfieldID, kKiwiTextfieldID, kFruitButtonID, kFruitCheckBoxID, + kComboboxID, kBroccoliButtonID, kRosettaLinkID, + kStupeurEtTremblementLinkID, kDinerGameLinkID, kRidiculeLinkID, + kClosetLinkID, kVisitingLinkID, kAmelieLinkID, kJoyeuxNoelLinkID, + kCampingLinkID, kBriceDeNiceLinkID, kTaxiLinkID, kAsterixLinkID, + kCancelButtonID, kHelpButtonID, kStyleContainerID, kBoldCheckBoxID, + kItalicCheckBoxID, kUnderlinedCheckBoxID, kStyleHelpLinkID, + kStyleTextEditID, kSearchTextfieldID, kSearchButtonID, kHelpLinkID }; + + + // Let's make some views invisible. + for (size_t i = 0; i < arraysize(kInvisibleIDs); i++) { + View* v = FindViewByID(kInvisibleIDs[i]); + ASSERT_TRUE(v != NULL); + v->SetVisible(false); + } + + // Uncomment the following line if you want to test manually the UI of this + // test. + // MessageLoopForUI::current()->Run(new AcceleratorHandler()); + + View* focused_view; + // Let's do one traversal (several times, to make sure it loops ok). + GetFocusManager()->ClearFocus(); + for (int i = 0; i < 3; ++i) { + for (size_t j = 0; j < arraysize(kTraversalIDs); j++) { + GetFocusManager()->AdvanceFocus(false); + focused_view = GetFocusManager()->GetFocusedView(); + EXPECT_TRUE(focused_view != NULL); + if (focused_view) + EXPECT_EQ(kTraversalIDs[j], focused_view->GetID()); + } + } + + // Same thing in reverse. + GetFocusManager()->ClearFocus(); + for (int i = 0; i < 3; ++i) { + for (int j = arraysize(kTraversalIDs) - 1; j >= 0; --j) { + GetFocusManager()->AdvanceFocus(true); + focused_view = GetFocusManager()->GetFocusedView(); + EXPECT_TRUE(focused_view != NULL); + if (focused_view) + EXPECT_EQ(kTraversalIDs[j], focused_view->GetID()); + } + } +} + +TEST_F(FocusTraversalTest, PaneTraversal) { + // Tests trapping the traversal within a pane - useful for full + // keyboard accessibility for toolbars. + + // First test the left container. + const int kLeftTraversalIDs[] = { + kAppleTextfieldID, + kOrangeTextfieldID, kBananaTextfieldID, kKiwiTextfieldID, + kFruitButtonID, kFruitCheckBoxID, kComboboxID }; + + FocusSearch focus_search_left(left_container_, true, false); + left_container_->EnablePaneFocus(&focus_search_left); + FindViewByID(kComboboxID)->RequestFocus(); + + // Traverse the focus hierarchy within the pane several times. + for (int i = 0; i < 3; ++i) { + for (size_t j = 0; j < arraysize(kLeftTraversalIDs); j++) { + GetFocusManager()->AdvanceFocus(false); + View* focused_view = GetFocusManager()->GetFocusedView(); + EXPECT_TRUE(focused_view != NULL); + if (focused_view) + EXPECT_EQ(kLeftTraversalIDs[j], focused_view->GetID()); + } + } + + // Traverse in reverse order. + FindViewByID(kAppleTextfieldID)->RequestFocus(); + for (int i = 0; i < 3; ++i) { + for (int j = arraysize(kLeftTraversalIDs) - 1; j >= 0; --j) { + GetFocusManager()->AdvanceFocus(true); + View* focused_view = GetFocusManager()->GetFocusedView(); + EXPECT_TRUE(focused_view != NULL); + if (focused_view) + EXPECT_EQ(kLeftTraversalIDs[j], focused_view->GetID()); + } + } + + // Now test the right container, but this time with accessibility mode. + // Make some links not focusable, but mark one of them as + // "accessibility focusable", so it should show up in the traversal. + const int kRightTraversalIDs[] = { + kBroccoliButtonID, kDinerGameLinkID, kRidiculeLinkID, + kClosetLinkID, kVisitingLinkID, kAmelieLinkID, kJoyeuxNoelLinkID, + kCampingLinkID, kBriceDeNiceLinkID, kTaxiLinkID, kAsterixLinkID }; + + FocusSearch focus_search_right(right_container_, true, true); + right_container_->EnablePaneFocus(&focus_search_right); + FindViewByID(kRosettaLinkID)->SetFocusable(false); + FindViewByID(kStupeurEtTremblementLinkID)->SetFocusable(false); + FindViewByID(kDinerGameLinkID)->set_accessibility_focusable(true); + FindViewByID(kDinerGameLinkID)->SetFocusable(false); + FindViewByID(kAsterixLinkID)->RequestFocus(); + + // Traverse the focus hierarchy within the pane several times. + for (int i = 0; i < 3; ++i) { + for (size_t j = 0; j < arraysize(kRightTraversalIDs); j++) { + GetFocusManager()->AdvanceFocus(false); + View* focused_view = GetFocusManager()->GetFocusedView(); + EXPECT_TRUE(focused_view != NULL); + if (focused_view) + EXPECT_EQ(kRightTraversalIDs[j], focused_view->GetID()); + } + } + + // Traverse in reverse order. + FindViewByID(kBroccoliButtonID)->RequestFocus(); + for (int i = 0; i < 3; ++i) { + for (int j = arraysize(kRightTraversalIDs) - 1; j >= 0; --j) { + GetFocusManager()->AdvanceFocus(true); + View* focused_view = GetFocusManager()->GetFocusedView(); + EXPECT_TRUE(focused_view != NULL); + if (focused_view) + EXPECT_EQ(kRightTraversalIDs[j], focused_view->GetID()); + } + } + +} + +// Counts accelerator calls. +class TestAcceleratorTarget : public AcceleratorTarget { + public: + explicit TestAcceleratorTarget(bool process_accelerator) + : accelerator_count_(0), process_accelerator_(process_accelerator) {} + + virtual bool AcceleratorPressed(const Accelerator& accelerator) { + ++accelerator_count_; + return process_accelerator_; + } + + int accelerator_count() const { return accelerator_count_; } + + private: + int accelerator_count_; // number of times that the accelerator is activated + bool process_accelerator_; // return value of AcceleratorPressed + + DISALLOW_COPY_AND_ASSIGN(TestAcceleratorTarget); +}; + +TEST_F(FocusManagerTest, CallsNormalAcceleratorTarget) { + FocusManager* focus_manager = GetFocusManager(); + Accelerator return_accelerator(ui::VKEY_RETURN, false, false, false); + Accelerator escape_accelerator(ui::VKEY_ESCAPE, false, false, false); + + TestAcceleratorTarget return_target(true); + TestAcceleratorTarget escape_target(true); + EXPECT_EQ(return_target.accelerator_count(), 0); + EXPECT_EQ(escape_target.accelerator_count(), 0); + EXPECT_EQ(NULL, + focus_manager->GetCurrentTargetForAccelerator(return_accelerator)); + EXPECT_EQ(NULL, + focus_manager->GetCurrentTargetForAccelerator(escape_accelerator)); + + // Register targets. + focus_manager->RegisterAccelerator(return_accelerator, &return_target); + focus_manager->RegisterAccelerator(escape_accelerator, &escape_target); + + // Checks if the correct target is registered. + EXPECT_EQ(&return_target, + focus_manager->GetCurrentTargetForAccelerator(return_accelerator)); + EXPECT_EQ(&escape_target, + focus_manager->GetCurrentTargetForAccelerator(escape_accelerator)); + + // Hitting the return key. + EXPECT_TRUE(focus_manager->ProcessAccelerator(return_accelerator)); + EXPECT_EQ(return_target.accelerator_count(), 1); + EXPECT_EQ(escape_target.accelerator_count(), 0); + + // Hitting the escape key. + EXPECT_TRUE(focus_manager->ProcessAccelerator(escape_accelerator)); + EXPECT_EQ(return_target.accelerator_count(), 1); + EXPECT_EQ(escape_target.accelerator_count(), 1); + + // Register another target for the return key. + TestAcceleratorTarget return_target2(true); + EXPECT_EQ(return_target2.accelerator_count(), 0); + focus_manager->RegisterAccelerator(return_accelerator, &return_target2); + EXPECT_EQ(&return_target2, + focus_manager->GetCurrentTargetForAccelerator(return_accelerator)); + + // Hitting the return key; return_target2 has the priority. + EXPECT_TRUE(focus_manager->ProcessAccelerator(return_accelerator)); + EXPECT_EQ(return_target.accelerator_count(), 1); + EXPECT_EQ(return_target2.accelerator_count(), 1); + + // Register a target that does not process the accelerator event. + TestAcceleratorTarget return_target3(false); + EXPECT_EQ(return_target3.accelerator_count(), 0); + focus_manager->RegisterAccelerator(return_accelerator, &return_target3); + EXPECT_EQ(&return_target3, + focus_manager->GetCurrentTargetForAccelerator(return_accelerator)); + + // Hitting the return key. + // Since the event handler of return_target3 returns false, return_target2 + // should be called too. + EXPECT_TRUE(focus_manager->ProcessAccelerator(return_accelerator)); + EXPECT_EQ(return_target.accelerator_count(), 1); + EXPECT_EQ(return_target2.accelerator_count(), 2); + EXPECT_EQ(return_target3.accelerator_count(), 1); + + // Unregister return_target2. + focus_manager->UnregisterAccelerator(return_accelerator, &return_target2); + EXPECT_EQ(&return_target3, + focus_manager->GetCurrentTargetForAccelerator(return_accelerator)); + + // Hitting the return key. return_target3 and return_target should be called. + EXPECT_TRUE(focus_manager->ProcessAccelerator(return_accelerator)); + EXPECT_EQ(return_target.accelerator_count(), 2); + EXPECT_EQ(return_target2.accelerator_count(), 2); + EXPECT_EQ(return_target3.accelerator_count(), 2); + + // Unregister targets. + focus_manager->UnregisterAccelerator(return_accelerator, &return_target); + focus_manager->UnregisterAccelerator(return_accelerator, &return_target3); + focus_manager->UnregisterAccelerator(escape_accelerator, &escape_target); + + // Now there is no target registered. + EXPECT_EQ(NULL, + focus_manager->GetCurrentTargetForAccelerator(return_accelerator)); + EXPECT_EQ(NULL, + focus_manager->GetCurrentTargetForAccelerator(escape_accelerator)); + + // Hitting the return key and the escape key. Nothing should happen. + EXPECT_FALSE(focus_manager->ProcessAccelerator(return_accelerator)); + EXPECT_EQ(return_target.accelerator_count(), 2); + EXPECT_EQ(return_target2.accelerator_count(), 2); + EXPECT_EQ(return_target3.accelerator_count(), 2); + EXPECT_FALSE(focus_manager->ProcessAccelerator(escape_accelerator)); + EXPECT_EQ(escape_target.accelerator_count(), 1); +} + +// Unregisters itself when its accelerator is invoked. +class SelfUnregisteringAcceleratorTarget : public AcceleratorTarget { + public: + SelfUnregisteringAcceleratorTarget(Accelerator accelerator, + FocusManager* focus_manager) + : accelerator_(accelerator), + focus_manager_(focus_manager), + accelerator_count_(0) { + } + + virtual bool AcceleratorPressed(const Accelerator& accelerator) { + ++accelerator_count_; + focus_manager_->UnregisterAccelerator(accelerator, this); + return true; + } + + int accelerator_count() const { return accelerator_count_; } + + private: + Accelerator accelerator_; + FocusManager* focus_manager_; + int accelerator_count_; + + DISALLOW_COPY_AND_ASSIGN(SelfUnregisteringAcceleratorTarget); +}; + +TEST_F(FocusManagerTest, CallsSelfDeletingAcceleratorTarget) { + FocusManager* focus_manager = GetFocusManager(); + Accelerator return_accelerator(ui::VKEY_RETURN, false, false, false); + SelfUnregisteringAcceleratorTarget target(return_accelerator, focus_manager); + EXPECT_EQ(target.accelerator_count(), 0); + EXPECT_EQ(NULL, + focus_manager->GetCurrentTargetForAccelerator(return_accelerator)); + + // Register the target. + focus_manager->RegisterAccelerator(return_accelerator, &target); + EXPECT_EQ(&target, + focus_manager->GetCurrentTargetForAccelerator(return_accelerator)); + + // Hitting the return key. The target will be unregistered. + EXPECT_TRUE(focus_manager->ProcessAccelerator(return_accelerator)); + EXPECT_EQ(target.accelerator_count(), 1); + EXPECT_EQ(NULL, + focus_manager->GetCurrentTargetForAccelerator(return_accelerator)); + + // Hitting the return key again; nothing should happen. + EXPECT_FALSE(focus_manager->ProcessAccelerator(return_accelerator)); + EXPECT_EQ(target.accelerator_count(), 1); +} + +class MessageTrackingView : public View { + public: + MessageTrackingView() : accelerator_pressed_(false) { + } + + virtual bool OnKeyPressed(const KeyEvent& e) { + keys_pressed_.push_back(e.GetKeyCode()); + return true; + } + + virtual bool OnKeyReleased(const KeyEvent& e) { + keys_released_.push_back(e.GetKeyCode()); + return true; + } + + virtual bool AcceleratorPressed(const Accelerator& accelerator) { + accelerator_pressed_ = true; + return true; + } + + void Reset() { + accelerator_pressed_ = false; + keys_pressed_.clear(); + keys_released_.clear(); + } + + const std::vector<ui::KeyboardCode>& keys_pressed() const { + return keys_pressed_; + } + + const std::vector<ui::KeyboardCode>& keys_released() const { + return keys_released_; + } + + bool accelerator_pressed() const { + return accelerator_pressed_; + } + + private: + bool accelerator_pressed_; + std::vector<ui::KeyboardCode> keys_pressed_; + std::vector<ui::KeyboardCode> keys_released_; + + DISALLOW_COPY_AND_ASSIGN(MessageTrackingView); +}; + +#if defined(OS_WIN) +// This test is now Windows only. Linux Views port does not handle accelerator +// keys in AcceleratorHandler anymore. The logic has been moved into +// WidgetGtk::OnKeyEvent(). +// Tests that the keyup messages are eaten for accelerators. +TEST_F(FocusManagerTest, IgnoreKeyupForAccelerators) { + FocusManager* focus_manager = GetFocusManager(); + MessageTrackingView* mtv = new MessageTrackingView(); + mtv->AddAccelerator(Accelerator(ui::VKEY_0, false, false, false)); + mtv->AddAccelerator(Accelerator(ui::VKEY_1, false, false, false)); + content_view_->AddChildView(mtv); + focus_manager->SetFocusedView(mtv); + + // First send a non-accelerator key sequence. + PostKeyDown(ui::VKEY_9); + PostKeyUp(ui::VKEY_9); + AcceleratorHandler accelerator_handler; + MessageLoopForUI::current()->PostTask(FROM_HERE, new MessageLoop::QuitTask()); + MessageLoopForUI::current()->Run(&accelerator_handler); + // Make sure we get a key-up and key-down. + ASSERT_EQ(1U, mtv->keys_pressed().size()); + EXPECT_EQ(ui::VKEY_9, mtv->keys_pressed().at(0)); + ASSERT_EQ(1U, mtv->keys_released().size()); + EXPECT_EQ(ui::VKEY_9, mtv->keys_released().at(0)); + EXPECT_FALSE(mtv->accelerator_pressed()); + mtv->Reset(); + + // Same thing with repeat and more than one key at once. + PostKeyDown(ui::VKEY_9); + PostKeyDown(ui::VKEY_9); + PostKeyDown(ui::VKEY_8); + PostKeyDown(ui::VKEY_9); + PostKeyDown(ui::VKEY_7); + PostKeyUp(ui::VKEY_9); + PostKeyUp(ui::VKEY_7); + PostKeyUp(ui::VKEY_8); + MessageLoopForUI::current()->PostTask(FROM_HERE, new MessageLoop::QuitTask()); + MessageLoopForUI::current()->Run(&accelerator_handler); + // Make sure we get a key-up and key-down. + ASSERT_EQ(5U, mtv->keys_pressed().size()); + EXPECT_EQ(ui::VKEY_9, mtv->keys_pressed().at(0)); + EXPECT_EQ(ui::VKEY_9, mtv->keys_pressed().at(1)); + EXPECT_EQ(ui::VKEY_8, mtv->keys_pressed().at(2)); + EXPECT_EQ(ui::VKEY_9, mtv->keys_pressed().at(3)); + EXPECT_EQ(ui::VKEY_7, mtv->keys_pressed().at(4)); + ASSERT_EQ(3U, mtv->keys_released().size()); + EXPECT_EQ(ui::VKEY_9, mtv->keys_released().at(0)); + EXPECT_EQ(ui::VKEY_7, mtv->keys_released().at(1)); + EXPECT_EQ(ui::VKEY_8, mtv->keys_released().at(2)); + EXPECT_FALSE(mtv->accelerator_pressed()); + mtv->Reset(); + + // Now send an accelerator key sequence. + PostKeyDown(ui::VKEY_0); + PostKeyUp(ui::VKEY_0); + MessageLoopForUI::current()->PostTask(FROM_HERE, new MessageLoop::QuitTask()); + MessageLoopForUI::current()->Run(&accelerator_handler); + EXPECT_TRUE(mtv->keys_pressed().empty()); + EXPECT_TRUE(mtv->keys_released().empty()); + EXPECT_TRUE(mtv->accelerator_pressed()); + mtv->Reset(); + + // Same thing with repeat and more than one key at once. + PostKeyDown(ui::VKEY_0); + PostKeyDown(ui::VKEY_1); + PostKeyDown(ui::VKEY_1); + PostKeyDown(ui::VKEY_0); + PostKeyDown(ui::VKEY_0); + PostKeyUp(ui::VKEY_1); + PostKeyUp(ui::VKEY_0); + MessageLoopForUI::current()->PostTask(FROM_HERE, new MessageLoop::QuitTask()); + MessageLoopForUI::current()->Run(&accelerator_handler); + EXPECT_TRUE(mtv->keys_pressed().empty()); + EXPECT_TRUE(mtv->keys_released().empty()); + EXPECT_TRUE(mtv->accelerator_pressed()); + mtv->Reset(); +} +#endif + +#if defined(OS_WIN) +// Test that the focus manager is created successfully for the first view +// window parented to a native dialog. +TEST_F(FocusManagerTest, CreationForNativeRoot) { + // Create a window class. + WNDCLASSEX class_ex; + memset(&class_ex, 0, sizeof(class_ex)); + class_ex.cbSize = sizeof(WNDCLASSEX); + class_ex.lpfnWndProc = &DefWindowProc; + class_ex.lpszClassName = L"TestWindow"; + ATOM atom = RegisterClassEx(&class_ex); + ASSERT_TRUE(atom); + + // Create a native dialog window. + HWND hwnd = CreateWindowEx(0, class_ex.lpszClassName, NULL, + WS_OVERLAPPEDWINDOW, 0, 0, 200, 200, + NULL, NULL, NULL, NULL); + ASSERT_TRUE(hwnd); + + // Create a view window parented to native dialog. + WidgetWin window1; + window1.set_delete_on_destroy(false); + window1.set_window_style(WS_CHILD); + window1.Init(hwnd, gfx::Rect(0, 0, 100, 100)); + + // Get the focus manager directly from the first window. Should exist + // because the first window is the root widget. + views::FocusManager* focus_manager_member1 = window1.GetFocusManager(); + EXPECT_TRUE(focus_manager_member1); + + // Create another view window parented to the first view window. + WidgetWin window2; + window2.set_delete_on_destroy(false); + window2.set_window_style(WS_CHILD); + window2.Init(window1.GetNativeView(), gfx::Rect(0, 0, 100, 100)); + + // Get the focus manager directly from the second window. Should return the + // first window's focus manager. + views::FocusManager* focus_manager_member2 = window2.GetFocusManager(); + EXPECT_EQ(focus_manager_member2, focus_manager_member1); + + // Get the focus manager indirectly using the first window handle. Should + // return the first window's focus manager. + views::FocusManager* focus_manager_indirect = + views::FocusManager::GetFocusManagerForNativeView( + window1.GetNativeView()); + EXPECT_EQ(focus_manager_indirect, focus_manager_member1); + + // Get the focus manager indirectly using the second window handle. Should + // return the first window's focus manager. + focus_manager_indirect = + views::FocusManager::GetFocusManagerForNativeView( + window2.GetNativeView()); + EXPECT_EQ(focus_manager_indirect, focus_manager_member1); + + DestroyWindow(hwnd); +} +#endif + +#if defined(OS_CHROMEOS) +class FocusManagerDtorTest : public FocusManagerTest { + protected: + typedef std::vector<std::string> DtorTrackVector; + + class FocusManagerDtorTracked : public FocusManager { + public: + FocusManagerDtorTracked(Widget* widget, DtorTrackVector* dtor_tracker) + : FocusManager(widget), + dtor_tracker_(dtor_tracker) { + } + + virtual ~FocusManagerDtorTracked() { + dtor_tracker_->push_back("FocusManagerDtorTracked"); + } + + DtorTrackVector* dtor_tracker_; + }; + + class NativeButtonDtorTracked : public NativeButton { + public: + NativeButtonDtorTracked(const std::wstring& text, + DtorTrackVector* dtor_tracker) + : NativeButton(NULL, text), + dtor_tracker_(dtor_tracker) { + }; + virtual ~NativeButtonDtorTracked() { + dtor_tracker_->push_back("NativeButtonDtorTracked"); + } + + DtorTrackVector* dtor_tracker_; + }; + + class WindowGtkDtorTracked : public WindowGtk { + public: + WindowGtkDtorTracked(WindowDelegate* window_delegate, + DtorTrackVector* dtor_tracker) + : WindowGtk(window_delegate), + dtor_tracker_(dtor_tracker) { + tracked_focus_manager_ = new FocusManagerDtorTracked(this, + dtor_tracker_); + // Replace focus_manager_ with FocusManagerDtorTracked + set_focus_manager(tracked_focus_manager_); + + GetNonClientView()->SetFrameView(CreateFrameViewForWindow()); + Init(NULL, gfx::Rect(0, 0, 100, 100)); + } + + virtual ~WindowGtkDtorTracked() { + dtor_tracker_->push_back("WindowGtkDtorTracked"); + } + + FocusManagerDtorTracked* tracked_focus_manager_; + DtorTrackVector* dtor_tracker_; + }; + + public: + virtual void SetUp() { + // Create WindowGtkDtorTracked that uses FocusManagerDtorTracked. + window_ = new WindowGtkDtorTracked(this, &dtor_tracker_); + ASSERT_TRUE(GetFocusManager() == + static_cast<WindowGtkDtorTracked*>(window_)->tracked_focus_manager_); + + window_->Show(); + } + + virtual void TearDown() { + if (window_) { + window_->Close(); + message_loop()->RunAllPending(); + } + } + + DtorTrackVector dtor_tracker_; +}; + +TEST_F(FocusManagerDtorTest, FocusManagerDestructedLast) { + // Setup views hierarchy. + TabbedPane* tabbed_pane = new TabbedPane(); + content_view_->AddChildView(tabbed_pane); + + NativeButtonDtorTracked* button = new NativeButtonDtorTracked(L"button", + &dtor_tracker_); + tabbed_pane->AddTab(L"Awesome tab", button); + + // Close the window. + window_->Close(); + message_loop()->RunAllPending(); + + // Test window, button and focus manager should all be destructed. + ASSERT_EQ(3, static_cast<int>(dtor_tracker_.size())); + + // Focus manager should be the last one to destruct. + ASSERT_STREQ("FocusManagerDtorTracked", dtor_tracker_[2].c_str()); + + // Clear window_ so that we don't try to close it again. + window_ = NULL; +} + +#endif // defined(OS_CHROMEOS) + +} // namespace views diff --git a/ui/views/focus/focus_search.cc b/ui/views/focus/focus_search.cc new file mode 100644 index 0000000..2050b1d --- /dev/null +++ b/ui/views/focus/focus_search.cc @@ -0,0 +1,274 @@ +// Copyright (c) 2011 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 "base/logging.h" +#include "ui/views/focus/focus_manager.h" +#include "ui/views/focus/focus_search.h" +#include "ui/views/view.h" + +namespace ui { + +FocusSearch::FocusSearch(View* root, bool cycle, bool accessibility_mode) + : root_(root), + cycle_(cycle), + accessibility_mode_(accessibility_mode) { +} + +View* FocusSearch::FindNextFocusableView(View* starting_view, + bool reverse, + Direction direction, + bool check_starting_view, + FocusTraversable** focus_traversable, + View** focus_traversable_view) { + *focus_traversable = NULL; + *focus_traversable_view = NULL; + + if (root_->child_count() == 0) { + NOTREACHED(); + // Nothing to focus on here. + return NULL; + } + + View* initial_starting_view = starting_view; + int starting_view_group = -1; + if (starting_view) + starting_view_group = starting_view->group(); + + if (!starting_view) { + // Default to the first/last child + starting_view = + reverse ? + root_->GetChildViewAt(root_->child_count() - 1) : + root_->GetChildViewAt(0); + // If there was no starting view, then the one we select is a potential + // focus candidate. + check_starting_view = true; + } else { + // The starting view should be a direct or indirect child of the root. + DCHECK(root_->Contains(starting_view)); + } + + View* v = NULL; + if (!reverse) { + v = FindNextFocusableViewImpl(starting_view, check_starting_view, + true, + (direction == DOWN) ? true : false, + starting_view_group, + focus_traversable, + focus_traversable_view); + } else { + // If the starting view is focusable, we don't want to go down, as we are + // traversing the view hierarchy tree bottom-up. + bool can_go_down = (direction == DOWN) && !IsFocusable(starting_view); + v = FindPreviousFocusableViewImpl(starting_view, check_starting_view, + true, + can_go_down, + starting_view_group, + focus_traversable, + focus_traversable_view); + } + + // Don't set the focus to something outside of this view hierarchy. + if (v && v != root_ && !root_->Contains(v)) + v = NULL; + + // If |cycle_| is true, prefer to keep cycling rather than returning NULL. + if (cycle_ && !v && initial_starting_view) { + v = FindNextFocusableView(NULL, reverse, direction, check_starting_view, + focus_traversable, focus_traversable_view); + DCHECK(IsFocusable(v)); + return v; + } + + // Doing some sanity checks. + if (v) { + DCHECK(IsFocusable(v)); + return v; + } + if (*focus_traversable) { + DCHECK(*focus_traversable_view); + return NULL; + } + // Nothing found. + return NULL; +} + +bool FocusSearch::IsViewFocusableCandidate(View* v, int skip_group_id) { + return IsFocusable(v) && + (v->IsGroupFocusTraversable() || skip_group_id == -1 || + v->group() != skip_group_id); +} + +bool FocusSearch::IsFocusable(View* v) { + if (accessibility_mode_) + return v && v->IsAccessibilityFocusableInRootView(); + + return v && v->IsFocusableInRootView(); +} + +View* FocusSearch::FindSelectedViewForGroup(View* view) { + // No group for that view. + if (view->IsGroupFocusTraversable() || view->group() == -1) + return view; + + View* selected_view = view->GetSelectedViewForGroup(view->group()); + if (selected_view) + return selected_view; + + // No view selected for that group, default to the specified view. + return view; +} + +View* FocusSearch::GetParent(View* v) { + return root_->Contains(v) ? v->parent() : NULL; +} + +// Strategy for finding the next focusable view: +// - keep going down the first child, stop when you find a focusable view or +// a focus traversable view (in that case return it) or when you reach a view +// with no children. +// - go to the right sibling and start the search from there (by invoking +// FindNextFocusableViewImpl on that view). +// - if the view has no right sibling, go up the parents until you find a parent +// with a right sibling and start the search from there. +View* FocusSearch::FindNextFocusableViewImpl( + View* starting_view, + bool check_starting_view, + bool can_go_up, + bool can_go_down, + int skip_group_id, + FocusTraversable** focus_traversable, + View** focus_traversable_view) { + if (check_starting_view) { + if (IsViewFocusableCandidate(starting_view, skip_group_id)) { + View* v = FindSelectedViewForGroup(starting_view); + // The selected view might not be focusable (if it is disabled for + // example). + if (IsFocusable(v)) + return v; + } + + *focus_traversable = starting_view->GetFocusTraversable(); + if (*focus_traversable) { + *focus_traversable_view = starting_view; + return NULL; + } + } + + // First let's try the left child. + if (can_go_down) { + if (starting_view->child_count() > 0) { + View* v = FindNextFocusableViewImpl(starting_view->GetChildViewAt(0), + true, false, true, skip_group_id, + focus_traversable, + focus_traversable_view); + if (v || *focus_traversable) + return v; + } + } + + // Then try the right sibling. + View* sibling = starting_view->GetNextFocusableView(); + if (sibling) { + View* v = FindNextFocusableViewImpl(sibling, + true, false, true, skip_group_id, + focus_traversable, + focus_traversable_view); + if (v || *focus_traversable) + return v; + } + + // Then go up to the parent sibling. + if (can_go_up) { + View* parent = GetParent(starting_view); + while (parent) { + sibling = parent->GetNextFocusableView(); + if (sibling) { + return FindNextFocusableViewImpl(sibling, + true, true, true, + skip_group_id, + focus_traversable, + focus_traversable_view); + } + parent = GetParent(parent); + } + } + + // We found nothing. + return NULL; +} + +// Strategy for finding the previous focusable view: +// - keep going down on the right until you reach a view with no children, if it +// it is a good candidate return it. +// - start the search on the left sibling. +// - if there are no left sibling, start the search on the parent (without going +// down). +View* FocusSearch::FindPreviousFocusableViewImpl( + View* starting_view, + bool check_starting_view, + bool can_go_up, + bool can_go_down, + int skip_group_id, + FocusTraversable** focus_traversable, + View** focus_traversable_view) { + // Let's go down and right as much as we can. + if (can_go_down) { + // Before we go into the direct children, we have to check if this view has + // a FocusTraversable. + *focus_traversable = starting_view->GetFocusTraversable(); + if (*focus_traversable) { + *focus_traversable_view = starting_view; + return NULL; + } + + if (starting_view->child_count() > 0) { + View* view = + starting_view->GetChildViewAt(starting_view->child_count() - 1); + View* v = FindPreviousFocusableViewImpl(view, true, false, true, + skip_group_id, + focus_traversable, + focus_traversable_view); + if (v || *focus_traversable) + return v; + } + } + + // Then look at this view. Here, we do not need to see if the view has + // a FocusTraversable, since we do not want to go down any more. + if (check_starting_view && + IsViewFocusableCandidate(starting_view, skip_group_id)) { + View* v = FindSelectedViewForGroup(starting_view); + // The selected view might not be focusable (if it is disabled for + // example). + if (IsFocusable(v)) + return v; + } + + // Then try the left sibling. + View* sibling = starting_view->GetPreviousFocusableView(); + if (sibling) { + return FindPreviousFocusableViewImpl(sibling, + true, true, true, + skip_group_id, + focus_traversable, + focus_traversable_view); + } + + // Then go up the parent. + if (can_go_up) { + View* parent = GetParent(starting_view); + if (parent) + return FindPreviousFocusableViewImpl(parent, + true, true, false, + skip_group_id, + focus_traversable, + focus_traversable_view); + } + + // We found nothing. + return NULL; +} + +} // namespace ui diff --git a/ui/views/focus/focus_search.h b/ui/views/focus/focus_search.h new file mode 100644 index 0000000..0b0c749 --- /dev/null +++ b/ui/views/focus/focus_search.h @@ -0,0 +1,122 @@ +// Copyright (c) 2011 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_VIEWS_FOCUS_FOCUS_SEARCH_H_ +#define UI_VIEWS_FOCUS_FOCUS_SEARCH_H_ +#pragma once + +#include "ui/views/view.h" + +namespace ui { + +class FocusTraversable; + +// FocusSearch is an object that implements the algorithm to find the +// next view to focus. +class FocusSearch { + public: + // The direction in which the focus traversal is going. + // TODO (jcampan): add support for lateral (left, right) focus traversal. The + // goal is to switch to focusable views on the same level when using the arrow + // keys (ala Windows: in a dialog box, arrow keys typically move between the + // dialog OK, Cancel buttons). + enum Direction { + UP = 0, + DOWN + }; + + // Constructor. + // - |root| is the root of the view hierarchy to traverse. Focus will be + // trapped inside. + // - |cycle| should be true if you want FindNextFocusableView to cycle back + // to the first view within this root when the traversal reaches + // the end. If this is true, then if you pass a valid starting + // view to FindNextFocusableView you will always get a valid view + // out, even if it's the same view. + // - |accessibility_mode| should be true if full keyboard accessibility is + // needed and you want to check IsAccessibilityFocusableInRootView(), + // rather than IsFocusableInRootView(). + FocusSearch(View* root, bool cycle, bool accessibility_mode); + virtual ~FocusSearch() {} + + // Finds the next view that should be focused and returns it. If a + // FocusTraversable is found while searching for the focusable view, + // returns NULL and sets |focus_traversable| to the FocusTraversable + // and |focus_traversable_view| to the view associated with the + // FocusTraversable. + // + // Return NULL if the end of the focus loop is reached, unless this object + // was initialized with |cycle|=true, in which case it goes back to the + // beginning when it reaches the end of the traversal. + // - |starting_view| is the view that should be used as the starting point + // when looking for the previous/next view. It may be NULL (in which case + // the first/last view should be used depending if normal/reverse). + // - |reverse| whether we should find the next (reverse is false) or the + // previous (reverse is true) view. + // - |direction| specifies whether we are traversing down (meaning we should + // look into child views) or traversing up (don't look at child views). + // - |check_starting_view| is true if starting_view may obtain the next focus. + // - |focus_traversable| is set to the focus traversable that should be + // traversed if one is found (in which case the call returns NULL). + // - |focus_traversable_view| is set to the view associated with the + // FocusTraversable set in the previous parameter (it is used as the + // starting view when looking for the next focusable view). + virtual View* FindNextFocusableView(View* starting_view, + bool reverse, + Direction direction, + bool check_starting_view, + FocusTraversable** focus_traversable, + View** focus_traversable_view); + + private: + // Convenience method that returns true if a view is focusable and does not + // belong to the specified group. + bool IsViewFocusableCandidate(View* v, int skip_group_id); + + // Convenience method; returns true if a view is not NULL and is focusable + // (checking IsAccessibilityFocusableInRootView() if accessibility_mode_ is + // true). + bool IsFocusable(View* v); + + // Returns the view selected for the group of the selected view. If the view + // does not belong to a group or if no view is selected in the group, the + // specified view is returned. + View* FindSelectedViewForGroup(View* view); + + // Get the parent, but stay within the root. Returns NULL if asked for + // the parent of root_. + View* GetParent(View* view); + + // Returns the next focusable view or view containing a FocusTraversable + // (NULL if none was found), starting at the starting_view. + // |check_starting_view|, |can_go_up| and |can_go_down| controls the + // traversal of the views hierarchy. |skip_group_id| specifies a group_id, + // -1 means no group. All views from a group are traversed in one pass. + View* FindNextFocusableViewImpl(View* starting_view, + bool check_starting_view, + bool can_go_up, + bool can_go_down, + int skip_group_id, + FocusTraversable** focus_traversable, + View** focus_traversable_view); + + // Same as FindNextFocusableViewImpl but returns the previous focusable view. + View* FindPreviousFocusableViewImpl(View* starting_view, + bool check_starting_view, + bool can_go_up, + bool can_go_down, + int skip_group_id, + FocusTraversable** focus_traversable, + View** focus_traversable_view); + + View* root_; + bool cycle_; + bool accessibility_mode_; + + DISALLOW_COPY_AND_ASSIGN(FocusSearch); +}; + +} // namespace ui + +#endif // UI_VIEWS_FOCUS_FOCUS_SEARCH_H_ diff --git a/ui/views/focus/view_storage.cc b/ui/views/focus/view_storage.cc new file mode 100644 index 0000000..504787a --- /dev/null +++ b/ui/views/focus/view_storage.cc @@ -0,0 +1,115 @@ +// Copyright (c) 2011 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/views/focus/view_storage.h" + +#include <algorithm> + +#include "base/logging.h" +#include "base/stl_util-inl.h" + +namespace ui { + +// static +ViewStorage* ViewStorage::GetInstance() { + return Singleton<ViewStorage>::get(); +} + +ViewStorage::ViewStorage() : view_storage_next_id_(0) { +} + +ViewStorage::~ViewStorage() { + STLDeleteContainerPairSecondPointers(view_to_ids_.begin(), + view_to_ids_.end()); +} + +int ViewStorage::CreateStorageID() { + return view_storage_next_id_++; +} + +void ViewStorage::StoreView(int storage_id, View* view) { + DCHECK(view); + std::map<int, View*>::iterator iter = id_to_view_.find(storage_id); + + if (iter != id_to_view_.end()) { + NOTREACHED(); + RemoveView(storage_id); + } + + id_to_view_[storage_id] = view; + + std::vector<int>* ids = NULL; + std::map<View*, std::vector<int>*>::iterator id_iter = + view_to_ids_.find(view); + if (id_iter == view_to_ids_.end()) { + ids = new std::vector<int>(); + view_to_ids_[view] = ids; + } else { + ids = id_iter->second; + } + ids->push_back(storage_id); +} + +View* ViewStorage::RetrieveView(int storage_id) { + std::map<int, View*>::iterator iter = id_to_view_.find(storage_id); + if (iter == id_to_view_.end()) + return NULL; + return iter->second; +} + +void ViewStorage::RemoveView(int storage_id) { + EraseView(storage_id, false); +} + +void ViewStorage::ViewRemoved(View* parent, View* removed) { + // Let's first retrieve the ids for that view. + std::map<View*, std::vector<int>*>::iterator ids_iter = + view_to_ids_.find(removed); + + if (ids_iter == view_to_ids_.end()) { + // That view is not in the view storage. + return; + } + + std::vector<int>* ids = ids_iter->second; + DCHECK(!ids->empty()); + EraseView((*ids)[0], true); +} + +void ViewStorage::EraseView(int storage_id, bool remove_all_ids) { + // Remove the view from id_to_view_location_. + std::map<int, View*>::iterator view_iter = id_to_view_.find(storage_id); + if (view_iter == id_to_view_.end()) + return; + + View* view = view_iter->second; + id_to_view_.erase(view_iter); + + // Also update view_to_ids_. + std::map<View*, std::vector<int>*>::iterator ids_iter = + view_to_ids_.find(view); + DCHECK(ids_iter != view_to_ids_.end()); + std::vector<int>* ids = ids_iter->second; + + if (remove_all_ids) { + for (size_t i = 0; i < ids->size(); ++i) { + view_iter = id_to_view_.find((*ids)[i]); + if (view_iter != id_to_view_.end()) + id_to_view_.erase(view_iter); + } + ids->clear(); + } else { + std::vector<int>::iterator id_iter = + std::find(ids->begin(), ids->end(), storage_id); + DCHECK(id_iter != ids->end()); + ids->erase(id_iter); + } + + if (ids->empty()) { + delete ids; + view_to_ids_.erase(ids_iter); + } +} + +} // namespace ui diff --git a/ui/views/focus/view_storage.h b/ui/views/focus/view_storage.h new file mode 100644 index 0000000..40ecf18 --- /dev/null +++ b/ui/views/focus/view_storage.h @@ -0,0 +1,74 @@ +// Copyright (c) 2011 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_VIEWS_FOCUS_VIEW_STORAGE_H_ +#define UI_VIEWS_FOCUS_VIEW_STORAGE_H_ +#pragma once + +#include <map> + +#include "base/singleton.h" +#include "ui/views/view.h" + +// This class is a simple storage place for storing/retrieving views. It is +// used for example in the FocusManager to store/restore focused views when the +// main window becomes active/inactive. +// It automatically removes a view from the storage if the view is removed from +// the tree hierarchy. +// +// To use it, you first need to create a view storage id that can then be used +// to store/retrieve views. + +namespace ui { + +class ViewStorage { + public: + // Returns the global ViewStorage instance. + // It is guaranted to be non NULL. + static ViewStorage* GetInstance(); + + // Returns a unique storage id that can be used to store/retrieve views. + int CreateStorageID(); + + // Associates |view| with the specified |storage_id|. + void StoreView(int storage_id, View* view); + + // Returns the view associated with |storage_id| if any, NULL otherwise. + View* RetrieveView(int storage_id); + + // Removes the view associated with |storage_id| if any. + void RemoveView(int storage_id); + + // Notifies the ViewStorage that a view was removed from its parent somewhere. + void ViewRemoved(View* parent, View* removed); + +#ifdef UNIT_TEST + size_t view_count() const { return view_to_ids_.size(); } +#endif + + private: + friend struct DefaultSingletonTraits<ViewStorage>; + + ViewStorage(); + ~ViewStorage(); + + // Removes the view associated with |storage_id|. If |remove_all_ids| is true, + // all other mapping pointing to the same view are removed as well. + void EraseView(int storage_id, bool remove_all_ids); + + // Next id for the view storage. + int view_storage_next_id_; + + // The association id to View used for the view storage. + std::map<int, View*> id_to_view_; + + // Association View to id, used to speed up view notification removal. + std::map<View*, std::vector<int>*> view_to_ids_; + + DISALLOW_COPY_AND_ASSIGN(ViewStorage); +}; + +} // namespace ui + +#endif // UI_VIEWS_FOCUS_VIEW_STORAGE_H_ diff --git a/ui/views/view.cc b/ui/views/view.cc index f4fe78c..7937f5d 100644 --- a/ui/views/view.cc +++ b/ui/views/view.cc @@ -293,6 +293,11 @@ void View::GetViewsWithGroup(int group, ViewVector* vec) const { (*it)->GetViewsWithGroup(group, vec); } +View* View::GetSelectedViewForGroup(int group_id) { + // TODO(beng): implementme + return NULL; +} + // Painting -------------------------------------------------------------------- void View::Invalidate() { @@ -393,6 +398,25 @@ bool View::SkipDefaultKeyEventProcessing(const KeyEvent& event) const { return false; } +bool View::IsGroupFocusTraversable() const { + return true; +} + +bool View::IsFocusableInRootView() const { + // TODO(beng): kill this, replace with direct check in focus manager. + return IsFocusable(); +} + +bool View::IsAccessibilityFocusableInRootView() const { + // TODO(beng): kill this, replace with direct check in focus manager. + return false; +} + +FocusTraversable* View::GetPaneFocusTraversable() const { + // TODO(beng): figure out what to do about this. + return NULL; +} + void View::OnFocus(/* const FocusEvent& event */) { } diff --git a/ui/views/view.h b/ui/views/view.h index f5c674532..aa51ce8 100644 --- a/ui/views/view.h +++ b/ui/views/view.h @@ -161,6 +161,9 @@ class View { // match the specified group id. void GetViewsWithGroup(int group, ViewVector* vec) const; + // TODO(beng): implementme + virtual View* GetSelectedViewForGroup(int group_id); + // Painting ------------------------------------------------------------------ // Add all or part of a View's bounds to the enclosing Widget's invalid @@ -227,6 +230,11 @@ class View { // Focus --------------------------------------------------------------------- virtual bool SkipDefaultKeyEventProcessing(const KeyEvent& event) const; + virtual bool IsGroupFocusTraversable() const; + // TODO(beng): kill these, move to focus manager. + virtual bool IsFocusableInRootView() const; + virtual bool IsAccessibilityFocusableInRootView() const; + virtual FocusTraversable* GetPaneFocusTraversable() const; virtual void OnFocus(/* const FocusEvent& event */); virtual void OnBlur(); @@ -281,6 +289,8 @@ class View { private: friend internal::RootView; + friend class FocusManager; + friend class FocusSearch; // State collected during a MousePressed event to detect possible drag // operations. diff --git a/ui/views/views.gyp b/ui/views/views.gyp index 63e00ae..f6c5bc8 100644 --- a/ui/views/views.gyp +++ b/ui/views/views.gyp @@ -47,10 +47,13 @@ 'msvs_guid': '70760ECA-4D8B-47A4-ACDC-D3E7F25F0543', 'dependencies': [ '<(DEPTH)/app/app.gyp:app_base', + '<(DEPTH)/app/app.gyp:app_strings', '<(DEPTH)/gfx/gfx.gyp:gfx', '<(DEPTH)/skia/skia.gyp:skia', ], 'sources': [ + 'events/accelerator.cc', + 'events/accelerator.h', 'events/context_menu_controller.h', 'events/drag_controller.h', 'events/event.cc', @@ -58,6 +61,12 @@ 'events/event_win.cc', 'focus/accelerator_handler.h', 'focus/accelerator_handler_win.cc', + 'focus/focus_manager.cc', + 'focus/focus_manager.h', + 'focus/focus_search.cc', + 'focus/focus_search.h', + 'focus/view_storage.cc', + 'focus/view_storage.h', 'layout/fill_layout.cc', 'layout/fill_layout.h', 'layout/layout_manager.cc', @@ -119,6 +128,7 @@ 'v2', ], 'sources': [ + # 'focus/focus_manager_unittest.cc', 'rendering/border_unittest.cc', 'run_all_unittests.cc', 'view_unittest.cc', |