summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-02-02 21:24:22 +0000
committerben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-02-02 21:24:22 +0000
commit72fc4b91fd45c00004a69790b59ba4917ba117ed (patch)
tree908a0e34be29a3206f88cac0064d43ec00db7ab9
parentd89ff811f8c0e5033a38273979d6b367ed057b0e (diff)
downloadchromium_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.h1
-rw-r--r--ui/views/events/accelerator.cc179
-rw-r--r--ui/views/events/accelerator.h32
-rw-r--r--ui/views/events/event.cc13
-rw-r--r--ui/views/events/event.h4
-rw-r--r--ui/views/focus/focus_manager.cc530
-rw-r--r--ui/views/focus/focus_manager.h344
-rw-r--r--ui/views/focus/focus_manager_unittest.cc1730
-rw-r--r--ui/views/focus/focus_search.cc274
-rw-r--r--ui/views/focus/focus_search.h122
-rw-r--r--ui/views/focus/view_storage.cc115
-rw-r--r--ui/views/focus/view_storage.h74
-rw-r--r--ui/views/view.cc24
-rw-r--r--ui/views/view.h10
-rw-r--r--ui/views/views.gyp10
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',