diff options
author | ben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-05-08 00:34:05 +0000 |
---|---|---|
committer | ben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-05-08 00:34:05 +0000 |
commit | 2362e4fe2905ab75d3230ebc3e307ae53e2b8362 (patch) | |
tree | e6d88357a2021811e0e354f618247217be8bb3da /views/focus | |
parent | db23ac3e713dc17509b2b15d3ee634968da45715 (diff) | |
download | chromium_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.cc | 65 | ||||
-rw-r--r-- | views/focus/external_focus_tracker.h | 76 | ||||
-rw-r--r-- | views/focus/focus_manager.cc | 716 | ||||
-rw-r--r-- | views/focus/focus_manager.h | 343 | ||||
-rw-r--r-- | views/focus/focus_manager_unittest.cc | 666 | ||||
-rw-r--r-- | views/focus/focus_util_win.cc | 118 | ||||
-rw-r--r-- | views/focus/focus_util_win.h | 28 | ||||
-rw-r--r-- | views/focus/view_storage.cc | 182 | ||||
-rw-r--r-- | views/focus/view_storage.h | 78 |
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_ |