summaryrefslogtreecommitdiffstats
path: root/views/focus
diff options
context:
space:
mode:
authorben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-05-08 00:34:05 +0000
committerben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-05-08 00:34:05 +0000
commit2362e4fe2905ab75d3230ebc3e307ae53e2b8362 (patch)
treee6d88357a2021811e0e354f618247217be8bb3da /views/focus
parentdb23ac3e713dc17509b2b15d3ee634968da45715 (diff)
downloadchromium_src-2362e4fe2905ab75d3230ebc3e307ae53e2b8362.zip
chromium_src-2362e4fe2905ab75d3230ebc3e307ae53e2b8362.tar.gz
chromium_src-2362e4fe2905ab75d3230ebc3e307ae53e2b8362.tar.bz2
Move src/chrome/views to src/views. RS=darin http://crbug.com/11387
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@15604 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'views/focus')
-rw-r--r--views/focus/external_focus_tracker.cc65
-rw-r--r--views/focus/external_focus_tracker.h76
-rw-r--r--views/focus/focus_manager.cc716
-rw-r--r--views/focus/focus_manager.h343
-rw-r--r--views/focus/focus_manager_unittest.cc666
-rw-r--r--views/focus/focus_util_win.cc118
-rw-r--r--views/focus/focus_util_win.h28
-rw-r--r--views/focus/view_storage.cc182
-rw-r--r--views/focus/view_storage.h78
9 files changed, 2272 insertions, 0 deletions
diff --git a/views/focus/external_focus_tracker.cc b/views/focus/external_focus_tracker.cc
new file mode 100644
index 0000000..8f8bfdf
--- /dev/null
+++ b/views/focus/external_focus_tracker.cc
@@ -0,0 +1,65 @@
+// Copyright (c) 2006-2008 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 "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) {
+ view_storage_ = ViewStorage::GetSharedInstance();
+ 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::FocusWillChange(View* focused_before,
+ View* focused_now) {
+ if (focused_now && !parent_view_->IsParentOf(focused_now) &&
+ parent_view_ != focused_now) {
+ // Store the newly focused view.
+ StoreLastFocusedView(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/views/focus/external_focus_tracker.h b/views/focus/external_focus_tracker.h
new file mode 100644
index 0000000..22e9b7e
--- /dev/null
+++ b/views/focus/external_focus_tracker.h
@@ -0,0 +1,76 @@
+// Copyright (c) 2006-2008 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 VIEWS_FOCUS_EXTERNAL_FOCUS_TRACKER_H_
+#define VIEWS_FOCUS_EXTERNAL_FOCUS_TRACKER_H_
+
+#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 ExternalFocusTracker : public FocusChangeListener {
+ public:
+ ExternalFocusTracker(View* parent_view, FocusManager* focus_manager);
+
+ virtual ~ExternalFocusTracker();
+ // FocusChangeListener implementation.
+ virtual void FocusWillChange(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_EVIL_CONSTRUCTORS(ExternalFocusTracker);
+};
+
+} // namespace views
+
+#endif // VIEWS_FOCUS_EXTERNAL_FOCUS_TRACKER_H_
diff --git a/views/focus/focus_manager.cc b/views/focus/focus_manager.cc
new file mode 100644
index 0000000..5653e7a
--- /dev/null
+++ b/views/focus/focus_manager.cc
@@ -0,0 +1,716 @@
+// Copyright (c) 2006-2008 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 <algorithm>
+
+#include "base/histogram.h"
+#include "base/logging.h"
+#include "base/win_util.h"
+#include "chrome/browser/renderer_host/render_widget_host_view_win.h"
+#include "views/accelerator.h"
+#include "views/focus/focus_manager.h"
+#include "views/focus/view_storage.h"
+#include "views/view.h"
+#include "views/widget/root_view.h"
+#include "views/widget/widget.h"
+
+// The following keys are used in SetProp/GetProp to associate additional
+// information needed for focus tracking with a window.
+
+// Maps to the FocusManager instance for a top level window. See
+// CreateFocusManager/DestoryFocusManager for usage.
+static const wchar_t* const kFocusManagerKey = L"__VIEW_CONTAINER__";
+
+// Maps to the View associated with a window.
+// We register views with window so we can:
+// - keep in sync the native focus with the view focus (when the native
+// component gets the focus, we get the WM_SETFOCUS event and we can focus the
+// associated view).
+// - prevent tab key events from being sent to views.
+static const wchar_t* const kViewKey = L"__CHROME_VIEW__";
+
+// A property set to 1 to indicate whether the focus manager has subclassed that
+// window. We are doing this to ensure we are not subclassing several times.
+// Subclassing twice is not a problem if no one is subclassing the HWND between
+// the 2 subclassings (the 2nd subclassing is ignored since the WinProc is the
+// same as the current one). However if some other app goes and subclasses the
+// HWND between the 2 subclassings, we will end up subclassing twice.
+// This flag lets us test that whether we have or not subclassed yet.
+static const wchar_t* const kFocusSubclassInstalled =
+ L"__FOCUS_SUBCLASS_INSTALLED__";
+
+namespace views {
+
+// Callback installed via InstallFocusSubclass.
+static LRESULT CALLBACK FocusWindowCallback(HWND window, UINT message,
+ WPARAM wParam, LPARAM lParam) {
+ if (!::IsWindow(window)) {
+ // QEMU has reported crashes when calling GetProp (this seems to happen for
+ // some weird messages, not sure what they are).
+ // Here we are just trying to avoid the crasher.
+ NOTREACHED();
+ return 0;
+ }
+
+ WNDPROC original_handler = win_util::GetSuperclassWNDPROC(window);
+ DCHECK(original_handler);
+ FocusManager* focus_manager = FocusManager::GetFocusManager(window);
+ // There are cases when we have no FocusManager for the window. This happens
+ // because we subclass certain windows (such as the TabContents window)
+ // but that window may not have an associated FocusManager.
+ if (focus_manager) {
+ switch (message) {
+ case WM_SETFOCUS:
+ if (!focus_manager->OnSetFocus(window))
+ return 0;
+ break;
+ case WM_NCDESTROY:
+ if (!focus_manager->OnNCDestroy(window))
+ return 0;
+ break;
+ case WM_ACTIVATE: {
+ // We call the DefWindowProc before calling OnActivate as some of our
+ // windows need the OnActivate notifications. The default activation on
+ // the window causes it to focus the main window, and since
+ // FocusManager::OnActivate attempts to restore the focused view, it
+ // needs to be called last so the focus it is setting does not get
+ // overridden.
+ LRESULT result = CallWindowProc(original_handler, window, WM_ACTIVATE,
+ wParam, lParam);
+ if (!focus_manager->OnPostActivate(window,
+ LOWORD(wParam), HIWORD(wParam)))
+ return 0;
+ return result;
+ }
+ default:
+ break;
+ }
+ }
+ return CallWindowProc(original_handler, window, message, wParam, lParam);
+}
+
+// FocusManager -----------------------------------------------------
+
+// static
+FocusManager* FocusManager::CreateFocusManager(HWND window,
+ RootView* root_view) {
+ DCHECK(window);
+ DCHECK(root_view);
+ InstallFocusSubclass(window, NULL);
+ FocusManager* focus_manager = new FocusManager(window, root_view);
+ SetProp(window, kFocusManagerKey, focus_manager);
+
+ return focus_manager;
+}
+
+// static
+void FocusManager::InstallFocusSubclass(HWND window, View* view) {
+ DCHECK(window);
+
+ bool already_subclassed =
+ reinterpret_cast<bool>(GetProp(window,
+ kFocusSubclassInstalled));
+ if (already_subclassed &&
+ !win_util::IsSubclassed(window, &FocusWindowCallback)) {
+ NOTREACHED() << "window sub-classed by someone other than the FocusManager";
+ // Track in UMA so we know if this case happens.
+ UMA_HISTOGRAM_COUNTS("FocusManager.MultipleSubclass", 1);
+ } else {
+ win_util::Subclass(window, &FocusWindowCallback);
+ SetProp(window, kFocusSubclassInstalled, reinterpret_cast<HANDLE>(true));
+ }
+ if (view)
+ SetProp(window, kViewKey, view);
+}
+
+void FocusManager::UninstallFocusSubclass(HWND window) {
+ DCHECK(window);
+ if (win_util::Unsubclass(window, &FocusWindowCallback)) {
+ RemoveProp(window, kViewKey);
+ RemoveProp(window, kFocusSubclassInstalled);
+ }
+}
+
+// static
+FocusManager* FocusManager::GetFocusManager(HWND window) {
+ DCHECK(window);
+
+ // In case parent windows belong to a different process, yet
+ // have the kFocusManagerKey property set, we have to be careful
+ // to also check the process id of the window we're checking.
+ DWORD window_pid = 0, current_pid = GetCurrentProcessId();
+ FocusManager* focus_manager;
+ for (focus_manager = NULL; focus_manager == NULL && IsWindow(window);
+ window = GetParent(window)) {
+ GetWindowThreadProcessId(window, &window_pid);
+ if (current_pid != window_pid)
+ break;
+ focus_manager = reinterpret_cast<FocusManager*>(
+ GetProp(window, kFocusManagerKey));
+ }
+ return focus_manager;
+}
+
+// static
+View* FocusManager::GetViewForWindow(HWND window, bool look_in_parents) {
+ DCHECK(window);
+ do {
+ View* v = reinterpret_cast<View*>(GetProp(window, kViewKey));
+ if (v)
+ return v;
+ } while (look_in_parents && (window = ::GetParent(window)));
+
+ return NULL;
+}
+
+FocusManager::FocusManager(HWND root, RootView* root_view)
+ : root_(root),
+ top_root_view_(root_view),
+ focused_view_(NULL),
+ ignore_set_focus_msg_(false) {
+ stored_focused_view_storage_id_ =
+ ViewStorage::GetSharedInstance()->CreateStorageID();
+ DCHECK(root_);
+}
+
+FocusManager::~FocusManager() {
+ // If there are still registered FocusChange listeners, chances are they were
+ // leaked so warn about them.
+ DCHECK(focus_change_listeners_.empty());
+}
+
+// Message handlers.
+bool FocusManager::OnSetFocus(HWND window) {
+ if (ignore_set_focus_msg_)
+ return true;
+
+ // Focus the view associated with that window.
+ View* v = static_cast<View*>(GetProp(window, kViewKey));
+ if (v && v->IsFocusable()) {
+ v->GetRootView()->FocusView(v);
+ } else {
+ SetFocusedView(NULL);
+ }
+
+ return true;
+}
+
+bool FocusManager::OnNCDestroy(HWND window) {
+ // Window is being destroyed, undo the subclassing.
+ FocusManager::UninstallFocusSubclass(window);
+
+ if (window == root_) {
+ // We are the top window.
+
+ DCHECK(GetProp(window, kFocusManagerKey));
+
+ // Make sure this is called on the window that was set with the
+ // FocusManager.
+ RemoveProp(window, kFocusManagerKey);
+
+ delete this;
+ }
+ return true;
+}
+
+bool FocusManager::OnKeyDown(HWND window, UINT message, WPARAM wparam,
+ LPARAM lparam) {
+ DCHECK((message == WM_KEYDOWN) || (message == WM_SYSKEYDOWN));
+
+ if (!IsWindowVisible(root_)) {
+ // We got a message for a hidden window. Because WidgetWin::Close hides the
+ // window, then destroys it, it it possible to get a message after we've
+ // hidden the window. If we allow the message to be dispatched chances are
+ // we'll crash in some weird place. By returning false we make sure the
+ // message isn't dispatched.
+ return false;
+ }
+
+ // First give the registered keystroke handlers a chance a processing
+ // the message
+ // Do some basic checking to try to catch evil listeners that change the list
+ // from under us.
+ KeystrokeListenerList::size_type original_count =
+ keystroke_listeners_.size();
+ for (int i = 0; i < static_cast<int>(keystroke_listeners_.size()); i++) {
+ if (keystroke_listeners_[i]->ProcessKeyStroke(window, message, wparam,
+ lparam)) {
+ return false;
+ }
+ }
+ DCHECK_EQ(original_count, keystroke_listeners_.size())
+ << "KeystrokeListener list modified during notification";
+
+ int virtual_key_code = static_cast<int>(wparam);
+ // 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.
+ HWND active_window = ::GetActiveWindow();
+ if ((active_window == root_ || ::IsChild(active_window, root_)) &&
+ (virtual_key_code == VK_TAB) && !win_util::IsCtrlPressed()) {
+ if (!focused_view_ || !focused_view_->CanProcessTabKeyEvents()) {
+ AdvanceFocus(win_util::IsShiftPressed());
+ return false;
+ }
+ }
+
+ // Intercept arrow key messages to switch between grouped views.
+ if (focused_view_ && focused_view_->GetGroup() != -1 &&
+ (virtual_key_code == VK_UP || virtual_key_code == VK_DOWN ||
+ virtual_key_code == VK_LEFT || virtual_key_code == VK_RIGHT)) {
+ bool next = (virtual_key_code == VK_RIGHT || virtual_key_code == VK_DOWN);
+ std::vector<View*> views;
+ focused_view_->GetParent()->GetViewsWithGroup(focused_view_->GetGroup(),
+ &views);
+ std::vector<View*>::const_iterator iter = std::find(views.begin(),
+ views.end(),
+ focused_view_);
+ DCHECK(iter != views.end());
+ int index = static_cast<int>(iter - views.begin());
+ index += next ? 1 : -1;
+ if (index < 0) {
+ index = static_cast<int>(views.size()) - 1;
+ } else if (index >= static_cast<int>(views.size())) {
+ index = 0;
+ }
+ views[index]->RequestFocus();
+ return false;
+ }
+
+ int repeat_count = LOWORD(lparam);
+ int flags = HIWORD(lparam);
+ if (focused_view_ &&
+ !focused_view_->ShouldLookupAccelerators(KeyEvent(
+ Event::ET_KEY_PRESSED, virtual_key_code,
+ repeat_count, flags))) {
+ // This should not be processed as an accelerator.
+ return true;
+ }
+
+ // Process keyboard accelerators.
+ // We process accelerators here as we have no way of knowing if a HWND has
+ // really processed a key event. If the key combination matches an
+ // accelerator, the accelerator is triggered, otherwise we forward the
+ // event to the HWND.
+ Accelerator accelerator(Accelerator(static_cast<int>(virtual_key_code),
+ win_util::IsShiftPressed(),
+ win_util::IsCtrlPressed(),
+ win_util::IsAltPressed()));
+ if (ProcessAccelerator(accelerator)) {
+ // If a shortcut was activated for this keydown message, do not
+ // propagate the message further.
+ return false;
+ }
+ return true;
+}
+
+bool FocusManager::OnKeyUp(HWND window, UINT message, WPARAM wparam,
+ LPARAM lparam) {
+ for (int i = 0; i < static_cast<int>(keystroke_listeners_.size()); ++i) {
+ if (keystroke_listeners_[i]->ProcessKeyStroke(window, message, wparam,
+ lparam)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool FocusManager::OnPostActivate(HWND window,
+ int activation_state,
+ int minimized_state) {
+ if (WA_INACTIVE == LOWORD(activation_state)) {
+ StoreFocusedView();
+ } else {
+ RestoreFocusedView();
+ }
+ return false;
+}
+
+void FocusManager::ValidateFocusedView() {
+ if (focused_view_) {
+ if (!ContainsView(focused_view_)) {
+ focused_view_ = NULL;
+ }
+ }
+}
+
+// Tests whether a view is valid, whether it still belongs to the window
+// hierarchy of the FocusManager.
+bool FocusManager::ContainsView(View* view) {
+ DCHECK(view);
+ RootView* root_view = view->GetRootView();
+ if (!root_view)
+ return false;
+
+ Widget* widget = root_view->GetWidget();
+ if (!widget)
+ return false;
+
+ HWND window = widget->GetNativeView();
+ while (window) {
+ if (window == root_)
+ return true;
+ window = ::GetParent(window);
+ }
+ return false;
+}
+
+void FocusManager::AdvanceFocus(bool reverse) {
+ View* v = GetNextFocusableView(focused_view_, reverse, false);
+ // Note: Do not skip this next block when v == focused_view_. If the user
+ // tabs past the last focusable element in a webpage, we'll get here, and if
+ // the TabContentsContainerView is the only focusable view (possible in
+ // fullscreen mode), we need to run this block in order to cycle around to the
+ // first element on the page.
+ if (v) {
+ v->AboutToRequestFocusFromTabTraversal(reverse);
+ v->RequestFocus();
+ }
+}
+
+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) {
+ // If the starting view has a focus traversable, use it.
+ // This is the case with WidgetWins for example.
+ focus_traversable = original_starting_view->GetFocusTraversable();
+
+ // Otherwise default to the root view.
+ if (!focus_traversable) {
+ focus_traversable = original_starting_view->GetRootView();
+ starting_view = original_starting_view;
+ }
+ } else {
+ focus_traversable = top_root_view_;
+ }
+
+ // Traverse the FocusTraversable tree down to find the focusable view.
+ View* v = FindFocusableView(focus_traversable, starting_view,
+ reverse, dont_loop);
+ 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;
+ v = parent_focus_traversable ->FindNextFocusableView(
+ starting_view, reverse, FocusTraversable::UP, dont_loop,
+ &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, dont_loop);
+ }
+
+ if (v)
+ return v;
+
+ starting_view = focus_traversable->GetFocusTraversableParentView();
+ parent_focus_traversable =
+ parent_focus_traversable->GetFocusTraversableParent();
+ }
+
+ if (!dont_loop) {
+ // If we get here, we have reached the end of the focus hierarchy, let's
+ // loop.
+ if (reverse) {
+ // When reversing from the top, the next focusable view is at the end
+ // of the focus hierarchy.
+ return FindLastFocusableView();
+ } else {
+ // Easy, just clear the selection and press tab again.
+ if (original_starting_view) { // Make sure there was at least a view to
+ // start with, to prevent infinitely
+ // looping in empty windows.
+ // By calling with NULL as the starting view, we'll start from the
+ // top_root_view.
+ return GetNextFocusableView(NULL, false, true);
+ }
+ }
+ }
+ }
+ return NULL;
+}
+
+View* FocusManager::FindLastFocusableView() {
+ // Just walk the entire focus loop from where we're at until we reach the end.
+ View* new_focused = NULL;
+ View* last_focused = focused_view_;
+ while (new_focused = GetNextFocusableView(last_focused, false, true))
+ last_focused = new_focused;
+ return last_focused;
+}
+
+void FocusManager::SetFocusedView(View* view) {
+ if (focused_view_ != view) {
+ View* prev_focused_view = focused_view_;
+ if (focused_view_)
+ focused_view_->WillLoseFocus();
+
+ if (view)
+ view->WillGainFocus();
+
+ // Notified listeners that the focus changed.
+ FocusChangeListenerList::const_iterator iter;
+ for (iter = focus_change_listeners_.begin();
+ iter != focus_change_listeners_.end(); ++iter) {
+ (*iter)->FocusWillChange(prev_focused_view, view);
+ }
+
+ focused_view_ = view;
+
+ if (prev_focused_view)
+ prev_focused_view->SchedulePaint(); // Remove focus artifacts.
+
+ if (view) {
+ view->SchedulePaint();
+ view->Focus();
+ view->DidGainFocus();
+ }
+ }
+}
+
+void FocusManager::ClearFocus() {
+ SetFocusedView(NULL);
+ ClearHWNDFocus();
+}
+
+void FocusManager::ClearHWNDFocus() {
+ // Keep the top root window focused so we get keyboard events.
+ ignore_set_focus_msg_ = true;
+ ::SetFocus(root_);
+ ignore_set_focus_msg_ = false;
+}
+
+void FocusManager::FocusHWND(HWND hwnd) {
+ ignore_set_focus_msg_ = true;
+ // Only reset focus if hwnd is not already focused.
+ if (hwnd && ::GetFocus() != hwnd)
+ ::SetFocus(hwnd);
+ ignore_set_focus_msg_ = false;
+}
+
+void FocusManager::StoreFocusedView() {
+ ViewStorage* view_storage = ViewStorage::GetSharedInstance();
+ 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_;
+ focused_view_ = NULL;
+
+ if (v)
+ v->SchedulePaint(); // Remove focus border.
+}
+
+void FocusManager::RestoreFocusedView() {
+ ViewStorage* view_storage = ViewStorage::GetSharedInstance();
+ 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))
+ view->RequestFocus();
+ } else {
+ // Clearing the focus will focus the root window, so we still get key
+ // events.
+ ClearFocus();
+ }
+}
+
+void FocusManager::ClearStoredFocusedView() {
+ ViewStorage* view_storage = ViewStorage::GetSharedInstance();
+ 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_);
+}
+
+FocusManager* FocusManager::GetParentFocusManager() const {
+ HWND parent = ::GetParent(root_);
+ // If we are a top window, we don't have a parent FocusManager.
+ if (!parent)
+ return NULL;
+
+ return GetFocusManager(parent);
+}
+
+// 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,
+ bool dont_loop) {
+ FocusTraversable* new_focus_traversable = NULL;
+ View* new_starting_view = NULL;
+ View* v = focus_traversable->FindNextFocusableView(starting_view,
+ reverse,
+ FocusTraversable::DOWN,
+ dont_loop,
+ &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->FindNextFocusableView(starting_view,
+ reverse,
+ FocusTraversable::DOWN,
+ dont_loop,
+ &new_focus_traversable,
+ &new_starting_view);
+ }
+ return v;
+}
+
+AcceleratorTarget* FocusManager::RegisterAccelerator(
+ const Accelerator& accelerator,
+ AcceleratorTarget* target) {
+ AcceleratorMap::const_iterator iter = accelerators_.find(accelerator);
+ AcceleratorTarget* previous_target = NULL;
+ if (iter != accelerators_.end())
+ previous_target = iter->second;
+
+ accelerators_[accelerator] = target;
+
+ return previous_target;
+}
+
+void FocusManager::UnregisterAccelerator(const Accelerator& accelerator,
+ AcceleratorTarget* target) {
+ AcceleratorMap::iterator iter = accelerators_.find(accelerator);
+ if (iter == accelerators_.end()) {
+ NOTREACHED() << "Unregistering non-existing accelerator";
+ return;
+ }
+
+ if (iter->second != target) {
+ NOTREACHED() << "Unregistering accelerator for wrong target";
+ return;
+ }
+
+ accelerators_.erase(iter);
+}
+
+void FocusManager::UnregisterAccelerators(AcceleratorTarget* target) {
+ for (AcceleratorMap::iterator iter = accelerators_.begin();
+ iter != accelerators_.end();) {
+ if (iter->second == target)
+ accelerators_.erase(iter++);
+ else
+ ++iter;
+ }
+}
+
+bool FocusManager::ProcessAccelerator(const Accelerator& accelerator) {
+ FocusManager* focus_manager = this;
+ do {
+ AcceleratorTarget* target =
+ focus_manager->GetTargetForAccelerator(accelerator);
+ if (target) {
+ // If there is focused view, we give it a chance to process that
+ // accelerator.
+ if (!focused_view_ ||
+ !focused_view_->OverrideAccelerator(accelerator)) {
+ if (target->AcceleratorPressed(accelerator))
+ return true;
+ }
+ }
+
+ // When dealing with child windows that have their own FocusManager (such
+ // as ConstrainedWindow), we still want the parent FocusManager to process
+ // the accelerator if the child window did not process it.
+ focus_manager = focus_manager->GetParentFocusManager();
+ } while (focus_manager);
+ return false;
+}
+
+AcceleratorTarget* FocusManager::GetTargetForAccelerator(
+ const Accelerator& accelerator) const {
+ AcceleratorMap::const_iterator iter = accelerators_.find(accelerator);
+ if (iter != accelerators_.end())
+ return iter->second;
+ return NULL;
+}
+
+void FocusManager::ViewRemoved(View* parent, View* removed) {
+ if (focused_view_ && focused_view_ == removed)
+ focused_view_ = NULL;
+}
+
+void FocusManager::AddKeystrokeListener(KeystrokeListener* listener) {
+ DCHECK(std::find(keystroke_listeners_.begin(), keystroke_listeners_.end(),
+ listener) == keystroke_listeners_.end())
+ << "Adding a listener twice.";
+ keystroke_listeners_.push_back(listener);
+}
+
+void FocusManager::RemoveKeystrokeListener(KeystrokeListener* listener) {
+ KeystrokeListenerList::iterator place =
+ std::find(keystroke_listeners_.begin(), keystroke_listeners_.end(),
+ listener);
+ if (place == keystroke_listeners_.end()) {
+ NOTREACHED() << "Removing a listener that isn't registered.";
+ return;
+ }
+ keystroke_listeners_.erase(place);
+}
+
+void FocusManager::AddFocusChangeListener(FocusChangeListener* listener) {
+ DCHECK(std::find(focus_change_listeners_.begin(),
+ focus_change_listeners_.end(), listener) ==
+ focus_change_listeners_.end()) << "Adding a listener twice.";
+ focus_change_listeners_.push_back(listener);
+}
+
+void FocusManager::RemoveFocusChangeListener(FocusChangeListener* listener) {
+ FocusChangeListenerList::iterator place =
+ std::find(focus_change_listeners_.begin(), focus_change_listeners_.end(),
+ listener);
+ if (place == focus_change_listeners_.end()) {
+ NOTREACHED() << "Removing a listener that isn't registered.";
+ return;
+ }
+ focus_change_listeners_.erase(place);
+}
+
+} // namespace views
diff --git a/views/focus/focus_manager.h b/views/focus/focus_manager.h
new file mode 100644
index 0000000..8c33036
--- /dev/null
+++ b/views/focus/focus_manager.h
@@ -0,0 +1,343 @@
+// Copyright (c) 2006-2008 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 VIEWS_FOCUS_FOCUS_MANAGER_H_
+#define VIEWS_FOCUS_FOCUS_MANAGER_H_
+
+#include "base/basictypes.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+#include <vector>
+#include <map>
+
+#include "views/accelerator.h"
+
+// The FocusManager class is used to handle focus traversal, store/restore
+// focused views and handle keyboard accelerators.
+//
+// There are 2 types of focus:
+// - the native focus, which is the focus that an HWND 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 HWNDView class.
+//
+// When creating a top window, if it derives from WidgetWin, the
+// |has_own_focus_manager| of the Init method lets you specify whether that
+// window should have its own focus manager (so focus traversal stays confined
+// in that window). If you are not deriving from WidgetWin or one of its
+// derived classes (Window, FramelessWindow, ConstrainedWindow), you must
+// create a FocusManager when the window is created (it is automatically deleted
+// when the window is destroyed).
+//
+// The FocusTraversable interface exposes the methods a class should implement
+// in order to be able to be focus traversed when tab key is pressed.
+// RootViews implement FocusTraversable.
+// The FocusManager contains a top FocusTraversable instance, which is the top
+// RootView.
+//
+// If you just use views, then the focus traversal is handled for you by the
+// RootView. The default traversal order is the order in which the views have
+// been added to their container. You can modify this order by using the View
+// method SetNextFocusableView().
+//
+// If you are embedding a native view containing a nested RootView (for example
+// by adding a NativeControl that contains a WidgetWin as its native
+// component), then you need to:
+// - override the View::GetFocusTraversable() method in your outter component.
+// It should return the RootView of the inner component. This is used when
+// the focus traversal traverse down the focus hierarchy to enter the nested
+// RootView. In the example mentioned above, the NativeControl overrides
+// GetFocusTraversable() and returns hwnd_view_container_->GetRootView().
+// - call RootView::SetFocusTraversableParent() on the nested RootView and point
+// it to the outter RootView. This is used when the focus goes out of the
+// nested RootView. In the example:
+// hwnd_view_container_->GetRootView()->SetFocusTraversableParent(
+// native_control->GetRootView());
+// - call RootView::SetFocusTraversableParentView() on the nested RootView with
+// the parent view that directly contains the native window. This is needed
+// when traversing up from the nested RootView to know which view to start
+// with when going to the next/previous view.
+// In our example:
+// hwnd_view_container_->GetRootView()->SetFocusTraversableParent(
+// native_control);
+//
+// Note that FocusTraversable do not have to be RootViews: TabContents is
+// FocusTraversable.
+
+namespace views {
+
+class View;
+class RootView;
+
+// The FocusTraversable interface is used by components that want to process
+// focus traversal events (due to Tab/Shift-Tab key events).
+class FocusTraversable {
+ 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
+ };
+
+ // Should find the next view that should be focused and return it. If a
+ // FocusTraversable is found while searching for the focusable view, NULL
+ // should be returned, focus_traversable should be set to the FocusTraversable
+ // and focus_traversable_view should be set to the view associated with the
+ // FocusTraversable.
+ // This call should return NULL if the end of the focus loop is reached.
+ // - |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).
+ // - |dont_loop| if true specifies that if there is a loop in the focus
+ // hierarchy, we should keep traversing after the last view of the loop.
+ // - |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 dont_loop,
+ FocusTraversable** focus_traversable,
+ View** focus_traversable_view) = 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;
+};
+
+// The KeystrokeListener interface is used by components (such as the
+// ExternalTabContainer class) which need a crack at handling all
+// keystrokes.
+class KeystrokeListener {
+ public:
+ // If this returns true, then the component handled the keystroke and ate
+ // it.
+#if defined(OS_WIN)
+ virtual bool ProcessKeyStroke(HWND window, UINT message, WPARAM wparam,
+ LPARAM lparam) = 0;
+#endif
+};
+
+// This interface should be implemented by classes that want to be notified when
+// the focus is about to change. See the Add/RemoveFocusChangeListener methods.
+class FocusChangeListener {
+ public:
+ virtual void FocusWillChange(View* focused_before, View* focused_now) = 0;
+};
+
+class FocusManager {
+ public:
+#if defined(OS_WIN)
+ // Creates a FocusManager for the specified window. Top level windows
+ // must invoked this when created.
+ // The RootView specified should be the top RootView of the window.
+ // This also invokes InstallFocusSubclass.
+ static FocusManager* CreateFocusManager(HWND window, RootView* root_view);
+
+ // Subclasses the specified window. The subclassed window procedure listens
+ // for WM_SETFOCUS notification and keeps the FocusManager's focus owner
+ // property in sync.
+ // It's not necessary to explicitly invoke Uninstall, it's automatically done
+ // when the window is destroyed and Uninstall wasn't invoked.
+ static void InstallFocusSubclass(HWND window, View* view);
+
+ // Uninstalls the window subclass installed by InstallFocusSubclass.
+ static void UninstallFocusSubclass(HWND window);
+
+ static FocusManager* GetFocusManager(HWND window);
+
+ // Message handlers (for messages received from registered windows).
+ // Should return true if the message should be forwarded to the window
+ // original proc function, false otherwise.
+ bool OnSetFocus(HWND window);
+ bool OnNCDestroy(HWND window);
+ // OnKeyDown covers WM_KEYDOWN and WM_SYSKEYDOWN.
+ bool OnKeyDown(HWND window,
+ UINT message,
+ WPARAM wparam,
+ LPARAM lparam);
+ bool OnKeyUp(HWND window,
+ UINT message,
+ WPARAM wparam,
+ LPARAM lparam);
+ // OnPostActivate is called after WM_ACTIVATE has been propagated to the
+ // DefWindowProc.
+ bool OnPostActivate(HWND window, int activation_state, int minimized_state);
+#endif
+
+ // 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 is handling the selected view for the RootView.
+ View* GetFocusedView() const { return focused_view_; }
+ void SetFocusedView(View* view);
+
+ // Clears the focused view. The window associated with the top root view gets
+ // the native focus (so we still get keyboard events).
+ void ClearFocus();
+
+ // Clears the HWND that has the focus by focusing the HWND from the top
+ // RootView (so we still get keyboard events).
+ // Note that this does not change the currently focused view.
+ void ClearHWNDFocus();
+
+#if defined(OS_WIN)
+ // Focus the specified |hwnd| without changing the focused view.
+ void FocusHWND(HWND hwnd);
+#endif
+
+ // Validates the focused view, clearing it if the window it belongs too is not
+ // attached to the window hierarchy anymore.
+ void ValidateFocusedView();
+
+#if defined(OS_WIN)
+ // Returns the view associated with the specified window if any.
+ // If |look_in_parents| is true, it goes up the window parents until it find
+ // a view.
+ static View* GetViewForWindow(HWND window, bool look_in_parents);
+#endif
+
+ // 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 the FocusManager of the parent window of the window that is the
+ // root of this FocusManager. This is useful with ConstrainedWindows that have
+ // their own FocusManager and need to return focus to the browser when closed.
+ FocusManager* GetParentFocusManager() const;
+
+ // Register a keyboard accelerator for the specified target. If an
+ // AcceleratorTarget is already registered for that accelerator, it is
+ // returned.
+ // 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)
+ AcceleratorTarget* RegisterAccelerator(const Accelerator& accelerator,
+ AcceleratorTarget* target);
+
+ // Unregister the specified keyboard accelerator for the specified target.
+ void UnregisterAccelerator(const Accelerator& accelerator,
+ AcceleratorTarget* target);
+
+ // Unregister all keyboard accelerator for the specified target.
+ void UnregisterAccelerators(AcceleratorTarget* target);
+
+ // Activate the target associated with the specified accelerator if any.
+ // Returns true if an accelerator was activated.
+ bool ProcessAccelerator(const Accelerator& accelerator);
+
+ // Called by a RootView when a view within its hierarchy is removed from its
+ // parent. This will only be called by a RootView in a hierarchy of Widgets
+ // that this FocusManager is attached to the parent Widget of.
+ void ViewRemoved(View* parent, View* removed);
+
+ void AddKeystrokeListener(KeystrokeListener* listener);
+ void RemoveKeystrokeListener(KeystrokeListener* listener);
+
+ // 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.
+ // TODO(finnur): http://b/1307173 Make this private once the bug is fixed.
+ AcceleratorTarget* GetTargetForAccelerator(
+ const Accelerator& accelerator) const;
+
+ private:
+#if defined(OS_WIN)
+ explicit FocusManager(HWND root, RootView* root_view);
+#endif
+ ~FocusManager();
+
+ // Returns the next focusable view.
+ View* GetNextFocusableView(View* starting_view, bool reverse, bool dont_loop);
+
+ // Returns the last view of the focus traversal hierarchy.
+ View* FindLastFocusableView();
+
+ // 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,
+ bool dont_loop);
+
+ // The RootView of the window associated with this FocusManager.
+ RootView* top_root_view_;
+
+ // The view that currently is focused.
+ View* focused_view_;
+
+ // The storage id used in the ViewStorage to store/restore the view that last
+ // had focus.
+ int stored_focused_view_storage_id_;
+
+#if defined(OS_WIN)
+ // The window associated with this focus manager.
+ HWND root_;
+#endif
+
+ // Used to allow setting the focus on an HWND without changing the currently
+ // focused view.
+ bool ignore_set_focus_msg_;
+
+ // The accelerators and associated targets.
+ typedef std::map<Accelerator, AcceleratorTarget*> AcceleratorMap;
+ AcceleratorMap accelerators_;
+
+ // The list of registered keystroke listeners
+ typedef std::vector<KeystrokeListener*> KeystrokeListenerList;
+ KeystrokeListenerList keystroke_listeners_;
+
+ // The list of registered FocusChange listeners.
+ typedef std::vector<FocusChangeListener*> FocusChangeListenerList;
+ FocusChangeListenerList focus_change_listeners_;
+
+ DISALLOW_COPY_AND_ASSIGN(FocusManager);
+};
+
+} // namespace views
+
+#endif // VIEWS_FOCUS_FOCUS_MANAGER_H_
diff --git a/views/focus/focus_manager_unittest.cc b/views/focus/focus_manager_unittest.cc
new file mode 100644
index 0000000..234db1d
--- /dev/null
+++ b/views/focus/focus_manager_unittest.cc
@@ -0,0 +1,666 @@
+// Copyright (c) 2006-2008 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.
+
+// Disabled right now as this won't work on BuildBots right now as this test
+// require the box it runs on to be unlocked (and no screen-savers).
+// The test actually simulates mouse and key events, so if the screen is locked,
+// the events don't go to the Chrome window.
+#include "testing/gtest/include/gtest/gtest.h"
+
+#include "app/resource_bundle.h"
+#include "base/gfx/rect.h"
+#include "skia/include/SkColor.h"
+#include "views/background.h"
+#include "views/border.h"
+#include "views/controls/button/checkbox.h"
+#include "views/controls/button/native_button.h"
+#include "views/controls/button/radio_button.h"
+#include "views/controls/label.h"
+#include "views/controls/link.h"
+#include "views/controls/scroll_view.h"
+#include "views/controls/tabbed_pane.h"
+#include "views/controls/text_field.h"
+#include "views/widget/accelerator_handler.h"
+#include "views/widget/root_view.h"
+#include "views/widget/widget_win.h"
+#include "views/window/window.h"
+#include "views/window/window_delegate.h"
+
+
+
+namespace {
+
+static const int kWindowWidth = 600;
+static const int kWindowHeight = 500;
+
+static int count = 1;
+
+static const int kTopCheckBoxID = count++; // 1
+static const int kLeftContainerID = count++;
+static const int kAppleLabelID = count++;
+static const int kAppleTextFieldID = count++;
+static const int kOrangeLabelID = count++; // 5
+static const int kOrangeTextFieldID = count++;
+static const int kBananaLabelID = count++;
+static const int kBananaTextFieldID = count++;
+static const int kKiwiLabelID = count++;
+static const int kKiwiTextFieldID = count++; // 10
+static const int kFruitButtonID = count++;
+static const int kFruitCheckBoxID = count++;
+
+static const int kRightContainerID = count++;
+static const int kAsparagusButtonID = count++;
+static const int kBroccoliButtonID = count++; // 15
+static const int kCauliflowerButtonID = count++;
+
+static const int kInnerContainerID = count++;
+static const int kScrollViewID = count++;
+static const int kScrollContentViewID = count++;
+static const int kRosettaLinkID = count++; // 20
+static const int kStupeurEtTremblementLinkID = count++;
+static const int kDinerGameLinkID = count++;
+static const int kRidiculeLinkID = count++;
+static const int kClosetLinkID = count++;
+static const int kVisitingLinkID = count++; // 25
+static const int kAmelieLinkID = count++;
+static const int kJoyeuxNoelLinkID = count++;
+static const int kCampingLinkID = count++;
+static const int kBriceDeNiceLinkID = count++;
+static const int kTaxiLinkID = count++; // 30
+static const int kAsterixLinkID = count++;
+
+static const int kOKButtonID = count++;
+static const int kCancelButtonID = count++;
+static const int kHelpButtonID = count++;
+
+static const int kStyleContainerID = count++; // 35
+static const int kBoldCheckBoxID = count++;
+static const int kItalicCheckBoxID = count++;
+static const int kUnderlinedCheckBoxID = count++;
+
+static const int kSearchContainerID = count++;
+static const int kSearchTextFieldID = count++; // 40
+static const int kSearchButtonID = count++;
+static const int kHelpLinkID = count++;
+
+static const int kThumbnailContainerID = count++;
+static const int kThumbnailStarID = count++;
+static const int kThumbnailSuperStarID = count++;
+
+class FocusManagerTest;
+
+// BorderView is a NativeControl that creates a tab control as its child and
+// takes a View to add as the child of the tab control. The tab control is used
+// to give a nice background for the view. At some point we'll have a real
+// wrapper for TabControl, and this can be nuked in favor of it.
+// Taken from keyword_editor_view.cc.
+// It is interesting in our test as it is a native control containing another
+// RootView.
+class BorderView : public views::NativeControl {
+ public:
+ explicit BorderView(View* child) : child_(child) {
+ DCHECK(child);
+ SetFocusable(false);
+ }
+
+ virtual ~BorderView() {}
+
+ virtual HWND CreateNativeControl(HWND parent_container) {
+ // Create the tab control.
+ HWND tab_control = ::CreateWindowEx(GetAdditionalExStyle(),
+ WC_TABCONTROL,
+ L"",
+ WS_CHILD,
+ 0, 0, width(), height(),
+ parent_container, NULL, NULL, NULL);
+ // Create the view container which is a child of the TabControl.
+ widget_ = new views::WidgetWin();
+ widget_->Init(tab_control, gfx::Rect(), false);
+ widget_->SetContentsView(child_);
+ widget_->SetFocusTraversableParentView(this);
+ ResizeContents(tab_control);
+ return tab_control;
+ }
+
+ virtual LRESULT OnNotify(int w_param, LPNMHDR l_param) {
+ return 0;
+ }
+
+ virtual void Layout() {
+ NativeControl::Layout();
+ ResizeContents(GetNativeControlHWND());
+ }
+
+ virtual views::RootView* GetContentsRootView() {
+ return widget_->GetRootView();
+ }
+
+ virtual views::FocusTraversable* GetFocusTraversable() {
+ return widget_;
+ }
+
+ virtual void ViewHierarchyChanged(bool is_add, View *parent, View *child) {
+ NativeControl::ViewHierarchyChanged(is_add, parent, child);
+
+ if (child == this && is_add) {
+ // We have been added to a view hierarchy, update the FocusTraversable
+ // parent.
+ widget_->SetFocusTraversableParent(GetRootView());
+ }
+ }
+
+private:
+ void ResizeContents(HWND tab_control) {
+ DCHECK(tab_control);
+ CRect content_bounds;
+ if (!GetClientRect(tab_control, &content_bounds))
+ return;
+ TabCtrl_AdjustRect(tab_control, FALSE, &content_bounds);
+ widget_->MoveWindow(content_bounds.left, content_bounds.top,
+ content_bounds.Width(), content_bounds.Height(),
+ TRUE);
+ }
+
+ View* child_;
+ views::WidgetWin* widget_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(BorderView);
+};
+
+class TestViewWindow : public views::WidgetWin {
+ public:
+ explicit TestViewWindow(FocusManagerTest* test);
+ ~TestViewWindow() { }
+
+ void Init();
+
+ views::View* contents() const { return contents_; }
+
+
+ // Return the ID of the component that currently has the focus.
+ int GetFocusedComponentID();
+
+ // Simulate pressing the tab button in the window.
+ void PressTab(bool shift_pressed, bool ctrl_pressed);
+
+ views::RootView* GetContentsRootView() const {
+ return contents_->GetRootView();
+ }
+
+ views::RootView* GetStyleRootView() const {
+ return style_tab_->GetContentsRootView();
+ }
+
+ views::RootView* GetSearchRootView() const {
+ return search_border_view_->GetContentsRootView();
+ }
+
+ private:
+ views::View* contents_;
+
+ views::TabbedPane* style_tab_;
+ BorderView* search_border_view_;
+
+ FocusManagerTest* test_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(TestViewWindow);
+};
+
+class FocusManagerTest : public testing::Test {
+ public:
+ TestViewWindow* GetWindow();
+ ~FocusManagerTest();
+
+ protected:
+ FocusManagerTest();
+
+ virtual void SetUp();
+ virtual void TearDown();
+
+ MessageLoopForUI message_loop_;
+ TestViewWindow* test_window_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// TestViewWindow
+////////////////////////////////////////////////////////////////////////////////
+
+TestViewWindow::TestViewWindow(FocusManagerTest* test)
+ : test_(test),
+ contents_(NULL),
+ style_tab_(NULL),
+ search_border_view_(NULL) {
+}
+
+// Initializes and shows the window with the contents view.
+void TestViewWindow::Init() {
+ gfx::Rect bounds(0, 0, 600, 460);
+ contents_ = new views::View();
+ contents_->set_background(
+ views::Background::CreateSolidBackground(255, 255, 255));
+
+ WidgetWin::Init(NULL, bounds, true);
+ SetContentsView(contents_);
+
+ views::Checkbox* cb =
+ new views::Checkbox(L"This is a checkbox");
+ contents_->AddChildView(cb);
+ // In this fast paced world, who really has time for non hard-coded layout?
+ cb->SetBounds(10, 10, 200, 20);
+ cb->SetID(kTopCheckBoxID);
+
+ views::View* left_container = new views::View();
+ left_container->set_border(
+ views::Border::CreateSolidBorder(1, SK_ColorBLACK));
+ left_container->set_background(
+ views::Background::CreateSolidBackground(240, 240, 240));
+ left_container->SetID(kLeftContainerID);
+ contents_->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;
+
+ views::Label* label = new views::Label(L"Apple:");
+ label->SetID(kAppleLabelID);
+ left_container->AddChildView(label);
+ label->SetBounds(label_x, y, label_width, label_height);
+
+ views::TextField* text_field = new views::TextField();
+ text_field->SetID(kAppleTextFieldID);
+ left_container->AddChildView(text_field);
+ text_field->SetBounds(label_x + label_width + 5, y,
+ text_field_width, label_height);
+
+ y += label_height + gap_between_labels;
+
+ label = new views::Label(L"Orange:");
+ label->SetID(kOrangeLabelID);
+ left_container->AddChildView(label);
+ label->SetBounds(label_x, y, label_width, label_height);
+
+ text_field = new views::TextField();
+ text_field->SetID(kOrangeTextFieldID);
+ left_container->AddChildView(text_field);
+ text_field->SetBounds(label_x + label_width + 5, y,
+ text_field_width, label_height);
+
+ y += label_height + gap_between_labels;
+
+ label = new views::Label(L"Banana:");
+ label->SetID(kBananaLabelID);
+ left_container->AddChildView(label);
+ label->SetBounds(label_x, y, label_width, label_height);
+
+ text_field = new views::TextField();
+ text_field->SetID(kBananaTextFieldID);
+ left_container->AddChildView(text_field);
+ text_field->SetBounds(label_x + label_width + 5, y,
+ text_field_width, label_height);
+
+ y += label_height + gap_between_labels;
+
+ label = new views::Label(L"Kiwi:");
+ label->SetID(kKiwiLabelID);
+ left_container->AddChildView(label);
+ label->SetBounds(label_x, y, label_width, label_height);
+
+ text_field = new views::TextField();
+ text_field->SetID(kKiwiTextFieldID);
+ left_container->AddChildView(text_field);
+ text_field->SetBounds(label_x + label_width + 5, y,
+ text_field_width, label_height);
+
+ y += label_height + gap_between_labels;
+
+ views::NativeButton* button = new views::NativeButton(NULL, L"Click me");
+ button->SetBounds(label_x, y + 10, 50, 20);
+ button->SetID(kFruitButtonID);
+ left_container->AddChildView(button);
+ y += 40;
+
+ cb = new views::Checkbox(L"This is another check box");
+ cb->SetBounds(label_x + label_width + 5, y, 100, 20);
+ cb->SetID(kFruitCheckBoxID);
+ left_container->AddChildView(cb);
+
+ views::View* right_container = new views::View();
+ right_container->set_border(
+ views::Border::CreateSolidBorder(1, SK_ColorBLACK));
+ right_container->set_background(
+ views::Background::CreateSolidBackground(240, 240, 240));
+ right_container->SetID(kRightContainerID);
+ contents_->AddChildView(right_container);
+ right_container->SetBounds(270, 35, 300, 200);
+
+ y = 10;
+ int radio_button_height = 15;
+ int gap_between_radio_buttons = 10;
+ views::View* radio_button =
+ new views::RadioButton(L"Asparagus", 1);
+ radio_button->SetID(kAsparagusButtonID);
+ right_container->AddChildView(radio_button);
+ radio_button->SetBounds(5, y, 70, radio_button_height);
+ radio_button->SetGroup(1);
+ y += radio_button_height + gap_between_radio_buttons;
+ radio_button = new views::RadioButton(L"Broccoli", 1);
+ radio_button->SetID(kBroccoliButtonID);
+ right_container->AddChildView(radio_button);
+ radio_button->SetBounds(5, y, 70, radio_button_height);
+ radio_button->SetGroup(1);
+ y += radio_button_height + gap_between_radio_buttons;
+ radio_button = new views::RadioButton(L"Cauliflower", 1);
+ radio_button->SetID(kCauliflowerButtonID);
+ right_container->AddChildView(radio_button);
+ radio_button->SetBounds(5, y, 70, radio_button_height);
+ radio_button->SetGroup(1);
+ y += radio_button_height + gap_between_radio_buttons;
+
+ views::View* inner_container = new views::View();
+ inner_container->set_border(
+ views::Border::CreateSolidBorder(1, SK_ColorBLACK));
+ inner_container->set_background(
+ views::Background::CreateSolidBackground(230, 230, 230));
+ inner_container->SetID(kInnerContainerID);
+ right_container->AddChildView(inner_container);
+ inner_container->SetBounds(100, 10, 150, 180);
+
+ views::ScrollView* scroll_view = new views::ScrollView();
+ scroll_view->SetID(kScrollViewID);
+ inner_container->AddChildView(scroll_view);
+ scroll_view->SetBounds(1, 1, 148, 178);
+
+ views::View* scroll_content = new views::View();
+ scroll_content->SetBounds(0, 0, 200, 200);
+ scroll_content->set_background(
+ views::Background::CreateSolidBackground(200, 200, 200));
+ scroll_view->SetContents(scroll_content);
+
+ static const wchar_t* const kTitles[] = {
+ L"Rosetta", L"Stupeur et tremblement", L"The diner game",
+ L"Ridicule", L"Le placard", L"Les Visiteurs", L"Amelie",
+ L"Joyeux Noel", L"Camping", L"Brice de Nice",
+ L"Taxi", L"Asterix"
+ };
+
+ static const int kIDs[] = {
+ kRosettaLinkID, kStupeurEtTremblementLinkID, kDinerGameLinkID,
+ kRidiculeLinkID, kClosetLinkID, kVisitingLinkID, kAmelieLinkID,
+ kJoyeuxNoelLinkID, kCampingLinkID, kBriceDeNiceLinkID,
+ kTaxiLinkID, kAsterixLinkID
+ };
+
+ DCHECK(arraysize(kTitles) == arraysize(kIDs));
+
+ y = 5;
+ for (int i = 0; i < arraysize(kTitles); ++i) {
+ views::Link* link = new views::Link(kTitles[i]);
+ link->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
+ link->SetID(kIDs[i]);
+ scroll_content->AddChildView(link);
+ link->SetBounds(5, y, 300, 15);
+ y += 15;
+ }
+
+ y = 250;
+ int width = 50;
+ button = new views::NativeButton(NULL, L"OK");
+ button->SetID(kOKButtonID);
+
+ contents_->AddChildView(button);
+ button->SetBounds(150, y, width, 20);
+
+ button = new views::NativeButton(NULL, L"Cancel");
+ button->SetID(kCancelButtonID);
+ contents_->AddChildView(button);
+ button->SetBounds(250, y, width, 20);
+
+ button = new views::NativeButton(NULL, L"Help");
+ button->SetID(kHelpButtonID);
+ contents_->AddChildView(button);
+ button->SetBounds(350, y, width, 20);
+
+ y += 40;
+
+ // Left bottom box with style checkboxes.
+ views::View* contents = new views::View();
+ contents->set_background(
+ views::Background::CreateSolidBackground(SK_ColorWHITE));
+ cb = new views::Checkbox(L"Bold");
+ contents->AddChildView(cb);
+ cb->SetBounds(10, 10, 50, 20);
+ cb->SetID(kBoldCheckBoxID);
+
+ cb = new views::Checkbox(L"Italic");
+ contents->AddChildView(cb);
+ cb->SetBounds(70, 10, 50, 20);
+ cb->SetID(kItalicCheckBoxID);
+
+ cb = new views::Checkbox(L"Underlined");
+ contents->AddChildView(cb);
+ cb->SetBounds(130, 10, 70, 20);
+ cb->SetID(kUnderlinedCheckBoxID);
+
+ style_tab_ = new views::TabbedPane();
+ style_tab_->SetID(kStyleContainerID);
+ contents_->AddChildView(style_tab_);
+ style_tab_->SetBounds(10, y, 210, 50);
+ style_tab_->AddTab(L"Style", contents);
+ style_tab_->AddTab(L"Other", new views::View());
+
+ // Right bottom box with search.
+ contents = new views::View();
+ contents->set_background(
+ views::Background::CreateSolidBackground(SK_ColorWHITE));
+ text_field = new views::TextField();
+ contents->AddChildView(text_field);
+ text_field->SetBounds(10, 10, 100, 20);
+ text_field->SetID(kSearchTextFieldID);
+
+ button = new views::NativeButton(NULL, L"Search");
+ contents->AddChildView(button);
+ button->SetBounds(115, 10, 50, 20);
+ button->SetID(kSearchButtonID);
+
+ views::Link* link = new views::Link(L"Help");
+ link->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
+ link->SetID(kHelpLinkID);
+ contents->AddChildView(link);
+ link->SetBounds(170, 10, 30, 15);
+
+ search_border_view_ = new BorderView(contents);
+ search_border_view_->SetID(kSearchContainerID);
+
+ contents_->AddChildView(search_border_view_);
+ search_border_view_->SetBounds(300, y, 200, 50);
+
+ y += 60;
+
+ contents = new views::View();
+ contents->SetFocusable(true);
+ contents->set_background(
+ views::Background::CreateSolidBackground(SK_ColorBLUE));
+ contents->SetID(kThumbnailContainerID);
+ button = new views::NativeButton(NULL, L"Star");
+ contents->AddChildView(button);
+ button->SetBounds(5, 5, 50, 20);
+ button->SetID(kThumbnailStarID);
+ button = new views::NativeButton(NULL, L"SuperStar");
+ contents->AddChildView(button);
+ button->SetBounds(60, 5, 100, 20);
+ button->SetID(kThumbnailSuperStarID);
+
+ contents_->AddChildView(contents);
+ contents->SetBounds(200, y, 200, 50);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// FocusManagerTest
+////////////////////////////////////////////////////////////////////////////////
+
+FocusManagerTest::FocusManagerTest() {
+}
+
+FocusManagerTest::~FocusManagerTest() {
+}
+
+TestViewWindow* FocusManagerTest::GetWindow() {
+ return test_window_;
+}
+
+void FocusManagerTest::SetUp() {
+ OleInitialize(NULL);
+ test_window_ = new TestViewWindow(this);
+ test_window_->Init();
+ ShowWindow(test_window_->GetNativeView(), SW_SHOW);
+}
+
+void FocusManagerTest::TearDown() {
+ test_window_->CloseNow();
+
+ // Flush the message loop to make Purify happy.
+ message_loop_.RunAllPending();
+ OleUninitialize();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// The tests
+////////////////////////////////////////////////////////////////////////////////
+
+
+TEST_F(FocusManagerTest, NormalTraversal) {
+ const int kTraversalIDs[] = { kTopCheckBoxID, kAppleTextFieldID,
+ kOrangeTextFieldID, kBananaTextFieldID, kKiwiTextFieldID,
+ kFruitButtonID, kFruitCheckBoxID, kAsparagusButtonID, kRosettaLinkID,
+ kStupeurEtTremblementLinkID,
+ kDinerGameLinkID, kRidiculeLinkID, kClosetLinkID, kVisitingLinkID,
+ kAmelieLinkID, kJoyeuxNoelLinkID, kCampingLinkID, kBriceDeNiceLinkID,
+ kTaxiLinkID, kAsterixLinkID, kOKButtonID, kCancelButtonID, kHelpButtonID,
+ kStyleContainerID, kBoldCheckBoxID, kItalicCheckBoxID,
+ kUnderlinedCheckBoxID, kSearchTextFieldID, kSearchButtonID, kHelpLinkID,
+ kThumbnailContainerID, kThumbnailStarID, kThumbnailSuperStarID };
+
+ // Uncomment the following line if you want to test manually the UI of this
+ // test.
+ // MessageLoop::current()->Run(new views::AcceleratorHandler());
+
+ views::FocusManager* focus_manager =
+ views::FocusManager::GetFocusManager(test_window_->GetNativeView());
+ // Let's traverse the whole focus hierarchy (several times, to make sure it
+ // loops OK).
+ focus_manager->SetFocusedView(NULL);
+ for (int i = 0; i < 3; ++i) {
+ for (int j = 0; j < arraysize(kTraversalIDs); j++) {
+ focus_manager->AdvanceFocus(false);
+ views::View* focused_view = focus_manager->GetFocusedView();
+ EXPECT_TRUE(focused_view != NULL);
+ if (focused_view)
+ EXPECT_EQ(kTraversalIDs[j], focused_view->GetID());
+ }
+ }
+
+ // Focus the 1st item.
+ views::RootView* root_view = test_window_->GetContentsRootView();
+ focus_manager->SetFocusedView(root_view->GetViewByID(kTraversalIDs[0]));
+
+ /* BROKEN because of bug #1153276. The reverse order of traversal in Tabbed
+ Panes is broken (we go to the tab before going to the content
+ // Let's traverse in reverse order.
+ for (int i = 0; i < 3; ++i) {
+ for (int j = arraysize(kTraversalIDs) - 1; j >= 0; --j) {
+ focus_manager->AdvanceFocus(true);
+ views::View* focused_view = focus_manager->GetFocusedView();
+ EXPECT_TRUE(focused_view != NULL);
+ if (focused_view)
+ EXPECT_EQ(kTraversalIDs[j], focused_view->GetID());
+ }
+ }
+ */
+}
+
+TEST_F(FocusManagerTest, TraversalWithNonEnabledViews) {
+ const int kMainContentsDisabledIDs[] = {
+ kBananaTextFieldID, kFruitCheckBoxID, kAsparagusButtonID,
+ kCauliflowerButtonID, kClosetLinkID, kVisitingLinkID, kBriceDeNiceLinkID,
+ kTaxiLinkID, kAsterixLinkID, kHelpButtonID };
+
+ const int kStyleContentsDisabledIDs[] = { kBoldCheckBoxID };
+
+ const int kSearchContentsDisabledIDs[] = { kSearchTextFieldID, kHelpLinkID };
+
+ const int kTraversalIDs[] = { kTopCheckBoxID, kAppleTextFieldID,
+ kOrangeTextFieldID, kKiwiTextFieldID, kFruitButtonID, kBroccoliButtonID,
+ kRosettaLinkID, kStupeurEtTremblementLinkID, kDinerGameLinkID,
+ kRidiculeLinkID, kAmelieLinkID, kJoyeuxNoelLinkID, kCampingLinkID,
+ kOKButtonID, kCancelButtonID, kStyleContainerID,
+ kItalicCheckBoxID, kUnderlinedCheckBoxID, kSearchButtonID,
+ kThumbnailContainerID, kThumbnailStarID, kThumbnailSuperStarID };
+
+ // Let's disable some views.
+ views::RootView* root_view = test_window_->GetContentsRootView();
+ for (int i = 0; i < arraysize(kMainContentsDisabledIDs); i++) {
+ views::View* v = root_view->GetViewByID(kMainContentsDisabledIDs[i]);
+ ASSERT_TRUE(v != NULL);
+ if (v)
+ v->SetEnabled(false);
+ }
+ root_view = test_window_->GetStyleRootView();
+ for (int i = 0; i < arraysize(kStyleContentsDisabledIDs); i++) {
+ views::View* v = root_view->GetViewByID(kStyleContentsDisabledIDs[i]);
+ ASSERT_TRUE(v != NULL);
+ if (v)
+ v->SetEnabled(false);
+ }
+ root_view = test_window_->GetSearchRootView();
+ for (int i = 0; i < arraysize(kSearchContentsDisabledIDs); i++) {
+ views::View* v =
+ root_view->GetViewByID(kSearchContentsDisabledIDs[i]);
+ ASSERT_TRUE(v != NULL);
+ if (v)
+ v->SetEnabled(false);
+ }
+
+
+ views::FocusManager* focus_manager =
+ views::FocusManager::GetFocusManager(test_window_->GetNativeView());
+ views::View* focused_view;
+ // Let's do one traversal (several times, to make sure it loops ok).
+ for (int i = 0; i < 3;++i) {
+ for (int j = 0; j < arraysize(kTraversalIDs); j++) {
+ focus_manager->AdvanceFocus(false);
+ focused_view = focus_manager->GetFocusedView();
+ EXPECT_TRUE(focused_view != NULL);
+ if (focused_view)
+ EXPECT_EQ(kTraversalIDs[j], focused_view->GetID());
+ }
+ }
+
+ // Focus the 1st item.
+ focus_manager->AdvanceFocus(false);
+ focused_view = focus_manager->GetFocusedView();
+ EXPECT_TRUE(focused_view != NULL);
+ if (focused_view)
+ EXPECT_EQ(kTraversalIDs[0], focused_view->GetID());
+
+ // Same thing in reverse.
+ /* BROKEN because of bug #1153276. The reverse order of traversal in Tabbed
+ Panes is broken (we go to the tab before going to the content
+
+ for (int i = 0; i < 3; ++i) {
+ for (int j = arraysize(kTraversalIDs) - 1; j >= 0; --j) {
+ focus_manager->AdvanceFocus(true);
+ focused_view = focus_manager->GetFocusedView();
+ EXPECT_TRUE(focused_view != NULL);
+ if (focused_view)
+ EXPECT_EQ(kTraversalIDs[j], focused_view->GetID());
+ }
+ }
+ */
+}
+
+}
diff --git a/views/focus/focus_util_win.cc b/views/focus/focus_util_win.cc
new file mode 100644
index 0000000..046df70
--- /dev/null
+++ b/views/focus/focus_util_win.cc
@@ -0,0 +1,118 @@
+// Copyright (c) 2009 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_util_win.h"
+
+#include <windowsx.h>
+
+#include "base/win_util.h"
+
+namespace views {
+
+// Property used to indicate the HWND supports having mouse wheel messages
+// rerouted to it.
+static const wchar_t* const kHWNDSupportMouseWheelRerouting =
+ L"__HWND_MW_REROUTE_OK";
+
+static bool WindowSupportsRerouteMouseWheel(HWND window) {
+ while (GetWindowLong(window, GWL_STYLE) & WS_CHILD) {
+ if (!IsWindow(window))
+ break;
+
+ if (reinterpret_cast<bool>(GetProp(window,
+ kHWNDSupportMouseWheelRerouting))) {
+ return true;
+ }
+ window = GetParent(window);
+ }
+ return false;
+}
+
+static bool IsCompatibleWithMouseWheelRedirection(HWND window) {
+ std::wstring class_name = win_util::GetClassName(window);
+ // Mousewheel redirection to comboboxes is a surprising and
+ // undesireable user behavior.
+ return !(class_name == L"ComboBox" ||
+ class_name == L"ComboBoxEx32");
+}
+
+static bool CanRedirectMouseWheelFrom(HWND window) {
+ std::wstring class_name = win_util::GetClassName(window);
+
+ // Older Thinkpad mouse wheel drivers create a window under mouse wheel
+ // pointer. Detect if we are dealing with this window. In this case we
+ // don't need to do anything as the Thinkpad mouse driver will send
+ // mouse wheel messages to the right window.
+ if ((class_name == L"Syn Visual Class") ||
+ (class_name == L"SynTrackCursorWindowClass"))
+ return false;
+
+ return true;
+}
+
+void SetWindowSupportsRerouteMouseWheel(HWND hwnd) {
+ SetProp(hwnd, kHWNDSupportMouseWheelRerouting,
+ reinterpret_cast<HANDLE>(true));
+}
+
+bool RerouteMouseWheel(HWND window, WPARAM w_param, LPARAM l_param) {
+ // Since this is called from a subclass for every window, we can get
+ // here recursively. This will happen if, for example, a control
+ // reflects wheel scroll messages to its parent. Bail out if we got
+ // here recursively.
+ static bool recursion_break = false;
+ if (recursion_break)
+ return false;
+ // Check if this window's class has a bad interaction with rerouting.
+ if (!IsCompatibleWithMouseWheelRedirection(window))
+ return false;
+
+ DWORD current_process = GetCurrentProcessId();
+ POINT wheel_location = { GET_X_LPARAM(l_param), GET_Y_LPARAM(l_param) };
+ HWND window_under_wheel = WindowFromPoint(wheel_location);
+
+ if (!CanRedirectMouseWheelFrom(window_under_wheel))
+ return false;
+
+ // Find the lowest Chrome window in the hierarchy that can be the
+ // target of mouse wheel redirection.
+ while (window != window_under_wheel) {
+ // If window_under_wheel is not a valid Chrome window, then return true to
+ // suppress further processing of the message.
+ if (!::IsWindow(window_under_wheel))
+ return true;
+ DWORD wheel_window_process = 0;
+ GetWindowThreadProcessId(window_under_wheel, &wheel_window_process);
+ if (current_process != wheel_window_process) {
+ if (IsChild(window, window_under_wheel)) {
+ // If this message is reflected from a child window in a different
+ // process (happens with out of process windowed plugins) then
+ // we don't want to reroute the wheel message.
+ return false;
+ } else {
+ // The wheel is scrolling over an unrelated window. Make sure that we
+ // have marked that window as supporting mouse wheel rerouting.
+ // Otherwise, we cannot send random WM_MOUSEWHEEL messages to arbitrary
+ // windows. So just drop the message.
+ if (!WindowSupportsRerouteMouseWheel(window_under_wheel))
+ return true;
+ }
+ }
+
+ // window_under_wheel is a Chrome window. If allowed, redirect.
+ if (IsCompatibleWithMouseWheelRedirection(window_under_wheel)) {
+ recursion_break = true;
+ SendMessage(window_under_wheel, WM_MOUSEWHEEL, w_param, l_param);
+ recursion_break = false;
+ return true;
+ }
+ // If redirection is disallowed, try the parent.
+ window_under_wheel = GetAncestor(window_under_wheel, GA_PARENT);
+ }
+ // If we traversed back to the starting point, we should process
+ // this message normally; return false.
+ return false;
+}
+
+} // namespace views
diff --git a/views/focus/focus_util_win.h b/views/focus/focus_util_win.h
new file mode 100644
index 0000000..832a6df
--- /dev/null
+++ b/views/focus/focus_util_win.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2009 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 VIEWS_FOCUS_FOCUS_UTIL_WIN_H_
+#define VIEWS_FOCUS_FOCUS_UTIL_WIN_H_
+
+#include <windows.h>
+
+namespace views {
+
+// Marks the passed |hwnd| as supporting mouse-wheel message rerouting.
+// We reroute the mouse wheel messages to such HWND when they are under the
+// mouse pointer (but are not the active window)
+void SetWindowSupportsRerouteMouseWheel(HWND hwnd);
+
+// Forwards mouse wheel messages to the window under it.
+// Windows sends mouse wheel messages to the currently active window.
+// This causes a window to scroll even if it is not currently under the mouse
+// wheel. The following code gives mouse wheel messages to the window under the
+// mouse wheel in order to scroll that window. This is arguably a better user
+// experience. The returns value says whether the mouse wheel message was
+// successfully redirected.
+bool RerouteMouseWheel(HWND window, WPARAM w_param, LPARAM l_param);
+
+} // namespace views
+
+#endif // VIEWS_FOCUS_FOCUS_UTIL_WIN_H_
diff --git a/views/focus/view_storage.cc b/views/focus/view_storage.cc
new file mode 100644
index 0000000..a108530
--- /dev/null
+++ b/views/focus/view_storage.cc
@@ -0,0 +1,182 @@
+// Copyright (c) 2006-2008 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-inl.h"
+
+namespace views {
+
+// This struct contains the information to locate a specific view.
+// Locating a view is not always straight-forward as a view can be a floating
+// view, or the child of a floating view. Floating views are frequently deleted
+// and recreated (typically when their container is laid out).
+struct ViewLocationInfo {
+ // True if the view is floating or the child of a floating view. False
+ // otherwise.
+ bool is_floating_view;
+
+ // If non floating, this is the stored view. If floating, the parent of the
+ // floating view.
+ View* view;
+
+ // The id of the floating view.
+ int floating_view_id;
+
+ // The path from the floating view to the stored view. The path is composed
+ // of the indexes of the views in the hierarchy.
+ std::vector<int> floating_view_to_view_path;
+};
+
+// static
+ViewStorage* ViewStorage::GetSharedInstance() {
+ return Singleton<ViewStorage>::get();
+}
+
+ViewStorage::ViewStorage() : view_storage_next_id_(0) {
+}
+
+ViewStorage::~ViewStorage() {
+ STLDeleteContainerPairSecondPointers(id_to_view_location_.begin(),
+ id_to_view_location_.end());
+
+ 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, ViewLocationInfo*>::iterator iter =
+ id_to_view_location_.find(storage_id);
+ DCHECK(iter == id_to_view_location_.end());
+
+ if (iter != id_to_view_location_.end())
+ RemoveView(storage_id);
+
+ ViewLocationInfo* view_location_info = new ViewLocationInfo();
+ View* floating_view_parent = view->RetrieveFloatingViewParent();
+ if (floating_view_parent) {
+ // The view is a floating view or is a child of a floating view.
+ view_location_info->is_floating_view = true;
+ view_location_info->view = floating_view_parent->GetParent();
+ view_location_info->floating_view_id =
+ floating_view_parent->GetFloatingViewID();
+ // Ley's store the path from the floating view to the actual view so we can
+ // locate it when restoring.
+ View::GetViewPath(floating_view_parent, view,
+ &(view_location_info->floating_view_to_view_path));
+ } else {
+ // It is a non floating view, it can be stored as is.
+ view_location_info->is_floating_view = false;
+ view_location_info->view = view;
+ }
+ id_to_view_location_[storage_id] = view_location_info;
+
+ std::vector<int>* ids = NULL;
+ std::map<View*, std::vector<int>*>::iterator id_iter =
+ view_to_ids_.find(view_location_info->view);
+ if (id_iter == view_to_ids_.end()) {
+ ids = new std::vector<int>();
+ view_to_ids_[view_location_info->view] = ids;
+ } else {
+ ids = id_iter->second;
+ }
+ ids->push_back(storage_id);
+}
+
+View* ViewStorage::RetrieveView(int storage_id) {
+ std::map<int, ViewLocationInfo*>::iterator iter =
+ id_to_view_location_.find(storage_id);
+ if (iter == id_to_view_location_.end())
+ return NULL;
+
+ ViewLocationInfo* view_location_info = iter->second;
+ if (view_location_info->is_floating_view) {
+ View* floating_view = view_location_info->view->
+ RetrieveFloatingViewForID(view_location_info->floating_view_id);
+ View* v = NULL;
+ if (floating_view) {
+ v = View::GetViewForPath(floating_view,
+ view_location_info->floating_view_to_view_path);
+ }
+ if (!v) {
+ // If we have not found the view, it means either the floating view with
+ // the id we have is gone, or it has changed and the actual child view
+ // we have the path for is not accessible. In that case, let's make sure
+ // we don't leak the ViewLocationInfo.
+ RemoveView(storage_id);
+ }
+ return v;
+ } else {
+ return view_location_info->view;
+ }
+}
+
+void ViewStorage::RemoveView(int storage_id) {
+ EraseView(storage_id, false);
+}
+
+void ViewStorage::ViewRemoved(View* parent, View* removed) {
+ // Let's first retrieve the ids for that view.
+ std::map<View*, std::vector<int>*>::iterator ids_iter =
+ view_to_ids_.find(removed);
+
+ if (ids_iter == view_to_ids_.end()) {
+ // That view is not in the view storage.
+ return;
+ }
+
+ std::vector<int>* ids = ids_iter->second;
+ DCHECK(!ids->empty());
+ EraseView((*ids)[0], true);
+}
+
+void ViewStorage::EraseView(int storage_id, bool remove_all_ids) {
+ // Remove the view from id_to_view_location_.
+ std::map<int, ViewLocationInfo*>::iterator location_iter =
+ id_to_view_location_.find(storage_id);
+ if (location_iter == id_to_view_location_.end())
+ return;
+
+ ViewLocationInfo* view_location = location_iter->second;
+ View* view = view_location->view;
+ delete view_location;
+ id_to_view_location_.erase(location_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) {
+ location_iter = id_to_view_location_.find((*ids)[i]);
+ if (location_iter != id_to_view_location_.end()) {
+ delete location_iter->second;
+ id_to_view_location_.erase(location_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/views/focus/view_storage.h b/views/focus/view_storage.h
new file mode 100644
index 0000000..9182429
--- /dev/null
+++ b/views/focus/view_storage.h
@@ -0,0 +1,78 @@
+// Copyright (c) 2006-2008 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 VIEWS_FOCUS_VIEW_STORAGE_H_
+#define VIEWS_FOCUS_VIEW_STORAGE_H_
+
+#include "base/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 supports floating views, meaning
+// that when you store a view, it can be retrieved even if it is a floating
+// view or the child of a floating view that has been detached since the view
+// was stored (in which case the floating view is recreated and reattached).
+// It also automatically removes a view from the storage if the view is removed
+// from the tree hierarchy (or in the case of a floating view, if the view
+// containing the floating view is removed).
+//
+// To use it, you first need to create a view storage id that can then be used
+// to store/retrieve views.
+
+namespace views {
+
+struct ViewLocationInfo;
+
+class ViewStorage {
+ public:
+ // Returns the global ViewStorage instance.
+ // It is guaranted to be non NULL.
+ static ViewStorage* GetSharedInstance();
+
+ // Returns a unique storage id that can be used to store/retrieve views.
+ int CreateStorageID();
+
+ // Associates |view| with the specified |storage_id|.
+ void StoreView(int storage_id, View* view);
+
+ // Returns the view associated with |storage_id| if any, NULL otherwise.
+ View* RetrieveView(int storage_id);
+
+ // Removes the view associated with |storage_id| if any.
+ void RemoveView(int storage_id);
+
+ // Notifies the ViewStorage that a view was removed from its parent somewhere.
+ void ViewRemoved(View* parent, View* removed);
+
+#ifdef UNIT_TEST
+ size_t view_count() const { return view_to_ids_.size(); }
+#endif
+
+ private:
+ friend struct DefaultSingletonTraits<ViewStorage>;
+
+ ViewStorage();
+ ~ViewStorage();
+
+ // Removes the view associated with |storage_id|. If |remove_all_ids| is true,
+ // all other mapping pointing to the same view are removed as well.
+ void EraseView(int storage_id, bool remove_all_ids);
+
+ // Next id for the view storage.
+ int view_storage_next_id_;
+
+ // The association id to View used for the view storage. The ViewStorage owns
+ // the ViewLocationInfo.
+ std::map<int, ViewLocationInfo*> id_to_view_location_;
+
+ // 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 // #ifndef VIEWS_FOCUS_VIEW_STORAGE_H_