summaryrefslogtreecommitdiffstats
path: root/chrome/views/focus_manager.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/views/focus_manager.cc')
-rw-r--r--chrome/views/focus_manager.cc794
1 files changed, 794 insertions, 0 deletions
diff --git a/chrome/views/focus_manager.cc b/chrome/views/focus_manager.cc
new file mode 100644
index 0000000..cfad627
--- /dev/null
+++ b/chrome/views/focus_manager.cc
@@ -0,0 +1,794 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/win_util.h"
+#include "chrome/browser/render_widget_host_hwnd.h"
+#include "chrome/common/notification_types.h"
+#include "chrome/views/accelerator.h"
+#include "chrome/views/focus_manager.h"
+#include "chrome/views/root_view.h"
+#include "chrome/views/view.h"
+#include "chrome/views/view_container.h"
+#include "chrome/views/view_storage.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__";
+
+namespace ChromeViews {
+
+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")
+ return false;
+
+ return true;
+}
+
+bool IsPluginWindow(HWND window) {
+ HWND current_window = window;
+ while (GetWindowLong(current_window, GWL_STYLE) & WS_CHILD) {
+ current_window = GetParent(current_window);
+ if (!IsWindow(current_window))
+ break;
+
+ std::wstring class_name = win_util::GetClassName(current_window);
+ if (class_name == kRenderWidgetHostHWNDClass)
+ return true;
+ }
+
+ return false;
+}
+
+// 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.
+static bool RerouteMouseWheel(HWND window, WPARAM wParam, LPARAM lParam) {
+ // 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(lParam), GET_Y_LPARAM(lParam) };
+ 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. If that window
+ // is a plugin window in a different chrome then we can send it a
+ // WM_MOUSEWHEEL. Otherwise, we cannot send random WM_MOUSEWHEEL
+ // messages to arbitrary windows. So just drop the message.
+ if (!IsPluginWindow(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, wParam, lParam);
+ 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;
+}
+
+// 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;
+ }
+ case WM_MOUSEWHEEL:
+ if (RerouteMouseWheel(window, wParam, lParam))
+ return 0;
+ break;
+ 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);
+
+ // We register for view removed notifications so we can make sure we don't
+ // keep references to invalidated views.
+ NotificationService::current()->AddObserver(
+ focus_manager, NOTIFY_VIEW_REMOVED, NotificationService::AllSources());
+
+ return focus_manager;
+}
+
+// static
+void FocusManager::InstallFocusSubclass(HWND window, View* view) {
+ DCHECK(window);
+ win_util::Subclass(window, &FocusWindowCallback);
+ if (view)
+ SetProp(window, kViewKey, view);
+}
+
+void FocusManager::UninstallFocusSubclass(HWND window) {
+ DCHECK(window);
+ if (win_util::Unsubclass(window, &FocusWindowCallback))
+ RemoveProp(window, kViewKey);
+}
+
+// static
+FocusManager* FocusManager::GetFocusManager(HWND window) {
+ DCHECK(window);
+ FocusManager* focus_manager =
+ reinterpret_cast<FocusManager*>(GetProp(window, kFocusManagerKey));
+ HWND parent = GetParent(window);
+ while (!focus_manager && parent) {
+ focus_manager =
+ reinterpret_cast<FocusManager*>(GetProp(parent, kFocusManagerKey));
+ parent = GetParent(parent);
+ }
+ 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));
+ // Unregister notifications.
+ NotificationService::current()->RemoveObserver(
+ this, NOTIFY_VIEW_REMOVED, NotificationService::AllSources());
+
+ // 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));
+ // First give the registered keystoke 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]->ProcessKeyDown(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(ChromeViews::KeyEvent(
+ ChromeViews::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.
+ int modifiers = 0;
+ if (win_util::IsShiftPressed())
+ modifiers |= Event::EF_SHIFT_DOWN;
+ if (win_util::IsCtrlPressed())
+ modifiers |= Event::EF_CONTROL_DOWN;
+ if (win_util::IsAltPressed())
+ modifiers |= Event::EF_ALT_DOWN;
+ Accelerator accelerator(Accelerator(static_cast<int>(virtual_key_code),
+ win_util::IsShiftPressed(),
+ win_util::IsCtrlPressed(),
+ win_util::IsAltPressed()));
+ if (ProcessAccelerator(accelerator, true)) {
+ // If a shortcut was activated for this keydown message, do not
+ // propagate the message further.
+ 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;
+
+ ViewContainer* view_container = root_view->GetViewContainer();
+ if (!view_container)
+ return false;
+
+ HWND window = view_container->GetHWND();
+ while (window) {
+ if (window == root_)
+ return true;
+ window = ::GetParent(window);
+ }
+ return false;
+}
+
+void FocusManager::AdvanceFocus(bool reverse) {
+ View* v = GetNextFocusableView(focused_view_, reverse, false);
+ if (v && (v != focused_view_)) {
+ 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 HWNDViewContainers 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() {
+ // For now we'll just walk the entire focus loop until we reach the end.
+
+ // Let's start at whatever focused view we are at.
+ View* starting_view = focused_view_;
+
+ // Now advance until you reach the end.
+ View* new_focused = NULL;
+ View* last_focused = NULL;
+ while (new_focused = GetNextFocusableView(starting_view, false, true)) {
+ last_focused = new_focused;
+ starting_view = 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();
+
+ // TODO (jcampan): when a WebContents 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();
+
+ 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::GetSharedInstance()->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::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,
+ bool prioritary_accelerators_only) {
+ if (!prioritary_accelerators_only ||
+ accelerator.IsCtrlDown() || accelerator.IsAltDown() ||
+ accelerator.GetKeyCode() == VK_ESCAPE ||
+ accelerator.GetKeyCode() == VK_RETURN ||
+ (accelerator.GetKeyCode() >= VK_F1 &&
+ accelerator.GetKeyCode() <= VK_F24) ||
+ (accelerator.GetKeyCode() >= VK_BROWSER_BACK &&
+ accelerator.GetKeyCode() <= VK_BROWSER_HOME)) {
+ 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::Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ DCHECK(type == NOTIFY_VIEW_REMOVED);
+ if (focused_view_ &&
+ Source<ChromeViews::View>(focused_view_) == source)
+ 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);
+}
+
+}