summaryrefslogtreecommitdiffstats
path: root/ui/views/focus
diff options
context:
space:
mode:
authortfarina@chromium.org <tfarina@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-11-18 21:58:23 +0000
committertfarina@chromium.org <tfarina@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-11-18 21:58:23 +0000
commitb1ed27851db7f86d7f71dfe4c897c796a953fc25 (patch)
treeb9d972e7a6770b4c9055507fe2e9d329b0974106 /ui/views/focus
parentcee34b707ac41e83ff9d9045c0ffda49ec3f556f (diff)
downloadchromium_src-b1ed27851db7f86d7f71dfe4c897c796a953fc25.zip
chromium_src-b1ed27851db7f86d7f71dfe4c897c796a953fc25.tar.gz
chromium_src-b1ed27851db7f86d7f71dfe4c897c796a953fc25.tar.bz2
views: Move bubble, events, focus and layout to ui/views/.
Left stub files that will be removed in a follow up patch after updating the files to point to the new location. BUG=104039 R=ben@chromium.org Review URL: http://codereview.chromium.org/8588064 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@110761 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ui/views/focus')
-rw-r--r--ui/views/focus/accelerator_handler.h61
-rw-r--r--ui/views/focus/accelerator_handler_aura.cc29
-rw-r--r--ui/views/focus/accelerator_handler_gtk.cc22
-rw-r--r--ui/views/focus/accelerator_handler_gtk_unittest.cc199
-rw-r--r--ui/views/focus/accelerator_handler_touch.cc187
-rw-r--r--ui/views/focus/accelerator_handler_wayland.cc18
-rw-r--r--ui/views/focus/accelerator_handler_win.cc60
-rw-r--r--ui/views/focus/external_focus_tracker.cc72
-rw-r--r--ui/views/focus/external_focus_tracker.h78
-rw-r--r--ui/views/focus/focus_manager.cc413
-rw-r--r--ui/views/focus/focus_manager.h283
-rw-r--r--ui/views/focus/focus_manager_factory.cc55
-rw-r--r--ui/views/focus/focus_manager_factory.h41
-rw-r--r--ui/views/focus/focus_manager_unittest.cc1763
-rw-r--r--ui/views/focus/focus_search.cc272
-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.h70
-rw-r--r--ui/views/focus/widget_focus_manager.cc50
-rw-r--r--ui/views/focus/widget_focus_manager.h82
20 files changed, 3992 insertions, 0 deletions
diff --git a/ui/views/focus/accelerator_handler.h b/ui/views/focus/accelerator_handler.h
new file mode 100644
index 0000000..c0f9591
--- /dev/null
+++ b/ui/views/focus/accelerator_handler.h
@@ -0,0 +1,61 @@
+// 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_ACCELERATOR_HANDLER_H_
+#define UI_VIEWS_FOCUS_ACCELERATOR_HANDLER_H_
+#pragma once
+
+#include "build/build_config.h"
+
+#if defined(TOOLKIT_USES_GTK)
+#include <gdk/gdk.h>
+#endif
+
+#include <set>
+#include <vector>
+
+#include "base/message_loop.h"
+#include "views/views_export.h"
+
+namespace views {
+
+#if defined(TOUCH_UI) || defined(USE_AURA)
+#if defined(USE_X11) && !defined(USE_WAYLAND)
+// Dispatch an XEvent to the RootView. Return true if the event was dispatched
+// and handled, false otherwise.
+bool VIEWS_EXPORT DispatchXEvent(XEvent* xevent);
+#endif // USE_X11 && !USE_WAYLAND
+#endif // TOUCH_UI || USE_AURA
+
+// This class delegates the key messages to the associated FocusManager class
+// for the window that is receiving these messages for accelerator processing.
+class VIEWS_EXPORT AcceleratorHandler : public MessageLoop::Dispatcher {
+ public:
+ AcceleratorHandler();
+
+ // Dispatcher method. This returns true if an accelerator was processed by the
+ // focus manager
+#if defined(OS_WIN)
+ virtual bool Dispatch(const MSG& msg);
+#elif defined(USE_WAYLAND)
+ virtual base::MessagePumpDispatcher::DispatchStatus Dispatch(
+ base::wayland::WaylandEvent* ev);
+#elif defined(TOUCH_UI) || defined(USE_AURA)
+ virtual base::MessagePumpDispatcher::DispatchStatus Dispatch(XEvent* xev);
+#else
+ virtual bool Dispatch(GdkEvent* event);
+#endif
+
+ private:
+#if defined(OS_WIN)
+ // The keys currently pressed and consumed by the FocusManager.
+ std::set<WPARAM> pressed_keys_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(AcceleratorHandler);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_FOCUS_ACCELERATOR_HANDLER_H_
diff --git a/ui/views/focus/accelerator_handler_aura.cc b/ui/views/focus/accelerator_handler_aura.cc
new file mode 100644
index 0000000..ab552899
--- /dev/null
+++ b/ui/views/focus/accelerator_handler_aura.cc
@@ -0,0 +1,29 @@
+// 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 "views/focus/accelerator_handler.h"
+
+namespace views {
+
+AcceleratorHandler::AcceleratorHandler() {
+}
+
+#if defined(OS_WIN)
+bool AcceleratorHandler::Dispatch(const MSG& msg) {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ return true;
+}
+#else
+base::MessagePumpDispatcher::DispatchStatus AcceleratorHandler::Dispatch(
+ XEvent*) {
+ return base::MessagePumpDispatcher::EVENT_IGNORED;
+}
+
+bool DispatchXEvent(XEvent* xev) {
+ return false;
+}
+#endif // defined(OS_WIN)
+
+} // namespace views
diff --git a/ui/views/focus/accelerator_handler_gtk.cc b/ui/views/focus/accelerator_handler_gtk.cc
new file mode 100644
index 0000000..fab3b86
--- /dev/null
+++ b/ui/views/focus/accelerator_handler_gtk.cc
@@ -0,0 +1,22 @@
+// 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 "views/focus/accelerator_handler.h"
+
+#include <gtk/gtk.h>
+
+#include "views/focus/focus_manager.h"
+
+namespace views {
+
+AcceleratorHandler::AcceleratorHandler() {}
+
+bool AcceleratorHandler::Dispatch(GdkEvent* event) {
+ // The logic for handling keyboard accelerators has been moved into
+ // NativeWidgetGtk::OnEventKey handler (views/widget/widget_gtk.cc).
+ gtk_main_do_event(event);
+ return true;
+}
+
+} // namespace views
diff --git a/ui/views/focus/accelerator_handler_gtk_unittest.cc b/ui/views/focus/accelerator_handler_gtk_unittest.cc
new file mode 100644
index 0000000..098325d
--- /dev/null
+++ b/ui/views/focus/accelerator_handler_gtk_unittest.cc
@@ -0,0 +1,199 @@
+// 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 <gdk/gdkkeysyms.h>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/rect.h"
+#include "ui/base/models/accelerator.h"
+#include "views/focus/accelerator_handler.h"
+#include "views/focus/focus_manager.h"
+#include "views/view.h"
+#include "views/widget/widget.h"
+#include "views/widget/widget_delegate.h"
+
+namespace views {
+
+class AcceleratorHandlerGtkTest
+ : public testing::Test,
+ public WidgetDelegate,
+ public ui::AcceleratorTarget {
+ public:
+ AcceleratorHandlerGtkTest()
+ : kMenuAccelerator(ui::VKEY_MENU, false, false, false),
+ kHomepageAccelerator(ui::VKEY_HOME, false, false, true),
+ content_view_(NULL) {
+ }
+
+ virtual void SetUp() {
+ window_ = Widget::CreateWindowWithBounds(this, gfx::Rect(0, 0, 500, 500));
+ window_->Show();
+ FocusManager* focus_manager = window_->GetFocusManager();
+ focus_manager->RegisterAccelerator(kMenuAccelerator, this);
+ focus_manager->RegisterAccelerator(kHomepageAccelerator, this);
+ menu_pressed_ = false;
+ home_pressed_ = false;
+ }
+
+ virtual void TearDown() {
+ window_->Close();
+
+ // Flush the message loop to make application verifiers happy.
+ message_loop_.RunAllPending();
+ }
+
+ GdkEventKey CreateKeyEvent(GdkEventType type, guint keyval, guint state) {
+ GdkEventKey evt;
+ memset(&evt, 0, sizeof(evt));
+ evt.type = type;
+ evt.keyval = keyval;
+ // The keyval won't be a "correct" hardware keycode for any real hardware,
+ // but the code should never depend on exact hardware keycodes, just the
+ // fact that the code for presses and releases of the same key match.
+ evt.hardware_keycode = keyval;
+ evt.state = state;
+ GtkWidget* widget = GTK_WIDGET(window_->GetNativeWindow());
+ evt.window = widget->window;
+ return evt;
+ }
+
+ // AcceleratorTarget implementation.
+ virtual bool AcceleratorPressed(const ui::Accelerator& accelerator) {
+ if (accelerator == kMenuAccelerator)
+ menu_pressed_ = true;
+ else if (accelerator == kHomepageAccelerator)
+ home_pressed_ = true;
+ return true;
+ }
+
+ // WidgetDelegate Implementation.
+ virtual View* GetContentsView() {
+ if (!content_view_)
+ content_view_ = new View();
+ return content_view_;
+ }
+ virtual const views::Widget* GetWidget() const {
+ return content_view_->GetWidget();
+ }
+ virtual views::Widget* GetWidget() {
+ return content_view_->GetWidget();
+ }
+
+ virtual void InitContentView() {
+ }
+
+ protected:
+ bool menu_pressed_;
+ bool home_pressed_;
+
+ private:
+ ui::Accelerator kMenuAccelerator;
+ ui::Accelerator kHomepageAccelerator;
+ Widget* window_;
+ View* content_view_;
+ MessageLoopForUI message_loop_;
+ DISALLOW_COPY_AND_ASSIGN(AcceleratorHandlerGtkTest);
+};
+
+// Test that the homepage accelerator (Alt+Home) is activated on key down
+// and that the menu accelerator (Alt) is never activated.
+TEST_F(AcceleratorHandlerGtkTest, TestHomepageAccelerator) {
+ AcceleratorHandler handler;
+ GdkEventKey evt;
+
+ ASSERT_FALSE(menu_pressed_);
+ ASSERT_FALSE(home_pressed_);
+
+ evt = CreateKeyEvent(GDK_KEY_PRESS, GDK_Alt_L, 0);
+ EXPECT_TRUE(handler.Dispatch(reinterpret_cast<GdkEvent*>(&evt)));
+
+ ASSERT_FALSE(menu_pressed_);
+ ASSERT_FALSE(home_pressed_);
+
+ evt = CreateKeyEvent(GDK_KEY_PRESS, GDK_Home, GDK_MOD1_MASK);
+ EXPECT_TRUE(handler.Dispatch(reinterpret_cast<GdkEvent*>(&evt)));
+
+ ASSERT_FALSE(menu_pressed_);
+ ASSERT_TRUE(home_pressed_);
+
+ evt = CreateKeyEvent(GDK_KEY_RELEASE, GDK_Home, GDK_MOD1_MASK);
+ EXPECT_TRUE(handler.Dispatch(reinterpret_cast<GdkEvent*>(&evt)));
+ evt = CreateKeyEvent(GDK_KEY_RELEASE, GDK_Alt_L, 0);
+ EXPECT_TRUE(handler.Dispatch(reinterpret_cast<GdkEvent*>(&evt)));
+
+ ASSERT_FALSE(menu_pressed_);
+ ASSERT_TRUE(home_pressed_);
+}
+
+// Test that the menu accelerator is activated on key up and not key down.
+TEST_F(AcceleratorHandlerGtkTest, TestMenuAccelerator) {
+ AcceleratorHandler handler;
+ GdkEventKey evt;
+
+ ASSERT_FALSE(menu_pressed_);
+
+ evt = CreateKeyEvent(GDK_KEY_PRESS, GDK_Alt_L, 0);
+ EXPECT_TRUE(handler.Dispatch(reinterpret_cast<GdkEvent*>(&evt)));
+
+ ASSERT_FALSE(menu_pressed_);
+
+ evt = CreateKeyEvent(GDK_KEY_RELEASE, GDK_Alt_L, 0);
+ EXPECT_TRUE(handler.Dispatch(reinterpret_cast<GdkEvent*>(&evt)));
+
+ ASSERT_TRUE(menu_pressed_);
+}
+
+// Test that the menu accelerator isn't confused by the interaction of the
+// Alt and Shift keys. Try the following sequence on Linux:
+// Press Alt
+// Press Shift
+// Release Alt
+// Release Shift
+// The key codes for pressing Alt and releasing Alt are different! This
+// caused a bug in a previous version of the code, which is now fixed by
+// keeping track of hardware keycodes, which are consistent.
+TEST_F(AcceleratorHandlerGtkTest, TestAltShiftInteraction) {
+ AcceleratorHandler handler;
+ GdkEventKey evt;
+
+ ASSERT_FALSE(menu_pressed_);
+
+ // Press Shift.
+ evt = CreateKeyEvent(GDK_KEY_PRESS, GDK_Shift_L, 0);
+ evt.hardware_keycode = 0x32;
+ EXPECT_TRUE(handler.Dispatch(reinterpret_cast<GdkEvent*>(&evt)));
+ // Press Alt - but GDK calls this Meta when Shift is also down.
+ evt = CreateKeyEvent(GDK_KEY_PRESS, GDK_Meta_L, 0);
+ evt.hardware_keycode = 0x40;
+ EXPECT_TRUE(handler.Dispatch(reinterpret_cast<GdkEvent*>(&evt)));
+
+ // Release Shift.
+ evt = CreateKeyEvent(GDK_KEY_RELEASE, GDK_Shift_L, 0);
+ evt.hardware_keycode = 0x32;
+ EXPECT_TRUE(handler.Dispatch(reinterpret_cast<GdkEvent*>(&evt)));
+ // Release Alt - with Shift not down, the keyval is now Alt, but
+ // the hardware keycode is unchanged.
+ evt = CreateKeyEvent(GDK_KEY_RELEASE, GDK_Alt_L, 0);
+ evt.hardware_keycode = 0x40;
+ EXPECT_TRUE(handler.Dispatch(reinterpret_cast<GdkEvent*>(&evt)));
+
+ ASSERT_FALSE(menu_pressed_);
+
+ // Press Alt by itself.
+ evt = CreateKeyEvent(GDK_KEY_PRESS, GDK_Alt_L, 0);
+ evt.hardware_keycode = 0x40;
+ EXPECT_TRUE(handler.Dispatch(reinterpret_cast<GdkEvent*>(&evt)));
+
+ // This line fails if we don't keep track of hardware keycodes.
+ ASSERT_FALSE(menu_pressed_);
+
+ // Release Alt - now this should trigger the menu shortcut.
+ evt = CreateKeyEvent(GDK_KEY_RELEASE, GDK_Alt_L, 0);
+ evt.hardware_keycode = 0x40;
+ EXPECT_TRUE(handler.Dispatch(reinterpret_cast<GdkEvent*>(&evt)));
+
+ ASSERT_TRUE(menu_pressed_);
+}
+
+} // namespace views
diff --git a/ui/views/focus/accelerator_handler_touch.cc b/ui/views/focus/accelerator_handler_touch.cc
new file mode 100644
index 0000000..1c4c9b3
--- /dev/null
+++ b/ui/views/focus/accelerator_handler_touch.cc
@@ -0,0 +1,187 @@
+// 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 "views/focus/accelerator_handler.h"
+
+#include <bitset>
+#include <gtk/gtk.h>
+#include <X11/extensions/XInput2.h>
+
+#include "ui/base/touch/touch_factory.h"
+#include "ui/views/ime/input_method.h"
+#include "views/events/event.h"
+#include "views/focus/focus_manager.h"
+#include "views/view.h"
+#include "views/widget/native_widget.h"
+
+namespace views {
+
+namespace {
+
+Widget* FindWidgetForGdkWindow(GdkWindow* gdk_window) {
+ gpointer data = NULL;
+ gdk_window_get_user_data(gdk_window, &data);
+ GtkWidget* gtk_widget = reinterpret_cast<GtkWidget*>(data);
+ if (!gtk_widget || !GTK_IS_WIDGET(gtk_widget)) {
+ DLOG(WARNING) << "no GtkWidget found for that GdkWindow";
+ return NULL;
+ }
+ Widget* widget = Widget::GetWidgetForNativeView(gtk_widget);
+
+ if (!widget) {
+ DLOG(WARNING) << "no NativeWidgetGtk found for that GtkWidget";
+ return NULL;
+ }
+ return widget;
+}
+
+} // namespace
+
+bool DispatchX2Event(Widget* widget, XEvent* xev) {
+ XGenericEventCookie* cookie = &xev->xcookie;
+ switch (cookie->evtype) {
+ case XI_KeyPress:
+ case XI_KeyRelease: {
+ // TODO(sad): We don't capture XInput2 events from keyboard yet.
+ break;
+ }
+#if defined(USE_XI2_MT)
+ case XI_TouchBegin:
+ case XI_TouchEnd:
+ case XI_TouchUpdate: {
+ // Hide the cursor when a touch event comes in.
+ ui::TouchFactory::GetInstance()->SetCursorVisible(false, false);
+
+ // If the TouchEvent is processed by |widget|, then return.
+ TouchEvent touch(xev);
+ if (widget->OnTouchEvent(touch) != ui::TOUCH_STATUS_UNKNOWN)
+ return true;
+
+ // We do not want to generate a mouse event for an unprocessed touch
+ // event here. That is already done by the gesture manager in
+ // RootView::OnTouchEvent.
+ return false;
+ }
+#endif
+ case XI_ButtonPress:
+ case XI_ButtonRelease:
+ case XI_Motion: {
+ XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(cookie->data);
+
+ // Scrolling the wheel generates press/release events with button id's 4
+ // and 5. In case of a wheelscroll, we do not want to show the cursor.
+ if (xievent->detail == 4 || xievent->detail == 5) {
+ MouseWheelEvent wheelev(xev);
+ return widget->OnMouseEvent(wheelev);
+ }
+
+ ui::TouchFactory* factory = ui::TouchFactory::GetInstance();
+ // Is the event coming from a touch device?
+ if (factory->IsTouchDevice(xievent->sourceid)) {
+ // Hide the cursor when a touch event comes in.
+ factory->SetCursorVisible(false, false);
+
+ // With XInput 2.0, XI_ButtonPress and XI_ButtonRelease events are
+ // ignored, as XI_Motion events contain enough data to detect finger
+ // press and release. See more notes in TouchFactory::TouchParam.
+ if ((cookie->evtype == XI_ButtonPress ||
+ cookie->evtype == XI_ButtonRelease) &&
+ factory->IsRealTouchDevice(xievent->sourceid))
+ return false;
+
+ // If the TouchEvent is processed by |widget|, then return. Otherwise
+ // let it fall through so it can be used as a MouseEvent, if desired.
+ TouchEvent touch(xev);
+ if (widget->OnTouchEvent(touch) != ui::TOUCH_STATUS_UNKNOWN)
+ return true;
+
+ // We do not want to generate a mouse event for an unprocessed touch
+ // event here. That is already done by the gesture manager in
+ // RootView::OnTouchEvent.
+ return false;
+ } else {
+ MouseEvent mouseev(xev);
+
+ // Show the cursor. Start a timer to hide the cursor after a delay on
+ // move (not drag) events, or if the only button pressed is released.
+ bool start_timer = mouseev.type() == ui::ET_MOUSE_MOVED;
+ start_timer |= mouseev.type() == ui::ET_MOUSE_RELEASED &&
+ (mouseev.IsOnlyLeftMouseButton() ||
+ mouseev.IsOnlyMiddleMouseButton() ||
+ mouseev.IsOnlyRightMouseButton());
+ factory->SetCursorVisible(true, start_timer);
+
+ return widget->OnMouseEvent(mouseev);
+ }
+ }
+ }
+ return false;
+}
+
+bool DispatchXEvent(XEvent* xev) {
+ GdkDisplay* gdisp = gdk_display_get_default();
+ XID xwindow = xev->xany.window;
+
+ if (xev->type == GenericEvent) {
+ if (!ui::TouchFactory::GetInstance()->ShouldProcessXI2Event(xev))
+ return true; // Consume the event.
+
+ XGenericEventCookie* cookie = &xev->xcookie;
+ if (cookie->evtype == XI_HierarchyChanged) {
+ ui::TouchFactory::GetInstance()->UpdateDeviceList(cookie->display);
+ return true;
+ }
+
+ XIDeviceEvent* xiev = static_cast<XIDeviceEvent*>(cookie->data);
+ xwindow = xiev->event;
+ }
+
+ GdkWindow* gwind = gdk_window_lookup_for_display(gdisp, xwindow);
+ Widget* widget = FindWidgetForGdkWindow(gwind);
+ if (widget) {
+ switch (xev->type) {
+ case KeyPress:
+ case KeyRelease: {
+ KeyEvent keyev(xev);
+ InputMethod* ime = widget->GetInputMethod();
+ // Always dispatch key events to the input method first, to make sure
+ // that the input method's hotkeys work all time.
+ if (ime) {
+ ime->DispatchKeyEvent(keyev);
+ return true;
+ }
+ return widget->OnKeyEvent(keyev);
+ }
+ case ButtonPress:
+ case ButtonRelease:
+ if (xev->xbutton.button == 4 || xev->xbutton.button == 5) {
+ // Scrolling the wheel triggers button press/release events.
+ MouseWheelEvent wheelev(xev);
+ return widget->OnMouseEvent(wheelev);
+ }
+ // fallthrough
+ case MotionNotify: {
+ MouseEvent mouseev(xev);
+ return widget->OnMouseEvent(mouseev);
+ }
+
+ case GenericEvent: {
+ return DispatchX2Event(widget, xev);
+ }
+ }
+ }
+
+ return false;
+}
+
+AcceleratorHandler::AcceleratorHandler() {}
+
+base::MessagePumpDispatcher::DispatchStatus
+ AcceleratorHandler::Dispatch(XEvent* xev) {
+ return DispatchXEvent(xev) ?
+ base::MessagePumpDispatcher::EVENT_PROCESSED :
+ base::MessagePumpDispatcher::EVENT_IGNORED;
+}
+
+} // namespace views
diff --git a/ui/views/focus/accelerator_handler_wayland.cc b/ui/views/focus/accelerator_handler_wayland.cc
new file mode 100644
index 0000000..a8963ee
--- /dev/null
+++ b/ui/views/focus/accelerator_handler_wayland.cc
@@ -0,0 +1,18 @@
+// 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 "views/focus/accelerator_handler.h"
+
+#include "views/focus/focus_manager.h"
+
+namespace views {
+
+AcceleratorHandler::AcceleratorHandler() {}
+
+base::MessagePumpDispatcher::DispatchStatus
+ AcceleratorHandler::Dispatch(base::wayland::WaylandEvent* ev) {
+ return base::MessagePumpDispatcher::EVENT_IGNORED;
+}
+
+} // namespace views
diff --git a/ui/views/focus/accelerator_handler_win.cc b/ui/views/focus/accelerator_handler_win.cc
new file mode 100644
index 0000000..8f2c8e8
--- /dev/null
+++ b/ui/views/focus/accelerator_handler_win.cc
@@ -0,0 +1,60 @@
+// 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 "views/focus/accelerator_handler.h"
+
+#include "ui/base/keycodes/keyboard_codes.h"
+#include "ui/base/keycodes/keyboard_code_conversion_win.h"
+#include "views/events/event.h"
+#include "views/focus/focus_manager.h"
+#include "views/widget/widget.h"
+
+namespace views {
+
+AcceleratorHandler::AcceleratorHandler() {
+}
+
+bool AcceleratorHandler::Dispatch(const MSG& msg) {
+ bool process_message = true;
+
+ if (msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST) {
+ Widget* widget = Widget::GetTopLevelWidgetForNativeView(msg.hwnd);
+ FocusManager* focus_manager = widget ? widget->GetFocusManager() : NULL;
+ if (focus_manager) {
+ switch (msg.message) {
+ case WM_KEYDOWN:
+ case WM_SYSKEYDOWN: {
+ KeyEvent event(msg);
+ process_message = focus_manager->OnKeyEvent(event);
+ if (!process_message) {
+ // Record that this key is pressed so we can remember not to
+ // translate and dispatch the associated WM_KEYUP.
+ pressed_keys_.insert(msg.wParam);
+ }
+ break;
+ }
+ case WM_KEYUP:
+ case WM_SYSKEYUP: {
+ std::set<WPARAM>::iterator iter = pressed_keys_.find(msg.wParam);
+ if (iter != pressed_keys_.end()) {
+ // Don't translate/dispatch the KEYUP since we have eaten the
+ // associated KEYDOWN.
+ pressed_keys_.erase(iter);
+ return true;
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ if (process_message) {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+
+ return true;
+}
+
+} // namespace views
diff --git a/ui/views/focus/external_focus_tracker.cc b/ui/views/focus/external_focus_tracker.cc
new file mode 100644
index 0000000..193b57e
--- /dev/null
+++ b/ui/views/focus/external_focus_tracker.cc
@@ -0,0 +1,72 @@
+// 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 "views/focus/external_focus_tracker.h"
+
+#include "base/logging.h"
+#include "views/view.h"
+#include "views/focus/view_storage.h"
+
+namespace views {
+
+ExternalFocusTracker::ExternalFocusTracker(View* parent_view,
+ FocusManager* focus_manager)
+ : focus_manager_(focus_manager),
+ parent_view_(parent_view) {
+ DCHECK(focus_manager);
+ DCHECK(parent_view);
+ view_storage_ = ViewStorage::GetInstance();
+ last_focused_view_storage_id_ = view_storage_->CreateStorageID();
+ // Store the view which is focused when we're created.
+ StartTracking();
+}
+
+ExternalFocusTracker::~ExternalFocusTracker() {
+ view_storage_->RemoveView(last_focused_view_storage_id_);
+ if (focus_manager_)
+ focus_manager_->RemoveFocusChangeListener(this);
+}
+
+void ExternalFocusTracker::OnWillChangeFocus(View* focused_before,
+ View* focused_now) {
+ if (focused_now && !parent_view_->Contains(focused_now) &&
+ parent_view_ != focused_now) {
+ // Store the newly focused view.
+ StoreLastFocusedView(focused_now);
+ }
+}
+
+void ExternalFocusTracker::OnDidChangeFocus(View* focused_before,
+ View* focused_now) {
+}
+
+void ExternalFocusTracker::FocusLastFocusedExternalView() {
+ View* last_focused_view =
+ view_storage_->RetrieveView(last_focused_view_storage_id_);
+ if (last_focused_view)
+ last_focused_view->RequestFocus();
+}
+
+void ExternalFocusTracker::SetFocusManager(FocusManager* focus_manager) {
+ if (focus_manager_)
+ focus_manager_->RemoveFocusChangeListener(this);
+ focus_manager_ = focus_manager;
+ if (focus_manager_)
+ StartTracking();
+}
+
+void ExternalFocusTracker::StoreLastFocusedView(View* view) {
+ view_storage_->RemoveView(last_focused_view_storage_id_);
+ // If the view is NULL, remove the last focused view from storage, but don't
+ // try to store NULL.
+ if (view != NULL)
+ view_storage_->StoreView(last_focused_view_storage_id_, view);
+}
+
+void ExternalFocusTracker::StartTracking() {
+ StoreLastFocusedView(focus_manager_->GetFocusedView());
+ focus_manager_->AddFocusChangeListener(this);
+}
+
+} // namespace views
diff --git a/ui/views/focus/external_focus_tracker.h b/ui/views/focus/external_focus_tracker.h
new file mode 100644
index 0000000..0ea1479
--- /dev/null
+++ b/ui/views/focus/external_focus_tracker.h
@@ -0,0 +1,78 @@
+// 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_EXTERNAL_FOCUS_TRACKER_H_
+#define UI_VIEWS_FOCUS_EXTERNAL_FOCUS_TRACKER_H_
+#pragma once
+
+#include "views/focus/focus_manager.h"
+
+namespace views {
+
+class View;
+class ViewStorage;
+
+// ExternalFocusTracker tracks the last focused view which belongs to the
+// provided focus manager and is not either the provided parent view or one of
+// its descendants. This is generally used if the parent view want to return
+// focus to some other view once it is dismissed. The parent view and the focus
+// manager must exist for the duration of the tracking. If the focus manager
+// must be deleted before this object is deleted, make sure to call
+// SetFocusManager(NULL) first.
+//
+// Typical use: When a view is added to the view hierarchy, it instantiates an
+// ExternalFocusTracker and passes in itself and its focus manager. Then,
+// when that view wants to return focus to the last focused view which is not
+// itself and not a descandant of itself, (usually when it is being closed)
+// it calls FocusLastFocusedExternalView.
+class VIEWS_EXPORT ExternalFocusTracker : public FocusChangeListener {
+ public:
+ ExternalFocusTracker(View* parent_view, FocusManager* focus_manager);
+
+ virtual ~ExternalFocusTracker();
+ // FocusChangeListener implementation.
+ virtual void OnWillChangeFocus(View* focused_before, View* focused_now);
+ virtual void OnDidChangeFocus(View* focused_before, View* focused_now);
+
+ // Focuses last focused view which is not a child of parent view and is not
+ // parent view itself. Returns true if focus for a view was requested, false
+ // otherwise.
+ void FocusLastFocusedExternalView();
+
+ // Sets the focus manager whose focus we are tracking. |focus_manager| can
+ // be NULL, but no focus changes will be tracked. This is useful if the focus
+ // manager went away, but you might later want to start tracking with a new
+ // manager later, or call FocusLastFocusedExternalView to focus the previous
+ // view.
+ void SetFocusManager(FocusManager* focus_manager);
+
+ private:
+ // Store the provided view. This view will be focused when
+ // FocusLastFocusedExternalView is called.
+ void StoreLastFocusedView(View* view);
+
+ // Store the currently focused view for our view manager and register as a
+ // listener for future focus changes.
+ void StartTracking();
+
+ // Focus manager which we are a listener for.
+ FocusManager* focus_manager_;
+
+ // ID of the last focused view, which we store in view_storage_.
+ int last_focused_view_storage_id_;
+
+ // Used to store the last focused view which is not a child of
+ // ExternalFocusTracker.
+ ViewStorage* view_storage_;
+
+ // The parent view of views which we should not track focus changes to. We
+ // also do not track changes to parent_view_ itself.
+ View* parent_view_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExternalFocusTracker);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_FOCUS_EXTERNAL_FOCUS_TRACKER_H_
diff --git a/ui/views/focus/focus_manager.cc b/ui/views/focus/focus_manager.cc
new file mode 100644
index 0000000..3ee0755
--- /dev/null
+++ b/ui/views/focus/focus_manager.cc
@@ -0,0 +1,413 @@
+// 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 "views/focus/focus_manager.h"
+
+#include <algorithm>
+
+#include "base/auto_reset.h"
+#include "base/logging.h"
+#include "build/build_config.h"
+#include "ui/base/accelerator_manager.h"
+#include "ui/base/keycodes/keyboard_codes.h"
+#include "ui/base/models/accelerator.h"
+#include "views/focus/focus_search.h"
+#include "views/focus/view_storage.h"
+#include "views/focus/widget_focus_manager.h"
+#include "views/view.h"
+#include "views/widget/root_view.h"
+#include "views/widget/widget.h"
+
+namespace views {
+
+FocusManager::FocusManager(Widget* widget)
+ : widget_(widget),
+ focused_view_(NULL),
+ accelerator_manager_(new ui::AcceleratorManager),
+ focus_change_reason_(kReasonDirectFocusChange),
+ is_changing_focus_(false) {
+ DCHECK(widget_);
+ stored_focused_view_storage_id_ =
+ ViewStorage::GetInstance()->CreateStorageID();
+}
+
+FocusManager::~FocusManager() {
+}
+
+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 NativeWidgetGtk::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) && !defined(USE_AURA)
+ HWND top_window = 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_->GetGroup() != -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);
+ View::Views views;
+ focused_view_->parent()->GetViewsInGroup(focused_view_->GetGroup(), &views);
+ View::Views::const_iterator i(
+ std::find(views.begin(), views.end(), focused_view_));
+ DCHECK(i != views.end());
+ int index = static_cast<int>(i - 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.
+ ui::Accelerator accelerator(event.key_code(),
+ event.IsShiftDown(),
+ event.IsControlDown(),
+ event.IsAltDown());
+ 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) {
+ Widget* widget = view->GetWidget();
+ return widget ? widget->GetFocusManager() == this : 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->AboutToRequestFocusFromTabTraversal(reverse);
+ SetFocusedViewWithReason(v, kReasonFocusTraversal);
+ }
+}
+
+void FocusManager::ClearNativeFocus() {
+ // Keep the top root window focused so we get keyboard events.
+ widget_->ClearNativeFocus();
+}
+
+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();
+ }
+
+ if (!focus_traversable) {
+ if (!reverse) {
+ // If the starting view has a focus traversable, use it.
+ // This is the case with NativeWidgetWins for example.
+ focus_traversable = original_starting_view->GetFocusTraversable();
+
+ // Otherwise default to the root view.
+ if (!focus_traversable) {
+ focus_traversable =
+ original_starting_view->GetWidget()->GetFocusTraversable();
+ starting_view = original_starting_view;
+ }
+ } else {
+ // When you are going back, starting view's FocusTraversable
+ // should not be used.
+ focus_traversable =
+ original_starting_view->GetWidget()->GetFocusTraversable();
+ starting_view = original_starting_view;
+ }
+ }
+ } else {
+ focus_traversable = widget_->GetFocusTraversable();
+ }
+
+ // 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) {
+ if (focused_view_ == view)
+ return;
+
+ AutoReset<bool> auto_changing_focus(&is_changing_focus_, true);
+ // Update the reason for the focus change (since this is checked by
+ // some listeners), then notify all listeners.
+ focus_change_reason_ = reason;
+ FOR_EACH_OBSERVER(FocusChangeListener, focus_change_listeners_,
+ OnWillChangeFocus(focused_view_, view));
+
+ View* old_focused_view = focused_view_;
+ focused_view_ = view;
+ if (old_focused_view)
+ old_focused_view->Blur();
+ if (focused_view_)
+ focused_view_->Focus();
+
+ FOR_EACH_OBSERVER(FocusChangeListener, focus_change_listeners_,
+ OnDidChangeFocus(old_focused_view, focused_view_));
+}
+
+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->SchedulePaint(); // 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;
+ }
+ }
+ }
+}
+
+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 ui::Accelerator& accelerator,
+ ui::AcceleratorTarget* target) {
+ accelerator_manager_->Register(accelerator, target);
+}
+
+void FocusManager::UnregisterAccelerator(const ui::Accelerator& accelerator,
+ ui::AcceleratorTarget* target) {
+ accelerator_manager_->Unregister(accelerator, target);
+}
+
+void FocusManager::UnregisterAccelerators(ui::AcceleratorTarget* target) {
+ accelerator_manager_->UnregisterAll(target);
+}
+
+bool FocusManager::ProcessAccelerator(const ui::Accelerator& accelerator) {
+ return accelerator_manager_->Process(accelerator);
+}
+
+ui::AcceleratorTarget* FocusManager::GetCurrentTargetForAccelerator(
+ const ui::Accelerator& accelerator) const {
+ return accelerator_manager_->GetCurrentTarget(accelerator);
+}
+
+void FocusManager::FocusNativeView(gfx::NativeView native_view) {
+ widget_->FocusNativeView(native_view);
+}
+
+// static
+bool FocusManager::IsTabTraversalKeyEvent(const KeyEvent& key_event) {
+ return key_event.key_code() == ui::VKEY_TAB && !key_event.IsControlDown();
+}
+
+void FocusManager::ViewRemoved(View* removed) {
+ // If the view being removed contains (or is) the focused view,
+ // clear the focus. However, it's not safe to call ClearFocus()
+ // (and in turn ClearNativeFocus()) here because ViewRemoved() can
+ // be called while the top level widget is being destroyed.
+ if (focused_view_ && removed && removed->Contains(focused_view_))
+ SetFocusedView(NULL);
+}
+
+void FocusManager::AddFocusChangeListener(FocusChangeListener* listener) {
+ focus_change_listeners_.AddObserver(listener);
+}
+
+void FocusManager::RemoveFocusChangeListener(FocusChangeListener* listener) {
+ focus_change_listeners_.RemoveObserver(listener);
+}
+
+} // namespace views
diff --git a/ui/views/focus/focus_manager.h b/ui/views/focus/focus_manager.h
new file mode 100644
index 0000000..0fdecc1
--- /dev/null
+++ b/ui/views/focus/focus_manager.h
@@ -0,0 +1,283 @@
+// 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 "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/observer_list.h"
+#include "ui/base/models/accelerator.h"
+#include "ui/gfx/native_widget_types.h"
+#include "views/views_export.h"
+#include "views/events/event.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 NativeWidgetWin 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 Widget::SetFocusTraversableParent() on the nested RootView and point
+// it to the outer RootView. This is used when the focus goes out of the
+// nested RootView. In the example:
+// hwnd_view_container_->GetWidget()->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_->GetWidget()->SetFocusTraversableParent(
+// native_control);
+//
+// Note that FocusTraversable do not have to be RootViews: AccessibleToolbarView
+// is FocusTraversable.
+
+namespace ui {
+class AcceleratorTarget;
+class AcceleratorManager;
+}
+
+namespace views {
+
+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 VIEWS_EXPORT 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 VIEWS_EXPORT FocusChangeListener {
+ public:
+ // No change to focus state has occurred yet when this function is called.
+ virtual void OnWillChangeFocus(View* focused_before, View* focused_now) = 0;
+
+ // Called after focus state has changed.
+ virtual void OnDidChangeFocus(View* focused_before, View* focused_now) = 0;
+
+ protected:
+ virtual ~FocusChangeListener() {}
+};
+
+class VIEWS_EXPORT FocusManager {
+ public:
+ // 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();
+
+ // 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() { return focused_view_; }
+ const 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
+ // currenty 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();
+
+ // Returns true if in the process of changing the focused view.
+ bool is_changing_focus() const { return is_changing_focus_; }
+
+ // 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 ui::Accelerator& accelerator,
+ ui::AcceleratorTarget* target);
+
+ // Unregister the specified keyboard accelerator for the specified target.
+ void UnregisterAccelerator(const ui::Accelerator& accelerator,
+ ui::AcceleratorTarget* target);
+
+ // Unregister all keyboard accelerator for the specified target.
+ void UnregisterAccelerators(ui::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 ui::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* 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.
+ ui::AcceleratorTarget* GetCurrentTargetForAccelerator(
+ const ui::Accelerator& accelertor) const;
+
+ // 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();
+
+ // 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);
+
+ 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 AcceleratorManager this FocusManager is associated with.
+ scoped_ptr<ui::AcceleratorManager> accelerator_manager_;
+
+ // 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 list of registered FocusChange listeners.
+ ObserverList<FocusChangeListener, true> focus_change_listeners_;
+
+ // See description above getter.
+ bool is_changing_focus_;
+
+ DISALLOW_COPY_AND_ASSIGN(FocusManager);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_FOCUS_FOCUS_MANAGER_H_
diff --git a/ui/views/focus/focus_manager_factory.cc b/ui/views/focus/focus_manager_factory.cc
new file mode 100644
index 0000000..457f60b
--- /dev/null
+++ b/ui/views/focus/focus_manager_factory.cc
@@ -0,0 +1,55 @@
+// 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 "views/focus/focus_manager_factory.h"
+
+#include "base/compiler_specific.h"
+#include "views/focus/focus_manager.h"
+
+namespace {
+
+using views::FocusManager;
+
+class DefaultFocusManagerFactory : public views::FocusManagerFactory {
+ public:
+ DefaultFocusManagerFactory() : views::FocusManagerFactory() {}
+ virtual ~DefaultFocusManagerFactory() {}
+
+ protected:
+ virtual FocusManager* CreateFocusManager(views::Widget* widget) OVERRIDE {
+ return new FocusManager(widget);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DefaultFocusManagerFactory);
+};
+
+views::FocusManagerFactory* focus_manager_factory = NULL;
+
+} // namespace
+
+namespace views {
+
+FocusManagerFactory::FocusManagerFactory() {
+}
+
+FocusManagerFactory::~FocusManagerFactory() {
+}
+
+// static
+FocusManager* FocusManagerFactory::Create(Widget* widget) {
+ if (!focus_manager_factory)
+ focus_manager_factory = new DefaultFocusManagerFactory();
+ return focus_manager_factory->CreateFocusManager(widget);
+}
+
+// static
+void FocusManagerFactory::Install(FocusManagerFactory* f) {
+ if (f == focus_manager_factory)
+ return;
+ delete focus_manager_factory;
+ focus_manager_factory = f ? f : new DefaultFocusManagerFactory();
+}
+
+} // namespace views
diff --git a/ui/views/focus/focus_manager_factory.h b/ui/views/focus/focus_manager_factory.h
new file mode 100644
index 0000000..7aae11d
--- /dev/null
+++ b/ui/views/focus/focus_manager_factory.h
@@ -0,0 +1,41 @@
+// 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_FACTORY_H_
+#define UI_VIEWS_FOCUS_FOCUS_MANAGER_FACTORY_H_
+#pragma once
+
+#include "base/basictypes.h"
+#include "views/views_export.h"
+
+namespace views {
+
+class FocusManager;
+class Widget;
+
+// A factory to create FocusManager. This is used in unit tests
+// to inject a custom factory.
+class VIEWS_EXPORT FocusManagerFactory {
+ public:
+ // Create a FocusManager for the given |widget| using installe Factory.
+ static FocusManager* Create(Widget* widget);
+
+ // Installs FocusManagerFactory. If |factory| is NULL, it resets
+ // to the default factory which creates plain FocusManager.
+ static void Install(FocusManagerFactory* factory);
+
+ protected:
+ FocusManagerFactory();
+ virtual ~FocusManagerFactory();
+
+ // Create a FocusManager for the given |widget|.
+ virtual FocusManager* CreateFocusManager(Widget* widget) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FocusManagerFactory);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_FOCUS_FOCUS_MANAGER_FACTORY_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..d4372f2
--- /dev/null
+++ b/ui/views/focus/focus_manager_unittest.cc
@@ -0,0 +1,1763 @@
+// 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 "base/string16.h"
+#include "base/string_number_conversions.h"
+#include "base/utf_string_conversions.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/base/keycodes/keyboard_codes.h"
+#include "ui/base/models/accelerator.h"
+#include "ui/base/models/combobox_model.h"
+#include "ui/gfx/rect.h"
+#include "ui/views/test/views_test_base.h"
+#include "ui/views/window/non_client_view.h"
+#include "views/background.h"
+#include "views/border.h"
+#include "views/controls/button/checkbox.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/scroll_view.h"
+#include "views/controls/tabbed_pane/native_tabbed_pane_wrapper.h"
+#include "views/controls/tabbed_pane/tabbed_pane.h"
+#include "views/controls/textfield/textfield.h"
+#include "views/focus/accelerator_handler.h"
+#include "views/focus/focus_manager_factory.h"
+#include "views/widget/root_view.h"
+#include "views/widget/widget.h"
+#include "views/widget/widget_delegate.h"
+
+#if defined(OS_LINUX)
+#include "ui/base/keycodes/keyboard_code_conversion_gtk.h"
+#endif
+
+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
+
+namespace views {
+
+class FocusManagerTest : public ViewsTestBase, public WidgetDelegate {
+ public:
+ FocusManagerTest()
+ : window_(NULL),
+ content_view_(NULL),
+ focus_change_listener_(NULL) {
+ }
+
+ ~FocusManagerTest() {
+ }
+
+ virtual void SetUp() OVERRIDE {
+ ViewsTestBase::SetUp();
+ window_ = Widget::CreateWindowWithBounds(this, bounds());
+ InitContentView();
+ window_->Show();
+ }
+
+ virtual void TearDown() OVERRIDE {
+ if (focus_change_listener_)
+ GetFocusManager()->RemoveFocusChangeListener(focus_change_listener_);
+ window_->Close();
+
+ // Flush the message loop to make application verifiers happy.
+ RunPendingMessages();
+ ViewsTestBase::TearDown();
+ }
+
+ FocusManager* GetFocusManager() {
+ return window_->GetFocusManager();
+ }
+
+ void FocusNativeView(gfx::NativeView native_view) {
+#if defined(USE_AURA)
+ NOTIMPLEMENTED();
+#elif 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
+ }
+
+ // WidgetDelegate Implementation.
+ virtual View* GetContentsView() OVERRIDE {
+ if (!content_view_)
+ content_view_ = new View();
+ return content_view_;
+ }
+ virtual Widget* GetWidget() OVERRIDE {
+ return content_view_->GetWidget();
+ }
+ virtual const Widget* GetWidget() const OVERRIDE {
+ return content_view_->GetWidget();
+ }
+
+ virtual void InitContentView() {
+ }
+
+ protected:
+ virtual gfx::Rect bounds() {
+ return gfx::Rect(0, 0, 500, 500);
+ }
+
+ // Mocks activating/deactivating the window.
+ void SimulateActivateWindow() {
+#if defined(USE_AURA)
+ NOTIMPLEMENTED();
+#elif 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(USE_AURA)
+ NOTIMPLEMENTED();
+#elif 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
+ }
+
+ Widget* window_;
+ View* content_view_;
+
+ void AddFocusChangeListener(FocusChangeListener* listener) {
+ ASSERT_FALSE(focus_change_listener_);
+ focus_change_listener_ = listener;
+ GetFocusManager()->AddFocusChangeListener(listener);
+ }
+
+#if defined(USE_AURA)
+ void PostKeyDown(ui::KeyboardCode key_code) {
+ NOTIMPLEMENTED();
+ }
+
+ void PostKeyUp(ui::KeyboardCode key_code) {
+ NOTIMPLEMENTED();
+ }
+#elif 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_;
+
+ 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);
+ set_focusable(false);
+ }
+
+ virtual ~BorderView() {}
+
+ virtual internal::RootView* GetContentsRootView() {
+ return static_cast<internal::RootView*>(widget_->GetRootView());
+ }
+
+ virtual FocusTraversable* GetFocusTraversable() {
+ return static_cast<internal::RootView*>(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_) {
+ widget_ = new Widget;
+ Widget::InitParams params(Widget::InitParams::TYPE_CONTROL);
+#if defined(OS_WIN)
+ params.parent = parent->GetWidget()->GetNativeView();
+#elif defined(TOOLKIT_USES_GTK)
+ params.parent = native_view();
+#else
+ NOTREACHED();
+#endif
+ widget_->Init(params);
+ widget_->SetFocusTraversableParentView(this);
+ 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.
+ static_cast<internal::RootView*>(widget_->GetRootView())->
+ SetFocusTraversableParent(GetWidget()->GetFocusTraversable());
+ }
+ }
+
+ private:
+ View* child_;
+ Widget* widget_;
+
+ DISALLOW_COPY_AND_ASSIGN(BorderView);
+};
+
+class DummyComboboxModel : public ui::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;
+ if (style_tab_)
+ 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(ASCIIToUTF16("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->set_id(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_->set_id(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(ASCIIToUTF16("Apple:"));
+ label->set_id(kAppleLabelID);
+ left_container_->AddChildView(label);
+ label->SetBounds(label_x, y, label_width, label_height);
+
+ Textfield* text_field = new Textfield();
+ text_field->set_id(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(ASCIIToUTF16("Orange:"));
+ label->set_id(kOrangeLabelID);
+ left_container_->AddChildView(label);
+ label->SetBounds(label_x, y, label_width, label_height);
+
+ text_field = new Textfield();
+ text_field->set_id(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(ASCIIToUTF16("Banana:"));
+ label->set_id(kBananaLabelID);
+ left_container_->AddChildView(label);
+ label->SetBounds(label_x, y, label_width, label_height);
+
+ text_field = new Textfield();
+ text_field->set_id(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(ASCIIToUTF16("Kiwi:"));
+ label->set_id(kKiwiLabelID);
+ left_container_->AddChildView(label);
+ label->SetBounds(label_x, y, label_width, label_height);
+
+ text_field = new Textfield();
+ text_field->set_id(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;
+
+ NativeTextButton* button = new NativeTextButton(NULL,
+ ASCIIToUTF16("Click me"));
+ button->SetBounds(label_x, y + 10, 80, 30);
+ button->set_id(kFruitButtonID);
+ left_container_->AddChildView(button);
+ y += 40;
+
+ cb = new Checkbox(ASCIIToUTF16("This is another check box"));
+ cb->SetBounds(label_x + label_width + 5, y, 180, 20);
+ cb->set_id(kFruitCheckBoxID);
+ left_container_->AddChildView(cb);
+ y += 20;
+
+ Combobox* combobox = new Combobox(&combobox_model_);
+ combobox->SetBounds(label_x + label_width + 5, y, 150, 30);
+ combobox->set_id(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_->set_id(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(ASCIIToUTF16("Asparagus"), 1);
+ radio_button->set_id(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(ASCIIToUTF16("Broccoli"), 1);
+ radio_button->set_id(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(ASCIIToUTF16("Cauliflower"), 1);
+ radio_button->set_id(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->set_id(kInnerContainerID);
+ right_container_->AddChildView(inner_container);
+ inner_container->SetBounds(100, 10, 150, 180);
+
+ ScrollView* scroll_view = new ScrollView();
+ scroll_view->set_id(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 char* const kTitles[] = {
+ "Rosetta", "Stupeur et tremblement", "The diner game",
+ "Ridicule", "Le placard", "Les Visiteurs", "Amelie",
+ "Joyeux Noel", "Camping", "Brice de Nice",
+ "Taxi", "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(ASCIIToUTF16(kTitles[i]));
+ link->SetHorizontalAlignment(Label::ALIGN_LEFT);
+ link->set_id(kIDs[i]);
+ scroll_content->AddChildView(link);
+ link->SetBounds(5, y, 300, 15);
+ y += 15;
+ }
+
+ y = 250;
+ int width = 60;
+ button = new NativeTextButton(NULL, ASCIIToUTF16("OK"));
+ button->set_id(kOKButtonID);
+ button->SetIsDefault(true);
+
+ content_view_->AddChildView(button);
+ button->SetBounds(150, y, width, 30);
+
+ button = new NativeTextButton(NULL, ASCIIToUTF16("Cancel"));
+ button->set_id(kCancelButtonID);
+ content_view_->AddChildView(button);
+ button->SetBounds(220, y, width, 30);
+
+ button = new NativeTextButton(NULL, ASCIIToUTF16("Help"));
+ button->set_id(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(ASCIIToUTF16("Bold"));
+ contents->AddChildView(cb);
+ cb->SetBounds(10, 10, 50, 20);
+ cb->set_id(kBoldCheckBoxID);
+
+ cb = new Checkbox(ASCIIToUTF16("Italic"));
+ contents->AddChildView(cb);
+ cb->SetBounds(70, 10, 50, 20);
+ cb->set_id(kItalicCheckBoxID);
+
+ cb = new Checkbox(ASCIIToUTF16("Underlined"));
+ contents->AddChildView(cb);
+ cb->SetBounds(130, 10, 70, 20);
+ cb->set_id(kUnderlinedCheckBoxID);
+
+ Link* link = new Link(ASCIIToUTF16("Help"));
+ contents->AddChildView(link);
+ link->SetBounds(10, 35, 70, 10);
+ link->set_id(kStyleHelpLinkID);
+
+ text_field = new Textfield();
+ contents->AddChildView(text_field);
+ text_field->SetBounds(10, 50, 100, 20);
+ text_field->set_id(kStyleTextEditID);
+
+ style_tab_ = new TabbedPane();
+ style_tab_->set_id(kStyleContainerID);
+ content_view_->AddChildView(style_tab_);
+ style_tab_->SetBounds(10, y, 210, 100);
+ style_tab_->AddTab(ASCIIToUTF16("Style"), contents);
+ style_tab_->AddTab(ASCIIToUTF16("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->set_id(kSearchTextfieldID);
+
+ button = new NativeTextButton(NULL, ASCIIToUTF16("Search"));
+ contents->AddChildView(button);
+ button->SetBounds(112, 5, 60, 30);
+ button->set_id(kSearchButtonID);
+
+ link = new Link(ASCIIToUTF16("Help"));
+ link->SetHorizontalAlignment(Label::ALIGN_LEFT);
+ link->set_id(kHelpLinkID);
+ contents->AddChildView(link);
+ link->SetBounds(175, 10, 30, 20);
+
+ search_border_view_ = new BorderView(contents);
+ search_border_view_->set_id(kSearchContainerID);
+
+ content_view_->AddChildView(search_border_view_);
+ search_border_view_->SetBounds(300, y, 240, 50);
+
+ y += 60;
+
+ contents = new View();
+ contents->set_focusable(true);
+ contents->set_background(Background::CreateSolidBackground(SK_ColorBLUE));
+ contents->set_id(kThumbnailContainerID);
+ button = new NativeTextButton(NULL, ASCIIToUTF16("Star"));
+ contents->AddChildView(button);
+ button->SetBounds(5, 5, 50, 30);
+ button->set_id(kThumbnailStarID);
+ button = new NativeTextButton(NULL, ASCIIToUTF16("SuperStar"));
+ contents->AddChildView(button);
+ button->SetBounds(60, 5, 100, 30);
+ button->set_id(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 {
+ ON_FOCUS = 0,
+ ON_BLUR
+};
+
+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) {
+ set_focusable(true);
+ set_id(view_id);
+ }
+
+ virtual void OnFocus() {
+ event_list_->push_back(FocusTestEvent(ON_FOCUS, id()));
+ }
+
+ virtual void OnBlur() {
+ event_list_->push_back(FocusTestEvent(ON_BLUR, id()));
+ }
+
+ 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(1, static_cast<int>(event_list.size()));
+ EXPECT_EQ(ON_FOCUS, event_list[0].type);
+ EXPECT_EQ(kView1ID, event_list[0].view_id);
+
+ event_list.clear();
+ view2->RequestFocus();
+ ASSERT_EQ(2, static_cast<int>(event_list.size()));
+ EXPECT_EQ(ON_BLUR, event_list[0].type);
+ EXPECT_EQ(kView1ID, event_list[0].view_id);
+ EXPECT_EQ(ON_FOCUS, event_list[1].type);
+ EXPECT_EQ(kView2ID, event_list[1].view_id);
+
+ event_list.clear();
+ GetFocusManager()->ClearFocus();
+ ASSERT_EQ(1, static_cast<int>(event_list.size()));
+ EXPECT_EQ(ON_BLUR, 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 OnWillChangeFocus(View* focused_before, View* focused_now) {
+ focus_changes_.push_back(ViewPair(focused_before, focused_now));
+ }
+ virtual void OnDidChangeFocus(View* focused_before, View* 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->set_focusable(true);
+ View* view2 = new View();
+ view2->set_focusable(true);
+ content_view_->AddChildView(view1);
+ content_view_->AddChildView(view2);
+
+ TestFocusChangeListener listener;
+ AddFocusChangeListener(&listener);
+
+ // Visual Studio 2010 has problems converting NULL to the null pointer for
+ // std::pair. See http://connect.microsoft.com/VisualStudio/feedback/details/520043/error-converting-from-null-to-a-pointer-type-in-std-pair
+ // It will work if we pass nullptr.
+#if defined(_MSC_VER) && _MSC_VER >= 1600
+ views::View* null_view = nullptr;
+#else
+ views::View* null_view = NULL;
+#endif
+
+ view1->RequestFocus();
+ ASSERT_EQ(1, static_cast<int>(listener.focus_changes().size()));
+ EXPECT_TRUE(listener.focus_changes()[0] == ViewPair(null_view, 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_view));
+}
+
+class TestNativeButton : public NativeTextButton {
+ public:
+ explicit TestNativeButton(const string16& text)
+ : NativeTextButton(NULL, text) {
+ };
+ virtual gfx::NativeView TestGetNativeControlView() {
+ return GetWidget()->GetNativeView();
+ }
+};
+
+class TestCheckbox : public Checkbox {
+ public:
+ explicit TestCheckbox(const string16& text) : Checkbox(text) {
+ };
+ virtual gfx::NativeView TestGetNativeControlView() {
+ return GetWidget()->GetNativeView();
+ }
+};
+
+class TestRadioButton : public RadioButton {
+ public:
+ explicit TestRadioButton(const string16& text)
+ : RadioButton(text, 1) {
+ }
+ virtual gfx::NativeView TestGetNativeControlView() {
+ return GetWidget()->GetNativeView();
+ }
+};
+
+class TestTextfield : public Textfield {
+ public:
+ TestTextfield() { }
+ virtual gfx::NativeView TestGetNativeControlView() {
+ return native_wrapper_->GetTestingHandle();
+ }
+};
+
+class TestCombobox : public Combobox, public ui::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();
+ }
+};
+
+#if !defined(TOUCH_UI)
+// TODO(oshima): replace TOUCH_UI with PURE_VIEWS
+
+// Tests that NativeControls do set the focus View appropriately on the
+// FocusManager.
+TEST_F(FocusManagerTest, FAILS_FocusNativeControls) {
+ TestTextfield* textfield = new TestTextfield();
+ TestTabbedPane* tabbed_pane = new TestTabbedPane();
+ TestTextfield* textfield2 = new TestTextfield();
+
+ content_view_->AddChildView(textfield);
+ content_view_->AddChildView(tabbed_pane);
+
+ tabbed_pane->AddTab(ASCIIToUTF16("Awesome textfield"), textfield2);
+
+ // Simulate the native view getting the native focus (such as by user click).
+ FocusNativeView(textfield->TestGetNativeControlView());
+ EXPECT_EQ(textfield, GetFocusManager()->GetFocusedView());
+
+ FocusNativeView(tabbed_pane->TestGetNativeControlView());
+ EXPECT_EQ(tabbed_pane, GetFocusManager()->GetFocusedView());
+
+ FocusNativeView(textfield2->TestGetNativeControlView());
+ EXPECT_EQ(textfield2, GetFocusManager()->GetFocusedView());
+}
+#endif
+
+// On linux, we don't store/restore focused view because gtk handles
+// this (and pure views will be the same).
+#if defined(OS_WIN)
+
+// 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();
+
+ NativeTextButton* button = new NativeTextButton(NULL,
+ ASCIIToUTF16("Press me"));
+ View* view = new View();
+ view->set_focusable(true);
+
+ content_view_->AddChildView(button);
+ button->SetBounds(10, 10, 200, 30);
+ content_view_->AddChildView(view);
+ RunPendingMessages();
+
+ TestFocusChangeListener listener;
+ AddFocusChangeListener(&listener);
+
+ view->RequestFocus();
+ RunPendingMessages();
+ // MessageLoopForUI::current()->RunWithDispatcher(new AcceleratorHandler());
+
+ // Visual Studio 2010 has problems converting NULL to the null pointer for
+ // std::pair. See http://connect.microsoft.com/VisualStudio/feedback/details/520043/error-converting-from-null-to-a-pointer-type-in-std-pair
+ // It will work if we pass nullptr.
+#if defined(_MSC_VER) && _MSC_VER >= 1600
+ views::View* null_view = nullptr;
+#else
+ views::View* null_view = NULL;
+#endif
+
+ // 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, view));
+ EXPECT_TRUE(listener.focus_changes()[1] == ViewPair(view, null_view));
+ 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, 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_view));
+ 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_view, 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_view));
+ EXPECT_TRUE(listener.focus_changes()[1] == ViewPair(null_view, view));
+ */
+}
+#endif
+
+#if !defined(TOUCH_UI)
+// TODO(oshima): There is no tabbed pane in pure views. Replace it
+// with different implementation.
+
+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();
+ NativeTextButton* tab_button = new NativeTextButton(
+ NULL, ASCIIToUTF16("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(ASCIIToUTF16("Awesome tab"), nested_tabbed_pane);
+ nested_tabbed_pane->AddTab(ASCIIToUTF16("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()));
+}
+#endif
+
+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()->RunWithDispatcher(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->id());
+ }
+ }
+
+ // 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->id());
+ }
+ }
+}
+
+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()->RunWithDispatcher(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->id());
+ }
+ }
+
+ // 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->id());
+ }
+ }
+}
+
+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()->RunWithDispatcher(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->id());
+ }
+ }
+
+ // 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->id());
+ }
+ }
+}
+
+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->id());
+ }
+ }
+
+ // 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->id());
+ }
+ }
+
+ // 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)->set_focusable(false);
+ FindViewByID(kStupeurEtTremblementLinkID)->set_focusable(false);
+ FindViewByID(kDinerGameLinkID)->set_accessibility_focusable(true);
+ FindViewByID(kDinerGameLinkID)->set_focusable(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->id());
+ }
+ }
+
+ // 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->id());
+ }
+ }
+}
+
+// Counts accelerator calls.
+class TestAcceleratorTarget : public ui::AcceleratorTarget {
+ public:
+ explicit TestAcceleratorTarget(bool process_accelerator)
+ : accelerator_count_(0), process_accelerator_(process_accelerator) {}
+
+ virtual bool AcceleratorPressed(const ui::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();
+ ui::Accelerator return_accelerator(ui::VKEY_RETURN, false, false, false);
+ ui::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 ui::AcceleratorTarget {
+ public:
+ SelfUnregisteringAcceleratorTarget(ui::Accelerator accelerator,
+ FocusManager* focus_manager)
+ : accelerator_(accelerator),
+ focus_manager_(focus_manager),
+ accelerator_count_(0) {
+ }
+
+ virtual bool AcceleratorPressed(const ui::Accelerator& accelerator) {
+ ++accelerator_count_;
+ focus_manager_->UnregisterAccelerator(accelerator, this);
+ return true;
+ }
+
+ int accelerator_count() const { return accelerator_count_; }
+
+ private:
+ ui::Accelerator accelerator_;
+ FocusManager* focus_manager_;
+ int accelerator_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(SelfUnregisteringAcceleratorTarget);
+};
+
+TEST_F(FocusManagerTest, CallsSelfDeletingAcceleratorTarget) {
+ FocusManager* focus_manager = GetFocusManager();
+ ui::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.key_code());
+ return true;
+ }
+
+ virtual bool OnKeyReleased(const KeyEvent& e) {
+ keys_released_.push_back(e.key_code());
+ return true;
+ }
+
+ virtual bool AcceleratorPressed(const ui::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
+// NativeWidgetGtk::OnKeyEvent().
+// Tests that the keyup messages are eaten for accelerators.
+TEST_F(FocusManagerTest, IgnoreKeyupForAccelerators) {
+ FocusManager* focus_manager = GetFocusManager();
+ MessageTrackingView* mtv = new MessageTrackingView();
+ mtv->AddAccelerator(ui::Accelerator(ui::VKEY_0, false, false, false));
+ mtv->AddAccelerator(ui::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()->RunWithDispatcher(&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()[0]);
+ ASSERT_EQ(1U, mtv->keys_released().size());
+ EXPECT_EQ(ui::VKEY_9, mtv->keys_released()[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()->RunWithDispatcher(&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()[0]);
+ EXPECT_EQ(ui::VKEY_9, mtv->keys_pressed()[1]);
+ EXPECT_EQ(ui::VKEY_8, mtv->keys_pressed()[2]);
+ EXPECT_EQ(ui::VKEY_9, mtv->keys_pressed()[3]);
+ EXPECT_EQ(ui::VKEY_7, mtv->keys_pressed()[4]);
+ ASSERT_EQ(3U, mtv->keys_released().size());
+ EXPECT_EQ(ui::VKEY_9, mtv->keys_released()[0]);
+ EXPECT_EQ(ui::VKEY_7, mtv->keys_released()[1]);
+ EXPECT_EQ(ui::VKEY_8, mtv->keys_released()[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()->RunWithDispatcher(&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()->RunWithDispatcher(&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) && !defined(USE_AURA)
+// 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.
+ scoped_ptr<Widget> widget1(new Widget);
+ Widget::InitParams params(Widget::InitParams::TYPE_CONTROL);
+ params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.parent = hwnd;
+ params.bounds = gfx::Rect(0, 0, 100, 100);
+ params.top_level = true; // This is top level in views hierarchy.
+ widget1->Init(params);
+
+ // Get the focus manager directly from the first window. Should exist
+ // because the first window is the root widget.
+ views::FocusManager* focus_manager1 = widget1->GetFocusManager();
+ EXPECT_TRUE(focus_manager1);
+
+ // Create another view window parented to the first view window.
+ scoped_ptr<Widget> widget2(new Widget);
+ params.parent = widget1->GetNativeView();
+ params.top_level = false; // This is child widget.
+ widget2->Init(params);
+
+ // Access the shared focus manager directly from the second window.
+ views::FocusManager* focus_manager2 = widget2->GetFocusManager();
+ EXPECT_EQ(focus_manager2, focus_manager1);
+
+ // Access the shared focus manager indirectly from the first window handle.
+ gfx::NativeWindow native_window = widget1->GetNativeWindow();
+ views::Widget* widget =
+ views::Widget::GetWidgetForNativeWindow(native_window);
+ EXPECT_EQ(widget->GetFocusManager(), focus_manager1);
+
+ // Access the shared focus manager indirectly from the second window handle.
+ native_window = widget2->GetNativeWindow();
+ widget = views::Widget::GetWidgetForNativeWindow(native_window);
+ EXPECT_EQ(widget->GetFocusManager(), focus_manager1);
+
+ // Access the shared focus manager indirectly from the first view handle.
+ gfx::NativeView native_view = widget1->GetNativeView();
+ widget = views::Widget::GetTopLevelWidgetForNativeView(native_view);
+ EXPECT_EQ(widget->GetFocusManager(), focus_manager1);
+
+ // Access the shared focus manager indirectly from the second view handle.
+ native_view = widget2->GetNativeView();
+ widget = views::Widget::GetTopLevelWidgetForNativeView(native_view);
+ EXPECT_EQ(widget->GetFocusManager(), focus_manager1);
+
+ DestroyWindow(hwnd);
+}
+#endif
+
+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_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FocusManagerDtorTracked);
+ };
+
+ class TestFocusManagerFactory : public FocusManagerFactory {
+ public:
+ explicit TestFocusManagerFactory(DtorTrackVector* dtor_tracker)
+ : dtor_tracker_(dtor_tracker) {
+ }
+
+ FocusManager* CreateFocusManager(Widget* widget) OVERRIDE {
+ return new FocusManagerDtorTracked(widget, dtor_tracker_);
+ }
+
+ private:
+ DtorTrackVector* dtor_tracker_;
+ DISALLOW_COPY_AND_ASSIGN(TestFocusManagerFactory);
+ };
+
+ class NativeButtonDtorTracked : public NativeTextButton {
+ public:
+ NativeButtonDtorTracked(const string16& text,
+ DtorTrackVector* dtor_tracker)
+ : NativeTextButton(NULL, text),
+ dtor_tracker_(dtor_tracker) {
+ };
+ virtual ~NativeButtonDtorTracked() {
+ dtor_tracker_->push_back("NativeButtonDtorTracked");
+ }
+
+ DtorTrackVector* dtor_tracker_;
+ };
+
+ class WindowDtorTracked : public Widget {
+ public:
+ explicit WindowDtorTracked(DtorTrackVector* dtor_tracker)
+ : dtor_tracker_(dtor_tracker) {
+ }
+
+ virtual ~WindowDtorTracked() {
+ dtor_tracker_->push_back("WindowDtorTracked");
+ }
+
+ DtorTrackVector* dtor_tracker_;
+ };
+
+ virtual void SetUp() {
+ ViewsTestBase::SetUp();
+ FocusManagerFactory::Install(new TestFocusManagerFactory(&dtor_tracker_));
+ // Create WindowDtorTracked that uses FocusManagerDtorTracked.
+ window_ = new WindowDtorTracked(&dtor_tracker_);
+ Widget::InitParams params;
+ params.delegate = this;
+ params.bounds = gfx::Rect(0, 0, 100, 100);
+ window_->Init(params);
+
+ tracked_focus_manager_ =
+ static_cast<FocusManagerDtorTracked*>(GetFocusManager());
+ window_->Show();
+ }
+
+ virtual void TearDown() {
+ if (window_) {
+ window_->Close();
+ RunPendingMessages();
+ }
+ FocusManagerFactory::Install(NULL);
+ ViewsTestBase::TearDown();
+ }
+
+ FocusManager* tracked_focus_manager_;
+ DtorTrackVector dtor_tracker_;
+};
+
+TEST_F(FocusManagerDtorTest, FocusManagerDestructedLast) {
+ // Setup views hierarchy.
+ TabbedPane* tabbed_pane = new TabbedPane();
+ content_view_->AddChildView(tabbed_pane);
+
+ NativeButtonDtorTracked* button = new NativeButtonDtorTracked(
+ ASCIIToUTF16("button"), &dtor_tracker_);
+ tabbed_pane->AddTab(ASCIIToUTF16("Awesome tab"), button);
+
+ // Close the window.
+ window_->Close();
+ RunPendingMessages();
+
+ // 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;
+}
+
+} // namespace views
diff --git a/ui/views/focus/focus_search.cc b/ui/views/focus/focus_search.cc
new file mode 100644
index 0000000..eac3f21
--- /dev/null
+++ b/ui/views/focus/focus_search.cc
@@ -0,0 +1,272 @@
+// 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 "views/focus/focus_manager.h"
+#include "views/focus/focus_search.h"
+#include "views/view.h"
+
+namespace views {
+
+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_->has_children()) {
+ 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->GetGroup();
+
+ if (!starting_view) {
+ // Default to the first/last child
+ starting_view = reverse ? root_->child_at(root_->child_count() - 1) :
+ root_->child_at(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->GetGroup() != skip_group_id);
+}
+
+bool FocusSearch::IsFocusable(View* v) {
+ if (accessibility_mode_)
+ return v && v->IsAccessibilityFocusableInRootView();
+
+ return v && v->IsFocusableInRootView();
+}
+
+View* FocusSearch::FindSelectedViewForGroup(View* view) {
+ if (view->IsGroupFocusTraversable() ||
+ view->GetGroup() == -1) // No group for that view.
+ return view;
+
+ View* selected_view = view->GetSelectedViewForGroup(view->GetGroup());
+ 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->has_children()) {
+ View* v = FindNextFocusableViewImpl(starting_view->child_at(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->has_children()) {
+ View* view =
+ starting_view->child_at(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 views
diff --git a/ui/views/focus/focus_search.h b/ui/views/focus/focus_search.h
new file mode 100644
index 0000000..9fe37a0
--- /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 "views/view.h"
+
+namespace views {
+
+class FocusTraversable;
+
+// FocusSearch is an object that implements the algorithm to find the
+// next view to focus.
+class VIEWS_EXPORT 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 views
+
+#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..3b9ec10
--- /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 "views/focus/view_storage.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+
+namespace views {
+
+// 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* 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 views
diff --git a/ui/views/focus/view_storage.h b/ui/views/focus/view_storage.h
new file mode 100644
index 0000000..7d748e2
--- /dev/null
+++ b/ui/views/focus/view_storage.h
@@ -0,0 +1,70 @@
+// 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 "base/memory/singleton.h"
+#include "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 views {
+
+class VIEWS_EXPORT 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* removed);
+
+ size_t view_count() const { return view_to_ids_.size(); }
+
+ 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 views
+
+#endif // UI_VIEWS_FOCUS_VIEW_STORAGE_H_
diff --git a/ui/views/focus/widget_focus_manager.cc b/ui/views/focus/widget_focus_manager.cc
new file mode 100644
index 0000000..57cd2cd
--- /dev/null
+++ b/ui/views/focus/widget_focus_manager.cc
@@ -0,0 +1,50 @@
+// 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 "views/focus/widget_focus_manager.h"
+
+#include "base/memory/singleton.h"
+
+namespace views {
+
+// WidgetFocusManager ----------------------------------------------------------
+
+// static
+WidgetFocusManager* WidgetFocusManager::GetInstance() {
+ return Singleton<WidgetFocusManager>::get();
+}
+
+void WidgetFocusManager::AddFocusChangeListener(
+ WidgetFocusChangeListener* listener) {
+ focus_change_listeners_.AddObserver(listener);
+}
+
+void WidgetFocusManager::RemoveFocusChangeListener(
+ WidgetFocusChangeListener* listener) {
+ focus_change_listeners_.RemoveObserver(listener);
+}
+
+void WidgetFocusManager::OnWidgetFocusEvent(gfx::NativeView focused_before,
+ gfx::NativeView focused_now) {
+ if (enabled_) {
+ FOR_EACH_OBSERVER(WidgetFocusChangeListener, focus_change_listeners_,
+ OnNativeFocusChange(focused_before, focused_now));
+ }
+}
+
+WidgetFocusManager::WidgetFocusManager() : enabled_(true) {}
+
+WidgetFocusManager::~WidgetFocusManager() {}
+
+// AutoNativeNotificationDisabler ----------------------------------------------
+
+AutoNativeNotificationDisabler::AutoNativeNotificationDisabler() {
+ WidgetFocusManager::GetInstance()->DisableNotifications();
+}
+
+AutoNativeNotificationDisabler::~AutoNativeNotificationDisabler() {
+ WidgetFocusManager::GetInstance()->EnableNotifications();
+}
+
+} // namespace views
diff --git a/ui/views/focus/widget_focus_manager.h b/ui/views/focus/widget_focus_manager.h
new file mode 100644
index 0000000..7a0143b
--- /dev/null
+++ b/ui/views/focus/widget_focus_manager.h
@@ -0,0 +1,82 @@
+// 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_WIDGET_FOCUS_MANAGER_H_
+#define UI_VIEWS_FOCUS_WIDGET_FOCUS_MANAGER_H_
+#pragma once
+
+#include "base/basictypes.h"
+#include "base/observer_list.h"
+#include "ui/gfx/native_widget_types.h"
+#include "views/views_export.h"
+
+template <typename T> struct DefaultSingletonTraits;
+
+namespace views {
+
+// 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 OnNativeFocusChange(gfx::NativeView focused_before,
+ gfx::NativeView focused_now) = 0;
+
+ protected:
+ virtual ~WidgetFocusChangeListener() {}
+};
+
+class VIEWS_EXPORT 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:
+ friend struct DefaultSingletonTraits<WidgetFocusManager>;
+
+ WidgetFocusManager();
+ ~WidgetFocusManager();
+
+ ObserverList<WidgetFocusChangeListener> focus_change_listeners_;
+
+ bool enabled_;
+
+ DISALLOW_COPY_AND_ASSIGN(WidgetFocusManager);
+};
+
+// A basic helper class that is used to disable native focus change
+// notifications within a scope.
+class VIEWS_EXPORT AutoNativeNotificationDisabler {
+ public:
+ AutoNativeNotificationDisabler();
+ ~AutoNativeNotificationDisabler();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AutoNativeNotificationDisabler);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_FOCUS_WIDGET_FOCUS_MANAGER_H_